leerness 1.9.444 → 1.9.445
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 +4 -4
- package/bin/leerness.js +16 -5
- package/lib/pure-utils.js +1 -1
- package/package.json +1 -1
- package/scripts/e2e.js +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.445 — 2026-06-08 — add-family positional path 일관화 (UR-0151, UR-0141 후속)
|
|
4
|
+
|
|
5
|
+
**🐛 데이터 오염 일관화: `decision/lesson/rule add` 도 positional path 인식 (task 와 동일).**
|
|
6
|
+
|
|
7
|
+
### 변경
|
|
8
|
+
- `decision add`, `lesson save`, `rule add` 가 positional path 를 인식 — `--path` > path-like positional > cwd. 1.9.442(task)에서 도입한 순수 `_taskPositionalPath` 재사용 → 제목/내용은 경로로 오인 안 함(선행 구분자 only), 값-플래그값(`--reason`/`--trigger`/`--tag` 등) 제외.
|
|
9
|
+
- `_TASK_VALUE_FLAGS` 에 add-family 값-플래그(`--trigger`/`--tag`) 추가.
|
|
10
|
+
- 기존엔 블록 root 가 `arg('--path', cwd)` 만 사용 → 비-루트 cwd 에서 add 시 cwd 오염(멀티프로젝트). 이제 task 와 일관.
|
|
11
|
+
|
|
12
|
+
### 검증 (회귀 0)
|
|
13
|
+
- **selftest 189→190** (와이어 3종 + --trigger 값 제외), **E2E 신규 B(1.9.445)**: 다른 cwd 에서 decision/lesson/rule add `<ws>` → ws 저장 + cwd 오염 0.
|
|
14
|
+
|
|
3
15
|
## 1.9.444 — 2026-06-08 — CI/PR 턴키: leerness ci init (GPT-5.5 전략리뷰 §6.7, UR-0152)
|
|
4
16
|
|
|
5
17
|
**⚙️ leerness 를 팀/CI 품질 게이트로 확장 — PR 마다 `leerness gate` 를 실행하는 GitHub Actions 워크플로 1-커맨드 생성.**
|
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ MIT
|
|
|
168
168
|
<!-- leerness:project-readme:start -->
|
|
169
169
|
## Leerness Project Harness
|
|
170
170
|
|
|
171
|
-
이 프로젝트는 Leerness v1.9.
|
|
171
|
+
이 프로젝트는 Leerness v1.9.445 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
172
172
|
|
|
173
173
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
174
174
|
|
|
@@ -222,7 +222,7 @@ leerness memory restore decision <date|title>
|
|
|
222
222
|
|
|
223
223
|
### MCP server (외부 AI 통합)
|
|
224
224
|
|
|
225
|
-
Leerness v1.9.
|
|
225
|
+
Leerness v1.9.445는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
226
226
|
|
|
227
227
|
```jsonc
|
|
228
228
|
// 카테고리별
|
|
@@ -243,7 +243,7 @@ Leerness v1.9.444는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
243
243
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
244
244
|
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) 다음 라운드 예약.
|
|
245
245
|
|
|
246
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
246
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.445)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
247
247
|
|
|
248
248
|
### 성능 가이드 (1.9.140 측정)
|
|
249
249
|
|
|
@@ -281,6 +281,6 @@ leerness release pack --close --auto-main-push
|
|
|
281
281
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
282
282
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
283
283
|
|
|
284
|
-
Last synced by Leerness v1.9.
|
|
284
|
+
Last synced by Leerness v1.9.445: 2026-06-08
|
|
285
285
|
<!-- leerness:project-readme:end -->
|
|
286
286
|
|
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.445';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3096,7 +3096,7 @@ function _selfTestCases() {
|
|
|
3096
3096
|
} },
|
|
3097
3097
|
{ name: '10th 외부평가 Sonnet P2: rule add flag/경로 break(_parseAddTitle) — trigger 값/경로 흡수 차단 (1.9.426)', run: () => {
|
|
3098
3098
|
const src = read(__filename);
|
|
3099
|
-
const wired = src.includes("ruleAdd(arg('--path', process.cwd()
|
|
3099
|
+
const wired = src.includes("ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2))");
|
|
3100
3100
|
const m = require('../lib/pure-utils');
|
|
3101
3101
|
const u = m._parseAddTitle(['rule', 'add', '세션', '점검', '--trigger', 'every-session', '/p'], 2) === '세션 점검';
|
|
3102
3102
|
return wired && u;
|
|
@@ -3355,6 +3355,17 @@ function _selfTestCases() {
|
|
|
3355
3355
|
const wired = src.includes("cmd === 'ci' && (args[1] === 'init'") && src.includes('ciInitCmd(absRoot(_resolveRoot(args[2]))');
|
|
3356
3356
|
return contentOk && wired;
|
|
3357
3357
|
} },
|
|
3358
|
+
{ name: 'UR-0151: decision/lesson/rule add positional path 지원(_taskPositionalPath 재사용, cwd 오염 차단) (1.9.445)', run: () => {
|
|
3359
|
+
const src = read(__filename);
|
|
3360
|
+
const rule = src.includes("ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2))");
|
|
3361
|
+
const lesson = src.includes("if (cmd === 'lesson') {\n const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd());");
|
|
3362
|
+
const decision = src.includes("if (cmd === 'decision') {\n const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd());");
|
|
3363
|
+
// rule add 의 --trigger 값은 경로 아님(path-like 아님) + 값-플래그 제외
|
|
3364
|
+
const m = require('../lib/pure-utils');
|
|
3365
|
+
const trig = m._taskPositionalPath(['rule', 'add', '룰', '--trigger', 'every-update', '/p'], 2) === '/p'
|
|
3366
|
+
&& m._taskPositionalPath(['rule', 'add', '룰', '--trigger', 'every-update'], 2) === null;
|
|
3367
|
+
return rule && lesson && decision && trig;
|
|
3368
|
+
} },
|
|
3358
3369
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3359
3370
|
];
|
|
3360
3371
|
}
|
|
@@ -19044,7 +19055,7 @@ async function main() {
|
|
|
19044
19055
|
if (cmd === 'idempotency') return idempotencyCmd(arg('--path', process.cwd()), args[1]);
|
|
19045
19056
|
// 1.9.213: leerness intent <classify|expand|domains> — intent inference + scope expansion (사용자 명시)
|
|
19046
19057
|
if (cmd === 'intent') return intentCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
19047
|
-
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()
|
|
19058
|
+
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2)); // 1.9.426: flag/경로 break(_parseAddTitle) · 1.9.445 (UR-0151): positional path 지원(제목과 분리)
|
|
19048
19059
|
if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
|
|
19049
19060
|
if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
|
|
19050
19061
|
if (cmd === 'rule' && args[1] === 'pause') return rulePause(arg('--path', process.cwd()), args[2]);
|
|
@@ -19125,7 +19136,7 @@ async function main() {
|
|
|
19125
19136
|
// 1.9.112: lesson save — lessons.md에 새 lesson 추가
|
|
19126
19137
|
// 1.9.117: lesson list — lessons.md 조회 + --tag 필터 + --json
|
|
19127
19138
|
if (cmd === 'lesson') {
|
|
19128
|
-
const root = absRoot(arg('--path', process.cwd())
|
|
19139
|
+
const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()); const sub = args[1] || ''; // 1.9.445 (UR-0151): positional path 지원
|
|
19129
19140
|
if (sub === 'save') {
|
|
19130
19141
|
const textParts = [];
|
|
19131
19142
|
for (let i = 2; i < args.length; i++) {
|
|
@@ -19152,7 +19163,7 @@ async function main() {
|
|
|
19152
19163
|
// 1.9.108: decision add — decisions.md에 새 설계 결정 추가
|
|
19153
19164
|
// 1.9.118: decision list — decisions.md 전체 조회 + --json
|
|
19154
19165
|
if (cmd === 'decision') {
|
|
19155
|
-
const root = absRoot(arg('--path', process.cwd())
|
|
19166
|
+
const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()); const sub = args[1] || ''; // 1.9.445 (UR-0151): positional path 지원
|
|
19156
19167
|
if (sub === 'add') {
|
|
19157
19168
|
// args[2..] 가 title (단, --flag 또는 경로형 positional 이 시작되기 전까지)
|
|
19158
19169
|
// 1.9.351 (UR-0064) → 1.9.416 (UR-0122): 공유 헬퍼 _parseAddTitle 로 단일화(flag/경로 break) + 빈 입력 거부
|
package/lib/pure-utils.js
CHANGED
|
@@ -1305,7 +1305,7 @@ function _parseAddTitle(args, startIdx = 0) {
|
|
|
1305
1305
|
// 1.9.442 (12th 외부평가 Sonnet UR-0141): task 계열 positional path 안전 추출.
|
|
1306
1306
|
// _parseAddTitle 과 동일한 path-like 판정(선행 구분자 / ./ ../ C:\)으로 제목/ID/맨이름은 경로로 오인 안 함(src/auth 같은 내부 슬래시 제목 보호).
|
|
1307
1307
|
// 값-취하는 플래그(--evidence /abs/log 등)의 값은 root 후보에서 제외(직전 토큰이 값-플래그면 skip) → 오탐 차단. 첫 path-like positional 만 반환, 없으면 null.
|
|
1308
|
-
const _TASK_VALUE_FLAGS = new Set(['--status', '--evidence', '--priority', '--note', '--reason', '--title', '--desc', '--summary', '--id', '--limit', '--from', '--to']);
|
|
1308
|
+
const _TASK_VALUE_FLAGS = new Set(['--status', '--evidence', '--priority', '--note', '--reason', '--title', '--desc', '--summary', '--id', '--limit', '--from', '--to', '--trigger', '--tag']); // 1.9.445 (UR-0151): rule/lesson add 값-플래그(--trigger/--tag) 포함
|
|
1309
1309
|
function _taskPositionalPath(args, startIdx = 2) {
|
|
1310
1310
|
const a = args || [];
|
|
1311
1311
|
for (let i = startIdx; i < a.length; i++) {
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6174,5 +6174,31 @@ total++;
|
|
|
6174
6174
|
if (!ok) failed++;
|
|
6175
6175
|
}
|
|
6176
6176
|
|
|
6177
|
+
// 1.9.445 (UR-0151): add-family(decision/lesson/rule) positional path — 다른 cwd 에서 실행해도 positional 경로에 저장(cwd 오염 차단).
|
|
6178
|
+
total++;
|
|
6179
|
+
{
|
|
6180
|
+
let ok = false;
|
|
6181
|
+
try {
|
|
6182
|
+
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-addpos-'));
|
|
6183
|
+
const cwd3 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cwd3-'));
|
|
6184
|
+
cp.spawnSync(process.execPath, [CLI, 'init', ws, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6185
|
+
cp.spawnSync(process.execPath, [CLI, 'decision', 'add', '결정E2E', ws, '--reason', 'r'], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6186
|
+
cp.spawnSync(process.execPath, [CLI, 'lesson', 'save', '교훈E2E', ws], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6187
|
+
cp.spawnSync(process.execPath, [CLI, 'rule', 'add', '룰E2E', '--trigger', 'every-update', ws], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6188
|
+
const hdir = path.join(ws, '.harness');
|
|
6189
|
+
let savedAll = false;
|
|
6190
|
+
try {
|
|
6191
|
+
const blob = fs.readdirSync(hdir).map(f => { try { return fs.readFileSync(path.join(hdir, f), 'utf8'); } catch { return ''; } }).join('\n');
|
|
6192
|
+
savedAll = blob.includes('결정E2E') && blob.includes('교훈E2E') && blob.includes('룰E2E');
|
|
6193
|
+
} catch {}
|
|
6194
|
+
const cwdClean = !fs.existsSync(path.join(cwd3, '.harness'));
|
|
6195
|
+
fs.rmSync(ws, { recursive: true, force: true });
|
|
6196
|
+
fs.rmSync(cwd3, { recursive: true, force: true });
|
|
6197
|
+
ok = savedAll && cwdClean;
|
|
6198
|
+
} catch {}
|
|
6199
|
+
console.log(ok ? '✓ B(1.9.445) UR-0151: decision/lesson/rule add positional path 저장 + cwd 오염 차단' : '✗ add-family positional path 실패');
|
|
6200
|
+
if (!ok) failed++;
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6177
6203
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
6178
6204
|
if (failed > 0) process.exit(1);
|