leerness 1.9.374 → 1.9.375
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 +16 -0
- package/README.md +5 -5
- package/bin/harness.js +3 -2
- package/package.json +1 -1
- package/scripts/e2e.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.375 — 2026-06-06 — UR-0084: 동시성 락 하드닝 (e2e flake 제거 + lost-update 창 축소)
|
|
4
|
+
|
|
5
|
+
**🛠 고부하 시 간헐 flake 한 락 테스트 + `_withLock` fail-open 창 하드닝.**
|
|
6
|
+
|
|
7
|
+
### 배경
|
|
8
|
+
1.9.374 e2e(561s 고부하)에서 "lost-update 락" 테스트가 1회 flake(UR-0084). 근원: `_withLock` 이 `maxWaitMs`(5s) 초과 시 **락 없이 진행**(fail-open) → 극단 경합 시 lost-update 창. + e2e 폴 60s 가 CPU 포화 시 6 병렬 spawn 완료에 빠듯.
|
|
9
|
+
|
|
10
|
+
### 구현
|
|
11
|
+
1. **`_withLock` maxWaitMs 5s→10s**: fail-open(락 없이 쓰기) 전 인내 2배 — 고부하 경합에서 lost-update 창 축소(fail-open 정책은 유지, 무한 차단 방지).
|
|
12
|
+
2. **e2e lost-update 폴 60s→120s**: CPU 포화 헤드룸 2배(격리 실측 0.4s 라 대폭 여유).
|
|
13
|
+
3. **`_withLock` 행위 selftest 추가**: 실제 락 획득(lock 파일 존재)→재진입(중첩 호출 42)→해제(cleanup) + maxWaitMs 하드닝 확인.
|
|
14
|
+
|
|
15
|
+
### 검증 (회귀 0)
|
|
16
|
+
- **selftest 120→121 PASS** (신규: _withLock 실호출 행위). **E2E 320/320 PASS**(락 테스트 강건화).
|
|
17
|
+
- 실측: _withLock 획득/재진입/해제 정상, 락 파일 정리.
|
|
18
|
+
|
|
3
19
|
## 1.9.374 — 2026-06-06 — UR-0074: release cadence 진단 (외부리뷰 케이던스 가시화)
|
|
4
20
|
|
|
5
21
|
**🧩 외부 리뷰가 반복 지적한 "릴리스 케이던스 과다"를 측정·가시화 — `leerness release cadence`.**
|
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.375 하네스를 사용합니다. 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.375는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **83개 도구**를 노출:
|
|
529
529
|
|
|
530
530
|
```jsonc
|
|
531
531
|
// 카테고리별
|
|
@@ -546,7 +546,7 @@ Leerness v1.9.374는 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.375)** · 매 라운드 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.375: 2026-06-06
|
|
588
588
|
<!-- leerness:project-readme:end -->
|
|
589
589
|
|
package/bin/harness.js
CHANGED
|
@@ -28,7 +28,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
28
28
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
29
29
|
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, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
|
|
30
30
|
|
|
31
|
-
const VERSION = '1.9.
|
|
31
|
+
const VERSION = '1.9.375';
|
|
32
32
|
|
|
33
33
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
34
34
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -199,7 +199,7 @@ const _heldLocks = new Set(); // 프로세스 내 재진입 추적 (중첩 _wit
|
|
|
199
199
|
function _withLock(targetPath, fn, opts = {}) {
|
|
200
200
|
const lockPath = targetPath + '.lock';
|
|
201
201
|
if (_heldLocks.has(lockPath)) return fn(); // 이미 이 프로세스가 보유 → 재진입(중첩 호출 안전)
|
|
202
|
-
const maxWaitMs = opts.maxWaitMs ||
|
|
202
|
+
const maxWaitMs = opts.maxWaitMs || 10000; // 1.9.375 (UR-0084): 5s→10s — 고부하 경합 시 fail-open(락 없이 진행) 전 인내 상향, lost-update 창 축소
|
|
203
203
|
const staleMs = opts.staleMs || 30000;
|
|
204
204
|
try { mkdirp(path.dirname(lockPath)); } catch {}
|
|
205
205
|
const start = Date.now();
|
|
@@ -3011,6 +3011,7 @@ function _selfTestCases() {
|
|
|
3011
3011
|
{ name: 'UR-0073 Phase B: _composeTeamPlan dry-run 실행 계획 (멤버별 dispatch, 실행 없음) 행위 (1.9.372)', run: () => { const m = require('../lib/pure-utils'); if (typeof _composeTeamPlan !== 'function' || m._composeTeamPlan !== _composeTeamPlan) return false; const team = { id: 'rev', name: 'R', purpose: 'PR 리뷰', personas: ['security', 'perf'], members: ['claude', 'codex'], schedule: 'manual' }; const p1 = _composeTeamPlan(team, '점검'); const ok1 = p1.steps.length === 2 && p1.task === '점검' && p1.steps[0].member === 'claude' && p1.steps[0].suggestedCommand.includes('agents dispatch') && p1.steps[0].suggestedCommand.includes('--to claude') && p1.steps[0].dispatchPrompt.includes('security'); const p2 = _composeTeamPlan(team, null); const ok2 = p2.task === 'PR 리뷰'; const p3 = _composeTeamPlan({ id: 'e', personas: [], members: [] }, 'x'); const ok3 = p3.steps.length === 0 && p3.memberCount === 0; return ok1 && ok2 && ok3; } },
|
|
3012
3012
|
{ name: 'UR-0073 Phase C: _teamHandoffReminders 스케줄 알림 (비-manual·active 만, 실행 트리거 아님) 행위 (1.9.373)', run: () => { const m = require('../lib/pure-utils'); if (typeof _teamHandoffReminders !== 'function' || m._teamHandoffReminders !== _teamHandoffReminders) return false; const r = _teamHandoffReminders([{ id: 'rev', schedule: 'every-session', status: 'active', members: ['a', 'b'] }, { id: 'man', schedule: 'manual', status: 'active' }, { id: 'paused', schedule: 'daily', status: 'paused' }]); return r.length === 1 && r[0].includes('rev') && r[0].includes('every-session') && r[0].includes('team preview rev') && !r.join('|').includes('man') && !r.join('|').includes('paused'); } },
|
|
3013
3013
|
{ name: 'UR-0074: _cadenceAssessment 릴리스 빈도 평가 (임계값) 행위 (1.9.374)', run: () => { const m = require('../lib/pure-utils'); if (typeof _cadenceAssessment !== 'function' || m._cadenceAssessment !== _cadenceAssessment || typeof releaseCadenceCmd !== 'function') return false; return _cadenceAssessment(7, 1, 1).level === 'very-high' && _cadenceAssessment(3, 1, 1).level === 'high' && _cadenceAssessment(1, 1, 1).level === 'moderate' && _cadenceAssessment(0.2, 1, 1).level === 'healthy' && _cadenceAssessment(7, 1, 1).recommendation.length > 0; } },
|
|
3014
|
+
{ name: 'UR-0084: _withLock 획득/재진입/해제 + maxWaitMs 하드닝(10s) 행위 (1.9.375)', run: () => { if (typeof _withLock !== 'function') return false; const src = read(__filename); const hardened = /maxWaitMs = opts\.maxWaitMs \|\| 10000/.test(src); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_lock_')); try { const target = path.join(tmp, 'f.md'); let reentrant = false; const lockSeen = _withLock(target, () => { const exists = fs.existsSync(target + '.lock'); reentrant = (_withLock(target, () => 42) === 42); return exists; }); const cleaned = !fs.existsSync(target + '.lock'); return hardened && lockSeen === true && reentrant === true && cleaned; } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } } },
|
|
3014
3015
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3015
3016
|
];
|
|
3016
3017
|
}
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -3413,8 +3413,8 @@ total++;
|
|
|
3413
3413
|
const ptPath = path.join(lDir, '.harness', 'progress-tracker.md');
|
|
3414
3414
|
// 자식들이 OS 프로세스로 독립 진행 → 부모는 파일을 sync 폴링(원자쓰기라 부분읽기 없음)
|
|
3415
3415
|
const start = Date.now(); let found = 0;
|
|
3416
|
-
// 1.9.321: 폴 타임아웃
|
|
3417
|
-
while (Date.now() - start <
|
|
3416
|
+
// 1.9.321/1.9.375 (UR-0084): 폴 타임아웃 60s→120s — 전체 e2e CPU 포화(561s 실측 시 1회 flake) 대비 헤드룸 2배(격리 실측 0.4s, 대폭 여유)
|
|
3417
|
+
while (Date.now() - start < 120000) {
|
|
3418
3418
|
try { const pt = fs.readFileSync(ptPath, 'utf8'); found = Array.from({ length: N }, (_, i) => i).filter(i => pt.includes('LOCKTEST-' + i)).length; if (found === N) break; } catch {}
|
|
3419
3419
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
3420
3420
|
}
|