leerness 1.13.0 → 1.15.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.
@@ -1,289 +1,297 @@
1
- // lib/review-request.js — review-request 핸들러 (UR-0125 큰 핸들러 모듈화, 1.9.420)
2
- // bin/leerness.js 에서 reviewRequestCmd(277줄) 분리. DI: harness 고유 의존(has · harnessPath · _checkRequestConstraints · _recordRun) 주입.
3
- // io 프리미티브는 ./io, cp/path 는 빌트인. 동작/출력 무변경(thin wrapper 위임).
4
- 'use strict';
5
- const cp = require('child_process');
6
- const path = require('path');
7
- const { absRoot, exists, read, log, fail, failJson } = require('./io');
8
-
9
- function reviewRequestCmd(root, request, deps = {}) {
10
- const { has, harnessPath, _checkRequestConstraints, _recordRun } = deps;
11
- root = absRoot(root || process.cwd());
12
- if (!request || !String(request).trim()) {
13
- // 1.9.428 (10th 외부평가 UR-0128): --json 오류 경로도 순수 JSON (failJson 이 모드 분기)
14
- return failJson(!!(has && has('--json')), 'review_request_empty', 'leerness review-request "<request>" — 사용자 요청 텍스트 필요');
15
- }
16
- const t0 = Date.now();
17
- const text = String(request).trim();
18
-
19
- // 1) 작업 유형 추정 (route 기반 키워드 매핑)
20
- const lower = text.toLowerCase();
21
- const routeKw = {
22
- bugfix: ['버그', '오류', '에러', '수정', '고쳐', '실패', 'fix', 'bug', 'error'],
23
- refactor: ['리팩토', '재구성', '정리', '개선', 'refactor', 'cleanup'],
24
- feature: ['추가', '구현', '만들', '새', '기능', 'add', 'implement', 'feature', 'create', 'new'],
25
- research: ['조사', '분석', '비교', '검토', '연구', 'research', 'analyze', 'compare', 'investigate'],
26
- planning: ['계획', '설계', '로드맵', 'plan', 'design', 'architecture', 'roadmap'],
27
- release: ['배포', '릴리즈', '버전', 'release', 'deploy', 'publish'],
28
- consistency: ['일관성', '통합', '동기화', '맞춰', 'consistency', 'sync', 'align']
29
- };
30
- let estimatedType = 'feature'; // default
31
- let maxScore = 0;
32
- for (const [type, kws] of Object.entries(routeKw)) {
33
- const score = kws.filter(k => lower.includes(k)).length;
34
- if (score > maxScore) { maxScore = score; estimatedType = type; }
35
- }
36
-
37
- // 2) 기존 자원 회수 — brainstorm spawn (모든 surface 통합 회수)
38
- const conflictHints = []; // ⚠ 같은 키워드 + 실패/오류 패턴
39
- const reuseCandidates = []; // 🔁 기존 skill / reuse-map / decision 후보
40
- const lessonsRecall = []; // 🧠 과거 lesson
41
- const planConflicts = []; // 📋 진행 중 milestone과 충돌 가능
42
-
43
- // brainstorm 호출 (1.9.13~) — JSON 결과 회수
44
- try {
45
- const r = cp.spawnSync(process.execPath, [harnessPath, 'brainstorm', text, '--path', root, '--json'], {
46
- encoding: 'utf8', timeout: 12000,
47
- env: { ...process.env, LEERNESS_INTERNAL: '1', LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
48
- });
49
- if (r.stdout) {
50
- const j = JSON.parse(r.stdout);
51
- const hits = j.hits || {};
52
- // decisions — 과거 결정 후보
53
- (hits.decisions || []).slice(0, 5).forEach(d => {
54
- lessonsRecall.push({ kind: 'decision', title: d.title, line: d.line, preview: (d.preview || '').slice(0, 100) });
55
- });
56
- // lessons — 과거 교훈 (특히 실패 키워드)
57
- (hits.lessons || []).slice(0, 5).forEach(l => {
58
- const preview = (l.text || l.preview || '').slice(0, 100);
59
- const isFailure = /실패|오류|에러|fail|error|bug|문제|warning/i.test(preview);
60
- if (isFailure) {
61
- conflictHints.push({ kind: 'lesson-failure', preview, tags: l.tags });
62
- } else {
63
- lessonsRecall.push({ kind: 'lesson', preview, tags: l.tags });
64
- }
65
- });
66
- // skills — 기존 skill 후보
67
- (hits.skills || []).slice(0, 3).forEach(s => {
68
- reuseCandidates.push({ kind: 'skill', id: s.id, displayNameKo: s.displayNameKo, capabilities: s.capabilities });
69
- });
70
- // tasks — 진행 중 task 충돌
71
- (hits.tasks || []).slice(0, 3).forEach(tsk => {
72
- if (tsk.status && /in-progress|진행/.test(String(tsk.status))) {
73
- conflictHints.push({ kind: 'task-in-progress', id: tsk.id, title: tsk.title });
74
- }
75
- });
76
- // plan milestones — 진행 중 milestone
77
- (hits.planMilestones || []).slice(0, 3).forEach(m => {
78
- if (m.status && /in-progress|진행/.test(String(m.status))) {
79
- planConflicts.push({ kind: 'milestone-in-progress', id: m.id, title: m.title });
80
- }
81
- });
82
- // taskLogFails — 과거 같은 키워드 실패 흔적
83
- (hits.taskLogFails || []).slice(0, 3).forEach(f => {
84
- conflictHints.push({ kind: 'task-log-failure', preview: (f.preview || f.text || '').slice(0, 100) });
85
- });
86
- }
87
- } catch {}
88
-
89
- // 3) reuse-map 매칭 — 기존 capability 등록 후보
90
- try {
91
- const reusePath = path.join(root, '.harness/reuse-map.md');
92
- if (exists(reusePath)) {
93
- const reuseLines = read(reusePath).split('\n');
94
- const tokens = lower.split(/\s+/).filter(t => t.length >= 3);
95
- for (const line of reuseLines) {
96
- if (!/^\| /.test(line)) continue; // 테이블 row만
97
- const ll = line.toLowerCase();
98
- const matched = tokens.filter(t => ll.includes(t)).length;
99
- if (matched > 0) {
100
- const cols = line.split('|').map(s => s.trim());
101
- if (cols[1]) {
102
- reuseCandidates.push({ kind: 'reuse-map', capability: cols[1], where: cols[2] || '', note: cols[3] || '' });
103
- }
104
- }
105
- }
106
- }
107
- } catch {}
108
-
109
- // 4) feature_graph — 같은 영역 변경 가능성
110
- const featureConflicts = [];
111
- try {
112
- const fgPath = path.join(root, '.harness/feature_graph.md');
113
- if (exists(fgPath)) {
114
- const fg = read(fgPath);
115
- const tokens = lower.split(/\s+/).filter(t => t.length >= 4);
116
- // F-XXXX 노드 라인 추출
117
- const nodeBlocks = fg.split(/\n### /);
118
- for (const blk of nodeBlocks.slice(1)) {
119
- const bl = blk.toLowerCase();
120
- const matched = tokens.filter(t => bl.includes(t)).length;
121
- if (matched > 0) {
122
- const titleMatch = blk.match(/^([^\n]+)/);
123
- const idMatch = blk.match(/F-\d+/);
124
- if (titleMatch && idMatch) {
125
- featureConflicts.push({ kind: 'feature', id: idMatch[0], title: titleMatch[1].trim() });
126
- }
127
- }
128
- }
129
- }
130
- } catch {}
131
-
132
- // 5) 권장 단계 (작업 유형별)
133
- const recommendedSteps = {
134
- feature: [
135
- '1) leerness reuse-check "<기능>" — 외부 OSS 빌드 vs 재사용 판단 (1.9.285)',
136
- '2) leerness reuse find "<핵심 capability>" — 내부 중복 구현 사전 차단',
137
- '3) leerness plan add "<milestone>" — 진행 추적',
138
- '4) leerness contract verify SPEC.md src/<mod>.js — 사양 ↔ 구현 일치 검증',
139
- '5) verify-claim --run-tests 로 evidence 의무화'
140
- ],
141
- bugfix: [
142
- '1) leerness brainstorm "<버그 키워드>" — 과거 같은 영역 lesson 회수',
143
- '2) leerness verify-claim T-XXX --strict-claims — 낙관적 표시 사전 감지',
144
- '3) verify-code --run-tests — 재현 + fix 검증',
145
- '4) leerness lesson save "<root cause>" — 같은 실수 재발 차단'
146
- ],
147
- refactor: [
148
- '1) leerness reuse-map — 영향 범위 파악',
149
- '2) leerness impact <file> — 강한/약한 참조 분리',
150
- '3) leerness contract verify — 외부 인터페이스 보존 확인',
151
- '4) verify-code --run-tests + 회귀 테스트'
152
- ],
153
- research: [
154
- '1) leerness brainstorm "<주제>" — 누적 컨텍스트 회수',
155
- '2) leerness lessons --query "<주제>" — 과거 같은 영역 결정',
156
- '3) leerness review <file> --persona research — 깊이 검토',
157
- '4) leerness decision add "<결론>" — 회수 가능하게 영구화'
158
- ],
159
- planning: [
160
- '1) leerness plan add "<milestone>" — 분해 시작',
161
- '2) leerness reuse-map — 기존 자원 인벤토리',
162
- '3) leerness agents recommend planning — sub-agent 분배',
163
- '4) leerness session close — 결정 영구화'
164
- ],
165
- release: [
166
- '1) leerness health — production-ready 확인',
167
- '2) leerness audit + verify-code — 보안 + 검수',
168
- '3) leerness release bump + note + publish'
169
- ],
170
- consistency: [
171
- '1) leerness audit — design/reuse/handoff 일관성 검사',
172
- '2) leerness consistency check — 잠재 일관성 위반',
173
- '3) leerness drift check --auto-fix — 자동 회복'
174
- ]
175
- }[estimatedType] || [];
176
-
177
- // 6) 효율 제안 (적용 가능한 sub-agent + skill)
178
- const efficiencyHints = [];
179
- if (reuseCandidates.length > 0) {
180
- efficiencyHints.push(`🔁 기존 자원 ${reuseCandidates.length}건 발견 — 신규 구현 전 재사용 검토 권장`);
181
- }
182
- if (conflictHints.length > 0) {
183
- efficiencyHints.push(`⚠ 충돌 신호 ${conflictHints.length}건 — 과거 실패 lesson / 진행 중 task 확인 필요`);
184
- }
185
- if (planConflicts.length > 0) {
186
- efficiencyHints.push(`📋 진행 중 milestone ${planConflicts.length}건과 영역 겹침 가능 — plan 정렬 권장`);
187
- }
188
- if (featureConflicts.length > 0) {
189
- efficiencyHints.push(`🕸 Feature Graph ${featureConflicts.length}건 영역 겹침 — 의존성 사전 확인`);
190
- }
191
- // 다중 에이전트 분배 추천
192
- if (estimatedType === 'feature' || estimatedType === 'planning') {
193
- efficiencyHints.push(`👥 leerness agents recommend ${estimatedType} — 작업 유형별 sub-agent 매핑 활용 가능`);
194
- }
195
- if (efficiencyHints.length === 0) {
196
- efficiencyHints.push('✨ 충돌 신호 없음 — 즉시 진행 안전');
197
- }
198
-
199
- // 6.5) 1.9.208: 플랫폼/API 제약 사전 체크 — 사용자 명시 ("호출속도 초당 5회" 같은 규정 사전 확인)
200
- let constraintsCheck = { matched: [], suggestions: [] };
201
- try {
202
- constraintsCheck = _checkRequestConstraints(root, text);
203
- if (constraintsCheck.matched.length > 0) {
204
- efficiencyHints.push(`⚠ 플랫폼 제약 ${constraintsCheck.matched.length}건 — leerness constraints check 로 상세 확인`);
205
- }
206
- } catch {}
207
-
208
- // 7) proceed 권장 (충돌 critical false)
209
- const proceed = conflictHints.length < 3 && planConflicts.length === 0;
210
-
211
- const dt = Date.now() - t0;
212
- const out = {
213
- request: text,
214
- estimatedType,
215
- conflicts: conflictHints,
216
- reuseCandidates,
217
- lessonsRecall,
218
- planConflicts,
219
- featureConflicts,
220
- recommendedSteps,
221
- efficiencyHints,
222
- platformConstraints: constraintsCheck.matched,
223
- constraintSuggestions: constraintsCheck.suggestions,
224
- proceed,
225
- proceedReason: proceed ? '안전 — 충돌 신호 < 3 + plan 충돌 0' : '⚠ 충돌 critical — 사용자 확인 후 진행',
226
- durationMs: dt
227
- };
228
-
229
- try { _recordRun(root, { kind: 'review_request', estimatedType, conflicts: conflictHints.length, reuse: reuseCandidates.length, durationMs: dt, ok: true }); } catch {}
230
-
231
- if (has('--json')) {
232
- log(JSON.stringify(out, null, 2));
233
- return;
234
- }
235
-
236
- log(`# leerness review-request (1.9.176 사전 검토)`);
237
- log(`요청: "${text.slice(0, 200)}${text.length > 200 ? '…' : ''}"`);
238
- log(`추정 작업 유형: ${estimatedType}`);
239
- log('');
240
- if (conflictHints.length) {
241
- log(`## ⚠ 충돌 신호 (${conflictHints.length})`);
242
- conflictHints.slice(0, 5).forEach(c => log(` - [${c.kind}] ${c.title || c.id || ''} ${c.preview || ''}`.trim()));
243
- log('');
244
- }
245
- if (reuseCandidates.length) {
246
- log(`## 🔁 재사용 후보 (${reuseCandidates.length}) — 신규 구현 전 검토`);
247
- reuseCandidates.slice(0, 5).forEach(r => {
248
- if (r.kind === 'skill') log(` - [skill] ${r.id}${r.displayNameKo ? ' · ' + r.displayNameKo : ''}`);
249
- else if (r.kind === 'reuse-map') log(` - [reuse] ${r.capability} @ ${r.where}`);
250
- });
251
- log('');
252
- }
253
- if (lessonsRecall.length) {
254
- log(`## 🧠 과거 컨텍스트 (${lessonsRecall.length}) — 관련 결정/교훈`);
255
- lessonsRecall.slice(0, 3).forEach(l => log(` - [${l.kind}] ${l.title || l.preview}`));
256
- log('');
257
- }
258
- if (planConflicts.length || featureConflicts.length) {
259
- log(`## 📋 진행 중 영역 (${planConflicts.length + featureConflicts.length})`);
260
- planConflicts.forEach(m => log(` - [milestone] ${m.id}: ${m.title}`));
261
- featureConflicts.slice(0, 5).forEach(f => log(` - [feature] ${f.id}: ${f.title}`));
262
- log('');
263
- }
264
- log(`## 💡 효율 제안`);
265
- efficiencyHints.forEach(h => log(` ${h}`));
266
- log('');
267
- // 1.9.208: 플랫폼/API 제약 사전 노출 (사용자 명시)
268
- if (constraintsCheck.matched.length > 0) {
269
- log(`## 🚦 플랫폼/API 제약 사전 체크 (${constraintsCheck.matched.length})`);
270
- for (const m of constraintsCheck.matched) {
271
- log(` - 📦 ${m.platform} (docs: ${m.docs || '-'})`);
272
- for (const c of (m.constraints || []).slice(0, 3)) {
273
- log(` • [${c.kind}] ${c.detail}`);
274
- }
275
- }
276
- log(` leerness constraints check "${text.slice(0, 40)}…" 로 상세 확인`);
277
- log('');
278
- }
279
- if (recommendedSteps.length) {
280
- log(`## 📍 권장 단계 (${estimatedType})`);
281
- recommendedSteps.forEach(s => log(` ${s}`));
282
- log('');
283
- }
284
- log(`## 진행 권장: ${proceed ? '✓ 진행 안전' : '⚠ 사용자 확인 필요'}`);
285
- log(` 사유: ${out.proceedReason}`);
286
- log(` 분석 소요: ${dt}ms`);
287
- }
288
-
289
- module.exports = { reviewRequestCmd };
1
+ // lib/review-request.js — review-request 핸들러 (UR-0125 큰 핸들러 모듈화, 1.9.420)
2
+ // bin/leerness.js 에서 reviewRequestCmd(277줄) 분리. DI: harness 고유 의존(has · harnessPath · _checkRequestConstraints · _recordRun) 주입.
3
+ // io 프리미티브는 ./io, cp/path 는 빌트인. 동작/출력 무변경(thin wrapper 위임).
4
+ 'use strict';
5
+ const cp = require('child_process');
6
+ const path = require('path');
7
+ const { absRoot, exists, read, log, fail, failJson } = require('./io');
8
+
9
+ function reviewRequestCmd(root, request, deps = {}) {
10
+ const { has, harnessPath, _checkRequestConstraints, _recordRun } = deps;
11
+ root = absRoot(root || process.cwd());
12
+ if (!request || !String(request).trim()) {
13
+ // 1.9.428 (10th 외부평가 UR-0128): --json 오류 경로도 순수 JSON (failJson 이 모드 분기)
14
+ return failJson(!!(has && has('--json')), 'review_request_empty', 'leerness review-request "<request>" — 사용자 요청 텍스트 필요');
15
+ }
16
+ const t0 = Date.now();
17
+ const text = String(request).trim();
18
+
19
+ // 1) 작업 유형 추정 (route 기반 키워드 매핑)
20
+ const lower = text.toLowerCase();
21
+ const routeKw = {
22
+ bugfix: ['버그', '오류', '에러', '수정', '고쳐', '실패', 'fix', 'bug', 'error'],
23
+ refactor: ['리팩토', '재구성', '정리', '개선', 'refactor', 'cleanup'],
24
+ feature: ['추가', '구현', '만들', '새', '기능', 'add', 'implement', 'feature', 'create', 'new'],
25
+ research: ['조사', '분석', '비교', '검토', '연구', 'research', 'analyze', 'compare', 'investigate'],
26
+ planning: ['계획', '설계', '로드맵', 'plan', 'design', 'architecture', 'roadmap'],
27
+ release: ['배포', '릴리즈', '버전', 'release', 'deploy', 'publish'],
28
+ consistency: ['일관성', '통합', '동기화', '맞춰', 'consistency', 'sync', 'align']
29
+ };
30
+ let estimatedType = 'feature'; // default
31
+ let maxScore = 0;
32
+ for (const [type, kws] of Object.entries(routeKw)) {
33
+ const score = kws.filter(k => lower.includes(k)).length;
34
+ if (score > maxScore) { maxScore = score; estimatedType = type; }
35
+ }
36
+
37
+ // 2) 기존 자원 회수 — brainstorm spawn (모든 surface 통합 회수)
38
+ const conflictHints = []; // ⚠ 같은 키워드 + 실패/오류 패턴
39
+ const reuseCandidates = []; // 🔁 기존 skill / reuse-map / decision 후보
40
+ const lessonsRecall = []; // 🧠 과거 lesson
41
+ const planConflicts = []; // 📋 진행 중 milestone과 충돌 가능
42
+
43
+ // brainstorm 호출 (1.9.13~) — JSON 결과 회수
44
+ try {
45
+ const r = cp.spawnSync(process.execPath, [harnessPath, 'brainstorm', text, '--path', root, '--json'], {
46
+ encoding: 'utf8', timeout: 12000,
47
+ env: { ...process.env, LEERNESS_INTERNAL: '1', LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
48
+ });
49
+ if (r.stdout) {
50
+ const j = JSON.parse(r.stdout);
51
+ const hits = j.hits || {};
52
+ // decisions — 과거 결정 후보
53
+ (hits.decisions || []).slice(0, 5).forEach(d => {
54
+ lessonsRecall.push({ kind: 'decision', title: d.title, line: d.line, preview: (d.preview || '').slice(0, 100) });
55
+ });
56
+ // lessons — 과거 교훈 (특히 실패 키워드)
57
+ (hits.lessons || []).slice(0, 5).forEach(l => {
58
+ const preview = (l.text || l.preview || '').slice(0, 100);
59
+ const isFailure = /실패|오류|에러|fail|error|bug|문제|warning/i.test(preview);
60
+ if (isFailure) {
61
+ conflictHints.push({ kind: 'lesson-failure', preview, tags: l.tags });
62
+ } else {
63
+ lessonsRecall.push({ kind: 'lesson', preview, tags: l.tags });
64
+ }
65
+ });
66
+ // skills — 기존 skill 후보
67
+ (hits.skills || []).slice(0, 3).forEach(s => {
68
+ reuseCandidates.push({ kind: 'skill', id: s.id, displayNameKo: s.displayNameKo, capabilities: s.capabilities });
69
+ });
70
+ // tasks — 진행 중 task 충돌
71
+ (hits.tasks || []).slice(0, 3).forEach(tsk => {
72
+ if (tsk.status && /in-progress|진행/.test(String(tsk.status))) {
73
+ conflictHints.push({ kind: 'task-in-progress', id: tsk.id, title: tsk.title });
74
+ }
75
+ });
76
+ // plan milestones — 진행 중 milestone
77
+ (hits.planMilestones || []).slice(0, 3).forEach(m => {
78
+ if (m.status && /in-progress|진행/.test(String(m.status))) {
79
+ planConflicts.push({ kind: 'milestone-in-progress', id: m.id, title: m.title });
80
+ }
81
+ });
82
+ // taskLogFails — 과거 같은 키워드 실패 흔적
83
+ (hits.taskLogFails || []).slice(0, 3).forEach(f => {
84
+ conflictHints.push({ kind: 'task-log-failure', preview: (f.preview || f.text || '').slice(0, 100) });
85
+ });
86
+ }
87
+ } catch {}
88
+
89
+ // 3) reuse-map 매칭 — 기존 capability 등록 후보
90
+ try {
91
+ const reusePath = path.join(root, '.harness/reuse-map.md');
92
+ if (exists(reusePath)) {
93
+ const reuseLines = read(reusePath).split('\n');
94
+ const tokens = lower.split(/\s+/).filter(t => t.length >= 3);
95
+ for (const line of reuseLines) {
96
+ if (!/^\| /.test(line)) continue; // 테이블 row만
97
+ const ll = line.toLowerCase();
98
+ const matched = tokens.filter(t => ll.includes(t)).length;
99
+ if (matched > 0) {
100
+ const cols = line.split('|').map(s => s.trim());
101
+ if (cols[1]) {
102
+ reuseCandidates.push({ kind: 'reuse-map', capability: cols[1], where: cols[2] || '', note: cols[3] || '' });
103
+ }
104
+ }
105
+ }
106
+ }
107
+ } catch {}
108
+
109
+ // 4) feature_graph — 같은 영역 변경 가능성
110
+ const featureConflicts = [];
111
+ try {
112
+ const fgPath = path.join(root, '.harness/feature_graph.md');
113
+ if (exists(fgPath)) {
114
+ const fg = read(fgPath);
115
+ const tokens = lower.split(/\s+/).filter(t => t.length >= 4);
116
+ // F-XXXX 노드 라인 추출
117
+ const nodeBlocks = fg.split(/\n### /);
118
+ for (const blk of nodeBlocks.slice(1)) {
119
+ const bl = blk.toLowerCase();
120
+ const matched = tokens.filter(t => bl.includes(t)).length;
121
+ if (matched > 0) {
122
+ const titleMatch = blk.match(/^([^\n]+)/);
123
+ const idMatch = blk.match(/F-\d+/);
124
+ if (titleMatch && idMatch) {
125
+ featureConflicts.push({ kind: 'feature', id: idMatch[0], title: titleMatch[1].trim() });
126
+ }
127
+ }
128
+ }
129
+ }
130
+ } catch {}
131
+
132
+ // 5) 권장 단계 (작업 유형별)
133
+ const recommendedSteps = {
134
+ feature: [
135
+ '1) leerness reuse-check "<기능>" — 외부 OSS 빌드 vs 재사용 판단 (1.9.285)',
136
+ '2) leerness reuse find "<핵심 capability>" — 내부 중복 구현 사전 차단',
137
+ '3) leerness plan add "<milestone>" — 진행 추적',
138
+ '4) leerness contract verify SPEC.md src/<mod>.js — 사양 ↔ 구현 일치 검증',
139
+ '5) verify-claim --run-tests 로 evidence 의무화'
140
+ ],
141
+ bugfix: [
142
+ '1) leerness brainstorm "<버그 키워드>" — 과거 같은 영역 lesson 회수',
143
+ '2) leerness verify-claim T-XXX --strict-claims — 낙관적 표시 사전 감지',
144
+ '3) verify-code --run-tests — 재현 + fix 검증',
145
+ '4) leerness lesson save "<root cause>" — 같은 실수 재발 차단'
146
+ ],
147
+ refactor: [
148
+ '1) leerness reuse-map — 영향 범위 파악',
149
+ '2) leerness impact <file> — 강한/약한 참조 분리',
150
+ '3) leerness contract verify — 외부 인터페이스 보존 확인',
151
+ '4) verify-code --run-tests + 회귀 테스트'
152
+ ],
153
+ research: [
154
+ '1) leerness brainstorm "<주제>" — 누적 컨텍스트 회수',
155
+ '2) leerness lessons --query "<주제>" — 과거 같은 영역 결정',
156
+ '3) leerness review <file> --persona research — 깊이 검토',
157
+ '4) leerness decision add "<결론>" — 회수 가능하게 영구화'
158
+ ],
159
+ planning: [
160
+ '1) leerness plan add "<milestone>" — 분해 시작',
161
+ '2) leerness reuse-map — 기존 자원 인벤토리',
162
+ '3) leerness agents recommend planning — sub-agent 분배',
163
+ '4) leerness session close — 결정 영구화'
164
+ ],
165
+ release: [
166
+ '1) leerness health — production-ready 확인',
167
+ '2) leerness audit + verify-code — 보안 + 검수',
168
+ '3) leerness release bump + note + publish'
169
+ ],
170
+ consistency: [
171
+ '1) leerness audit — design/reuse/handoff 일관성 검사',
172
+ '2) leerness consistency check — 잠재 일관성 위반',
173
+ '3) leerness drift check --auto-fix — 자동 회복'
174
+ ]
175
+ }[estimatedType] || [];
176
+
177
+ // 6) 효율 제안 (적용 가능한 sub-agent + skill)
178
+ const efficiencyHints = [];
179
+ if (reuseCandidates.length > 0) {
180
+ efficiencyHints.push(`🔁 기존 자원 ${reuseCandidates.length}건 발견 — 신규 구현 전 재사용 검토 권장`);
181
+ }
182
+ if (conflictHints.length > 0) {
183
+ efficiencyHints.push(`⚠ 충돌 신호 ${conflictHints.length}건 — 과거 실패 lesson / 진행 중 task 확인 필요`);
184
+ }
185
+ if (planConflicts.length > 0) {
186
+ efficiencyHints.push(`📋 진행 중 milestone ${planConflicts.length}건과 영역 겹침 가능 — plan 정렬 권장`);
187
+ }
188
+ if (featureConflicts.length > 0) {
189
+ efficiencyHints.push(`🕸 Feature Graph ${featureConflicts.length}건 영역 겹침 — 의존성 사전 확인`);
190
+ }
191
+ // 다중 에이전트 분배 추천
192
+ if (estimatedType === 'feature' || estimatedType === 'planning') {
193
+ efficiencyHints.push(`👥 leerness agents recommend ${estimatedType} — 작업 유형별 sub-agent 매핑 활용 가능`);
194
+ }
195
+ if (efficiencyHints.length === 0) {
196
+ efficiencyHints.push('✨ 충돌 신호 없음 — 즉시 진행 안전');
197
+ }
198
+
199
+ // 6.5) 1.9.208: 플랫폼/API 제약 사전 체크 — 사용자 명시 ("호출속도 초당 5회" 같은 규정 사전 확인)
200
+ let constraintsCheck = { matched: [], suggestions: [] };
201
+ try {
202
+ constraintsCheck = _checkRequestConstraints(root, text);
203
+ if (constraintsCheck.matched.length > 0) {
204
+ efficiencyHints.push(`⚠ 플랫폼 제약 ${constraintsCheck.matched.length}건 — leerness constraints check 로 상세 확인`);
205
+ }
206
+ } catch {}
207
+
208
+ // 6.7) 1.14.1 (Karpathy 가이드라인 1+2, UR-0031): 범위 과대 / 투기적 신호 표면화 — "생각하고 코딩"(트레이드오프 표면화) + "단순성 우선"(요청 범위만, 추측성 일반화 보류). advisory(차단 X).
209
+ const broadHits = [...new Set((text.match(/(전체|싹\s*다|모두|전부|모든\s*(?:코드|파일|것)|리팩토링|리팩터|재구성|재작성|갈아\s*엎|overhaul|rewrite\s+everything|refactor\s+everything)/gi) || []).map(s => s.trim()))];
210
+ const specHits = [...new Set((text.match(/(나중에|추후|미래\s*대비|확장\s*가능|유연하게|범용화|일반화|추상화|future[-\s]?proof|extensible|pluggable|일단\s*만들)/gi) || []).map(s => s.trim()))];
211
+ const simplicitySignals = { broad: broadHits, speculative: specHits };
212
+ if (broadHits.length) efficiencyHints.push(`🤔 범위 과대 신호(${broadHits.slice(0, 3).join(', ')}) — 더 작게 쪼갤 수 있나? 요청에 명시된 것만 (Karpathy 단순성)`);
213
+ if (specHits.length) efficiencyHints.push(`🧪 투기적 신호(${specHits.slice(0, 3).join(', ')}) — 지금 필요한 범위만, 추측성 일반화 보류 (Karpathy 단순성)`);
214
+
215
+ // 7) proceed 권장 (충돌 critical 시 false)
216
+ const proceed = conflictHints.length < 3 && planConflicts.length === 0;
217
+
218
+ const dt = Date.now() - t0;
219
+ const out = {
220
+ request: text,
221
+ estimatedType,
222
+ conflicts: conflictHints,
223
+ reuseCandidates,
224
+ lessonsRecall,
225
+ planConflicts,
226
+ featureConflicts,
227
+ recommendedSteps,
228
+ efficiencyHints,
229
+ simplicitySignals,
230
+ platformConstraints: constraintsCheck.matched,
231
+ constraintSuggestions: constraintsCheck.suggestions,
232
+ proceed,
233
+ proceedReason: proceed ? '안전 — 충돌 신호 < 3 + plan 충돌 0' : '⚠ 충돌 critical — 사용자 확인 후 진행',
234
+ durationMs: dt
235
+ };
236
+
237
+ try { _recordRun(root, { kind: 'review_request', estimatedType, conflicts: conflictHints.length, reuse: reuseCandidates.length, durationMs: dt, ok: true }); } catch {}
238
+
239
+ if (has('--json')) {
240
+ log(JSON.stringify(out, null, 2));
241
+ return;
242
+ }
243
+
244
+ log(`# leerness review-request (1.9.176 사전 검토)`);
245
+ log(`요청: "${text.slice(0, 200)}${text.length > 200 ? '…' : ''}"`);
246
+ log(`추정 작업 유형: ${estimatedType}`);
247
+ log('');
248
+ if (conflictHints.length) {
249
+ log(`## 충돌 신호 (${conflictHints.length})`);
250
+ conflictHints.slice(0, 5).forEach(c => log(` - [${c.kind}] ${c.title || c.id || ''} ${c.preview || ''}`.trim()));
251
+ log('');
252
+ }
253
+ if (reuseCandidates.length) {
254
+ log(`## 🔁 재사용 후보 (${reuseCandidates.length}) — 신규 구현 전 검토`);
255
+ reuseCandidates.slice(0, 5).forEach(r => {
256
+ if (r.kind === 'skill') log(` - [skill] ${r.id}${r.displayNameKo ? ' · ' + r.displayNameKo : ''}`);
257
+ else if (r.kind === 'reuse-map') log(` - [reuse] ${r.capability} @ ${r.where}`);
258
+ });
259
+ log('');
260
+ }
261
+ if (lessonsRecall.length) {
262
+ log(`## 🧠 과거 컨텍스트 (${lessonsRecall.length}) — 관련 결정/교훈`);
263
+ lessonsRecall.slice(0, 3).forEach(l => log(` - [${l.kind}] ${l.title || l.preview}`));
264
+ log('');
265
+ }
266
+ if (planConflicts.length || featureConflicts.length) {
267
+ log(`## 📋 진행 영역 (${planConflicts.length + featureConflicts.length})`);
268
+ planConflicts.forEach(m => log(` - [milestone] ${m.id}: ${m.title}`));
269
+ featureConflicts.slice(0, 5).forEach(f => log(` - [feature] ${f.id}: ${f.title}`));
270
+ log('');
271
+ }
272
+ log(`## 💡 효율 제안`);
273
+ efficiencyHints.forEach(h => log(` ${h}`));
274
+ log('');
275
+ // 1.9.208: 플랫폼/API 제약 사전 노출 (사용자 명시)
276
+ if (constraintsCheck.matched.length > 0) {
277
+ log(`## 🚦 플랫폼/API 제약 사전 체크 (${constraintsCheck.matched.length})`);
278
+ for (const m of constraintsCheck.matched) {
279
+ log(` - 📦 ${m.platform} (docs: ${m.docs || '-'})`);
280
+ for (const c of (m.constraints || []).slice(0, 3)) {
281
+ log(` • [${c.kind}] ${c.detail}`);
282
+ }
283
+ }
284
+ log(` → leerness constraints check "${text.slice(0, 40)}…" 상세 확인`);
285
+ log('');
286
+ }
287
+ if (recommendedSteps.length) {
288
+ log(`## 📍 권장 단계 (${estimatedType})`);
289
+ recommendedSteps.forEach(s => log(` ${s}`));
290
+ log('');
291
+ }
292
+ log(`## ▶ 진행 권장: ${proceed ? '✓ 진행 안전' : '⚠ 사용자 확인 필요'}`);
293
+ log(` 사유: ${out.proceedReason}`);
294
+ log(` 분석 소요: ${dt}ms`);
295
+ }
296
+
297
+ module.exports = { reviewRequestCmd };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",