leerness 1.9.37 → 1.9.38
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 +35 -0
- package/README.md +245 -340
- package/bin/harness.js +176 -30
- package/package.json +1 -1
- package/scripts/e2e.js +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.38 — 2026-05-18
|
|
4
|
+
|
|
5
|
+
**drift 자동 reminder + 사용 통계 + TodoWrite 임포트 + drift 임계 학습**.
|
|
6
|
+
|
|
7
|
+
1.9.37의 drift detection을 더 능동적으로 만든 라운드. 메인 에이전트가 leerness를 "잊는" 시나리오를 4가지 채널로 보완.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **(A) `.harness/agent-reminders.md` 자동 생성** — drift 5일 이상(severe) 시 handoff 진입부에서 자동 생성. 메인 에이전트가 다음 라운드 시작 시 이 파일을 읽고 session close를 잊지 않도록.
|
|
12
|
+
- drift 회복 시 (handoff/session close) 파일 자동 청소
|
|
13
|
+
- **(B) `leerness usage stats`** 신규 명령 — `.harness/cache/usage-stats.json`에 명령별 누적 카운터 + drift 통계. `--json` 출력 지원.
|
|
14
|
+
- 매 명령 호출 시 자동 누적 (`_bumpUsage`)
|
|
15
|
+
- 통계 출력: 호출 수 상위 30 + drift critical 발견/skip/자동 해소 카운트
|
|
16
|
+
- **(C) `leerness task sync --from <todo.json>`** 신규 명령 — TodoWrite JSON을 leerness progress-tracker로 import. completed → done, in_progress → in-progress, pending → planned 매핑.
|
|
17
|
+
- 같은 content가 이미 있으면 status만 update, 없으면 신규 task 생성
|
|
18
|
+
- `--json` 출력 지원
|
|
19
|
+
- **(D) drift 임계 학습** — `--no-drift-check` 누적 ≥5회 시 stale 임계 2일 → 4일로 자동 완화 (false alarm 감소).
|
|
20
|
+
- usage-stats.json의 `drift.skipped` 카운터로 추적
|
|
21
|
+
- 학습된 임계 활성 시 handoff 출력에 "(학습: skip N회 누적 → 임계 N일 완화)" 안내
|
|
22
|
+
|
|
23
|
+
### 실측
|
|
24
|
+
- 실 워크스페이스에서 4 기능 모두 작동 확인:
|
|
25
|
+
- A: 5일 stale 시뮬 → agent-reminders.md 자동 생성 (drift critical 메시지)
|
|
26
|
+
- B: status/handoff/task 명령 자동 카운트
|
|
27
|
+
- C: 2건 TodoWrite JSON → 2건 progress-tracker import
|
|
28
|
+
- D: --no-drift-check 5회 누적 → drift.skipped=5 기록
|
|
29
|
+
|
|
30
|
+
### e2e: 174/174 PASS (1.9.37 170 + 신규 4)
|
|
31
|
+
|
|
32
|
+
### 정책
|
|
33
|
+
- ✅ 자동 reminder는 *파일 생성*만 — 메인 에이전트 자동 실행 강제 X
|
|
34
|
+
- ✅ usage stats는 read-only 추적, destructive 동작 X
|
|
35
|
+
- ✅ task sync는 idempotent (같은 content는 update만)
|
|
36
|
+
- ✅ drift 학습은 사용자 친화 (자주 끄면 덜 짖게)
|
|
37
|
+
|
|
3
38
|
## 1.9.37 — 2026-05-18
|
|
4
39
|
|
|
5
40
|
**메인 에이전트의 "leerness 점점 안 쓰는" drift 현상 자동 감지·경고**.
|
package/README.md
CHANGED
|
@@ -1,331 +1,268 @@
|
|
|
1
1
|
# Leerness
|
|
2
2
|
|
|
3
|
-
> **AI
|
|
3
|
+
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []()
|
|
8
6
|
|
|
9
7
|
```
|
|
10
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
11
|
-
║ ║
|
|
12
9
|
║ ██╗ ███████╗███████╗██████╗ ███╗ ██╗███████╗███████╗ ║
|
|
13
10
|
║ ██║ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔════╝██╔════╝ ║
|
|
14
11
|
║ ██║ █████╗ █████╗ ██████╔╝██╔██╗ ██║█████╗ ███████╗ ║
|
|
15
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
16
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
17
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
18
|
-
║
|
|
19
|
-
║
|
|
20
|
-
║ ★ verify · reuse-map · handoff · agents · orchestrate ║
|
|
21
|
-
║ ║
|
|
15
|
+
║ v1.9.38 AI Agent Reliability Harness ║
|
|
16
|
+
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
22
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
23
18
|
```
|
|
24
19
|
|
|
25
20
|
---
|
|
26
21
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
AI 에이전트(Claude Code, Cursor, Copilot, Codex, Gemini CLI 등)는 **빠르게 코드를 쓰지만, 다음 함정에 반복해서 빠집니다**:
|
|
30
|
-
|
|
31
|
-
| AI와 함께 일할 때 흔한 함정 | leerness의 해결책 |
|
|
32
|
-
|---|---|
|
|
33
|
-
| 🚨 "완료했습니다"라고 했지만 실제로 코드에 변경이 없음 | `verify-claim --run-tests` — 증거 파일 존재 + 테스트 실제 실행 검증 |
|
|
34
|
-
| 🚨 "API 호출 완료" 라고 했지만 코드에 URL이 없음 | `optimism-check` — 10 카테고리 패턴 + URL 매핑 + 신뢰도 점수 |
|
|
35
|
-
| 🚨 같은 함수를 여러 프로젝트에 중복 생성 | `reuse-map --all-apps --strict-elements` — 함수명 fuzzy 중복 감지 |
|
|
36
|
-
| 🚨 다음 세션이 컨텍스트를 잃음 | `handoff` — plan/progress/decisions 3채널 자동 적재 + `--compact` (500자) |
|
|
37
|
-
| 🚨 표면적 코드 리뷰 (도메인 깊이 부족) | `review --persona security,performance,ux` — 5 도메인 sub-agent 자동 부여 |
|
|
38
|
-
| 🚨 외부 AI CLI를 자동 호출하면 위험 | `agents list/dispatch/quota` — 환경변수 활성화 + 명시적 분배 (자동 호출 X) |
|
|
39
|
-
| 🚨 npx 캐시로 옛 버전이 실행됨 | `_warnIfStale` — install 시 npm latest 자동 비교 + 경고 (1.9.33+) |
|
|
40
|
-
| 🚨 멀티 sub-agent가 같은 파일을 동시에 써서 작업 손실 | 프롬프트 템플릿에 **파일 경로 격리 의무**, `agents dispatch` 안내문이 자동 추가 (1.9.34/35) |
|
|
41
|
-
| 🚨 sub-agent마다 사양 해석이 달라 통합 시 BUG 양산 | **`leerness contract verify <spec.md> <impl.js>`** — 명세 ↔ 구현 함수/필드 자동 검사 (1.9.35) |
|
|
42
|
-
| 🚨 신규 모듈의 capability가 reuse-map에 등록 안 됨 | **`leerness reuse autodetect`** — `module.exports` 스캔 + 자동 등록 (1.9.35) |
|
|
43
|
-
| 🚨 신규 디렉토리에서 sub-agent가 컨텍스트 없이 작업 시작 | `handoff` 호출 시 **.harness 부재 자동 경고** (1.9.35) |
|
|
44
|
-
| 🚨 audit warning이 쌓이지만 수동 fix가 번거로움 | **`audit --fix`** — session-handoff/current-state 자동 갱신 (1.9.35) |
|
|
45
|
-
| 🚨 **프로젝트가 길어질수록 메인 에이전트가 leerness를 점점 안 씀** (메타파일 stale, session close 누락) | **`leerness drift check`** — 4개 신호로 staleness 측정 + handoff 시 자동 경고 (1.9.37) |
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## ⚙️ 설치 (Install)
|
|
50
|
-
|
|
51
|
-
> ⚠️ **leerness는 CLI 도구입니다.** `npm i leerness`만으로는 명령이 PATH에 없습니다. 아래 셋 중 하나:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# ✅ 1) 추천 — 설치 없이 즉시 실행 (항상 @latest 명시)
|
|
55
|
-
npx leerness@latest init . --language ko --skills recommended
|
|
56
|
-
|
|
57
|
-
# ✅ 2) 전역 설치 (한 번이면 끝)
|
|
58
|
-
npm i -g leerness@latest && leerness --version
|
|
59
|
-
|
|
60
|
-
# ✅ 3) 로컬 dev dependency
|
|
61
|
-
npm i --save-dev leerness && npx leerness handoff .
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 🚨 npx 캐시 함정 (1.9.33부터 자동 경고)
|
|
65
|
-
|
|
66
|
-
`@latest` 없이 `npx leerness init`만 실행하면 **PC에 캐시된 옛 버전이 무한 재사용**됩니다.
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
# ❌ 위험 — 캐시된 옛 버전 실행 가능
|
|
70
|
-
npx leerness init .
|
|
22
|
+
## leerness는 무엇인가요?
|
|
71
23
|
|
|
72
|
-
|
|
73
|
-
npx leerness@latest init .
|
|
74
|
-
|
|
75
|
-
# ✅ 캐시 의심 시 비우고 다시
|
|
76
|
-
npx --yes clear-npx-cache && npx --yes leerness@latest init .
|
|
77
|
-
```
|
|
24
|
+
Claude Code · Cursor · Copilot · Codex · Gemini CLI 같은 **AI 코딩 에이전트와 함께 일할 때 발생하는 구조적 문제**를 자동으로 막는 CLI 하네스입니다.
|
|
78
25
|
|
|
79
|
-
|
|
26
|
+
AI 에이전트는 빠르게 코드를 쓰지만, 다음 **5가지 함정**에 반복해서 빠집니다:
|
|
80
27
|
|
|
81
28
|
```
|
|
82
|
-
|
|
83
|
-
|
|
29
|
+
1. 거짓 완료 — "구현했습니다"는데 코드에 변경이 없거나 테스트 미실행
|
|
30
|
+
2. 중복 생성 — 이미 있는 함수를 다른 프로젝트에 또 만듦
|
|
31
|
+
3. 망각 — 다음 세션에서 컨텍스트(plan/decisions)를 잃음
|
|
32
|
+
4. 충돌 — 멀티 에이전트가 같은 파일을 동시에 써서 작업 손실
|
|
33
|
+
5. 도구 drift — 시간이 지날수록 메인 에이전트가 도구·메타파일을 잊음
|
|
84
34
|
```
|
|
85
35
|
|
|
86
|
-
|
|
36
|
+
leerness는 이 5가지를 모두 **자동으로 감지·차단·회복**합니다.
|
|
87
37
|
|
|
88
38
|
---
|
|
89
39
|
|
|
90
|
-
##
|
|
40
|
+
## 어떻게 작동하나요?
|
|
91
41
|
|
|
92
|
-
|
|
42
|
+
### 1. 워크스페이스에 4개 채널을 만듭니다
|
|
93
43
|
|
|
94
44
|
```
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
설치할 스킬 라이브러리 (Space=토글, a=전체, n=해제, Enter=확정)
|
|
102
|
-
↑↓ 이동, Space 토글, a 전체, n 해제, Enter 확정
|
|
103
|
-
❯ ◉ office — 마이크로소프트 오피스 자동화
|
|
104
|
-
◉ commerce-api — 커머스 API 연동 (쿠팡/롯데온/스마트스토어)
|
|
105
|
-
◯ crawling — Playwright 기반 크롤링·브라우저 자동화
|
|
106
|
-
◉ ai-verified-skill-publisher — AI 검증 스킬 업로드·라이브러리화
|
|
107
|
-
...
|
|
108
|
-
|
|
109
|
-
활성화할 sub-agent CLI (Space=토글, a=전체, n=해제, Enter=확정)
|
|
110
|
-
❯ ◉ claude — 🟢 설치됨 · Anthropic Claude Code CLI
|
|
111
|
-
◯ codex — ⚪ 미설치 · OpenAI Codex CLI (격리 sandbox)
|
|
112
|
-
◉ gemini — 🟢 설치됨 · Google Gemini CLI
|
|
113
|
-
◯ copilot — ⚪ 미설치 · GitHub Copilot CLI
|
|
45
|
+
.harness/
|
|
46
|
+
├── plan.md ← 무엇을 할 것인가
|
|
47
|
+
├── progress-tracker.md ← 무엇을 했는가 (증거 포함)
|
|
48
|
+
├── decisions.md ← 왜 그렇게 했는가
|
|
49
|
+
└── session-handoff.md ← 다음 세션에 무엇을 전달하는가
|
|
114
50
|
```
|
|
115
51
|
|
|
116
|
-
|
|
52
|
+
이 4개 파일은 사용자 메모리로 보호됩니다. leerness가 자동 업데이트하지만, 사용자/AI가 직접 편집한 내용은 **항상 보존**됩니다.
|
|
53
|
+
|
|
54
|
+
### 2. AI가 작업할 때마다 자동 검증
|
|
55
|
+
|
|
117
56
|
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
57
|
+
AI: "T-0042 API 호출 구현 완료했습니다"
|
|
58
|
+
↓
|
|
59
|
+
leerness verify-claim T-0042 --run-tests
|
|
60
|
+
↓
|
|
61
|
+
evidence 파일 확인 → 테스트 실제 실행 → URL 코드 매칭
|
|
62
|
+
↓
|
|
63
|
+
✓ 검증 통과 또는 ✗ "코드에 fetch 호출 흔적 없음 (confidence 0.85)"
|
|
123
64
|
```
|
|
124
65
|
|
|
125
|
-
|
|
126
|
-
- `--yes` — 모든 prompt 스킵 (기본값 사용)
|
|
127
|
-
- `--no-interactive-select` 또는 `LEERNESS_NO_INTERACTIVE=1` — 구식 숫자 prompt
|
|
128
|
-
- `LEERNESS_NO_PROMPT=1` — readline prompt 강제 비활성
|
|
66
|
+
### 3. 워크스페이스 전체에서 중복·관계 추적
|
|
129
67
|
|
|
130
|
-
|
|
68
|
+
```bash
|
|
69
|
+
leerness reuse-map --all-apps --strict-elements
|
|
70
|
+
→ escapeHtml in 3 projects (project-a:42, project-b:18, project-c:103)
|
|
71
|
+
→ useCart depends on AuthContext (cycle detected)
|
|
72
|
+
```
|
|
131
73
|
|
|
132
|
-
|
|
74
|
+
### 4. 다중 AI 에이전트 안전 협업
|
|
133
75
|
|
|
134
76
|
```bash
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
npx leerness handoff . # 컨텍스트 적재
|
|
140
|
-
npx leerness audit . # 일관성 감사
|
|
141
|
-
npx leerness verify-claim T-0001 --run-tests # 증거 자동 검증
|
|
142
|
-
npx leerness session close . # 세션 마감 + 핸드오프
|
|
143
|
-
|
|
144
|
-
# 3) 워크스페이스 (멀티 프로젝트)
|
|
145
|
-
npx leerness handoff --all-apps --since 24h # 최근 24h 변경 통합 뷰
|
|
146
|
-
npx leerness reuse-map --all-apps # 중복 함수 감지
|
|
147
|
-
|
|
148
|
-
# 4) 외부 AI CLI 오케스트레이션
|
|
149
|
-
npx leerness setup-agents # 인터랙티브 CLI 활성화
|
|
150
|
-
npx leerness agents list # ready 상태표
|
|
151
|
-
npx leerness agents dispatch "X 해줘" --to gemini # 명령 자동 생성
|
|
77
|
+
leerness agents list # claude/codex/gemini/copilot 상태
|
|
78
|
+
leerness agents dispatch "<task>" --to gemini --write # 권장 플래그 자동 첨부
|
|
79
|
+
leerness agents bench "<task>" # 3 CLI 동시 호출 + 시간/품질 비교
|
|
80
|
+
leerness contract verify SPEC.md src/x.js # 사양 ↔ 구현 함수/필드 일치 검증
|
|
152
81
|
```
|
|
153
82
|
|
|
154
|
-
|
|
83
|
+
### 5. 도구 drift 자동 감지 (1.9.37+)
|
|
155
84
|
|
|
156
|
-
|
|
85
|
+
라운드가 길어지면 AI가 leerness를 "점점 안 쓰는" 현상을 자동 감지:
|
|
157
86
|
|
|
158
|
-
|
|
87
|
+
```bash
|
|
88
|
+
leerness drift check .
|
|
89
|
+
→ 🔴 critical (100/200) — session close 4.6일 누락, task update 3.6일 stale
|
|
90
|
+
→ 권장: leerness session close .
|
|
91
|
+
```
|
|
159
92
|
|
|
160
|
-
|
|
161
|
-
|---|---:|---:|---:|
|
|
162
|
-
| 멀티에이전트 효율 | 100/100 | 3/100 | **+97** |
|
|
163
|
-
| 검수 자동화 (verify-claim 1.5s vs 90s 수동) | 98/100 | 0/100 | **+98** |
|
|
164
|
-
| 재사용 인식 (113 cap + 33 deps) | 100/100 | 0/100 | **+100** |
|
|
165
|
-
| 워크스페이스 가시성 (1 명령 vs 112 명령) | 99/100 | 0/100 | **+99** |
|
|
166
|
-
| 버그 자동 감지 (169 신호) | 100/100 | 0/100 | **+100** |
|
|
167
|
-
| 컨텍스트 유지 (3 채널) | 100/100 | 0/100 | **+100** |
|
|
168
|
-
| **종합** | **597/600 (99%)** | **3/600 (0.5%)** | **+594** |
|
|
93
|
+
→ `handoff` 호출 시 inline 경고 자동, severe 시 `.harness/agent-reminders.md` 자동 생성.
|
|
169
94
|
|
|
170
95
|
---
|
|
171
96
|
|
|
172
|
-
##
|
|
173
|
-
|
|
174
|
-
claude/codex/gemini/copilot CLI들을 sub-agent로 활용. **자동 호출 금지** — 환경변수 활성화 + PATH 존재 시에만 ready.
|
|
97
|
+
## 60초 시작
|
|
175
98
|
|
|
176
|
-
### 활성화 (1.9.32+ 권장: 인터랙티브)
|
|
177
99
|
```bash
|
|
178
|
-
|
|
179
|
-
|
|
100
|
+
# 1) 설치 + 초기화 (방향키/스페이스 인터랙티브)
|
|
101
|
+
npx leerness@latest init .
|
|
180
102
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
LEERNESS_ENABLE_CLAUDE=1 # Anthropic Claude Code CLI
|
|
184
|
-
LEERNESS_ENABLE_CODEX=1 # OpenAI Codex CLI (격리 sandbox)
|
|
185
|
-
LEERNESS_ENABLE_GEMINI=1 # Google Gemini CLI (--yolo 워크스페이스 직접 수정 가능)
|
|
186
|
-
LEERNESS_ENABLE_COPILOT=1 # GitHub Copilot CLI (gh copilot)
|
|
187
|
-
```
|
|
103
|
+
# 2) AI 세션 시작 — 컨텍스트 자동 적재
|
|
104
|
+
npx leerness handoff .
|
|
188
105
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
leerness
|
|
106
|
+
# 3) AI 작업 끝 — 증거 자동 검증
|
|
107
|
+
npx leerness verify-claim T-0001 --run-tests
|
|
108
|
+
|
|
109
|
+
# 4) 세션 마감 — 다음 세션 인계
|
|
110
|
+
npx leerness session close .
|
|
194
111
|
```
|
|
195
112
|
|
|
196
|
-
|
|
197
|
-
| CLI | install 명령 | 안내 |
|
|
198
|
-
|---|---|---|
|
|
199
|
-
| claude | `npm i -g @anthropic-ai/claude-code` | https://docs.anthropic.com/en/docs/claude-code/setup |
|
|
200
|
-
| codex | `npm i -g @openai/codex` | https://github.com/openai/codex |
|
|
201
|
-
| gemini | `npm i -g @google/gemini-cli` | https://github.com/google-gemini/gemini-cli |
|
|
202
|
-
| copilot | `gh extension install github/gh-copilot` | https://github.com/github/gh-copilot |
|
|
113
|
+
---
|
|
203
114
|
|
|
204
|
-
|
|
115
|
+
## 적용시 효과 — 정량 데이터
|
|
205
116
|
|
|
206
|
-
|
|
117
|
+
`leerness-bench` 28 프로젝트 124 task 측정 결과:
|
|
207
118
|
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
|
|
|
211
|
-
|
|
|
212
|
-
|
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
119
|
+
| 카테고리 | 적용 | 미적용 | 개선 |
|
|
120
|
+
|---|---:|---:|---:|
|
|
121
|
+
| 다중 에이전트 효율 | 100/100 | 3/100 | **+97** |
|
|
122
|
+
| 자동 검수 (verify-claim) | 98/100 | 0/100 | +98 |
|
|
123
|
+
| 재사용 인식 | 100/100 | 0/100 | +100 |
|
|
124
|
+
| 워크스페이스 가시성 | 99/100 | 0/100 | +99 |
|
|
125
|
+
| 자동 BUG 감지 | 100/100 | 0/100 | +100 |
|
|
126
|
+
| 컨텍스트 유지 | 100/100 | 0/100 | +100 |
|
|
127
|
+
| **종합** | **597/600 (99%)** | 3/600 (0.5%) | **+594** |
|
|
128
|
+
|
|
129
|
+
### 실제 작업 시간 절감
|
|
130
|
+
- **수동 검수 90s → 자동 1.5s** (`verify-claim --run-tests`)
|
|
131
|
+
- **워크스페이스 28 프로젝트 1 명령** (vs 112 개별 명령)
|
|
132
|
+
- **컨텍스트 적재 500자** (`handoff --compact`, AI 토큰 비용 90% 절감)
|
|
133
|
+
|
|
134
|
+
### 실제 BUG 자동 발견 사례
|
|
135
|
+
- **거짓 완료**: 5건 (모두 verify-claim에서 evidence 누락 감지)
|
|
136
|
+
- **사양 불일치**: rpg-replay에서 `tick.damage` vs `tick.amount` 필드명 충돌 자동 감지 (contract verify)
|
|
137
|
+
- **보안 위험**: `contract verify`가 require()로 임의 코드 실행 → 정적 분석으로 즉시 수정 (1.9.36)
|
|
138
|
+
- **drift**: 실 워크스페이스에서 4.6일 stale 자동 감지 → session close 1회로 70점 회복 (1.9.37/38)
|
|
227
139
|
|
|
228
140
|
---
|
|
229
141
|
|
|
230
|
-
##
|
|
231
|
-
|
|
232
|
-
도메인 페르소나를 sub-agent에 자동 부여 → 표면 리뷰 대비 **도메인 발견 3-4배**, 토큰 비용 ~3%.
|
|
142
|
+
## 어떤 함정을 어떻게 막나요?
|
|
233
143
|
|
|
234
|
-
|
|
235
|
-
| ID | 역할 | 발견 깊이 |
|
|
144
|
+
| AI와 일할 때 함정 | leerness 도구 | 효과 |
|
|
236
145
|
|---|---|---|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
146
|
+
| "완료했습니다"인데 코드 변경이 없음 | `verify-claim --run-tests` | 증거 파일 + 테스트 실제 실행 검증 |
|
|
147
|
+
| "API 호출 완료"인데 URL 코드 없음 | `optimism-check` | 10 카테고리 패턴 + URL 매핑 + 신뢰도 점수 |
|
|
148
|
+
| 같은 함수를 여러 프로젝트에 중복 | `reuse-map --strict-elements` | 함수명 fuzzy 중복 감지 |
|
|
149
|
+
| 다음 세션이 컨텍스트 잃음 | `handoff` 3채널 자동 적재 | 500자 압축 (`--compact`) |
|
|
150
|
+
| 표면적 코드 리뷰 (도메인 깊이 부족) | `review --persona security,performance,ux` | 도메인 sub-agent 자동 부여 |
|
|
151
|
+
| 외부 AI CLI 자동 호출 위험 | `agents list/dispatch/quota` | 환경변수 활성화 + 명시적 분배 |
|
|
152
|
+
| npx 캐시로 옛 버전 실행 | `_warnIfStale` 자동 (1.9.33+) | npm latest 자동 비교 + 경고 |
|
|
153
|
+
| 멀티 sub-agent 파일 충돌 | `agents dispatch` 안내 + 경로 격리 | last-writer-wins 위험 사전 차단 |
|
|
154
|
+
| sub-agent마다 사양 해석 다름 | **`contract verify`** | 명세 ↔ 구현 함수/필드 자동 검사 |
|
|
155
|
+
| 신규 모듈 capability 미등록 | **`reuse autodetect`** | `module.exports` 정적 분석 + 자동 등록 |
|
|
156
|
+
| 라운드 길어지며 메인이 leerness 잊음 | **`drift check`** + `agent-reminders.md` 자동 | 4 신호 + 자동 reminder + 학습 |
|
|
157
|
+
| TodoWrite ↔ progress-tracker 분리 | **`task sync --from`** (1.9.38) | TodoWrite JSON 자동 import |
|
|
248
158
|
|
|
249
159
|
---
|
|
250
160
|
|
|
251
|
-
##
|
|
161
|
+
## 핵심 명령
|
|
252
162
|
|
|
253
|
-
### 일상
|
|
163
|
+
### 일상
|
|
254
164
|
```bash
|
|
255
|
-
leerness init [path] [--language
|
|
256
|
-
leerness handoff [path] [--compact]
|
|
257
|
-
leerness
|
|
258
|
-
leerness
|
|
259
|
-
leerness
|
|
260
|
-
leerness
|
|
261
|
-
leerness
|
|
165
|
+
leerness init [path] [--language ko|en] # 신규 프로젝트 (방향키 multi-select)
|
|
166
|
+
leerness handoff [path] [--compact] # 컨텍스트 적재 (drift 자동 경고)
|
|
167
|
+
leerness verify-claim T-XXX --run-tests # 증거 + 실 테스트
|
|
168
|
+
leerness optimism-check T-XXX # 낙관적 표시 감지
|
|
169
|
+
leerness audit [path] [--fix] # 일관성 감사 (--fix: 자동 갱신)
|
|
170
|
+
leerness session close [path] # 세션 마감
|
|
171
|
+
leerness drift check [path] # drift 점수 + 4단계 레벨
|
|
172
|
+
leerness usage stats [path] # 명령별 누적 카운터
|
|
262
173
|
```
|
|
263
174
|
|
|
264
|
-
### 워크스페이스
|
|
175
|
+
### 워크스페이스 (멀티 프로젝트)
|
|
265
176
|
```bash
|
|
266
|
-
leerness handoff --all-apps
|
|
267
|
-
leerness reuse-map --all-apps --strict-elements
|
|
268
|
-
leerness
|
|
269
|
-
leerness
|
|
270
|
-
leerness
|
|
271
|
-
leerness
|
|
177
|
+
leerness handoff --all-apps # 전 프로젝트 통합 뷰
|
|
178
|
+
leerness reuse-map --all-apps --strict-elements
|
|
179
|
+
leerness reuse autodetect [--apply] # src/bin/lib/app 정적 스캔 + 등록
|
|
180
|
+
leerness contract verify <spec> <impl> # 명세 ↔ 구현 검증
|
|
181
|
+
leerness deps <capability> --run-tests # 영향 추적 + 자동 회귀
|
|
182
|
+
leerness retro --all-apps --days 7 # 회고
|
|
183
|
+
leerness insights --all-apps # 통계
|
|
184
|
+
leerness task sync --from <todo.json> # TodoWrite import
|
|
272
185
|
```
|
|
273
186
|
|
|
274
|
-
### 외부 AI CLI · 멀티 에이전트
|
|
187
|
+
### 외부 AI CLI · 멀티 에이전트
|
|
275
188
|
```bash
|
|
276
|
-
leerness setup-agents
|
|
277
|
-
leerness agents list
|
|
278
|
-
leerness agents
|
|
279
|
-
leerness agents
|
|
280
|
-
leerness agents bench "<task>" [--timeout N] # 1.9.36 ready CLI 모두 병렬 호출 + 비교표
|
|
281
|
-
leerness contract verify <spec.md> <impl.js> # 1.9.35/36 명세 ↔ 구현 일치 검사 (정적 분석)
|
|
282
|
-
leerness reuse autodetect [path] [--apply] # 1.9.35/36 capability 자동 등록 (src/bin/lib/app)
|
|
283
|
-
leerness audit . --fix # 1.9.35 누락 메타 자동 갱신
|
|
189
|
+
leerness setup-agents # 인터랙티브 활성화 + 자동 설치
|
|
190
|
+
leerness agents list / check / quota # 상태/한도
|
|
191
|
+
leerness agents dispatch "<task>" --to gemini --write
|
|
192
|
+
leerness agents bench "<task>" # 3 CLI 동시 호출 + 비교표
|
|
284
193
|
```
|
|
285
194
|
|
|
286
|
-
### 페르소나·리뷰
|
|
195
|
+
### 페르소나·리뷰
|
|
287
196
|
```bash
|
|
288
|
-
leerness persona list
|
|
197
|
+
leerness persona list / show <id> / add <id>
|
|
289
198
|
leerness review <file> --persona security,performance,ux
|
|
290
199
|
```
|
|
291
200
|
|
|
292
|
-
###
|
|
201
|
+
### 보안·인코딩
|
|
293
202
|
```bash
|
|
294
203
|
leerness scan secrets . # AWS/GitHub/OpenAI/Anthropic/Google/Slack/PEM
|
|
295
204
|
leerness encoding check . # BOM/UTF-16/한글 라운드트립
|
|
296
205
|
leerness lazy detect . # 증거 없는 done · 빈 handoff · 추적 없는 TODO
|
|
206
|
+
leerness gate [path] # verify + audit + scan + encoding + lazy 통합
|
|
297
207
|
```
|
|
298
208
|
|
|
299
209
|
### 버전 관리
|
|
300
210
|
```bash
|
|
301
211
|
leerness update --check # 24h 캐시로 새 버전 감지
|
|
302
212
|
leerness update --yes # 자동 마이그레이션 + 검증
|
|
303
|
-
leerness update --from <tgz> # 오프라인/사내 미러
|
|
304
213
|
```
|
|
305
214
|
|
|
306
215
|
---
|
|
307
216
|
|
|
308
|
-
##
|
|
217
|
+
## 사용 시나리오
|
|
309
218
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
219
|
+
### 시나리오 1: AI에게 작업 시키고 거짓 완료 검증
|
|
220
|
+
```bash
|
|
221
|
+
# AI에게 작업 지시 후
|
|
222
|
+
leerness verify-claim T-0042 --run-tests --strict-claims
|
|
223
|
+
# → evidence 파일 존재 + 실제 npm test 실행 + 낙관적 표시 자동 감지
|
|
224
|
+
# → 거짓이면 exit 1, 진짜면 진행
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 시나리오 2: 멀티 AI 에이전트 협업
|
|
228
|
+
```bash
|
|
229
|
+
# 1) 외부 CLI 활성화
|
|
230
|
+
leerness setup-agents . # 방향키로 claude/codex/gemini 선택
|
|
231
|
+
|
|
232
|
+
# 2) 같은 task를 3 CLI에 동시 호출 → 비교
|
|
233
|
+
leerness agents bench "rpg-core/combat.js useSkill 함수 한 줄 요약"
|
|
234
|
+
# | claude | 7s | "..." | ← 가장 빠름
|
|
235
|
+
# | codex | 21s | "..." | ← 가장 상세
|
|
236
|
+
# | gemini | 19s | "..." | ← 중간
|
|
237
|
+
|
|
238
|
+
# 3) 작업 유형별 최적 CLI에 분배
|
|
239
|
+
leerness agents dispatch "파일 생성" --to gemini --write
|
|
240
|
+
# → --yolo 자동 첨부, 안전 규칙 안내
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 시나리오 3: 라운드가 길어지며 drift 감지
|
|
244
|
+
```bash
|
|
245
|
+
# 며칠 후 새 세션 시작
|
|
246
|
+
leerness handoff .
|
|
247
|
+
# ⚠ leerness drift 감지 — 메타파일이 stale합니다
|
|
248
|
+
# session-handoff.md: 4.6일 stale
|
|
249
|
+
# → 권장: leerness session close .
|
|
250
|
+
|
|
251
|
+
# 권장 명령 1회 실행으로 회복
|
|
252
|
+
leerness session close .
|
|
253
|
+
# 🔴 critical (100/200) → 🟠 attention (30/200)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 시나리오 4: TodoWrite ↔ leerness 동기화
|
|
257
|
+
```bash
|
|
258
|
+
# Claude Code의 TodoWrite를 JSON으로 export 후
|
|
259
|
+
leerness task sync --from /path/to/todos.json
|
|
260
|
+
# → completed → done, in_progress → in-progress, pending → planned로 자동 mirror
|
|
261
|
+
```
|
|
325
262
|
|
|
326
263
|
---
|
|
327
264
|
|
|
328
|
-
##
|
|
265
|
+
## 디렉토리 구조
|
|
329
266
|
|
|
330
267
|
```
|
|
331
268
|
.harness/
|
|
@@ -339,94 +276,46 @@ leerness update --from <tgz> # 오프라인/사내 미러
|
|
|
339
276
|
├── skill-index.md · skills/<id>/ · templates/
|
|
340
277
|
├── personas/<id>.md (1.9.29+, 사용자 정의 페르소나)
|
|
341
278
|
├── reviews/ (1.9.29+, 페르소나 리뷰 결과)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
.
|
|
345
|
-
|
|
279
|
+
├── cache/
|
|
280
|
+
│ ├── update-check.json (1.9.33+)
|
|
281
|
+
│ └── usage-stats.json (1.9.38+ 명령 카운터)
|
|
282
|
+
└── agent-reminders.md (1.9.38+, drift critical 시 자동 생성)
|
|
283
|
+
|
|
284
|
+
.claude/ (commands · skills · settings.local.json)
|
|
285
|
+
.cursor/rules/leerness.mdc
|
|
286
|
+
.github/copilot-instructions.md
|
|
287
|
+
AGENTS.md · CLAUDE.md
|
|
346
288
|
```
|
|
347
289
|
|
|
348
290
|
---
|
|
349
291
|
|
|
350
|
-
##
|
|
351
|
-
|
|
352
|
-
- 모든 변경 전 `.harness/archive/leerness-<version>-<timestamp>/` 자동 백업
|
|
353
|
-
- 사용자 메모리 (`plan`, `progress`, `decisions`, `task-log`, `architecture`, `reuse-map` 등) **항상 보존**
|
|
354
|
-
- 관리 인스트럭션은 새 템플릿으로 머지하되 이전 내용을 `<!-- leerness:migration-preserved -->` 블록에 보존
|
|
355
|
-
- 결과 보고: `.harness/migration-report.md`
|
|
356
|
-
|
|
357
|
-
---
|
|
292
|
+
## 환경변수
|
|
358
293
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
`init`/`migrate` 시 `.claude/settings.local.json`의 SessionStart hook에 `update --check` 자동 등록. 24시간 캐시로 npm 호출 폭주 방지.
|
|
362
|
-
|
|
363
|
-
| 명령 | 동작 |
|
|
294
|
+
| 변수 | 효과 |
|
|
364
295
|
|---|---|
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
| 도구 | 검출 | 추가 |
|
|
374
|
-
|---|---|---|
|
|
375
|
-
| `verify-claim T-XXX` | evidence 파일 존재 + 테스트 카운트 ≥ 주장 | 1.9.18 |
|
|
376
|
-
| `verify-claim --run-tests` | + 실제 `npm test` 실행 + pass 파싱 | 1.9.19/20 |
|
|
377
|
-
| `verify-claim --strict-claims` | + 낙관적 표시 검사 통합 | 1.9.26 |
|
|
378
|
-
| `optimism-check T-XXX` | 10 카테고리 + URL 매핑 + 신뢰도 점수 | 1.9.26/27 |
|
|
379
|
-
| `deps <capability>` | depends-on 역추적 + 자동 회귀 sweep | 1.9.24 |
|
|
380
|
-
| `memory search --include-code` | 소스 코드 본문 인덱싱/검색 | 1.9.25 |
|
|
381
|
-
| `register-pending` | 다중 세션 즉시 신호 | 1.9.25 |
|
|
382
|
-
| `reuse-map --strict-elements` | 함수명 동일 / capability 다른 잠재 중복 | 1.9.18 |
|
|
383
|
-
| `handoff --since 24h` | 최근 변경 T-row 자동 강조 | 1.9.18 |
|
|
384
|
-
| `verify-code --bench` | `scripts.bench` 자동 실행 + 누적 | 1.9.20 |
|
|
385
|
-
| `lazy detect` | 증거 없는 done · 빈 handoff · 추적 없는 TODO | 1.9.7 |
|
|
386
|
-
| `orchestrate --agents N` | 다중 LLM 동시 호출 (opt-in) | 1.9.22 |
|
|
387
|
-
| `handoff --compact` | LLM 시스템 프롬프트용 압축 출력 | 1.9.22 |
|
|
388
|
-
| `review --persona X` | 도메인별 sub-agent 자동 프롬프트 | 1.9.29 |
|
|
389
|
-
| `persona list/show/add` | 페르소나 카탈로그 관리 | 1.9.29 |
|
|
390
|
-
| `agents list/check` | 4 CLI 활성/설치 상태표 | 1.9.30 |
|
|
391
|
-
| `agents dispatch --to X` | ready CLI에 대상 명령 자동 생성 | 1.9.30 |
|
|
392
|
-
| `agents quota` | provider별 사용량/한도 추정 | 1.9.31 |
|
|
393
|
-
| `setup-agents` | 인터랙티브 CLI 활성화 + 자동 설치 시도 | 1.9.32 |
|
|
394
|
-
| `contract verify <spec> <impl>` | 명세 ↔ 구현 함수/필드 일치 자동 검사 | 1.9.35 |
|
|
395
|
-
| `reuse autodetect [--apply]` | `module.exports` 스캔 → reuse-map 후보 등록 | 1.9.35 |
|
|
396
|
-
| `audit --fix` | session-handoff/current-state 자동 갱신 | 1.9.35 |
|
|
397
|
-
| `handoff` (init 부재 경고) | 신규 디렉토리에서 즉시 init 안내 | 1.9.35 |
|
|
398
|
-
| `_warnIfStale` (자동) | npx 캐시 옛 버전 자동 경고 | 1.9.33 |
|
|
399
|
-
| `_selectOne/_selectMany` | 방향키/Space 인터랙티브 multi-select | 1.9.34 |
|
|
296
|
+
| `LEERNESS_OFFLINE=1` | npm 호출 스킵 (오프라인) |
|
|
297
|
+
| `LEERNESS_OLLAMA_BASE_URL` | orchestrate opt-in (1.9.22+) |
|
|
298
|
+
| `LEERNESS_ENABLE_CLAUDE/CODEX/GEMINI/COPILOT` | 외부 CLI 활성화 (1.9.30+) |
|
|
299
|
+
| `LEERNESS_NO_BANNER` | ASCII 배너 스킵 (1.9.32+) |
|
|
300
|
+
| `LEERNESS_NO_PROMPT` | readline prompt 비활성 (1.9.32+) |
|
|
301
|
+
| `LEERNESS_NO_STALE_CHECK` | npx 옛 버전 경고 끄기 (1.9.33+) |
|
|
302
|
+
| `LEERNESS_NO_INTERACTIVE` | 방향키 multi-select 비활성 (1.9.34+) |
|
|
303
|
+
| `LEERNESS_NO_DRIFT_CHECK` | drift 자동 경고 끄기 (1.9.37+) |
|
|
400
304
|
|
|
401
305
|
---
|
|
402
306
|
|
|
403
|
-
##
|
|
307
|
+
## Claude Code / Cursor / Copilot 통합
|
|
404
308
|
|
|
405
309
|
설치 시 자동 등록:
|
|
406
310
|
- `.claude/commands/{handoff, session-close, audit, lazy-detect, update}.md`
|
|
407
311
|
- `.claude/skills/leerness.md` (스킬 정의)
|
|
408
|
-
- `.claude/settings.local.json` (SessionStart hook `update --check`)
|
|
409
|
-
- `.cursor/rules/leerness.mdc`
|
|
410
|
-
-
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## 📦 스킬 라이브러리
|
|
415
|
-
|
|
416
|
-
```bash
|
|
417
|
-
leerness skill list
|
|
418
|
-
leerness skill info <id>
|
|
419
|
-
leerness skill add <id>
|
|
420
|
-
leerness skill learn <id> --doc <url> --command "..." --capability "..."
|
|
421
|
-
leerness skill optimize <id> --before "..." --after "..."
|
|
422
|
-
leerness skill consolidate
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
**기본 카탈로그**: `feature-implementation`, `ai-verified-skill-publisher`, `open-meteo`, `static-site-builder`, `project-roadmap-generator`, `office`, `commerce-api`, `crawling`, `firebase`, `ads-analytics`, `appstore-review`.
|
|
312
|
+
- `.claude/settings.local.json` (SessionStart hook = `update --check`)
|
|
313
|
+
- `.cursor/rules/leerness.mdc` · `.github/copilot-instructions.md`
|
|
314
|
+
- `AGENTS.md` · `CLAUDE.md`
|
|
426
315
|
|
|
427
316
|
---
|
|
428
317
|
|
|
429
|
-
##
|
|
318
|
+
## 자연어 트리거
|
|
430
319
|
|
|
431
320
|
| 사용자 발화 | 자동 실행 |
|
|
432
321
|
|---|---|
|
|
@@ -436,64 +325,80 @@ leerness skill consolidate
|
|
|
436
325
|
| "X 관련 자료 / X 시작 전 검토" | `leerness brainstorm "X"` |
|
|
437
326
|
| "매 X마다 Y를 해줘" | `leerness rule add "Y" --trigger every-X` |
|
|
438
327
|
| "외부 CLI 설정" | `leerness setup-agents` |
|
|
328
|
+
| "drift 점검 / leerness를 잘 쓰고 있나?" | `leerness drift check` |
|
|
329
|
+
|
|
330
|
+
`AGENTS.md`에 자동 등록 — AI 에이전트가 자연어를 명령으로 변환.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 설치 시 함정 주의
|
|
335
|
+
|
|
336
|
+
### `@latest` 명시 권장
|
|
337
|
+
```bash
|
|
338
|
+
# ❌ npx 캐시로 옛 버전 실행 가능
|
|
339
|
+
npx leerness init .
|
|
340
|
+
|
|
341
|
+
# ✅ 항상 최신 받음
|
|
342
|
+
npx leerness@latest init .
|
|
343
|
+
|
|
344
|
+
# ✅ 캐시 의심 시
|
|
345
|
+
npx --yes clear-npx-cache && npx --yes leerness@latest init .
|
|
346
|
+
```
|
|
439
347
|
|
|
440
|
-
|
|
348
|
+
1.9.33+ 부터 `leerness init` 시 npm latest와 자동 비교 → 옛 버전 경고.
|
|
441
349
|
|
|
442
350
|
---
|
|
443
351
|
|
|
444
|
-
##
|
|
352
|
+
## FAQ
|
|
445
353
|
|
|
446
|
-
**Q. leerness가
|
|
447
|
-
A. 사용자 메모리(plan/progress/decisions
|
|
354
|
+
**Q. leerness가 코드를 변경하나요?**
|
|
355
|
+
A. 사용자 메모리(plan/progress/decisions 등)는 **항상 보존**. 관리 인스트럭션은 머지 + preserved 블록 보존. 모든 변경 전 `.harness/archive/`에 자동 백업.
|
|
448
356
|
|
|
449
|
-
**Q.
|
|
450
|
-
A.
|
|
357
|
+
**Q. AI 에이전트 없이 사용 가능한가요?**
|
|
358
|
+
A. 네. 1인 개발자도 자기 자신의 작업 검증·기억·중복 감지에 활용 가능. `verify-claim` / `audit` / `reuse-map`은 모두 AI 무관.
|
|
451
359
|
|
|
452
360
|
**Q. CI에서 사용 가능?**
|
|
453
|
-
A. 네.
|
|
361
|
+
A. 네. 모든 명령이 exit code + `--json` 출력 + `--yes` 비대화형 지원. 임의 `LEERNESS_NO_*` env로 prompt 강제 비활성.
|
|
454
362
|
|
|
455
|
-
**Q. 외부 AI CLI를
|
|
456
|
-
A.
|
|
363
|
+
**Q. 외부 AI CLI를 자동 호출하나요?**
|
|
364
|
+
A. **절대 아니요.** `agents dispatch`는 명령 텍스트만 생성합니다. 사용자/메인이 명시적으로 spawn.
|
|
457
365
|
|
|
458
|
-
**Q.
|
|
459
|
-
A.
|
|
366
|
+
**Q. 멀티 언어 프로젝트는?**
|
|
367
|
+
A. 영어 인스트럭션 옵션 (`--language en`). 단 도구 출력 메시지/안내는 한국어가 풍부합니다.
|
|
460
368
|
|
|
461
369
|
---
|
|
462
370
|
|
|
463
|
-
##
|
|
371
|
+
## E2E
|
|
464
372
|
|
|
465
373
|
```bash
|
|
466
374
|
npm test # = node ./scripts/e2e.js
|
|
467
375
|
```
|
|
468
376
|
|
|
469
|
-
**
|
|
377
|
+
**174/174 시나리오** 통과 (1.9.7~1.9.38 회귀 + 신규 검증).
|
|
470
378
|
|
|
471
379
|
---
|
|
472
380
|
|
|
473
|
-
##
|
|
474
|
-
|
|
475
|
-
- **1.9.
|
|
476
|
-
- **1.9.
|
|
477
|
-
- **1.9.
|
|
478
|
-
- **1.9.
|
|
479
|
-
- **1.9.
|
|
480
|
-
- **1.9.
|
|
481
|
-
- **1.9.
|
|
482
|
-
- **1.9.
|
|
483
|
-
- **1.9.
|
|
484
|
-
- **1.9.
|
|
485
|
-
- **1.9.
|
|
486
|
-
- **1.9.
|
|
487
|
-
- **1.9.
|
|
488
|
-
- **1.9.
|
|
489
|
-
- **1.9.22** — Ollama opt-in 통합 (`orchestrate --agents N`), `handoff --compact`, `llm-bench record`.
|
|
490
|
-
- **1.9.18** — `--since`, `--strict-elements`, `depends-on` 그래프, `verify-claim`.
|
|
491
|
-
- **1.9.17** — `handoff/reuse-map --all-apps` 워크스페이스 모드.
|
|
381
|
+
## 변경 이력 (최근)
|
|
382
|
+
|
|
383
|
+
- **1.9.38** — drift 자동 reminder (`agent-reminders.md`) · `usage stats` 명령 · `task sync --from <todo.json>` · drift 임계 학습 (skip ≥5 → 임계 완화).
|
|
384
|
+
- **1.9.37** — `leerness drift check` (4 신호 + 4단계 레벨) — 라운드 길어지며 메인이 leerness 잊는 현상 자동 감지.
|
|
385
|
+
- **1.9.36** — `agents bench` (3 CLI 동시 비교) · `dispatch --write` (CLI별 권장 플래그) · 작업 유형 추천 · `contract verify` require() side-effect 25× 속도 회복.
|
|
386
|
+
- **1.9.35** — `contract verify` · `reuse autodetect` · `audit --fix` · `handoff` init 부재 경고 · `agents dispatch` 안전 규칙.
|
|
387
|
+
- **1.9.34** — 방향키/스페이스 인터랙티브 multi-select · 256색 그라데이션 배너 · 3단계 sub-agent 오케스트레이션 검증 (2.2× 효율).
|
|
388
|
+
- **1.9.33** — npx 캐시 옛 버전 자동 경고.
|
|
389
|
+
- **1.9.32** — ASCII 배너 · `setup-agents` 인터랙티브 + 자동 설치.
|
|
390
|
+
- **1.9.31** — `agents quota` (provider별 사용량 추정).
|
|
391
|
+
- **1.9.30** — 외부 AI CLI 오케스트레이션 (claude/codex/gemini/copilot).
|
|
392
|
+
- **1.9.29** — 페르소나 리뷰 (5종 내장).
|
|
393
|
+
- **1.9.24** — `deps <capability>` 영향 추적 + 자동 회귀.
|
|
394
|
+
- **1.9.22** — Ollama opt-in · `handoff --compact` · `llm-bench`.
|
|
395
|
+
- **1.9.18** — `verify-claim` · `--strict-elements` · depends-on 그래프.
|
|
396
|
+
- **1.9.17** — `--all-apps` 워크스페이스 모드.
|
|
492
397
|
- ... (전체 [CHANGELOG.md](CHANGELOG.md) 참조)
|
|
493
398
|
|
|
494
399
|
---
|
|
495
400
|
|
|
496
|
-
##
|
|
401
|
+
## 기여
|
|
497
402
|
|
|
498
403
|
- **이슈**: https://github.com/gugu9999gu/leerness/issues
|
|
499
404
|
- **PR**: e2e 통과 + 한국어 주석 + UTF-8 BOM 없음
|
|
@@ -504,5 +409,5 @@ npm test # = node ./scripts/e2e.js
|
|
|
504
409
|
|
|
505
410
|
MIT — © leerness contributors
|
|
506
411
|
|
|
507
|
-
> **AI
|
|
508
|
-
> 사용자 동의 없이
|
|
412
|
+
> **AI 에이전트가 신뢰할 수 있게 일하도록 만드는 도구.**
|
|
413
|
+
> 사용자 동의 없이 외부 LLM/API/CLI를 자동 호출하지 않습니다.
|
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.38';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -1433,36 +1433,76 @@ function handoffCmd(root) {
|
|
|
1433
1433
|
return _handoffWorkspace(absRoot(root));
|
|
1434
1434
|
}
|
|
1435
1435
|
// 1.9.37: drift 자동 경고 (메인 에이전트가 leerness를 점점 안 쓰는 현상 감지)
|
|
1436
|
+
// 1.9.38 (A): drift 임계 시 .harness/agent-reminders.md 자동 생성 — 메인 에이전트 프롬프트에 표시되도록.
|
|
1437
|
+
// 1.9.38 (D): skip 횟수 학습 — --no-drift-check 빈도 ≥5 시 임계 완화 (1d → 2d).
|
|
1436
1438
|
const absR0 = absRoot(root || process.cwd());
|
|
1437
|
-
if (exists(path.join(absR0, '.harness')) &&
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1439
|
+
if (exists(path.join(absR0, '.harness')) && process.env.LEERNESS_NO_DRIFT_CHECK !== '1') {
|
|
1440
|
+
// skip 카운트
|
|
1441
|
+
if (has('--no-drift-check')) {
|
|
1442
|
+
try {
|
|
1443
|
+
const stats = _readUsageStats(absR0);
|
|
1444
|
+
stats.drift = stats.drift || {};
|
|
1445
|
+
stats.drift.skipped = (stats.drift.skipped || 0) + 1;
|
|
1446
|
+
const p = _usageStatsPath(absR0);
|
|
1447
|
+
mkdirp(path.dirname(p));
|
|
1448
|
+
writeUtf8(p, JSON.stringify(stats, null, 2) + '\n');
|
|
1449
|
+
} catch {}
|
|
1450
|
+
} else {
|
|
1451
|
+
try {
|
|
1452
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
1453
|
+
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
1454
|
+
const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
|
|
1455
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
1456
|
+
// 1.9.38 (D): 학습된 임계 (skip 빈도 높으면 임계 완화)
|
|
1457
|
+
const stats = _readUsageStats(absR0);
|
|
1458
|
+
const skipCount = (stats.drift && stats.drift.skipped) || 0;
|
|
1459
|
+
const threshold = skipCount >= 5 ? 4 : 2; // 5회 이상 끄면 2일 → 4일로 완화
|
|
1460
|
+
// 간이 drift 계산
|
|
1461
|
+
const now = Date.now();
|
|
1462
|
+
const shPath = handoffPath(absR0);
|
|
1463
|
+
let shAge = null;
|
|
1464
|
+
if (exists(shPath)) {
|
|
1465
|
+
const m = read(shPath).match(/Last generated:\s*([\d\-T:.Z]+)/);
|
|
1466
|
+
if (m) shAge = (now - new Date(m[1]).getTime()) / 86400000;
|
|
1467
|
+
}
|
|
1468
|
+
const rows = readProgressRows(absR0);
|
|
1469
|
+
let ptAge = null;
|
|
1470
|
+
if (rows.length) {
|
|
1471
|
+
const dates = rows.map(r => (r.updated || '').match(/\d{4}-\d{2}-\d{2}/)).filter(Boolean).map(m => m[0]).sort();
|
|
1472
|
+
if (dates.length) ptAge = (now - new Date(dates[dates.length - 1]).getTime()) / 86400000;
|
|
1473
|
+
}
|
|
1474
|
+
const sevStale = (shAge !== null && shAge > 3) || (ptAge !== null && ptAge > 3);
|
|
1475
|
+
if ((shAge !== null && shAge > threshold) || (ptAge !== null && ptAge > threshold)) {
|
|
1476
|
+
log('');
|
|
1477
|
+
log(yel(' ⚠ leerness drift 감지 — 메타파일이 stale합니다'));
|
|
1478
|
+
if (shAge !== null && shAge > threshold) log(dim(` session-handoff.md: ${shAge.toFixed(1)}일 stale`));
|
|
1479
|
+
if (ptAge !== null && ptAge > threshold) log(dim(` progress-tracker: ${ptAge.toFixed(1)}일 stale`));
|
|
1480
|
+
log(dim(` → 권장: ${red('leerness session close .')} 또는 ${red('leerness drift check .')} 로 상세 보기`));
|
|
1481
|
+
if (skipCount >= 5) log(dim(` (학습: skip ${skipCount}회 누적 → 임계 ${threshold}일로 완화)`));
|
|
1482
|
+
log('');
|
|
1483
|
+
// 1.9.38 (A): critical 시 .harness/agent-reminders.md 자동 생성 — 다음 세션 시작 시 메인 에이전트가 읽도록.
|
|
1484
|
+
if (sevStale) {
|
|
1485
|
+
try {
|
|
1486
|
+
const remPath = path.join(absR0, '.harness', 'agent-reminders.md');
|
|
1487
|
+
const body = `<!-- leerness:managed:auto -->\n# 🔔 메인 에이전트용 자동 reminder\n\n_생성: ${new Date().toISOString()}_\n\n## drift critical 감지\n현재 워크스페이스의 메타파일이 매우 stale합니다. 이번 라운드 작업 끝에 반드시 다음 명령을 호출하세요:\n\n\`\`\`bash\nleerness session close .\n\`\`\`\n\n또는 상세 점검:\n\`\`\`bash\nleerness drift check .\n\`\`\`\n\nstale 신호:\n${shAge !== null ? `- session-handoff.md: ${shAge.toFixed(1)}일 stale\n` : ''}${ptAge !== null ? `- progress-tracker: ${ptAge.toFixed(1)}일 stale\n` : ''}\n\n_이 파일은 leerness 1.9.38+가 자동 갱신합니다. session close 후 자동 삭제.\n_사용자가 이 파일을 보고 메인 에이전트에 reminder 전달 가능._\n`;
|
|
1488
|
+
writeUtf8(remPath, body);
|
|
1489
|
+
} catch {}
|
|
1490
|
+
} else {
|
|
1491
|
+
// attention 등급으로 회복했으면 reminder 파일 삭제
|
|
1492
|
+
try {
|
|
1493
|
+
const remPath = path.join(absR0, '.harness', 'agent-reminders.md');
|
|
1494
|
+
if (exists(remPath)) fs.unlinkSync(remPath);
|
|
1495
|
+
} catch {}
|
|
1496
|
+
}
|
|
1497
|
+
} else {
|
|
1498
|
+
// healthy → reminder 파일 자동 청소
|
|
1499
|
+
try {
|
|
1500
|
+
const remPath = path.join(absR0, '.harness', 'agent-reminders.md');
|
|
1501
|
+
if (exists(remPath)) fs.unlinkSync(remPath);
|
|
1502
|
+
} catch {}
|
|
1503
|
+
}
|
|
1504
|
+
} catch {}
|
|
1505
|
+
}
|
|
1466
1506
|
}
|
|
1467
1507
|
// 1.9.35 개선 #1: .harness 부재 시 즉시 경고 (자동 init 권장)
|
|
1468
1508
|
// 사용자가 신규 디렉토리에서 handoff 호출 시 sub-agent 작업이 길을 잃지 않도록.
|
|
@@ -5335,6 +5375,17 @@ function driftCheckCmd(root, opts = {}) {
|
|
|
5335
5375
|
else if (totalScore >= 50) level = '🟡 warning';
|
|
5336
5376
|
else if (totalScore >= 20) level = '🟠 attention';
|
|
5337
5377
|
|
|
5378
|
+
// 1.9.38 (D): drift critical 등급은 누적 카운트 (학습 신호)
|
|
5379
|
+
try {
|
|
5380
|
+
if (level === '🔴 critical') {
|
|
5381
|
+
const stats = _readUsageStats(root);
|
|
5382
|
+
stats.drift = stats.drift || {};
|
|
5383
|
+
stats.drift.criticalSeen = (stats.drift.criticalSeen || 0) + 1;
|
|
5384
|
+
const p = _usageStatsPath(root);
|
|
5385
|
+
mkdirp(path.dirname(p));
|
|
5386
|
+
writeUtf8(p, JSON.stringify(stats, null, 2) + '\n');
|
|
5387
|
+
}
|
|
5388
|
+
} catch {}
|
|
5338
5389
|
if (has('--json')) {
|
|
5339
5390
|
log(JSON.stringify({ root, score: totalScore, level, signals, fired, appsZeroTask }, null, 2));
|
|
5340
5391
|
return;
|
|
@@ -5366,6 +5417,92 @@ function driftCheckCmd(root, opts = {}) {
|
|
|
5366
5417
|
if (level === '🔴 critical') process.exitCode = 1;
|
|
5367
5418
|
}
|
|
5368
5419
|
|
|
5420
|
+
// 1.9.38: 사용 통계 (cumulative count, command별)
|
|
5421
|
+
function _usageStatsPath(root) { return path.join(absRoot(root), '.harness', 'cache', 'usage-stats.json'); }
|
|
5422
|
+
function _readUsageStats(root) {
|
|
5423
|
+
const p = _usageStatsPath(root);
|
|
5424
|
+
if (!exists(p)) return { commands: {}, drift: { criticalSeen: 0, skipped: 0, autoResolved: 0 }, since: today() };
|
|
5425
|
+
try { return JSON.parse(read(p)); } catch { return { commands: {}, drift: {}, since: today() }; }
|
|
5426
|
+
}
|
|
5427
|
+
function _bumpUsage(root, cmdName) {
|
|
5428
|
+
// 가벼운 카운터 — 명령 실행마다 호출 (sync write로 작은 파일)
|
|
5429
|
+
try {
|
|
5430
|
+
const stats = _readUsageStats(root);
|
|
5431
|
+
if (!stats.commands) stats.commands = {};
|
|
5432
|
+
stats.commands[cmdName] = (stats.commands[cmdName] || 0) + 1;
|
|
5433
|
+
stats.lastCommand = cmdName;
|
|
5434
|
+
stats.lastAt = new Date().toISOString();
|
|
5435
|
+
if (!stats.since) stats.since = today();
|
|
5436
|
+
const p = _usageStatsPath(root);
|
|
5437
|
+
mkdirp(path.dirname(p));
|
|
5438
|
+
writeUtf8(p, JSON.stringify(stats, null, 2) + '\n');
|
|
5439
|
+
} catch {}
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
function usageStatsCmd(root) {
|
|
5443
|
+
root = absRoot(root || process.cwd());
|
|
5444
|
+
const stats = _readUsageStats(root);
|
|
5445
|
+
if (has('--json')) { log(JSON.stringify(stats, null, 2)); return; }
|
|
5446
|
+
log(`# leerness usage stats (1.9.38)`);
|
|
5447
|
+
log(`since: ${stats.since || '(unknown)'} · last: ${stats.lastAt || '(none)'}`);
|
|
5448
|
+
log('');
|
|
5449
|
+
const entries = Object.entries(stats.commands || {}).sort((a, b) => b[1] - a[1]);
|
|
5450
|
+
if (!entries.length) {
|
|
5451
|
+
log(' (사용 기록 없음)');
|
|
5452
|
+
return;
|
|
5453
|
+
}
|
|
5454
|
+
log(`| 명령 | 호출 수 |`);
|
|
5455
|
+
log(`|---|---:|`);
|
|
5456
|
+
for (const [cmd, n] of entries.slice(0, 30)) log(`| ${cmd} | ${n} |`);
|
|
5457
|
+
const total = entries.reduce((s, [, n]) => s + n, 0);
|
|
5458
|
+
log('');
|
|
5459
|
+
log(`총 ${total} 회 호출 · 종류 ${entries.length} 가지`);
|
|
5460
|
+
if (stats.drift) {
|
|
5461
|
+
log('');
|
|
5462
|
+
log(`drift 통계: critical 발견 ${stats.drift.criticalSeen || 0} · skip ${stats.drift.skipped || 0} · 자동 해소 ${stats.drift.autoResolved || 0}`);
|
|
5463
|
+
if ((stats.drift.skipped || 0) > 5) {
|
|
5464
|
+
log(`💡 drift 경고 ${stats.drift.skipped}회 스킵 → 1.9.38 학습: 임계 자동 완화 (--no-drift-check 빈도 ≥5)`);
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
// 1.9.38: task sync — TodoWrite/외부 JSON에서 leerness task로 mirror
|
|
5470
|
+
function taskSyncCmd(root) {
|
|
5471
|
+
root = absRoot(root || process.cwd());
|
|
5472
|
+
const file = arg('--from', null);
|
|
5473
|
+
if (!file) {
|
|
5474
|
+
fail('사용법: leerness task sync --from <todo.json>\n 파일 형식: [{"content":"...","status":"completed|in_progress|pending","activeForm":"..."}]');
|
|
5475
|
+
return process.exit(1);
|
|
5476
|
+
}
|
|
5477
|
+
const full = path.resolve(file);
|
|
5478
|
+
if (!exists(full)) { fail(`파일 없음: ${full}`); return process.exit(1); }
|
|
5479
|
+
let todos;
|
|
5480
|
+
try { todos = JSON.parse(read(full)); }
|
|
5481
|
+
catch (e) { fail(`JSON 파싱 실패: ${e.message}`); return process.exit(1); }
|
|
5482
|
+
if (!Array.isArray(todos)) { fail('JSON 최상위는 배열이어야 함'); return process.exit(1); }
|
|
5483
|
+
let imported = 0, updated = 0;
|
|
5484
|
+
for (const t of todos) {
|
|
5485
|
+
if (!t || !t.content) continue;
|
|
5486
|
+
const status = t.status === 'completed' ? 'done' : t.status === 'in_progress' ? 'in-progress' : 'planned';
|
|
5487
|
+
// 이미 같은 request 있는지
|
|
5488
|
+
const existing = readProgressRows(root).find(r => r.request === t.content);
|
|
5489
|
+
if (existing) {
|
|
5490
|
+
if (existing.status !== status) {
|
|
5491
|
+
upsertProgress(root, { id: existing.id, status });
|
|
5492
|
+
updated++;
|
|
5493
|
+
}
|
|
5494
|
+
} else {
|
|
5495
|
+
const id = nextId(root, 'T');
|
|
5496
|
+
upsertProgress(root, { id, status, request: t.content, evidence: 'todowrite-sync', nextAction: t.activeForm || '다음 액션' });
|
|
5497
|
+
imported++;
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
log(`# leerness task sync (1.9.38)`);
|
|
5501
|
+
log(`from: ${full}`);
|
|
5502
|
+
log(`imported: ${imported} · updated: ${updated} · total in source: ${todos.length}`);
|
|
5503
|
+
if (has('--json')) log(JSON.stringify({ imported, updated, total: todos.length }, null, 2));
|
|
5504
|
+
}
|
|
5505
|
+
|
|
5369
5506
|
// 1.9.35 개선 #3: contract verify <spec.md> <impl.js>
|
|
5370
5507
|
// 사양 문서(spec.md)에 명시된 함수 이름이 실제 module.exports에 모두 있는지 검사.
|
|
5371
5508
|
// 사용 예: leerness contract verify TICK_SPEC.md src/format.js
|
|
@@ -5533,6 +5670,13 @@ async function main() {
|
|
|
5533
5670
|
return log(VERSION);
|
|
5534
5671
|
}
|
|
5535
5672
|
if (has('--help') || has('-h')) return help();
|
|
5673
|
+
// 1.9.38 (B): 사용 통계 카운터 — usage stats 명령 자체와 비차단 경로는 제외
|
|
5674
|
+
if (cmd !== 'usage' && cmd !== 'init' && cmd !== 'migrate' && cmd !== '--version' && cmd !== '--help') {
|
|
5675
|
+
try {
|
|
5676
|
+
const root = absRoot(arg('--path', args[1] && !args[1].startsWith('-') ? args[1] : process.cwd()));
|
|
5677
|
+
if (exists(path.join(root, '.harness'))) _bumpUsage(root, cmd);
|
|
5678
|
+
} catch {}
|
|
5679
|
+
}
|
|
5536
5680
|
if (cmd === 'init') return await install(args[1] || process.cwd(), { force:false, dry:false, migration:false });
|
|
5537
5681
|
if (cmd === 'migrate') return await install(args[1] || process.cwd(), { force:has('--force'), dry:has('--dry-run'), migration:true });
|
|
5538
5682
|
if (cmd === 'update') return await updateCmd(args[1] || process.cwd(), { checkOnly: has('--check'), yes: has('--yes'), force: has('--force') });
|
|
@@ -5559,6 +5703,7 @@ async function main() {
|
|
|
5559
5703
|
if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
5560
5704
|
if (cmd === 'contract' && args[1] === 'verify') return contractVerifyCmd(args[2], args[3]);
|
|
5561
5705
|
if (cmd === 'drift' && (args[1] === 'check' || !args[1])) return driftCheckCmd(args[2] || arg('--path', process.cwd()));
|
|
5706
|
+
if (cmd === 'usage' && (args[1] === 'stats' || !args[1])) return usageStatsCmd(args[2] || arg('--path', process.cwd()));
|
|
5562
5707
|
if (cmd === 'reuse' && args[1] === 'autodetect') return reuseAutodetectCmd(args[2] || arg('--path', process.cwd()));
|
|
5563
5708
|
if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (args[2] || process.cwd()));
|
|
5564
5709
|
if (cmd === 'session' && args[1] === 'close') { const r = sessionClose(args[2] || process.cwd()); viewworkEmit(args[2] || process.cwd(), { action: 'task', tool: 'session-close', note: 'session close' }); return r; }
|
|
@@ -5623,6 +5768,7 @@ async function main() {
|
|
|
5623
5768
|
if (sub==='drop') return taskDrop(root, args[2]);
|
|
5624
5769
|
if (sub==='fix-evidence') return taskFixEvidence(root);
|
|
5625
5770
|
if (sub==='relink') return taskRelink(root);
|
|
5771
|
+
if (sub==='sync') return taskSyncCmd(root);
|
|
5626
5772
|
}
|
|
5627
5773
|
return help();
|
|
5628
5774
|
}
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -950,6 +950,83 @@ total++;
|
|
|
950
950
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// 1.9.38 회귀: usage stats, task sync, drift reminder, drift skip learning
|
|
954
|
+
total++;
|
|
955
|
+
{
|
|
956
|
+
// B. usage stats: 빈 상태 + 호출 후 카운터 증가
|
|
957
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-usage-'));
|
|
958
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
959
|
+
// 카운터 자극: status, handoff 호출
|
|
960
|
+
cp.spawnSync(process.execPath, [CLI, 'status', tmpC], { stdio: 'ignore', timeout: 10000 });
|
|
961
|
+
cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--compact', '--no-drift-check'], { stdio: 'ignore', timeout: 10000 });
|
|
962
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'usage', 'stats', tmpC, '--json'], { encoding: 'utf8', timeout: 10000 });
|
|
963
|
+
let parsed = null;
|
|
964
|
+
try { parsed = JSON.parse(r.stdout); } catch {}
|
|
965
|
+
const ok = parsed
|
|
966
|
+
&& parsed.commands
|
|
967
|
+
&& (parsed.commands.status >= 1 || parsed.commands.handoff >= 1)
|
|
968
|
+
&& parsed.drift
|
|
969
|
+
&& typeof parsed.drift.skipped === 'number';
|
|
970
|
+
console.log(ok ? '✓ B(1.9.38) usage stats: 명령 카운터 누적 + drift 통계 구조' : `✗ usage stats 실패`);
|
|
971
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
total++;
|
|
975
|
+
{
|
|
976
|
+
// C. task sync — TodoWrite JSON 임포트
|
|
977
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tasksync-'));
|
|
978
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
979
|
+
const todoFile = path.join(tmpC, 'todo.json');
|
|
980
|
+
fs.writeFileSync(todoFile, JSON.stringify([
|
|
981
|
+
{ content: 'sync 테스트 작업 A', status: 'completed', activeForm: 'syncA' },
|
|
982
|
+
{ content: 'sync 테스트 작업 B', status: 'in_progress', activeForm: 'syncB' },
|
|
983
|
+
{ content: 'sync 테스트 작업 C', status: 'pending', activeForm: 'syncC' }
|
|
984
|
+
]), 'utf8');
|
|
985
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'task', 'sync', '--from', todoFile, '--path', tmpC, '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
986
|
+
let parsed = null;
|
|
987
|
+
try { parsed = JSON.parse(r.stdout.split('\n').filter(l => l.startsWith('{')).pop() || '{}'); } catch {}
|
|
988
|
+
const ok = r.status === 0 && /imported: 3/.test(r.stdout);
|
|
989
|
+
console.log(ok ? '✓ B(1.9.38) task sync: 3개 TodoWrite → progress-tracker import' : `✗ task sync 실패`);
|
|
990
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
total++;
|
|
994
|
+
{
|
|
995
|
+
// A. drift reminder 파일 자동 생성 (인공 stale 시뮬)
|
|
996
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rem-'));
|
|
997
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
998
|
+
// session-handoff.md를 5일 전으로
|
|
999
|
+
const shPath = path.join(tmpC, '.harness', 'session-handoff.md');
|
|
1000
|
+
if (fs.existsSync(shPath)) {
|
|
1001
|
+
const oldDate = new Date(Date.now() - 5 * 86400000).toISOString();
|
|
1002
|
+
let body = fs.readFileSync(shPath, 'utf8');
|
|
1003
|
+
body = body.replace(/Last generated:.*/, `Last generated: ${oldDate}`);
|
|
1004
|
+
if (!/Last generated:/.test(body)) body = `Last generated: ${oldDate}\n` + body;
|
|
1005
|
+
fs.writeFileSync(shPath, body, 'utf8');
|
|
1006
|
+
}
|
|
1007
|
+
// handoff 호출 → reminder 자동 생성
|
|
1008
|
+
cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--compact'], { encoding: 'utf8', timeout: 10000 });
|
|
1009
|
+
const remPath = path.join(tmpC, '.harness', 'agent-reminders.md');
|
|
1010
|
+
const ok = fs.existsSync(remPath) && /drift critical/.test(fs.readFileSync(remPath, 'utf8'));
|
|
1011
|
+
console.log(ok ? '✓ B(1.9.38) drift critical → agent-reminders.md 자동 생성' : `✗ reminder 파일 실패`);
|
|
1012
|
+
if (!ok) { failed++; if (fs.existsSync(remPath)) console.log(fs.readFileSync(remPath, 'utf8').slice(0, 400)); else console.log('(reminder 파일 없음)'); }
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
total++;
|
|
1016
|
+
{
|
|
1017
|
+
// D. drift 학습 — --no-drift-check 5회 호출 후 임계 완화
|
|
1018
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-learn-'));
|
|
1019
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
1020
|
+
// 5회 --no-drift-check 호출
|
|
1021
|
+
for (let i = 0; i < 5; i++) {
|
|
1022
|
+
cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--compact', '--no-drift-check'], { stdio: 'ignore', timeout: 10000 });
|
|
1023
|
+
}
|
|
1024
|
+
const stats = JSON.parse(fs.readFileSync(path.join(tmpC, '.harness', 'cache', 'usage-stats.json'), 'utf8'));
|
|
1025
|
+
const ok = stats.drift && stats.drift.skipped >= 5;
|
|
1026
|
+
console.log(ok ? '✓ B(1.9.38) drift 학습: --no-drift-check 5회 누적 (skipped≥5)' : `✗ drift 학습 실패`);
|
|
1027
|
+
if (!ok) { failed++; console.log(JSON.stringify(stats.drift || {})); }
|
|
1028
|
+
}
|
|
1029
|
+
|
|
953
1030
|
// 1.9.37 회귀: drift detection
|
|
954
1031
|
total++;
|
|
955
1032
|
{
|