leerness 1.36.34 → 1.36.35

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.35 — 2026-07-15 — deps --run-tests 정직화 + #7 의도적 DEFER (codex 3차 잔여 소진)
4
+
5
+ - **#6 `deps <cap> --run-tests`**: (a) `--json` 이 테스트 스윕 **전에** return → 스윕이 무언 무시되고 exit 0(false-green) — json/human 이 같은 `_runImpactSweep` 를 쓰도록 공용화, json 은 `tests[]` 필드 + 실패 시 exit 1. (b) 프로젝트를 basename 으로 역추적해 **동명 프로젝트(a/service, b/service) 중 하나만 테스트**되고 실패쪽이 조용히 생략 — 절대경로 키로 교정. 실측: 동명 2 프로젝트 모두 실행(PASS/FAIL 각각), json/human 동등, 실패 exit 1.
6
+ - **#7 (주석 트레이스 오인) 의도적 DEFER — 정직 기록**: 주석 속 `fetch('/users')` 가 낙관검사 트레이스로 인정되는 건 사실이나, 교정에는 JS 주석 마스킹이 필요하다. 이 마스커 계열은 1.36.18 에서 7회 반증 끝에 리버트한 전력이 있고(0-deps 제약 하에 파서 없이 안전 불가), 잘못 벗기면 verify-claim `--strict-claims` 가 **false-BLOCK**(허위 실패로 머지 차단) 하는 방향이라 게이트 편향 원칙(false-PASS 편향)에 반한다. 현 한계는 문서화된 known-limitation 으로 유지: 낙관검사는 "주장 vs 텍스트 흔적" 수준의 advisory 신호다.
7
+ - **검증**: selftest 305/305(스윕 공용+절대경로+exit 배선; 자기참조 트랩 4번째 재발 → split-literal), 동명 충돌 원 재현 before/after, exit 전파형 게이트 e2e, 클린룸. **codex 3차 헌트 10건 전건 처분 완료(수정 9 + 정직 DEFER 1).**
8
+
3
9
  ## 1.36.34 — 2026-07-15 — 판정 정직화 5종 — 도구가 내리는 판정(허용/만료/최신/집계)이 거짓말하지 않도록 (codex 3차 #4/#5/#8/#9/#10)
4
10
 
5
11
  - **#8 policy check**: 차단 판정인데 exit 0 → **exit 1**(CI/스크립트가 판정 신뢰 가능). 손상 policy.json 을 기본값 폴백으로 오답하던 것 → `policy_corrupt` 구조화 에러.
package/README.md CHANGED
@@ -121,7 +121,7 @@ MIT
121
121
  <!-- leerness:project-readme:start -->
122
122
  ## Leerness Project Harness
123
123
 
124
- 이 프로젝트는 Leerness v1.36.34 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
124
+ 이 프로젝트는 Leerness v1.36.35 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
125
 
126
126
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
127
127
 
@@ -175,7 +175,7 @@ leerness memory restore decision <date|title>
175
175
 
176
176
  ### MCP server (외부 AI 통합)
177
177
 
178
- Leerness v1.36.34는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
178
+ Leerness v1.36.35는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
179
 
180
180
  ```jsonc
181
181
  // 카테고리별
@@ -196,7 +196,7 @@ Leerness v1.36.34는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
196
196
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
197
197
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push → 7) session close → 8) 다음 라운드 예약.
198
198
 
199
- 현재 누적: **70 라운드 (1.9.40 → 1.36.34)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
199
+ 현재 누적: **70 라운드 (1.9.40 → 1.36.35)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
200
200
 
201
201
  ### 성능 가이드
202
202
 
@@ -234,6 +234,6 @@ leerness release pack --close --auto-main-push
234
234
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
235
235
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
236
236
 
237
- Last synced by Leerness v1.36.34: 2026-07-15
237
+ Last synced by Leerness v1.36.35: 2026-07-15
238
238
  <!-- leerness:project-readme:end -->
239
239
 
package/bin/leerness.js CHANGED
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
34
34
  const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
35
35
  const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
36
36
 
37
- const VERSION = '1.36.34';
37
+ const VERSION = '1.36.35';
38
38
 
39
39
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
40
40
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -3168,6 +3168,16 @@ function _selfTestCases() {
3168
3168
  const tarOk = s.includes('const _qw = a =>') && s.includes("read(path.join(root, '.harness', 'HARNESS_VERSION')).trim(); if (_hv) nextLeerness = _hv;");
3169
3169
  return semverOk && cacheOk && policyOk && credsOk && closeOk && tarOk;
3170
3170
  } },
3171
+ { name: 'deps --run-tests 정직화 (1.36.35, codex 3차 #6): json/human 스윕 공용 + 절대경로 키(동명 프로젝트 오식별 방지) + 실패 시 exit1 (소스가드)', run: () => {
3172
+ const s = read(__filename);
3173
+ const sweepShared = s.includes('function _runImpactSweep(quietLog)') && s.includes("const tests = has('--run-tests') ? _runImpactSweep(true) : undefined;");
3174
+ const absKey = s.includes('fromProjectPath: p,') && s.includes('const impactedPaths = new Set(directImpact.map(e => e.fromProjectPath)');
3175
+ const jsonExit = s.includes('if (tests && tests.some(t => t.passed === false)) process.exitCode = 1;');
3176
+ const humanShared = s.includes('const results = _runImpactSweep(false);');
3177
+ // 구식 basename 역추적이 사라졌는지 (오식별 코드 부재) — split-literal: 이 줄 자신 매치 방지 (자기참조 트랩 4번째)
3178
+ const oldGone = !s.includes('const projPath = allPaths.find(' + 'p => path.basename(p) === projName);');
3179
+ return sweepShared && absKey && jsonExit && humanShared && oldGone;
3180
+ } },
3171
3181
  { name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
3172
3182
  const d = LENS_CATALOG.debug;
3173
3183
  if (!d) return false;
@@ -12338,7 +12348,7 @@ function depsImpactCmd(root, targetCapability) {
12338
12348
  for (const e of entries) {
12339
12349
  allEntries.push({ project: path.basename(p), projectPath: p, entry: e });
12340
12350
  for (const dep of e.dependsOn) {
12341
- allEdges.push({ fromProject: path.basename(p), fromCap: e.capability, toCap: dep });
12351
+ allEdges.push({ fromProject: path.basename(p), fromProjectPath: p, fromCap: e.capability, toCap: dep }); // 1.36.35 (#6): 절대경로 동반 — basename 충돌 시 프로젝트 오식별 방지
12342
12352
  }
12343
12353
  }
12344
12354
  }
@@ -12348,6 +12358,7 @@ function depsImpactCmd(root, targetCapability) {
12348
12358
  const targetLower = target.toLowerCase();
12349
12359
  const directImpact = allEdges.filter(e => e.toCap.toLowerCase() === targetLower);
12350
12360
  const impactedProjects = new Set(directImpact.map(e => e.fromProject));
12361
+ const impactedPaths = new Set(directImpact.map(e => e.fromProjectPath).filter(Boolean)); // 1.36.35 (#6): 절대경로 기준 집합
12351
12362
 
12352
12363
  // 2단계 전이: 영향받은 capability를 또 의존하는 것들 (2-hop)
12353
12364
  const transitiveImpact = [];
@@ -12356,6 +12367,7 @@ function depsImpactCmd(root, targetCapability) {
12356
12367
  if (e2.toCap.toLowerCase() === e1.fromCap.toLowerCase()) {
12357
12368
  transitiveImpact.push({ via: e1.fromCap, ...e2 });
12358
12369
  impactedProjects.add(e2.fromProject);
12370
+ if (e2.fromProjectPath) impactedPaths.add(e2.fromProjectPath);
12359
12371
  }
12360
12372
  }
12361
12373
  }
@@ -12363,13 +12375,39 @@ function depsImpactCmd(root, targetCapability) {
12363
12375
  // target capability 자체가 어디 등록됐는지
12364
12376
  const definedAt = allEntries.filter(e => e.entry.capability.toLowerCase() === targetLower);
12365
12377
 
12378
+ // 1.36.35 (#6): 테스트 스윕 — json/human 공용 (종전엔 json 이 스윕 전에 return 해 --run-tests 가 무언 무시됐고,
12379
+ // basename 으로 경로를 역추적해 동명 프로젝트 중 하나만 테스트됐다). 절대경로 순회로 둘 다 교정.
12380
+ function _runImpactSweep(quietLog) {
12381
+ const results = [];
12382
+ for (const projPath of impactedPaths) {
12383
+ const projName = path.basename(projPath);
12384
+ const pkgPath = path.join(projPath, 'package.json');
12385
+ if (!exists(pkgPath)) { if (!quietLog) log(` ⚠ ${projName}: package.json 없음 — skip`); results.push({ project: projName, projectPath: projPath, skipped: 'no_package_json' }); continue; }
12386
+ let pkg = null;
12387
+ try { pkg = JSON.parse(read(pkgPath)); } catch {}
12388
+ if (!pkg?.scripts?.test) { if (!quietLog) log(` ⚠ ${projName}: scripts.test 없음 — skip`); results.push({ project: projName, projectPath: projPath, skipped: 'no_test_script' }); continue; }
12389
+ const t0 = Date.now();
12390
+ const r = runCommandSafe('npm test', [], { cwd: projPath, root: projPath, encoding: 'utf8', allowShell: true, scrubSecrets: true, timeout: 5 * 60 * 1000, kind: 'reuse_impact_test' });
12391
+ const elapsed = Date.now() - t0;
12392
+ const out = (r.stdout || '') + (r.stderr || '');
12393
+ const passed = r.status === 0;
12394
+ results.push({ project: projName, projectPath: projPath, passed, status: r.status, elapsedMs: elapsed });
12395
+ if (!quietLog) log(` ${passed ? '✓' : '✗'} ${projName}: ${passed ? 'PASS' : `FAIL (exit ${r.status})`} · ${Math.round(elapsed / 1000)}s`);
12396
+ }
12397
+ return results;
12398
+ }
12399
+
12366
12400
  if (has('--json')) {
12401
+ const tests = has('--run-tests') ? _runImpactSweep(true) : undefined;
12402
+ if (tests && tests.some(t => t.passed === false)) process.exitCode = 1;
12367
12403
  log(JSON.stringify({
12368
12404
  target,
12369
12405
  definedAt: definedAt.map(d => ({ project: d.project, element: d.entry.element })),
12370
12406
  directImpact,
12371
12407
  transitiveImpact,
12372
- impactedProjects: Array.from(impactedProjects)
12408
+ impactedProjects: Array.from(impactedProjects),
12409
+ impactedProjectPaths: Array.from(impactedPaths),
12410
+ ...(tests ? { tests } : {})
12373
12411
  }, null, 2));
12374
12412
  return;
12375
12413
  }
@@ -12402,32 +12440,12 @@ function depsImpactCmd(root, targetCapability) {
12402
12440
  if (has('--run-tests')) {
12403
12441
  log('');
12404
12442
  log(`## 🚦 자동 회귀 sweep (--run-tests)`);
12405
- const results = [];
12406
- for (const projName of impactedProjects) {
12407
- const projPath = allPaths.find(p => path.basename(p) === projName);
12408
- if (!projPath) continue;
12409
- const pkgPath = path.join(projPath, 'package.json');
12410
- if (!exists(pkgPath)) { log(` ⚠ ${projName}: package.json 없음 — skip`); continue; }
12411
- let pkg = null;
12412
- try { pkg = JSON.parse(read(pkgPath)); } catch {}
12413
- if (!pkg?.scripts?.test) { log(` ⚠ ${projName}: scripts.test 없음 — skip`); continue; }
12414
- const t0 = Date.now();
12415
- // 1.9.299 (UR-0039): 영향 프로젝트 npm test → runCommandSafe + scrubSecrets (시크릿 노출 차단). root=projPath 라 cwd jail 통과.
12416
- const r = runCommandSafe('npm test', [], { cwd: projPath, root: projPath, encoding: 'utf8', allowShell: true, scrubSecrets: true, timeout: 5 * 60 * 1000, kind: 'reuse_impact_test' });
12417
- const elapsed = Date.now() - t0;
12418
- const out = (r.stdout || '') + (r.stderr || '');
12419
- const m = out.match(/(\d+)\s*\/\s*(\d+)\s*(?:passed|통과|pass|passing)/i);
12420
- const passed = r.status === 0;
12421
- results.push({ project: projName, passed, exit: r.status, elapsed, parsed: m ? { num: parseInt(m[1], 10), denom: parseInt(m[2], 10) } : null });
12422
- const tag = passed ? '✓' : '✗';
12423
- const ratio = m ? ` (${m[1]}/${m[2]})` : '';
12424
- log(` ${tag} ${projName}: exit=${r.status}${ratio} ${elapsed}ms`);
12425
- }
12443
+ const results = _runImpactSweep(false); // 1.36.35 (#6): json 과 동일한 절대경로 스윕 공용
12426
12444
  log('');
12427
12445
  const pass = results.filter(r => r.passed).length;
12428
- const fail = results.length - pass;
12446
+ const fail = results.filter(r => r.passed === false).length;
12429
12447
  log(`## 종합`);
12430
- log(` - 영향받는 프로젝트 ${impactedProjects.size}개 중 ${pass}개 통과, ${fail}개 실패`);
12448
+ log(` - 영향받는 프로젝트 ${impactedPaths.size}개 중 ${pass}개 통과, ${fail}개 실패`);
12431
12449
  if (fail > 0) {
12432
12450
  log(` ⚠ ${target} 변경이 ${fail}개 프로젝트에 회귀 발생 가능 — 해당 프로젝트 testing 우선`);
12433
12451
  return process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.34",
3
+ "version": "1.36.35",
4
4
  "description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
5
5
  "keywords": [
6
6
  "leerness",