leerness 1.36.38 → 1.36.40
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 +18 -0
- package/README.ko.md +2 -2
- package/README.md +6 -6
- package/bin/leerness.js +80 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.40 — 2026-07-16 — 사용자-대면 주장 사실성 감사 — 드리프트 수치 소거 (정기 정직성 점검)
|
|
4
|
+
|
|
5
|
+
22 릴리스 동안 README/docs 의 정량 주장이 코드와 어긋났는지 전수 대조(주기 점검 — 과거 1.36.5 감사의 후속).
|
|
6
|
+
|
|
7
|
+
- **정확 확인**: "0 runtime dependencies"(실측 0) · 템플릿 관리 구간의 MCP 도구 수(`_mcpToolCount()` 동적 — 86 실측 일치).
|
|
8
|
+
- **드리프트 3건 교정**: ① README "e2e **219/219**"(현재 386 — 릴리스마다 거짓이 되는 고정 수치) → "전체 e2e 스위트 통과" 서술로. ② "**70 라운드** 누적"(실제 태그 427) → "v1.9.x → 현재 릴리스 태그 이력(수백 라운드)". ③ README.ko **내부 모순**(같은 문서에 86 과 85 공존) + 수동 구간 배지/문구의 하드코드 → 산문은 **80+ 표기**, 배지는 정확 수치 유지 — 게이트 e2e 가 배지==tools/list 실측 정합을 매 릴리스 강제하는 가드-검증형 표면임을 재확인(80+ 시도는 그 가드가 차단 — 가드-검증형엔 정확 수치가 우월).
|
|
9
|
+
- 원칙: 자동 동기화되지 않는 표면에는 릴리스마다 낡는 고정 수치를 쓰지 않는다 — 정확 수치는 동적 주입 구간에만.
|
|
10
|
+
- **검증**: readme sync 재생성 후 잔여 스윕 0, selftest 310/310, exit 전파형 게이트 e2e, 클린룸.
|
|
11
|
+
|
|
12
|
+
## 1.36.39 — 2026-07-16 — 판정 정직화 배치 B — codex 4차 헌트 10건 전건 처분 완결 (#3/#5/#6/#7/#8)
|
|
13
|
+
|
|
14
|
+
- **#3 MCP 파라미터 검증**: `arguments: null`/비객체가 args 접근 throw → stdin 캐치가 -32700 "Parse error"(id null)로 오보 + `{"title":{...}}` 같은 비문자열이 `[object Object]` 로 영속. → 비객체 arguments/스키마 타입 위반/필수 누락은 **-32602 + 원 id**, 파싱과 핸들링 캐치 분리(-32603 은 원 id 유지). 원 재현 2종 실측 + 정상 호출 무회귀.
|
|
15
|
+
- **#5 decisions 이중 소스**: retro/insights 가 md 를 직접 읽어 `decision list`/`memory status`(canonical json)와 불일치 → canonical `_loadDecisions` 단일 소스(신호 텍스트·recent 목록·카운트 전부, cutoff 는 date 필드). 실측: md 수동 훼손 후에도 list=retro=2 일치.
|
|
16
|
+
- **#6 llm-bench record**: -1/NaN 점수·무제한 필드 무검증 저장 → 유한·비음수 점수 필수(`invalid_score`), 필드 200자 상한 + 파이프/개행 셀 이스케이프.
|
|
17
|
+
- **#7 benchmark**: 미초기화 빈 디렉토리에 **497/600** — 4개 차원이 상수·2개는 높은 바닥값 → .harness 없으면 측정 거부(`harness_missing`), 점수 응답에 `scoreBasis`(measuredDims/constantDims/시뮬 명시) 라벨 — 설계치를 실측으로 오독 방지([[lesson-tool-overstates-own-maturity]] 계열).
|
|
18
|
+
- **#8 pulse↔health 모순**: pulse 가 계산하지 않는 security/health/driftScore 를 'unknown' 상태값처럼 노출 → null + `notComputed` 명시(계산은 `leerness health`).
|
|
19
|
+
- **검증**: selftest 310/310, 5건 원 재현 before/after + 무회귀, exit 전파형 게이트 e2e, 클린룸. **codex 4차 10건 전건 처분(수정 10 · DEFER 0).**
|
|
20
|
+
|
|
3
21
|
## 1.36.38 — 2026-07-16 — 분석 정직화 — retro 기간필터·신호 정규식·태그 계보·usage 귀속 (codex 4차 #2/#4/#9/#10)
|
|
4
22
|
|
|
5
23
|
- **#2 `retro --days`**: cutoff 를 계산만 하고 미적용 — 주간 회고가 전기간 집계였다. → 날짜 헤딩 섹션 필터(`_filterDatedSections`)를 task-log/evidence/decisions-신호에 적용(결정 블록 수는 "누적" 라벨대로 누적 유지). 실측: days=1 에서 2020 데이터 제외.
|
package/README.ko.md
CHANGED
|
@@ -108,7 +108,7 @@ leerness verify-claim T-0001 --require-evidence
|
|
|
108
108
|
- **외부 에이전트**: `agents list/check/dispatch` · `provider` · `roles` · `adapter`
|
|
109
109
|
- **운영/확장**: `release` · `migrate` · `team` · `install-safety` · `route` · `review`(페르소나)
|
|
110
110
|
- **브리지(opt-in)**: `web`(playwright) · `pc`(robotjs) · `lsp`
|
|
111
|
-
- **MCP**: `mcp serve` — stdio JSON-RPC 서버로
|
|
111
|
+
- **MCP**: `mcp serve` — stdio JSON-RPC 서버로 80+ 도구 노출 (verify-claim --all 일괄 검증 포함, 1.33.3)
|
|
112
112
|
|
|
113
113
|
전체 명령은 `leerness commands` 또는 `leerness --help` 로 확인하세요.
|
|
114
114
|
|
|
@@ -142,7 +142,7 @@ leerness agents dispatch "코드 리뷰" --to codex # 실행 명령 생성(직
|
|
|
142
142
|
|
|
143
143
|
**MCP (외부 AI 에이전트에 도구로 노출)**
|
|
144
144
|
```bash
|
|
145
|
-
leerness mcp serve # JSON-RPC over stdio,
|
|
145
|
+
leerness mcp serve # JSON-RPC over stdio, 80+ 도구
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
---
|
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ You never have to type a command yourself. Paste this into Claude Code, Cursor,
|
|
|
49
49
|
|
|
50
50
|
The agent installs and operates it for you — `leerness init` also writes the instructions into CLAUDE.md / AGENTS.md so future sessions pick them up automatically.
|
|
51
51
|
|
|
52
|
-
Prefer pure natural language? leerness ships an **MCP server with
|
|
52
|
+
Prefer pure natural language? leerness ships an **MCP server with 80+ tools** (`leerness mcp serve`). Connect it once to Claude Desktop / Claude Code and just ask: *"what was I working on?"*, *"did the AI actually finish T-0001?"*
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
@@ -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.40 하네스를 사용합니다. 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.40는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
179
179
|
|
|
180
180
|
```jsonc
|
|
181
181
|
// 카테고리별
|
|
@@ -194,9 +194,9 @@ Leerness v1.36.38는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
194
194
|
### Autonomous mode (자율 모드)
|
|
195
195
|
|
|
196
196
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
197
|
-
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3)
|
|
197
|
+
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
|
|
198
198
|
|
|
199
|
-
현재 누적: **
|
|
199
|
+
현재 누적: **v1.9.x → 1.36.40 릴리스 태그 이력** (수백 라운드) · _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.40: 2026-07-16
|
|
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.40';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -365,10 +365,11 @@ function managedReadmeBlock(project) {
|
|
|
365
365
|
'',
|
|
366
366
|
'### Autonomous mode (자율 모드)',
|
|
367
367
|
'',
|
|
368
|
+
// 1.36.40 (주장 사실성 감사): 고정 수치(e2e 219/219, 70 라운드)는 릴리스마다 드리프트해 거짓이 됨 — 비드리프트 서술로.
|
|
368
369
|
'`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:',
|
|
369
|
-
'1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3)
|
|
370
|
+
'1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.',
|
|
370
371
|
'',
|
|
371
|
-
`현재 누적: **
|
|
372
|
+
`현재 누적: **v1.9.x → ${VERSION} 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.`,
|
|
372
373
|
'',
|
|
373
374
|
'### 성능 가이드',
|
|
374
375
|
'',
|
|
@@ -3257,6 +3258,21 @@ function _selfTestCases() {
|
|
|
3257
3258
|
const cutoffWired = s.includes('_retroAggregate(root, cutoff)');
|
|
3258
3259
|
return filterOk && sigOk && tagOk && celebOk && usageOk && cutoffWired;
|
|
3259
3260
|
} },
|
|
3261
|
+
{ name: '판정 정직화 배치B (1.36.39, codex 4차 #3/#5/#6/#7/#8): MCP -32602 검증 + retro canonical decisions + llm-bench 검증 + benchmark 정직 라벨 + pulse notComputed', run: () => {
|
|
3262
|
+
const s = read(__filename);
|
|
3263
|
+
// #3: arguments 비객체 -32602 + 스키마 타입 검증 + 파싱/핸들링 분리
|
|
3264
|
+
const mcpOk = s.includes("code: -32602, message: 'Invalid params: arguments must be an object'")
|
|
3265
|
+
&& s.includes("must be a string") && s.includes("catch (e) { send({ jsonrpc: '2.0', id: (req && req.id != null) ? req.id : null");
|
|
3266
|
+
// #5: retro 가 canonical _loadDecisions 사용 (행위: json 에 2건 → retro 도 2)
|
|
3267
|
+
const declOk = s.includes('const _decsCanon = (() => { try { return _loadDecisions(root) || []; }');
|
|
3268
|
+
// #6: llm-bench 점수 검증 + 셀 이스케이프
|
|
3269
|
+
const benchRecOk = s.includes("'invalid_score'") && s.includes('const _bcell = v =>');
|
|
3270
|
+
// #7: 미초기화 측정 거부 + 점수 근거 라벨
|
|
3271
|
+
const benchOk = s.includes("failJson(has('--json'), 'harness_missing', `측정 불가") && s.includes("constantDims: ['autoVerify', 'workspace', 'contextKeep']");
|
|
3272
|
+
// #8: pulse notComputed
|
|
3273
|
+
const pulseOk = s.includes("notComputed: ['security', 'health', 'driftScore']");
|
|
3274
|
+
return mcpOk && declOk && benchRecOk && benchOk && pulseOk;
|
|
3275
|
+
} },
|
|
3260
3276
|
{ name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
|
|
3261
3277
|
const d = LENS_CATALOG.debug;
|
|
3262
3278
|
if (!d) return false;
|
|
@@ -5326,9 +5342,12 @@ function pulseCmd(root) {
|
|
|
5326
5342
|
roundCount: 0,
|
|
5327
5343
|
mcpTools: 0,
|
|
5328
5344
|
memorySurface: 'T0/D0/R0/P0/L0',
|
|
5329
|
-
|
|
5330
|
-
health:
|
|
5345
|
+
// 1.36.39 (codex 4차 #8): pulse 는 이 3종을 계산하지 않는다 — 상태값처럼 보이던 'unknown' placeholder 가
|
|
5346
|
+
// health --json(healthy:true)과 모순돼 보였음. null + notComputed 로 명시(계산은 leerness health).
|
|
5347
|
+
security: null,
|
|
5348
|
+
health: null,
|
|
5331
5349
|
driftScore: null,
|
|
5350
|
+
notComputed: ['security', 'health', 'driftScore'],
|
|
5332
5351
|
nextMilestone: null,
|
|
5333
5352
|
etaDays: null,
|
|
5334
5353
|
abnormalShutdown: 'none'
|
|
@@ -13987,9 +14006,13 @@ function llmBenchRecordCmd(root) {
|
|
|
13987
14006
|
const tokens = arg('--tokens', null);
|
|
13988
14007
|
const model = arg('--model', 'unknown');
|
|
13989
14008
|
if (!score) { fail('--score 필요'); return process.exit(1); }
|
|
14009
|
+
// 1.36.39 (codex 4차 #6): 무의미 데이터 무검증 저장 — 유한/비음수 점수 + 필드 길이 상한 + 셀 파이프 이스케이프.
|
|
14010
|
+
const scoreN = Number(score);
|
|
14011
|
+
if (!Number.isFinite(scoreN) || scoreN < 0) { failJson(has('--json'), 'invalid_score', `--score 는 0 이상의 유한 숫자여야 합니다 (받음: ${score})`); return; }
|
|
14012
|
+
const _bcell = v => String(v == null ? '' : v).slice(0, 200).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
13990
14013
|
const histFile = path.join(root, '.harness', 'llm-bench-history.md');
|
|
13991
14014
|
if (!exists(path.dirname(histFile))) fs.mkdirSync(path.dirname(histFile), { recursive: true });
|
|
13992
|
-
const row = `| ${today()} | ${model} | ${label} | ${
|
|
14015
|
+
const row = `| ${today()} | ${_bcell(model)} | ${_bcell(label)} | ${scoreN} | ${_bcell(tokens) || '?'} |\n`;
|
|
13993
14016
|
if (!exists(histFile)) {
|
|
13994
14017
|
writeUtf8(histFile, `# LLM Bench History\n\n| Date | Model | Label | Score | Tokens |\n|---|---|---|---:|---:|\n${row}`);
|
|
13995
14018
|
} else {
|
|
@@ -14097,7 +14120,12 @@ function _retroAggregate(root, cutoff) {
|
|
|
14097
14120
|
// decisions 블록 수는 "누적" 라벨 그대로 누적 유지, 신호 카운트용 텍스트만 기간 필터.
|
|
14098
14121
|
const tlog = _filterDatedSections(exists(taskLogPath(root)) ? read(taskLogPath(root)) : '', cutoff);
|
|
14099
14122
|
const evidence = _filterDatedSections(exists(evidencePath(root)) ? read(evidencePath(root)) : '', cutoff);
|
|
14100
|
-
|
|
14123
|
+
// 1.36.39 (codex 4차 #5): decisions 는 canonical decisions.json 단일 소스 — md 직접 읽기는 decision list/memory status 와
|
|
14124
|
+
// 불일치를 만들었다(md 수동 편집/프로젝션 중단 시). 신호 텍스트도 canonical 객체에서 합성 + cutoff 는 date 필드로.
|
|
14125
|
+
const _decsCanon = (() => { try { return _loadDecisions(root) || []; } catch { return []; } })();
|
|
14126
|
+
const _decisionsSig = _decsCanon
|
|
14127
|
+
.filter(d => !cutoff || !d.date || String(d.date).slice(0, 10) >= cutoff)
|
|
14128
|
+
.map(d => [d.decision, d.title, d.reason, d.impact].filter(Boolean).join(' ')).join('\n');
|
|
14101
14129
|
const handoff = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
14102
14130
|
|
|
14103
14131
|
// 1) 작업 상태 분포
|
|
@@ -14105,13 +14133,12 @@ function _retroAggregate(root, cutoff) {
|
|
|
14105
14133
|
for (const s of STATUSES) statusCounts[s] = 0;
|
|
14106
14134
|
for (const r of rows) if (statusCounts[r.status] != null) statusCounts[r.status]++;
|
|
14107
14135
|
|
|
14108
|
-
// 2) 결정
|
|
14109
|
-
const decisionBlocks =
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
}).reverse();
|
|
14136
|
+
// 2) 결정 수 — 1.36.39 (#5): canonical decisions.json 기준 (decision list/memory status 와 항상 일치)
|
|
14137
|
+
const decisionBlocks = _decsCanon;
|
|
14138
|
+
const recentDecisions = _decsCanon.slice(-5).map(d => ({
|
|
14139
|
+
title: String(d.title || d.decision || '').trim(),
|
|
14140
|
+
block: [d.decision, d.reason].filter(Boolean).join(' — ').slice(0, 200)
|
|
14141
|
+
})).reverse();
|
|
14115
14142
|
|
|
14116
14143
|
// 3) 스킬 활용
|
|
14117
14144
|
const skillsDir = path.join(root, '.harness/skills');
|
|
@@ -17111,6 +17138,8 @@ function benchmarkCmd(root) {
|
|
|
17111
17138
|
log(`💡 verify-claim/audit 오버헤드는 일반적으로 검수 1회당 200~500ms (실 CLI 호출 대비 1-10%)`);
|
|
17112
17139
|
});
|
|
17113
17140
|
}
|
|
17141
|
+
// 1.36.39 (codex 4차 #7): 미초기화 디렉토리(증거 0)에 497/600 을 주던 것 — 측정 대상이 아니므로 거부.
|
|
17142
|
+
if (!exists(path.join(root, '.harness'))) { failJson(has('--json'), 'harness_missing', `측정 불가: ${root} 에 .harness 없음 — leerness init 후 사용`); return; }
|
|
17114
17143
|
const rows = readProgressRows(root);
|
|
17115
17144
|
const done = rows.filter(r => r.status === 'done').length;
|
|
17116
17145
|
const totalTasks = rows.length;
|
|
@@ -17146,6 +17175,12 @@ function benchmarkCmd(root) {
|
|
|
17146
17175
|
project: detectProjectName(root),
|
|
17147
17176
|
measured: { totalTasks, done, reuseLines, usage: usage.commands, driftLevel: usage.drift },
|
|
17148
17177
|
leernessScore: score, total,
|
|
17178
|
+
// 1.36.39 (#7 정직화): 어떤 차원이 측정 기반이고 어떤 게 설계 상수/정성 추정인지 명시 — 점수를 실측으로 오독 방지.
|
|
17179
|
+
scoreBasis: {
|
|
17180
|
+
measuredDims: ['multiAgent', 'reuse', 'bugDetect'],
|
|
17181
|
+
constantDims: ['autoVerify', 'workspace', 'contextKeep'],
|
|
17182
|
+
note: 'constantDims 는 도구 능력 설계치(이 워크스페이스의 측정값 아님) · compareSimulated 는 정성 추정'
|
|
17183
|
+
},
|
|
17149
17184
|
compareSimulated: vsTools
|
|
17150
17185
|
}, null, 2));
|
|
17151
17186
|
return;
|
|
@@ -17798,7 +17833,31 @@ function mcpServeCmd(root) {
|
|
|
17798
17833
|
} else if (req.method === 'tools/list') {
|
|
17799
17834
|
send({ jsonrpc: '2.0', id, result: { tools: TOOLS } });
|
|
17800
17835
|
} else if (req.method === 'tools/call') {
|
|
17801
|
-
const
|
|
17836
|
+
const name = (req.params || {}).name;
|
|
17837
|
+
// 1.36.39 (codex 4차 #3): arguments 가 null/비객체면 기본 {} 디폴트가 안 먹어 args.path 접근이 throw →
|
|
17838
|
+
// stdin 캐치가 -32700 "Parse error"(id null)로 오보했다. 유효 JSON 의 잘못된 파라미터는 -32602 + 원 id.
|
|
17839
|
+
const _rawArgs = (req.params || {}).arguments;
|
|
17840
|
+
if (_rawArgs != null && (typeof _rawArgs !== 'object' || Array.isArray(_rawArgs))) {
|
|
17841
|
+
return send({ jsonrpc: '2.0', id, error: { code: -32602, message: 'Invalid params: arguments must be an object' } });
|
|
17842
|
+
}
|
|
17843
|
+
const args = _rawArgs || {};
|
|
17844
|
+
// 1.36.39 (#3): 도구 inputSchema 기반 타입 검증 — {"title":{...}} 같은 비문자열이 "[object Object]" 로 영속되던 것 차단.
|
|
17845
|
+
{
|
|
17846
|
+
const _td = TOOLS.find(t => t.name === name);
|
|
17847
|
+
const _props = _td && _td.inputSchema && _td.inputSchema.properties || null;
|
|
17848
|
+
if (_props) {
|
|
17849
|
+
for (const [k, spec] of Object.entries(_props)) {
|
|
17850
|
+
const v = args[k];
|
|
17851
|
+
if (v === undefined || v === null) continue;
|
|
17852
|
+
if (spec.type === 'string' && typeof v !== 'string') return send({ jsonrpc: '2.0', id, error: { code: -32602, message: `Invalid params: '${k}' must be a string` } });
|
|
17853
|
+
if (spec.type === 'number' && typeof v !== 'number') return send({ jsonrpc: '2.0', id, error: { code: -32602, message: `Invalid params: '${k}' must be a number` } });
|
|
17854
|
+
if (spec.type === 'boolean' && typeof v !== 'boolean') return send({ jsonrpc: '2.0', id, error: { code: -32602, message: `Invalid params: '${k}' must be a boolean` } });
|
|
17855
|
+
}
|
|
17856
|
+
for (const rk of (_td.inputSchema.required || [])) {
|
|
17857
|
+
if (args[rk] === undefined || args[rk] === null) return send({ jsonrpc: '2.0', id, error: { code: -32602, message: `Invalid params: missing required '${rk}'` } });
|
|
17858
|
+
}
|
|
17859
|
+
}
|
|
17860
|
+
}
|
|
17802
17861
|
// 1.12.2 (14th 버그헌트, UR-0181): MCP 서버는 generic 설계 — cwd 에서 실행 후 각 호출의 path 로 대상 프로젝트 지정(의도된 기능, e2e 가 검증). 즉 path-타게팅 자체는 취약점 아님(신뢰된 로컬 MCP). policy 도 대상-프로젝트별로 path 에서 로드(올바름).
|
|
17803
17862
|
// 진짜 버그는 unknown tool 도 _bumpMcpUsage 가 임의 경로에 무조건 쓰던 것 → 사용 통계를 unknown-tool 검증 "후"로 이동. (프로젝트-scoped 가둠은 opt-in 플래그로 별도 검토 — UR backlog)
|
|
17804
17863
|
const targetPath = args.path || root;
|
|
@@ -17854,12 +17913,12 @@ function mcpServeCmd(root) {
|
|
|
17854
17913
|
const line = buf.slice(0, nl).trim();
|
|
17855
17914
|
buf = buf.slice(nl + 1);
|
|
17856
17915
|
if (!line) continue;
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
}
|
|
17916
|
+
// 1.36.39 (#3): 파싱과 핸들링 분리 — 핸들러 내부 예외가 -32700 "Parse error"(id null)로 오보되지 않게.
|
|
17917
|
+
let req = null;
|
|
17918
|
+
try { req = JSON.parse(line); }
|
|
17919
|
+
catch (e) { send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error: ' + e.message } }); continue; }
|
|
17920
|
+
try { handleRequest(req); }
|
|
17921
|
+
catch (e) { send({ jsonrpc: '2.0', id: (req && req.id != null) ? req.id : null, error: { code: -32603, message: 'Internal error: ' + e.message } }); }
|
|
17863
17922
|
}
|
|
17864
17923
|
});
|
|
17865
17924
|
process.stdin.on('end', () => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.40",
|
|
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",
|