leerness 1.9.55 → 1.9.59

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,73 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.59 — 2026-05-19
4
+
5
+ **`session close --suggest` default 활성 — 라운드 마감 잊을 단계 없음**.
6
+
7
+ ### Changed (호환성 보장)
8
+ - **`leerness session close`** — 1.9.57 `--suggest`를 **default 활성**으로 승격:
9
+ - 라운드 마감 시 자동으로 skill suggest + drift check + usage stats 통합 보고
10
+ - 사용자가 잊지 않도록 default 동작
11
+ - **새 옵션 `--no-suggest`** — 이전 동작으로 복귀
12
+ - **새 env `LEERNESS_NO_SUGGEST=1`** — CI/자동화 환경에서 suggest 강제 비활성
13
+ - `--suggest` 명시 호출도 그대로 동작 (호환성)
14
+
15
+ ### 설치 가이드 갱신
16
+ - banner quickStart에서 `--suggest` 명시 제거 (이제 default라 불필요)
17
+ - `.harness/session-workflow.md` Step 6 갱신 — `--no-suggest`로 비활성 가능 명시
18
+
19
+ ## 1.9.58 — 2026-05-19
20
+
21
+ **handoff lessons fuzzy 매칭 (어간 변형 + decisions.md 매칭)**.
22
+
23
+ ### Added
24
+ - **fuzzy 매칭** — `escapeRegex(keyword.slice(0, max(4, len*0.7)))` 으로 부분 매칭:
25
+ - webhook ↔ webhooks ↔ webhook-payload ↔ webhooked 모두 매칭
26
+ - 한국어 어미 변화도 부분 매칭 (예: "결제" ↔ "결제처리" ↔ "결제검증")
27
+ - **decisions.md 매칭 추가** — 이전엔 review-evidence.md만 → 이제 decisions.md의 *실패/롤백/취소/회귀* 결정도 자동 회수
28
+
29
+ ### 검증 (stress-v8)
30
+ - X1-X4 (fuzzy 매칭: 어간/복합어/decisions/false positive 차단) 4/4 PASS
31
+ - Y1-Y4 (session close default suggest + 옵션 호환) 4/4 PASS
32
+ - Z1-Z4 (1.9.43~57 누적 회귀) 4/4 PASS
33
+ - **stress-v8: 12/12 PASS**, e2e: **213/213 PASS**
34
+
35
+ ## 1.9.57 — 2026-05-19
36
+
37
+ **`session close --suggest` + 설치 가이드 갱신**.
38
+
39
+ ### Added
40
+ - **`leerness session close --suggest`** — 라운드 마감 통합 보고:
41
+ - skill suggest 후보 (Hermes-style 자동 학습) 상위 3
42
+ - drift check 상태 + 임계 초과 신호
43
+ - usage stats 가장 많이 쓴 명령 Top 3
44
+
45
+ ### 설치 가이드 갱신
46
+ - **`_banner` quickStart 재구성** — 1.9.57+ 워크플로 강조:
47
+ - `npx leerness handoff .` (lessons 자동 재상기 포함)
48
+ - `npx leerness session close . --suggest` (마감 + 다음 라운드)
49
+ - `npx leerness mcp serve` (메인 에이전트용 12 도구)
50
+ - **`.harness/session-workflow.md`** Step 6 갱신 — `--suggest`/1.9.56 lessons 자동 재상기 안내
51
+
52
+ ## 1.9.56 — 2026-05-19
53
+
54
+ **`handoff`에 `lessons --auto` 자동 통합 — 매 세션 시작 시 과거 실패 자동 재상기**.
55
+
56
+ ### Added
57
+ - **handoff 자동 lessons 재상기**:
58
+ - 가장 최근 in-progress/planned task의 `request`에서 키워드 자동 추출
59
+ - 그 키워드로 review-evidence.md의 과거 실패 매칭
60
+ - **🧠 과거 lessons 자동 재상기** 블록 출력 (관련 실패 ≥1건 시)
61
+ - 끄려면: `--no-lessons` 또는 `LEERNESS_NO_LESSONS=1`
62
+ - 매칭 실패 시 블록 자동 숨김 (false positive 차단)
63
+
64
+ ### 검증 (stress-v7)
65
+ - T1-T3 (handoff 자동 lessons) 3/3 PASS
66
+ - U1-U3 (session close --suggest) 3/3 PASS
67
+ - V1-V2 (설치 가이드 갱신 — banner + session-workflow.md) 2/2 PASS
68
+ - W1-W4 (1.9.43~55 누적 회귀) 4/4 PASS
69
+ - **stress-v7: 12/12 PASS**, e2e: **210/210 PASS**
70
+
3
71
  ## 1.9.55 — 2026-05-19
4
72
 
5
73
  **MCP server 12 도구 — `leerness_skill_suggest` + `leerness_lessons` 노출**.
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.55-green)]() [![tests](https://img.shields.io/badge/e2e-208%2F208-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.59-green)]() [![tests](https://img.shields.io/badge/e2e-213%2F213-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.55 AI Agent Reliability Harness ║
15
+ ║ v1.9.59 AI Agent Reliability Harness ║
16
16
  ║ verify · remember · orchestrate · audit · prevent drift ║
17
17
  ╚══════════════════════════════════════════════════════════════╝
18
18
  ```
@@ -433,6 +433,10 @@ npm test # = node ./scripts/e2e.js
433
433
 
434
434
  ## 변경 이력 (최근)
435
435
 
436
+ - **1.9.59** — `session close` **--suggest default 활성** (잊을 단계 없음, `--no-suggest`로 비활성 가능) · 설치 가이드 갱신.
437
+ - **1.9.58** — handoff lessons **fuzzy 매칭** (어간 변형, 복합어, decisions.md 매칭 추가).
438
+ - **1.9.57** — `session close --suggest` (마감 시 skill suggest + drift + 명령 통계 통합 보고) · install banner quickStart + session-workflow.md 갱신 (1.9.56/57 흐름 자동 안내).
439
+ - **1.9.56** — `handoff`에 lessons 자동 재상기 통합 (현재 in-progress task 키워드 매칭 → 과거 실패 자동 표시).
436
440
  - **1.9.55** — MCP server에 `leerness_skill_suggest` + `leerness_lessons` 추가 (10 → 12 도구) · lessons --auto의 stopword 확장 (false positive 차단).
437
441
  - **1.9.54** — `leerness lessons --auto` — 최근 in-progress task에서 키워드 자동 추출 → 과거 lessons 자동 매칭·재상기.
438
442
  - **1.9.53** — `leerness skill suggest` — task-log / progress-tracker / usage-stats에서 반복 패턴 **자동 감지 → 새 skill 후보 제안** (Hermes-style 자동 학습의 leerness 버전).
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.55';
9
+ const VERSION = '1.9.59';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -276,14 +276,19 @@ leerness review <file> --persona security,performance,ux
276
276
  - 메인이 직접 통합 시나리오 작성 + 실행 (independent 검증).
277
277
  - Sub-agent 검수 vs 메인 검수 결과 *교차 일치* 확인.
278
278
 
279
- ## Step 6. 세션 마감 + 인계
279
+ ## Step 6. 세션 마감 + 인계 + 다음 라운드 추천
280
280
  \`\`\`bash
281
- leerness session close . # handoff/current-state/task-log 자동 갱신
282
- leerness audit . --fix # 누락 메타 자동 보강
283
- leerness usage stats . # 이번 세션 명령 카운트 확인
281
+ leerness session close . # 1.9.59+ — --suggest default 활성 (마감 + 다음 라운드 자동)
282
+ leerness session close . --no-suggest # suggest 비활성 (이전 동작)
283
+
284
+ # 분리 호출도 가능:
285
+ leerness skill suggest . # 1.9.53 — 반복 패턴 → 새 skill 후보
286
+ leerness drift check . # 4 신호 + 4 레벨 점검
287
+ leerness audit . --fix # 누락 메타 자동 보강
284
288
  \`\`\`
285
289
  - session close가 누락되면 다음 세션 시작 시 drift critical 발생.
286
290
  - 자동 회복 옵션: \`drift check --auto-fix\` (critical 시 session close 자동 실행).
291
+ - 1.9.56+ handoff가 매 세션 시작 시 **과거 lessons 자동 재상기** (현재 task 키워드 기준).
287
292
 
288
293
  ---
289
294
 
@@ -1699,6 +1704,60 @@ function handoff(root) {
1699
1704
  const cs = read(currentStatePath(root)).replace(/Updated: \d{4}-\d{2}-\d{2}/, `Updated: ${today()}`);
1700
1705
  writeUtf8(currentStatePath(root), cs);
1701
1706
  }
1707
+ // 1.9.56: handoff에 lessons --auto 자동 통합 — 현재 in-progress task와 관련된 과거 실수/결정 자동 재상기
1708
+ // 매 세션 시작 시 AI가 과거에 같은 키워드로 실패한 사례를 잊지 않도록.
1709
+ // 끄려면: --no-lessons 또는 LEERNESS_NO_LESSONS=1
1710
+ if (!has('--no-lessons') && !has('--compact') && process.env.LEERNESS_NO_LESSONS !== '1') {
1711
+ try {
1712
+ const lrows = readProgressRows(root);
1713
+ const latestRow = lrows.filter(r => r.status === 'in-progress' || r.status === 'planned').pop() || lrows[lrows.length - 1];
1714
+ if (latestRow && latestRow.request) {
1715
+ const stopwords = new Set([
1716
+ '이런','저런','하다','하고','있는','하지','에서',
1717
+ '작업','구현','추가','진행','수정','변경','검토','확인',
1718
+ '프로젝트','관리','기능','시스템','코드','파일','버전','정리','계획',
1719
+ 'next','action','task','todo','work'
1720
+ ]);
1721
+ const tokens = String(latestRow.request).toLowerCase().match(/[\w가-힣]{4,}/g) || [];
1722
+ const keyword = tokens.filter(t => !stopwords.has(t)).sort((a, b) => b.length - a.length)[0];
1723
+ if (keyword) {
1724
+ // lessons 검색 — 1.9.58: fuzzy 매칭 (substring + 어간 변형)
1725
+ const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
1726
+ const evidence = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
1727
+ // fuzzy: keyword 또는 keyword 부분 (4자+) 일치
1728
+ // 예: "webhook" 매칭 시 "webhook-payload", "webhooks", "webhooked" 모두 매칭
1729
+ const fuzzyRe = new RegExp(escapeRegex(keyword.slice(0, Math.max(4, Math.floor(keyword.length * 0.7)))), 'i');
1730
+ const matches = [];
1731
+ for (const block of evidence.split(/\n(?=## )/)) {
1732
+ if (block.startsWith('## ') && fuzzyRe.test(block) && /✗|fail|롤백|버그|incomplete/i.test(block)) {
1733
+ const titleM = block.match(/^## (.+)$/m);
1734
+ if (titleM) matches.push({ source: 'review-evidence.md', title: titleM[1].trim(), block });
1735
+ }
1736
+ }
1737
+ // 1.9.58: decisions.md도 fuzzy 매칭 (실패/롤백 관련 결정만)
1738
+ for (const block of decisions.split(/\n(?=### )/)) {
1739
+ if (block.startsWith('### ') && fuzzyRe.test(block) && /롤백|실패|fail|취소|회귀|deprecate/i.test(block)) {
1740
+ const titleM = block.match(/^### (.+)$/m);
1741
+ if (titleM) matches.push({ source: 'decisions.md', title: titleM[1].trim(), block });
1742
+ }
1743
+ }
1744
+ if (matches.length) {
1745
+ const isTty = process.stdout && process.stdout.isTTY;
1746
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
1747
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
1748
+ log('');
1749
+ log(yel(`## 🧠 과거 lessons 자동 재상기 (1.9.56) — 키워드 "${keyword}"`));
1750
+ log(dim(` 현재 task와 관련된 과거 실패/롤백 ${matches.length}건 — 같은 실수 반복 방지`));
1751
+ for (const m of matches.slice(0, 3)) {
1752
+ log(dim(` • [${m.source}] ${m.title}`));
1753
+ }
1754
+ log(dim(` → 전체: leerness lessons --auto --path .`));
1755
+ log('');
1756
+ }
1757
+ }
1758
+ }
1759
+ } catch {}
1760
+ }
1702
1761
  // 1.9.41: 최근 migrate 차분 알림 — migration-report.md가 24h 내면 "AI must re-read" 블록 자동 표시
1703
1762
  // 같은 채팅 세션의 AI 청크가 이전 버전 마인드셋이어도 새 도구를 즉시 인지하도록.
1704
1763
  if (!has('--no-workflow-guide') && !has('--compact')) {
@@ -3044,11 +3103,15 @@ function _banner(opts = {}) {
3044
3103
  lines.push('');
3045
3104
  for (const ln of lines) log(ln);
3046
3105
  if (opts.quickStart) {
3047
- log(C.bold(C.cyan(' ✨ 빠른 시작')));
3048
- log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트'));
3049
- log(' ' + C.green('npx leerness@latest setup-agents .') + C.dim(' # 외부 AI CLI 설정'));
3050
- log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 적재'));
3051
- log(' ' + C.green('npx leerness verify-claim T-0001 --run-tests') + C.dim(' # 자동 검증'));
3106
+ log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.57+ 워크플로)')));
3107
+ log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
3108
+ log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 적재 + 과거 lessons 자동 재상기'));
3109
+ log(' ' + C.green('npx leerness verify-claim T-0001 --run-tests') + C.dim(' # AI 거짓 완료 자동 검증'));
3110
+ log(' ' + C.green('npx leerness session close .') + C.dim(' # 마감 + 다음 라운드 추천 (default)'));
3111
+ log('');
3112
+ log(C.bold(C.cyan(' 🤖 메인 에이전트 (Claude/Cursor/Copilot)용')));
3113
+ log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 — 12 도구 노출'));
3114
+ log(' ' + C.green('npx leerness agents bench "<task>"') + C.dim(' # 3 CLI 동시 비교'));
3052
3115
  log('');
3053
3116
  }
3054
3117
  }
@@ -3796,6 +3859,45 @@ function sessionClose(root) {
3796
3859
  ok(`session-handoff.md and current-state.md updated`);
3797
3860
  // 1.9.12: session close 끝에 roadmap.html 자동 갱신
3798
3861
  _autoRoadmap(root, 'session-close');
3862
+ // 1.9.57: --suggest 옵션 — 마감 시 skill suggest + drift check + lessons 통합 보고
3863
+ // 1.9.59: default 활성 — --no-suggest로 명시 비활성 가능
3864
+ const suggestEnabled = (has('--suggest') || (!has('--no-suggest') && process.env.LEERNESS_NO_SUGGEST !== '1'));
3865
+ if (suggestEnabled) {
3866
+ const isTty = process.stdout && process.stdout.isTTY;
3867
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3868
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3869
+ log('');
3870
+ log(cy('## 💡 다음 라운드 추천 (1.9.57 --suggest)'));
3871
+ // 1) skill suggest
3872
+ try {
3873
+ const r = cp.spawnSync(process.execPath, [__filename, 'skill', 'suggest', '--path', root, '--min', '3', '--json'],
3874
+ { encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' } });
3875
+ const j = JSON.parse(r.stdout);
3876
+ if (j.candidates && j.candidates.length) {
3877
+ log(dim(' 📌 신규 skill 후보 (Hermes-style 자동 학습):'));
3878
+ for (const c of j.candidates.slice(0, 3)) log(` • ${c.keyword} (${c.count}회 등장, 출처: ${c.source})`);
3879
+ }
3880
+ } catch {}
3881
+ // 2) drift check
3882
+ try {
3883
+ const r = cp.spawnSync(process.execPath, [__filename, 'drift', 'check', root, '--json'],
3884
+ { encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '0' } });
3885
+ const j = JSON.parse(r.stdout.trim());
3886
+ if (j.level) {
3887
+ log(dim(` 🩺 drift 상태: ${j.level} ${j.score}/200`));
3888
+ if (j.fired && j.fired.length) log(dim(` 🔥 ${j.fired.length}건 임계 초과 — \`leerness drift check\` 상세`));
3889
+ }
3890
+ } catch {}
3891
+ // 3) usage stats top
3892
+ try {
3893
+ const stats = _readUsageStats(root);
3894
+ const entries = Object.entries(stats.commands || {}).sort((a, b) => b[1] - a[1]).slice(0, 3);
3895
+ if (entries.length) {
3896
+ log(dim(` 📊 가장 많이 쓴 명령: ${entries.map(([c, n]) => `${c}(${n})`).join(', ')}`));
3897
+ }
3898
+ } catch {}
3899
+ log('');
3900
+ }
3799
3901
  // 1.9.13: 세션 카운터 + 자동 한 줄 요약 + 5세션마다 깊은 회고
3800
3902
  try {
3801
3903
  const sc = readSessionCounter(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.55",
3
+ "version": "1.9.59",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -950,6 +950,70 @@ total++;
950
950
  if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
951
951
  }
952
952
 
953
+ // 1.9.58/59 회귀
954
+ total++;
955
+ {
956
+ // fuzzy 매칭 — 어간 변형 (webhook ↔ webhooks)
957
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-fz-'));
958
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
959
+ fs.writeFileSync(path.join(tmpC, '.harness', 'review-evidence.md'),
960
+ '## 2026-04\nNote: ✗ webhooks payload 실패\n', 'utf8');
961
+ cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'webhook 작업', '--status', 'in-progress', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
962
+ const r = cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--no-drift-check', '--no-workflow-guide'], { encoding: 'utf8', timeout: 15000 });
963
+ const ok = /lessons 자동 재상기.*webhook/.test(r.stdout);
964
+ console.log(ok ? '✓ B(1.9.58) lessons fuzzy: webhook ↔ webhooks 어간 변형 매칭' : `✗ fuzzy 실패`);
965
+ if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
966
+ }
967
+
968
+ total++;
969
+ {
970
+ // session close default suggest
971
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sd-'));
972
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
973
+ const r = cp.spawnSync(process.execPath, [CLI, 'session', 'close', tmpC], { encoding: 'utf8', timeout: 30000 });
974
+ // 1.9.59: default activated → "다음 라운드 추천" 자동 표시
975
+ const ok = /다음 라운드 추천|drift 상태/.test(r.stdout);
976
+ console.log(ok ? '✓ B(1.9.59) session close: --suggest default 활성' : `✗ default suggest 실패`);
977
+ if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
978
+ }
979
+
980
+ total++;
981
+ {
982
+ // --no-suggest 비활성 (이전 동작 보존)
983
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ns-'));
984
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
985
+ const r = cp.spawnSync(process.execPath, [CLI, 'session', 'close', tmpC, '--no-suggest'], { encoding: 'utf8', timeout: 30000 });
986
+ const ok = r.status === 0 && !/다음 라운드 추천/.test(r.stdout) && /진행 요약/.test(r.stdout);
987
+ console.log(ok ? '✓ B(1.9.59) --no-suggest: suggest 비활성 (이전 동작)' : `✗ --no-suggest 실패`);
988
+ if (!ok) { failed++; console.log(r.stdout.slice(-300)); }
989
+ }
990
+
991
+ // 1.9.56/57 회귀
992
+ total++;
993
+ {
994
+ // handoff 자동 lessons 재상기
995
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ha-'));
996
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
997
+ fs.writeFileSync(path.join(tmpC, '.harness', 'review-evidence.md'),
998
+ '## 2026-04-01\nNote: ✗ webhook 처리 실패\n', 'utf8');
999
+ cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'webhook 처리 개선', '--status', 'in-progress', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
1000
+ const r = cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--no-drift-check', '--no-workflow-guide'], { encoding: 'utf8', timeout: 15000 });
1001
+ const ok = /lessons 자동 재상기.*webhook|🧠.*webhook/.test(r.stdout);
1002
+ console.log(ok ? '✓ B(1.9.56) handoff: lessons 자동 재상기 (현재 task 키워드 매칭)' : `✗ handoff lessons 실패`);
1003
+ if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
1004
+ }
1005
+
1006
+ total++;
1007
+ {
1008
+ // session close --suggest
1009
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sc-'));
1010
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
1011
+ const r = cp.spawnSync(process.execPath, [CLI, 'session', 'close', tmpC, '--suggest'], { encoding: 'utf8', timeout: 30000 });
1012
+ const ok = /다음 라운드 추천|drift 상태/.test(r.stdout);
1013
+ console.log(ok ? '✓ B(1.9.57) session close --suggest: drift + skill suggest 통합' : `✗ --suggest 실패`);
1014
+ if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
1015
+ }
1016
+
953
1017
  // 1.9.54/55 회귀
954
1018
  total++;
955
1019
  {