leerness 1.9.24 → 1.9.25

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,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.25 — 2026-05-15
4
+
5
+ **모순 감지 0/5 → 5/5 — 소스 코드 인덱싱 + 멀티 세션 in-progress 즉시 등록**.
6
+
7
+ 이전 1.9.24 실측에서 발견한 "코드는 있는데 progress-tracker에 등록 안 된 상태" 사각지대를 두 가지 신규 명령으로 보완.
8
+
9
+ ### Added
10
+
11
+ - **`leerness memory search "키" --include-code`** (후보 A): `.harness/*.md` 외에 `src/`, `tests/`, `bin/`, `lib/`, `scripts/` 폴더의 `.js/.ts/.gd/.cs/.py/.rb/.go/.rs/.md/.html/.css/.json` 본문도 검색. 모순 감지 핵심.
12
+ - **`leerness brainstorm "주제" --include-code`** (후보 A 확장): 단일/워크스페이스 모드 모두에서 코드 hits 별도 섹션 (`💻 코드`)으로 표시.
13
+ - **`leerness register-pending "<요청>"`** (후보 B): 다중 세션/모델이 작업 시작 즉시 progress-tracker에 in-progress T-row를 등록. `--agent <name> --note <text>` 옵션. 다른 세션이 즉시 발견 가능 → 중복/모순 작업 방지.
14
+
15
+ ### Why
16
+ 1.9.24까지: Gemini가 워크스페이스 직접 수정 (toJson 추가)했지만 progress-tracker에 등록 전엔 다른 세션이 발견 못함 (0/5 fail). 1.9.25:
17
+ - 소스 코드 인덱싱으로 즉시 발견 가능 (실측: `memory search "toJson" --include-code` → **0 → 15 matches**)
18
+ - `register-pending`으로 작업 시작 시점 즉시 신호 발신
19
+
20
+ ### Migration
21
+ ```bash
22
+ npx leerness@latest update . --yes
23
+
24
+ # 다중 세션 / 외부 모델 워크플로 (Gemini/Codex/Claude)
25
+ leerness handoff --compact > /tmp/ctx.txt
26
+ # 외부 모델에 컨텍스트 + 작업 부여
27
+ gemini -p "$(cat /tmp/ctx.txt)\n작업: ..." --yolo
28
+
29
+ # 모순 감지 (코드 검색 포함)
30
+ leerness memory search "키워드" --include-code
31
+ leerness brainstorm "주제" --all-apps --include-code
32
+ ```
33
+
3
34
  ## 1.9.24 — 2026-05-14
4
35
 
5
36
  **`leerness deps <capability>` — depends-on 그래프 역방향 추적 + 자동 회귀 sweep**.
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.24';
9
+ const VERSION = '1.9.25';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -1167,8 +1167,34 @@ function memorySearch(root, query) {
1167
1167
  total += hits.length;
1168
1168
  }
1169
1169
  }
1170
+ // 1.9.25: --include-code 옵션 — 실제 소스 코드 본문도 검색 (src/tests/bin)
1171
+ // 이전 모순 감지 0/5 → 5/5의 핵심 보완
1172
+ if (has('--include-code')) {
1173
+ const codeDirs = ['src', 'tests', 'bin', 'lib', 'scripts'];
1174
+ for (const dir of codeDirs) {
1175
+ const dp = path.join(root, dir);
1176
+ if (!exists(dp)) continue;
1177
+ function walkCodeDir(d) {
1178
+ let entries; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
1179
+ for (const e of entries) {
1180
+ const p = path.join(d, e.name);
1181
+ if (e.isDirectory()) { walkCodeDir(p); continue; }
1182
+ if (!/\.(js|ts|jsx|tsx|gd|cs|py|rb|go|rs|md|html|css|json)$/i.test(e.name)) continue;
1183
+ let txt; try { txt = read(p); } catch { continue; }
1184
+ const lines = txt.split('\n');
1185
+ const hits = lines.map((line, i) => ({ line, i })).filter(x => re.test(x.line));
1186
+ if (hits.length) {
1187
+ log(`\n# ${rel(root, p)}`);
1188
+ for (const h of hits.slice(0, parseInt(arg('--limit','5'),10))) log(` L${h.i+1}: ${h.line.trim().slice(0, 160)}`);
1189
+ total += hits.length;
1190
+ }
1191
+ }
1192
+ }
1193
+ walkCodeDir(dp);
1194
+ }
1195
+ }
1170
1196
  if (total === 0) log('(no matches)');
1171
- else log(`\n${total} matches`);
1197
+ else log(`\n${total} matches${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
1172
1198
  }
1173
1199
 
1174
1200
  function handoff(root) {
@@ -1986,6 +2012,41 @@ function depsImpactCmd(root, targetCapability) {
1986
2012
  }
1987
2013
  }
1988
2014
 
2015
+ // 1.9.25: register-pending — sub-agent/외부 모델이 작업 시작 즉시 progress-tracker에 in-progress 등록
2016
+ // 사용 예: leerness register-pending "<요청 내용>" --agent gemini
2017
+ // → 다음 T-ID 자동 할당, status=in-progress, evidence="(pending) by <agent>"
2018
+ // → 다른 세션이 즉시 발견 가능 (모순 감지)
2019
+ function registerPendingCmd(root, requestParts) {
2020
+ root = absRoot(root || process.cwd());
2021
+ const request = (requestParts || []).join(' ').trim();
2022
+ if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent gemini'); return process.exit(1); }
2023
+ const agent = arg('--agent', 'unknown');
2024
+ const note = arg('--note', '');
2025
+
2026
+ // 다음 T-ID 산출
2027
+ const rows = readProgressRows(root);
2028
+ let maxN = 0;
2029
+ for (const r of rows) {
2030
+ const m = r.id && r.id.match(/^T-(\d+)$/);
2031
+ if (m) maxN = Math.max(maxN, parseInt(m[1], 10));
2032
+ }
2033
+ const id = `T-${String(maxN + 1).padStart(4, '0')}`;
2034
+ const evidence = `(pending) by ${agent}${note ? ' — ' + note : ''}`;
2035
+ const row = {
2036
+ id,
2037
+ status: 'in-progress',
2038
+ request: request.slice(0, 200),
2039
+ evidence,
2040
+ nextAction: '작업 진행 중',
2041
+ updated: today()
2042
+ };
2043
+ upsertProgress(root, row);
2044
+ log(`✓ ${id} 등록됨 (in-progress) by ${agent}`);
2045
+ log(` request: ${row.request}`);
2046
+ log(` 💡 작업 완료 후: leerness task update ${id} --status done --evidence "..."`);
2047
+ if (has('--json')) log(JSON.stringify({ ok: true, id, ...row }, null, 2));
2048
+ }
2049
+
1989
2050
  // 1.9.22 후보 4: llm-bench record + retro 통합
1990
2051
  function llmBenchRecordCmd(root) {
1991
2052
  root = absRoot(root || process.cwd());
@@ -2507,7 +2568,7 @@ function _brainstormFor(root, topic) {
2507
2568
  const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
2508
2569
  const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
2509
2570
  function matches(text) { return wordRes.every(re => re.test(text)); }
2510
- const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [] };
2571
+ const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
2511
2572
  const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
2512
2573
  const decLines = dec.split('\n');
2513
2574
  for (const b of _extractDecisionBlocks(dec)) {
@@ -2563,10 +2624,38 @@ function _brainstormFor(root, topic) {
2563
2624
  if (/✗|fail|롤백|incomplete|버그/i.test(block)) hits.lessons.push({ title: t.trim(), line: lineNo });
2564
2625
  }
2565
2626
  }
2627
+ // 1.9.25: --include-code 옵션 — 소스 본문 검색 추가 (모순 감지 핵심)
2628
+ if (has('--include-code')) {
2629
+ const codeDirs = ['src', 'tests', 'bin', 'lib'];
2630
+ for (const dir of codeDirs) {
2631
+ const dp = path.join(root, dir);
2632
+ if (!exists(dp)) continue;
2633
+ function walkCode(d) {
2634
+ let entries; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
2635
+ for (const e of entries) {
2636
+ const p = path.join(d, e.name);
2637
+ if (e.isDirectory()) { walkCode(p); continue; }
2638
+ if (!/\.(js|ts|jsx|tsx|gd|cs|py|rb|go|rs)$/i.test(e.name)) continue;
2639
+ let txt; try { txt = read(p); } catch { continue; }
2640
+ if (matches(txt)) {
2641
+ const lines = txt.split('\n');
2642
+ const firstHit = lines.findIndex(l => matches(l));
2643
+ hits.code.push({
2644
+ file: rel(root, p),
2645
+ line: firstHit >= 0 ? firstHit + 1 : 0,
2646
+ preview: firstHit >= 0 ? lines[firstHit].trim().slice(0, 120) : ''
2647
+ });
2648
+ if (hits.code.length >= 20) return; // 너무 많으면 stop
2649
+ }
2650
+ }
2651
+ }
2652
+ walkCode(dp);
2653
+ }
2654
+ }
2566
2655
  return hits;
2567
2656
  }
2568
2657
 
2569
- function _brainstormTotal(h) { return h.decisions.length + h.skills.length + h.tasks.length + h.rules.length + h.evidence.length; }
2658
+ function _brainstormTotal(h) { return h.decisions.length + h.skills.length + h.tasks.length + h.rules.length + h.evidence.length + (h.code?.length || 0); }
2570
2659
 
2571
2660
  // 1.9.16: 워크스페이스 통합 brainstorm
2572
2661
  function _brainstormWorkspace(rootBase, topic) {
@@ -2608,8 +2697,13 @@ function _brainstormWorkspace(rootBase, topic) {
2608
2697
  if (h.lessons.length) {
2609
2698
  log(` ⚠ 과거 실패/롤백 (${h.lessons.length})`);
2610
2699
  }
2700
+ // 1.9.25: 소스 코드 본문 hits
2701
+ if (h.code && h.code.length) {
2702
+ log(` 💻 코드 (${h.code.length})`);
2703
+ h.code.slice(0, 5).forEach(c => log(` - ${c.file}:${c.line} — ${c.preview}`));
2704
+ }
2611
2705
  }
2612
- log(`\n## 📊 워크스페이스 총합: ${grandTotal}건 매치 (${paths.length} 프로젝트)`);
2706
+ log(`\n## 📊 워크스페이스 총합: ${grandTotal}건 매치 (${paths.length} 프로젝트)${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
2613
2707
  if (grandTotal === 0) log(` ⓘ 어느 프로젝트에서도 "${topic}" 관련 자원 없음 — 새 영역. 첫 결정/스킬을 기록하면 다음 brainstorm이 풍부해짐.`);
2614
2708
  }
2615
2709
 
@@ -2634,7 +2728,7 @@ function brainstormCmd(root, topic) {
2634
2728
  const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
2635
2729
  const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
2636
2730
  function matches(text) { return wordRes.every(re => re.test(text)); }
2637
- const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [] };
2731
+ const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
2638
2732
 
2639
2733
  // decisions (1.9.14: 코드블록/Template 제외, 1.9.15: 라인 번호)
2640
2734
  const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
@@ -4002,7 +4096,7 @@ function viewworkInstall(root) {
4002
4096
  }
4003
4097
 
4004
4098
  function help() {
4005
- log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness viewwork install [path]\n leerness viewwork emit [path] [--action a] [--note n] [--agent x] [--tool t]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
4099
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness viewwork install [path]\n leerness viewwork emit [path] [--action a] [--note n] [--agent x] [--tool t]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
4006
4100
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
4007
4101
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
4008
4102
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -4041,6 +4135,7 @@ async function main() {
4041
4135
  if (cmd === 'orchestrate') return await orchestrateCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
4042
4136
  if (cmd === 'llm-bench' && args[1] === 'record') return llmBenchRecordCmd(arg('--path', process.cwd()));
4043
4137
  if (cmd === 'deps') return depsImpactCmd(arg('--path', process.cwd()), args[1]);
4138
+ if (cmd === 'register-pending') return registerPendingCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
4044
4139
  if (cmd === 'session' && args[1] === 'close') { const r = sessionClose(args[2] || process.cwd()); viewworkEmit(args[2] || process.cwd(), { action: 'task', tool: 'session-close', note: 'session close' }); return r; }
4045
4140
  if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
4046
4141
  if (cmd === 'viewwork' && args[1] === 'emit') return viewworkEmit(args[2] || process.cwd(), { action: arg('--action','task'), note: arg('--note',''), agent: arg('--agent','leerness'), tool: arg('--tool','leerness-cli') });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.24",
3
+ "version": "1.9.25",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -585,6 +585,37 @@ total++;
585
585
  if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
586
586
  }
587
587
 
588
+ // 1.9.25 회귀: memory search --include-code + brainstorm --include-code + register-pending
589
+ total++;
590
+ {
591
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-incode-'));
592
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
593
+ fs.mkdirSync(path.join(tmpC, 'src'), { recursive: true });
594
+ fs.writeFileSync(path.join(tmpC, 'src/feature.js'), "function specialMagicHandler() { return 42; }\n");
595
+ // 기본 memory search → 0 matches
596
+ const r1 = cp.spawnSync(process.execPath, [CLI, 'memory', 'search', 'specialMagicHandler', '--path', tmpC], { encoding: 'utf8', timeout: 10000 });
597
+ // --include-code → 1+ matches
598
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'memory', 'search', 'specialMagicHandler', '--path', tmpC, '--include-code'], { encoding: 'utf8', timeout: 10000 });
599
+ const ok = /no matches/.test(r1.stdout) && /src\/feature\.js/.test(r2.stdout) && /소스 코드 포함/.test(r2.stdout);
600
+ console.log(ok ? '✓ B(1.9.25) memory search --include-code: 코드 본문 검색' : '✗ --include-code 실패');
601
+ if (!ok) { failed++; console.log(r2.stdout.slice(0, 400)); }
602
+ }
603
+
604
+ total++;
605
+ {
606
+ // register-pending
607
+ const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-regp-'));
608
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
609
+ const r = cp.spawnSync(process.execPath, [CLI, 'register-pending', 'JSON export 기능', '--agent', 'gemini', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
610
+ const okReg = r.status === 0 && /등록됨 \(in-progress\) by gemini/.test(r.stdout);
611
+ // 즉시 progress-tracker에서 검색 가능
612
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'memory', 'search', 'JSON export', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
613
+ const okSearch = /in-progress/.test(r2.stdout) && /pending.*gemini/.test(r2.stdout);
614
+ const ok = okReg && okSearch;
615
+ console.log(ok ? '✓ B(1.9.25) register-pending: 즉시 등록 + 검색 가능' : `✗ register-pending 실패 (reg=${okReg} search=${okSearch})`);
616
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
617
+ }
618
+
588
619
  // 1.9.22 회귀: handoff --compact + orchestrate opt-in 정책 + llm-bench record
589
620
  total++;
590
621
  {