leerness 1.9.141 → 1.9.142
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 +26 -0
- package/README.md +2 -2
- package/bin/harness.js +91 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.142 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**Feature Graph 통합 라운드** — 1.9.141 인과관계 시스템을 audit / MCP CRUD / session close 에 통합.
|
|
6
|
+
|
|
7
|
+
### Added — MCP feature CRUD 완성
|
|
8
|
+
- `leerness_feature_add` (MCP 45) — 외부 AI가 코드 작성 중 feature 등록
|
|
9
|
+
- `leerness_feature_link` (MCP 46) — 의존/영향/공변경 엣지 추가
|
|
10
|
+
- 이제 외부 AI (Claude Code/Cursor 등) 가 leerness 워크플로 밖에서도 인과관계를 직접 갱신
|
|
11
|
+
- 인자: `title/dependsOn/affects/coChangesWith/files/path`
|
|
12
|
+
|
|
13
|
+
### Added — audit Feature Graph 무결성 검증 (kind 13종으로 확장)
|
|
14
|
+
- `feature_graph_orphan` — 다른 노드가 참조하는데 정의 없는 ID (예: F-0001 → F-0099 missing)
|
|
15
|
+
- `feature_graph_cycle` — affects/depends-on 그래프 순환 감지 (DFS 3-color)
|
|
16
|
+
- 둘 다 warning (--strict 시 failures 승격)
|
|
17
|
+
- `--no-feature-check` 옵션으로 끄기
|
|
18
|
+
- `audit --json` findings 에 `count/orphans[]/cycles[]` 상세 포함
|
|
19
|
+
|
|
20
|
+
### Added — session close --json featureGraph 통합
|
|
21
|
+
- `{ total, edges, isolated, summary: "F<n>/E<n>[/iso<n>]" }` 추가
|
|
22
|
+
- 마감 시 Feature Graph 통계 자동 보고
|
|
23
|
+
|
|
24
|
+
### Validation
|
|
25
|
+
- stress-v87: PASS (MCP feature_add/link + audit orphan/cycle + session close featureGraph + 누적 회귀)
|
|
26
|
+
- e2e: 219/219 PASS
|
|
27
|
+
- MCP tools: **46** (+2 from 44)
|
|
28
|
+
|
|
3
29
|
## 1.9.141 — 2026-05-20
|
|
4
30
|
|
|
5
31
|
**ASCII 배너 모션 + Feature Causality Graph (인과관계 추적) — 사용자 요청 2종.**
|
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.142 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.142';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -2320,6 +2320,62 @@ function audit(root, opts = {}) {
|
|
|
2320
2320
|
}
|
|
2321
2321
|
} catch {}
|
|
2322
2322
|
}
|
|
2323
|
+
// 1.9.142: Feature Graph 무결성 검증 — orphan/cycle 자동 감지 (--no-feature-check로 끄기)
|
|
2324
|
+
if (!has('--no-feature-check')) {
|
|
2325
|
+
try {
|
|
2326
|
+
const { nodes: fNodes } = _readFeatureGraph(root);
|
|
2327
|
+
if (fNodes.length > 0) {
|
|
2328
|
+
const ids = new Set(fNodes.map(n => n.id));
|
|
2329
|
+
// (1) orphan: 다른 노드가 참조하는데 정의가 없는 ID
|
|
2330
|
+
const orphans = [];
|
|
2331
|
+
for (const n of fNodes) {
|
|
2332
|
+
for (const ref of [...(n.dependsOn || []), ...(n.affects || []), ...(n.coChangesWith || [])]) {
|
|
2333
|
+
if (!ids.has(ref)) orphans.push({ from: n.id, missingRef: ref });
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (orphans.length) {
|
|
2337
|
+
warnings++;
|
|
2338
|
+
warn(`Feature Graph: orphan 참조 ${orphans.length}건 — ${orphans.slice(0, 3).map(o => `${o.from}→${o.missingRef}`).join(', ')}${orphans.length > 3 ? ' …' : ''}`);
|
|
2339
|
+
_finding('feature_graph_orphan', 'warn', 'Feature Graph 에 정의되지 않은 ID 참조', { count: orphans.length, orphans: orphans.slice(0, 10) });
|
|
2340
|
+
log(` → 수정: leerness feature add 또는 link 제거`);
|
|
2341
|
+
}
|
|
2342
|
+
// (2) cycle: affects 그래프에서 순환 의존성 감지 (DFS)
|
|
2343
|
+
const cycles = [];
|
|
2344
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
2345
|
+
const color = new Map();
|
|
2346
|
+
for (const n of fNodes) color.set(n.id, WHITE);
|
|
2347
|
+
const byId = new Map(fNodes.map(n => [n.id, n]));
|
|
2348
|
+
const dfs = (nodeId, path) => {
|
|
2349
|
+
color.set(nodeId, GRAY);
|
|
2350
|
+
const node = byId.get(nodeId);
|
|
2351
|
+
if (!node) { color.set(nodeId, BLACK); return; }
|
|
2352
|
+
for (const next of [...(node.affects || []), ...(node.dependsOn || [])]) {
|
|
2353
|
+
if (!byId.has(next)) continue;
|
|
2354
|
+
const c = color.get(next);
|
|
2355
|
+
if (c === GRAY) {
|
|
2356
|
+
// 순환 발견 — path 에 next 까지 자르기
|
|
2357
|
+
const idx = path.indexOf(next);
|
|
2358
|
+
const cyc = idx >= 0 ? path.slice(idx).concat([next]) : [...path, next];
|
|
2359
|
+
if (!cycles.some(existing => existing.join() === cyc.join())) cycles.push(cyc);
|
|
2360
|
+
} else if (c === WHITE) {
|
|
2361
|
+
dfs(next, [...path, next]);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
color.set(nodeId, BLACK);
|
|
2365
|
+
};
|
|
2366
|
+
for (const n of fNodes) if (color.get(n.id) === WHITE) dfs(n.id, [n.id]);
|
|
2367
|
+
if (cycles.length) {
|
|
2368
|
+
warnings++;
|
|
2369
|
+
warn(`Feature Graph: 순환 의존 ${cycles.length}건 — ${cycles[0].join(' → ')}${cycles.length > 1 ? ` (외 ${cycles.length-1}건)` : ''}`);
|
|
2370
|
+
_finding('feature_graph_cycle', 'warn', 'Feature Graph 에 순환 의존', { count: cycles.length, cycles: cycles.slice(0, 5) });
|
|
2371
|
+
log(` → 수정: feature link 재구성 (affects/depends-on 방향 정리)`);
|
|
2372
|
+
}
|
|
2373
|
+
if (!orphans.length && !cycles.length) {
|
|
2374
|
+
ok(`Feature Graph OK (${fNodes.length} 노드, orphan/cycle 없음, 1.9.142)`);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
} catch {}
|
|
2378
|
+
}
|
|
2323
2379
|
// 1.9.63: --strict — warnings ≥ threshold 시 failures로 승격 (CI 친화)
|
|
2324
2380
|
if (has('--strict')) {
|
|
2325
2381
|
const threshold = parseInt(arg('--threshold', '1'), 10);
|
|
@@ -4526,7 +4582,7 @@ function _banner(opts = {}) {
|
|
|
4526
4582
|
for (const ln of lines) log(ln);
|
|
4527
4583
|
}
|
|
4528
4584
|
if (opts.quickStart) {
|
|
4529
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.
|
|
4585
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.142+ Feature Graph 통합: MCP CRUD + audit 검증 + session close — 72 라운드 자율 누적)')));
|
|
4530
4586
|
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
4531
4587
|
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + history hit + brainstorm hits + 헤드라인'));
|
|
4532
4588
|
log(' ' + C.green('npx leerness handoff . --quiet') + C.dim(' # 자동화/CI 모드 (1.9.99) — 자동 회수 라인 비활성'));
|
|
@@ -5486,6 +5542,22 @@ function sessionClose(root, opts = {}) {
|
|
|
5486
5542
|
archive: archiveCountsS, // 1.9.130
|
|
5487
5543
|
summary: `T${tasksInProgress0}/D${decisionsCount0}/R${rulesActive0}/P${milestones0}/L${lessonsCount0}`,
|
|
5488
5544
|
};
|
|
5545
|
+
// 1.9.142: featureCounts 통합 — session close JSON에 Feature Graph 통계
|
|
5546
|
+
try {
|
|
5547
|
+
const { nodes: fNodesC } = _readFeatureGraph(root);
|
|
5548
|
+
const edgeCount = fNodesC.reduce((s, n) => s + (n.dependsOn?.length || 0) + (n.affects?.length || 0) + (n.coChangesWith?.length || 0), 0);
|
|
5549
|
+
const linkedIds = new Set();
|
|
5550
|
+
for (const n of fNodesC) {
|
|
5551
|
+
for (const x of [...(n.dependsOn||[]), ...(n.affects||[]), ...(n.coChangesWith||[])]) { linkedIds.add(n.id); linkedIds.add(x); }
|
|
5552
|
+
}
|
|
5553
|
+
const isolated = fNodesC.length ? (fNodesC.length - linkedIds.size) : 0;
|
|
5554
|
+
jsonResult.featureGraph = {
|
|
5555
|
+
total: fNodesC.length,
|
|
5556
|
+
edges: edgeCount,
|
|
5557
|
+
isolated: Math.max(0, isolated),
|
|
5558
|
+
summary: `F${fNodesC.length}/E${edgeCount}${isolated > 0 ? `/iso${isolated}` : ''}`
|
|
5559
|
+
};
|
|
5560
|
+
} catch {}
|
|
5489
5561
|
} catch {}
|
|
5490
5562
|
process.stdout.write(JSON.stringify(jsonResult, null, 2) + '\n');
|
|
5491
5563
|
}
|
|
@@ -9151,7 +9223,9 @@ function mcpServeCmd(root) {
|
|
|
9151
9223
|
{ name: 'leerness_task_list', description: '1.9.134 — progress-tracker.md 전체 task 조회 JSON ({ total, tasks: [{ id, status, request, evidence, nextAction, updated }] }). --status 필터 지원 (planned|in-progress|done 등). 외부 AI가 task 상태 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' }, status: { type: 'string' } } } },
|
|
9152
9224
|
{ name: 'leerness_rule_remove', description: '1.9.135 — rules.md 에서 특정 rule 제거 (id: R-XXXX). 제거된 rule 은 .harness/rules.archive.md 에 자동 보존 (복구 가능). Rule surface CRUD MCP 완성 (add/list/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
9153
9225
|
{ name: 'leerness_feature_impact', description: '1.9.141 — Feature Causality Graph 인과관계 영향 추적 JSON ({ feature, total, impacted: [{ id, title, depth, via, files, errorModes }] }). 신규 기능 추가/형식 변경 전 호출: id 변경으로 영향받는 다른 feature를 transitive (affects + co-changes + reverse depends-on) 으로 회수. 1+1=20 cascade 방지', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
9154
|
-
{ name: 'leerness_feature_list', description: '1.9.141 — 전체 Feature Graph 노드 + 엣지 JSON. 외부 AI가 시스템 내 기능 의존성을 한 번에 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
|
|
9226
|
+
{ name: 'leerness_feature_list', description: '1.9.141 — 전체 Feature Graph 노드 + 엣지 JSON. 외부 AI가 시스템 내 기능 의존성을 한 번에 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
9227
|
+
{ name: 'leerness_feature_add', description: '1.9.142 — Feature Graph 에 새 노드 추가 (외부 AI가 코드 작성 중 직접 feature 등록). 인자: { title (required), dependsOn?, affects?, coChangesWith?, files?, path? }. 자동 F-XXXX ID 부여. CRUD 완성에 기여', inputSchema: { type: 'object', properties: { title: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, files: { type: 'string' }, path: { type: 'string' } }, required: ['title'] } },
|
|
9228
|
+
{ name: 'leerness_feature_link', description: '1.9.142 — 기존 feature 노드에 의존/영향/공변경 엣지 추가. 인자: { id (required, F-XXXX), dependsOn?, affects?, coChangesWith?, path? }. 외부 AI가 코드 변경 도중 발견한 인과관계를 즉시 그래프에 반영', inputSchema: { type: 'object', properties: { id: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } }
|
|
9155
9229
|
];
|
|
9156
9230
|
|
|
9157
9231
|
function send(obj) {
|
|
@@ -9228,6 +9302,20 @@ function mcpServeCmd(root) {
|
|
|
9228
9302
|
// 1.9.141: Feature Causality Graph
|
|
9229
9303
|
case 'leerness_feature_impact': cliArgs = ['feature', 'impact', String(args.id || ''), '--path', targetPath, '--json']; break;
|
|
9230
9304
|
case 'leerness_feature_list': cliArgs = ['feature', 'list', '--path', targetPath, '--json']; break;
|
|
9305
|
+
// 1.9.142: Feature Graph WRITE CRUD
|
|
9306
|
+
case 'leerness_feature_add':
|
|
9307
|
+
cliArgs = ['feature', 'add', String(args.title || ''), '--path', targetPath];
|
|
9308
|
+
if (args.dependsOn) cliArgs.push('--depends-on', String(args.dependsOn));
|
|
9309
|
+
if (args.affects) cliArgs.push('--affects', String(args.affects));
|
|
9310
|
+
if (args.coChangesWith) cliArgs.push('--co-changes-with', String(args.coChangesWith));
|
|
9311
|
+
if (args.files) cliArgs.push('--files', String(args.files));
|
|
9312
|
+
break;
|
|
9313
|
+
case 'leerness_feature_link':
|
|
9314
|
+
cliArgs = ['feature', 'link', String(args.id || ''), '--path', targetPath];
|
|
9315
|
+
if (args.dependsOn) cliArgs.push('--depends-on', String(args.dependsOn));
|
|
9316
|
+
if (args.affects) cliArgs.push('--affects', String(args.affects));
|
|
9317
|
+
if (args.coChangesWith) cliArgs.push('--co-changes-with', String(args.coChangesWith));
|
|
9318
|
+
break;
|
|
9231
9319
|
default:
|
|
9232
9320
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
9233
9321
|
}
|