leerness 1.9.425 → 1.9.427
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 +36 -0
- package/README.md +5 -5
- package/bin/leerness.js +41 -7
- package/lib/health.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.427 — 2026-06-07 — DI 완전성 메타체크: 모듈화 회귀 근본 차단 (UR-0131)
|
|
4
|
+
|
|
5
|
+
**🛡️ 10번째 외부평가의 구조적 발견(selftest가 DI 누락 회귀를 못 잡음) 근본 해결.**
|
|
6
|
+
|
|
7
|
+
### 변경
|
|
8
|
+
- **selftest 신규 케이스**: 6개 DI 모듈(review-request/audit/drift/health/agents/session-close) 각각의 `const {...} = deps` destructure 이름이 **bin wrapper 의 전달 deps 객체에 모두 포함**되는지 소스 분석으로 검증. 누락 시 selftest 실패 → 향후 모듈 추출에서 `STATUSES`류 dep 누락(1.9.423 회귀)을 **자동 적발**.
|
|
9
|
+
- 자기참조 함정 회피: 케이스의 regex 리터럴이 자신을 매치하지 않도록 `return ` 프리픽스로 실제 wrapper 만 타깃.
|
|
10
|
+
|
|
11
|
+
### 검증 (회귀 0)
|
|
12
|
+
- **selftest 172→173 PASS**. 회귀 적발력 확인: health destructure 에서 STATUSES 임시 제거 시 메타체크가 정확히 실패, 복원 시 통과.
|
|
13
|
+
- **E2E 무회귀**.
|
|
14
|
+
|
|
15
|
+
## 1.9.426 — 2026-06-07 — 10번째 외부평가: 모듈화 회귀(health) 수정 + contract/rule 일관성 (UR-0128~0131)
|
|
16
|
+
|
|
17
|
+
**🔍 10번째 외부 멀티모델 리뷰(Codex GPT-5.5 + Claude Sonnet/Opus 4.8, README 미참조 클린룸, 1.9.425)로 6회 모듈화 직후 무결성 독립 검증 — 모듈화 회귀 1건 적발·수정.**
|
|
18
|
+
|
|
19
|
+
### 배경
|
|
20
|
+
6회 큰 핸들러 추출 직후 코드-위험 0인 독립 리뷰로 회귀 여부 확인. 모든 발견 직접 재현·검증(맹신 X).
|
|
21
|
+
|
|
22
|
+
### 수정 (확정·저위험)
|
|
23
|
+
- **[P1 모듈화 회귀] health memorySurface 항상 실패**: 1.9.423 health 추출 시 종합 의존 스캔이 **대문자 상수 `STATUSES`를 제외**해 DI 누락 → `health --json` 의 `memorySurface` 가 항상 `{error}`. **Codex·Sonnet 독립 동시 적발**(Opus는 놓침). STATUSES 주입으로 수정. (session-close 1.9.425에서 STATUSES/MARK 교훈을 얻기 전 라운드라 누락됨.)
|
|
24
|
+
- **[P3] contract verify 텍스트 `tick.` 유령 프리픽스**: `## Fields` 범용화(1.9.417) 후에도 누락 필드를 `- tick.createdAt` 로 표기(JSON 은 정상). 레거시 프리픽스 제거.
|
|
25
|
+
- **[P2] rule add 경로/trigger 값 흡수**: `rule add "설명" --trigger X /path` 가 설명에 `X`·`/path` 를 흡수. `_parseAddTitle`(1.9.416) 적용으로 flag/경로 break.
|
|
26
|
+
|
|
27
|
+
### 회귀가드 강화
|
|
28
|
+
- health selftest 가 **STATUSES 주입 + memorySurface 비-error** 를 검증하도록 강화(리뷰 지적: 기존 selftest 가 이 회귀를 못 잡음).
|
|
29
|
+
|
|
30
|
+
### 검증 (회귀 0)
|
|
31
|
+
- **selftest 171→172 PASS** · **E2E 425→426 PASS**.
|
|
32
|
+
|
|
33
|
+
### 3모델 긍정 평가
|
|
34
|
+
데이터무결성·인코딩·시크릿·**동시성(8-way 무손실)**·**DI 모듈 경계**가 동급 대비 견고. P0/P1 데이터손상·크래시 없음. (Opus: "DI 구조 깨끗 + wrapper composition root 견고".)
|
|
35
|
+
|
|
36
|
+
### 후속 백로그 (UR-0128~0131, 다음 라운드)
|
|
37
|
+
--json 전 경로 순수성(drift --auto-fix/check/plan show/review-request/audit --fix) · contract impl 파서 강화(멀티라인/ESM) · health/audit/drift exit code 정책 · selftest DI 완전성 메타체크 + drift 재귀 depth 가드.
|
|
38
|
+
|
|
3
39
|
## 1.9.425 — 2026-06-07 — 무거움 점진 해소 6: sessionClose → lib/session-close.js 모듈화 (UR-0025/UR-0125)
|
|
4
40
|
|
|
5
41
|
**🪶 `bin/leerness.js` 무거움 점진 해소 6단계 — `session close` 핸들러(599줄, 9 카테고리 보고)를 lib/ 로 DI 분리.**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -471,7 +471,7 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.427 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
477
477
|
|
|
@@ -525,7 +525,7 @@ leerness memory restore decision <date|title>
|
|
|
525
525
|
|
|
526
526
|
### MCP server (외부 AI 통합)
|
|
527
527
|
|
|
528
|
-
Leerness v1.9.
|
|
528
|
+
Leerness v1.9.427는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
529
529
|
|
|
530
530
|
```jsonc
|
|
531
531
|
// 카테고리별
|
|
@@ -546,7 +546,7 @@ Leerness v1.9.425는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
546
546
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
547
547
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
|
|
548
548
|
|
|
549
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
549
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.427)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
550
550
|
|
|
551
551
|
### 성능 가이드 (1.9.140 측정)
|
|
552
552
|
|
|
@@ -584,6 +584,6 @@ leerness release pack --close --auto-main-push
|
|
|
584
584
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
585
585
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
586
586
|
|
|
587
|
-
Last synced by Leerness v1.9.
|
|
587
|
+
Last synced by Leerness v1.9.427: 2026-06-07
|
|
588
588
|
<!-- leerness:project-readme:end -->
|
|
589
589
|
|
package/bin/leerness.js
CHANGED
|
@@ -31,7 +31,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
31
31
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
32
32
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
|
|
33
33
|
|
|
34
|
-
const VERSION = '1.9.
|
|
34
|
+
const VERSION = '1.9.427';
|
|
35
35
|
|
|
36
36
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
37
37
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3094,6 +3094,13 @@ function _selfTestCases() {
|
|
|
3094
3094
|
const movedToLib = modSrc.includes("require('./io')") && modSrc.includes("require('./agent-registry')") && sigTransform && modSrc.includes(bodyMarker) && !src.includes(bodyMarker);
|
|
3095
3095
|
return expOk && delegated && movedToLib;
|
|
3096
3096
|
} },
|
|
3097
|
+
{ name: '10th 외부평가 Sonnet P2: rule add flag/경로 break(_parseAddTitle) — trigger 값/경로 흡수 차단 (1.9.426)', run: () => {
|
|
3098
|
+
const src = read(__filename);
|
|
3099
|
+
const wired = src.includes("ruleAdd(arg('--path', process.cwd()), _parseAddTitle(args, 2))");
|
|
3100
|
+
const m = require('../lib/pure-utils');
|
|
3101
|
+
const u = m._parseAddTitle(['rule', 'add', '세션', '점검', '--trigger', 'every-session', '/p'], 2) === '세션 점검';
|
|
3102
|
+
return wired && u;
|
|
3103
|
+
} },
|
|
3097
3104
|
{ name: 'UR-0025 큰핸들러 모듈화 8번째: healthCmd → lib/health.js + DI 위임 + 동작 (1.9.423)', run: () => {
|
|
3098
3105
|
const m = require('../lib/health');
|
|
3099
3106
|
const expOk = typeof m.healthCmd === 'function';
|
|
@@ -3101,16 +3108,19 @@ function _selfTestCases() {
|
|
|
3101
3108
|
const delegated = src.includes("require('../lib/health')") && src.includes('_health.healthCmd(root,');
|
|
3102
3109
|
const modSrc = read(path.join(path.dirname(__filename), '..', 'lib', 'health.js'));
|
|
3103
3110
|
const bodyMarker = 'capability' + 'Matrix'; // health 본문 고유(split-literal 자기참조 회피)
|
|
3104
|
-
|
|
3111
|
+
// 1.9.426 (10th 외부평가 회귀가드): wrapper 가 STATUSES 를 주입해야 memorySurface 가 동작(1.9.423 누락 회귀).
|
|
3112
|
+
const statusesWired = modSrc.includes('STATUSES') && /_health\.healthCmd\(root, \{ VERSION, STATUSES,/.test(src);
|
|
3113
|
+
const movedToLib = modSrc.includes("require('./io')") && modSrc.includes("require('./pure-utils')") && modSrc.includes(bodyMarker) && !src.includes(bodyMarker) && statusesWired;
|
|
3105
3114
|
let behavOk = false;
|
|
3106
3115
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_health_'));
|
|
3107
3116
|
const _w = process.stdout.write; let out = '';
|
|
3108
3117
|
try {
|
|
3109
3118
|
fs.mkdirSync(path.join(tmp, '.harness'), { recursive: true });
|
|
3110
3119
|
process.stdout.write = s => { out += s; return true; };
|
|
3111
|
-
m.healthCmd(tmp, { VERSION, has: f => f === '--json', arg: (k, d) => d, harnessPath: path.join(tmp, '__nope.js'), listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount });
|
|
3120
|
+
m.healthCmd(tmp, { VERSION, STATUSES, has: f => f === '--json', arg: (k, d) => d, harnessPath: path.join(tmp, '__nope.js'), listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount });
|
|
3112
3121
|
} catch (e) { out = 'ERR:' + e.message; } finally { process.stdout.write = _w; try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
3113
|
-
|
|
3122
|
+
// memorySurface 가 error 가 아니어야 함(STATUSES 누락 회귀 감지).
|
|
3123
|
+
try { const j = JSON.parse(out); behavOk = typeof j.healthy === 'boolean' && !!j.checks && !!j.memorySurface && !j.memorySurface.error && /^T\d/.test(j.memorySurface.summary || ''); } catch {}
|
|
3114
3124
|
return expOk && delegated && movedToLib && behavOk;
|
|
3115
3125
|
} },
|
|
3116
3126
|
{ name: 'UR-0025 큰핸들러 모듈화 7번째: driftCheckCmd → lib/drift.js + DI 위임 + 재귀/동작 (1.9.422)', run: () => {
|
|
@@ -3175,6 +3185,30 @@ function _selfTestCases() {
|
|
|
3175
3185
|
try { const j = JSON.parse(out); behavOk = j.estimatedType === 'feature' && Array.isArray(j.recommendedSteps) && j.recommendedSteps.length === 5; } catch {}
|
|
3176
3186
|
return expOk && delegated && movedToLib && behavOk;
|
|
3177
3187
|
} },
|
|
3188
|
+
{ name: '10th 외부평가 회귀가드 (UR-0131): DI 완전성 — 각 lib 모듈 destructure deps ⊆ bin wrapper 전달(STATUSES류 누락 자동 적발) (1.9.427)', run: () => {
|
|
3189
|
+
const src = read(__filename);
|
|
3190
|
+
const dir = path.join(path.dirname(__filename), '..', 'lib');
|
|
3191
|
+
// 'return ' 프리픽스로 실제 wrapper 만 매치(이 케이스의 regex 리터럴 자기참조 회피 — 리터럴엔 return 없음)
|
|
3192
|
+
const mods = [
|
|
3193
|
+
['review-request', /return _reviewRequest\.reviewRequestCmd\([^{]*\{([^}]*)\}/],
|
|
3194
|
+
['audit', /return _audit\.audit\([^{]*\{([^}]*)\}/],
|
|
3195
|
+
['drift', /return _drift\.driftCheckCmd\([^{]*\{([^}]*)\}/],
|
|
3196
|
+
['health', /return _health\.healthCmd\([^{]*\{([^}]*)\}/],
|
|
3197
|
+
['agents', /return _agents\.agentsCmd\([^{]*\{([^}]*)\}/],
|
|
3198
|
+
['session-close', /return _sessionClose\.sessionClose\([^{]*\{([^}]*)\}/],
|
|
3199
|
+
];
|
|
3200
|
+
for (const [mod, re] of mods) {
|
|
3201
|
+
let modSrc; try { modSrc = read(path.join(dir, mod + '.js')); } catch { return false; }
|
|
3202
|
+
const dm = modSrc.match(/const \{([^}]*)\} = deps;/);
|
|
3203
|
+
if (!dm) return false;
|
|
3204
|
+
const need = dm[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
3205
|
+
const wm = src.match(re);
|
|
3206
|
+
if (!wm) return false;
|
|
3207
|
+
const passed = new Set(wm[1].split(',').map(s => s.trim().split(':')[0].trim()).filter(Boolean));
|
|
3208
|
+
for (const n of need) if (!passed.has(n)) return false; // wrapper 가 누락한 dep → 실패(회귀 적발)
|
|
3209
|
+
}
|
|
3210
|
+
return true;
|
|
3211
|
+
} },
|
|
3178
3212
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3179
3213
|
];
|
|
3180
3214
|
}
|
|
@@ -17822,7 +17856,7 @@ async function deployAutoCmd(root, service) {
|
|
|
17822
17856
|
// 1.9.85: leerness health — 종합 헬스 체크 (drift + 보안 + skill + MCP + 누적)
|
|
17823
17857
|
const _health = require('../lib/health');
|
|
17824
17858
|
// 1.9.423 (UR-0025/UR-0125 큰 핸들러 모듈화 8번째): healthCmd → lib/health.js (DI 위임, thin wrapper)
|
|
17825
|
-
function healthCmd(root) { return _health.healthCmd(root, { VERSION, has, arg, harnessPath: __filename, listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount }); }
|
|
17859
|
+
function healthCmd(root) { return _health.healthCmd(root, { VERSION, STATUSES, has, arg, harnessPath: __filename, listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount }); }
|
|
17826
17860
|
|
|
17827
17861
|
function usageStatsCmd(root) {
|
|
17828
17862
|
root = absRoot(root || process.cwd());
|
|
@@ -18006,7 +18040,7 @@ function contractVerifyCmd(specPath, implPath) {
|
|
|
18006
18040
|
} else log(`✓ 모든 spec 함수가 impl에 존재`);
|
|
18007
18041
|
if (fieldMissing.length) {
|
|
18008
18042
|
log(`✗ 누락된 필드 (${fieldMissing.length}건):`);
|
|
18009
|
-
for (const m of fieldMissing) log(` -
|
|
18043
|
+
for (const m of fieldMissing) log(` - ${m}`); // 1.9.426 (10th 외부평가 Opus P3): 레거시 'tick.' 프리픽스 제거 — ## Fields 범용화(1.9.417) 표시 잔재. JSON 은 이미 정상.
|
|
18010
18044
|
} else log(`✓ 모든 spec 필드가 impl 소스에 존재`);
|
|
18011
18045
|
const ok = missing.length === 0 && fieldMissing.length === 0;
|
|
18012
18046
|
log('');
|
|
@@ -18821,7 +18855,7 @@ async function main() {
|
|
|
18821
18855
|
if (cmd === 'idempotency') return idempotencyCmd(arg('--path', process.cwd()), args[1]);
|
|
18822
18856
|
// 1.9.213: leerness intent <classify|expand|domains> — intent inference + scope expansion (사용자 명시)
|
|
18823
18857
|
if (cmd === 'intent') return intentCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
18824
|
-
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args
|
|
18858
|
+
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), _parseAddTitle(args, 2)); // 1.9.426 (10th 외부평가 Sonnet P2): flag/경로 break — 기존 filter 는 경로 + --trigger 값까지 설명에 흡수
|
|
18825
18859
|
if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
|
|
18826
18860
|
if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
|
|
18827
18861
|
if (cmd === 'rule' && args[1] === 'pause') return rulePause(arg('--path', process.cwd()), args[2]);
|
package/lib/health.js
CHANGED
|
@@ -10,7 +10,7 @@ const { log, ok, warn, fail, failJson, today, now, absRoot, exists, read, readBu
|
|
|
10
10
|
const { _parseArchiveBlocks } = require('./pure-utils');
|
|
11
11
|
|
|
12
12
|
function healthCmd(root, deps = {}) {
|
|
13
|
-
const { VERSION, has, arg, harnessPath, listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount } = deps;
|
|
13
|
+
const { VERSION, STATUSES, has, arg, harnessPath, listAllSkills, planPath, readProgressRows, readRules, envDiff, _collectSecretFindings, _readUsageStats, _loadDecisions, _loadLessons, _loadShellFailures, _readFeatureGraph, _scanShellScriptsEncoding, _shellEnvDrift, _computeMilestones, _computeRecentChanges, _computeRoundHistory, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _listAPISkills, _matchAPISkills, _mcpToolCount } = deps;
|
|
14
14
|
root = absRoot(root || process.cwd());
|
|
15
15
|
const out = { root, generatedAt: new Date().toISOString(), checks: {} };
|
|
16
16
|
// 1) drift level
|