leerness 1.9.23 → 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 +53 -0
- package/bin/harness.js +228 -7
- package/package.json +1 -1
- package/scripts/e2e.js +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
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
|
+
|
|
34
|
+
## 1.9.24 — 2026-05-14
|
|
35
|
+
|
|
36
|
+
**`leerness deps <capability>` — depends-on 그래프 역방향 추적 + 자동 회귀 sweep**.
|
|
37
|
+
|
|
38
|
+
오래된 작업 재진행 시 / 핵심 모듈 변경 시 영향받는 모듈을 자동 식별 + 해당 프로젝트의 `npm test`를 일괄 실행.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **`leerness deps <capability>`**: 워크스페이스 모든 `reuse-map.md`의 depends-on 엣지를 역방향 추적해 해당 capability를 의존하는 모든 capability와 프로젝트를 식별. 1-hop(직접 의존) + 2-hop(전이 의존) 모두 표시.
|
|
43
|
+
- **`leerness deps <capability> --run-tests`**: 영향받는 N개 프로젝트의 `npm test`를 자동 일괄 실행. 회귀 발견 시 어느 프로젝트인지 즉시 보고 + `exit 1`. CI 통합용 `--json`.
|
|
44
|
+
- 실측: `leerness deps Character --all-apps --run-tests` 실행 시 rpg-core의 `Character` capability에 의존하는 **6 프로젝트(8 capability) 자동 식별 + 6/6 npm test 자동 일괄 실행**.
|
|
45
|
+
|
|
46
|
+
### Why
|
|
47
|
+
오래된 작업을 재진행하거나 핵심 모듈을 변경할 때, 영향받는 다른 프로젝트를 수동으로 grep하던 패턴을 1 명령으로 자동화. depends-on 그래프(1.9.18부터 수집)가 활용됨.
|
|
48
|
+
|
|
49
|
+
### Migration
|
|
50
|
+
```bash
|
|
51
|
+
npx leerness@latest update . --yes
|
|
52
|
+
# 사용 예
|
|
53
|
+
leerness deps Character --all-apps --run-tests
|
|
54
|
+
```
|
|
55
|
+
|
|
3
56
|
## 1.9.23 — 2026-05-14
|
|
4
57
|
|
|
5
58
|
**Install 사용성 개선 — `preferGlobal` + `main` 필드 + README 상단 Install 섹션**.
|
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.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) {
|
|
@@ -1861,6 +1887,166 @@ async function orchestrateCmd(root, goalParts) {
|
|
|
1861
1887
|
log(`📜 누적 기록: .harness/orchestrate-log.md`);
|
|
1862
1888
|
}
|
|
1863
1889
|
|
|
1890
|
+
// 1.9.24: leerness deps <capability> — depends-on 그래프 역방향 추적 + 자동 회귀 sweep
|
|
1891
|
+
// 사용 예: leerness deps Character
|
|
1892
|
+
// → rpg-core/Character를 의존하는 모든 capability 식별 (rpg-net/Session, rpg-data/* 등)
|
|
1893
|
+
// → 영향받은 프로젝트의 npm test 자동 일괄 실행
|
|
1894
|
+
// → 회귀 발생 시 어느 프로젝트인지 즉시 보고
|
|
1895
|
+
function depsImpactCmd(root, targetCapability) {
|
|
1896
|
+
root = absRoot(root || process.cwd());
|
|
1897
|
+
if (!targetCapability) { fail('impact <capability> 필요. 예: leerness impact Character'); return process.exit(1); }
|
|
1898
|
+
const paths = _collectWorkspacePaths(root);
|
|
1899
|
+
if (!paths.length) {
|
|
1900
|
+
// --all-apps 자동
|
|
1901
|
+
process.argv.push('--all-apps');
|
|
1902
|
+
}
|
|
1903
|
+
const allPaths = _collectWorkspacePaths(root);
|
|
1904
|
+
if (!allPaths.length) return fail('워크스페이스 프로젝트 없음. _apps/* 또는 --include 사용.');
|
|
1905
|
+
|
|
1906
|
+
// 1) 모든 reuse-map에서 entries + depends-on 엣지 수집
|
|
1907
|
+
const allEntries = []; // { project, entry }
|
|
1908
|
+
const allEdges = []; // { fromProject, fromCap, toCap }
|
|
1909
|
+
for (const p of allPaths) {
|
|
1910
|
+
const entries = _readReuseMap(p);
|
|
1911
|
+
for (const e of entries) {
|
|
1912
|
+
allEntries.push({ project: path.basename(p), projectPath: p, entry: e });
|
|
1913
|
+
for (const dep of e.dependsOn) {
|
|
1914
|
+
allEdges.push({ fromProject: path.basename(p), fromCap: e.capability, toCap: dep });
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// 2) targetCapability를 의존하는 capability 식별 (역방향)
|
|
1920
|
+
const target = String(targetCapability);
|
|
1921
|
+
const targetLower = target.toLowerCase();
|
|
1922
|
+
const directImpact = allEdges.filter(e => e.toCap.toLowerCase() === targetLower);
|
|
1923
|
+
const impactedProjects = new Set(directImpact.map(e => e.fromProject));
|
|
1924
|
+
|
|
1925
|
+
// 2단계 전이: 영향받은 capability를 또 의존하는 것들 (2-hop)
|
|
1926
|
+
const transitiveImpact = [];
|
|
1927
|
+
for (const e1 of directImpact) {
|
|
1928
|
+
for (const e2 of allEdges) {
|
|
1929
|
+
if (e2.toCap.toLowerCase() === e1.fromCap.toLowerCase()) {
|
|
1930
|
+
transitiveImpact.push({ via: e1.fromCap, ...e2 });
|
|
1931
|
+
impactedProjects.add(e2.fromProject);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// target capability 자체가 어디 등록됐는지
|
|
1937
|
+
const definedAt = allEntries.filter(e => e.entry.capability.toLowerCase() === targetLower);
|
|
1938
|
+
|
|
1939
|
+
if (has('--json')) {
|
|
1940
|
+
log(JSON.stringify({
|
|
1941
|
+
target,
|
|
1942
|
+
definedAt: definedAt.map(d => ({ project: d.project, element: d.entry.element })),
|
|
1943
|
+
directImpact,
|
|
1944
|
+
transitiveImpact,
|
|
1945
|
+
impactedProjects: Array.from(impactedProjects)
|
|
1946
|
+
}, null, 2));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
log(`# leerness impact "${target}" (1.9.24)`);
|
|
1951
|
+
log('');
|
|
1952
|
+
log(`## 정의 위치`);
|
|
1953
|
+
if (!definedAt.length) {
|
|
1954
|
+
log(` ⚠ "${target}" capability가 reuse-map에 등록되지 않음 — 영향 추적 불가`);
|
|
1955
|
+
return process.exit(1);
|
|
1956
|
+
}
|
|
1957
|
+
for (const d of definedAt) log(` - ${d.project}: ${d.entry.element}`);
|
|
1958
|
+
|
|
1959
|
+
log('');
|
|
1960
|
+
log(`## 직접 의존 (1-hop, ${directImpact.length}건)`);
|
|
1961
|
+
if (!directImpact.length) log(` (없음) — 단독 capability. 변경 안전.`);
|
|
1962
|
+
for (const e of directImpact) log(` - ${e.fromProject}/${e.fromCap}`);
|
|
1963
|
+
|
|
1964
|
+
if (transitiveImpact.length) {
|
|
1965
|
+
log('');
|
|
1966
|
+
log(`## 전이 의존 (2-hop, ${transitiveImpact.length}건)`);
|
|
1967
|
+
for (const e of transitiveImpact) log(` - ${e.fromProject}/${e.fromCap} (경유: ${e.via})`);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
log('');
|
|
1971
|
+
log(`## 영향받는 프로젝트 (${impactedProjects.size}개)`);
|
|
1972
|
+
for (const p of impactedProjects) log(` - ${p}`);
|
|
1973
|
+
|
|
1974
|
+
// 3) --run-tests 옵션이면 영향받은 프로젝트의 npm test 일괄 실행
|
|
1975
|
+
if (has('--run-tests')) {
|
|
1976
|
+
log('');
|
|
1977
|
+
log(`## 🚦 자동 회귀 sweep (--run-tests)`);
|
|
1978
|
+
const results = [];
|
|
1979
|
+
for (const projName of impactedProjects) {
|
|
1980
|
+
const projPath = allPaths.find(p => path.basename(p) === projName);
|
|
1981
|
+
if (!projPath) continue;
|
|
1982
|
+
const pkgPath = path.join(projPath, 'package.json');
|
|
1983
|
+
if (!exists(pkgPath)) { log(` ⚠ ${projName}: package.json 없음 — skip`); continue; }
|
|
1984
|
+
let pkg = null;
|
|
1985
|
+
try { pkg = JSON.parse(read(pkgPath)); } catch {}
|
|
1986
|
+
if (!pkg?.scripts?.test) { log(` ⚠ ${projName}: scripts.test 없음 — skip`); continue; }
|
|
1987
|
+
const t0 = Date.now();
|
|
1988
|
+
const r = cp.spawnSync('npm test', [], { cwd: projPath, encoding: 'utf8', shell: true, timeout: 5 * 60 * 1000 });
|
|
1989
|
+
const elapsed = Date.now() - t0;
|
|
1990
|
+
const out = (r.stdout || '') + (r.stderr || '');
|
|
1991
|
+
const m = out.match(/(\d+)\s*\/\s*(\d+)\s*(?:passed|통과|pass|passing)/i);
|
|
1992
|
+
const passed = r.status === 0;
|
|
1993
|
+
results.push({ project: projName, passed, exit: r.status, elapsed, parsed: m ? { num: parseInt(m[1], 10), denom: parseInt(m[2], 10) } : null });
|
|
1994
|
+
const tag = passed ? '✓' : '✗';
|
|
1995
|
+
const ratio = m ? ` (${m[1]}/${m[2]})` : '';
|
|
1996
|
+
log(` ${tag} ${projName}: exit=${r.status}${ratio} ${elapsed}ms`);
|
|
1997
|
+
}
|
|
1998
|
+
log('');
|
|
1999
|
+
const pass = results.filter(r => r.passed).length;
|
|
2000
|
+
const fail = results.length - pass;
|
|
2001
|
+
log(`## 종합`);
|
|
2002
|
+
log(` - 영향받는 프로젝트 ${impactedProjects.size}개 중 ${pass}개 통과, ${fail}개 실패`);
|
|
2003
|
+
if (fail > 0) {
|
|
2004
|
+
log(` ⚠ ${target} 변경이 ${fail}개 프로젝트에 회귀 발생 가능 — 해당 프로젝트 testing 우선`);
|
|
2005
|
+
return process.exit(1);
|
|
2006
|
+
} else {
|
|
2007
|
+
log(` ✓ 모든 영향받는 프로젝트 회귀 없음 — ${target} 변경 안전`);
|
|
2008
|
+
}
|
|
2009
|
+
} else {
|
|
2010
|
+
log('');
|
|
2011
|
+
log(` 💡 \`--run-tests\` 옵션으로 영향받는 ${impactedProjects.size}개 프로젝트 npm test 자동 일괄 실행 가능`);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
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
|
+
|
|
1864
2050
|
// 1.9.22 후보 4: llm-bench record + retro 통합
|
|
1865
2051
|
function llmBenchRecordCmd(root) {
|
|
1866
2052
|
root = absRoot(root || process.cwd());
|
|
@@ -2382,7 +2568,7 @@ function _brainstormFor(root, topic) {
|
|
|
2382
2568
|
const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
|
|
2383
2569
|
const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
|
|
2384
2570
|
function matches(text) { return wordRes.every(re => re.test(text)); }
|
|
2385
|
-
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [] };
|
|
2571
|
+
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
|
|
2386
2572
|
const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
2387
2573
|
const decLines = dec.split('\n');
|
|
2388
2574
|
for (const b of _extractDecisionBlocks(dec)) {
|
|
@@ -2438,10 +2624,38 @@ function _brainstormFor(root, topic) {
|
|
|
2438
2624
|
if (/✗|fail|롤백|incomplete|버그/i.test(block)) hits.lessons.push({ title: t.trim(), line: lineNo });
|
|
2439
2625
|
}
|
|
2440
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
|
+
}
|
|
2441
2655
|
return hits;
|
|
2442
2656
|
}
|
|
2443
2657
|
|
|
2444
|
-
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); }
|
|
2445
2659
|
|
|
2446
2660
|
// 1.9.16: 워크스페이스 통합 brainstorm
|
|
2447
2661
|
function _brainstormWorkspace(rootBase, topic) {
|
|
@@ -2483,8 +2697,13 @@ function _brainstormWorkspace(rootBase, topic) {
|
|
|
2483
2697
|
if (h.lessons.length) {
|
|
2484
2698
|
log(` ⚠ 과거 실패/롤백 (${h.lessons.length})`);
|
|
2485
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
|
+
}
|
|
2486
2705
|
}
|
|
2487
|
-
log(`\n## 📊 워크스페이스 총합: ${grandTotal}건 매치 (${paths.length} 프로젝트)`);
|
|
2706
|
+
log(`\n## 📊 워크스페이스 총합: ${grandTotal}건 매치 (${paths.length} 프로젝트)${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
|
|
2488
2707
|
if (grandTotal === 0) log(` ⓘ 어느 프로젝트에서도 "${topic}" 관련 자원 없음 — 새 영역. 첫 결정/스킬을 기록하면 다음 brainstorm이 풍부해짐.`);
|
|
2489
2708
|
}
|
|
2490
2709
|
|
|
@@ -2509,7 +2728,7 @@ function brainstormCmd(root, topic) {
|
|
|
2509
2728
|
const tokens = String(topic).split(/\s+/).filter(t => t.length >= 2);
|
|
2510
2729
|
const wordRes = tokens.map(t => new RegExp(`(?<![\\p{L}\\p{N}_])${_escUnicode(t)}(?![\\p{L}\\p{N}_])`, 'iu'));
|
|
2511
2730
|
function matches(text) { return wordRes.every(re => re.test(text)); }
|
|
2512
|
-
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [] };
|
|
2731
|
+
const hits = { decisions: [], skills: [], tasks: [], rules: [], evidence: [], lessons: [], code: [] };
|
|
2513
2732
|
|
|
2514
2733
|
// decisions (1.9.14: 코드블록/Template 제외, 1.9.15: 라인 번호)
|
|
2515
2734
|
const dec = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
@@ -3877,7 +4096,7 @@ function viewworkInstall(root) {
|
|
|
3877
4096
|
}
|
|
3878
4097
|
|
|
3879
4098
|
function help() {
|
|
3880
|
-
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 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
|
|
3881
4100
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
3882
4101
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
3883
4102
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|
|
@@ -3915,6 +4134,8 @@ async function main() {
|
|
|
3915
4134
|
if (cmd === 'verify-claim') return verifyClaimCmd(arg('--path', process.cwd()), args[1]);
|
|
3916
4135
|
if (cmd === 'orchestrate') return await orchestrateCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
|
|
3917
4136
|
if (cmd === 'llm-bench' && args[1] === 'record') return llmBenchRecordCmd(arg('--path', process.cwd()));
|
|
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('-')));
|
|
3918
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; }
|
|
3919
4140
|
if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
|
|
3920
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
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
|
{
|