leerness 1.36.26 → 1.36.28
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 +24 -0
- package/README.md +4 -4
- package/bin/leerness.js +130 -10
- package/lib/pure-utils.js +41 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.28 — 2026-07-15 — 미검토 표면 헌트: 데이터 유실 클래스(손상 스토어 클로버 · URL 덮어쓰기) + 정확성(impact 전이 · alias 경계)
|
|
4
|
+
|
|
5
|
+
rotate-review-targets 교훈 적용 — 최근 잘 검토된 표면(lens/memory/plan/requests/skill)을 **제외**하고 미검토 표면(state/team/api-skill/wakeup/intent/feature)을 codex 로 QA 헌트. 10 findings 중 결정적 5건을 맹신 X(전부 재현) 후, 데이터 유실 클래스 + 저위험 순수 교정 4건을 수정. 동시성 3건(크로스프로세스 락)은 0-deps rabbit-hole 위험이라 정직하게 이연.
|
|
6
|
+
|
|
7
|
+
- **#3 P1 (데이터 유실 클래스) — 손상 JSON 스토어를 빈 값으로 오인해 덮어쓰기**: state.json/`teams.json`/`platform-constraints.json` 이 손상되면 fail-open 으로 빈 기본값이 되어 기존 데이터(진행 중 run·팀·커스텀 제약)를 소실시켰다. `_assertStoreParsable`(파일 "없음"과 "있으나 손상" 구분) + `_guardStore` 로 **변경(저장)을 거부하고 원본 보존** + `store_corrupt` 구조화 에러. 설계: SAVE 진입점에서만 던짐(handoff 등 비변경 읽기는 resilient 유지) — 단 state 는 카운터 리셋이 run 파일 클로버를 유발해 load 에서 던지고 stateCmd 를 감쌈.
|
|
8
|
+
- **#4 P2 (데이터 유실) — api-skill 이 쿼리 다른 URL 을 같은 id 로 덮어씀**: `?doc=alpha` 와 `?doc=beta` 가 둘 다 `example.md` 로 슬러그돼 앞 URL·방향이 소실. 같은 id 파일의 저장 URL 이 다르면 정규화 URL 해시(`_shortHash`)를 붙여 분리, 같은 URL 재등록은 멱등 갱신 유지.
|
|
9
|
+
- **#8 P2 (정확성) — feature impact 역의존 전이 누락**: `A←B←C` 에서 `impact A` 가 B 만 반환(C 누락). 역방향 depends-on 을 BFS 종료 후 1-홉이 아니라 BFS **안**에서 확장(visited 로 사이클 안전) → C 가 depth 2 로 포착.
|
|
10
|
+
- **#10 P2 (정확성) — intent alias 부분문자열 오탐**: `restore`→'rest'(API), `client`→'cli'(CLI) 오분류. ASCII 단어 alias 는 단어경계를 요구(`_aliasHit`), 구두점/CJK alias 는 substring 유지.
|
|
11
|
+
- **검증**: selftest 298/298(전이+사이클안전+alias 경계+가드 배선+해시), 4건 전부 원 재현본으로 before/after + 정상 무회귀 실측, 게이트 e2e, 게시본 클린룸.
|
|
12
|
+
- 이연: #7(pre-wake-audit 테이블형 in-progress 미인식 — session-resume 매처 재사용), 동시성 #1/#2/#5(state/team 병렬 lost-update — 크로스프로세스 락 필요).
|
|
13
|
+
|
|
14
|
+
**반복 마이그레이션 커스텀 유실 (사용자 보고, P1, 동일 릴리스 합류)**: "AI 에게 매번 최신 마이그레이션을 시키는" 표준 흐름에서 **2번째 마이그레이션부터 CLAUDE.md/AGENTS.md 의 사용자 커스텀 지시가 조용히 사라졌다** — `_managedMerge` 가 preserved 태그를 보면 병합 없이 새 템플릿만 반환했기 때문(구 `<details>` 중첩-방지 설계의 부작용). **실증**: 1.26.0/1.9.206 실제 구버전으로 워크스페이스 구축(23종 마커) → migrate: 1차는 유실 0 이지만, 커스텀 추가 후 2차에서 신·구 커스텀 모두 유실(archive 백업에만 잔존).
|
|
15
|
+
- **재설계**: 라인-집합 diff — "새 템플릿에 없는 라인"만 Preserved 섹션에 평문 이월. 멱등(연속 5회 마이그레이션에서 커스텀 3종 전부 보존, 크기 고정), 중첩 없음, 레거시 `<details>` 형식에서도 커스텀 회수(래퍼 미이월), overwriteSet(통째 교체 파일) 존중. 편향은 명시적으로 false-PRESERVE(과보존 무해) — false-DROP(유실)이 버그.
|
|
16
|
+
- 마이그레이션 유실 전수 검증 결과(이번 실측): `.harness/` 데이터 파일(task/lesson/decision/rule/request/plan/feature/team/skill + 수기 편집 project-brief/current-state/handoff/guideline/design-system)은 **1.26.0→최신, 1.9.206→최신, 반복 실행 모두 유실 0** — 유일한 유실 경로가 위 managed-merge 였고 이번에 봉합.
|
|
17
|
+
|
|
18
|
+
## 1.36.27 — 2026-07-15 — debug 렌즈 — "근본원인 조사 없이 수정 금지"를 자기질문으로 (obra/superpowers systematic-debugging)
|
|
19
|
+
|
|
20
|
+
superpowers 검토 아크 마지막 채택분. 원문(systematic-debugging SKILL)의 규율을 **lens 카테고리 정합대로 자기질문 6문항으로 재작성** — 대문자 명령형/강제 게이트는 이식하지 않음(lens 는 advisory 표면, 강제로 포장하면 과장).
|
|
21
|
+
|
|
22
|
+
- **`LENS_CATALOG.debug`** — persona "재현 못 한 버그는 고친 적 없다고 믿는 조사관". 6문항: 재현+스택 전문 / 근본원인 한 문장 / 유사 코드와 차이 전수 / 가설 하나·변수 하나 / **실패 테스트 먼저**(수정 후 초록불은 증거 아님) / 3번째 시도면 설계 의심 → decision·lesson 승격. ko/en 락스텝(병합 규칙상 개수 일치 필수). `affects: [test, code]`.
|
|
23
|
+
- 정직성: 6번(3번째 시도)은 **자기보고 질문** — 도구가 시도 횟수를 세지 않는다. 파일 확장자 매핑(`_lensDomainsForFiles`)에는 넣지 않음 — debug 는 파일 표면이 없어 오탐만 생김.
|
|
24
|
+
- **`route bugfix` 힌트** — 결정적 트리거(사용자가 task type 명시): `💡 수정 전 자기질문: leerness lens debug`. 표면 문자열 5곳 `|debug` 동시 갱신(selftest 가드 리터럴 포함 — 미갱신 시 게이트가 깨지는 구조를 판정단이 사전 지적).
|
|
25
|
+
- **검증**: selftest 297/297(ko/en 패리티 + 다중절 앵커 4쌍(1.36.16 F8 교훈 — 단어 하나 앵커 금지) + affects 유효 + route 힌트 + **파일매핑 미확장 확인**), lens/route/en 실측, 게이트 e2e, 게시본 클린룸.
|
|
26
|
+
|
|
3
27
|
## 1.36.26 — 2026-07-15 — `leerness skill lint` — SKILL 메타 품질 lint (2티어 severity · 한국어 트리거절 · CJK 본문예산) (obra/superpowers P1/P2)
|
|
4
28
|
|
|
5
29
|
1.36.25(P0 — description 데이터)로 선행조건이 충족된 후속. 판별 증거: **P0 없이 lint 를 먼저 붙였으면 내장 9/9 가 실패**했을 것 — 지금은 내장 9종이 lint clean(순서가 맞았다는 실증, selftest 로 고정).
|
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.
|
|
124
|
+
이 프로젝트는 Leerness v1.36.28 하네스를 사용합니다. 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.
|
|
178
|
+
Leerness v1.36.28는 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.26는 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.
|
|
199
|
+
현재 누적: **70 라운드 (1.9.40 → 1.36.28)** · 매 라운드 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.
|
|
237
|
+
Last synced by Leerness v1.36.28: 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.
|
|
37
|
+
const VERSION = '1.36.28';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3027,6 +3027,64 @@ function _selfTestCases() {
|
|
|
3027
3027
|
const encOk = s.includes("(result.applied || []).some(a => a.action === 'failed')) process.exitCode = 1"); // encoding: 실패 exit1
|
|
3028
3028
|
return reuseOk && releaseOk && encOk;
|
|
3029
3029
|
} },
|
|
3030
|
+
{ name: '미검토표면 헌트 (1.36.28, codex): #8 impact 역의존 전이 + #10 alias 단어경계 + #3 손상스토어 클로버 차단 + #4 URL 해시 — 순수/소스 행위검사', run: () => {
|
|
3031
|
+
const p = require('../lib/pure-utils');
|
|
3032
|
+
// #8: A←B←C 전이 (F-0003 이 depth 2 로 잡혀야)
|
|
3033
|
+
const nodes = [{ id: 'F1', title: 'A', dependsOn: [] }, { id: 'F2', title: 'B', dependsOn: ['F1'] }, { id: 'F3', title: 'C', dependsOn: ['F2'] }];
|
|
3034
|
+
const imp = p._featureImpactBfs(nodes, 'F1').map(x => x.id);
|
|
3035
|
+
const transitiveOk = imp.includes('F2') && imp.includes('F3');
|
|
3036
|
+
// 사이클 안전: F1↔F2 순환이어도 무한루프/중복 없이 종료
|
|
3037
|
+
const cyc = p._featureImpactBfs([{ id: 'F1', dependsOn: ['F2'] }, { id: 'F2', dependsOn: ['F1'] }], 'F1');
|
|
3038
|
+
const cycleSafe = Array.isArray(cyc) && cyc.length <= 2;
|
|
3039
|
+
// #10: 부분문자열 오탐 차단 + 실매칭 유지
|
|
3040
|
+
const aliasOk = typeof p._aliasHit === 'function'
|
|
3041
|
+
&& !p._aliasHit('restore deleted files', 'rest') && !p._aliasHit('the client cache', 'cli')
|
|
3042
|
+
&& p._aliasHit('call the rest endpoint', 'rest') && p._aliasHit('run cli now', 'cli')
|
|
3043
|
+
&& p._aliasHit('노드제이에스 node.js 설정', 'node.js'); // 구두점 alias 는 substring 유지
|
|
3044
|
+
// #3/#4: 소스 배선 확인 (행위는 e2e/실측, 여기선 가드 존재)
|
|
3045
|
+
const s = read(__filename);
|
|
3046
|
+
const guardOk = s.includes('function _assertStoreParsable') && s.includes("code: 'E_STORE_CORRUPT'")
|
|
3047
|
+
&& s.includes('_guardStore(has(') && s.includes("_assertStoreParsable(teamsJsonPath")
|
|
3048
|
+
&& s.includes("_assertStoreParsable(_platformConstraintsPath");
|
|
3049
|
+
const hashOk = s.includes('function _shortHash') && s.includes("id = id + '-' + _shortHash(url)") && s.includes('urls:\\s*\\r?\\n');
|
|
3050
|
+
return transitiveOk && cycleSafe && aliasOk && guardOk && hashOk;
|
|
3051
|
+
} },
|
|
3052
|
+
{ name: '반복 마이그레이션 커스텀 보존 (1.36.28, 사용자 보고): _managedMerge 라인-diff 이월 — 2번째부터 유실되던 CLAUDE/AGENTS 커스텀 지시 (행위검사)', run: () => {
|
|
3053
|
+
const p = require('../lib/pure-utils');
|
|
3054
|
+
const next = '# T\nline A\nline B\n';
|
|
3055
|
+
const prev = '# T\nline A\nline B\nMY CUSTOM 1\n';
|
|
3056
|
+
const m1 = p._managedMerge('CLAUDE.md', next, prev, '.harness/archive', null);
|
|
3057
|
+
const m2 = p._managedMerge('CLAUDE.md', next, m1, '.harness/archive', null);
|
|
3058
|
+
const m3 = p._managedMerge('CLAUDE.md', next, m2, '.harness/archive', null);
|
|
3059
|
+
// 핵심 판별: 종전 구현은 m2 에서 MY CUSTOM 1 이 사라졌다 (previous 에 preserved 태그 있으면 next 만 반환)
|
|
3060
|
+
const survived = m1.includes('MY CUSTOM 1') && m2.includes('MY CUSTOM 1') && m3.includes('MY CUSTOM 1');
|
|
3061
|
+
const idem = m2 === m3;
|
|
3062
|
+
// 레거시 <details> 형식에서 커스텀 회수 + 래퍼 미이월
|
|
3063
|
+
const F = String.fromCharCode(96, 96, 96);
|
|
3064
|
+
const legacy = next + '\n---\n<!-- leerness:migration-preserved -->\n## Preserved previous content\n\n<details>\n<summary>Previous CLAUDE.md</summary>\n\n' + F + 'md\n# T\nOLD CUSTOM X\n' + F + '\n\n</details>\n';
|
|
3065
|
+
const lm = p._managedMerge('CLAUDE.md', next, legacy, '.harness/archive', null);
|
|
3066
|
+
const legacyOk = lm.includes('OLD CUSTOM X') && !lm.includes('<details>');
|
|
3067
|
+
const cleanOk = p._managedMerge('CLAUDE.md', next, next, '.harness/archive', null) === next;
|
|
3068
|
+
const owOk = p._managedMerge('x.md', next, prev, '.', new Set(['x.md'])) === next;
|
|
3069
|
+
return survived && idem && legacyOk && cleanOk && owOk;
|
|
3070
|
+
} },
|
|
3071
|
+
{ name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
|
|
3072
|
+
const d = LENS_CATALOG.debug;
|
|
3073
|
+
if (!d) return false;
|
|
3074
|
+
const parity = Array.isArray(d.questions) && Array.isArray(d.questionsEn) && d.questions.length === d.questionsEn.length && d.questions.length >= 5;
|
|
3075
|
+
// 다중절 앵커(단어 하나 앵커는 실질 삭제를 못 잡음 — 1.36.16 F8 교훈)
|
|
3076
|
+
const anyKo = (...ws) => d.questions.some(q => ws.every(w => q.includes(w)));
|
|
3077
|
+
const anyEn = (...ws) => d.questionsEn.some(q => ws.every(w => q.toLowerCase().includes(w.toLowerCase())));
|
|
3078
|
+
const koOk = anyKo('근본원인', '한 문장') && anyKo('가설', '변수 하나') && anyKo('실패', '테스트') && anyKo('3번째', '자기보고');
|
|
3079
|
+
const enOk = anyEn('root cause', 'one sentence') && anyEn('one variable') && anyEn('failing test');
|
|
3080
|
+
const affectsOk = Array.isArray(d.affects) && d.affects.length && d.affects.every(a => !!LENS_CATALOG[a]);
|
|
3081
|
+
const s = read(__filename);
|
|
3082
|
+
const routeHint = s.includes("if (name === 'bugfix') log(") && s.includes('leerness lens debug');
|
|
3083
|
+
const surface = s.includes('lens [code|design|docs|test|security|database|debug]');
|
|
3084
|
+
// 파일 확장자 매핑 미확장(오탐 방지): _lensDomainsForFiles 가 debug 를 반환하지 않는다
|
|
3085
|
+
const noFileMap = !JSON.stringify(_lensDomainsForFiles(['a.sql', 'b.js', 'c.md'])).includes('debug');
|
|
3086
|
+
return parity && koOk && enOk && affectsOk && routeHint && surface && noFileMap;
|
|
3087
|
+
} },
|
|
3030
3088
|
{ name: 'skill lint (1.36.26, obra/superpowers P1/P2): 2티어 severity(ERROR 만 exit1) + 한국어 트리거절 + CJK 본문예산 분기 — 순수 행위검사', run: () => {
|
|
3031
3089
|
const p = require('../lib/pure-utils');
|
|
3032
3090
|
if (typeof p._lintSkillMeta !== 'function') return false;
|
|
@@ -4063,7 +4121,7 @@ function _selfTestCases() {
|
|
|
4063
4121
|
} },
|
|
4064
4122
|
{ name: '품질 렌즈 (1.18.3): lens 명령 표면 등재 + REPL 설치문항 제거 (소스 가드)', run: () => {
|
|
4065
4123
|
const src = read(__filename);
|
|
4066
|
-
const surface = src.includes("if (cmd === 'lens')") && src.includes("cmd: 'lens [code|design|docs|test|security|database]") && src.includes('leerness lens [code|design|docs|test|security|database]');
|
|
4124
|
+
const surface = src.includes("if (cmd === 'lens')") && src.includes("cmd: 'lens [code|design|docs|test|security|database|debug]") && src.includes('leerness lens [code|design|docs|test|security|database|debug]');
|
|
4067
4125
|
const replGone = !src.includes('설치 완료 후 REPL agent ' + '모드를 즉시 시작할까요') && src.includes('REPL agent 모드 진입 ' + '문항 제거');
|
|
4068
4126
|
return surface && replGone;
|
|
4069
4127
|
} },
|
|
@@ -4685,6 +4743,28 @@ function shellGuardCmd(root, cmd, opts = {}) {
|
|
|
4685
4743
|
if (analysis.issues.some(i => i.severity === 'error')) process.exitCode = 1;
|
|
4686
4744
|
}
|
|
4687
4745
|
|
|
4746
|
+
// 1.36.28 (codex 미검토표면 헌트 #3 — 데이터 유실 클래스): 손상된 JSON 스토어를 "빈 값"으로 오인해 덮어쓰는 fail-open 방지.
|
|
4747
|
+
// 파일이 "없음"과 "있으나 손상"을 구분 — 후자는 파싱 실패를 던져 변경(저장)을 거부하고 원본을 보존한다.
|
|
4748
|
+
// 설계: LOAD 가 아니라 SAVE/변경 진입점에서만 던진다(handoff 등 비변경 읽기 경로는 resilient 유지). state 는
|
|
4749
|
+
// 카운터 리셋이 run 파일 클로버를 유발하므로 예외적으로 load 에서 던지고 stateCmd 를 _guardStore 로 감싼다.
|
|
4750
|
+
function _assertStoreParsable(file, label) {
|
|
4751
|
+
if (exists(file)) {
|
|
4752
|
+
try { JSON.parse(read(file)); }
|
|
4753
|
+
catch { throw Object.assign(new Error(`${label} 저장 파일이 손상돼(JSON 파싱 실패) 덮어쓰기를 거부합니다: ${file} — 파일을 복구하거나 삭제 후 재시도하세요`), { code: 'E_STORE_CORRUPT', file }); }
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
function _guardStore(jsonMode, fn) {
|
|
4757
|
+
try { return fn(); }
|
|
4758
|
+
catch (e) { if (e && e.code === 'E_STORE_CORRUPT') { failJson(jsonMode, 'store_corrupt', e.message); return undefined; } throw e; }
|
|
4759
|
+
}
|
|
4760
|
+
// 1.36.28 (#4): 0-deps 짧은 결정적 해시 (URL 충돌 구분용, 보안 아님).
|
|
4761
|
+
function _shortHash(s) {
|
|
4762
|
+
let h = 5381;
|
|
4763
|
+
const str = String(s || '');
|
|
4764
|
+
for (let i = 0; i < str.length; i++) h = ((h << 5) + h + str.charCodeAt(i)) >>> 0;
|
|
4765
|
+
return h.toString(36).slice(0, 6);
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4688
4768
|
// 1.9.245: API skill cache — 공식 문서 + 관련 링크 자동 정리 (사용자 명시 UR-0015)
|
|
4689
4769
|
// .harness/api-skills/<id>.md 에 frontmatter + 본문 저장. handoff 자동 매칭.
|
|
4690
4770
|
// Node built-in https (의존성 0), depth=1 same-domain crawl, max 10 links.
|
|
@@ -4933,7 +5013,16 @@ async function apiSkillCmd(root, sub) {
|
|
|
4933
5013
|
try {
|
|
4934
5014
|
const doc = await _collectAPIDoc(url, { direction, noCrawl });
|
|
4935
5015
|
const skillName = name || doc.title || new URL(url).hostname;
|
|
4936
|
-
|
|
5016
|
+
let id = _apiSkillId(url, name);
|
|
5017
|
+
// 1.36.28 (#4): 쿼리/경로가 달라 URL 은 다른데 slug 가 같으면(예: example.com/?doc=alpha vs ?doc=beta) 기존 파일을
|
|
5018
|
+
// 덮어써 이전 URL·방향이 유실됐다. 같은 id 파일이 이미 있고 저장된 URL 이 다르면 정규화 URL 해시를 붙여 분리한다.
|
|
5019
|
+
const _idFile = (i) => path.join(dir, i + '.md');
|
|
5020
|
+
if (fs.existsSync(_idFile(id))) {
|
|
5021
|
+
const existing = fs.readFileSync(_idFile(id), 'utf8');
|
|
5022
|
+
const um = existing.match(/^urls:\s*\r?\n\s*-\s*(\S.*)$/m); // 'urls:' 블록의 첫 항목만 (--- 구분선 오매치 방지)
|
|
5023
|
+
const storedUrl = um ? um[1].trim() : '';
|
|
5024
|
+
if (storedUrl && storedUrl !== url) id = id + '-' + _shortHash(url);
|
|
5025
|
+
}
|
|
4937
5026
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4938
5027
|
const md = _serializeAPISkill(id, skillName, [url], direction, doc);
|
|
4939
5028
|
fs.writeFileSync(path.join(dir, id + '.md'), md, 'utf8');
|
|
@@ -5182,6 +5271,32 @@ const LENS_CATALOG = {
|
|
|
5182
5271
|
],
|
|
5183
5272
|
affects: ['code', 'test'], affectsNote: '동시성 방어엔 인터리빙/부하/재시도/멀티인스턴스 실패를 재현하는 테스트가 따라와야 함(단일 인스턴스 초록불은 증거가 아님)',
|
|
5184
5273
|
affectsNoteEn: 'a concurrency defense needs an interleaving/load/retry/multi-instance reproduction test'
|
|
5274
|
+
},
|
|
5275
|
+
// 1.36.27 (obra/superpowers systematic-debugging → 자기질문化): "근본원인 조사 없이 수정 금지" 계열.
|
|
5276
|
+
// 원문의 대문자 명령형/강제 게이트는 이식하지 않음 — lens 는 advisory 자기질문 표면(과장 금지).
|
|
5277
|
+
// 6번(3번째 시도)은 자기보고 질문 — 도구가 실패 횟수를 카운트하지 않는다(구현했다고 쓰지 말 것).
|
|
5278
|
+
// 파일 확장자 매핑(_lensDomainsForFiles)에는 넣지 않음 — debug 는 파일 표면이 없어 오탐만 생김. 트리거는 route bugfix 힌트.
|
|
5279
|
+
debug: {
|
|
5280
|
+
title: '디버깅', persona: '재현 못 한 버그는 고친 적 없다고 믿는 조사관',
|
|
5281
|
+
titleEn: 'debugging', personaEn: 'an investigator who believes an unreproduced bug was never fixed',
|
|
5282
|
+
questions: [
|
|
5283
|
+
"이 버그를 직접 재현했는가? 에러 전문과 스택을 끝까지 읽었는가 — 첫 줄만 보고 고치고 있지 않은가?",
|
|
5284
|
+
"근본원인을 한 문장으로 글로 쓸 수 있는가? 못 쓰면 아직 조사 단계다 — 조사 전에 해법부터 제시하고 있지 않은가?",
|
|
5285
|
+
"동작하는 유사 코드와의 차이를 전수 나열했는가? 최근 변경(diff) 중 이 경계를 건드린 것은 무엇인가?",
|
|
5286
|
+
"가설을 하나만 세우고 변수 하나만 바꿔 검증했는가 — 여러 곳을 동시에 고쳐서 무엇이 통했는지 모르게 되고 있지 않은가?",
|
|
5287
|
+
"그 버그를 재현하는 실패 테스트를 먼저 썼고, 수정 전엔 실제로 실패함을 확인했는가? — 수정 후 초록불만으론 그 테스트가 버그를 잡는지 알 수 없다.",
|
|
5288
|
+
"같은 버그에 3번째 수정 시도인가? 그렇다면 증상이 아니라 설계를 의심할 때다 — `leerness decision add`/`lesson save` 로 승격해 기록하라. (자기보고 — 도구가 시도 횟수를 세지 않는다)",
|
|
5289
|
+
],
|
|
5290
|
+
questionsEn: [
|
|
5291
|
+
"Did you reproduce the bug yourself, and read the full error and stack to the end — or are you fixing off the first line?",
|
|
5292
|
+
"Can you write the root cause in one sentence? If not, you are still investigating — are you proposing fixes before the investigation?",
|
|
5293
|
+
"Did you enumerate every difference from similar working code? Which recent change (diff) touched this boundary?",
|
|
5294
|
+
"One hypothesis, one variable changed per experiment — or are you changing several things at once so you can't tell what worked?",
|
|
5295
|
+
"Did you write a failing test that reproduces the bug first, and confirm it actually fails before the fix? A green light after the fix alone proves nothing.",
|
|
5296
|
+
"Is this your third attempt at the same bug? Then suspect the design, not the symptom — promote it via decision add / lesson save. (self-reported — the tool does not count attempts)",
|
|
5297
|
+
],
|
|
5298
|
+
affects: ['test', 'code'], affectsNote: '근본원인을 한 문장으로 못 쓰겠으면 코드가 복잡하다는 신호 — code 질문으로. 수정엔 그 버그를 재현하는 회귀 테스트가 따라와야 함(수정 후 초록불은 증거가 아님)',
|
|
5299
|
+
affectsNoteEn: 'if you cannot state the root cause in one sentence, the code is too complex — revisit the code lens; every fix needs a regression test that reproduces the bug'
|
|
5185
5300
|
}
|
|
5186
5301
|
};
|
|
5187
5302
|
// 1.19.3 (UR-0003 렌즈 완전판 v3): 프로젝트별 커스텀 렌즈 — .harness/quality-lenses.json 읽기-병합(쓰기 명령 없음, AI/사용자가 편집).
|
|
@@ -5351,7 +5466,7 @@ function commandsCmd(root) {
|
|
|
5351
5466
|
{ cmd: 'encoding check [path]', desc: '인코딩 검증' },
|
|
5352
5467
|
{ cmd: 'lazy detect [path] [--json]', desc: '게으른 작업 감지 (1.9.101)' },
|
|
5353
5468
|
{ cmd: 'verify-claim <T-ID|--all> [--run-tests] [--test-cmd "<명령>"] [--strict-claims] [--require-evidence]', desc: '주장 검증 (1.9.18~26) — --all: 모든 done 주장 일괄 검증(CI·스케일, 1.33.2) · --require-evidence: done 주장에 파일+테스트 근거 강제 (1.9.287) · --test-cmd: 비-JS 테스트 명령 (1.17.2)' },
|
|
5354
|
-
{ cmd: 'lens [code|design|docs|test|security|database] [--json]', desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3, database: 동시성/트랜잭션)' },
|
|
5469
|
+
{ cmd: 'lens [code|design|docs|test|security|database|debug] [--json]', desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3, database: 동시성/트랜잭션)' },
|
|
5355
5470
|
{ cmd: 'optimism-check <T-ID>', desc: '낙관적 API 감지 (1.9.26)' },
|
|
5356
5471
|
{ cmd: 'requests audit|list|complete|drop|auto-complete', desc: '사용자 요청 추적 (1.9.207/223)' },
|
|
5357
5472
|
{ cmd: 'pre-wake-audit [path] [--last]', desc: 'sleep 전 점검 (1.9.209)' },
|
|
@@ -5508,6 +5623,7 @@ function _loadPlatformConstraints(root) {
|
|
|
5508
5623
|
} catch { return _DEFAULT_PLATFORM_CONSTRAINTS; }
|
|
5509
5624
|
}
|
|
5510
5625
|
function _writePlatformConstraints(root, catalog) {
|
|
5626
|
+
_assertStoreParsable(_platformConstraintsPath(root), 'platform-constraints'); // 1.36.28 (#3): 손상 파일 덮어써 커스텀 제약 유실 방지 (try 밖 — 삼켜지지 않게)
|
|
5511
5627
|
try {
|
|
5512
5628
|
mkdirp(path.join(root, '.harness'));
|
|
5513
5629
|
writeUtf8(_platformConstraintsPath(root), JSON.stringify({ ...catalog, updatedAt: new Date().toISOString() }, null, 2));
|
|
@@ -7747,6 +7863,7 @@ function _loadTeams(root) {
|
|
|
7747
7863
|
}
|
|
7748
7864
|
function _saveTeams(root, teams) {
|
|
7749
7865
|
const arr = Array.isArray(teams) ? teams : [];
|
|
7866
|
+
_assertStoreParsable(teamsJsonPath(root), 'team'); // 1.36.28 (#3): 손상 teams.json 을 덮어써 기존 팀 유실 방지
|
|
7750
7867
|
mkdirp(path.dirname(teamsJsonPath(root)));
|
|
7751
7868
|
writeUtf8(teamsJsonPath(root), JSON.stringify(arr, null, 2) + '\n');
|
|
7752
7869
|
writeUtf8(teamsPath(root), _renderTeamsMd(arr));
|
|
@@ -8705,6 +8822,8 @@ function route(name) {
|
|
|
8705
8822
|
log(`# Route: ${name}\n`);
|
|
8706
8823
|
log('Read before work:'); r.read.forEach(x => log('- ' + x));
|
|
8707
8824
|
log('\nUpdate after work:'); r.update.forEach(x => log('- ' + x));
|
|
8825
|
+
// 1.36.27: bugfix 진입 시 debug 렌즈 힌트 — 결정적 트리거(사용자가 task type 명시)라 키워드 추론 아님. advisory 한 줄.
|
|
8826
|
+
if (name === 'bugfix') log('\n💡 수정 전 자기질문: leerness lens debug # 근본원인 조사 없이 수정 금지 (재현→원인 한 문장→가설 하나→실패 테스트 먼저)');
|
|
8708
8827
|
}
|
|
8709
8828
|
|
|
8710
8829
|
function status(root) {
|
|
@@ -18587,7 +18706,8 @@ function _leernessStateDir(root) { return path.join(absRoot(root), '.leerness');
|
|
|
18587
18706
|
function _loadLeernessState(root) {
|
|
18588
18707
|
const f = path.join(_leernessStateDir(root), 'state.json');
|
|
18589
18708
|
if (!exists(f)) return { schemaVersion: 1, project: detectProjectName(root), currentRunId: null, runCounter: 0, updatedAt: null };
|
|
18590
|
-
|
|
18709
|
+
// 1.36.28 (#3): 손상 파일을 빈 상태로 오인하면 카운터가 0으로 리셋돼 run-0001 을 덮어쓴다(데이터 유실). 파싱 실패는 던진다.
|
|
18710
|
+
try { return JSON.parse(read(f)); } catch { throw Object.assign(new Error(`.leerness/state.json 손상 — 덮어쓰기 거부: ${f} (복구/삭제 후 재시도)`), { code: 'E_STORE_CORRUPT', file: f }); }
|
|
18591
18711
|
}
|
|
18592
18712
|
function _saveLeernessState(root, state) {
|
|
18593
18713
|
const dir = _leernessStateDir(root); mkdirp(dir);
|
|
@@ -21117,7 +21237,7 @@ VERIFICATION (evidence-gated "done")
|
|
|
21117
21237
|
contract verify <spec.md> <impl.js> [--json] Spec <-> implementation match
|
|
21118
21238
|
verify-code [path] [--build] [--bench] Run tests/lint/typecheck, record evidence
|
|
21119
21239
|
gate [path] One-call CI gate: verify + audit + scan + encoding + lazy
|
|
21120
|
-
lens [code|design|docs|test|security|database] [--json] Quality self-question lenses
|
|
21240
|
+
lens [code|design|docs|test|security|database|debug] [--json] Quality self-question lenses
|
|
21121
21241
|
|
|
21122
21242
|
SECURITY & HYGIENE
|
|
21123
21243
|
scan secrets [path] Committed-secret detection
|
|
@@ -21179,7 +21299,7 @@ function help() {
|
|
|
21179
21299
|
// 1.23.1 (UR-0010 Phase 6): 영어 opt-in 시 큐레이트 영어판. 기본(ko) 은 아래 한국어 help 그대로.
|
|
21180
21300
|
if (_uiLang(arg('--path', process.cwd())) === 'en') { _helpEn(); return; }
|
|
21181
21301
|
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/agy/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness selftest [--json] # 1.9.258 코어 함수 무결성 자가 검증 (설치 손상/부분설치 감지, CI 친화 exit 1)\n leerness shell-guard "<command>" [--json] # 1.9.260 터미널 명령 셸 호환성 린터 (PowerShell 5.1 && 미지원 등 실행 전 감지, UR-0020)\n leerness shell-guard --record --cmd "..." --exit N # 1.9.260 실패한 터미널 명령 기록 → 다음 분석 시 회수\n leerness path-setup [--apply] [--json] # 1.9.254 leerness CLI PATH 자동 등록 (npm global bin 미등록 시)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합
|
|
21182
|
-
leerness lens [code|design|docs|test|security|database] [--json] # 1.18.3 분야별 자기질문 품질 렌즈 (database: 동시성/트랜잭션 — 완료 선언 전 자가 점검)\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
|
|
21302
|
+
leerness lens [code|design|docs|test|security|database|debug] [--json] # 1.18.3 분야별 자기질문 품질 렌즈 (database: 동시성/트랜잭션 — 완료 선언 전 자가 점검)\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
|
|
21183
21303
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
21184
21304
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
21185
21305
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|
|
@@ -21459,9 +21579,9 @@ async function main() {
|
|
|
21459
21579
|
// 1.9.272: leerness capabilities — 권한/보안 표면 공개 (GPT-5.5 외부 리뷰 반영)
|
|
21460
21580
|
if (cmd === 'capabilities' || cmd === 'security-surface') return capabilitiesCmd(arg('--path', process.cwd()), { json: has('--json') });
|
|
21461
21581
|
// 1.9.278 (UR-0032): leerness state <show|start|record|verify|handoff> — .leerness/ 구조화 상태 substrate
|
|
21462
|
-
if (cmd === 'state') return stateCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21582
|
+
if (cmd === 'state') return _guardStore(has('--json'), () => stateCmd(arg('--path', process.cwd()), args[1], ...args.slice(2))); // 1.36.28 (#3): 손상 스토어 클로버 차단
|
|
21463
21583
|
if (cmd === 'context') return contextCmd(_resolveRoot(args[1]), { json: has('--json') });
|
|
21464
|
-
if (cmd === 'team') return teamCmd(_resolveRoot(args[1] === 'list' ? args[2] : null), args[1], args[2], { json: has('--json') }); // 1.9.371 (UR-0073 Phase A): 팀 레지스트리. 1.9.412 (UR-0100): team list 만 positional path
|
|
21584
|
+
if (cmd === 'team') return _guardStore(has('--json'), () => teamCmd(_resolveRoot(args[1] === 'list' ? args[2] : null), args[1], args[2], { json: has('--json') })); // 1.9.371 (UR-0073 Phase A): 팀 레지스트리. 1.9.412 (UR-0100): team list 만 positional path 지원. 1.36.28 (#3): 손상 스토어 클로버 차단
|
|
21465
21585
|
if (cmd === 'brief') return briefCmd(arg('--path', process.cwd()), args[1]);
|
|
21466
21586
|
if (cmd === 'about' || cmd === 'identity') return aboutCmd({ json: has('--json') });
|
|
21467
21587
|
// 1.9.281 (UR-0034): leerness policy <show|set|check> — 권한 등급 (opt-in enforced)
|
|
@@ -21490,7 +21610,7 @@ async function main() {
|
|
|
21490
21610
|
// 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
|
|
21491
21611
|
if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
|
|
21492
21612
|
// 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
|
|
21493
|
-
if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21613
|
+
if (cmd === 'constraints') return _guardStore(has('--json'), () => constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2))); // 1.36.28 (#3): 손상 스토어 클로버 차단
|
|
21494
21614
|
// 1.9.209: leerness pre-wake-audit — sleep 전 sub-agent audit (사용자 명시)
|
|
21495
21615
|
if (cmd === 'pre-wake-audit') return preWakeAuditCmd(arg('--path', process.cwd()), args[1]);
|
|
21496
21616
|
// 1.9.210: leerness wakeup-interval <get|set|auto|history|record> — adaptive interval (사용자 명시)
|
package/lib/pure-utils.js
CHANGED
|
@@ -518,13 +518,22 @@ function _matchConstraints(catalog, text, lang) {
|
|
|
518
518
|
return { matched, suggestions, totalPlatforms: Object.keys(catalog.platforms).length };
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
// 1.36.28 (codex 미검토표면 헌트 #10): alias 매칭 — 순수 ASCII 단어 alias 는 단어경계를 요구해
|
|
522
|
+
// 'rest'→'restore', 'cli'→'client' 같은 부분문자열 오탐을 막는다. 구두점/CJK alias 는 경계 개념이 없어 substring 유지.
|
|
523
|
+
function _aliasHit(lowerText, alias) {
|
|
524
|
+
const a = String(alias || '').toLowerCase();
|
|
525
|
+
if (!a) return false;
|
|
526
|
+
if (/^[a-z0-9]+$/.test(a)) return new RegExp('(?:^|[^a-z0-9])' + a + '(?![a-z0-9])').test(lowerText);
|
|
527
|
+
return lowerText.includes(a);
|
|
528
|
+
}
|
|
529
|
+
|
|
521
530
|
// 1.9.333 패턴 적용: 순수 도메인 매칭 — catalog + 텍스트 → 첫 매칭 domain/alias/components (fs 의존 0, catalog 주입).
|
|
522
531
|
function _matchDomain(catalog, text) {
|
|
523
532
|
if (!text || typeof text !== 'string' || !catalog || !catalog.domains) return { domain: null, alias: null };
|
|
524
533
|
const lower = text.toLowerCase();
|
|
525
534
|
for (const [domain, info] of Object.entries(catalog.domains)) {
|
|
526
535
|
for (const a of info.aliases || []) {
|
|
527
|
-
if (lower
|
|
536
|
+
if (_aliasHit(lower, a)) {
|
|
528
537
|
return { domain, alias: a, components: info.components };
|
|
529
538
|
}
|
|
530
539
|
}
|
|
@@ -746,10 +755,29 @@ function _mergeReadmeSection(existing, block, START, END) {
|
|
|
746
755
|
function _managedMerge(file, next, previous, archiveRel, overwriteSet) {
|
|
747
756
|
if (!previous || previous.trim() === next.trim()) return next;
|
|
748
757
|
const tag = '<!-- leerness:migration-preserved -->';
|
|
749
|
-
if (previous.includes(tag)) return next;
|
|
750
758
|
if (overwriteSet && overwriteSet.has(String(file).replace(/\\/g, '/'))) return next;
|
|
759
|
+
// 1.36.28 (사용자 보고 — 반복 마이그레이션 데이터 유실): 종전 <details> 중첩 설계는 previous 에 preserved 태그가
|
|
760
|
+
// 있으면 통째로 next 만 반환 → 2번째 마이그레이션부터 사용자가 CLAUDE/AGENTS 에 직접 쓴 커스텀 지시가 조용히
|
|
761
|
+
// 사라졌다(매 버전 AI 마이그레이션 흐름에서 상시 발화). 재설계: 라인-집합 diff 로 "새 템플릿에 없는 라인"만
|
|
762
|
+
// Preserved 섹션에 평문으로 이월 — 멱등(재실행해도 동일), 중첩 없음, 크기는 커스텀 라인 수에 비례.
|
|
763
|
+
// 편향은 false-PRESERVE(과보존은 무해) 쪽 — false-DROP(유실)이 버그다.
|
|
764
|
+
const nextLines = new Set(next.split('\n').map(l => l.trim()).filter(Boolean));
|
|
765
|
+
const seen = new Set();
|
|
766
|
+
const custom = [];
|
|
767
|
+
for (const raw of previous.split('\n')) {
|
|
768
|
+
const l = raw.trim();
|
|
769
|
+
if (!l || nextLines.has(l)) continue;
|
|
770
|
+
// preserved 래퍼 자체의 보일러플레이트는 이월 대상 아님 (구 <details> 형식 + 신 평문 형식 모두)
|
|
771
|
+
if (l === tag || l === '---' || l === '<details>' || l === '</details>') continue;
|
|
772
|
+
if (/^```/.test(l) || /^<summary>/.test(l)) continue;
|
|
773
|
+
if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진/.test(l)) continue;
|
|
774
|
+
if (seen.has(l)) continue;
|
|
775
|
+
seen.add(l);
|
|
776
|
+
custom.push(raw.replace(/\\`\\`\\`/g, '```')); // 구 형식에서 이스케이프된 펜스 복원
|
|
777
|
+
}
|
|
778
|
+
if (!custom.length) return next;
|
|
751
779
|
const ar = archiveRel || '.harness/archive';
|
|
752
|
-
return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\
|
|
780
|
+
return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\n> 이전 버전에서 이어진 사용자/프로젝트 커스텀 내용 — 마이그레이션마다 자동 이월됩니다. 전체 원본 백업: \`${ar}\`\n\n` + custom.join('\n') + '\n';
|
|
753
781
|
}
|
|
754
782
|
|
|
755
783
|
// 1.9.369 (UR-0025): --skills 값 파싱 순수 코어 — catalog 주입(harness skillCatalog 비결합). all/recommended/csv 처리 + catalog 필터.
|
|
@@ -1145,7 +1173,7 @@ module.exports = {
|
|
|
1145
1173
|
// 1.9.333 (UR-0025 심층): 순수 플랫폼 제약 매칭
|
|
1146
1174
|
_matchConstraints,
|
|
1147
1175
|
// 1.9.333 패턴 적용: 순수 도메인 매칭
|
|
1148
|
-
_matchDomain,
|
|
1176
|
+
_matchDomain, _aliasHit,
|
|
1149
1177
|
// 1.9.335 (UR-0025 심층): LSP 서브시스템 — 순수 언어감지 + 정규식 심볼 매처
|
|
1150
1178
|
_detectLspLang, _matchLspSymbols,
|
|
1151
1179
|
// anti-laziness optimism-check 순수 로직
|
|
@@ -1370,6 +1398,12 @@ function _featureBlock(node) {
|
|
|
1370
1398
|
// 1.9.391 (UR-0025): feature 영향 BFS — affects + co-changes-with transitive + depends-on 역방향. 순수(nodes,startId→result). harness(handoff/audit)+lib/feature 공유.
|
|
1371
1399
|
function _featureImpactBfs(nodes, startId) {
|
|
1372
1400
|
const byId = new Map(nodes.map(n => [n.id, n]));
|
|
1401
|
+
// 역방향 인접: X 에 depends-on 하는 노드들 (BFS 안에서 전이 확장하려고 사전 구축)
|
|
1402
|
+
const dependents = new Map();
|
|
1403
|
+
for (const n of nodes) for (const dep of n.dependsOn || []) {
|
|
1404
|
+
if (!dependents.has(dep)) dependents.set(dep, []);
|
|
1405
|
+
dependents.get(dep).push(n.id);
|
|
1406
|
+
}
|
|
1373
1407
|
const visited = new Set();
|
|
1374
1408
|
const queue = [{ id: startId, depth: 0, via: 'self' }];
|
|
1375
1409
|
const result = [];
|
|
@@ -1382,14 +1416,9 @@ function _featureImpactBfs(nodes, startId) {
|
|
|
1382
1416
|
if (cur.depth > 0) result.push({ id: cur.id, title: node.title, depth: cur.depth, via: cur.via, files: node.files, errorModes: node.errorModes });
|
|
1383
1417
|
for (const next of node.affects || []) queue.push({ id: next, depth: cur.depth + 1, via: 'affects' });
|
|
1384
1418
|
for (const next of node.coChangesWith || []) queue.push({ id: next, depth: cur.depth + 1, via: 'co-changes-with' });
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
if (visited.has(n.id)) continue;
|
|
1389
|
-
if ((n.dependsOn || []).includes(startId)) {
|
|
1390
|
-
result.push({ id: n.id, title: n.title, depth: 1, via: 'depends-on(reverse)', files: n.files, errorModes: n.errorModes });
|
|
1391
|
-
visited.add(n.id);
|
|
1392
|
-
}
|
|
1419
|
+
// 1.36.28 (codex 미검토표면 헌트 #8): 역방향 depends-on 도 BFS 안에서 확장 — 전이 영향(A←B←C 에서 A 변경이 C 까지)을
|
|
1420
|
+
// 잡는다. 종전엔 BFS 종료 후 1-홉만 추가해 간접 의존이 누락됐다. visited 로 사이클 안전.
|
|
1421
|
+
for (const next of dependents.get(cur.id) || []) queue.push({ id: next, depth: cur.depth + 1, via: 'depends-on(reverse)' });
|
|
1393
1422
|
}
|
|
1394
1423
|
return result;
|
|
1395
1424
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.28",
|
|
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",
|