leerness 1.9.283 → 1.9.285
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 +36 -0
- package/README.md +5 -5
- package/bin/harness.js +81 -7
- package/package.json +1 -1
- package/scripts/e2e.js +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.285 — 2026-06-03 — UR-0023: 외부 OSS "빌드 vs 재사용" 결정 게이트 (오프라인)
|
|
4
|
+
|
|
5
|
+
**🔍 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트 (GPT-5.5 / 사용자 요청). 네트워크 크롤러 아님 — offline-first 유지.**
|
|
6
|
+
|
|
7
|
+
### 배경 + 설계 판단
|
|
8
|
+
사용자 요청(이전 라운드 기록 UR-0023): 기능 계획 시 GitHub/HuggingFace 등에 유사 OSS 존재 여부 파악 → 재사용 vs 신규. **권고대로 opt-in·offline 게이트**로 구현: leerness 는 네트워크 자동 호출 안 함(호스트 AI 가 실제 탐색), 대신 카테고리 후보 + 적합성 체크리스트를 제공해 **"재사용 검토 생략"을 방지**. 기존 `reuse`(내부 자원)와 명확히 구분.
|
|
9
|
+
|
|
10
|
+
### 구현
|
|
11
|
+
1. **`leerness reuse-check "<기능>"`** — 키워드→OSS 카테고리 14종(auth/http/date/validation/state/ui/markdown/cli/db/test/pdf/csv/queue/i18n/logging) 매칭 → 잘 알려진 후보 라이브러리 제시 + **6항 적합성 체크리스트**(라이선스/유지보수/보안/적합성/통합비용/제어) + 결정 템플릿. `--json`.
|
|
12
|
+
2. **`review-request` feature 권장 단계 통합** — feature 요청 시 1단계로 `reuse-check` 자동 안내 (게이트 자동 노출).
|
|
13
|
+
3. **offline 명시** — "실제 GitHub/HuggingFace 탐색은 호스트 AI 수행, leerness 는 네트워크 자동 호출 안 함".
|
|
14
|
+
4. selftest 31→32 + e2e 229→230.
|
|
15
|
+
|
|
16
|
+
### 검증
|
|
17
|
+
- **selftest 32/32 PASS** · **E2E 230/230 PASS · 301s** (회귀 0) · auth/date 카테고리 감지 + 체크리스트 + 인자 누락 exit 1 실측.
|
|
18
|
+
|
|
19
|
+
## 1.9.284 — 2026-06-03 — UR-0029: handoff 성능 회귀 수정 (10 provider 전부 spawn → 활성만) + e2e 단축
|
|
20
|
+
|
|
21
|
+
**⚡ GPT-5.5 "e2e 5분 초과" 추적 중 발견 — `handoff` 가 매번 10개 provider 의 `--version` 을 전부 spawn(≈5.5s 오버헤드). 활성(env=1) provider 만 체크하도록 수정 → handoff 7.3s → 2.6s.**
|
|
22
|
+
|
|
23
|
+
### 배경 (프로파일 기반 디버그)
|
|
24
|
+
GPT-5.5 가 두 번 "npm test(e2e) 5분 내 미완료" 지적. 실측 336s. `node --prof` 프로파일 결과 **handoff 시간의 90%가 `spawnSync`** — handoff 헤드라인이 `EXTERNAL_AGENTS.map(_checkAgent)` 로 **10개 provider 의 `--version` 을 매번 전부 실행**(1.9.277 provider 6→10 으로 악화). 'ready'(=활성+설치) 는 **활성(env flag=1) 일 때만** 가능하므로 비활성 provider spawn 은 순수 낭비.
|
|
25
|
+
|
|
26
|
+
### 구현
|
|
27
|
+
1. **handoff 헤드라인 agent 체크 — 활성 필터 선행** (bin/harness.js:7723): `EXTERNAL_AGENTS.filter(a => process.env[a.envFlag]==='1').map(_checkAgent)`. 결과 동일(비활성은 ready 불가), spawn 만 제거. **활성 0(대부분/모든 e2e) → spawn 0**.
|
|
28
|
+
- 효과: handoff **7.3s → 2.6s** (회당 4.6s↓). 실사용에서도 매 handoff 5초 절감 + provider 확장 회귀 영구 해결.
|
|
29
|
+
2. **e2e roadmap 기본 OFF** (roadmap 전용 블록만 ON) + **총 소요시간 출력**(투명성).
|
|
30
|
+
3. e2e 총 **336s → 306s**. (구조적으로 133 init × ~1.1s 가 잔여 비용 — init 은 I/O 바운드.)
|
|
31
|
+
|
|
32
|
+
### 재현성 (GPT 우려 해소)
|
|
33
|
+
- 빠른 게이트: **`npm run test:fast`** (selftest + smoke 13종, ~10s) — 로컬/PR 즉시 검증.
|
|
34
|
+
- 전체 e2e: 229 케이스(subprocess 바운드 ~5min) — CI 매트릭스(ubuntu+windows × node 18/20/22) 병렬 실행으로 재현.
|
|
35
|
+
|
|
36
|
+
### 검증
|
|
37
|
+
- **selftest 31/31 PASS** · **E2E 229/229 PASS** (회귀 0) · handoff 활성 0 시 spawn 0 실측.
|
|
38
|
+
|
|
3
39
|
## 1.9.283 — 2026-06-03 — UR-0025 2단계: 순수 함수 추가 모듈 분리 (lib/pure-utils 7→14종)
|
|
4
40
|
|
|
5
41
|
**🧩 GPT-5.5 "1.2MB 단일 파일" 지적의 점진적·비파괴 모듈화 계속 — 최근 추가한 순수 로직을 lib/ 로 추출.**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -471,7 +471,7 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.285 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### Core Commands
|
|
477
477
|
|
|
@@ -513,7 +513,7 @@ leerness memory restore decision <date|title>
|
|
|
513
513
|
|
|
514
514
|
### MCP server (외부 AI 통합)
|
|
515
515
|
|
|
516
|
-
Leerness v1.9.
|
|
516
|
+
Leerness v1.9.285는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
|
|
517
517
|
|
|
518
518
|
```jsonc
|
|
519
519
|
// 카테고리별
|
|
@@ -534,7 +534,7 @@ Leerness v1.9.283는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
534
534
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
535
535
|
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) 다음 라운드 예약.
|
|
536
536
|
|
|
537
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
537
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.285)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
538
538
|
|
|
539
539
|
### 성능 가이드 (1.9.140 측정)
|
|
540
540
|
|
|
@@ -572,6 +572,6 @@ leerness release pack --close --auto-main-push
|
|
|
572
572
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
573
573
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
574
574
|
|
|
575
|
-
Last synced by Leerness v1.9.
|
|
575
|
+
Last synced by Leerness v1.9.285: 2026-06-03
|
|
576
576
|
<!-- leerness:project-readme:end -->
|
|
577
577
|
|
package/bin/harness.js
CHANGED
|
@@ -10,7 +10,7 @@ const readline = require('readline');
|
|
|
10
10
|
const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
|
|
11
11
|
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord } = require('../lib/pure-utils');
|
|
12
12
|
|
|
13
|
-
const VERSION = '1.9.
|
|
13
|
+
const VERSION = '1.9.285';
|
|
14
14
|
|
|
15
15
|
// 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
|
|
16
16
|
// leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
|
|
@@ -2961,6 +2961,7 @@ function _selfTestCases() {
|
|
|
2961
2961
|
{ name: '_pickModel: top/code/fast 등급 선택 (1.9.270)', run: () => { return _pickModel('codex', 'code') === 'gpt-5-codex' && _pickModel('claude', 'top') === 'claude-opus-4-7' && /haiku/.test(_pickModel('claude', 'fast')); } },
|
|
2962
2962
|
{ name: 'CAPABILITY_SURFACE: 6 영역 + risk/optOut + 주의명령 (1.9.272)', run: () => { const ks = Object.keys(CAPABILITY_SURFACE); return ks.length === 6 && ks.includes('automationBridges') && Object.values(CAPABILITY_SURFACE).every(v => ['low','medium','high'].includes(v.risk) && v.optOut && v.desc) && POWERFUL_COMMANDS.length >= 5; } },
|
|
2963
2963
|
{ name: '_resolveNpmTag: latest 기본 + next 허용 + 잘못된 형식 폴백 (1.9.275)', run: () => _resolveNpmTag(null, {}) === 'latest' && _resolveNpmTag('next', {}) === 'next' && _resolveNpmTag(null, { LEERNESS_NPM_TAG: 'next' }) === 'next' && _resolveNpmTag('bad tag!', {}) === 'latest' && _resolveNpmTag('Beta', {}) === 'beta' },
|
|
2964
|
+
{ name: '_reuseDetect: 키워드→OSS 카테고리 + 체크리스트 (1.9.285)', run: () => { const a = _reuseDetect('JWT 인증 구현'); const b = _reuseDetect('날짜 포맷 date'); const c = _reuseDetect('전혀무관한xyzzy'); return a.some(x => x.key === 'auth') && b.some(x => x.key === 'date') && c.length === 0 && REUSE_CHECKLIST.length >= 5 && REUSE_CATEGORIES.length >= 12; } },
|
|
2964
2965
|
{ name: 'AGENTS.md: 정적 vs 동적 leerness 역할 경계 (1.9.282)', run: () => { const a = coreFiles('.', 'ko', [])['AGENTS.md']; return /정적 vs 동적/.test(a) && /대체하지 않고 \*\*보완\*\*|대체하지 않고 보완/.test(a) && /leerness state/.test(a) && /\.leerness\//.test(a); } },
|
|
2965
2966
|
{ name: '권한 등급: _requiredTier + _policyAllows 순서 (1.9.281)', run: () => { return _requiredTier('release publish') === 'publish' && _requiredTier('agents multi --execute') === 'shell-write' && _requiredTier('handoff') === 'read-only' && _requiredTier('init') === 'project-write' && _policyAllows('project-write', 'safe-write') === true && _policyAllows('project-write', 'publish') === false && _tierRank('read-only') < _tierRank('publish'); } },
|
|
2966
2967
|
{ name: 'ADAPTERS + _mcpJsonContent: 도구 매핑 + .mcp.json 등록 (1.9.280)', run: () => { const ids = Object.keys(ADAPTERS); const okMap = ['claude','cursor','codex','goose','opencode'].every(id => ADAPTERS[id] && Array.isArray(ADAPTERS[id].keys) && ADAPTERS[id].keys.length); const m = JSON.parse(_mcpJsonContent()); return ids.length >= 9 && okMap && ADAPTERS.claude.mcp === true && ADAPTERS.copilot.mcp === false && m.mcpServers.leerness.command === 'npx' && m.mcpServers.leerness.args.join(' ') === 'leerness mcp serve'; } },
|
|
@@ -3626,6 +3627,7 @@ function commandsCmd(root) {
|
|
|
3626
3627
|
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
|
|
3627
3628
|
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
3628
3629
|
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
|
|
3630
|
+
{ cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
|
|
3629
3631
|
{ cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275' },
|
|
3630
3632
|
{ cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
|
|
3631
3633
|
{ cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
|
|
@@ -7720,7 +7722,9 @@ function handoff(root) {
|
|
|
7720
7722
|
// 8) 1.9.152: 활성 외부 AI CLI 카운트 (1.9.151 복수 선택 결과 반영) — 메인 에이전트가 sub-agent 분배 가능성 즉시 인지
|
|
7721
7723
|
try {
|
|
7722
7724
|
_loadEnvFile(root); // .env 자동 로드 (handoff 컨텍스트)
|
|
7723
|
-
|
|
7725
|
+
// 1.9.284 (UR-0029) 성능: 'ready'(=활성+설치)는 활성(env=1)일 때만 가능 → 활성 agent 만 --version spawn.
|
|
7726
|
+
// 비활성(대부분/모든 e2e) 은 spawn 0 (이전: 10개 전부 spawn = handoff 5.5s 병목, provider 6→10 으로 악화).
|
|
7727
|
+
const ready = EXTERNAL_AGENTS.filter(a => process.env[a.envFlag] === '1').map(a => _checkAgent(a)).filter(c => c.status === 'ready');
|
|
7724
7728
|
if (ready.length > 0) {
|
|
7725
7729
|
parts.push(`🤖 agents ${ready.length} (${ready.map(c => c.id).join(',')})`);
|
|
7726
7730
|
}
|
|
@@ -18312,6 +18316,71 @@ function adapterCmd(root, tool, opts = {}) {
|
|
|
18312
18316
|
return;
|
|
18313
18317
|
}
|
|
18314
18318
|
|
|
18319
|
+
// ===== 1.9.285 (UR-0023, GPT-5.5): 외부 OSS "빌드 vs 재사용" 결정 게이트 =====
|
|
18320
|
+
// 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트.
|
|
18321
|
+
// 네트워크 크롤러가 아님(offline-first 유지) — 호스트 AI(메인)가 실제 OSS 탐색을 하되, 이 게이트가
|
|
18322
|
+
// 카테고리 후보 + 적합성 체크리스트를 제공해 "재사용 검토 생략"을 방지. 기존 `reuse`(내부 자원)와 구분.
|
|
18323
|
+
const REUSE_CATEGORIES = [
|
|
18324
|
+
{ key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
|
|
18325
|
+
{ key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
|
|
18326
|
+
{ key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
|
|
18327
|
+
{ key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
|
|
18328
|
+
{ key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
|
|
18329
|
+
{ key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
|
|
18330
|
+
{ key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
|
|
18331
|
+
{ key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
|
|
18332
|
+
{ key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
|
|
18333
|
+
{ key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
|
|
18334
|
+
{ key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
|
|
18335
|
+
{ key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
|
|
18336
|
+
{ key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
|
|
18337
|
+
{ key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
|
|
18338
|
+
{ key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
|
|
18339
|
+
];
|
|
18340
|
+
function _reuseDetect(feature) {
|
|
18341
|
+
const t = String(feature || '').toLowerCase();
|
|
18342
|
+
return REUSE_CATEGORIES.filter(c => c.kw.some(k => t.includes(k)));
|
|
18343
|
+
}
|
|
18344
|
+
const REUSE_CHECKLIST = [
|
|
18345
|
+
'라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
|
|
18346
|
+
'유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
|
|
18347
|
+
'보안: 알려진 취약점이 없는가 (npm audit / advisory)',
|
|
18348
|
+
'적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
|
|
18349
|
+
'통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
|
|
18350
|
+
'제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
|
|
18351
|
+
];
|
|
18352
|
+
function reuseCheckCmd(root, feature, opts = {}) {
|
|
18353
|
+
const json = has('--json');
|
|
18354
|
+
feature = String(feature || '').trim();
|
|
18355
|
+
if (!feature) { fail('reuse-check "<구현하려는 기능>" 필요 (예: leerness reuse-check "JWT 인증")'); process.exitCode = 1; return; }
|
|
18356
|
+
const matched = _reuseDetect(feature);
|
|
18357
|
+
if (json) {
|
|
18358
|
+
log(JSON.stringify({ feature, categories: matched.map(c => ({ key: c.key, candidates: c.candidates })), checklist: REUSE_CHECKLIST, network: false, note: '오프라인 게이트 — 실제 OSS 탐색/검증은 호스트 AI(GitHub/HuggingFace 등)가 수행' }, null, 2));
|
|
18359
|
+
return;
|
|
18360
|
+
}
|
|
18361
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
18362
|
+
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
18363
|
+
const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
18364
|
+
log(cy(`# leerness reuse-check (1.9.285, UR-0023) — 빌드 vs 재사용 결정 게이트`));
|
|
18365
|
+
log(`기능: ${feature}`);
|
|
18366
|
+
log('');
|
|
18367
|
+
if (matched.length) {
|
|
18368
|
+
log(`## 먼저 검토할 OSS 카테고리 (${matched.length})`);
|
|
18369
|
+
for (const c of matched) log(` • ${c.key.padEnd(11)} 후보: ${c.candidates}`);
|
|
18370
|
+
} else {
|
|
18371
|
+
log(`## 일반 카테고리 미매칭 — 그래도 아래를 검토`);
|
|
18372
|
+
log(dm(` GitHub/HuggingFace/awesome-* 목록에서 유사 구현을 먼저 검색하세요.`));
|
|
18373
|
+
}
|
|
18374
|
+
log('');
|
|
18375
|
+
log(`## 적합성 체크리스트 (재사용 결정 전)`);
|
|
18376
|
+
for (const q of REUSE_CHECKLIST) log(` [ ] ${q}`);
|
|
18377
|
+
log('');
|
|
18378
|
+
log(`## 결정`);
|
|
18379
|
+
log(` → 위 체크 통과하는 OSS 가 있으면 재사용, 없으면 신규 구현 (이유를 .harness/reuse-map.md 또는 decisions 에 기록).`);
|
|
18380
|
+
log(dm(` ※ 오프라인 게이트입니다. 실제 GitHub/HuggingFace 탐색·검증은 호스트 AI 가 수행하세요 (leerness 는 네트워크 자동 호출 안 함).`));
|
|
18381
|
+
return;
|
|
18382
|
+
}
|
|
18383
|
+
|
|
18315
18384
|
// 1.9.149+1.9.153: REPL 모드 — leerness 자율 AI 에이전트 (multi-provider 세션)
|
|
18316
18385
|
async function _agentRepl(root, opts) {
|
|
18317
18386
|
// 1.9.153: .env 자동 로드 (REPL 진입 직전) — install 직후 LEERNESS_ENABLE_* 즉시 반영
|
|
@@ -20783,10 +20852,11 @@ function reviewRequestCmd(root, request) {
|
|
|
20783
20852
|
// 5) 권장 단계 (작업 유형별)
|
|
20784
20853
|
const recommendedSteps = {
|
|
20785
20854
|
feature: [
|
|
20786
|
-
'1) leerness reuse
|
|
20787
|
-
'2) leerness
|
|
20788
|
-
'3) leerness
|
|
20789
|
-
'4) verify
|
|
20855
|
+
'1) leerness reuse-check "<기능>" — 외부 OSS 빌드 vs 재사용 판단 (1.9.285)',
|
|
20856
|
+
'2) leerness reuse find "<핵심 capability>" — 내부 중복 구현 사전 차단',
|
|
20857
|
+
'3) leerness plan add "<milestone>" — 진행 추적',
|
|
20858
|
+
'4) leerness contract verify SPEC.md src/<mod>.js — 사양 ↔ 구현 일치 검증',
|
|
20859
|
+
'5) verify-claim --run-tests 로 evidence 의무화'
|
|
20790
20860
|
],
|
|
20791
20861
|
bugfix: [
|
|
20792
20862
|
'1) leerness brainstorm "<버그 키워드>" — 과거 같은 영역 lesson 회수',
|
|
@@ -21242,6 +21312,8 @@ async function main() {
|
|
|
21242
21312
|
if (cmd === 'state') return stateCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21243
21313
|
// 1.9.281 (UR-0034): leerness policy <show|set|check> — 권한 등급 (opt-in enforced)
|
|
21244
21314
|
if (cmd === 'policy') return policyCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21315
|
+
// 1.9.285 (UR-0023): leerness reuse-check "<기능>" — 외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인)
|
|
21316
|
+
if (cmd === 'reuse-check') return reuseCheckCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')).join(' '));
|
|
21245
21317
|
// 1.9.280 (UR-0033): leerness adapter <tool> [path] — 도구별 지침/.mcp.json 선택 생성 (path: 위치 args[2] 또는 --path)
|
|
21246
21318
|
if (cmd === 'adapter') {
|
|
21247
21319
|
const _aTool = args[1] && !args[1].startsWith('-') ? args[1] : 'list';
|
|
@@ -21434,5 +21506,7 @@ module.exports = {
|
|
|
21434
21506
|
// 1.9.280: adapter (UR-0033) — 단위 테스트
|
|
21435
21507
|
ADAPTERS, _mcpJsonContent, _mergeMcpJson, adapterCmd,
|
|
21436
21508
|
// 1.9.281: 권한 등급 (UR-0034) — 단위 테스트
|
|
21437
|
-
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _loadPolicy, _savePolicy, _policyEnforce, policyCmd
|
|
21509
|
+
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _loadPolicy, _savePolicy, _policyEnforce, policyCmd,
|
|
21510
|
+
// 1.9.285: 외부 OSS 재사용 게이트 (UR-0023) — 단위 테스트
|
|
21511
|
+
REUSE_CATEGORIES, REUSE_CHECKLIST, _reuseDetect, reuseCheckCmd
|
|
21438
21512
|
};
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -8,9 +8,13 @@ const cp = require('child_process');
|
|
|
8
8
|
|
|
9
9
|
// 1.9.12: e2e 안정성을 위해 자식 프로세스의 npm 호출 차단 (hang 방지)
|
|
10
10
|
process.env.LEERNESS_OFFLINE = process.env.LEERNESS_OFFLINE || '1';
|
|
11
|
+
// 1.9.284 (UR-0029): e2e 속도 — 기본 roadmap.html(70KB HTML) 자동 생성 OFF (roadmap 전용 테스트 블록만 일시 ON).
|
|
12
|
+
// 대부분의 init/session 테스트는 roadmap 을 검증하지 않으므로 생성 비용 제거 → 5분 내 완료.
|
|
13
|
+
process.env.LEERNESS_NO_AUTO_ROADMAP = '1';
|
|
11
14
|
const CLI = path.resolve(__dirname, '..', 'bin', 'harness.js');
|
|
12
15
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-e2e-'));
|
|
13
16
|
let failed = 0; let total = 0;
|
|
17
|
+
const _e2eStart = Date.now(); // 1.9.284 (UR-0029): 총 소요시간 투명성
|
|
14
18
|
|
|
15
19
|
function run(label, args, opts = {}) {
|
|
16
20
|
total++;
|
|
@@ -2217,6 +2221,8 @@ total++;
|
|
|
2217
2221
|
if (!ok) failed++;
|
|
2218
2222
|
}
|
|
2219
2223
|
|
|
2224
|
+
// 1.9.284: roadmap 전용 테스트 블록 — roadmap 생성 일시 ON (이후 다시 OFF)
|
|
2225
|
+
process.env.LEERNESS_NO_AUTO_ROADMAP = '0';
|
|
2220
2226
|
// 1.9.12: auto roadmap — install 직후 자동 생성
|
|
2221
2227
|
total++;
|
|
2222
2228
|
{
|
|
@@ -2278,6 +2284,8 @@ total++;
|
|
|
2278
2284
|
console.log(ok ? '✓ B(1.9.12) --on-every-change: task add만으로 즉시 갱신' : `✗ on-every-change 미작동`);
|
|
2279
2285
|
if (!ok) failed++;
|
|
2280
2286
|
}
|
|
2287
|
+
// 1.9.284: roadmap 전용 블록 종료 — 다시 OFF (나머지 테스트 속도)
|
|
2288
|
+
process.env.LEERNESS_NO_AUTO_ROADMAP = '1';
|
|
2281
2289
|
|
|
2282
2290
|
// 1.9.12: status 출력
|
|
2283
2291
|
total++;
|
|
@@ -2962,5 +2970,21 @@ total++;
|
|
|
2962
2970
|
if (!ok) failed++;
|
|
2963
2971
|
}
|
|
2964
2972
|
|
|
2965
|
-
|
|
2973
|
+
// 1.9.285 회귀 (UR-0023): reuse-check — 외부 OSS 빌드 vs 재사용 게이트
|
|
2974
|
+
total++;
|
|
2975
|
+
{
|
|
2976
|
+
const r1 = cp.spawnSync(process.execPath, [CLI, 'reuse-check', 'JWT 인증 로그인', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
2977
|
+
let jsonOk = false;
|
|
2978
|
+
try { const j = JSON.parse(r1.stdout); jsonOk = j.feature && j.categories.some(c => c.key === 'auth') && Array.isArray(j.checklist) && j.checklist.length >= 5 && j.network === false; } catch {}
|
|
2979
|
+
const r2 = cp.spawnSync(process.execPath, [CLI, 'reuse-check', '날짜 date 포맷'], { encoding: 'utf8', timeout: 15000 });
|
|
2980
|
+
const txtOk = r2.status === 0 && /빌드 vs 재사용/.test(r2.stdout) && /date-fns/.test(r2.stdout) && /체크리스트/.test(r2.stdout);
|
|
2981
|
+
// 인자 없으면 실패
|
|
2982
|
+
const r3 = cp.spawnSync(process.execPath, [CLI, 'reuse-check'], { encoding: 'utf8', timeout: 10000 });
|
|
2983
|
+
const failOk = r3.status !== 0;
|
|
2984
|
+
const ok = jsonOk && txtOk && failOk;
|
|
2985
|
+
console.log(ok ? '✓ B(1.9.285) reuse-check: 카테고리 감지 + 체크리스트 + json/인자검증' : `✗ reuse-check 실패 (json=${jsonOk} txt=${txtOk} fail=${failOk})`);
|
|
2986
|
+
if (!ok) failed++;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
2966
2990
|
if (failed > 0) process.exit(1);
|