leerness 1.13.0 → 1.14.0
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 +45 -0
- package/README.md +23 -5
- package/bin/leerness.js +20 -5
- package/lib/diagnostics.js +194 -193
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.14.0 — 2026-06-09 — 🛡️ [안정화/Stable] 블라인드 리뷰 수정 + Karpathy 정렬 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor.** 블라인드 3-모델 리뷰(codex/Sonnet/Opus) 수정 + Karpathy 가이드라인 정렬(1.13.1~1.13.2)을 검증·통합해 npm 공개. R-0011 정책의 5번째 minor. (이 릴리스의 소개 영상부터 **HyperFrames 파이프라인**으로 자동 제작됩니다.)
|
|
6
|
+
|
|
7
|
+
### 이번 minor 통합 (1.13.1~1.13.2)
|
|
8
|
+
- **🔴 memory search 5종 표면 복구** (블라인드 Sonnet P1): `memory search` 가 lessons.md/rules.md 를 누락해 저장한 교훈·룰을 못 찾던 문제 → 추가.
|
|
9
|
+
- **🔴 옵션-only 무명령의 묵시적 init 쓰기 차단** (블라인드 codex P1): `leerness --json` 처럼 명령 없이 옵션만 주면 cwd 에 `.harness` 가 생성되던 부작용 → help 처리(명시 init 만 쓰기).
|
|
10
|
+
- **📖 README 블라인드 재구성**: 3모델이 코드/행위만으로 파악한 정체성 반영(거짓완료 차단 전면화·다언어·경로규칙·한국어 우선).
|
|
11
|
+
- **🔬 verify-claim scope-creep 표면화** (Karpathy 원칙3 "외과적 변경"): git 에 변경됐으나 evidence/주장에 없는 파일(요청 범위 밖 변경)을 advisory 로 노출(`--json scopeCreep`).
|
|
12
|
+
- doctor 문구 + selftest README exists 가드 (블라인드 P3).
|
|
13
|
+
|
|
14
|
+
### 검증 (회귀 0)
|
|
15
|
+
- **selftest 211 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump. scope-creep 행위(claim A + change A,B→[B]), memory search 5종 검색, --json-무명령 비쓰기 재현.
|
|
16
|
+
|
|
17
|
+
### 안정화 표시 (R-0006)
|
|
18
|
+
CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
|
|
19
|
+
|
|
20
|
+
## 1.13.2 — 2026-06-09 — Karpathy 가이드라인 정렬: verify-claim scope-creep 표면화 (외과적 변경)
|
|
21
|
+
|
|
22
|
+
**🔬 외부 에이전트(Sonnet/Opus) 가 leerness 를 Andrej Karpathy 4대 코딩 가이드라인 대비 검토.** 두 리뷰가 수렴: leerness 는 원칙4(목표주도/검증루프)는 최강이나, **원칙3(외과적 변경)이 최대 갭** — 변경 파일이 요청 범위 내인지 검사가 없었음. 최고가치·최저노력 항목을 즉시 구현(신규 명령 0 — leerness 자신의 원칙2 위반 악화 방지).
|
|
23
|
+
|
|
24
|
+
### 변경 (UR-0030, Karpathy 원칙3)
|
|
25
|
+
- **verify-claim 역방향 git 교차검증**: 기존엔 "주장한 파일이 git 변경에 있는가"(한 방향)만 봤음. 이제 **"git 에 변경됐으나 evidence/주장에 없는 파일"(scope-creep / 요청 범위 밖 변경 신호)** 도 표면화 — `--json scopeCreep` + 사람용 advisory(`🔬 외과적 변경 점검`). 하네스 자체 기록(.harness/.git/node_modules 등) 제외, 오탐 방지 위해 advisory(기본 FAIL 아님). 이미 계산하던 `gitChanged` Set 재사용 — 최소 변경.
|
|
26
|
+
|
|
27
|
+
### 검증 (회귀 0)
|
|
28
|
+
- **selftest 210→211**, 행위 재현: claim a.js + change a.js,b.js → `scopeCreep={count:1,files:["b.js"]}` (.harness 정확 제외).
|
|
29
|
+
- 백로그(기존 명령 확장 권장): review-request scope/단순성 신호(원칙1+2, UR-0031) · plan `--done-when` 성공기준(원칙4, UR-0032) · leerness 자체 단순화 검토(원칙2, UR-0033).
|
|
30
|
+
- patch(1.13.2) — R-0011 정책상 npm 미배포(GitHub).
|
|
31
|
+
|
|
32
|
+
## 1.13.1 — 2026-06-09 — 블라인드 3-모델 리뷰 수정 + README 재구성 (codex gpt-5.5 · Sonnet 4.8 · Opus 4.8)
|
|
33
|
+
|
|
34
|
+
**🔍 블라인드 멀티모델 리뷰**: 3개 모델(codex gpt-5.5 외부 CLI + Claude Sonnet/Opus 4.8 서브에이전트)이 **leerness 소개 없이, README 를 물리적으로 제거(숨김)한 클린룸**에서 npm 최신본(1.13.0)을 코드·CLI·행위만으로 분석. 발견을 맹신 X로 직접 재현·검증해 수정하고, 그 리뷰 내용으로 README 를 재구성.
|
|
35
|
+
|
|
36
|
+
### 버그 수정 (전부 재현 검증)
|
|
37
|
+
- **🔴 memory search 가 lessons/rules 누락** (Sonnet P1): `memory search` 가 5종 메모리 표면을 표방하나 `lessons.md`/`rules.md` 를 검색 못 해(저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음 → 두 파일 추가. (검증: lesson/rule/decision 3종 모두 검색됨)
|
|
38
|
+
- **🔴 옵션-only 무명령의 묵시적 init 쓰기** (codex P1): `leerness --json` 처럼 명령 없이 옵션만 주면 cwd 에 `.harness` 가 의도치 않게 생성되던 쓰기 부작용 → 무명령+옵션만은 help 로 처리(명시 `init` 만 쓰기). bare `leerness` 온보딩은 유지.
|
|
39
|
+
- **doctor 문구 + selftest README 의존** (Sonnet/codex/Opus P3): doctor 가 pass 수에 '실패' 를 붙여 "209/210 실패"(=209건 실패로 오독)되던 것 → "통과 N/M (K건 실패)". selftest 의 README 배너 케이스에 `exists` 가드 추가 → 문서가 없는(pruned) 설치에서도 코어 무결성 진단 통과.
|
|
40
|
+
|
|
41
|
+
### README 재구성 (블라인드 리뷰 기반)
|
|
42
|
+
3모델이 코드/행위만으로 파악한 정체성("AI 코딩 에이전트 운영/신뢰성 하네스, 코드를 대신 쓰지 않음")을 반영 — **거짓 완료 차단(핵심 차별점) 섹션 신설**(verify-claim 다언어 인식 포함), selftest 카운트 정정(→210), 경로 규칙(메모리 명령은 `--path`/`./dir`)·한국어-우선 출력 명시.
|
|
43
|
+
|
|
44
|
+
### 검증 / 잔여
|
|
45
|
+
- **selftest 210 PASS** · E2E 365/365. 백로그: bare relative dir cwd-fallback(codex P2, README 경로규칙으로 문서화 + pure 함수 계약 변경은 후속).
|
|
46
|
+
- patch(1.13.1, 같은 minor) — R-0011 정책상 npm 미배포(GitHub). Opus 가 "버그처럼 보였으나 자기 테스트 데이터 오류"로 정직하게 철회한 항목은 수정 안 함(과수정 회피).
|
|
47
|
+
|
|
3
48
|
## 1.13.0 — 2026-06-09 — 🛡️ [안정화/Stable] verify-claim 다언어 + 정직성·자원·보안 안정화
|
|
4
49
|
|
|
5
50
|
**🛡️ 안정화(Stable) minor. 헤드라인 = verify-claim 다언어 지원(비-JS 개발자 핵심 회귀 수정).** 15번째 멀티에이전트 버그헌트 성과(1.12.2~1.12.5)를 검증·통합해 npm 공개. R-0011 정책의 4번째 minor.
|
package/README.md
CHANGED
|
@@ -67,6 +67,22 @@ CLAUDE.md / AGENTS.md 에 `세션 시작 시 leerness handoff .`, `종료 전 le
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
+
## 거짓 완료 차단 — 핵심 차별점
|
|
71
|
+
|
|
72
|
+
세 모델의 블라인드 리뷰가 공통으로 꼽은 leerness의 핵심 가치입니다. AI 에이전트가 가장 자주 하는 거짓말 "다 했어요"를, leerness는 **완료 주장을 실제 코드와 대조**해 검증합니다.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 에이전트가 "결제 API 연동 완료"라고 주장하지만 코드엔 호출 흔적이 없으면:
|
|
76
|
+
leerness verify-claim T-0001 --require-evidence
|
|
77
|
+
# ⚠ FAIL (낙관 1) · [Payment] 결제: evidence에 주장 있으나 코드에 호출 흔적 없음 → exit 1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `optimism-check` · `verify-claim` — evidence의 도메인 주장(API·DB·결제·이메일·큐·캐시·알림·스토리지 등 10종)을 실제 소스 호출과 대조. **JavaScript뿐 아니라 Python·Ruby·Go·C#·Java·PHP·Rust 구현도 인식**합니다(1.13).
|
|
81
|
+
- `lazy detect` — 증거 없는 done, 빈 handoff, 테스트 미실행, 미추적 TODO를 탐지.
|
|
82
|
+
- 정직한 완료는 통과하고 가짜 완료만 exit 1로 차단 — `gate` 또는 CI에 그대로 연결됩니다.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
70
86
|
## 핵심 개념 — 5계층
|
|
71
87
|
|
|
72
88
|
- **기억(Memory)** — `task`/`plan`/`decision`/`lesson`/`rule`/`feature`(그래프). canonical JSON 을 단일 진실소스로 저장하고 마크다운은 projection. archive/restore 지원.
|
|
@@ -90,6 +106,8 @@ CLAUDE.md / AGENTS.md 에 `세션 시작 시 leerness handoff .`, `종료 전 le
|
|
|
90
106
|
|
|
91
107
|
전체 명령은 `leerness commands` 또는 `leerness --help` 로 확인하세요.
|
|
92
108
|
|
|
109
|
+
> **경로 규칙** — 대부분 명령은 `[path]` 위치 인자를 받습니다. `task`/`decision`/`lesson` 등 메모리 명령에 다른 폴더를 지정할 땐 `--path <dir>` 또는 `./dir` 형태를 쓰세요(맨 폴더 이름만 주면 현재 폴더로 처리됩니다). 출력은 한국어가 기본이며 `--language en` 으로 영어를 늘릴 수 있습니다(일부 메시지는 한국어 유지).
|
|
110
|
+
|
|
93
111
|
---
|
|
94
112
|
|
|
95
113
|
## 대표 워크플로
|
|
@@ -130,7 +148,7 @@ leerness mcp serve # JSON-RPC over stdio, 85 도구
|
|
|
130
148
|
- **원자적 UTF-8 쓰기** — temp + rename 으로 부분쓰기 손상 방지, BOM 자동 strip.
|
|
131
149
|
- **shell 미경유 MCP** — `mcp serve` 의 도구 호출은 셸을 거치지 않고 인자를 직접 전달(명령 주입 차단).
|
|
132
150
|
- **순수 `--json` / 일관 exit code** — 성공·실패·미존재 경로 모두 파싱 가능한 JSON(`{ok,error,code}`) + 실패 시 exit 1. 자동화·CI 친화.
|
|
133
|
-
- **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest`
|
|
151
|
+
- **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest` 210 케이스로 설치 무결성 검증(`doctor` 가 함께 실행). 문서(README) 부재에도 코어 무결성 진단은 통과.
|
|
134
152
|
|
|
135
153
|
---
|
|
136
154
|
|
|
@@ -168,7 +186,7 @@ MIT
|
|
|
168
186
|
<!-- leerness:project-readme:start -->
|
|
169
187
|
## Leerness Project Harness
|
|
170
188
|
|
|
171
|
-
이 프로젝트는 Leerness v1.
|
|
189
|
+
이 프로젝트는 Leerness v1.14.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
172
190
|
|
|
173
191
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
174
192
|
|
|
@@ -222,7 +240,7 @@ leerness memory restore decision <date|title>
|
|
|
222
240
|
|
|
223
241
|
### MCP server (외부 AI 통합)
|
|
224
242
|
|
|
225
|
-
Leerness v1.
|
|
243
|
+
Leerness v1.14.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
226
244
|
|
|
227
245
|
```jsonc
|
|
228
246
|
// 카테고리별
|
|
@@ -243,7 +261,7 @@ Leerness v1.13.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
243
261
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
244
262
|
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) 다음 라운드 예약.
|
|
245
263
|
|
|
246
|
-
현재 누적: **70 라운드 (1.9.40 → 1.
|
|
264
|
+
현재 누적: **70 라운드 (1.9.40 → 1.14.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
247
265
|
|
|
248
266
|
### 성능 가이드 (1.9.140 측정)
|
|
249
267
|
|
|
@@ -281,6 +299,6 @@ leerness release pack --close --auto-main-push
|
|
|
281
299
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
282
300
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
283
301
|
|
|
284
|
-
Last synced by Leerness v1.
|
|
302
|
+
Last synced by Leerness v1.14.0: 2026-06-09
|
|
285
303
|
<!-- leerness:project-readme:end -->
|
|
286
304
|
|
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
|
+
const VERSION = '1.14.0';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3313,9 +3313,11 @@ function _selfTestCases() {
|
|
|
3313
3313
|
return wired && behav;
|
|
3314
3314
|
} },
|
|
3315
3315
|
{ name: 'README ASCII 배너 표시 + CLI _banner 와 동일 아트 (1.9.441)', run: () => {
|
|
3316
|
-
|
|
3316
|
+
// 1.13.1 (15th 블라인드 리뷰 P3, Opus): README 부재 시 read() throw 로 selftest 가 깨끗한 실패 대신 예외 → exists 가드. README 는 코어 무결성 파일이 아니므로(문서 pruning 가능) 없으면 통과 처리, 있으면 CLI _banner 와 일치 검사.
|
|
3317
|
+
const readmePath = path.join(path.dirname(__filename), '..', 'README.md');
|
|
3317
3318
|
const bannerLine = '███████╗███████╗██╗ ██╗'.slice(0, 0) + '██║ █████╗ █████╗ ██████╔╝'; // LEERNESS 배너 고유 라인(자기참조 회피 split)
|
|
3318
|
-
|
|
3319
|
+
if (!exists(readmePath)) return read(__filename).includes(bannerLine); // 문서 없으면 CLI 측 아트만 확인
|
|
3320
|
+
return read(readmePath).includes(bannerLine) && read(__filename).includes(bannerLine); // README ↔ CLI _banner 동일 아트
|
|
3319
3321
|
} },
|
|
3320
3322
|
{ name: '12th 외부평가 Sonnet P1 (UR-0141): task 계열 positional path 지원 (cwd 오염 차단) (1.9.442)', run: () => {
|
|
3321
3323
|
const m = require('../lib/pure-utils');
|
|
@@ -3560,6 +3562,10 @@ function _selfTestCases() {
|
|
|
3560
3562
|
const reqDirectives = reqs.length === 1 && reqs[0] === 'requests';
|
|
3561
3563
|
return apiCrlf && statBeforeRead && nestedSkip && shellNoSpace && reqDirectives;
|
|
3562
3564
|
} },
|
|
3565
|
+
{ name: 'Karpathy 가이드라인3 "외과적 변경" (UR-0030): verify-claim 역방향 git 교차검증 scope-creep 표면화 (1.13.2)', run: () => {
|
|
3566
|
+
const src = read(__filename);
|
|
3567
|
+
return src.includes('const changedNotClaimed = gitApplicable') && src.includes('files.some(f => _claimFileInGit(f, new Set([g])))') && src.includes('scopeCreep:') && src.includes('외과적 변경 점검');
|
|
3568
|
+
} },
|
|
3563
3569
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3564
3570
|
];
|
|
3565
3571
|
}
|
|
@@ -7653,7 +7659,8 @@ function preCheck(root) {
|
|
|
7653
7659
|
function memorySearch(root, query) {
|
|
7654
7660
|
root = absRoot(root);
|
|
7655
7661
|
if (!query) { fail('query required (e.g., memory search "키워드")'); return; }
|
|
7656
|
-
|
|
7662
|
+
// 1.13.1 (15th 블라인드 리뷰 P1, Sonnet): lessons.md + rules.md 누락 수정 — memory search 가 5종 메모리 표면을 표방하나 lesson/rule 을 검색 못 해(lesson add/rule add 로 저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음.
|
|
7663
|
+
const files = ['.harness/decisions.md','.harness/lessons.md','.harness/rules.md','.harness/task-log.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/plan.md','.harness/review-evidence.md','.harness/architecture.md'];
|
|
7657
7664
|
const re = new RegExp(query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i');
|
|
7658
7665
|
let total = 0;
|
|
7659
7666
|
for (const f of files) {
|
|
@@ -9627,6 +9634,9 @@ function verifyClaimCmd(root, taskId) {
|
|
|
9627
9634
|
const gitApplicable = !!gitChanged && gitChanged.size > 0 && files.length > 0;
|
|
9628
9635
|
const claimedInGit = gitApplicable ? files.filter(f => _claimFileInGit(f, gitChanged)) : [];
|
|
9629
9636
|
const claimedNotInGit = gitApplicable ? files.filter(f => !_claimFileInGit(f, gitChanged)) : [];
|
|
9637
|
+
// 1.13.2 (Karpathy 가이드라인 3 "외과적 변경", UR-0030): 역방향 교차검증 — git 에 변경됐으나 evidence/주장에 없는 파일(scope-creep / 요청 범위 밖 변경 신호). 하네스 자체 기록(.harness 등)은 제외. advisory(오탐 방지 — 기본 FAIL 아님, 표면화만).
|
|
9638
|
+
const _SCOPE_SKIP = /^(\.harness[\\/]|\.git[\\/]|node_modules[\\/]|\.claude[\\/]|dist[\\/]|build[\\/])/;
|
|
9639
|
+
const changedNotClaimed = gitApplicable ? [...gitChanged].filter(g => !_SCOPE_SKIP.test(g) && !files.some(f => _claimFileInGit(f, new Set([g])))) : [];
|
|
9630
9640
|
// 테스트 카운트: tests/test.js의 check( 또는 it( 또는 test( 개수
|
|
9631
9641
|
let actualTestCount = null;
|
|
9632
9642
|
const candidateTestFiles = ['tests/test.js', 'test/test.js', 'tests/index.js'];
|
|
@@ -9728,7 +9738,8 @@ function verifyClaimCmd(root, taskId) {
|
|
|
9728
9738
|
},
|
|
9729
9739
|
evidence: { required: mustHaveEvidence, ...evq },
|
|
9730
9740
|
claims: !claimsChecked ? null : { ok: strictOk, optimism: optimismSuspects.map(s => ({ kind: s.kind, label: s.label })), honesty: honestyFindings.map(f => ({ dim: f.dim, label: f.label })) },
|
|
9731
|
-
git: gitChanged === null ? { applicable: false, reason: 'not-a-git-repo' } : (!gitApplicable ? { applicable: false, reason: 'no-working-changes-or-no-claimed-files' } : { applicable: true, claimedInGit: claimedInGit.length, claimedNotInGit, strongMismatch: gitStrongMismatch })
|
|
9741
|
+
git: gitChanged === null ? { applicable: false, reason: 'not-a-git-repo' } : (!gitApplicable ? { applicable: false, reason: 'no-working-changes-or-no-claimed-files' } : { applicable: true, claimedInGit: claimedInGit.length, claimedNotInGit, strongMismatch: gitStrongMismatch, changedNotClaimed }),
|
|
9742
|
+
scopeCreep: !gitApplicable ? null : { count: changedNotClaimed.length, files: changedNotClaimed.slice(0, 10) } // 1.13.2 (Karpathy 외과적 변경): 요청/주장 밖 변경 파일(advisory)
|
|
9732
9743
|
};
|
|
9733
9744
|
if (runResult) {
|
|
9734
9745
|
out.run = runResult;
|
|
@@ -9811,6 +9822,8 @@ function verifyClaimCmd(root, taskId) {
|
|
|
9811
9822
|
} else {
|
|
9812
9823
|
log(` - git diff 교차검증: ${gitStrongMismatch ? '⚠ 불일치' : '✓'} 주장 ${files.length}개 중 실제 변경 ${claimedInGit.length}개${claimedNotInGit.length ? ` · git 변경에 없음: ${claimedNotInGit.slice(0, 5).join(', ')}` : ''}`);
|
|
9813
9824
|
if (gitStrongMismatch) log(` · 주장한 파일이 working tree/직전커밋 변경에 전무 — 변경이 더 오래전 커밋이거나, 실제로 변경 안 됐을 수 있음(허위완료 의심)${has('--strict-claims') ? ' → FAIL' : ' (advisory — 커밋 후 검증 시 정상일 수 있음)'}`);
|
|
9825
|
+
// 1.13.2 (Karpathy 가이드라인 3 "외과적 변경", UR-0030): scope-creep 표면화 — git 변경됐으나 evidence/주장에 없는 파일.
|
|
9826
|
+
if (changedNotClaimed.length) log(` · 🔬 외과적 변경 점검: git 에 변경됐으나 evidence/주장에 없는 파일 ${changedNotClaimed.length}건: ${changedNotClaimed.slice(0, 5).join(', ')}${changedNotClaimed.length > 5 ? ' …' : ''} — 요청 범위 밖 변경(scope creep)인지 확인 (advisory)`);
|
|
9814
9827
|
}
|
|
9815
9828
|
// 1.9.309 (UR-0048): done 주장 evidence 완전성 — 기본 강제(상단 pre-compute). --lenient 로 opt-out.
|
|
9816
9829
|
if (mustHaveEvidence) {
|
|
@@ -19107,6 +19120,8 @@ async function main() {
|
|
|
19107
19120
|
return log(VERSION);
|
|
19108
19121
|
}
|
|
19109
19122
|
if (has('--help') || has('-h')) return help();
|
|
19123
|
+
// 1.13.1 (15th 블라인드 리뷰 P1, codex gpt-5.5): 무명령 + 옵션만(예: leerness --json) 은 묵시적 init 쓰기를 하지 않음 — cwd 에 .harness 가 의도치 않게 생성되던 부작용 차단. 명시 명령 없이 옵션만이면 help (bare leerness 온보딩 init 은 유지).
|
|
19124
|
+
if (!args[0] && process.argv.slice(2).some(a => a.startsWith('-'))) { help(); return; }
|
|
19110
19125
|
// 1.9.38 (B): 사용 통계 카운터 — usage stats 명령 자체와 비차단 경로는 제외
|
|
19111
19126
|
// 1.9.317 (UR-0051, 설치리뷰): 내부 auto-call(LEERNESS_INTERNAL=1) 은 usage 집계 제외 — 텔레메트리 오염(거짓 skill 추천) 방지.
|
|
19112
19127
|
if (process.env.LEERNESS_INTERNAL !== '1' && cmd !== 'usage' && cmd !== 'init' && cmd !== 'migrate' && cmd !== '--version' && cmd !== '--help') {
|
package/lib/diagnostics.js
CHANGED
|
@@ -1,193 +1,194 @@
|
|
|
1
|
-
// lib/diagnostics.js — 설치/환경 진단 핸들러 (doctor / which).
|
|
2
|
-
// 1.9.392 (UR-0025 큰 핸들러 모듈화 4번째): bin/harness.js 에서 doctor/which 분리.
|
|
3
|
-
// - I/O: ./io(log). node: child_process.
|
|
4
|
-
// - harness 고유 의존(VERSION · _selfTestCases · _detectShellCtx · _mcpToolCount · argv 파서 has · harnessPath)은 deps 주입(DI).
|
|
5
|
-
// _selfTestCases 의 각 case.run 클로저는 harness 스코프를 유지하므로 함수 참조 주입만으로 정상 동작.
|
|
6
|
-
'use strict';
|
|
7
|
-
const cp = require('child_process');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { log, fail, absRoot, exists, read } = require('./io');
|
|
10
|
-
const { parseHarnessVersion, _parseChangelogBetween } = require('./pure-utils');
|
|
11
|
-
|
|
12
|
-
function doctorCmd(opts = {}, deps = {}) {
|
|
13
|
-
const { VERSION, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath } = deps;
|
|
14
|
-
const json = opts.json || has('--json');
|
|
15
|
-
// 1) 코어 무결성: selftest 케이스 인라인 실행
|
|
16
|
-
let pass = 0; const failNames = [];
|
|
17
|
-
try {
|
|
18
|
-
for (const c of _selfTestCases()) {
|
|
19
|
-
let okc = false;
|
|
20
|
-
try { okc = !!c.run(); } catch { okc = false; }
|
|
21
|
-
if (okc) pass++; else failNames.push(c.name);
|
|
22
|
-
}
|
|
23
|
-
} catch {}
|
|
24
|
-
const total = pass + failNames.length;
|
|
25
|
-
// 2) 셸/PowerShell 컨텍스트 (UR-0052)
|
|
26
|
-
let shell = null, psVersion = null;
|
|
27
|
-
try { const ctx = _detectShellCtx(); shell = ctx.shell; psVersion = ctx.psVersion; } catch {}
|
|
28
|
-
const mcpCount = (() => { try { return _mcpToolCount(); } catch { return null; } })();
|
|
29
|
-
const report = {
|
|
30
|
-
version: VERSION, node: process.version, platform: process.platform + '/' + process.arch,
|
|
31
|
-
runningFrom: harnessPath, mcpTools: mcpCount,
|
|
32
|
-
selftest: { pass, total, ok: failNames.length === 0, failed: failNames },
|
|
33
|
-
shell, psVersion, healthy: failNames.length === 0
|
|
34
|
-
};
|
|
35
|
-
if (json) { process.stdout.write(JSON.stringify(report, null, 2) + '\n'); if (!report.healthy) process.exitCode = 1; return report; }
|
|
36
|
-
const isTty = process.stdout && process.stdout.isTTY;
|
|
37
|
-
const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s, rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s, cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s, dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
38
|
-
log(cy(`# leerness doctor — 설치/환경 진단`));
|
|
39
|
-
log('');
|
|
40
|
-
log(` ${gr('✓')} version ${VERSION} · node ${process.version} · ${process.platform}/${process.arch}`);
|
|
41
|
-
log(` ${gr('✓')} 설치 경로: ${dm(harnessPath)}`);
|
|
42
|
-
log(` ${gr('✓')} MCP 도구: ${mcpCount}`);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
log('');
|
|
47
|
-
log(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
out.diagnostics.push(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
out.diagnostics.push(
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
log(
|
|
103
|
-
log(
|
|
104
|
-
log(
|
|
105
|
-
log(
|
|
106
|
-
log(
|
|
107
|
-
|
|
108
|
-
if (out.npm.
|
|
109
|
-
if (out.npm.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
log(
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
log(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
log(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
log(
|
|
126
|
-
log(
|
|
127
|
-
log(`
|
|
128
|
-
log(`
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return null;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!exists(changelogPath))
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
log(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
log(
|
|
162
|
-
log(
|
|
163
|
-
log(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
v.
|
|
172
|
-
v.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
if (
|
|
177
|
-
log('');
|
|
178
|
-
log(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
log(
|
|
187
|
-
log(
|
|
188
|
-
log(`
|
|
189
|
-
log(`
|
|
190
|
-
log(`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
1
|
+
// lib/diagnostics.js — 설치/환경 진단 핸들러 (doctor / which).
|
|
2
|
+
// 1.9.392 (UR-0025 큰 핸들러 모듈화 4번째): bin/harness.js 에서 doctor/which 분리.
|
|
3
|
+
// - I/O: ./io(log). node: child_process.
|
|
4
|
+
// - harness 고유 의존(VERSION · _selfTestCases · _detectShellCtx · _mcpToolCount · argv 파서 has · harnessPath)은 deps 주입(DI).
|
|
5
|
+
// _selfTestCases 의 각 case.run 클로저는 harness 스코프를 유지하므로 함수 참조 주입만으로 정상 동작.
|
|
6
|
+
'use strict';
|
|
7
|
+
const cp = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { log, fail, absRoot, exists, read } = require('./io');
|
|
10
|
+
const { parseHarnessVersion, _parseChangelogBetween } = require('./pure-utils');
|
|
11
|
+
|
|
12
|
+
function doctorCmd(opts = {}, deps = {}) {
|
|
13
|
+
const { VERSION, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath } = deps;
|
|
14
|
+
const json = opts.json || has('--json');
|
|
15
|
+
// 1) 코어 무결성: selftest 케이스 인라인 실행
|
|
16
|
+
let pass = 0; const failNames = [];
|
|
17
|
+
try {
|
|
18
|
+
for (const c of _selfTestCases()) {
|
|
19
|
+
let okc = false;
|
|
20
|
+
try { okc = !!c.run(); } catch { okc = false; }
|
|
21
|
+
if (okc) pass++; else failNames.push(c.name);
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
const total = pass + failNames.length;
|
|
25
|
+
// 2) 셸/PowerShell 컨텍스트 (UR-0052)
|
|
26
|
+
let shell = null, psVersion = null;
|
|
27
|
+
try { const ctx = _detectShellCtx(); shell = ctx.shell; psVersion = ctx.psVersion; } catch {}
|
|
28
|
+
const mcpCount = (() => { try { return _mcpToolCount(); } catch { return null; } })();
|
|
29
|
+
const report = {
|
|
30
|
+
version: VERSION, node: process.version, platform: process.platform + '/' + process.arch,
|
|
31
|
+
runningFrom: harnessPath, mcpTools: mcpCount,
|
|
32
|
+
selftest: { pass, total, ok: failNames.length === 0, failed: failNames },
|
|
33
|
+
shell, psVersion, healthy: failNames.length === 0
|
|
34
|
+
};
|
|
35
|
+
if (json) { process.stdout.write(JSON.stringify(report, null, 2) + '\n'); if (!report.healthy) process.exitCode = 1; return report; }
|
|
36
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
37
|
+
const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s, rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s, cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s, dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
38
|
+
log(cy(`# leerness doctor — 설치/환경 진단`));
|
|
39
|
+
log('');
|
|
40
|
+
log(` ${gr('✓')} version ${VERSION} · node ${process.version} · ${process.platform}/${process.arch}`);
|
|
41
|
+
log(` ${gr('✓')} 설치 경로: ${dm(harnessPath)}`);
|
|
42
|
+
log(` ${gr('✓')} MCP 도구: ${mcpCount}`);
|
|
43
|
+
// 1.13.1 (15th 블라인드 리뷰 P3, Sonnet): pass 수에 '실패' 가 붙어 "209/210 실패"(=209건 실패로 오독)되던 문구 → "통과 N/M (K건 실패)" 로 명확화.
|
|
44
|
+
log(` ${report.selftest.ok ? gr('✓') : rd('✗')} selftest: ${pass}/${total} 통과${report.selftest.ok ? '' : ` (${total - pass}건 실패)`}`);
|
|
45
|
+
if (!report.selftest.ok) report.selftest.failed.slice(0, 5).forEach(n => log(rd(` ✗ ${n}`)));
|
|
46
|
+
log(` ${gr('✓')} 셸: ${shell || 'unknown'}${psVersion && shell === 'powershell' ? ` (PowerShell ${psVersion})` : ''}`);
|
|
47
|
+
log('');
|
|
48
|
+
log(report.healthy ? gr(' ✓ leerness 설치 정상') : rd(' ✗ 문제 감지 — 재설치: npm i -g leerness@latest · 진단: leerness which'));
|
|
49
|
+
if (!report.healthy) process.exitCode = 1;
|
|
50
|
+
return report;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function whichCmd(deps = {}) {
|
|
54
|
+
const { VERSION, has, harnessPath } = deps;
|
|
55
|
+
const out = {
|
|
56
|
+
version: VERSION,
|
|
57
|
+
runningFrom: harnessPath,
|
|
58
|
+
nodeVersion: process.version,
|
|
59
|
+
platform: process.platform,
|
|
60
|
+
arch: process.arch,
|
|
61
|
+
npm: {},
|
|
62
|
+
pathCandidates: []
|
|
63
|
+
};
|
|
64
|
+
// npm root -g (글로벌 설치 경로)
|
|
65
|
+
try {
|
|
66
|
+
const r = cp.spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, shell: true });
|
|
67
|
+
if (r.status === 0) out.npm.globalRoot = (r.stdout || '').trim();
|
|
68
|
+
} catch {}
|
|
69
|
+
// npm cache (npx 캐시 경로)
|
|
70
|
+
try {
|
|
71
|
+
const r = cp.spawnSync('npm', ['config', 'get', 'cache'], { encoding: 'utf8', timeout: 5000, shell: true });
|
|
72
|
+
if (r.status === 0) out.npm.cacheDir = (r.stdout || '').trim();
|
|
73
|
+
} catch {}
|
|
74
|
+
// npm 글로벌 leerness 설치 정보
|
|
75
|
+
try {
|
|
76
|
+
const r = cp.spawnSync('npm', ['ls', '-g', 'leerness', '--depth=0', '--json'], { encoding: 'utf8', timeout: 8000, shell: true });
|
|
77
|
+
if (r.stdout) {
|
|
78
|
+
try {
|
|
79
|
+
const j = JSON.parse(r.stdout);
|
|
80
|
+
if (j.dependencies?.leerness) out.npm.globalInstalled = j.dependencies.leerness.version;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
// PATH 후보 (Windows: where / Unix: which)
|
|
85
|
+
try {
|
|
86
|
+
const isWin = process.platform === 'win32';
|
|
87
|
+
const tool = isWin ? 'where' : 'which';
|
|
88
|
+
const r = cp.spawnSync(tool, ['-a', 'leerness'], { encoding: 'utf8', timeout: 5000, shell: true });
|
|
89
|
+
if (r.stdout) out.pathCandidates = (r.stdout || '').trim().split(/\r?\n/).filter(Boolean);
|
|
90
|
+
} catch {}
|
|
91
|
+
// 진단: 글로벌 설치된 leerness 와 현재 실행 버전이 다르면 경고
|
|
92
|
+
out.diagnostics = [];
|
|
93
|
+
if (out.npm.globalInstalled && out.npm.globalInstalled !== VERSION) {
|
|
94
|
+
out.diagnostics.push(`⚠ 글로벌 설치 ${out.npm.globalInstalled} ≠ 현재 실행 ${VERSION} — npx 캐시 또는 PATH 충돌 의심`);
|
|
95
|
+
out.diagnostics.push(` → 강제 최신: npm i -g leerness@latest / 또는 npx --yes leerness@latest <command>`);
|
|
96
|
+
}
|
|
97
|
+
if (out.pathCandidates.length > 1) {
|
|
98
|
+
out.diagnostics.push(`⚠ PATH 에 leerness 가 ${out.pathCandidates.length}개 — 우선순위 충돌 가능`);
|
|
99
|
+
out.diagnostics.push(` → 명시적 경로 사용: ${out.runningFrom}`);
|
|
100
|
+
}
|
|
101
|
+
if (has('--json')) { log(JSON.stringify(out, null, 2)); return; }
|
|
102
|
+
log(`# leerness which (1.9.164)`);
|
|
103
|
+
log(`현재 실행: ${out.runningFrom}`);
|
|
104
|
+
log(`버전: v${out.version}`);
|
|
105
|
+
log(`Node: ${out.nodeVersion} (${out.platform}/${out.arch})`);
|
|
106
|
+
log('');
|
|
107
|
+
log(`## npm 환경`);
|
|
108
|
+
if (out.npm.globalRoot) log(` npm root -g: ${out.npm.globalRoot}`);
|
|
109
|
+
if (out.npm.cacheDir) log(` npm cache: ${out.npm.cacheDir} (npx 옛 버전이 여기 캐싱 — 의심 시 \`npm cache clean --force\`)`);
|
|
110
|
+
if (out.npm.globalInstalled) log(` 글로벌 설치: leerness@${out.npm.globalInstalled}`);
|
|
111
|
+
else log(` 글로벌 설치: (없음 — npx 또는 로컬 경로만 사용 중)`);
|
|
112
|
+
if (out.pathCandidates.length) {
|
|
113
|
+
log('');
|
|
114
|
+
log(`## PATH 후보 (${out.pathCandidates.length}개)`);
|
|
115
|
+
for (const p of out.pathCandidates) log(` - ${p}`);
|
|
116
|
+
}
|
|
117
|
+
if (out.diagnostics.length) {
|
|
118
|
+
log('');
|
|
119
|
+
log(`## ⚠ 진단`);
|
|
120
|
+
for (const d of out.diagnostics) log(` ${d}`);
|
|
121
|
+
} else {
|
|
122
|
+
log('');
|
|
123
|
+
log(`✓ 충돌 없음 (현재 실행 버전 = 글로벌 설치 버전)`);
|
|
124
|
+
}
|
|
125
|
+
log('');
|
|
126
|
+
log(`💡 강제 최신 실행 방법:`);
|
|
127
|
+
log(` 1) npx --yes leerness@latest <command> # npx 캐시 무시하고 최신 다운로드`);
|
|
128
|
+
log(` 2) npm i -g leerness@latest # 글로벌 설치 갱신`);
|
|
129
|
+
log(` 3) npm cache clean --force # npx 캐시 강제 비우기 (의심 시)`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 1.9.394 (UR-0025): whats-new — 현재 워크스페이스 버전 → 도구 버전 CHANGELOG 차분(신규 명령/플래그/파일 요약). introspection 핸들러.
|
|
133
|
+
// 순수 파서 _parseChangelogBetween(pure-utils) 사용. deps: VERSION/arg/has. CHANGELOG 경로는 root 우선, 없으면 pkg 자체(lib/../).
|
|
134
|
+
function whatsNewCmd(root, deps = {}) {
|
|
135
|
+
const { VERSION, arg, has } = deps;
|
|
136
|
+
root = absRoot(root || process.cwd());
|
|
137
|
+
const fromV = arg('--from', null) || (function () {
|
|
138
|
+
const hv = path.join(root, '.harness', 'HARNESS_VERSION');
|
|
139
|
+
if (exists(hv)) { try { return parseHarnessVersion(read(hv)).base || parseHarnessVersion(read(hv)).plus; } catch { return null; } }
|
|
140
|
+
return null;
|
|
141
|
+
})();
|
|
142
|
+
const toV = arg('--to', null) || VERSION;
|
|
143
|
+
if (!fromV) {
|
|
144
|
+
fail('현재 버전을 파악할 수 없습니다. --from <version> 명시');
|
|
145
|
+
return process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
// CHANGELOG.md — 우선 root, 없으면 leerness-pkg 자체 (lib/../CHANGELOG.md = pkg 루트)
|
|
148
|
+
let changelogPath = path.join(root, 'CHANGELOG.md');
|
|
149
|
+
if (!exists(changelogPath)) changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
|
|
150
|
+
if (!exists(changelogPath)) {
|
|
151
|
+
fail('CHANGELOG.md 없음');
|
|
152
|
+
return process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
const diff = _parseChangelogBetween(read(changelogPath), fromV, toV);
|
|
155
|
+
if (has('--json')) { log(JSON.stringify({ from: fromV, to: toV, versions: diff }, null, 2)); return; }
|
|
156
|
+
if (!diff.length) {
|
|
157
|
+
log(`# leerness whats-new (1.9.41)`);
|
|
158
|
+
log(`현재 ${fromV} ↔ 대상 ${toV}: 새 항목 없음 (또는 CHANGELOG에 기록 안 됨)`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
log(`# leerness whats-new (1.9.41)`);
|
|
162
|
+
log(`현재 워크스페이스 버전: ${fromV} → 대상: ${toV}`);
|
|
163
|
+
log(`범위: ${diff.length}개 버전 (${diff[0].version} → ${diff[diff.length - 1].version})`);
|
|
164
|
+
log('');
|
|
165
|
+
// AI 가독 요약 — 각 버전당 한 줄 + 신규 명령/플래그/파일
|
|
166
|
+
log(`## 🆕 신규 명령·플래그·파일 (AI 에이전트는 다음 명령을 우선 시도)`);
|
|
167
|
+
const allCommands = new Set();
|
|
168
|
+
const allFlags = new Set();
|
|
169
|
+
const allFiles = new Set();
|
|
170
|
+
for (const v of diff) {
|
|
171
|
+
v.newCommands.forEach(c => allCommands.add(c));
|
|
172
|
+
v.newFlags.forEach(f => allFlags.add(f));
|
|
173
|
+
v.newFiles.forEach(f => allFiles.add(f));
|
|
174
|
+
}
|
|
175
|
+
if (allCommands.size) log(` 📌 신규 명령: ${[...allCommands].join(', ')}`);
|
|
176
|
+
if (allFlags.size) log(` 🚩 신규 플래그: ${[...allFlags].join(', ')}`);
|
|
177
|
+
if (allFiles.size) log(` 📄 신규 파일: ${[...allFiles].join(', ')}`);
|
|
178
|
+
log('');
|
|
179
|
+
log(`## 📜 버전별 헤드라인`);
|
|
180
|
+
for (const v of diff) {
|
|
181
|
+
// body 첫 줄(또는 strong header) 추출
|
|
182
|
+
const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
|
|
183
|
+
|| (v.body.split('\n').find(l => l.trim() && !l.startsWith('##')) || '').trim().slice(0, 120);
|
|
184
|
+
log(` • ${v.version}${v.date ? ` (${v.date})` : ''} — ${firstLine || '(no headline)'}`);
|
|
185
|
+
}
|
|
186
|
+
log('');
|
|
187
|
+
log(`## 💡 권장 행동`);
|
|
188
|
+
log(` 1. 위 신규 명령들을 시도해 보세요 (예: \`leerness <명령> --help\`)`);
|
|
189
|
+
log(` 2. 신규 파일들을 읽어 보세요 (예: \`cat .harness/session-workflow.md\`)`);
|
|
190
|
+
log(` 3. AGENTS.md/CLAUDE.md 재독 — migrate가 인스트럭션을 업데이트했을 수 있음`);
|
|
191
|
+
log(` 4. 상세: \`cat CHANGELOG.md\` 또는 \`leerness whats-new --json\``);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { doctorCmd, whichCmd, whatsNewCmd };
|