leerness 1.9.59 → 1.9.62

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,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.62 — 2026-05-19
4
+
5
+ **`leerness audit`에 npm CVE 자동 감지 통합**.
6
+
7
+ ### Added
8
+ - **`leerness audit`** — package.json 있으면 `npm audit --json` 자동 호출 → CVE 보고:
9
+ - `metadata.vulnerabilities` 파싱 → critical/high/moderate/low 카운트
10
+ - critical/high 발견 시 warnings +2 (가중치)
11
+ - 0건이면 ✓ "npm CVE: 0건"
12
+ - **스킵 조건** (자동): package.json 없음, `LEERNESS_OFFLINE=1`, `--no-npm-audit` 플래그
13
+
14
+ ## 1.9.61 — 2026-05-19
15
+
16
+ **MCP server cursor 기반 페이지네이션**.
17
+
18
+ ### Added
19
+ - **MCP `tools/call` 응답에 cursor 메타 추가** — 50KB 넘는 출력 자동 분할:
20
+ - `result.nextCursor` — 다음 청크 offset (`args._cursor`로 재호출)
21
+ - `result._truncated` — `{ totalLength, returned, hint }` 메타
22
+ - 청크 크기 override 가능: `args._chunkSize` (기본 50000)
23
+ - 사용 예: 100 task 워크스페이스의 handoff → 청크 1 → cursor → 청크 2 → ... 완료
24
+
25
+ ## 1.9.60 — 2026-05-19
26
+
27
+ **`leerness task export` — TodoWrite ↔ leerness 양방향 sync 완성**.
28
+
29
+ ### Added
30
+ - **`leerness task export [--to <file>] [--json]`**:
31
+ - progress-tracker → TodoWrite JSON 형식 변환
32
+ - status 매핑: `done` → `completed`, `in-progress` → `in_progress`, `planned` → `pending`, `dropped` → `cancelled`
33
+ - 필드: `content` / `status` / `activeForm` (TodoWrite 호환)
34
+ - 1.9.38 `task sync --from`(TodoWrite → leerness)과 함께 **양방향 sync 완성**
35
+
36
+ ### 검증 (stress-v9)
37
+ - AA1-AA4 (task export + status 매핑 + round-trip) 4/4 PASS
38
+ - BB1-BB3 (MCP cursor 페이지네이션, chunkSize override) 3/3 PASS
39
+ - CC1-CC3 (audit npm CVE, OFFLINE/no-npm-audit/no-package.json 스킵) 3/3 PASS
40
+ - DD1-DD3 (1.9.43~59 누적 회귀) 3/3 PASS
41
+ - **stress-v9: 13/13 PASS** (BB2/BB3 BUG 발견·즉시 패치: chunkSize override 추가), e2e: **216/216 PASS**
42
+
3
43
  ## 1.9.59 — 2026-05-19
4
44
 
5
45
  **`session close --suggest` default 활성 — 라운드 마감 잊을 단계 없음**.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.59-green)]() [![tests](https://img.shields.io/badge/e2e-213%2F213-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.62-green)]() [![tests](https://img.shields.io/badge/e2e-216%2F216-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,7 +12,7 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.59 AI Agent Reliability Harness ║
15
+ ║ v1.9.62 AI Agent Reliability Harness ║
16
16
  ║ verify · remember · orchestrate · audit · prevent drift ║
17
17
  ╚══════════════════════════════════════════════════════════════╝
18
18
  ```
@@ -433,6 +433,9 @@ npm test # = node ./scripts/e2e.js
433
433
 
434
434
  ## 변경 이력 (최근)
435
435
 
436
+ - **1.9.62** — `leerness audit` npm CVE 자동 감지 (npm audit --json 통합, OFFLINE/no-npm-audit 스킵).
437
+ - **1.9.61** — MCP server cursor 기반 페이지네이션 — `nextCursor` + `_chunkSize` override.
438
+ - **1.9.60** — `leerness task export` — progress-tracker → TodoWrite JSON 형식. **양방향 sync 완성** (1.9.38 sync + 1.9.60 export).
436
439
  - **1.9.59** — `session close` **--suggest default 활성** (잊을 단계 없음, `--no-suggest`로 비활성 가능) · 설치 가이드 갱신.
437
440
  - **1.9.58** — handoff lessons **fuzzy 매칭** (어간 변형, 복합어, decisions.md 매칭 추가).
438
441
  - **1.9.57** — `session close --suggest` (마감 시 skill suggest + drift + 명령 통계 통합 보고) · install banner quickStart + session-workflow.md 갱신 (1.9.56/57 흐름 자동 안내).
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
- const VERSION = '1.9.59';
9
+ const VERSION = '1.9.62';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -1423,6 +1423,34 @@ function audit(root) {
1423
1423
  }
1424
1424
  }
1425
1425
  } catch {}
1426
+ // 1.9.62: package.json 있으면 npm audit --json 자동 호출 → CVE 보고 (opt-out: --no-npm-audit)
1427
+ // 정책: leerness가 외부 호출하지만 사용자 컨텍스트에 이미 npm 설치되어 있음을 가정 (offline 시 자동 스킵)
1428
+ if (exists(path.join(root, 'package.json')) && !has('--no-npm-audit') && process.env.LEERNESS_OFFLINE !== '1') {
1429
+ try {
1430
+ const r = cp.spawnSync('npm', ['audit', '--json'], {
1431
+ cwd: root, encoding: 'utf8', shell: true, timeout: 30000
1432
+ });
1433
+ if (r.stdout) {
1434
+ let j = null;
1435
+ try { j = JSON.parse(r.stdout); } catch {}
1436
+ if (j && j.metadata && j.metadata.vulnerabilities) {
1437
+ const v = j.metadata.vulnerabilities;
1438
+ const total = (v.critical || 0) + (v.high || 0) + (v.moderate || 0) + (v.low || 0);
1439
+ if (total > 0) {
1440
+ warnings++;
1441
+ warn(`npm CVE: ${total}건 (critical=${v.critical||0}, high=${v.high||0}, moderate=${v.moderate||0}, low=${v.low||0})`);
1442
+ log(` → 수정: npm audit fix · 상세: npm audit`);
1443
+ if (v.critical || v.high) {
1444
+ warnings++; // critical/high는 추가 가중
1445
+ warn(` ⚠ critical/high CVE 즉시 대응 권장`);
1446
+ }
1447
+ } else {
1448
+ ok('npm CVE: 0건');
1449
+ }
1450
+ }
1451
+ }
1452
+ } catch {}
1453
+ }
1426
1454
  log(`Audit summary: warnings=${warnings} failures=${failures}${fix ? ` fixed=${fixed}` : ''}`);
1427
1455
  if (failures) process.exitCode = 1;
1428
1456
  }
@@ -6782,10 +6810,21 @@ function mcpServeCmd(root) {
6782
6810
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
6783
6811
  }
6784
6812
  const r = callLeerness(cliArgs);
6785
- send({ jsonrpc: '2.0', id, result: {
6786
- content: [{ type: 'text', text: (r.stdout || r.stderr || '(no output)').slice(0, 50000) }],
6813
+ // 1.9.61: cursor 기반 페이지네이션 — 긴 출력은 cursor offset로 다음 청크
6814
+ const fullText = r.stdout || r.stderr || '(no output)';
6815
+ const CHUNK_SIZE = (args._chunkSize && Number.isFinite(args._chunkSize)) ? args._chunkSize : 50000;
6816
+ const cursor = (args._cursor && /^\d+$/.test(String(args._cursor))) ? parseInt(args._cursor, 10) : 0;
6817
+ const chunk = fullText.slice(cursor, cursor + CHUNK_SIZE);
6818
+ const nextCursor = (cursor + CHUNK_SIZE) < fullText.length ? String(cursor + CHUNK_SIZE) : null;
6819
+ const result = {
6820
+ content: [{ type: 'text', text: chunk }],
6787
6821
  isError: !r.ok
6788
- } });
6822
+ };
6823
+ if (nextCursor) {
6824
+ result.nextCursor = nextCursor;
6825
+ result._truncated = { totalLength: fullText.length, returned: chunk.length, hint: `args._cursor=${nextCursor} 로 다음 청크 호출 가능` };
6826
+ }
6827
+ send({ jsonrpc: '2.0', id, result });
6789
6828
  } catch (e) {
6790
6829
  send({ jsonrpc: '2.0', id, error: { code: -32603, message: 'Internal error: ' + e.message } });
6791
6830
  }
@@ -6905,6 +6944,36 @@ function usageStatsCmd(root) {
6905
6944
  }
6906
6945
 
6907
6946
  // 1.9.38: task sync — TodoWrite/외부 JSON에서 leerness task로 mirror
6947
+ // 1.9.60: leerness task export [--to <todo.json>] [--json]
6948
+ // progress-tracker → TodoWrite JSON 형식 (status: completed/in_progress/pending)
6949
+ function taskExportCmd(root) {
6950
+ root = absRoot(root || process.cwd());
6951
+ const out = arg('--to', null);
6952
+ const rows = readProgressRows(root);
6953
+ // leerness status → TodoWrite status 매핑
6954
+ const statusMap = { 'done': 'completed', 'in-progress': 'in_progress', 'planned': 'pending', 'requested': 'pending', 'dropped': 'cancelled', 'in_progress': 'in_progress', 'incomplete': 'in_progress', 'blocked': 'in_progress', 'on-hold': 'pending' };
6955
+ const todos = rows.map(r => ({
6956
+ content: r.request,
6957
+ status: statusMap[r.status] || 'pending',
6958
+ activeForm: r.nextAction || r.request.slice(0, 40)
6959
+ }));
6960
+ if (out) {
6961
+ writeUtf8(path.resolve(out), JSON.stringify(todos, null, 2) + '\n');
6962
+ log(`# leerness task export (1.9.60)`);
6963
+ log(`exported: ${todos.length} task → ${path.resolve(out)}`);
6964
+ log(``);
6965
+ log(`💡 다음: 메인 에이전트가 이 JSON을 TodoWrite로 import 가능`);
6966
+ return;
6967
+ }
6968
+ if (has('--json')) { log(JSON.stringify(todos, null, 2)); return; }
6969
+ log(`# leerness task export (1.9.60)`);
6970
+ log(`총 ${todos.length} task (--to <file>로 저장)`);
6971
+ for (const t of todos.slice(0, 10)) {
6972
+ log(` - [${t.status}] ${t.content.slice(0, 60)}`);
6973
+ }
6974
+ if (todos.length > 10) log(` ... ${todos.length - 10}건 더`);
6975
+ }
6976
+
6908
6977
  function taskSyncCmd(root) {
6909
6978
  root = absRoot(root || process.cwd());
6910
6979
  const file = arg('--from', null);
@@ -7218,6 +7287,7 @@ async function main() {
7218
7287
  if (sub==='fix-evidence') return taskFixEvidence(root);
7219
7288
  if (sub==='relink') return taskRelink(root);
7220
7289
  if (sub==='sync') return taskSyncCmd(root);
7290
+ if (sub==='export') return taskExportCmd(root);
7221
7291
  }
7222
7292
  return help();
7223
7293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.59",
3
+ "version": "1.9.62",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -950,6 +950,46 @@ total++;
950
950
  if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
951
951
  }
952
952
 
953
+ // 1.9.60/61/62 회귀
954
+ total++;
955
+ {
956
+ // task export → TodoWrite JSON 호환
957
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tex-'));
958
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
959
+ cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'export 검증', '--status', 'done', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
960
+ const r = cp.spawnSync(process.execPath, [CLI, 'task', 'export', '--path', tmpC, '--json'], { encoding: 'utf8', timeout: 15000 });
961
+ let j = null;
962
+ try { j = JSON.parse(r.stdout); } catch {}
963
+ const ok = Array.isArray(j) && j.length >= 1 && j.some(t => t.status === 'completed' && 'content' in t && 'activeForm' in t);
964
+ console.log(ok ? '✓ B(1.9.60) task export: TodoWrite JSON 형식 (content/status/activeForm)' : `✗ task export 실패`);
965
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
966
+ }
967
+
968
+ total++;
969
+ {
970
+ // MCP cursor 페이지네이션 (chunkSize override)
971
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mcc-'));
972
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
973
+ const req = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'leerness_handoff', arguments: { path: tmpC, _cursor: '0', _chunkSize: 200 } } });
974
+ const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { encoding: 'utf8', timeout: 15000, input: req + '\n' });
975
+ const resp = JSON.parse(r.stdout.split('\n').filter(Boolean)[0]);
976
+ const ok = resp.result && resp.result.nextCursor && resp.result._truncated;
977
+ console.log(ok ? '✓ B(1.9.61) MCP cursor 페이지네이션: nextCursor + _truncated' : `✗ cursor 실패`);
978
+ if (!ok) { failed++; console.log(JSON.stringify(resp).slice(0, 300)); }
979
+ }
980
+
981
+ total++;
982
+ {
983
+ // audit npm CVE — OFFLINE 또는 package.json 없을 때 graceful
984
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ac-'));
985
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
986
+ // package.json 없음 → npm audit 스킵
987
+ const r = cp.spawnSync(process.execPath, [CLI, 'audit', tmpC], { encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_OFFLINE: '1' } });
988
+ const ok = r.status === 0 && !/npm CVE/.test(r.stdout) && /Audit summary/.test(r.stdout);
989
+ console.log(ok ? '✓ B(1.9.62) audit: package.json 없을 때 npm audit 자동 스킵' : `✗ audit CVE 실패`);
990
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
991
+ }
992
+
953
993
  // 1.9.58/59 회귀
954
994
  total++;
955
995
  {