leerness 1.9.23 → 1.9.24

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.24 — 2026-05-14
4
+
5
+ **`leerness deps <capability>` — depends-on 그래프 역방향 추적 + 자동 회귀 sweep**.
6
+
7
+ 오래된 작업 재진행 시 / 핵심 모듈 변경 시 영향받는 모듈을 자동 식별 + 해당 프로젝트의 `npm test`를 일괄 실행.
8
+
9
+ ### Added
10
+
11
+ - **`leerness deps <capability>`**: 워크스페이스 모든 `reuse-map.md`의 depends-on 엣지를 역방향 추적해 해당 capability를 의존하는 모든 capability와 프로젝트를 식별. 1-hop(직접 의존) + 2-hop(전이 의존) 모두 표시.
12
+ - **`leerness deps <capability> --run-tests`**: 영향받는 N개 프로젝트의 `npm test`를 자동 일괄 실행. 회귀 발견 시 어느 프로젝트인지 즉시 보고 + `exit 1`. CI 통합용 `--json`.
13
+ - 실측: `leerness deps Character --all-apps --run-tests` 실행 시 rpg-core의 `Character` capability에 의존하는 **6 프로젝트(8 capability) 자동 식별 + 6/6 npm test 자동 일괄 실행**.
14
+
15
+ ### Why
16
+ 오래된 작업을 재진행하거나 핵심 모듈을 변경할 때, 영향받는 다른 프로젝트를 수동으로 grep하던 패턴을 1 명령으로 자동화. depends-on 그래프(1.9.18부터 수집)가 활용됨.
17
+
18
+ ### Migration
19
+ ```bash
20
+ npx leerness@latest update . --yes
21
+ # 사용 예
22
+ leerness deps Character --all-apps --run-tests
23
+ ```
24
+
3
25
  ## 1.9.23 — 2026-05-14
4
26
 
5
27
  **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.23';
9
+ const VERSION = '1.9.24';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -1861,6 +1861,131 @@ async function orchestrateCmd(root, goalParts) {
1861
1861
  log(`📜 누적 기록: .harness/orchestrate-log.md`);
1862
1862
  }
1863
1863
 
1864
+ // 1.9.24: leerness deps <capability> — depends-on 그래프 역방향 추적 + 자동 회귀 sweep
1865
+ // 사용 예: leerness deps Character
1866
+ // → rpg-core/Character를 의존하는 모든 capability 식별 (rpg-net/Session, rpg-data/* 등)
1867
+ // → 영향받은 프로젝트의 npm test 자동 일괄 실행
1868
+ // → 회귀 발생 시 어느 프로젝트인지 즉시 보고
1869
+ function depsImpactCmd(root, targetCapability) {
1870
+ root = absRoot(root || process.cwd());
1871
+ if (!targetCapability) { fail('impact <capability> 필요. 예: leerness impact Character'); return process.exit(1); }
1872
+ const paths = _collectWorkspacePaths(root);
1873
+ if (!paths.length) {
1874
+ // --all-apps 자동
1875
+ process.argv.push('--all-apps');
1876
+ }
1877
+ const allPaths = _collectWorkspacePaths(root);
1878
+ if (!allPaths.length) return fail('워크스페이스 프로젝트 없음. _apps/* 또는 --include 사용.');
1879
+
1880
+ // 1) 모든 reuse-map에서 entries + depends-on 엣지 수집
1881
+ const allEntries = []; // { project, entry }
1882
+ const allEdges = []; // { fromProject, fromCap, toCap }
1883
+ for (const p of allPaths) {
1884
+ const entries = _readReuseMap(p);
1885
+ for (const e of entries) {
1886
+ allEntries.push({ project: path.basename(p), projectPath: p, entry: e });
1887
+ for (const dep of e.dependsOn) {
1888
+ allEdges.push({ fromProject: path.basename(p), fromCap: e.capability, toCap: dep });
1889
+ }
1890
+ }
1891
+ }
1892
+
1893
+ // 2) targetCapability를 의존하는 capability 식별 (역방향)
1894
+ const target = String(targetCapability);
1895
+ const targetLower = target.toLowerCase();
1896
+ const directImpact = allEdges.filter(e => e.toCap.toLowerCase() === targetLower);
1897
+ const impactedProjects = new Set(directImpact.map(e => e.fromProject));
1898
+
1899
+ // 2단계 전이: 영향받은 capability를 또 의존하는 것들 (2-hop)
1900
+ const transitiveImpact = [];
1901
+ for (const e1 of directImpact) {
1902
+ for (const e2 of allEdges) {
1903
+ if (e2.toCap.toLowerCase() === e1.fromCap.toLowerCase()) {
1904
+ transitiveImpact.push({ via: e1.fromCap, ...e2 });
1905
+ impactedProjects.add(e2.fromProject);
1906
+ }
1907
+ }
1908
+ }
1909
+
1910
+ // target capability 자체가 어디 등록됐는지
1911
+ const definedAt = allEntries.filter(e => e.entry.capability.toLowerCase() === targetLower);
1912
+
1913
+ if (has('--json')) {
1914
+ log(JSON.stringify({
1915
+ target,
1916
+ definedAt: definedAt.map(d => ({ project: d.project, element: d.entry.element })),
1917
+ directImpact,
1918
+ transitiveImpact,
1919
+ impactedProjects: Array.from(impactedProjects)
1920
+ }, null, 2));
1921
+ return;
1922
+ }
1923
+
1924
+ log(`# leerness impact "${target}" (1.9.24)`);
1925
+ log('');
1926
+ log(`## 정의 위치`);
1927
+ if (!definedAt.length) {
1928
+ log(` ⚠ "${target}" capability가 reuse-map에 등록되지 않음 — 영향 추적 불가`);
1929
+ return process.exit(1);
1930
+ }
1931
+ for (const d of definedAt) log(` - ${d.project}: ${d.entry.element}`);
1932
+
1933
+ log('');
1934
+ log(`## 직접 의존 (1-hop, ${directImpact.length}건)`);
1935
+ if (!directImpact.length) log(` (없음) — 단독 capability. 변경 안전.`);
1936
+ for (const e of directImpact) log(` - ${e.fromProject}/${e.fromCap}`);
1937
+
1938
+ if (transitiveImpact.length) {
1939
+ log('');
1940
+ log(`## 전이 의존 (2-hop, ${transitiveImpact.length}건)`);
1941
+ for (const e of transitiveImpact) log(` - ${e.fromProject}/${e.fromCap} (경유: ${e.via})`);
1942
+ }
1943
+
1944
+ log('');
1945
+ log(`## 영향받는 프로젝트 (${impactedProjects.size}개)`);
1946
+ for (const p of impactedProjects) log(` - ${p}`);
1947
+
1948
+ // 3) --run-tests 옵션이면 영향받은 프로젝트의 npm test 일괄 실행
1949
+ if (has('--run-tests')) {
1950
+ log('');
1951
+ log(`## 🚦 자동 회귀 sweep (--run-tests)`);
1952
+ const results = [];
1953
+ for (const projName of impactedProjects) {
1954
+ const projPath = allPaths.find(p => path.basename(p) === projName);
1955
+ if (!projPath) continue;
1956
+ const pkgPath = path.join(projPath, 'package.json');
1957
+ if (!exists(pkgPath)) { log(` ⚠ ${projName}: package.json 없음 — skip`); continue; }
1958
+ let pkg = null;
1959
+ try { pkg = JSON.parse(read(pkgPath)); } catch {}
1960
+ if (!pkg?.scripts?.test) { log(` ⚠ ${projName}: scripts.test 없음 — skip`); continue; }
1961
+ const t0 = Date.now();
1962
+ const r = cp.spawnSync('npm test', [], { cwd: projPath, encoding: 'utf8', shell: true, timeout: 5 * 60 * 1000 });
1963
+ const elapsed = Date.now() - t0;
1964
+ const out = (r.stdout || '') + (r.stderr || '');
1965
+ const m = out.match(/(\d+)\s*\/\s*(\d+)\s*(?:passed|통과|pass|passing)/i);
1966
+ const passed = r.status === 0;
1967
+ results.push({ project: projName, passed, exit: r.status, elapsed, parsed: m ? { num: parseInt(m[1], 10), denom: parseInt(m[2], 10) } : null });
1968
+ const tag = passed ? '✓' : '✗';
1969
+ const ratio = m ? ` (${m[1]}/${m[2]})` : '';
1970
+ log(` ${tag} ${projName}: exit=${r.status}${ratio} ${elapsed}ms`);
1971
+ }
1972
+ log('');
1973
+ const pass = results.filter(r => r.passed).length;
1974
+ const fail = results.length - pass;
1975
+ log(`## 종합`);
1976
+ log(` - 영향받는 프로젝트 ${impactedProjects.size}개 중 ${pass}개 통과, ${fail}개 실패`);
1977
+ if (fail > 0) {
1978
+ log(` ⚠ ${target} 변경이 ${fail}개 프로젝트에 회귀 발생 가능 — 해당 프로젝트 testing 우선`);
1979
+ return process.exit(1);
1980
+ } else {
1981
+ log(` ✓ 모든 영향받는 프로젝트 회귀 없음 — ${target} 변경 안전`);
1982
+ }
1983
+ } else {
1984
+ log('');
1985
+ log(` 💡 \`--run-tests\` 옵션으로 영향받는 ${impactedProjects.size}개 프로젝트 npm test 자동 일괄 실행 가능`);
1986
+ }
1987
+ }
1988
+
1864
1989
  // 1.9.22 후보 4: llm-bench record + retro 통합
1865
1990
  function llmBenchRecordCmd(root) {
1866
1991
  root = absRoot(root || process.cwd());
@@ -3877,7 +4002,7 @@ function viewworkInstall(root) {
3877
4002
  }
3878
4003
 
3879
4004
  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
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
3881
4006
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
3882
4007
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
3883
4008
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -3915,6 +4040,7 @@ async function main() {
3915
4040
  if (cmd === 'verify-claim') return verifyClaimCmd(arg('--path', process.cwd()), args[1]);
3916
4041
  if (cmd === 'orchestrate') return await orchestrateCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
3917
4042
  if (cmd === 'llm-bench' && args[1] === 'record') return llmBenchRecordCmd(arg('--path', process.cwd()));
4043
+ if (cmd === 'deps') return depsImpactCmd(arg('--path', process.cwd()), args[1]);
3918
4044
  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
4045
  if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
3920
4046
  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.23",
3
+ "version": "1.9.24",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",