leerness 1.36.70 → 1.36.72

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.72 — 2026-07-24 — 성장 한계 클래스 4(task list) + F16 lint 거짓 PASS 차단 — R-0001 검수 15회전
4
+
5
+ - **task list --json 상한**: 기본 최신 100행 + `shown/truncated` 정직 표기(`total` 은 필터 후 전량), `--all/--limit N` 옵트인. MCP `leerness_task_list` 계약 동반 갱신(limit/all 노출+전달). 텍스트 모드 무변경(codex: HEAD 와 바이트 동일 확인).
6
+ - **F16 (8차 헌트 이연분)**: lint 가 존재하지 않는 루트/빈 루트에서 `✓ 0 js clean` **거짓 PASS** — "아무것도 검사하지 않았다"는 명시 실패(exit 1)로 전환. 검증 도구가 무검사 통과 도장을 찍는 것은 핵심 철학 위반.
7
+ - (검수 Low) README/내장 문서의 "전체 JSON 조회" 낡은 계약 서술 정합.
8
+ - codex 실측: status 필터×캡 상호작용, 무효 limit 폴백, --all 우선, 최신 유지, 소비자 부재(tasks.length===total 가정 없음), lint 4개 부정 경로(missing/non-dir/empty/src-only) 전부 exit 1.
9
+ - 검증: selftest 334, e2e +1. 8차 헌트 잔여는 이제 설계 판단 2건(F9/F12)만.
10
+
11
+ ## 1.36.71 — 2026-07-24 — 성장 한계 클래스 3: retro/insights --json rows 상한 + 워크스페이스 --days 기존 버그 — R-0001 검수 14회전
12
+
13
+ - **retro/insights --json 의 data.rows 전량 임베드**(무한 성장, 72 task 에 22KB)를 기본 최신 30행으로 상한 — `rowsTotal/rowsShown/rowsTruncated` 정직 표기, `--all/--limit N` 옵트인, **집계 수치(totalTasks 등)는 전량 기준 그대로**(수치 왜곡 없음). 4개 JSON 배출 지점(단일/워크스페이스 × retro/insights) 일괄. 실측 33KB → 14KB.
14
+ - (검수 High) e2e 가 init 기본 T-0001 을 몰라 하드코딩 35 로 확정 실패할 케이스 — 기준선 유도 방식 + init/add exit 단언으로 교정(--force 가 init 실패를 가리던 문제도 제거).
15
+ - (검수 Medium) MCP `leerness_retro` 에 `limit`/`all` 노출+전달 (whats-new 1.36.69 와 동일 계약 규율). insights 는 MCP 도구 자체가 없음(현행 유지).
16
+ - (검수 Medium, **기존 버그**) 워크스페이스 retro(`--include/--all-apps`)가 `--days` 를 조용히 무시 — cutoff 를 `_retroWorkspace` 로 전달해 단일 모드와 일관(실측: days 0→fix 0 / 36500→fix 3 분리 확인).
17
+ - (검수 Low) help 에 `[--limit N|--all]` + rowsTotal 의미(수명 전체, --days 창 아님) 명시.
18
+ - 검증: selftest 334, e2e +1. codex 확인: 텍스트 모드 바이트 동일, data.rows 하위 소비자 없음, focusNext/집계 무변형.
19
+
3
20
  ## 1.36.70 — 2026-07-24 — 성장 한계 클래스 2: migrate 보고 상한 (88KB → 13.5KB) — R-0001 검수 13회전
4
21
 
5
22
  1.36.69 whats-new 상한과 동일 규율을 migrate 보고서의 "AI must re-read" 섹션에 적용. 큰 버전 점프(602 릴리스)에서 보고서가 ~88KB 로 폭주해 AI 컨텍스트를 태우던 것.
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.70 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.72 하네스를 사용합니다. 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.70는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.72는 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.70는 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.70 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.72 릴리스 태그 이력** (수백 라운드) · _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.70: 2026-07-24
238
+ Last synced by Leerness v1.36.72: 2026-07-25
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.70';
37
+ const VERSION = '1.36.72';
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') 시 호스트 프로세스 오염.
@@ -699,7 +699,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
699
699
  - \`brainstorm\` 회수 범위에 3 archive 파일 (decisions/lessons/plan archive) 통합 — 과거 제거된 ideas 가 새 brainstorm 시 다시 후보로 노출. \`hits.archive\` 필드 + 복원 안내 라인.
700
700
  - \`session close\` 텍스트 모드에 archive 누적 라인 추가 — 마감 시점 DELETE 활동 가시화 (handoff 7번째 회수와 symmetric). archive 가시성 6 surface 완성.
701
701
  - \`brainstorm\` 텍스트 모드 lessonsExplicit / planMilestones display 추가 — 데이터 수집은 됐지만 display 가 누락돼 있던 gap 보완 fix.
702
- - \`leerness task list --json\` + MCP **41 도구** (\`leerness_task_list\`) — progress-tracker.md task 전체 JSON 조회 + \`--status\` 필터. Task surface CRUD MCP 완전 완성 (add/list/update/drop).
702
+ - \`leerness task list --json\` + MCP **41 도구** (\`leerness_task_list\`) — progress-tracker.md task JSON 조회 + \`--status\` 필터 (1.36.72 부터 기본 최신 100행, 전체는 \`--all\`). Task surface CRUD MCP 완전 완성 (add/list/update/drop).
703
703
  - MCP **42 도구** (\`leerness_rule_remove\`) — rules.md 에서 특정 rule 제거 + archive 보존. **5 surface CRUD MCP 완전 완성** (task/decision/lesson/plan/rule 모두 add/list/delete MCP 노출).
704
704
  - MCP \`leerness_drift_check\` JSON 응답 fix — \`--json\` 플래그 자동 추가하여 외부 AI가 구조화된 drift 신호 회수 (score, level, signals[], healthy).
705
705
  - \`.harness/session-workflow.md\` 템플릿에 **🧠 Memory CRUD Quick Reference** 섹션 추가 — 5 surface × CRUD 매트릭스 + archive cycle 워크플로 가이드. 신규 \`init\` 워크스페이스 즉시 적용.
@@ -9349,11 +9349,18 @@ function taskList(root) {
9349
9349
  const statusFilter = arg('--status', '');
9350
9350
  const filtered = statusFilter ? rows.filter(r => r.status === statusFilter) : rows;
9351
9351
  if (has('--json')) {
9352
+ // 1.36.72 (성장 한계 클래스 4, 1.36.69~71 규율): tasks 전량 임베드는 무제한 성장 — 기본 최신 100행 +
9353
+ // shown/truncated 정직 표기, --all/--limit 옵트인. total 은 필터 후 전량 그대로(수치 무왜곡).
9354
+ const _rawLim = parseInt(arg('--limit', '100'), 10);
9355
+ const _lim = has('--all') ? filtered.length : (Number.isFinite(_rawLim) && _rawLim > 0 ? _rawLim : 100);
9356
+ const _shownRows = filtered.slice(-_lim); // rows 는 오래된 것부터 — 최신 유지
9352
9357
  const payload = {
9353
9358
  version: VERSION,
9354
9359
  root: absRoot(root),
9355
9360
  total: filtered.length,
9356
- tasks: filtered.map(r => ({
9361
+ shown: _shownRows.length,
9362
+ truncated: _shownRows.length < filtered.length,
9363
+ tasks: _shownRows.map(r => ({
9357
9364
  id: r.id,
9358
9365
  status: r.status,
9359
9366
  request: r.request,
@@ -15302,21 +15309,31 @@ function _collectWorkspacePaths(rootBase) {
15302
15309
  return Array.from(set);
15303
15310
  }
15304
15311
 
15312
+ // 1.36.71 (성장 한계 클래스 3, whats-new 1.36.69 규율): retro/insights --json 이 data.rows 에 전체 task row 를
15313
+ // 임베드해 프로젝트와 함께 무한 성장(72 task 에 이미 22KB — AI 소비자 컨텍스트 낭비). 기본 최신 30행 +
15314
+ // rowsTotal/rowsShown/rowsTruncated 정직 표기, 전체는 --all, 개수는 --limit N. 집계 수치는 전량 기준 그대로.
15315
+ function _retroJsonData(agg) {
15316
+ const rawLim = parseInt(arg('--limit', '30'), 10);
15317
+ const lim = has('--all') ? agg.rows.length : (Number.isFinite(rawLim) && rawLim > 0 ? rawLim : 30);
15318
+ const rows = agg.rows.slice(-lim); // progress rows 는 오래된 것부터 — 최신 lim 개
15319
+ return Object.assign({}, agg, { rows, rowsTotal: agg.rows.length, rowsShown: rows.length, rowsTruncated: rows.length < agg.rows.length });
15320
+ }
15321
+
15305
15322
  function retroCmd(root) {
15306
15323
  root = absRoot(root);
15307
- // 1.9.15: --all-apps / --include 통합 모드
15308
- if (has('--all-apps') || arg('--include', null)) {
15309
- return _retroWorkspace(root);
15310
- }
15311
15324
  // 1.26.1 (13번째 외부리뷰 P2): 비숫자 --days → NaN → new Date(Invalid) throw 로 --json 소비자에 plain text 누출. 숫자 가드 + 음수/오버플로 클램프(insights/round-history 와 일관).
15312
15325
  let days = parseInt(arg('--days', '7'), 10);
15313
15326
  if (!Number.isFinite(days)) { failJson(has('--json'), 'invalid_arg', _uiLang(root) === 'en' ? '--days must be a number' : '--days 는 숫자여야 합니다'); return; }
15314
15327
  days = Math.max(0, Math.min(days, 36500));
15315
15328
  const cutoff = new Date(Date.now() - days * 86400 * 1000).toISOString().slice(0, 10);
15329
+ // 1.9.15: --all-apps / --include 통합 모드 — 1.36.71 (검수, 기존 버그): 워크스페이스 모드가 --days 를 조용히 무시하던 것, cutoff 전달
15330
+ if (has('--all-apps') || arg('--include', null)) {
15331
+ return _retroWorkspace(root, cutoff);
15332
+ }
15316
15333
  const agg = _retroAggregate(root, cutoff); // 1.36.38 (#2): 기간 필터 적용
15317
15334
  // 1.9.16: --json
15318
15335
  if (has('--json')) {
15319
- log(JSON.stringify({ project: path.basename(root), days, cutoff, summary: _retroOneLine(agg), data: agg }, null, 2));
15336
+ log(JSON.stringify({ project: path.basename(root), days, cutoff, summary: _retroOneLine(agg), data: _retroJsonData(agg) }, null, 2));
15320
15337
  return;
15321
15338
  }
15322
15339
  log(`# 회고 (retro) — 최근 ${days}일 (since ${cutoff})`);
@@ -15365,14 +15382,14 @@ function retroCmd(root) {
15365
15382
  }
15366
15383
 
15367
15384
  // 1.9.15: 워크스페이스 통합 retro (다수 프로젝트 묶음 회고)
15368
- function _retroWorkspace(rootBase) {
15385
+ function _retroWorkspace(rootBase, cutoff) {
15369
15386
  const paths = _collectWorkspacePaths(rootBase);
15370
15387
  if (!paths.length) return fail('대상 프로젝트 없음. --include <path1,path2> 또는 --all-apps 사용 필요.');
15371
15388
  // 1.9.16: --json
15372
15389
  if (has('--json')) {
15373
15390
  const projects = paths.map(p => {
15374
- const a = _retroAggregate(p);
15375
- return { project: path.basename(p), path: p, summary: _retroOneLine(a), data: a };
15391
+ const a = _retroAggregate(p, cutoff);
15392
+ return { project: path.basename(p), path: p, summary: _retroOneLine(a), data: _retroJsonData(a) };
15376
15393
  });
15377
15394
  const totals = projects.reduce((t, p) => ({
15378
15395
  tasks: t.tasks + p.data.totalTasks, done: t.done + p.data.doneCount,
@@ -15386,7 +15403,7 @@ function _retroWorkspace(rootBase) {
15386
15403
  log(`# Cross-project retro — ${paths.length}개 프로젝트`);
15387
15404
  const totals = { tasks: 0, done: 0, decisions: 0, skills: 0, totalSkillUsage: 0, totalOpts: 0, activeRules: 0, fixSig: 0, passSig: 0 };
15388
15405
  for (const p of paths) {
15389
- const agg = _retroAggregate(p);
15406
+ const agg = _retroAggregate(p, cutoff);
15390
15407
  const name = path.basename(p);
15391
15408
  log(`\n## ${name}`);
15392
15409
  log(` 📈 ${_retroOneLine(agg)}`);
@@ -15421,7 +15438,7 @@ function insightsCmd(root) {
15421
15438
  // 1.9.16: --json
15422
15439
  if (has('--json')) {
15423
15440
  const sc = readSessionCounter(root);
15424
- log(JSON.stringify({ project: path.basename(root), sessionCount: sc.count, lastCloseAt: sc.lastCloseAt, data: agg }, null, 2));
15441
+ log(JSON.stringify({ project: path.basename(root), sessionCount: sc.count, lastCloseAt: sc.lastCloseAt, data: _retroJsonData(agg) }, null, 2));
15425
15442
  return;
15426
15443
  }
15427
15444
  const sc = readSessionCounter(root);
@@ -15463,7 +15480,7 @@ function _insightsWorkspace(rootBase) {
15463
15480
  if (!paths.length) return fail('대상 프로젝트 없음. --include 또는 --all-apps 사용.');
15464
15481
  // 1.9.16: --json
15465
15482
  if (has('--json')) {
15466
- const projects = paths.map(p => ({ project: path.basename(p), path: p, data: _retroAggregate(p) }));
15483
+ const projects = paths.map(p => ({ project: path.basename(p), path: p, data: _retroJsonData(_retroAggregate(p)) }));
15467
15484
  log(JSON.stringify({ projects, projectCount: paths.length }, null, 2));
15468
15485
  return;
15469
15486
  }
@@ -18686,7 +18703,7 @@ function _mcpToCliArgs(name, args, targetPath) {
18686
18703
  case 'leerness_skill_info': cliArgs = ['skill', 'info', args.id || '', '--path', targetPath, '--json']; break;
18687
18704
  case 'leerness_benchmark': cliArgs = ['benchmark', targetPath, '--json', ...(args.scenario ? ['--scenario', args.scenario] : [])]; break;
18688
18705
  case 'leerness_lazy_detect': cliArgs = ['lazy', 'detect', targetPath, '--json']; break;
18689
- case 'leerness_retro': cliArgs = ['retro', targetPath, '--json', ...(args.days ? ['--days', String(args.days)] : []), ...(args.allApps ? ['--all-apps'] : [])]; break;
18706
+ 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 상한 조절 인자 전달
18690
18707
  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;
18691
18708
  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;
18692
18709
  case 'leerness_task_drop': cliArgs = ['task', 'drop', String(args.id || ''), '--path', targetPath, ...(args.reason ? ['--reason', args.reason] : [])]; break;
@@ -18704,7 +18721,7 @@ function _mcpToCliArgs(name, args, targetPath) {
18704
18721
  case 'leerness_plan_remove': cliArgs = ['plan', 'remove', String(args.target || ''), '--path', targetPath]; break;
18705
18722
  case 'leerness_memory_archive_list': cliArgs = ['memory', 'archive', 'list', '--path', targetPath, '--json', ...(args.surface ? ['--surface', args.surface] : []), ...(args.query ? ['--query', args.query] : [])]; break;
18706
18723
  case 'leerness_memory_restore': cliArgs = ['memory', 'restore', String(args.surface || ''), String(args.target || ''), '--path', targetPath]; break;
18707
- case 'leerness_task_list': cliArgs = ['task', 'list', '--path', targetPath, '--json', ...(args.status ? ['--status', args.status] : [])]; break;
18724
+ case 'leerness_task_list': cliArgs = ['task', 'list', '--path', targetPath, '--json', ...(args.status ? ['--status', args.status] : []), ...(args.all ? ['--all'] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break; // 1.36.72: 상한 조절 인자 전달
18708
18725
  case 'leerness_rule_remove': cliArgs = ['rule', 'remove', String(args.id || ''), '--path', targetPath]; break;
18709
18726
  // 1.9.141: Feature Causality Graph
18710
18727
  case 'leerness_feature_impact': cliArgs = ['feature', 'impact', String(args.id || ''), '--path', targetPath, '--json']; break;
@@ -22829,7 +22846,7 @@ HANDOFF & SESSION
22829
22846
  handoff [path] [--compact] [--all-apps] [--json] Session-start context in one call
22830
22847
  session close [path] Closing report + auto handoff
22831
22848
  context [path] [--json] Agent onboarding context
22832
- retro | insights [path] [--json] Retrospective / cumulative stats
22849
+ retro | insights [path] [--json] [--limit N|--all] Retrospective / cumulative stats (JSON rows: newest 30 by default; rowsTotal = lifetime count, not the --days window)
22833
22850
 
22834
22851
  MEMORY (canonical JSON + markdown projections)
22835
22852
  task list|add|update|drop|fix-evidence|relink [args]
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가 누적 패턴 자동 학습', inputSchema: { type: 'object', properties: { path: { type: 'string' }, days: { type: 'number' }, allApps: { type: 'boolean' } } } },
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'] } },
@@ -45,7 +45,7 @@ module.exports = [
45
45
  { name: 'leerness_plan_remove', requiredTier: 'safe-write', description: '1.9.126 — plan.md 에서 특정 milestone 블록 (### M-XXXX) 제거 (target: M-XXXX 또는 title substring). 제거된 블록은 .harness/plan.archive.md 에 자동 보존. Memory Surface DELETE 5종 완전 완성', inputSchema: { type: 'object', properties: { target: { type: 'string' }, path: { type: 'string' } }, required: ['target'] } },
46
46
  { name: 'leerness_memory_archive_list', requiredTier: 'read-only', description: '1.9.127 — DELETE 5종 archive 파일 통합 조회 JSON ({ decisions: [], lessons: [], plan: [], totals: { decisions, lessons, plan, all } }). 외부 AI가 과거에 제거된 항목을 회수/복원 후보로 참조. --surface 필터: decisions|lessons|plan. 1.9.138+ --query 키워드 필터 (target/originalHeader case-insensitive 매칭)', inputSchema: { type: 'object', properties: { surface: { type: 'string' }, query: { type: 'string' }, path: { type: 'string' } } } },
47
47
  { name: 'leerness_memory_restore', requiredTier: 'safe-write', description: '1.9.128 — archive 의 항목을 active 파일로 복귀 (DELETE→RESTORE cycle). surface: decisions|lessons|plan. target: date YYYY-MM-DD 또는 target substring 매칭. 복원된 블록은 archive 에서 제거됨. 🎉 MCP 40 도구 마일스톤', inputSchema: { type: 'object', properties: { surface: { type: 'string', enum: ['decisions', 'lessons', 'plan'] }, target: { type: 'string' }, path: { type: 'string' } }, required: ['surface', 'target'] } },
48
- { name: 'leerness_task_list', requiredTier: 'read-only', description: '1.9.134 — progress-tracker.md 전체 task 조회 JSON ({ total, tasks: [{ id, status, request, evidence, nextAction, updated }] }). --status 필터 지원 (planned|in-progress|done 등). 외부 AI가 task 상태 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' }, status: { type: 'string' } } } },
48
+ { name: 'leerness_task_list', requiredTier: 'read-only', description: '1.9.134 — progress-tracker.md task 조회 JSON ({ total, shown, truncated, tasks: [...] }). --status 필터 지원 (planned|in-progress|done 등). 1.36.72: 기본 최신 100행(total 은 전량) — 더 필요하면 limit(개수)/all=true(전체) 지정', inputSchema: { type: 'object', properties: { path: { type: 'string' }, status: { type: 'string' }, limit: { type: 'number' }, all: { type: 'boolean' } } } },
49
49
  { name: 'leerness_rule_remove', requiredTier: 'safe-write', description: '1.9.135 — rules.md 에서 특정 rule 제거 (id: R-XXXX). 제거된 rule 은 .harness/rules.archive.md 에 자동 보존 (복구 가능). Rule surface CRUD MCP 완성 (add/list/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
50
50
  { name: 'leerness_feature_impact', requiredTier: 'read-only', description: '1.9.141 — Feature Causality Graph 인과관계 영향 추적 JSON ({ feature, total, impacted: [{ id, title, depth, via, files, errorModes }] }). 신규 기능 추가/형식 변경 전 호출: id 변경으로 영향받는 다른 feature를 transitive (affects + co-changes + reverse depends-on) 으로 회수. 1+1=20 cascade 방지', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
51
51
  { name: 'leerness_feature_list', requiredTier: 'read-only', description: '1.9.141 — 전체 Feature Graph 노드 + 엣지 JSON. 외부 AI가 시스템 내 기능 의존성을 한 번에 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.70",
3
+ "version": "1.36.72",
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
@@ -7238,5 +7238,65 @@ total++;
7238
7238
  if (!ok) failed++;
7239
7239
  }
7240
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
+
7273
+ // 1.36.72 (성장 한계 클래스 4): task list --json 상한 100 + lint 거짓 PASS(F16) 차단
7274
+ total++;
7275
+ {
7276
+ let ok = false;
7277
+ const _d = [];
7278
+ try {
7279
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tlcap-')); _d.push(d);
7280
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--no-env', '--no-stale-check'], { encoding: 'utf8', timeout: 40000 });
7281
+ const R = (extra) => JSON.parse(cp.spawnSync(process.execPath, [CLI, 'task', 'list', '--path', d, '--json', ...extra], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 }).stdout);
7282
+ const baseline = R(['--all']).total;
7283
+ for (let i = 1; i <= 105; i++) { const a = cp.spawnSync(process.execPath, [CLI, 'task', 'add', `x${i}`, '--path', d], { encoding: 'utf8', timeout: 15000 }); if (a.status !== 0) throw new Error('add fail'); }
7284
+ const expTotal = baseline + 105;
7285
+ const j = R([]);
7286
+ const capOk = j.shown === 100 && j.total === expTotal && j.truncated === true && j.tasks.length === 100 && j.tasks[99].request === 'x105';
7287
+ const allOk = R(['--all']).shown === expTotal;
7288
+ const limOk = R(['--limit', '7']).tasks.length === 7;
7289
+ // F16: lint 가 없는 루트/빈 루트에서 실패해야 함 (거짓 PASS 차단)
7290
+ const LINT = path.join(path.dirname(CLI), '..', 'scripts', 'lint.js');
7291
+ const missOk = cp.spawnSync(process.execPath, [LINT, path.join(d, 'no-such-dir')], { encoding: 'utf8', timeout: 20000 }).status === 1;
7292
+ const emptyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lintempty-')); _d.push(emptyDir);
7293
+ const emptyOk = cp.spawnSync(process.execPath, [LINT, emptyDir], { encoding: 'utf8', timeout: 20000 }).status === 1;
7294
+ ok = capOk && allOk && limOk && missOk && emptyOk;
7295
+ if (!ok) console.log(` [tlcap 디버그] cap=${capOk}(${j.shown}/${j.total}) all=${allOk} lim=${limOk} lintMiss=${missOk} lintEmpty=${emptyOk}`);
7296
+ } catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
7297
+ console.log(ok ? '✓ B(1.36.72) task list --json 상한 100 + --all/--limit + lint 거짓 PASS 차단(F16)' : '✗ task list 상한/lint F16 실패');
7298
+ if (!ok) failed++;
7299
+ }
7300
+
7241
7301
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
7242
7302
  if (failed > 0) process.exit(1);
package/scripts/lint.js CHANGED
@@ -8,6 +8,11 @@ const path = require('path');
8
8
  const cp = require('child_process');
9
9
 
10
10
  const ROOT = path.resolve(process.argv[2] || path.join(__dirname, '..')); // 테스트용 루트 오버라이드 허용
11
+ // 1.36.72 (8차 헌트 F16): 존재하지 않는/빈 루트가 "0 js clean" 거짓 PASS 로 통과하던 것 — 명시 오류
12
+ if (!fs.existsSync(ROOT) || !fs.statSync(ROOT).isDirectory()) {
13
+ console.log(`✗ lint: root not found — ${ROOT}`);
14
+ process.exit(1);
15
+ }
11
16
  const TARGET_DIRS = ['bin', 'lib', 'scripts'];
12
17
  const failures = [];
13
18
 
@@ -26,6 +31,11 @@ function listFiles(dir, ext) {
26
31
  }
27
32
 
28
33
  const jsFiles = TARGET_DIRS.flatMap(d => listFiles(path.join(ROOT, d), '.js'));
34
+ // 1.36.72 (F16): 대상 0건도 거짓 PASS — lint 가 아무것도 검사하지 않았다는 뜻이므로 실패
35
+ if (!jsFiles.length) {
36
+ console.log(`✗ lint: no js files found under ${TARGET_DIRS.join('/')} in ${ROOT} — nothing was checked`);
37
+ process.exit(1);
38
+ }
29
39
 
30
40
  // ① 구문 (node --check)
31
41
  for (const f of jsFiles) {