leerness 1.36.85 → 1.36.86
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 +141 -156
- package/lib/mcp-tools.js +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.86 — 2026-07-29 — brainstorm 두 경로 수집 단일화(중복 149줄 제거) · 총계 계약 정정 · 조용한 절단 제거
|
|
4
|
+
|
|
5
|
+
1.36.85 가 다음 라운드 과제로 남긴 "사람용 vs `--json` 이 갈린다"를 근본 수정했다. 원인은 **세 겹**이었다: ① 사람용이 자체 수집 코드를 갖고 있어 `code` 를 아예 모으지 않았고 ② 렌더러에 code 섹션이 없었으며 ③ 총계를 각자 인라인 계산해 서로 다른 표면을 누락했다.
|
|
6
|
+
|
|
7
|
+
- **수집 단일화** — 사람용도 `_brainstormFor` 를 쓴다(중복 **149줄 제거**, 순 -87줄). 게시본 1.36.85 와 같은 픽스처로 출력을 줄 단위 비교해 **`--include-code` 없이는 byte-identical**, 있을 때만 의도한 code 섹션·총계 증가만 나타남을 확인했다(외부 검수도 동일 결론: 토큰화·2자 필터·preview 길이·코드펜스 제외·순서·cap·경로 표기 모두 무변경).
|
|
8
|
+
- **총계 계약 정정** — `_brainstormTotal` 하나를 두 경로가 공유한다. archive 를 포함하도록 고쳤고(이전엔 JSON 이 hit 을 보여주면서 `total=0` 인 자기모순이 있었다), 반대로 **`lessons` 는 제외**했다 — `evidence` 의 파생 뷰(같은 블록을 실패 키워드로 재투영)라 더하면 **같은 자원을 두 번 센다**(검수 지적, 실측: evidence 1/lessons 1 인데 total 2). `total` 은 **고유 자원 수**를 뜻한다.
|
|
9
|
+
- **workspace 경로에도 같은 결함이 남아 있었다** — 단일 프로젝트만 고치고 `--include` 경로를 놓쳤다. 집계에는 넣고 렌더는 빠뜨려 **"3건" 이라 적고 1건만 표시**했다(lessons·plan·skill이력·task-log·archive 5개 표면 누락, 실측). 전부 렌더하도록 고쳤다. — *증상만 고치고 완료로 판정하는 그 패턴을 이 라운드에서 내가 그대로 저질렀고, 외부 검수가 짚어서야 발견했다.*
|
|
10
|
+
- **조용한 절단 2건 제거** — `--include-code` 의 상한 20 이 **재귀 프레임만** 종료해 형제 디렉토리와 다음 스캔 대상은 계속 훑었다(실측: 30개 하위 디렉토리 → 30건 수집). 전역 상한으로 교체. 사람용은 전체 건수를 헤딩에 적고 5건만 보여주면서 안내가 없었다 → `(20 — 상위 5건 표시)` 로 명시.
|
|
11
|
+
- **가드의 보장 범위를 정확히 적는다**(검수가 반례로 좁혀 준 대로): 신설 가드는 KO 단일 프로젝트의 4개 표면 건수·헤더 합계·workspace 헤더 대비 실제 표시 항목 수를 단언한다. **표면별 렌더 내용의 완전 일치나 EN 로케일·표면당 상한 초과·항목 identity 비교는 보장하지 않는다.** "구조적으로 드리프트 불가능"은 **수집에만** 해당하며 렌더 드리프트는 별도다.
|
|
12
|
+
- **총계를 같은 함수로 비교하던 동어반복도 고쳤다** — 1차판 가드는 `j.total !== _brainstormTotal(h)` 로 검사해 그 함수의 code 항을 빼도 양변이 함께 변해 통과했다. 기대값을 **독립 계산**으로 바꿔 확인.
|
|
13
|
+
- 검증: selftest 340 · e2e 406/406 · 1.36.85 대비 사람용 출력 회귀 비교 · 신규 가드 5방향 변이(사람용 code 섹션 제거 · 제품 총계에서 code 제외 · 자체 수집 회귀 · workspace lessons/plan 렌더 제거).
|
|
14
|
+
|
|
3
15
|
## 1.36.85 — 2026-07-29 — brainstorm 줄번호 오보 수정(10곳) · 실행되지 않던 명령 경로에 가드
|
|
4
16
|
|
|
5
17
|
여러 라운드 연속 테스트 인프라를 손봤으므로 **표면을 제품 정확성으로 옮겨** 검색했다(신뢰도가 낮은 곳을 보는 편이 낫다).
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ MIT
|
|
|
122
122
|
<!-- leerness:project-readme:start -->
|
|
123
123
|
## Leerness Project Harness
|
|
124
124
|
|
|
125
|
-
이 프로젝트는 Leerness v1.36.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.86 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
|
|
|
176
176
|
|
|
177
177
|
### MCP server (외부 AI 통합)
|
|
178
178
|
|
|
179
|
-
Leerness v1.36.
|
|
179
|
+
Leerness v1.36.86는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
|
|
180
180
|
|
|
181
181
|
```jsonc
|
|
182
182
|
// 카테고리별
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.85는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
197
197
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
198
198
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
|
|
199
199
|
|
|
200
|
-
현재 누적: **v1.9.x → 1.36.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.86 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,5 +235,5 @@ leerness release pack --close --auto-main-push
|
|
|
235
235
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
236
236
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
237
237
|
|
|
238
|
-
Last synced by Leerness v1.36.
|
|
238
|
+
Last synced by Leerness v1.36.86: 2026-07-29
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
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.86';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -5113,6 +5113,73 @@ function _selfTestCases() {
|
|
|
5113
5113
|
return lessonsOk && decisionsOk && humanOk;
|
|
5114
5114
|
} catch { return false; } finally { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
5115
5115
|
} },
|
|
5116
|
+
{ name: 'brainstorm 두 경로 등가 (1.36.86): 사람용/--json 이 표면별로 같은 결과 — 수집 단일화 회귀 가드 (행위)', run: () => {
|
|
5117
|
+
// 사고: 사람용이 자체 수집 코드를 갖고 있어 `--include-code` 에서 --json 은 code 1건,
|
|
5118
|
+
// 사람용은 "관련 자원 없음" 을 냈다. 수집을 _brainstormFor 로 단일화(중복 149줄 제거)하고
|
|
5119
|
+
// 총계도 _brainstormTotal 하나로 통일했다(이전엔 lessons/archive/code 를 서로 다르게 누락).
|
|
5120
|
+
// 표면별로 "JSON 이 hit 을 보고하면 사람용도 반드시 보여준다"를 강제한다.
|
|
5121
|
+
const d = _selftestFixture('ko');
|
|
5122
|
+
if (!d) return false;
|
|
5123
|
+
try {
|
|
5124
|
+
const KW = 'eqv' + 'probe';
|
|
5125
|
+
const _run = (a) => cp.spawnSync(process.execPath, [__filename, 'brainstorm', KW, '--path', d, ...a],
|
|
5126
|
+
{ encoding: 'utf8', timeout: 90000, maxBuffer: 16 * 1024 * 1024 });
|
|
5127
|
+
// 여러 표면에 동시에 심는다 — 한 표면만 보면 다른 표면의 불일치를 놓친다
|
|
5128
|
+
mkdirp(path.join(d, 'src'));
|
|
5129
|
+
writeUtf8(path.join(d, 'src', 'a.js'), `// ${KW} 구현\n`);
|
|
5130
|
+
writeUtf8(path.join(d, '.harness', 'lessons.md'), `# L\n\n### 2026-01-01\n- Lesson: ${KW} 교훈\n`);
|
|
5131
|
+
writeUtf8(path.join(d, '.harness', 'plan.md'), `# P\n\n### M-0001. ${KW} 마일스톤\nStatus: planned\nProgress: 0%\n`);
|
|
5132
|
+
writeUtf8(path.join(d, '.harness', 'decisions.md'), `# D\n\n### 2026-01-01 — ${KW} 결정\n- Decision: ${KW}\n`);
|
|
5133
|
+
const rj = _run(['--include-code', '--json']);
|
|
5134
|
+
if (rj.status !== 0) return false;
|
|
5135
|
+
const j = JSON.parse(rj.stdout.slice(rj.stdout.indexOf('{')));
|
|
5136
|
+
const h = j.hits || {};
|
|
5137
|
+
const n = (x) => (x && x.length) || 0;
|
|
5138
|
+
// JSON 이 실제로 여러 표면을 잡았는가(픽스처가 무력하면 이 가드도 공허해진다)
|
|
5139
|
+
if (!(n(h.code) && n(h.lessonsExplicit) && n(h.planMilestones) && n(h.decisions))) return false;
|
|
5140
|
+
// 총계는 **독립 계산**과 비교한다 — `_brainstormTotal(h)` 로 비교하면 그 함수를 망가뜨려도
|
|
5141
|
+
// 양변이 같이 변해 통과하는 동어반복이 된다(1차판이 실제로 그랬다: code 항을 빼도 초록).
|
|
5142
|
+
const _arc = h.archive ? Object.values(h.archive).reduce((s, v) => s + n(v), 0) : 0;
|
|
5143
|
+
const _expect = n(h.decisions) + n(h.skills) + n(h.tasks) + n(h.rules) + n(h.evidence) + n(h.lessons)
|
|
5144
|
+
+ n(h.code) + n(h.skillHistory) + n(h.taskLogFails) + n(h.lessonsExplicit) + n(h.planMilestones) + _arc;
|
|
5145
|
+
if (j.total !== _expect) return false;
|
|
5146
|
+
const ht = (_run(['--include-code']).stdout) || '';
|
|
5147
|
+
if (/관련 자원 없음/.test(ht)) return false; // hit 이 있는데 "없음" 은 자기모순
|
|
5148
|
+
// 표면별 건수가 사람용 헤딩에도 그대로 나타나야
|
|
5149
|
+
const shown = [
|
|
5150
|
+
[n(h.code), /관련 소스 코드 \((\d+)\)/],
|
|
5151
|
+
[n(h.lessonsExplicit), /관련 lessons \((\d+)\)/],
|
|
5152
|
+
[n(h.planMilestones), /관련 plan milestones \((\d+)\)/],
|
|
5153
|
+
[n(h.decisions), /관련 결정 \((\d+)\)/],
|
|
5154
|
+
];
|
|
5155
|
+
// 사람용 헤더의 **내역 합 = 총계** — 총계에만 반영되고 내역에서 빠진 표면이 있으면 사용자가 더했을 때 안 맞는다
|
|
5156
|
+
const _hdr = (ht.match(/총 (\d+)건 발견 \(([^)]*)\)/) || []);
|
|
5157
|
+
const _sum = (_hdr[2] || '').split('·').map(s => Number((s.trim().match(/(\d+)$/) || [])[1] || 0)).reduce((a, b) => a + b, 0);
|
|
5158
|
+
const headerOk = Number(_hdr[1]) === j.total && _sum === j.total;
|
|
5159
|
+
if (!(shown.every(([cnt, re]) => { const m = re.exec(ht); return !!m && Number(m[1]) === cnt; }) && headerOk)) return false;
|
|
5160
|
+
// 1.36.86 (검수 High#1): **workspace 경로**도 같은 계약을 지켜야 한다 —
|
|
5161
|
+
// 집계에는 넣고 렌더는 빠뜨려 "3건" 이라 적고 1건만 보여주던 상태였다(실측).
|
|
5162
|
+
// 프로젝트 헤더의 건수만큼 실제 항목 줄이 나와야 한다.
|
|
5163
|
+
const _wsBase = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_ws_'));
|
|
5164
|
+
try {
|
|
5165
|
+
const _app = path.join(_wsBase, 'app');
|
|
5166
|
+
mkdirp(path.join(_app, '.harness'));
|
|
5167
|
+
writeUtf8(path.join(_app, '.harness', 'HARNESS_VERSION'), VERSION);
|
|
5168
|
+
writeUtf8(path.join(_app, '.harness', 'lessons.md'), `# L\n\n### 2026-01-01\n- Lesson: ${KW} 교훈\n`);
|
|
5169
|
+
writeUtf8(path.join(_app, '.harness', 'plan.md'), `# P\n\n### M-0001. ${KW} 마일스톤\nStatus: planned\nProgress: 0%\n`);
|
|
5170
|
+
writeUtf8(path.join(_app, '.harness', 'decisions.md'), `# D\n\n### 2026-01-01 — ${KW} 결정\n- Decision: ${KW}\n`);
|
|
5171
|
+
const _opt = { cwd: _wsBase, encoding: 'utf8', timeout: 90000, maxBuffer: 16 * 1024 * 1024 };
|
|
5172
|
+
const _wj = cp.spawnSync(process.execPath, [__filename, 'brainstorm', KW, '--include', 'app', '--json'], _opt);
|
|
5173
|
+
const _wh = cp.spawnSync(process.execPath, [__filename, 'brainstorm', KW, '--include', 'app'], _opt);
|
|
5174
|
+
if (_wj.status !== 0 || _wh.status !== 0) return false;
|
|
5175
|
+
const _wjj = JSON.parse(_wj.stdout.slice(_wj.stdout.indexOf('{')));
|
|
5176
|
+
const _wt = (_wh.stdout || '');
|
|
5177
|
+
const _hdrN = Number((_wt.match(/^## app \((\d+)건\)/m) || [])[1] || -1);
|
|
5178
|
+
const _items = (_wt.match(/^ {4}- /gm) || []).length; // 실제 렌더된 항목 줄
|
|
5179
|
+
return _hdrN === _wjj.total && _items >= _wjj.total; // 헤더 건수 = 총계 = 실제 표시 항목
|
|
5180
|
+
} catch { return false; } finally { try { fs.rmSync(_wsBase, { recursive: true, force: true }); } catch {} }
|
|
5181
|
+
} catch { return false; } finally { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
5182
|
+
} },
|
|
5116
5183
|
{ name: '자기소스 검사 동결 (1.36.84): selftest 가 자기 소스를 읽어 검증하는 케이스는 더 늘지 않는다 — 신규는 행위검사로 (메타가드)', run: () => {
|
|
5117
5184
|
// 왜 "탐지"가 아니라 "동결"인가:
|
|
5118
5185
|
// 1.36.82~83 에서 자기참조 공허 가드를 탐지하려 세 라운드 연속 탐지기를 고쳤는데,
|
|
@@ -16463,12 +16530,18 @@ function _brainstormFor(root, topic) {
|
|
|
16463
16530
|
// 1.9.25: --include-code 옵션 — 소스 본문 검색 추가 (모순 감지 핵심)
|
|
16464
16531
|
if (has('--include-code')) {
|
|
16465
16532
|
const codeDirs = ['src', 'tests', 'bin', 'lib'];
|
|
16533
|
+
// 1.36.86 (검수 M3): 상한 20 이 **재귀 프레임만** 종료해 형제 디렉토리와 다음 codeDir 은 계속 훑었다
|
|
16534
|
+
// (실측: 30개 하위 디렉토리 → 30건 수집). 진입·복귀·바깥 루프 모두에서 전역 상한을 본다.
|
|
16535
|
+
const CODE_CAP = 20;
|
|
16466
16536
|
for (const dir of codeDirs) {
|
|
16537
|
+
if (hits.code.length >= CODE_CAP) break;
|
|
16467
16538
|
const dp = path.join(root, dir);
|
|
16468
16539
|
if (!exists(dp)) continue;
|
|
16469
16540
|
function walkCode(d) {
|
|
16541
|
+
if (hits.code.length >= CODE_CAP) return;
|
|
16470
16542
|
let entries; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
16471
16543
|
for (const e of entries) {
|
|
16544
|
+
if (hits.code.length >= CODE_CAP) return;
|
|
16472
16545
|
const p = path.join(d, e.name);
|
|
16473
16546
|
if (e.isDirectory()) { walkCode(p); continue; }
|
|
16474
16547
|
if (!/\.(js|ts|jsx|tsx|gd|cs|py|rb|go|rs)$/i.test(e.name)) continue;
|
|
@@ -16481,7 +16554,7 @@ function _brainstormFor(root, topic) {
|
|
|
16481
16554
|
line: firstHit >= 0 ? firstHit + 1 : 0,
|
|
16482
16555
|
preview: firstHit >= 0 ? lines[firstHit].trim().slice(0, 120) : ''
|
|
16483
16556
|
});
|
|
16484
|
-
if (hits.code.length >=
|
|
16557
|
+
if (hits.code.length >= CODE_CAP) return; // 전역 상한 — 위 진입/복귀 검사와 함께 동작
|
|
16485
16558
|
}
|
|
16486
16559
|
}
|
|
16487
16560
|
}
|
|
@@ -16491,7 +16564,17 @@ function _brainstormFor(root, topic) {
|
|
|
16491
16564
|
return hits;
|
|
16492
16565
|
}
|
|
16493
16566
|
|
|
16494
|
-
|
|
16567
|
+
// 1.36.86: 모든 표면을 센다 — 이전엔 `lessons` 와 `archive` 가 빠져 있어 **JSON 이 hit 을 보여주면서 total=0** 을
|
|
16568
|
+
// 보고했다(자기모순). 사람용도 자체 인라인 합계를 쓰다 `code` 까지 빠뜨렸다 → 두 경로가 이 한 함수를 공유한다.
|
|
16569
|
+
function _brainstormTotal(h) {
|
|
16570
|
+
if (!h) return 0;
|
|
16571
|
+
const n = (x) => (x && x.length) || 0;
|
|
16572
|
+
const arc = h.archive ? Object.values(h.archive).reduce((s, v) => s + n(v), 0) : 0;
|
|
16573
|
+
// `lessons` 는 **evidence 의 파생 뷰**다 — 같은 블록을 실패 키워드로 한 번 더 투영한 것이라
|
|
16574
|
+
// 더하면 같은 자원을 두 번 센다(검수 확인: evidence=1/lessons=1 인데 total 이 2). 고유 자원 수를 뜻하도록 제외한다.
|
|
16575
|
+
return n(h.decisions) + n(h.skills) + n(h.tasks) + n(h.rules) + n(h.evidence)
|
|
16576
|
+
+ n(h.code) + n(h.skillHistory) + n(h.taskLogFails) + n(h.lessonsExplicit) + n(h.planMilestones) + arc;
|
|
16577
|
+
}
|
|
16495
16578
|
|
|
16496
16579
|
// 1.9.16: 워크스페이스 통합 brainstorm
|
|
16497
16580
|
function _brainstormWorkspace(rootBase, topic) {
|
|
@@ -16535,8 +16618,35 @@ function _brainstormWorkspace(rootBase, topic) {
|
|
|
16535
16618
|
}
|
|
16536
16619
|
// 1.9.25: 소스 코드 본문 hits
|
|
16537
16620
|
if (h.code && h.code.length) {
|
|
16538
|
-
|
|
16539
|
-
|
|
16621
|
+
const _s = Math.min(5, h.code.length);
|
|
16622
|
+
log(` 💻 코드 (${h.code.length}${h.code.length > _s ? ` — 상위 ${_s}건` : ''})`);
|
|
16623
|
+
h.code.slice(0, _s).forEach(c => log(` - ${c.file}:${c.line} — ${c.preview}`));
|
|
16624
|
+
}
|
|
16625
|
+
// 1.36.86 (검수 High#1): 아래 5개 표면은 **집계에는 들어가는데 렌더되지 않아**
|
|
16626
|
+
// "3건" 이라 적고 1건만 보여주는 상태였다(실측). 단일 프로젝트 경로와 같은 표면을 모두 보여준다.
|
|
16627
|
+
if (h.skillHistory && h.skillHistory.length) {
|
|
16628
|
+
log(` 📒 skill match 이력 (${h.skillHistory.length})`);
|
|
16629
|
+
h.skillHistory.slice(0, 3).forEach(x => log(` - skill-suggestions.md:${x.line || '?'} — [${x.at}] "${x.query}"`));
|
|
16630
|
+
}
|
|
16631
|
+
if (h.taskLogFails && h.taskLogFails.length) {
|
|
16632
|
+
log(` 📜 task-log 실패 (${h.taskLogFails.length})`);
|
|
16633
|
+
h.taskLogFails.slice(0, 3).forEach(x => log(` - task-log.md:${x.line || '?'} — ${x.title}`));
|
|
16634
|
+
}
|
|
16635
|
+
if (h.lessonsExplicit && h.lessonsExplicit.length) {
|
|
16636
|
+
log(` 💡 lessons (${h.lessonsExplicit.length})`);
|
|
16637
|
+
h.lessonsExplicit.slice(0, 3).forEach(x => log(` - lessons.md:${x.line || '?'} — ${x.title}`));
|
|
16638
|
+
}
|
|
16639
|
+
if (h.planMilestones && h.planMilestones.length) {
|
|
16640
|
+
log(` 🗺 plan milestones (${h.planMilestones.length})`);
|
|
16641
|
+
h.planMilestones.slice(0, 3).forEach(x => log(` - plan.md:${x.line || '?'} — ${x.id} ${x.title}`));
|
|
16642
|
+
}
|
|
16643
|
+
if (h.archive) {
|
|
16644
|
+
const _arc = Object.entries(h.archive).filter(([, v]) => v && v.length);
|
|
16645
|
+
const _arcN = _arc.reduce((s, [, v]) => s + v.length, 0);
|
|
16646
|
+
if (_arcN) {
|
|
16647
|
+
log(` 🗑 archive 후보 (${_arcN})`);
|
|
16648
|
+
_arc.forEach(([k, v]) => v.slice(0, 2).forEach(x => log(` - ${k}-archive.md:${x.line || '?'} — ${x.originalHeader || x.target || ''}`)));
|
|
16649
|
+
}
|
|
16540
16650
|
}
|
|
16541
16651
|
}
|
|
16542
16652
|
log(`\n## 📊 워크스페이스 총합: ${grandTotal}건 매치 (${paths.length} 프로젝트)${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
|
|
@@ -16559,157 +16669,24 @@ function brainstormCmd(root, topic) {
|
|
|
16559
16669
|
log(`# Brainstorm — "${topic}"`);
|
|
16560
16670
|
log(`\n누적된 leerness 데이터에서 주제 관련 자원을 회수합니다.`);
|
|
16561
16671
|
|
|
16562
|
-
// 1.
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
const
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
const
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
// skills
|
|
16581
|
-
const skillsDir = path.join(root, '.harness/skills');
|
|
16582
|
-
if (exists(skillsDir)) {
|
|
16583
|
-
for (const id of fs.readdirSync(skillsDir)) {
|
|
16584
|
-
const f = path.join(skillsDir, id, 'skill.json');
|
|
16585
|
-
if (!exists(f)) continue;
|
|
16586
|
-
try {
|
|
16587
|
-
const s = JSON.parse(read(f));
|
|
16588
|
-
const text = JSON.stringify(s);
|
|
16589
|
-
if (matches(text)) hits.skills.push({ id, displayNameKo: s.displayNameKo, capabilities: s.capabilities, usage: s.usage });
|
|
16590
|
-
} catch {}
|
|
16591
|
-
}
|
|
16592
|
-
}
|
|
16593
|
-
// tasks (1.9.14: token 매칭, 1.9.15: 매치 필드 + 라인 번호)
|
|
16594
|
-
const rows = readProgressRows(root);
|
|
16595
|
-
const progressText = exists(progressPath(root)) ? read(progressPath(root)) : '';
|
|
16596
|
-
for (const r of rows) {
|
|
16597
|
-
const fields = [];
|
|
16598
|
-
if (matches(r.request)) fields.push('request');
|
|
16599
|
-
if (matches(r.evidence)) fields.push('evidence');
|
|
16600
|
-
if (matches(r.nextAction)) fields.push('nextAction');
|
|
16601
|
-
if (fields.length) {
|
|
16602
|
-
const idx = progressText.indexOf(`| ${r.id} |`);
|
|
16603
|
-
const lineNo = idx >= 0 ? progressText.slice(0, idx).split('\n').length : 0;
|
|
16604
|
-
hits.tasks.push({ ...r, _fields: fields, line: lineNo });
|
|
16605
|
-
}
|
|
16606
|
-
}
|
|
16607
|
-
// rules (1.9.15: 라인 번호)
|
|
16608
|
-
if (exists(rulesPath(root))) {
|
|
16609
|
-
const rulesText = read(rulesPath(root));
|
|
16610
|
-
for (const r of readRules(root)) {
|
|
16611
|
-
if (matches(r.rule)) {
|
|
16612
|
-
const idx = rulesText.indexOf(`| ${r.id} |`);
|
|
16613
|
-
const lineNo = idx >= 0 ? rulesText.slice(0, idx).split('\n').length : 0;
|
|
16614
|
-
hits.rules.push({ ...r, line: lineNo });
|
|
16615
|
-
}
|
|
16616
|
-
}
|
|
16617
|
-
}
|
|
16618
|
-
// evidence — lessons 키워드 (fail/롤백/incomplete) 동반 (1.9.15: 라인 번호)
|
|
16619
|
-
const ev = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
|
|
16620
|
-
for (const { block: block, line: _ln } of _blocksWithOffset(ev, /\n(?=## )/)) {
|
|
16621
|
-
if (!block.startsWith('## ')) continue;
|
|
16622
|
-
if (matches(block)) {
|
|
16623
|
-
const t = (block.match(/^## (.+)$/m) || [, ''])[1];
|
|
16624
|
-
const lineNo = _ln;
|
|
16625
|
-
hits.evidence.push({ title: t.trim(), preview: block.slice(0, 200).replace(/\n+/g, ' '), line: lineNo });
|
|
16626
|
-
if (/✗|fail|롤백|incomplete|버그/i.test(block)) hits.lessons.push({ title: t.trim(), line: lineNo });
|
|
16627
|
-
}
|
|
16628
|
-
}
|
|
16629
|
-
// 1.9.72: skill-suggestions.md rolling history hits (이전 매칭 결과 회수)
|
|
16630
|
-
const histPath = path.join(root, '.harness', 'skill-suggestions.md');
|
|
16631
|
-
if (exists(histPath)) {
|
|
16632
|
-
const histTxt = read(histPath);
|
|
16633
|
-
let pos = 0;
|
|
16634
|
-
for (const { block: block, line: _ln } of _blocksWithOffset(histTxt, /\n(?=## )/)) {
|
|
16635
|
-
if (!block.startsWith('## ')) { pos += block.length + 1; continue; }
|
|
16636
|
-
const h = block.match(/^## ([\d-]+ [\d:]+) — query "([^"]+)"/);
|
|
16637
|
-
if (h && matches(block)) {
|
|
16638
|
-
const lineNo = _ln;
|
|
16639
|
-
hits.skillHistory.push({ at: h[1], query: h[2], preview: block.slice(0, 220).replace(/\n+/g, ' '), line: lineNo });
|
|
16640
|
-
}
|
|
16641
|
-
}
|
|
16642
|
-
}
|
|
16643
|
-
// 1.9.72: task-log.md 실패 라인 hits
|
|
16644
|
-
const tlogPath = path.join(root, '.harness', 'task-log.md');
|
|
16645
|
-
if (exists(tlogPath)) {
|
|
16646
|
-
const tlog = read(tlogPath);
|
|
16647
|
-
const lines = tlog.split('\n');
|
|
16648
|
-
for (let i = 0; i < lines.length; i++) {
|
|
16649
|
-
const line = lines[i];
|
|
16650
|
-
if (line.length > 4 && /✗|\bfail|롤백|재발|incomplete|버그/i.test(line) && matches(line)) {
|
|
16651
|
-
hits.taskLogFails.push({ title: line.replace(/^[-*]\s*/, '').slice(0, 100), line: i + 1 });
|
|
16652
|
-
}
|
|
16653
|
-
}
|
|
16654
|
-
}
|
|
16655
|
-
// 1.9.131: 3 archive 파일 hits (brainstormCmd 변종) — DELETE 5종 archive 도 brainstorm 후보로
|
|
16656
|
-
if (!hits.archive) hits.archive = { decisions: [], lessons: [], plan: [] };
|
|
16657
|
-
const archiveSources_bsCmd = [
|
|
16658
|
-
{ key: 'decisions', file: 'decisions.archive.md' },
|
|
16659
|
-
{ key: 'lessons', file: 'lessons.archive.md' },
|
|
16660
|
-
{ key: 'plan', file: 'plan.archive.md' }
|
|
16661
|
-
];
|
|
16662
|
-
for (const src of archiveSources_bsCmd) {
|
|
16663
|
-
const fp = path.join(root, '.harness', src.file);
|
|
16664
|
-
if (!exists(fp)) continue;
|
|
16665
|
-
const txt = read(fp);
|
|
16666
|
-
const blocks = _blocksWithOffset(txt, /\n(?=## 제거 )/);
|
|
16667
|
-
for (const { block: b, line: _ln } of blocks) {
|
|
16668
|
-
const m = b.match(/^## 제거 (\d{4}-\d{2}-\d{2})\s*\(target:\s*"([^"]*)"\)/);
|
|
16669
|
-
if (!m) continue;
|
|
16670
|
-
if (matches(b)) {
|
|
16671
|
-
const headerMatch = b.match(/^### (.+)$/m);
|
|
16672
|
-
const lineNo = _ln;
|
|
16673
|
-
hits.archive[src.key].push({
|
|
16674
|
-
date: m[1],
|
|
16675
|
-
target: m[2],
|
|
16676
|
-
originalHeader: headerMatch ? headerMatch[1].trim() : null,
|
|
16677
|
-
preview: b.slice(0, 220).replace(/\n+/g, ' '),
|
|
16678
|
-
line: lineNo
|
|
16679
|
-
});
|
|
16680
|
-
}
|
|
16681
|
-
}
|
|
16682
|
-
}
|
|
16683
|
-
// 1.9.116: lessons.md + plan.md milestone hits (Memory Surface 5종 완전 통합)
|
|
16684
|
-
if (!hits.lessonsExplicit) hits.lessonsExplicit = [];
|
|
16685
|
-
if (!hits.planMilestones) hits.planMilestones = [];
|
|
16686
|
-
const lp_b2 = lessonsPath(root);
|
|
16687
|
-
if (exists(lp_b2)) {
|
|
16688
|
-
const lessonsText = read(lp_b2);
|
|
16689
|
-
for (const { block: block, line: _ln } of _blocksWithOffset(lessonsText, /\n(?=### )/)) {
|
|
16690
|
-
if (!block.startsWith('### ')) continue;
|
|
16691
|
-
const lessonMatch = block.match(/- Lesson:[ \t]*(.+)/);
|
|
16692
|
-
if (lessonMatch && matches(block)) {
|
|
16693
|
-
const lineNo = _ln;
|
|
16694
|
-
hits.lessonsExplicit.push({ title: lessonMatch[1].trim().slice(0, 120), preview: block.slice(0, 220).replace(/\n+/g, ' '), line: lineNo });
|
|
16695
|
-
}
|
|
16696
|
-
}
|
|
16697
|
-
}
|
|
16698
|
-
const planFile_b2 = planPath(root);
|
|
16699
|
-
if (exists(planFile_b2)) {
|
|
16700
|
-
const planText = read(planFile_b2);
|
|
16701
|
-
const milestoneBlocks = _blocksWithOffset(planText, /\n(?=### M-\d{4,}\.)/);
|
|
16702
|
-
for (const { block: b, line: _ln } of milestoneBlocks) {
|
|
16703
|
-
const m = b.match(/^### (M-\d{4,})\.[ \t]*(.+?)$/m);
|
|
16704
|
-
if (m && matches(b)) {
|
|
16705
|
-
const lineNo = _ln;
|
|
16706
|
-
hits.planMilestones.push({ id: m[1], title: m[2].trim().slice(0, 120), preview: b.slice(0, 220).replace(/\n+/g, ' '), line: lineNo });
|
|
16707
|
-
}
|
|
16708
|
-
}
|
|
16709
|
-
}
|
|
16710
|
-
|
|
16711
|
-
const total = hits.decisions.length + hits.skills.length + hits.tasks.length + hits.rules.length + hits.evidence.length + (hits.skillHistory ? hits.skillHistory.length : 0) + (hits.taskLogFails ? hits.taskLogFails.length : 0) + (hits.lessonsExplicit ? hits.lessonsExplicit.length : 0) + (hits.planMilestones ? hits.planMilestones.length : 0);
|
|
16712
|
-
log(`\n📦 총 ${total}건 발견 (decisions ${hits.decisions.length} · skills ${hits.skills.length} · tasks ${hits.tasks.length} · rules ${hits.rules.length} · evidence ${hits.evidence.length}${hits.skillHistory && hits.skillHistory.length ? ` · skill-history ${hits.skillHistory.length}` : ''}${hits.taskLogFails && hits.taskLogFails.length ? ` · task-log-fails ${hits.taskLogFails.length}` : ''}${hits.lessonsExplicit && hits.lessonsExplicit.length ? ` · lessons ${hits.lessonsExplicit.length}` : ''}${hits.planMilestones && hits.planMilestones.length ? ` · plan ${hits.planMilestones.length}` : ''})`);
|
|
16672
|
+
// 1.36.86: 사람용도 **같은 수집기**(_brainstormFor)를 쓴다 — 이전엔 사람용이 자체 수집 코드를 갖고 있어
|
|
16673
|
+
// `--include-code` 에서 --json 은 code 1건, 사람용은 "없음" 을 내는 등 두 경로가 실제로 갈렸다(실측).
|
|
16674
|
+
// 수집은 하나, 렌더만 둘 — 구조적으로 드리프트가 불가능해진다(중복 ~145줄 제거).
|
|
16675
|
+
const hits = _brainstormFor(root, topic);
|
|
16676
|
+
// 1.36.86: 사람용 인라인 합계는 code(그리고 lessons/archive)를 빠뜨려 --json 과 달랐다 → 공유 함수 사용.
|
|
16677
|
+
const total = _brainstormTotal(hits);
|
|
16678
|
+
// 1.36.86: 내역에 code/lessons(과거 실패)/archive 를 포함한다 — 총계에는 들어가는데 내역에 없어
|
|
16679
|
+
// 사용자가 내역을 더하면 총계와 맞지 않았다(총 7건인데 내역 합 6).
|
|
16680
|
+
const _arcN = hits.archive ? Object.values(hits.archive).reduce((s, v) => s + ((v && v.length) || 0), 0) : 0;
|
|
16681
|
+
const _part = (label, n) => (n ? ` · ${label} ${n}` : '');
|
|
16682
|
+
log(`\n📦 총 ${total}건 발견 (decisions ${hits.decisions.length} · skills ${hits.skills.length} · tasks ${hits.tasks.length} · rules ${hits.rules.length} · evidence ${hits.evidence.length}`
|
|
16683
|
+
+ _part('code', hits.code && hits.code.length)
|
|
16684
|
+
+ _part('past-fails', hits.lessons && hits.lessons.length)
|
|
16685
|
+
+ _part('skill-history', hits.skillHistory && hits.skillHistory.length)
|
|
16686
|
+
+ _part('task-log-fails', hits.taskLogFails && hits.taskLogFails.length)
|
|
16687
|
+
+ _part('lessons', hits.lessonsExplicit && hits.lessonsExplicit.length)
|
|
16688
|
+
+ _part('plan', hits.planMilestones && hits.planMilestones.length)
|
|
16689
|
+
+ _part('archive', _arcN) + ')');
|
|
16713
16690
|
|
|
16714
16691
|
// 1.9.15: 모든 출력에 출처 파일:라인 표시
|
|
16715
16692
|
if (hits.decisions.length) {
|
|
@@ -16736,6 +16713,14 @@ function brainstormCmd(root, topic) {
|
|
|
16736
16713
|
log(`\n## ⚠ 같은 주제 과거 실패/롤백 (${hits.lessons.length}) — 같은 실수 방지`);
|
|
16737
16714
|
hits.lessons.slice(0, 5).forEach(l => log(` - .harness/review-evidence.md:${l.line || '?'} — ${l.title}`));
|
|
16738
16715
|
}
|
|
16716
|
+
// 1.36.86: `--include-code` 결과를 사람용에도 렌더한다 — 이전엔 --json 만 code 를 보여주고
|
|
16717
|
+
// 사람용은 같은 입력에 "관련 자원 없음" 을 출력했다(실측 확인된 두 경로 불일치).
|
|
16718
|
+
if (hits.code && hits.code.length) {
|
|
16719
|
+
// 1.36.86 (검수): 헤딩은 전체 건수인데 5건만 보여주면서 안내가 없어 **조용한 절단**이었다 — 표시 수를 명시한다.
|
|
16720
|
+
const _shown = Math.min(5, hits.code.length);
|
|
16721
|
+
log(`\n## 💻 관련 소스 코드 (${hits.code.length}${hits.code.length > _shown ? ` — 상위 ${_shown}건 표시` : ''}) — --include-code 매칭`);
|
|
16722
|
+
hits.code.slice(0, _shown).forEach(c => log(` - ${c.file}${c.line ? ':' + c.line : ''}${c.preview ? ' — ' + String(c.preview).slice(0, 100) : ''}`));
|
|
16723
|
+
}
|
|
16739
16724
|
// 1.9.72: skill-suggestions.md rolling history hits
|
|
16740
16725
|
if (hits.skillHistory.length) {
|
|
16741
16726
|
log(`\n## 📒 같은 주제 이전 skill match 이력 (${hits.skillHistory.length}) — 1.9.68 누적`);
|
package/lib/mcp-tools.js
CHANGED
|
@@ -19,7 +19,7 @@ module.exports = [
|
|
|
19
19
|
{ name: 'leerness_lessons', requiredTier: 'read-only', description: '1.9.7/54 — 과거 결정·실수 자동 회수 (--auto: 현재 task 키워드 자동 추출)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, query: { type: 'string' }, auto: { type: 'boolean' }, limit: { type: 'number' } } } },
|
|
20
20
|
{ name: 'leerness_task_export', requiredTier: 'project-write', description: '1.9.60/66 — leerness task → Claude Code TodoWrite 호환 JSON (외부 AI 양방향 sync)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, to: { type: 'string' } } } },
|
|
21
21
|
{ name: 'leerness_env_check', requiredTier: 'read-only', description: '1.9.71/73 — .env vs .env.example 동기화 검사 (보안: 키만, 값 미노출). exit 1 if 누락 키 있음', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
22
|
-
{ name: 'leerness_brainstorm', requiredTier: 'read-only', description: '1.9.16/72/77 — 누적 컨텍스트(decisions
|
|
22
|
+
{ name: 'leerness_brainstorm', requiredTier: 'read-only', description: '1.9.16/72/77 · 1.36.86 — 누적 컨텍스트 자원 회수(decisions·skills·tasks·rules·evidence·code(--include-code)·skillHistory·taskLogFails·lessonsExplicit·planMilestones·archive). `total` 은 **고유 자원 수**로, archive 를 포함하고 `lessons`(evidence 의 실패-키워드 파생 뷰)는 중복이라 제외한다. 외부 AI가 새 작업 시작 전 호출', inputSchema: { type: 'object', properties: { topic: { type: 'string' }, path: { type: 'string' }, allApps: { type: 'boolean' } }, required: ['topic'] } },
|
|
23
23
|
{ name: 'leerness_skill_match', requiredTier: 'safe-write', description: '1.9.45/50/83 — 사용자 task 키워드에 매칭되는 설치된 skill 추천 (jaccard 또는 embedding). 1.9.68 rolling history 자동 누적', inputSchema: { type: 'object', properties: { query: { type: 'string' }, path: { type: 'string' }, useEmbedding: { type: 'boolean' } }, required: ['query'] } },
|
|
24
24
|
{ name: 'leerness_skill_list', requiredTier: 'read-only', description: '1.9.84 — 워크스페이스에 설치된 skill 목록 + 사용 횟수 + 출처 (catalog/user). 외부 AI가 사용 가능한 skill 조회', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
25
25
|
{ name: 'leerness_health', requiredTier: 'read-only', description: '1.9.85/86 — 종합 헬스 체크 (drift + 보안 + skills + MCP + tasks + issues 배열). 외부 AI가 워크스페이스 상태 한 번에 확인', inputSchema: { type: 'object', properties: { path: { type: 'string' }, strict: { type: 'boolean' } } } },
|
|
@@ -92,9 +92,9 @@ module.exports = [
|
|
|
92
92
|
{ name: 'leerness_honesty_check', requiredTier: 'read-only', description: '1.9.305 (사용자 명시) — AI 인식론적 정직성 점검. 주장/evidence 텍스트가 (1) 모르는 걸 아는 척(근거 없는 단정), (2) 검증 없는 섣부른 판단(추정+완료 결론), (3) 외부 정보 미수집(API/버전/스펙 언급+수집흔적 없음) 중 무엇에 해당하는지 휴리스틱 탐지. read-only. 에이전트가 자기 주장을 단언 전 self-check. 인자: { path?, taskId?, text? }. 응답: { ok, findings[], dimensions[], highCount }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, taskId: { type: 'string' }, text: { type: 'string' } } } },
|
|
93
93
|
{ name: 'leerness_brief', requiredTier: 'read-only', description: '1.9.308 (UR-0055) — 프로젝트 청사진 회수. 프로젝트 개요/소개/목적/기능/스택/아키텍처/성공기준/방향이력을 구조화 반환(show) 또는 복사용 blueprint 텍스트(export=true). read-only. 외부 에이전트가 "이 프로젝트가 무엇이고 어디로 가는가"를 1콜로 파악하거나, 신규 프로젝트 계획으로 복사. 인자: { path?, export? }. 응답(show): { project, intro, purpose, features[], stack[], directionHistory[], ... }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, export: { type: 'boolean' } } } },
|
|
94
94
|
{ name: 'leerness_clarify', requiredTier: 'read-only', description: '1.36.77 (UR-0061) — 사용자 요청 텍스트의 판단-모호 신호 감지 → 사용자에게 그대로 물을 질문 목록 JSON ({ ambiguous, signals[], questions[] }). 추측 구현 방지 — 모호하면 질문 먼저.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
95
|
-
{ name: 'leerness_preview', requiredTier: 'safe-write', description: '1.36.77 (UR-0061/0066) — 신규 기능/디자인 미리보기 승인 워크플로. action: add(title/design/features/mockup)·list·show·approve·revise(note 필수)·mockup(HTML 시안 스캐폴드 생성, force 로 재생성). 계약: approve 전 해당 기능 코드 작성 금지. list/show 는 읽기지만 단일 도구라 safe-write 로 보수 선언.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'approve', 'revise', 'mockup'] }, path: { type: 'string' }, title: { type: 'string' }, design: { type: 'string' }, features: { type: 'string' }, mockup: { type: 'string' }, id: { type: 'string' }, note: { type: 'string' }, force: { type: 'boolean' } }, required: ['action'] } },
|
|
96
|
-
{ name: 'leerness_referee', requiredTier: 'safe-write', description: '1.36.80 (P-0001) — 검증기 캘리브레이션: 검증기를 신뢰하기 전에 탐지력을 실행으로 증명(known-good 통과 + 일부러 망가뜨린 known-bad 를 기대 사유로 거부). action: add(id/check/good/bad/expectBad)·list·show·verify·drop. 명령/기대치가 바뀌면 캘리브레이션이 stale 이 되어 신뢰가 철회된다. verify-claim 의 refereeId 로 게이팅.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'verify', 'drop'] }, path: { type: 'string' }, id: { type: 'string' }, check: { type: 'string' }, good: { type: 'string' }, bad: { type: 'string' }, expectBad: { type: 'string' } }, required: ['action'] } },
|
|
97
|
-
];
|
|
95
|
+
{ name: 'leerness_preview', requiredTier: 'safe-write', description: '1.36.77 (UR-0061/0066) — 신규 기능/디자인 미리보기 승인 워크플로. action: add(title/design/features/mockup)·list·show·approve·revise(note 필수)·mockup(HTML 시안 스캐폴드 생성, force 로 재생성). 계약: approve 전 해당 기능 코드 작성 금지. list/show 는 읽기지만 단일 도구라 safe-write 로 보수 선언.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'approve', 'revise', 'mockup'] }, path: { type: 'string' }, title: { type: 'string' }, design: { type: 'string' }, features: { type: 'string' }, mockup: { type: 'string' }, id: { type: 'string' }, note: { type: 'string' }, force: { type: 'boolean' } }, required: ['action'] } },
|
|
96
|
+
{ name: 'leerness_referee', requiredTier: 'safe-write', description: '1.36.80 (P-0001) — 검증기 캘리브레이션: 검증기를 신뢰하기 전에 탐지력을 실행으로 증명(known-good 통과 + 일부러 망가뜨린 known-bad 를 기대 사유로 거부). action: add(id/check/good/bad/expectBad)·list·show·verify·drop. 명령/기대치가 바뀌면 캘리브레이션이 stale 이 되어 신뢰가 철회된다. verify-claim 의 refereeId 로 게이팅.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'verify', 'drop'] }, path: { type: 'string' }, id: { type: 'string' }, check: { type: 'string' }, good: { type: 'string' }, bad: { type: 'string' }, expectBad: { type: 'string' } }, required: ['action'] } },
|
|
97
|
+
];
|
|
98
98
|
|
|
99
99
|
// 1.36.58 (외부 GPT 감사 F-09): core 프로필 — 세션 시작 토큰/도구 선택 비용을 줄이는 핵심 20종.
|
|
100
100
|
// `leerness mcp serve --profile core` (또는 LEERNESS_MCP_PROFILE=core) 에서 tools/list·tools/call 이 이 집합만 노출.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.86",
|
|
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",
|