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 +68 -0
- package/README.md +6 -2
- package/bin/harness.js +112 -10
- package/package.json +1 -1
- package/scripts/e2e.js +64 -0
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
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
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.
|
|
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 .
|
|
282
|
-
leerness
|
|
283
|
-
|
|
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
|
|
3050
|
-
log(' ' + C.green('npx leerness
|
|
3051
|
-
log(' ' + C.green('npx leerness
|
|
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
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
|
{
|