leerness 1.9.71 → 1.9.73
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 +36 -0
- package/README.md +4 -2
- package/bin/harness.js +76 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.73 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**MCP server 14번째 도구 `leerness_env_check` 추가 (1.9.71 env 보안을 외부 AI에 노출)**.
|
|
6
|
+
|
|
7
|
+
### Added — MCP 14번째 도구
|
|
8
|
+
- `leerness_env_check` — 워크스페이스 `.env` ↔ `.env.example` 동기화 검사를 외부 AI 에이전트에 노출.
|
|
9
|
+
- inputSchema: `{ path: string }`
|
|
10
|
+
- 응답: 1.9.71의 `env check --json` 결과 그대로 (envPath/examplePath/envKeys/exKeys/inEnvOnly/inExampleOnly).
|
|
11
|
+
- 외부 AI가 워크스페이스 보안 자동 점검 가능 (Claude Code/Cursor 등에서 호출).
|
|
12
|
+
- MCP server 도구 카운트: 13 → **14**.
|
|
13
|
+
|
|
14
|
+
### Verified
|
|
15
|
+
- stress-v19 — MCP 14 도구 + env_check JSON 응답 + 1.9.43~72 누적 회귀 + 성능.
|
|
16
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1.9.72 — 2026-05-20
|
|
21
|
+
|
|
22
|
+
**`leerness brainstorm`에 skill-suggestions.md history + task-log 실패 라인 통합**.
|
|
23
|
+
|
|
24
|
+
### Improved — brainstorm 자원 회수 확장
|
|
25
|
+
- 기존: decisions / skills / tasks / rules / evidence / lessons.
|
|
26
|
+
- **신규**: `skillHistory` (1.9.68 rolling history) + `taskLogFails` (1.9.67 task-log 실패 라인).
|
|
27
|
+
- 출력 추가 섹션:
|
|
28
|
+
- `📒 같은 주제 이전 skill match 이력` — `[timestamp] "query"` 형식
|
|
29
|
+
- `📜 task-log 실패 라인` — 실패/롤백/incomplete/버그 라인 회수
|
|
30
|
+
- total 카운트에 신규 필드 합산.
|
|
31
|
+
- 매칭 알고리즘: 기존 unicode word boundary regex 그대로 사용.
|
|
32
|
+
|
|
33
|
+
### Verified
|
|
34
|
+
- stress-v18 — brainstorm 신규 hits + 누적 회귀 + 성능.
|
|
35
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
3
39
|
## 1.9.71 — 2026-05-20
|
|
4
40
|
|
|
5
41
|
**`.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.73 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,8 @@ npm test # = node ./scripts/e2e.js
|
|
|
433
433
|
|
|
434
434
|
## 변경 이력 (최근)
|
|
435
435
|
|
|
436
|
+
- **1.9.73** — **MCP server 14번째 도구 `leerness_env_check`** — 1.9.71 env 보안 검사를 외부 AI에 노출 (외부 워크스페이스 .env/.env.example 동기화 자동 점검).
|
|
437
|
+
- **1.9.72** — **`leerness brainstorm`에 skill-suggestions.md history + task-log 실패 라인 통합** — 누적 컨텍스트 기반 brainstorm 강화 (이전 매칭 이력 + task-log 실패 회수).
|
|
436
438
|
- **1.9.71** — **`.env` / `.env.example` 자동 동기화** — `leerness env check` / `env sync` 명령 + `audit` 통합 (`--fix`로 누락 키 자동 추가). 보안 정책: 실제 값 절대 노출 안 함 (키만 추가, 값은 빈 문자열).
|
|
437
439
|
- **1.9.70** — **MCP server `tools/call` 자동 사용 통계** — 도구별 호출 카운트 (`.harness/cache/usage-stats.json#mcp.tools`) + `leerness usage stats` 출력에 MCP 섹션 + 드물게 호출되는 도구 식별.
|
|
438
440
|
- **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.73';
|
|
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.73+ 워크플로)')));
|
|
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)'));
|
|
@@ -3251,7 +3251,7 @@ function _banner(opts = {}) {
|
|
|
3251
3251
|
log(' ' + C.green('npx leerness session close .') + C.dim(' # 마감 + 다음 라운드 추천 (default)'));
|
|
3252
3252
|
log('');
|
|
3253
3253
|
log(C.bold(C.cyan(' 🤖 메인 에이전트 (Claude/Cursor/Copilot)용')));
|
|
3254
|
-
log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 —
|
|
3254
|
+
log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 — 14 도구 (env_check 포함) + tools/call 통계'));
|
|
3255
3255
|
log(' ' + C.green('npx leerness agents bench "<task>"') + C.dim(' # 3 CLI 동시 비교'));
|
|
3256
3256
|
log('');
|
|
3257
3257
|
}
|
|
@@ -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로 새 작업 등록`);
|
|
@@ -7010,7 +7075,8 @@ function mcpServeCmd(root) {
|
|
|
7010
7075
|
{ name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
7011
7076
|
{ name: 'leerness_skill_suggest', description: '1.9.53 — 사용 패턴 자동 분석 → 새 skill 후보 제안 (Hermes-style 자동 학습)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, min: { type: 'number' }, days: { type: 'number' } } } },
|
|
7012
7077
|
{ name: 'leerness_lessons', description: '1.9.7/54 — 과거 결정·실수 자동 회수 (--auto: 현재 task 키워드 자동 추출)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, query: { type: 'string' }, auto: { type: 'boolean' }, limit: { type: 'number' } } } },
|
|
7013
|
-
{ name: 'leerness_task_export', description: '1.9.60/66 — leerness task → Claude Code TodoWrite 호환 JSON (외부 AI 양방향 sync)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, to: { type: 'string' } } } }
|
|
7078
|
+
{ name: 'leerness_task_export', description: '1.9.60/66 — leerness task → Claude Code TodoWrite 호환 JSON (외부 AI 양방향 sync)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, to: { type: 'string' } } } },
|
|
7079
|
+
{ name: 'leerness_env_check', description: '1.9.71/73 — .env vs .env.example 동기화 검사 (보안: 키만, 값 미노출). exit 1 if 누락 키 있음', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
|
|
7014
7080
|
];
|
|
7015
7081
|
|
|
7016
7082
|
function send(obj) {
|
|
@@ -7055,6 +7121,7 @@ function mcpServeCmd(root) {
|
|
|
7055
7121
|
case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
|
|
7056
7122
|
case 'leerness_lessons': cliArgs = ['lessons', '--path', targetPath, ...(args.auto ? ['--auto'] : []), ...(args.query ? ['--query', args.query] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break;
|
|
7057
7123
|
case 'leerness_task_export': cliArgs = ['task', 'export', '--path', targetPath, ...(args.to ? ['--to', args.to] : ['--json'])]; break;
|
|
7124
|
+
case 'leerness_env_check': cliArgs = ['env', 'check', targetPath, '--json']; break;
|
|
7058
7125
|
default:
|
|
7059
7126
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
7060
7127
|
}
|