leerness 1.35.3 → 1.35.5
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 +22 -0
- package/README.md +4 -4
- package/bin/leerness.js +3 -2
- package/lib/analyzers.js +1 -1
- package/lib/graph.js +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.35.5 — 2026-06-27 — 17th 버그헌트: scan .json5/.jsonc FN + verify-claim git 매칭 정밀도
|
|
4
|
+
|
|
5
|
+
**비-graph 버그헌트(게시본 1.35.4 후, R-0011)**. Explore 에이전트가 낸 6개 후보를 **맹신 X 재현으로 검증** → 강한 후보 다수가 거짓 판명(멀티라인 시크릿·`test_`고엔트로피 FN은 실제 재현에서 이미 flagged; agent의 `api_secret`↔`secret` 매칭 가정도 word-boundary로 오류), **확정 2건만** 수정.
|
|
6
|
+
|
|
7
|
+
### 수정 (재현 확인)
|
|
8
|
+
- **scan secrets — .json5/.jsonc 미스캔 FN (확인)**: `SCAN_TEXT_EXT` 가 `.json` 만 포함하고 `.json5`/`.jsonc` 를 제외 → 해당 설정 파일의 시크릿이 스캔에서 누락. 두 확장자 추가. 재현: JSON5 의 `api_key: "sk-…"` 시크릿이 이제 flagged. (참고: 따옴표 JSON 키 `"api_key":` 미매칭은 `.json` 포함 **기존 패턴 한계** — 본 변경 무관, 별도 영역.)
|
|
9
|
+
- **verify-claim — git 교차검사 basename 충돌 (정밀도)**: `_claimFileInGit` 의 역방향 매칭 `c.endsWith('/'+g)` 가 bare basename 도 매칭 → claimed `src/test.js` 가 무관한 git 변경 `test.js` 와 오매칭(외과적-변경 신호 약화). git 경로가 다중세그먼트일 때만 역매칭하도록 한정. forward/exact 정상. (정직: 완전한 false-pass 시나리오는 미재현 — 정밀도 개선으로 분류.)
|
|
10
|
+
|
|
11
|
+
### 검증
|
|
12
|
+
- selftest **265** (신규: `_claimFileInGit` 단위 — 충돌 차단 + forward/exact/nested-reverse 보존, .json5/.jsonc 멤버십). full e2e (verify-claim git 로직 무회귀). patch — npm 미배포(R-0011).
|
|
13
|
+
|
|
14
|
+
## 1.35.4 — 2026-06-27 — graph 폴리시: 엣지 종류별 색상 + PNG 내보내기
|
|
15
|
+
|
|
16
|
+
**graph 시각/공유(1.35.3 게시 후 누적, R-0011)**.
|
|
17
|
+
|
|
18
|
+
### 변경 (lib/graph.js 템플릿)
|
|
19
|
+
- **엣지 종류별 색상**: milestone(amber)/ref(blue)/link(green)/feature(gray) 엣지를 색으로 구분 — 관계 종류를 한눈에. 선택 노드 연결 엣지는 기존 하이라이트 유지.
|
|
20
|
+
- **PNG 내보내기**: `p` 키 → 현재 그래프를 `leerness-graph.png`로 저장(배경색 합성 후 canvas toDataURL). 문서/PR 공유용. hint 바 안내 추가.
|
|
21
|
+
|
|
22
|
+
### 검증
|
|
23
|
+
- selftest **264** (임베드-script JS 신택스 가드가 EKIND/exportPng/keydown 추가분 컴파일 유효성 자동 검증) · lib/graph.js 템플릿만 변경(데이터/엣지 로직 무변경). patch — npm 미배포(R-0011).
|
|
24
|
+
|
|
3
25
|
## 1.35.3 — 2026-06-27 — graph 네비게이션: 검색 Enter 점프 + f/dblclick fit + Esc
|
|
4
26
|
|
|
5
27
|
**graph "손쉽게 조회" 강화(1.35.0 게시 후 누적, R-0011)**: 온톨로지 그래프에서 노드를 빠르게 찾아 조회하는 키보드/마우스 네비게이션.
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ MIT
|
|
|
115
115
|
<!-- leerness:project-readme:start -->
|
|
116
116
|
## Leerness Project Harness
|
|
117
117
|
|
|
118
|
-
이 프로젝트는 Leerness v1.35.
|
|
118
|
+
이 프로젝트는 Leerness v1.35.5 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
119
119
|
|
|
120
120
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
121
121
|
|
|
@@ -169,7 +169,7 @@ leerness memory restore decision <date|title>
|
|
|
169
169
|
|
|
170
170
|
### MCP server (외부 AI 통합)
|
|
171
171
|
|
|
172
|
-
Leerness v1.35.
|
|
172
|
+
Leerness v1.35.5는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
173
173
|
|
|
174
174
|
```jsonc
|
|
175
175
|
// 카테고리별
|
|
@@ -190,7 +190,7 @@ Leerness v1.35.3는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
190
190
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
191
191
|
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) 다음 라운드 예약.
|
|
192
192
|
|
|
193
|
-
현재 누적: **70 라운드 (1.9.40 → 1.35.
|
|
193
|
+
현재 누적: **70 라운드 (1.9.40 → 1.35.5)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
194
194
|
|
|
195
195
|
### 성능 가이드 (1.9.140 측정)
|
|
196
196
|
|
|
@@ -228,6 +228,6 @@ leerness release pack --close --auto-main-push
|
|
|
228
228
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
229
229
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
230
230
|
|
|
231
|
-
Last synced by Leerness v1.35.
|
|
231
|
+
Last synced by Leerness v1.35.5: 2026-06-27
|
|
232
232
|
<!-- leerness:project-readme:end -->
|
|
233
233
|
|
package/bin/leerness.js
CHANGED
|
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
32
32
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
33
33
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_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 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
|
|
34
34
|
|
|
35
|
-
const VERSION = '1.35.
|
|
35
|
+
const VERSION = '1.35.5';
|
|
36
36
|
|
|
37
37
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
38
38
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -2873,6 +2873,7 @@ function _selfTestCases() {
|
|
|
2873
2873
|
{ name: 'verify-claim git diff 시맨틱 교차검증: _gitChangedFiles/_claimFileInGit + strict FAIL 통합 (UR-0042 외부리뷰 1.9.302)', run: () => { const fnOk = typeof _gitChangedFiles === 'function' && typeof _claimFileInGit === 'function'; const matchOk = _claimFileInGit('src/api.js', new Set(['src/api.js'])) === true && _claimFileInGit('./src/api.js', new Set(['src/api.js'])) === true && _claimFileInGit('other.js', new Set(['src/api.js'])) === false && _claimFileInGit('x', null) === null; const src = read(__filename); const wired = /git diff 교차검증/.test(src) && /\|\| !gitClaimOk/.test(src) && /_gitChangedFiles\(root\)/.test(src); return fnOk && matchOk && wired; } },
|
|
2874
2874
|
{ name: '_withLock/_updateRun: lost-update 락(O_EXCL+재진입) + 적용 (UR-0043 외부리뷰 1.9.303)', run: () => { const src = read(__filename); const fnOk = typeof _withLock === 'function' && typeof _sleepSyncMs === 'function' && typeof _updateRun === 'function'; const reentrant = /if \(_heldLocks\.has\(lockPath\)\) return fn\(\)/.test(src); const excl = /fs\.openSync\(lockPath, 'wx'\)/.test(src); const applied = /const id = _withLock\(progressPath\(root\)/.test(src) && /_updateRun\(root, curId/.test(src); return fnOk && reentrant && excl && applied; } },
|
|
2875
2875
|
{ name: 'lib/analyzers: 분석/검증 함수 4종 모듈 단일출처 분리 (UR-0025 1.9.304)', run: () => { const m = require('../lib/analyzers'); return m._evidenceQuality === _evidenceQuality && m._shellGuardAnalyze === _shellGuardAnalyze && m._parseEvidenceStats === _parseEvidenceStats && m._claimFileInGit === _claimFileInGit && !/function _evidenceQuality\(evidence\) \{/.test(read(__filename)) && !/function _shellGuardAnalyze\(cmd, ctx\) \{/.test(read(__filename)); } },
|
|
2876
|
+
{ name: '17th헌트: _claimFileInGit bare-basename 충돌 차단 + scan .json5/.jsonc 포함 (1.35.5)', run: () => { const a = require('../lib/analyzers'); const collisionFixed = a._claimFileInGit('src/test.js', new Set(['test.js'])) !== true; const forwardOk = a._claimFileInGit('test.js', new Set(['src/test.js'])) === true; const exactOk = a._claimFileInGit('src/a.js', new Set(['src/a.js'])) === true; const nestedReverseOk = a._claimFileInGit('x/src/a.js', new Set(['src/a.js'])) === true; const src = read(__filename); const json5Ext = src.includes("'.js" + "on5'") && src.includes("'.js" + "onc'"); return collisionFixed && forwardOk && exactOk && nestedReverseOk && json5Ext; } },
|
|
2876
2877
|
{ name: 'honesty-check: AI 인식론적 정직성 3차원 + MCP/CLI/strict 통합 (사용자명시 1.9.305)', run: () => { const h = _epistemicHonestyCheck; const d1 = h('이 기능은 항상 정상 동작합니다').findings.some(f => f.dim === 'pretend-knowledge'); const d2 = h('아마 될 것 같습니다. 구현 완료했습니다').findings.some(f => f.dim === 'premature-judgment'); const d3 = h('이 API 의 rate limit 은 초당 5회입니다').findings.some(f => f.dim === 'no-info-gathering'); const clean = h('src/api.js 수정, 12/12 통과 (Exit: 0)').ok === true; const src = read(__filename); const wired = require('../lib/mcp-tools').some(t => t.name === 'leerness_honesty_check') && /if \(cmd === 'honesty-check'\)/.test(src) && /honestyFindings = _epistemicHonestyCheck/.test(src); return d1 && d2 && d3 && clean && wired; } },
|
|
2877
2878
|
{ name: 'exit code 일관성: fail()→exitCode 1 행위 + unknown 명령 안내 (UR-0045 / CV-5 행위화 1.9.366)', run: () => { if (typeof fail !== 'function') return false; const saved = process.exitCode; const _w = process.stdout.write; let setOk = false; try { process.stdout.write = () => true; process.exitCode = 0; fail('selftest probe'); setOk = process.exitCode === 1; } finally { process.stdout.write = _w; process.exitCode = saved; } const src = read(__filename); const dispatchOk = /알 수 없는 명령: \$\{cmd\}/.test(src); return setOk && dispatchOk; } },
|
|
2878
2879
|
{ name: 'brief: 프로젝트 청사진 set/show/export + README 개요 섹션 (UR-0055 사용자명시 1.9.307)', run: () => { const src = read(__filename); const fnOk = typeof briefCmd === 'function' && typeof _loadBrief === 'function' && typeof _briefBlueprint === 'function' && _BRIEF_FIELDS.length === 10; const b = { project: 'X', intro: 'i', purpose: 'p', problem: '', features: ['f1', 'f2'], stack: ['s'], architecture: '', users: [], success: [], nonGoals: [], currentState: '' }; const bp = _briefBlueprint(b, VERSION); const bpOk = /Blueprint/.test(bp) && /소개 \(Intro\)/.test(bp) && /f1/.test(bp) && /신규 프로젝트 시작 가이드/.test(bp); const rb = _briefReadmeBlock(b); const rbOk = rb.includes(BRIEF_START) && rb.includes(BRIEF_END) && /프로젝트 개요/.test(rb) && /\*\*목적\*\*/.test(rb); return fnOk && bpOk && rbOk && /if \(cmd === 'brief'\)/.test(src); } },
|
|
@@ -8120,7 +8121,7 @@ function isSkippedRel(rel, extras = []) {
|
|
|
8120
8121
|
if (segs.some(s => SCAN_SKIP_DIRS.has(s))) return true; // SCAN_SKIP_DIRS 는 Set
|
|
8121
8122
|
return extras.some(d => rel === d || rel.startsWith(d + '/'));
|
|
8122
8123
|
}
|
|
8123
|
-
const SCAN_TEXT_EXT = new Set(['.js','.ts','.jsx','.tsx','.mjs','.cjs','.json','.md','.txt','.env','.bash','.sh','.yml','.yaml','.toml','.ini','.cfg','.py','.rb','.go','.rs','.java','.kt','.swift','.cs','.php','.sql','.html','.css','.scss','.less','.xml','.bat','.ps1','']);
|
|
8124
|
+
const SCAN_TEXT_EXT = new Set(['.js','.ts','.jsx','.tsx','.mjs','.cjs','.json','.json5','.jsonc','.md','.txt','.env','.bash','.sh','.yml','.yaml','.toml','.ini','.cfg','.py','.rb','.go','.rs','.java','.kt','.swift','.cs','.php','.sql','.html','.css','.scss','.less','.xml','.bat','.ps1','']);
|
|
8124
8125
|
function* walk(root, base = root, depth = 0, extras = null) {
|
|
8125
8126
|
if (depth > 12) return;
|
|
8126
8127
|
if (extras === null) extras = getExtraSkipDirs(root);
|
package/lib/analyzers.js
CHANGED
|
@@ -48,7 +48,7 @@ function _evidenceQuality(evidence) {
|
|
|
48
48
|
function _claimFileInGit(claimed, gitSet) {
|
|
49
49
|
if (!gitSet) return null;
|
|
50
50
|
const c = String(claimed).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
51
|
-
for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || c.endsWith('/' + g)) return true; }
|
|
51
|
+
for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || (g.indexOf('/') >= 0 && c.endsWith('/' + g))) return true; } // 1.35.5: reverse match 는 git 경로가 다중세그먼트일 때만 — bare basename 충돌(src/test.js ↔ test.js) 차단
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
function _parseEvidenceStats(text) {
|
package/lib/graph.js
CHANGED
|
@@ -49,11 +49,12 @@ canvas{position:fixed;inset:0;top:46px}
|
|
|
49
49
|
<canvas id="c"></canvas>
|
|
50
50
|
<div id="panel"><span class="x" onclick="closePanel()">✕</span><div id="pbody"></div></div>
|
|
51
51
|
<div id="empty">No nodes — run <b>leerness handoff .</b> to populate the harness, then regenerate.</div>
|
|
52
|
-
<div id="hint">drag node · scroll zoom · drag bg pan · click node → details · search+Enter jump · f / dblclick fit · Esc close</div>
|
|
52
|
+
<div id="hint">drag node · scroll zoom · drag bg pan · click node → details · search+Enter jump · f / dblclick fit · p export PNG · Esc close</div>
|
|
53
53
|
<script>
|
|
54
54
|
var DATA = /*__DATA__*/null;
|
|
55
55
|
var COLORS={task:'#58a6ff',plan:'#d29922',decision:'#39d0d8',lesson:'#e3b341',rule:'#bc8cff',skill:'#2dd4bf',feature:'#6e7681'};
|
|
56
56
|
var STATUSCOL={done:'#3fb950',verified:'#3fb950','in-progress':'#58a6ff',in_progress:'#58a6ff',blocked:'#f85149',waiting:'#d29922',planned:'#8b949e',requested:'#8b949e'};
|
|
57
|
+
var EKIND={milestone:'rgba(210,153,34,.22)',ref:'rgba(88,166,255,.20)',link:'rgba(57,211,83,.20)',feature:'rgba(110,118,129,.26)'};
|
|
57
58
|
function nodeColor(n){ if(n.type==='task'&&STATUSCOL[n.status])return STATUSCOL[n.status]; return COLORS[n.type]||'#8b949e'; }
|
|
58
59
|
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
59
60
|
|
|
@@ -102,7 +103,7 @@ function draw(){
|
|
|
102
103
|
ctx.clearRect(0,0,W,H);
|
|
103
104
|
// edges
|
|
104
105
|
ctx.lineWidth=1;
|
|
105
|
-
edges.forEach(function(e){var a=idx[e.source],b=idx[e.target]; if(off[a.type]||off[b.type])return; var p=toScreen(a),q=toScreen(b); var on=sel&&(e.source===sel.id||e.target===sel.id); ctx.strokeStyle=on?'rgba(57,211,83,.55)':'rgba(120,130,145,.16)'; ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke();});
|
|
106
|
+
edges.forEach(function(e){var a=idx[e.source],b=idx[e.target]; if(off[a.type]||off[b.type])return; var p=toScreen(a),q=toScreen(b); var on=sel&&(e.source===sel.id||e.target===sel.id); ctx.strokeStyle=on?'rgba(57,211,83,.55)':(EKIND[e.kind]||'rgba(120,130,145,.16)'); ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke();});
|
|
106
107
|
// nodes
|
|
107
108
|
nodes.forEach(function(n){ if(off[n.type])return; var p=toScreen(n); var r=(3+Math.min(7,n.deg*0.7))*Math.max(.6,view.k*.9); var dim=sel&&!nbr[n.id]&&n.id!==sel.id; var srch=window._q&&(n.label||'').toLowerCase().indexOf(window._q)<0&&n.id.toLowerCase().indexOf(window._q)<0;
|
|
108
109
|
ctx.globalAlpha=(dim||srch)?0.18:1; ctx.fillStyle=nodeColor(n); ctx.beginPath();ctx.arc(p.x,p.y,r,0,6.2832);ctx.fill();
|
|
@@ -140,7 +141,8 @@ function showPanel(n){
|
|
|
140
141
|
window.goto=function(id){var n=idx[id];if(n){select(n);cam.cx=n.x;cam.cy=n.y;view.x=0;view.y=0;}};
|
|
141
142
|
document.getElementById('search').addEventListener('input',function(ev){window._q=ev.target.value.trim().toLowerCase()||null;});
|
|
142
143
|
document.getElementById('search').addEventListener('keydown',function(ev){ if(ev.key!=='Enter'||!window._q)return; var h=null; for(var i=0;i<nodes.length;i++){var n=nodes[i]; if(off[n.type])continue; if((n.label||'').toLowerCase().indexOf(window._q)>=0||n.id.toLowerCase().indexOf(window._q)>=0){h=n;break;}} if(h){_fit=true;goto(h.id);} });
|
|
143
|
-
|
|
144
|
+
function exportPng(){ try{ var t=document.createElement('canvas'); t.width=cv.width; t.height=cv.height; var tx=t.getContext('2d'); tx.fillStyle='#0a0d12'; tx.fillRect(0,0,t.width,t.height); tx.drawImage(cv,0,0); var a=document.createElement('a'); a.download='leerness-graph.png'; a.href=t.toDataURL('image/png'); a.click(); }catch(e){} }
|
|
145
|
+
window.addEventListener('keydown',function(ev){ if(ev.target&&ev.target.tagName==='INPUT')return; if(ev.key==='f'||ev.key==='F'){_fit=true;fitView();} else if(ev.key==='p'||ev.key==='P'){exportPng();} else if(ev.key==='Escape'){closePanel();} });
|
|
144
146
|
cv.addEventListener('dblclick',function(ev){ if(!hit(ev.offsetX,ev.offsetY)){_fit=true;fitView();} });
|
|
145
147
|
</script></body></html>`;
|
|
146
148
|
|