leerness 1.9.71 → 1.9.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 +19 -0
- package/README.md +3 -2
- package/bin/harness.js +72 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.72 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**`leerness brainstorm`에 skill-suggestions.md history + task-log 실패 라인 통합**.
|
|
6
|
+
|
|
7
|
+
### Improved — brainstorm 자원 회수 확장
|
|
8
|
+
- 기존: decisions / skills / tasks / rules / evidence / lessons.
|
|
9
|
+
- **신규**: `skillHistory` (1.9.68 rolling history) + `taskLogFails` (1.9.67 task-log 실패 라인).
|
|
10
|
+
- 출력 추가 섹션:
|
|
11
|
+
- `📒 같은 주제 이전 skill match 이력` — `[timestamp] "query"` 형식
|
|
12
|
+
- `📜 task-log 실패 라인` — 실패/롤백/incomplete/버그 라인 회수
|
|
13
|
+
- total 카운트에 신규 필드 합산.
|
|
14
|
+
- 매칭 알고리즘: 기존 unicode word boundary regex 그대로 사용.
|
|
15
|
+
|
|
16
|
+
### Verified
|
|
17
|
+
- stress-v18 — brainstorm 신규 hits + 누적 회귀 + 성능.
|
|
18
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
3
22
|
## 1.9.71 — 2026-05-20
|
|
4
23
|
|
|
5
24
|
**`.env` / `.env.example` 자동 동기화 (보안 정책: 키만, 실제 값 절대 노출 안 함)**.
|
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.72 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,7 @@ npm test # = node ./scripts/e2e.js
|
|
|
433
433
|
|
|
434
434
|
## 변경 이력 (최근)
|
|
435
435
|
|
|
436
|
+
- **1.9.72** — **`leerness brainstorm`에 skill-suggestions.md history + task-log 실패 라인 통합** — 누적 컨텍스트 기반 brainstorm 강화 (이전 매칭 이력 + task-log 실패 회수).
|
|
436
437
|
- **1.9.71** — **`.env` / `.env.example` 자동 동기화** — `leerness env check` / `env sync` 명령 + `audit` 통합 (`--fix`로 누락 키 자동 추가). 보안 정책: 실제 값 절대 노출 안 함 (키만 추가, 값은 빈 문자열).
|
|
437
438
|
- **1.9.70** — **MCP server `tools/call` 자동 사용 통계** — 도구별 호출 카운트 (`.harness/cache/usage-stats.json#mcp.tools`) + `leerness usage stats` 출력에 MCP 섹션 + 드물게 호출되는 도구 식별.
|
|
438
439
|
- **1.9.69** — **handoff에 skill-suggestions.md history hit 노출** (fuzzy 매칭, 최근 2건 + top 2 매치). AI가 이전 세션 결정을 일관 유지. mtime 기반 캐시 (1.9.65/66/67 캐시 패밀리 연속).
|
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.72';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -3242,7 +3242,7 @@ function _banner(opts = {}) {
|
|
|
3242
3242
|
lines.push('');
|
|
3243
3243
|
for (const ln of lines) log(ln);
|
|
3244
3244
|
if (opts.quickStart) {
|
|
3245
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.
|
|
3245
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.72+ 워크플로)')));
|
|
3246
3246
|
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
3247
3247
|
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + 이전 history hit (1.9.69)'));
|
|
3248
3248
|
log(' ' + C.green('npx leerness skill match "<query>"') + C.dim(' # 매칭 skill + rolling history 자동 누적 (1.9.68)'));
|
|
@@ -4462,7 +4462,8 @@ function _brainstormFor(root, topic) {
|
|
|
4462
4462
|
const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
|
|
4463
4463
|
const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
|
|
4464
4464
|
function matches(text) { return wordRes.every(re => re.test(text)); }
|
|
4465
|
-
|
|
4465
|
+
// 1.9.72: skillHistory + taskLogFails 필드 추가
|
|
4466
|
+
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [], skillHistory: [], taskLogFails: [] };
|
|
4466
4467
|
const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
4467
4468
|
const decLines = dec.split('\n');
|
|
4468
4469
|
for (const b of _extractDecisionBlocks(dec)) {
|
|
@@ -4518,6 +4519,32 @@ function _brainstormFor(root, topic) {
|
|
|
4518
4519
|
if (/✗|fail|롤백|incomplete|버그/i.test(block)) hits.lessons.push({ title: t.trim(), line: lineNo });
|
|
4519
4520
|
}
|
|
4520
4521
|
}
|
|
4522
|
+
// 1.9.72: skill-suggestions.md rolling history hits
|
|
4523
|
+
const histPath = path.join(root, '.harness', 'skill-suggestions.md');
|
|
4524
|
+
if (exists(histPath)) {
|
|
4525
|
+
const histTxt = read(histPath);
|
|
4526
|
+
for (const block of histTxt.split(/\n(?=## )/)) {
|
|
4527
|
+
if (!block.startsWith('## ')) continue;
|
|
4528
|
+
const h = block.match(/^## ([\d-]+ [\d:]+) — query "([^"]+)"/);
|
|
4529
|
+
if (h && matches(block)) {
|
|
4530
|
+
const idx = histTxt.indexOf(block);
|
|
4531
|
+
const lineNo = idx >= 0 ? histTxt.slice(0, idx).split('\n').length : 0;
|
|
4532
|
+
hits.skillHistory.push({ at: h[1], query: h[2], preview: block.slice(0, 220).replace(/\n+/g, ' '), line: lineNo });
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
// 1.9.72: task-log.md 실패 라인 hits
|
|
4537
|
+
const tlogPath = path.join(root, '.harness', 'task-log.md');
|
|
4538
|
+
if (exists(tlogPath)) {
|
|
4539
|
+
const tlog = read(tlogPath);
|
|
4540
|
+
const lines = tlog.split('\n');
|
|
4541
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4542
|
+
const line = lines[i];
|
|
4543
|
+
if (line.length > 4 && /✗|\bfail|롤백|재발|incomplete|버그/i.test(line) && matches(line)) {
|
|
4544
|
+
hits.taskLogFails.push({ title: line.replace(/^[-*]\s*/, '').slice(0, 100), line: i + 1 });
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4521
4548
|
// 1.9.25: --include-code 옵션 — 소스 본문 검색 추가 (모순 감지 핵심)
|
|
4522
4549
|
if (has('--include-code')) {
|
|
4523
4550
|
const codeDirs = ['src', 'tests', 'bin', 'lib'];
|
|
@@ -4549,7 +4576,7 @@ function _brainstormFor(root, topic) {
|
|
|
4549
4576
|
return hits;
|
|
4550
4577
|
}
|
|
4551
4578
|
|
|
4552
|
-
function _brainstormTotal(h) { return h.decisions.length + h.skills.length + h.tasks.length + h.rules.length + h.evidence.length + (h.code?.length || 0); }
|
|
4579
|
+
function _brainstormTotal(h) { return h.decisions.length + h.skills.length + h.tasks.length + h.rules.length + h.evidence.length + (h.code?.length || 0) + (h.skillHistory?.length || 0) + (h.taskLogFails?.length || 0); }
|
|
4553
4580
|
|
|
4554
4581
|
// 1.9.16: 워크스페이스 통합 brainstorm
|
|
4555
4582
|
function _brainstormWorkspace(rootBase, topic) {
|
|
@@ -4622,7 +4649,8 @@ function brainstormCmd(root, topic) {
|
|
|
4622
4649
|
const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
|
|
4623
4650
|
const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
|
|
4624
4651
|
function matches(text) { return wordRes.every(re => re.test(text)); }
|
|
4625
|
-
|
|
4652
|
+
// 1.9.72: skillHistory + taskLogFails 필드 추가 (brainstorm에 누적 컨텍스트 추가 회수)
|
|
4653
|
+
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [], skillHistory: [], taskLogFails: [] };
|
|
4626
4654
|
|
|
4627
4655
|
// decisions (1.9.14: 코드블록/Template 제외, 1.9.15: 라인 번호)
|
|
4628
4656
|
const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
@@ -4685,9 +4713,36 @@ function brainstormCmd(root, topic) {
|
|
|
4685
4713
|
if (/✗|fail|롤백|incomplete|버그/i.test(block)) hits.lessons.push({ title: t.trim(), line: lineNo });
|
|
4686
4714
|
}
|
|
4687
4715
|
}
|
|
4716
|
+
// 1.9.72: skill-suggestions.md rolling history hits (이전 매칭 결과 회수)
|
|
4717
|
+
const histPath = path.join(root, '.harness', 'skill-suggestions.md');
|
|
4718
|
+
if (exists(histPath)) {
|
|
4719
|
+
const histTxt = read(histPath);
|
|
4720
|
+
let pos = 0;
|
|
4721
|
+
for (const block of histTxt.split(/\n(?=## )/)) {
|
|
4722
|
+
if (!block.startsWith('## ')) { pos += block.length + 1; continue; }
|
|
4723
|
+
const h = block.match(/^## ([\d-]+ [\d:]+) — query "([^"]+)"/);
|
|
4724
|
+
if (h && matches(block)) {
|
|
4725
|
+
const idx = histTxt.indexOf(block);
|
|
4726
|
+
const lineNo = idx >= 0 ? histTxt.slice(0, idx).split('\n').length : 0;
|
|
4727
|
+
hits.skillHistory.push({ at: h[1], query: h[2], preview: block.slice(0, 220).replace(/\n+/g, ' '), line: lineNo });
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
// 1.9.72: task-log.md 실패 라인 hits
|
|
4732
|
+
const tlogPath = path.join(root, '.harness', 'task-log.md');
|
|
4733
|
+
if (exists(tlogPath)) {
|
|
4734
|
+
const tlog = read(tlogPath);
|
|
4735
|
+
const lines = tlog.split('\n');
|
|
4736
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4737
|
+
const line = lines[i];
|
|
4738
|
+
if (line.length > 4 && /✗|\bfail|롤백|재발|incomplete|버그/i.test(line) && matches(line)) {
|
|
4739
|
+
hits.taskLogFails.push({ title: line.replace(/^[-*]\s*/, '').slice(0, 100), line: i + 1 });
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4688
4743
|
|
|
4689
|
-
const total = hits.decisions.length + hits.skills.length + hits.tasks.length + hits.rules.length + hits.evidence.length;
|
|
4690
|
-
log(`\n📦 총 ${total}건 발견 (decisions ${hits.decisions.length} · skills ${hits.skills.length} · tasks ${hits.tasks.length} · rules ${hits.rules.length} · evidence ${hits.evidence.length})`);
|
|
4744
|
+
const total = hits.decisions.length + hits.skills.length + hits.tasks.length + hits.rules.length + hits.evidence.length + (hits.skillHistory ? hits.skillHistory.length : 0) + (hits.taskLogFails ? hits.taskLogFails.length : 0);
|
|
4745
|
+
log(`\n📦 총 ${total}건 발견 (decisions ${hits.decisions.length} · skills ${hits.skills.length} · tasks ${hits.tasks.length} · rules ${hits.rules.length} · evidence ${hits.evidence.length}${hits.skillHistory && hits.skillHistory.length ? ` · skill-history ${hits.skillHistory.length}` : ''}${hits.taskLogFails && hits.taskLogFails.length ? ` · task-log-fails ${hits.taskLogFails.length}` : ''})`);
|
|
4691
4746
|
|
|
4692
4747
|
// 1.9.15: 모든 출력에 출처 파일:라인 표시
|
|
4693
4748
|
if (hits.decisions.length) {
|
|
@@ -4714,6 +4769,16 @@ function brainstormCmd(root, topic) {
|
|
|
4714
4769
|
log(`\n## ⚠ 같은 주제 과거 실패/롤백 (${hits.lessons.length}) — 같은 실수 방지`);
|
|
4715
4770
|
hits.lessons.slice(0, 5).forEach(l => log(` - .harness/review-evidence.md:${l.line || '?'} — ${l.title}`));
|
|
4716
4771
|
}
|
|
4772
|
+
// 1.9.72: skill-suggestions.md rolling history hits
|
|
4773
|
+
if (hits.skillHistory.length) {
|
|
4774
|
+
log(`\n## 📒 같은 주제 이전 skill match 이력 (${hits.skillHistory.length}) — 1.9.68 누적`);
|
|
4775
|
+
hits.skillHistory.slice(0, 5).forEach(h => log(` - .harness/skill-suggestions.md:${h.line || '?'} — [${h.at}] "${h.query}"`));
|
|
4776
|
+
}
|
|
4777
|
+
// 1.9.72: task-log.md 실패 라인 hits
|
|
4778
|
+
if (hits.taskLogFails.length) {
|
|
4779
|
+
log(`\n## 📜 task-log 실패 라인 (${hits.taskLogFails.length}) — 1.9.67 인덱스 + brainstorm`);
|
|
4780
|
+
hits.taskLogFails.slice(0, 5).forEach(t => log(` - .harness/task-log.md:${t.line || '?'} — ${t.title}`));
|
|
4781
|
+
}
|
|
4717
4782
|
|
|
4718
4783
|
log(`\n## 💡 시작 전 권장 액션`);
|
|
4719
4784
|
log(` 1. 위 자원을 모두 검토 후 plan add 또는 task add로 새 작업 등록`);
|