leerness 1.9.138 → 1.9.139
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 +30 -0
- package/README.md +2 -2
- package/bin/harness.js +42 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.139 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**`leerness lesson list --query <keyword>` + `leerness decision list --query <keyword>` 필터 추가** — Memory Surface READ 명령 키워드 검색.
|
|
6
|
+
|
|
7
|
+
### Added — --query 필터
|
|
8
|
+
- `lesson list --query` — text/tag case-insensitive 매칭
|
|
9
|
+
- `decision list --query` — title/decision/reason/alternatives/impact 매칭
|
|
10
|
+
- 정규식 특수문자 자동 escape
|
|
11
|
+
- JSON 응답 `query` 필드, 텍스트 모드 헤더 표시
|
|
12
|
+
|
|
13
|
+
### Updated MCP
|
|
14
|
+
- `leerness_lesson_list` 인자에 `query` 추가
|
|
15
|
+
- `leerness_decision_list` 인자에 `query` 추가
|
|
16
|
+
|
|
17
|
+
### 사용 시나리오
|
|
18
|
+
```bash
|
|
19
|
+
leerness decision list --query PostgreSQL --json
|
|
20
|
+
leerness lesson list --query auth --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### --query 필터 매트릭스 (1.9.139)
|
|
24
|
+
| 명령 | --query 도입 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `memory archive list` | 1.9.138 |
|
|
27
|
+
| `lesson list` | **1.9.139** |
|
|
28
|
+
| `decision list` | **1.9.139** |
|
|
29
|
+
| `task list` | (--status 필터만) |
|
|
30
|
+
| `plan list` | (필터 없음, 미래 후보) |
|
|
31
|
+
| `rule list` | (필터 없음, 미래 후보) |
|
|
32
|
+
|
|
3
33
|
## 1.9.138 — 2026-05-20
|
|
4
34
|
|
|
5
35
|
**`leerness memory archive list --query <keyword>` 필터 추가** — archive 항목 키워드 검색.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.139 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
package/bin/harness.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const cp = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const VERSION = '1.9.
|
|
9
|
+
const VERSION = '1.9.139';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -364,6 +364,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
364
364
|
- 1.9.136+ MCP \`leerness_drift_check\` JSON 응답 fix — \`--json\` 플래그 자동 추가하여 외부 AI가 구조화된 drift 신호 회수 (score, level, signals[], healthy).
|
|
365
365
|
- 1.9.137+ \`.harness/session-workflow.md\` 템플릿에 **🧠 Memory CRUD Quick Reference** 섹션 추가 — 5 surface × CRUD 매트릭스 + archive cycle 워크플로 가이드. 신규 \`init\` 워크스페이스 즉시 적용.
|
|
366
366
|
- 1.9.138+ \`leerness memory archive list --query <keyword>\` + MCP \`leerness_memory_archive_list\` query 인자 — archive 항목 키워드 case-insensitive 검색 (target/originalHeader 매칭).
|
|
367
|
+
- 1.9.139+ \`leerness lesson list --query\` + \`leerness decision list --query\` + MCP 동일 인자 — active Memory 항목 키워드 검색 (lesson: text/tag, decision: title/decision/reason/alternatives/impact).
|
|
367
368
|
|
|
368
369
|
---
|
|
369
370
|
|
|
@@ -1677,10 +1678,15 @@ function lessonListCmd(root, opts = {}) {
|
|
|
1677
1678
|
root = absRoot(root);
|
|
1678
1679
|
const jsonMode = !!opts.json || has('--json');
|
|
1679
1680
|
const tagFilter = arg('--tag', null);
|
|
1681
|
+
// 1.9.139: --query 필터 (lesson text case-insensitive 매칭)
|
|
1682
|
+
const queryFilter = arg('--query', null);
|
|
1683
|
+
const queryRe = queryFilter ? new RegExp(queryFilter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') : null;
|
|
1680
1684
|
const lp = lessonsPath(root);
|
|
1681
1685
|
if (!exists(lp)) {
|
|
1682
1686
|
if (jsonMode) {
|
|
1683
|
-
|
|
1687
|
+
const empty = { version: VERSION, root, total: 0, lessons: [], tag: tagFilter };
|
|
1688
|
+
if (queryFilter) empty.query = queryFilter;
|
|
1689
|
+
process.stdout.write(JSON.stringify(empty, null, 2) + '\n');
|
|
1684
1690
|
return;
|
|
1685
1691
|
}
|
|
1686
1692
|
return ok('lessons.md 없음 — leerness lesson save "<text>" 로 첫 lesson 영구화');
|
|
@@ -1699,19 +1705,24 @@ function lessonListCmd(root, opts = {}) {
|
|
|
1699
1705
|
tag: tagMatch ? tagMatch[1].trim() : null,
|
|
1700
1706
|
};
|
|
1701
1707
|
if (tagFilter && lesson.tag !== tagFilter) continue;
|
|
1708
|
+
// 1.9.139: query 필터 — lesson text 또는 tag 매칭
|
|
1709
|
+
if (queryRe && !queryRe.test(lesson.text) && !queryRe.test(lesson.tag || '')) continue;
|
|
1702
1710
|
lessons.push(lesson);
|
|
1703
1711
|
}
|
|
1704
1712
|
if (jsonMode) {
|
|
1705
|
-
|
|
1713
|
+
const payload = { version: VERSION, root, total: lessons.length, lessons, tag: tagFilter };
|
|
1714
|
+
if (queryFilter) payload.query = queryFilter;
|
|
1715
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
1706
1716
|
return;
|
|
1707
1717
|
}
|
|
1708
|
-
log(`# 💡 Lessons (1.9.117)${tagFilter ? ` — tag: ${tagFilter}` : ''}\n`);
|
|
1718
|
+
log(`# 💡 Lessons (1.9.117)${tagFilter ? ` — tag: ${tagFilter}` : ''}${queryFilter ? ` — query: "${queryFilter}"` : ''}\n`);
|
|
1709
1719
|
if (!lessons.length) {
|
|
1710
|
-
if (
|
|
1720
|
+
if (queryFilter) ok(`"${queryFilter}" 매칭 lesson 없음`);
|
|
1721
|
+
else if (tagFilter) ok(`"${tagFilter}" 태그 lesson 없음`);
|
|
1711
1722
|
else ok('lessons 비어있음');
|
|
1712
1723
|
return;
|
|
1713
1724
|
}
|
|
1714
|
-
log(`총 ${lessons.length}건${tagFilter ? ` (tag: ${tagFilter})` : ''}:`);
|
|
1725
|
+
log(`총 ${lessons.length}건${tagFilter ? ` (tag: ${tagFilter})` : ''}${queryFilter ? ` (query: "${queryFilter}")` : ''}:`);
|
|
1715
1726
|
for (const l of lessons) {
|
|
1716
1727
|
log(`\n[${l.date || '?'}]${l.tag ? ` #${l.tag}` : ''}`);
|
|
1717
1728
|
log(` ${l.text}`);
|
|
@@ -1772,10 +1783,15 @@ function lessonSave(root, text) {
|
|
|
1772
1783
|
function decisionListCmd(root, opts = {}) {
|
|
1773
1784
|
root = absRoot(root);
|
|
1774
1785
|
const jsonMode = !!opts.json || has('--json');
|
|
1786
|
+
// 1.9.139: --query 필터 (title/decision/reason case-insensitive 매칭)
|
|
1787
|
+
const queryFilter = arg('--query', null);
|
|
1788
|
+
const queryRe = queryFilter ? new RegExp(queryFilter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') : null;
|
|
1775
1789
|
const dp = decisionsPath(root);
|
|
1776
1790
|
if (!exists(dp)) {
|
|
1777
1791
|
if (jsonMode) {
|
|
1778
|
-
|
|
1792
|
+
const empty = { version: VERSION, root, total: 0, decisions: [] };
|
|
1793
|
+
if (queryFilter) empty.query = queryFilter;
|
|
1794
|
+
process.stdout.write(JSON.stringify(empty, null, 2) + '\n');
|
|
1779
1795
|
return;
|
|
1780
1796
|
}
|
|
1781
1797
|
return ok('decisions.md 없음 — leerness decision add "<title>" 로 첫 결정 영구화');
|
|
@@ -1795,22 +1811,30 @@ function decisionListCmd(root, opts = {}) {
|
|
|
1795
1811
|
const reasonMatch = block.match(/- Reason:\s*(.+)/);
|
|
1796
1812
|
const alternativesMatch = block.match(/- Alternatives:\s*(.+)/);
|
|
1797
1813
|
const impactMatch = block.match(/- Impact:\s*(.+)/);
|
|
1798
|
-
|
|
1814
|
+
const entry = {
|
|
1799
1815
|
date,
|
|
1800
1816
|
title,
|
|
1801
1817
|
decision: decisionMatch ? decisionMatch[1].trim() : null,
|
|
1802
1818
|
reason: reasonMatch ? reasonMatch[1].trim() : null,
|
|
1803
1819
|
alternatives: alternativesMatch ? alternativesMatch[1].trim() : null,
|
|
1804
1820
|
impact: impactMatch ? impactMatch[1].trim() : null,
|
|
1805
|
-
}
|
|
1821
|
+
};
|
|
1822
|
+
// 1.9.139: query 필터 — title/decision/reason 매칭
|
|
1823
|
+
if (queryRe) {
|
|
1824
|
+
const hay = [entry.title, entry.decision, entry.reason, entry.alternatives, entry.impact].filter(Boolean).join(' ');
|
|
1825
|
+
if (!queryRe.test(hay)) continue;
|
|
1826
|
+
}
|
|
1827
|
+
decisions.push(entry);
|
|
1806
1828
|
}
|
|
1807
1829
|
if (jsonMode) {
|
|
1808
|
-
|
|
1830
|
+
const payload = { version: VERSION, root, total: decisions.length, decisions };
|
|
1831
|
+
if (queryFilter) payload.query = queryFilter;
|
|
1832
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
1809
1833
|
return;
|
|
1810
1834
|
}
|
|
1811
|
-
log(`# 🧠 Decisions (1.9.118)\n`);
|
|
1812
|
-
if (!decisions.length) return ok('decisions 비어있음');
|
|
1813
|
-
log(`총 ${decisions.length}
|
|
1835
|
+
log(`# 🧠 Decisions (1.9.118)${queryFilter ? ` — query: "${queryFilter}"` : ''}\n`);
|
|
1836
|
+
if (!decisions.length) return ok(queryFilter ? `"${queryFilter}" 매칭 decision 없음` : 'decisions 비어있음');
|
|
1837
|
+
log(`총 ${decisions.length}건${queryFilter ? ` (query: "${queryFilter}")` : ''}:`);
|
|
1814
1838
|
for (const d of decisions) {
|
|
1815
1839
|
log(`\n[${d.date || '?'}] ${d.title}`);
|
|
1816
1840
|
if (d.reason) log(` Reason: ${d.reason}`);
|
|
@@ -4334,7 +4358,7 @@ function _banner(opts = {}) {
|
|
|
4334
4358
|
lines.push('');
|
|
4335
4359
|
for (const ln of lines) log(ln);
|
|
4336
4360
|
if (opts.quickStart) {
|
|
4337
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.
|
|
4361
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.139+ lesson/decision list --query 필터 — 69 라운드 자율 누적)')));
|
|
4338
4362
|
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
4339
4363
|
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + history hit + brainstorm hits + 헤드라인'));
|
|
4340
4364
|
log(' ' + C.green('npx leerness handoff . --quiet') + C.dim(' # 자동화/CI 모드 (1.9.99) — 자동 회수 라인 비활성'));
|
|
@@ -8644,8 +8668,8 @@ function mcpServeCmd(root) {
|
|
|
8644
8668
|
{ name: 'leerness_plan_add', description: '1.9.110 — plan.md 에 새 milestone 추가 + progress-tracker.md에 자동 동기화 task 생성. 외부 AI가 계획 단계를 직접 등록. 인자: { text (required), status?, progress?, nextAction?, path? }', inputSchema: { type: 'object', properties: { text: { type: 'string' }, status: { type: 'string' }, progress: { type: 'string' }, nextAction: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
8645
8669
|
{ name: 'leerness_lesson_save', description: '1.9.112 — .harness/lessons.md 에 새 lesson 영구화 (Memory Write Surface 5번째). 외부 AI가 세션 중 얻은 통찰을 즉시 영구 기록 — handoff 자동 회수와 통합. 인자: { text (required), tag?, path? }', inputSchema: { type: 'object', properties: { text: { type: 'string' }, tag: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
8646
8670
|
{ name: 'leerness_memory_status', description: '1.9.114 — Memory Write Surface 5종 (tasks/decisions/rules/plan/lessons) 통합 상태 JSON. 외부 AI가 한 호출로 영구화 상태 + 카운트 + 최근 항목 회수. summary 필드는 "T2/D3/R1/P5/L7" 형식', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
8647
|
-
{ name: 'leerness_lesson_list', description: '1.9.117 — lessons.md 전용 list JSON ({ date, text, tag }[]). --tag 필터 지원. 외부 AI가 영구화된 lesson 전체 회수 (vs leerness_lessons 는 다중 source fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, tag: { type: 'string' } } } },
|
|
8648
|
-
{ name: 'leerness_decision_list', description: '1.9.118 — decisions.md 전체 조회 JSON ({ date, title, decision, reason, alternatives, impact }[]). 외부 AI가 영구화된 설계 결정 전체 회수
|
|
8671
|
+
{ name: 'leerness_lesson_list', description: '1.9.117 — lessons.md 전용 list JSON ({ date, text, tag }[]). --tag 필터 지원. 1.9.139+ --query 키워드 필터 (text/tag case-insensitive). 외부 AI가 영구화된 lesson 전체 회수 (vs leerness_lessons 는 다중 source fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, tag: { type: 'string' }, query: { type: 'string' } } } },
|
|
8672
|
+
{ name: 'leerness_decision_list', description: '1.9.118 — decisions.md 전체 조회 JSON ({ date, title, decision, reason, alternatives, impact }[]). 1.9.139+ --query 키워드 필터 (title/decision/reason/alternatives/impact case-insensitive). 외부 AI가 영구화된 설계 결정 전체 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' }, query: { type: 'string' } } } },
|
|
8649
8673
|
{ name: 'leerness_plan_list', description: '1.9.119 — plan.md 의 모든 milestone (M-XXXX) 조회 JSON ({ id, title, status, progress, tasks: [{ done, text }] }[]). 외부 AI가 영구화된 계획 + 진행률 + tasks checkbox 전체 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
8650
8674
|
{ name: 'leerness_lesson_drop', description: '1.9.124 — lessons.md 에서 특정 lesson 제거 (target: date YYYY-MM-DD 또는 text substring). 잘못 저장한 lesson 제거. 제거된 블록은 .harness/lessons.archive.md 에 자동 보존 (복구 가능)', inputSchema: { type: 'object', properties: { target: { type: 'string' }, path: { type: 'string' } }, required: ['target'] } },
|
|
8651
8675
|
{ name: 'leerness_decision_drop', description: '1.9.125 — decisions.md 에서 특정 결정 제거 (target: date YYYY-MM-DD 또는 title substring). 제거된 블록은 .harness/decisions.archive.md 에 자동 보존', inputSchema: { type: 'object', properties: { target: { type: 'string' }, path: { type: 'string' } }, required: ['target'] } },
|
|
@@ -8717,8 +8741,8 @@ function mcpServeCmd(root) {
|
|
|
8717
8741
|
case 'leerness_plan_add': cliArgs = ['plan', 'add', String(args.text || ''), '--path', targetPath, ...(args.status ? ['--status', args.status] : []), ...(args.progress ? ['--progress', String(args.progress)] : []), ...(args.nextAction ? ['--next', args.nextAction] : [])]; break;
|
|
8718
8742
|
case 'leerness_lesson_save': cliArgs = ['lesson', 'save', String(args.text || ''), '--path', targetPath, ...(args.tag ? ['--tag', args.tag] : [])]; break;
|
|
8719
8743
|
case 'leerness_memory_status': cliArgs = ['memory', 'status', '--path', targetPath, '--json']; break;
|
|
8720
|
-
case 'leerness_lesson_list': cliArgs = ['lesson', 'list', '--path', targetPath, '--json', ...(args.tag ? ['--tag', args.tag] : [])]; break;
|
|
8721
|
-
case 'leerness_decision_list': cliArgs = ['decision', 'list', '--path', targetPath, '--json']; break;
|
|
8744
|
+
case 'leerness_lesson_list': cliArgs = ['lesson', 'list', '--path', targetPath, '--json', ...(args.tag ? ['--tag', args.tag] : []), ...(args.query ? ['--query', args.query] : [])]; break;
|
|
8745
|
+
case 'leerness_decision_list': cliArgs = ['decision', 'list', '--path', targetPath, '--json', ...(args.query ? ['--query', args.query] : [])]; break;
|
|
8722
8746
|
case 'leerness_plan_list': cliArgs = ['plan', 'list', '--path', targetPath, '--json']; break;
|
|
8723
8747
|
case 'leerness_lesson_drop': cliArgs = ['lesson', 'drop', String(args.target || ''), '--path', targetPath]; break;
|
|
8724
8748
|
case 'leerness_decision_drop': cliArgs = ['decision', 'drop', String(args.target || ''), '--path', targetPath]; break;
|