leerness 1.9.282 → 1.9.283
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 +12 -0
- package/README.md +5 -5
- package/bin/harness.js +7 -48
- package/lib/pure-utils.js +55 -1
- package/package.json +1 -1
- package/scripts/e2e.js +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.283 — 2026-06-03 — UR-0025 2단계: 순수 함수 추가 모듈 분리 (lib/pure-utils 7→14종)
|
|
4
|
+
|
|
5
|
+
**🧩 GPT-5.5 "1.2MB 단일 파일" 지적의 점진적·비파괴 모듈화 계속 — 최근 추가한 순수 로직을 lib/ 로 추출.**
|
|
6
|
+
|
|
7
|
+
### 구현
|
|
8
|
+
1. **`lib/pure-utils.js` 7 → 14 export** — 권한 등급(`PERMISSION_TIERS`/`_tierRank`/`_requiredTier`/`_policyAllows`) + dist-tag(`_resolveNpmTag`) + run 스키마(`_newRunRecord`) + mcp.json(`_mcpJsonContent`) 추가 이동. 모두 harness 내부 상태 의존 0(순수).
|
|
9
|
+
2. **harness.js require 재연결** — 상단 destructure 확장 + 인라인 정의 7개 제거(`module.exports` 재노출 유지). 동작 동일.
|
|
10
|
+
3. e2e lib 테스트 7 → 14종 export + 동작 검증. selftest 31/31 그대로(이미 해당 함수 커버).
|
|
11
|
+
|
|
12
|
+
### 검증
|
|
13
|
+
- **selftest 31/31 PASS** · **E2E 229/229 PASS** (회귀 0) · `npm pack` tarball lib 포함 · require 무결성(14종 함수 + PERMISSION_TIERS) 확인.
|
|
14
|
+
|
|
3
15
|
## 1.9.282 — 2026-06-03 — UR-0035: AGENTS.md(정적) vs leerness(동적) 포지셔닝 — 범용 하네스 전략 완성
|
|
4
16
|
|
|
5
17
|
**🧭 GPT-5.5 마지막 전략 항목 — leerness 는 AGENTS.md 를 대체하지 않고 보완. 정적 지침 vs 동적 상태 역할 경계 명시.**
|
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.283 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### Core Commands
|
|
477
477
|
|
|
@@ -513,7 +513,7 @@ leerness memory restore decision <date|title>
|
|
|
513
513
|
|
|
514
514
|
### MCP server (외부 AI 통합)
|
|
515
515
|
|
|
516
|
-
Leerness v1.9.
|
|
516
|
+
Leerness v1.9.283는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
|
|
517
517
|
|
|
518
518
|
```jsonc
|
|
519
519
|
// 카테고리별
|
|
@@ -534,7 +534,7 @@ Leerness v1.9.282는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
534
534
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
535
535
|
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) 다음 라운드 예약.
|
|
536
536
|
|
|
537
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
537
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.283)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
538
538
|
|
|
539
539
|
### 성능 가이드 (1.9.140 측정)
|
|
540
540
|
|
|
@@ -572,6 +572,6 @@ leerness release pack --close --auto-main-push
|
|
|
572
572
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
573
573
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
574
574
|
|
|
575
|
-
Last synced by Leerness v1.9.
|
|
575
|
+
Last synced by Leerness v1.9.283: 2026-06-03
|
|
576
576
|
<!-- leerness:project-readme:end -->
|
|
577
577
|
|
package/bin/harness.js
CHANGED
|
@@ -7,9 +7,10 @@ const cp = require('child_process');
|
|
|
7
7
|
const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
// 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
|
|
10
|
-
const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp
|
|
10
|
+
const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
|
|
11
|
+
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord } = require('../lib/pure-utils');
|
|
11
12
|
|
|
12
|
-
const VERSION = '1.9.
|
|
13
|
+
const VERSION = '1.9.283';
|
|
13
14
|
|
|
14
15
|
// 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
|
|
15
16
|
// leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
|
|
@@ -14279,12 +14280,7 @@ function releaseCleanupCmd(root) {
|
|
|
14279
14280
|
// - NPM_TOKEN 또는 LEERNESS_NPM_TOKEN 환경변수에서만 읽음 (값 절대 로그 X)
|
|
14280
14281
|
// - 임시 .npmrc 파일 (mkdtempSync) 생성 → publish → 즉시 삭제 (finally)
|
|
14281
14282
|
// - 이미 publish된 버전 자동 detect → skip (중복 publish 차단)
|
|
14282
|
-
//
|
|
14283
|
-
function _resolveNpmTag(explicit, env) {
|
|
14284
|
-
env = env || process.env;
|
|
14285
|
-
const raw = String(explicit || env.LEERNESS_NPM_TAG || 'latest').trim().toLowerCase();
|
|
14286
|
-
return /^[a-z][a-z0-9-]{0,38}$/.test(raw) ? raw : 'latest';
|
|
14287
|
-
}
|
|
14283
|
+
// _resolveNpmTag → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
|
|
14288
14284
|
// 1.9.275 (UR-0026): leerness release channel — 안정/실험 채널 정책 + 버전 고정 안내 (npm dist-tag).
|
|
14289
14285
|
function releaseChannelCmd(root) {
|
|
14290
14286
|
root = absRoot(root || process.cwd());
|
|
@@ -18068,21 +18064,7 @@ function capabilitiesCmd(root, opts = {}) {
|
|
|
18068
18064
|
// ===== 1.9.281 (UR-0034, GPT-5.5 범용 하네스): 권한 등급(permission tiers) =====
|
|
18069
18065
|
// capabilities(1.9.272 공개)를 enforced 등급으로 확장. 기본 OFF(opt-in) — enforce 켤 때만 차단.
|
|
18070
18066
|
// 등급 오름차순(왼→오 위험↑). 허용 등급 이하의 명령만 통과. 기본 허용 'project-write'(파일 작업까지, 셸/git/publish 차단).
|
|
18071
|
-
|
|
18072
|
-
function _tierRank(t) { const i = PERMISSION_TIERS.indexOf(String(t || '')); return i < 0 ? PERMISSION_TIERS.length : i; }
|
|
18073
|
-
// 명령/capability → 요구 등급 (순수 함수, selftest)
|
|
18074
|
-
function _requiredTier(cmd) {
|
|
18075
|
-
const c = String(cmd || '').toLowerCase();
|
|
18076
|
-
if (/release\s+publish|npm\s+publish|\bpublish\b/.test(c)) return 'publish';
|
|
18077
|
-
if (/\bweb\b/.test(c)) return 'network';
|
|
18078
|
-
if (/git\s+push|sync-main/.test(c)) return 'git-write';
|
|
18079
|
-
if (/multi\s+--execute|dispatch\s+--write|--yolo|\bpc\b/.test(c)) return 'shell-write';
|
|
18080
|
-
if (/agents\s+(list|quota|bench)|--run-tests/.test(c)) return 'shell-read';
|
|
18081
|
-
if (/\binit\b|\badapter\b|update\s+--yes|\bmigrate\b/.test(c)) return 'project-write';
|
|
18082
|
-
if (/state\s+(start|record|verify|handoff)|decision|lesson|plan\s+add|task\s+add|rule\s+add/.test(c)) return 'safe-write';
|
|
18083
|
-
return 'read-only';
|
|
18084
|
-
}
|
|
18085
|
-
function _policyAllows(allowedTier, requiredTier) { return _tierRank(requiredTier) <= _tierRank(allowedTier); }
|
|
18067
|
+
// PERMISSION_TIERS / _tierRank / _requiredTier / _policyAllows → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
|
|
18086
18068
|
function _policyFile(root) { return path.join(_leernessStateDir(root), 'policy.json'); }
|
|
18087
18069
|
function _loadPolicy(root) {
|
|
18088
18070
|
const f = _policyFile(root);
|
|
@@ -18148,28 +18130,7 @@ function policyCmd(root, sub, ...args) {
|
|
|
18148
18130
|
// Claude Code 가 한 작업을 Goose/Codex 가 JSON 으로 정확히 이어받음. 비파괴 (신규 .leerness/ 디렉토리).
|
|
18149
18131
|
// 향후 UR-0031(MCP verb)/UR-0033(adapter) 가 이 스키마를 공통 호출 표면으로 사용.
|
|
18150
18132
|
function _leernessStateDir(root) { return path.join(absRoot(root), '.leerness'); }
|
|
18151
|
-
//
|
|
18152
|
-
function _newRunRecord(opts = {}) {
|
|
18153
|
-
return {
|
|
18154
|
-
schemaVersion: 1,
|
|
18155
|
-
run_id: opts.run_id || null,
|
|
18156
|
-
task_id: opts.task_id || null,
|
|
18157
|
-
agent_name: opts.agent_name || null,
|
|
18158
|
-
model_name: opts.model_name || null,
|
|
18159
|
-
started_at: opts.started_at || new Date().toISOString(),
|
|
18160
|
-
ended_at: opts.ended_at || null,
|
|
18161
|
-
goal: opts.goal || '',
|
|
18162
|
-
files_read: Array.isArray(opts.files_read) ? opts.files_read : [],
|
|
18163
|
-
files_changed: Array.isArray(opts.files_changed) ? opts.files_changed : [],
|
|
18164
|
-
commands_run: Array.isArray(opts.commands_run) ? opts.commands_run : [],
|
|
18165
|
-
tests_run: Array.isArray(opts.tests_run) ? opts.tests_run : [],
|
|
18166
|
-
errors: Array.isArray(opts.errors) ? opts.errors : [],
|
|
18167
|
-
decisions: Array.isArray(opts.decisions) ? opts.decisions : [],
|
|
18168
|
-
verification_result: opts.verification_result || null, // 'pass' | 'fail' | null
|
|
18169
|
-
handoff_summary: opts.handoff_summary || null,
|
|
18170
|
-
status: opts.status || 'in-progress' // in-progress | verified | handed-off
|
|
18171
|
-
};
|
|
18172
|
-
}
|
|
18133
|
+
// _newRunRecord → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
|
|
18173
18134
|
function _loadLeernessState(root) {
|
|
18174
18135
|
const f = path.join(_leernessStateDir(root), 'state.json');
|
|
18175
18136
|
if (!exists(f)) return { schemaVersion: 1, project: detectProjectName(root), currentRunId: null, runCounter: 0, updatedAt: null };
|
|
@@ -18296,9 +18257,7 @@ const ADAPTERS = {
|
|
|
18296
18257
|
aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
|
|
18297
18258
|
qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
|
|
18298
18259
|
};
|
|
18299
|
-
|
|
18300
|
-
return JSON.stringify({ mcpServers: { leerness: { command: 'npx', args: ['leerness', 'mcp', 'serve'] } } }, null, 2) + '\n';
|
|
18301
|
-
}
|
|
18260
|
+
// _mcpJsonContent → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
|
|
18302
18261
|
// .mcp.json 병합 — 기존 mcpServers 보존하고 leerness 항목만 추가/갱신.
|
|
18303
18262
|
function _mergeMcpJson(root) {
|
|
18304
18263
|
const f = path.join(absRoot(root), '.mcp.json');
|
package/lib/pure-utils.js
CHANGED
|
@@ -103,7 +103,61 @@ function _parseSlashFromHelp(text, invoke = 'slash') {
|
|
|
103
103
|
return out;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// 1.9.283 (UR-0025 2단계): 권한 등급(permission tiers) 순수 로직 — capabilities/policy 공유.
|
|
107
|
+
const PERMISSION_TIERS = ['read-only', 'safe-write', 'project-write', 'shell-read', 'shell-write', 'git-write', 'network', 'publish'];
|
|
108
|
+
function _tierRank(t) { const i = PERMISSION_TIERS.indexOf(String(t || '')); return i < 0 ? PERMISSION_TIERS.length : i; }
|
|
109
|
+
// 명령/capability → 요구 등급 (순수 매핑)
|
|
110
|
+
function _requiredTier(cmd) {
|
|
111
|
+
const c = String(cmd || '').toLowerCase();
|
|
112
|
+
if (/release\s+publish|npm\s+publish|\bpublish\b/.test(c)) return 'publish';
|
|
113
|
+
if (/\bweb\b/.test(c)) return 'network';
|
|
114
|
+
if (/git\s+push|sync-main/.test(c)) return 'git-write';
|
|
115
|
+
if (/multi\s+--execute|dispatch\s+--write|--yolo|\bpc\b/.test(c)) return 'shell-write';
|
|
116
|
+
if (/agents\s+(list|quota|bench)|--run-tests/.test(c)) return 'shell-read';
|
|
117
|
+
if (/\binit\b|\badapter\b|update\s+--yes|\bmigrate\b/.test(c)) return 'project-write';
|
|
118
|
+
if (/state\s+(start|record|verify|handoff)|decision|lesson|plan\s+add|task\s+add|rule\s+add/.test(c)) return 'safe-write';
|
|
119
|
+
return 'read-only';
|
|
120
|
+
}
|
|
121
|
+
function _policyAllows(allowedTier, requiredTier) { return _tierRank(requiredTier) <= _tierRank(allowedTier); }
|
|
122
|
+
|
|
123
|
+
// 1.9.283: npm dist-tag 결정 (UR-0026) — latest(안정)/next(실험), 잘못된 형식은 latest.
|
|
124
|
+
function _resolveNpmTag(explicit, env) {
|
|
125
|
+
env = env || process.env;
|
|
126
|
+
const raw = String(explicit || env.LEERNESS_NPM_TAG || 'latest').trim().toLowerCase();
|
|
127
|
+
return /^[a-z][a-z0-9-]{0,38}$/.test(raw) ? raw : 'latest';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 1.9.283: .mcp.json 내용 (UR-0033) — leerness MCP 서버 등록.
|
|
131
|
+
function _mcpJsonContent() {
|
|
132
|
+
return JSON.stringify({ mcpServers: { leerness: { command: 'npx', args: ['leerness', 'mcp', 'serve'] } } }, null, 2) + '\n';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 1.9.283: run 레코드 빌더 (UR-0032) — GPT-5.5 권고 14필드. startedAt 주입 가능(테스트).
|
|
136
|
+
function _newRunRecord(opts = {}) {
|
|
137
|
+
return {
|
|
138
|
+
schemaVersion: 1,
|
|
139
|
+
run_id: opts.run_id || null,
|
|
140
|
+
task_id: opts.task_id || null,
|
|
141
|
+
agent_name: opts.agent_name || null,
|
|
142
|
+
model_name: opts.model_name || null,
|
|
143
|
+
started_at: opts.started_at || new Date().toISOString(),
|
|
144
|
+
ended_at: opts.ended_at || null,
|
|
145
|
+
goal: opts.goal || '',
|
|
146
|
+
files_read: Array.isArray(opts.files_read) ? opts.files_read : [],
|
|
147
|
+
files_changed: Array.isArray(opts.files_changed) ? opts.files_changed : [],
|
|
148
|
+
commands_run: Array.isArray(opts.commands_run) ? opts.commands_run : [],
|
|
149
|
+
tests_run: Array.isArray(opts.tests_run) ? opts.tests_run : [],
|
|
150
|
+
errors: Array.isArray(opts.errors) ? opts.errors : [],
|
|
151
|
+
decisions: Array.isArray(opts.decisions) ? opts.decisions : [],
|
|
152
|
+
verification_result: opts.verification_result || null,
|
|
153
|
+
handoff_summary: opts.handoff_summary || null,
|
|
154
|
+
status: opts.status || 'in-progress'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
106
158
|
module.exports = {
|
|
107
159
|
_isSecretKey, compareVer, parseHarnessVersion,
|
|
108
|
-
_classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp
|
|
160
|
+
_classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
|
|
161
|
+
// 1.9.283 (UR-0025 2단계)
|
|
162
|
+
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord
|
|
109
163
|
};
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -2793,13 +2793,19 @@ total++;
|
|
|
2793
2793
|
let exportsOk = false;
|
|
2794
2794
|
try {
|
|
2795
2795
|
const m = require(libPath);
|
|
2796
|
-
|
|
2797
|
-
|
|
2796
|
+
// 1.9.283: 1단계 7종 + 2단계 7종(권한등급/dist-tag/run스키마/mcp.json)
|
|
2797
|
+
exportsOk = ['_isSecretKey','compareVer','parseHarnessVersion','_classifyCJK','_riskLabel','_detectSystemLang','_parseSlashFromHelp',
|
|
2798
|
+
'_tierRank','_requiredTier','_policyAllows','_resolveNpmTag','_mcpJsonContent','_newRunRecord']
|
|
2799
|
+
.every(n => typeof m[n] === 'function')
|
|
2800
|
+
&& m.compareVer('1.9.1','1.9.0') === 1 && m._detectSystemLang({ LANG: 'ko_KR.UTF-8' }) === 'ko'
|
|
2801
|
+
&& Array.isArray(m.PERMISSION_TIERS) && m.PERMISSION_TIERS.length === 8
|
|
2802
|
+
&& m._requiredTier('release publish') === 'publish' && m._resolveNpmTag(null, {}) === 'latest'
|
|
2803
|
+
&& m._newRunRecord({ run_id: 'r1' }).schemaVersion === 1;
|
|
2798
2804
|
} catch {}
|
|
2799
2805
|
let filesOk = false;
|
|
2800
2806
|
try { const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8')); filesOk = Array.isArray(pkg.files) && pkg.files.includes('lib'); } catch {}
|
|
2801
2807
|
const ok = libExists && exportsOk && filesOk;
|
|
2802
|
-
console.log(ok ? '✓ B(1.9.274) lib 모듈 분리: pure-utils
|
|
2808
|
+
console.log(ok ? '✓ B(1.9.274/283) lib 모듈 분리: pure-utils 14종 export + 동작 + files 포함' : `✗ lib 분리 실패 (exists=${libExists} exports=${exportsOk} files=${filesOk})`);
|
|
2803
2809
|
if (!ok) failed++;
|
|
2804
2810
|
}
|
|
2805
2811
|
|