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 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.3 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
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.3는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
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.3)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
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.3: 2026-06-27
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.3';
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
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
- 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==='Escape'){closePanel();} });
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.35.3",
3
+ "version": "1.35.5",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",