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 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
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.71-green)]() [![tests](https://img.shields.io/badge/e2e-219%2F219-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.72-green)]() [![tests](https://img.shields.io/badge/e2e-219%2F219-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.71 AI Agent Reliability Harness ║
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.71';
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.71+ 워크플로)')));
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
- const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
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
- const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
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로 새 작업 등록`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.71",
3
+ "version": "1.9.72",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",