leerness 1.9.294 → 1.9.296
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 +41 -0
- package/README.md +18 -6
- package/bin/harness.js +77 -55
- package/lib/catalogs.js +60 -0
- package/package.json +1 -1
- package/scripts/e2e.js +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.296 — 2026-06-04 — UR-0030: 정체성 = AI 에이전트 운영 레이어 (leerness about verb)
|
|
4
|
+
|
|
5
|
+
**🧭 GPT-5.5 범용 하네스 전략의 정체성 정립 — leerness 는 "실행기"가 아니라 어떤 AI 코딩 에이전트 위에도 얹는 "운영 레이어"임을 조회 가능·테스트 가능한 형태로 명문화.**
|
|
6
|
+
|
|
7
|
+
### 배경
|
|
8
|
+
GPT-5.5 범용 하네스 리뷰: leerness 의 정체성을 "AI 에이전트 운영 레이어(Memory+Policy+Handoff+Verification+Audit 공통 계층)"로 포지셔닝 권고(UR-0030). 하위 구현(MCP-first/state schema/adapters/tiers)은 이미 완료됐으나, **정체성 자체가 코드/문서에서 명시적으로 조회 가능하지 않았음**.
|
|
9
|
+
|
|
10
|
+
### 구현 (UR-0030)
|
|
11
|
+
1. **`leerness about [--json]` (alias `identity`) CLI 신설** — 정체성을 구조화 노출: identity("AI 에이전트 운영 레이어") / isNot(실행기 아님) / 5계층(기억·정책·인수인계·검증·감사) / complements(AGENTS.md 보완) / entryPoints / surface(동적: MCP 도구수·어댑터·provider·런타임의존성 0).
|
|
12
|
+
2. **MCP `leerness_about` (81번째 도구)** — read-only. 어떤 에이전트든 "이 도구가 무엇이고 무엇을 보완하는가"를 1콜로 파악.
|
|
13
|
+
3. **`_leernessIdentity()` 단일 출처** — about CLI / MCP / README 정체성 섹션이 공유(동적 표면 데이터는 ADAPTERS/_mcpToolCount/EXTERNAL_AGENTS 에서 합성).
|
|
14
|
+
4. **README 정체성 섹션** — managedReadmeBlock 에 "정체성 — AI 에이전트 운영 레이어" 섹션 추가(5계층 + AGENTS.md 보완 관계 명시). readme sync 로 자동 propagate.
|
|
15
|
+
5. selftest 43→44 · e2e 240→241. MCP 배지 80→81 자동 동기화.
|
|
16
|
+
|
|
17
|
+
### 검증
|
|
18
|
+
- **selftest 44/44 PASS** · **E2E 241/241 PASS** (회귀 0).
|
|
19
|
+
- 실측: `about --json` → identity/5계층/surface(MCP 81·어댑터 9·provider 10·deps 0). MCP tools/list 81개에 `leerness_about` 노출. README 정체성 섹션 생성.
|
|
20
|
+
- B(1.9.288) 도구수 정합 가드가 배지 stale(80 vs 81) 즉시 검출 → sync 정합(Codex #5 가드 정상).
|
|
21
|
+
|
|
22
|
+
## 1.9.295 — 2026-06-04 — UR-0025 (4단계): 잔여 정적 카탈로그 → lib/catalogs.js — 데이터 추출 단계 완료 ✅
|
|
23
|
+
|
|
24
|
+
**🧩 모놀리스 분리 4단계 — 잔여 정적 데이터 카탈로그 5종을 비파괴 분리. 이로써 leerness 의 모든 정적 데이터 카탈로그가 lib/ 모듈로 외부화됨(데이터 추출 단계 완료).**
|
|
25
|
+
|
|
26
|
+
### 배경
|
|
27
|
+
GPT-5.5 + Codex 공통 지적(단일 대형 `bin/harness.js`)을 점진 분리 중. 1단계(pure-utils 14함수)·2단계(agent-registry)·3단계(role-catalog)에 이어 4단계로 capability/adapter/reuse 정적 카탈로그를 분리.
|
|
28
|
+
|
|
29
|
+
### 구현 (UR-0025 4단계)
|
|
30
|
+
1. **`lib/catalogs.js` 신설** — 5개 순수 데이터 카탈로그 이동(53줄):
|
|
31
|
+
- `CAPABILITY_SURFACE`(6영역 위험/optOut 공개) + `POWERFUL_COMMANDS`(주의 명령 7종)
|
|
32
|
+
- `ADAPTERS`(도구별 어댑터 9종 — claude/cursor/codex/goose/opencode 등)
|
|
33
|
+
- `REUSE_CATEGORIES`(OSS 빌드 vs 재사용 게이트 15종) + `REUSE_CHECKLIST`(적합성 6항목)
|
|
34
|
+
2. **비파괴 require-based 분리** — `capabilitiesCmd`/`adapterCmd`/`_reuseDetect`/`reuseCheckCmd` 소비처는 동일 바인딩. 런타임 변형 0(모두 읽기 전용 검증). 상단 단일 require + 원위치 마커 주석.
|
|
35
|
+
3. **harness.js 21607→21560줄**. **lib 4모듈 체제 완성**: pure-utils(163)+agent-registry(147)+role-catalog(103)+catalogs(60) = 473줄 외부화.
|
|
36
|
+
4. selftest 42→43 (5 카탈로그 동일참조 단일출처 + 인라인 제거 검증) · e2e 239→240 (모듈 standalone + `capabilities`/`reuse-check` 동작 회귀).
|
|
37
|
+
|
|
38
|
+
### 검증
|
|
39
|
+
- **selftest 43/43 PASS** · **E2E 240/240 PASS** (회귀 0).
|
|
40
|
+
- 기존 selftest(CAPABILITY_SURFACE 6영역 + _reuseDetect 카테고리 + ADAPTERS .mcp.json)가 추출 정합성 즉시 검증.
|
|
41
|
+
- `capabilities --json` / `reuse-check "JWT 인증" --json` 정상 동작(모듈 분리 후 회귀 없음).
|
|
42
|
+
- **UR-0025 정적 데이터 카탈로그 추출 단계 완료** — 이후는 기능(command handler) 모듈화 검토 영역(별도 신중 진행).
|
|
43
|
+
|
|
3
44
|
## 1.9.294 — 2026-06-04 — UR-0025 (3단계): 역할/모델 카탈로그 → lib/role-catalog.js 모듈 분리
|
|
4
45
|
|
|
5
46
|
**🧩 모놀리스 분리 3단계 — 역할/모델 레지스트리(4개 카탈로그)를 비파괴 데이터 모듈로 분리. 1.9.274(pure-utils)/1.9.291(agent-registry) 패턴 계승.**
|
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,19 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.296 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
|
+
|
|
476
|
+
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
477
|
+
|
|
478
|
+
Leerness 는 **실행기/코딩 에이전트가 아니라**, 어떤 AI 코딩 에이전트(Claude Code · Codex · Cursor · Goose 등) 위에도 얹는 **범용 운영 레이어**입니다. 5개 공통 계층을 제공합니다:
|
|
479
|
+
|
|
480
|
+
- **기억(Memory)** — 프로젝트 상태/결정/진행을 `.leerness/` 에 영속화
|
|
481
|
+
- **정책(Policy)** — 8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트
|
|
482
|
+
- **인수인계(Handoff)** — 에이전트 간 컨텍스트 표준 전달 + `get_project_context` 1콜 온보딩
|
|
483
|
+
- **검증(Verification)** — 근거 기반 완료 검증으로 허위 완료 차단
|
|
484
|
+
- **감사(Audit)** — drift/idempotency/secret/encoding 자동 감사 + self-heal
|
|
485
|
+
|
|
486
|
+
AGENTS.md(정적 지침)을 **대체하지 않고 보완**합니다 — 정적 규칙은 AGENTS.md, 동적 상태·검증·인수인계는 leerness. 정체성 조회: `leerness about` (MCP `leerness_about`).
|
|
475
487
|
|
|
476
488
|
### Core Commands
|
|
477
489
|
|
|
@@ -513,7 +525,7 @@ leerness memory restore decision <date|title>
|
|
|
513
525
|
|
|
514
526
|
### MCP server (외부 AI 통합)
|
|
515
527
|
|
|
516
|
-
Leerness v1.9.
|
|
528
|
+
Leerness v1.9.296는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **81개 도구**를 노출:
|
|
517
529
|
|
|
518
530
|
```jsonc
|
|
519
531
|
// 카테고리별
|
|
@@ -526,7 +538,7 @@ Leerness v1.9.294는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
526
538
|
// • Workflow: session_close / agents_list / task_export / env_check / usage_stats / reuse_map / whats_new
|
|
527
539
|
|
|
528
540
|
// MCP server 실행: leerness mcp serve
|
|
529
|
-
// tools/list 응답:
|
|
541
|
+
// tools/list 응답: 81 도구
|
|
530
542
|
```
|
|
531
543
|
|
|
532
544
|
### Autonomous mode (자율 모드)
|
|
@@ -534,7 +546,7 @@ Leerness v1.9.294는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
534
546
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
535
547
|
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
548
|
|
|
537
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
549
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.296)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
538
550
|
|
|
539
551
|
### 성능 가이드 (1.9.140 측정)
|
|
540
552
|
|
|
@@ -572,6 +584,6 @@ leerness release pack --close --auto-main-push
|
|
|
572
584
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
573
585
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
574
586
|
|
|
575
|
-
Last synced by Leerness v1.9.
|
|
587
|
+
Last synced by Leerness v1.9.296: 2026-06-04
|
|
576
588
|
<!-- leerness:project-readme:end -->
|
|
577
589
|
|
package/bin/harness.js
CHANGED
|
@@ -9,8 +9,10 @@ const readline = require('readline');
|
|
|
9
9
|
// 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
|
|
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
|
+
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
13
|
+
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST } = require('../lib/catalogs');
|
|
12
14
|
|
|
13
|
-
const VERSION = '1.9.
|
|
15
|
+
const VERSION = '1.9.296';
|
|
14
16
|
|
|
15
17
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
16
18
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -286,6 +288,18 @@ function managedReadmeBlock(project) {
|
|
|
286
288
|
'',
|
|
287
289
|
`이 프로젝트는 Leerness v${VERSION} 하네스를 사용합니다. AI 에이전트는 작업 전 \`leerness handoff\`로 컨텍스트를 적재하고, 작업 후 \`leerness check\`/\`leerness audit\`/\`leerness session close\`를 수행해야 합니다.`,
|
|
288
290
|
'',
|
|
291
|
+
'### 정체성 — AI 에이전트 운영 레이어 (UR-0030)',
|
|
292
|
+
'',
|
|
293
|
+
'Leerness 는 **실행기/코딩 에이전트가 아니라**, 어떤 AI 코딩 에이전트(Claude Code · Codex · Cursor · Goose 등) 위에도 얹는 **범용 운영 레이어**입니다. 5개 공통 계층을 제공합니다:',
|
|
294
|
+
'',
|
|
295
|
+
'- **기억(Memory)** — 프로젝트 상태/결정/진행을 `.leerness/` 에 영속화',
|
|
296
|
+
'- **정책(Policy)** — 8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트',
|
|
297
|
+
'- **인수인계(Handoff)** — 에이전트 간 컨텍스트 표준 전달 + `get_project_context` 1콜 온보딩',
|
|
298
|
+
'- **검증(Verification)** — 근거 기반 완료 검증으로 허위 완료 차단',
|
|
299
|
+
'- **감사(Audit)** — drift/idempotency/secret/encoding 자동 감사 + self-heal',
|
|
300
|
+
'',
|
|
301
|
+
'AGENTS.md(정적 지침)을 **대체하지 않고 보완**합니다 — 정적 규칙은 AGENTS.md, 동적 상태·검증·인수인계는 leerness. 정체성 조회: `leerness about` (MCP `leerness_about`).',
|
|
302
|
+
'',
|
|
289
303
|
'### Core Commands',
|
|
290
304
|
'',
|
|
291
305
|
'```bash',
|
|
@@ -3001,6 +3015,8 @@ function _selfTestCases() {
|
|
|
3001
3015
|
{ name: 'get_project_context: MCP 시맨틱 verb 등록 + CLI context 디스패치 (UR-0031 1.9.292)', run: () => { const src = read(__filename); const mcpDef = /name: 'leerness_get_project_context'/.test(src); const mcpCase = /case 'leerness_get_project_context':[\s\S]*?cliArgs = \['context'/.test(src); const cliDisp = /if \(cmd === 'context'\)\s+return contextCmd/.test(src); return typeof contextCmd === 'function' && mcpDef && mcpCase && cliDisp && _mcpToolCount() >= 80; } },
|
|
3002
3016
|
{ name: '_canonicalProgressHeader + idempotency auto-fix (근본 복제버그 fix 1.9.293)', run: () => { const h = _canonicalProgressHeader(); const headerOk = /leernessRole: progress-tracker/.test(h) && /\| ID \| Status \| Request \|/.test(h) && /\|---\|/.test(h); const src = read(__filename); const fnOk = typeof _autoFixIdempotency === 'function'; const noWholeTextFallback = /if \(idx < 0\) return _canonicalProgressHeader\(\);/.test(src); const driftWired = /_autoFixIdempotency\(root\)/.test(src) && /idempotency 중복/.test(src); return headerOk && fnOk && noWholeTextFallback && driftWired; } },
|
|
3003
3017
|
{ name: 'lib/role-catalog: ROLE/PROVIDER/ALIASES/PROMPTS 모듈 단일출처 분리 (UR-0025 1.9.294)', run: () => { const m = require('../lib/role-catalog'); return m.ROLE_CATALOG === ROLE_CATALOG && m._PROVIDER_MODEL_CATALOG === _PROVIDER_MODEL_CATALOG && m._ROLE_ALIASES === _ROLE_ALIASES && m._AGENT_ROLE_PROMPTS === _AGENT_ROLE_PROMPTS && Object.keys(m.ROLE_CATALOG).length === 7 && Object.keys(m._PROVIDER_MODEL_CATALOG).length === 10 && !/const ROLE_CATALOG = \{/.test(read(__filename)); } },
|
|
3018
|
+
{ name: 'lib/catalogs: CAPABILITY/ADAPTERS/REUSE 모듈 단일출처 분리 (UR-0025 1.9.295)', run: () => { const m = require('../lib/catalogs'); return m.CAPABILITY_SURFACE === CAPABILITY_SURFACE && m.ADAPTERS === ADAPTERS && m.REUSE_CATEGORIES === REUSE_CATEGORIES && m.REUSE_CHECKLIST === REUSE_CHECKLIST && m.POWERFUL_COMMANDS === POWERFUL_COMMANDS && Object.keys(m.CAPABILITY_SURFACE).length === 6 && !/const CAPABILITY_SURFACE = \{/.test(read(__filename)); } },
|
|
3019
|
+
{ name: 'about: 정체성 verb(AI 운영 레이어) + MCP leerness_about 등록 (UR-0030 1.9.296)', run: () => { const id = _leernessIdentity(); const src = read(__filename); return typeof aboutCmd === 'function' && /운영 레이어/.test(id.identity) && id.layers.length === 5 && id.surface.mcpTools >= 81 && /name: 'leerness_about'/.test(src) && /case 'leerness_about':/.test(src) && /cmd === 'about' \|\| cmd === 'identity'/.test(src); } },
|
|
3004
3020
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3005
3021
|
];
|
|
3006
3022
|
}
|
|
@@ -16631,7 +16647,8 @@ function mcpServeCmd(root) {
|
|
|
16631
16647
|
{ name: 'leerness_state_record', description: '1.9.279 (UR-0031) — record_file_change / record_decision. 진행 중 run 에 변경 파일/명령/테스트/결정을 누적. 인자: { path?, filesChanged? (콤마), filesRead?, commands?, tests?, errors?, decision? }. 응답: { recorded, run }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, filesChanged: { type: 'string' }, filesRead: { type: 'string' }, commands: { type: 'string' }, tests: { type: 'string' }, errors: { type: 'string' }, decision: { type: 'string' } } } },
|
|
16632
16648
|
{ name: 'leerness_state_verify', description: '1.9.279 (UR-0031) — request_verification / verify_done. 현재 run 의 검증 결과 기록. 인자: { path?, result (required: "pass"|"fail"), note? }. 응답: { verified, result, run }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, result: { type: 'string', enum: ['pass', 'fail'] }, note: { type: 'string' } }, required: ['result'] } },
|
|
16633
16649
|
{ name: 'leerness_state_handoff', description: '1.9.279 (UR-0031) — create_handoff / make_handoff. 현재 run 을 마감하고 다음 에이전트용 .leerness/handoff/latest.{md,json} 작성 (Claude→Goose→Codex 인수인계 표준). 인자: { path?, summary (required) }. 응답: { handoff, run }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, summary: { type: 'string' } }, required: ['summary'] } },
|
|
16634
|
-
{ name: 'leerness_get_project_context', description: '1.9.292 (UR-0031) — get_project_context. 외부 에이전트가 작업 시작 전 단 1콜로 "지금 무엇을 알아야 하는가"를 파악하는 집약 컨텍스트. 여러 소스(현재 작업/미답 사용자 요청/최근 결정/활성 룰/next-actions/memory surface/프로젝트 의도)를 한 번에 구조화 회수. read-only. 인자: { path? }. 응답: { version, project, currentTask, openRequests, recentDecisions, activeRules, nextActions, memory }.', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
|
|
16650
|
+
{ name: 'leerness_get_project_context', description: '1.9.292 (UR-0031) — get_project_context. 외부 에이전트가 작업 시작 전 단 1콜로 "지금 무엇을 알아야 하는가"를 파악하는 집약 컨텍스트. 여러 소스(현재 작업/미답 사용자 요청/최근 결정/활성 룰/next-actions/memory surface/프로젝트 의도)를 한 번에 구조화 회수. read-only. 인자: { path? }. 응답: { version, project, currentTask, openRequests, recentDecisions, activeRules, nextActions, memory }.', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
16651
|
+
{ name: 'leerness_about', description: '1.9.296 (UR-0030) — leerness 정체성/포지셔닝 회수. leerness 가 무엇인가: "AI 에이전트 운영 레이어"(실행기 아님) — 기억·정책·인수인계·검증·감사 5계층. read-only. 어떤 에이전트든 "이 도구가 무엇이고 무엇을 보완하는가"를 1콜로 파악. 응답: { identity, isNot, tagline, layers[], complements, entryPoints, surface }.', inputSchema: { type: 'object', properties: {} } }
|
|
16635
16652
|
];
|
|
16636
16653
|
|
|
16637
16654
|
function send(obj) {
|
|
@@ -16884,6 +16901,9 @@ function mcpServeCmd(root) {
|
|
|
16884
16901
|
case 'leerness_get_project_context':
|
|
16885
16902
|
cliArgs = ['context', '--path', targetPath, '--json'];
|
|
16886
16903
|
break;
|
|
16904
|
+
case 'leerness_about':
|
|
16905
|
+
cliArgs = ['about', '--json'];
|
|
16906
|
+
break;
|
|
16887
16907
|
default:
|
|
16888
16908
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
16889
16909
|
}
|
|
@@ -17981,23 +18001,7 @@ function rolesCmd(root, sub, ...args) {
|
|
|
17981
18001
|
// ===== 1.9.272: Capability / Security Surface 공개 (GPT-5.5 외부 리뷰 반영) =====
|
|
17982
18002
|
// leerness 는 권한이 큰 CLI 하네스(child_process/git/외부 CLI/automation 브리지/hook 설치)이므로,
|
|
17983
18003
|
// 무엇을 할 수 있고 어떻게 끄는지를 명시적으로 공개해 신뢰도를 높인다. SECURITY.md 와 동일 출처.
|
|
17984
|
-
|
|
17985
|
-
filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
|
|
17986
|
-
network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
|
|
17987
|
-
childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
|
|
17988
|
-
externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
|
|
17989
|
-
automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
|
|
17990
|
-
claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
|
|
17991
|
-
};
|
|
17992
|
-
const POWERFUL_COMMANDS = [
|
|
17993
|
-
{ cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
|
|
17994
|
-
{ cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
|
|
17995
|
-
{ cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
|
|
17996
|
-
{ cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
|
|
17997
|
-
{ cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
|
|
17998
|
-
{ cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
|
|
17999
|
-
{ cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
|
|
18000
|
-
];
|
|
18004
|
+
// CAPABILITY_SURFACE / POWERFUL_COMMANDS → lib/catalogs.js (1.9.295 UR-0025 4단계)
|
|
18001
18005
|
function capabilitiesCmd(root, opts = {}) {
|
|
18002
18006
|
if (opts.json) {
|
|
18003
18007
|
log(JSON.stringify({ version: VERSION, surface: CAPABILITY_SURFACE, powerfulCommands: POWERFUL_COMMANDS,
|
|
@@ -18117,6 +18121,56 @@ function _splitList(v) { return String(v || '').split(',').map(s => s.trim()).fi
|
|
|
18117
18121
|
// 1.9.292 (UR-0031): get_project_context — 외부 에이전트 온보딩용 단일 집약 컨텍스트.
|
|
18118
18122
|
// 1콜로 현재 작업/미답 요청/최근 결정/활성 룰/next-actions/memory/프로젝트 의도를 구조화 회수.
|
|
18119
18123
|
// MCP leerness_get_project_context 가 이 명령(--json)을 호출. handoff(인간용 장문)와 달리 기계 친화 lean payload.
|
|
18124
|
+
// 1.9.296 (UR-0030, GPT-5.5 범용 하네스 전략): leerness 정체성 = 'AI 에이전트 운영 레이어'.
|
|
18125
|
+
// 실행기가 아니라 기억·정책·인수인계·검증·감사의 공통 계층. 어떤 AI 코딩 에이전트 위에도 얹힌다.
|
|
18126
|
+
// _leernessIdentity() 단일 출처 — about 명령 / MCP leerness_about / README 정체성 섹션이 공유.
|
|
18127
|
+
function _leernessIdentity() {
|
|
18128
|
+
let adapterIds = [];
|
|
18129
|
+
try { adapterIds = Object.keys(ADAPTERS); } catch {}
|
|
18130
|
+
let providerCount = 0;
|
|
18131
|
+
try { providerCount = EXTERNAL_AGENTS.length; } catch {}
|
|
18132
|
+
return {
|
|
18133
|
+
name: 'leerness',
|
|
18134
|
+
version: VERSION,
|
|
18135
|
+
identity: 'AI 에이전트 운영 레이어 (AI agent operating layer)',
|
|
18136
|
+
isNot: '실행기/코딩 에이전트가 아님 — 어떤 에이전트 위에도 얹는 공통 운영 계층',
|
|
18137
|
+
tagline: '어떤 AI 코딩 에이전트에도 적용되는 범용 운영 레이어 — 기억·정책·인수인계·검증·감사',
|
|
18138
|
+
layers: [
|
|
18139
|
+
{ key: 'memory', ko: '기억', desc: '프로젝트 상태/결정/진행을 .leerness/ 에 영속화 (state start|record|verify|handoff)' },
|
|
18140
|
+
{ key: 'policy', ko: '정책', desc: '8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트' },
|
|
18141
|
+
{ key: 'handoff', ko: '인수인계', desc: '에이전트 간 컨텍스트 표준 전달 (Claude→Codex→Goose), get_project_context 1콜 온보딩' },
|
|
18142
|
+
{ key: 'verification', ko: '검증', desc: '근거 기반 완료 검증 (verify-claim --require-evidence) — 허위 완료 차단' },
|
|
18143
|
+
{ key: 'audit', ko: '감사', desc: 'drift/idempotency/secret/encoding 자동 감사 + self-heal (--auto-fix)' }
|
|
18144
|
+
],
|
|
18145
|
+
complements: 'AGENTS.md(정적 지침)을 대체하지 않고 보완 — 정적 규칙/명령/금지는 AGENTS.md, 동적 상태·기억·검증·인수인계는 leerness',
|
|
18146
|
+
entryPoints: { context: 'leerness context (MCP get_project_context)', mcp: 'leerness mcp serve', adapter: 'leerness adapter <tool>' },
|
|
18147
|
+
surface: { mcpTools: _mcpToolCount(), adapters: adapterIds, providers: providerCount, runtimeDeps: 0, offlineFirst: true }
|
|
18148
|
+
};
|
|
18149
|
+
}
|
|
18150
|
+
function aboutCmd(opts = {}) {
|
|
18151
|
+
const id = _leernessIdentity();
|
|
18152
|
+
if (opts.json || has('--json')) { process.stdout.write(JSON.stringify(id, null, 2) + '\n'); return id; }
|
|
18153
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
18154
|
+
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
18155
|
+
const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
|
|
18156
|
+
const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
18157
|
+
log(cy(`# ${id.name} v${id.version} — ${id.identity}`));
|
|
18158
|
+
log('');
|
|
18159
|
+
log(` ${id.tagline}`);
|
|
18160
|
+
log(dm(` ✗ ${id.isNot}`));
|
|
18161
|
+
log('');
|
|
18162
|
+
log(gr('## 5 운영 계층 (Memory · Policy · Handoff · Verification · Audit)'));
|
|
18163
|
+
id.layers.forEach(l => log(` • ${l.ko} (${l.key}) — ${l.desc}`));
|
|
18164
|
+
log('');
|
|
18165
|
+
log(gr('## 보완 관계'));
|
|
18166
|
+
log(` ${id.complements}`);
|
|
18167
|
+
log('');
|
|
18168
|
+
log(gr('## 진입점'));
|
|
18169
|
+
Object.values(id.entryPoints).forEach(e => log(dm(` • ${e}`)));
|
|
18170
|
+
log('');
|
|
18171
|
+
log(`표면: MCP ${id.surface.mcpTools} 도구 · 어댑터 ${id.surface.adapters.length} · provider ${id.surface.providers} · 런타임 의존성 ${id.surface.runtimeDeps} · offline-first ${id.surface.offlineFirst ? '✓' : '✗'}`);
|
|
18172
|
+
return id;
|
|
18173
|
+
}
|
|
18120
18174
|
function contextCmd(root, opts = {}) {
|
|
18121
18175
|
root = absRoot(root);
|
|
18122
18176
|
const rows = readProgressRows(root);
|
|
@@ -18284,17 +18338,7 @@ function stateCmd(root, sub, ...args) {
|
|
|
18284
18338
|
// 목적: 도구별로 "필요한 지침/연결 파일만" 선택 생성 (전체 init 대신). --minimal + adapter 조합으로 침투성↓·범용성↑.
|
|
18285
18339
|
// MCP 지원 도구는 .mcp.json (leerness mcp serve 등록) 도 생성 → UR-0031 상태 verb 를 즉시 호출 가능.
|
|
18286
18340
|
// 파일 템플릿은 coreFiles() 재사용(단일 출처). 비파괴(writeIfSafe + mergeManaged).
|
|
18287
|
-
|
|
18288
|
-
claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
|
|
18289
|
-
cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
|
|
18290
|
-
copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
|
|
18291
|
-
codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
|
|
18292
|
-
goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
|
|
18293
|
-
gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
|
|
18294
|
-
opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
|
|
18295
|
-
aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
|
|
18296
|
-
qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
|
|
18297
|
-
};
|
|
18341
|
+
// ADAPTERS → lib/catalogs.js (1.9.295 UR-0025 4단계)
|
|
18298
18342
|
// _mcpJsonContent → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
|
|
18299
18343
|
// .mcp.json 병합 — 기존 mcpServers 보존하고 leerness 항목만 추가/갱신.
|
|
18300
18344
|
function _mergeMcpJson(root) {
|
|
@@ -18354,35 +18398,12 @@ function adapterCmd(root, tool, opts = {}) {
|
|
|
18354
18398
|
// 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트.
|
|
18355
18399
|
// 네트워크 크롤러가 아님(offline-first 유지) — 호스트 AI(메인)가 실제 OSS 탐색을 하되, 이 게이트가
|
|
18356
18400
|
// 카테고리 후보 + 적합성 체크리스트를 제공해 "재사용 검토 생략"을 방지. 기존 `reuse`(내부 자원)와 구분.
|
|
18357
|
-
|
|
18358
|
-
{ key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
|
|
18359
|
-
{ key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
|
|
18360
|
-
{ key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
|
|
18361
|
-
{ key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
|
|
18362
|
-
{ key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
|
|
18363
|
-
{ key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
|
|
18364
|
-
{ key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
|
|
18365
|
-
{ key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
|
|
18366
|
-
{ key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
|
|
18367
|
-
{ key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
|
|
18368
|
-
{ key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
|
|
18369
|
-
{ key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
|
|
18370
|
-
{ key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
|
|
18371
|
-
{ key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
|
|
18372
|
-
{ key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
|
|
18373
|
-
];
|
|
18401
|
+
// REUSE_CATEGORIES → lib/catalogs.js (1.9.295 UR-0025 4단계)
|
|
18374
18402
|
function _reuseDetect(feature) {
|
|
18375
18403
|
const t = String(feature || '').toLowerCase();
|
|
18376
18404
|
return REUSE_CATEGORIES.filter(c => c.kw.some(k => t.includes(k)));
|
|
18377
18405
|
}
|
|
18378
|
-
|
|
18379
|
-
'라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
|
|
18380
|
-
'유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
|
|
18381
|
-
'보안: 알려진 취약점이 없는가 (npm audit / advisory)',
|
|
18382
|
-
'적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
|
|
18383
|
-
'통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
|
|
18384
|
-
'제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
|
|
18385
|
-
];
|
|
18406
|
+
// REUSE_CHECKLIST → lib/catalogs.js (1.9.295 UR-0025 4단계)
|
|
18386
18407
|
function reuseCheckCmd(root, feature, opts = {}) {
|
|
18387
18408
|
const json = has('--json');
|
|
18388
18409
|
feature = String(feature || '').trim();
|
|
@@ -21396,6 +21417,7 @@ async function main() {
|
|
|
21396
21417
|
// 1.9.278 (UR-0032): leerness state <show|start|record|verify|handoff> — .leerness/ 구조화 상태 substrate
|
|
21397
21418
|
if (cmd === 'state') return stateCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21398
21419
|
if (cmd === 'context') return contextCmd(arg('--path', process.cwd()), { json: has('--json') });
|
|
21420
|
+
if (cmd === 'about' || cmd === 'identity') return aboutCmd({ json: has('--json') });
|
|
21399
21421
|
// 1.9.281 (UR-0034): leerness policy <show|set|check> — 권한 등급 (opt-in enforced)
|
|
21400
21422
|
if (cmd === 'policy') return policyCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21401
21423
|
// 1.9.285 (UR-0023): leerness reuse-check "<기능>" — 외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인)
|
package/lib/catalogs.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// lib/catalogs.js — capability/adapter/reuse 정적 데이터 카탈로그 (순수 데이터, 부작용 0)
|
|
2
|
+
// 1.9.295 (UR-0025 4단계): bin/harness.js 에서 비파괴 분리. selftest(CAPABILITY_SURFACE 6영역 + ADAPTERS + REUSE)가 동작 검증.
|
|
3
|
+
// 런타임 변형 없음 — capabilitiesCmd/adapterCmd/_reuseDetect/reuseCheckCmd 소비처 모두 읽기 전용.
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const CAPABILITY_SURFACE = {
|
|
7
|
+
filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
|
|
8
|
+
network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
|
|
9
|
+
childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
|
|
10
|
+
externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
|
|
11
|
+
automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
|
|
12
|
+
claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
|
|
13
|
+
};
|
|
14
|
+
const POWERFUL_COMMANDS = [
|
|
15
|
+
{ cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
|
|
16
|
+
{ cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
|
|
17
|
+
{ cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
|
|
18
|
+
{ cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
|
|
19
|
+
{ cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
|
|
20
|
+
{ cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
|
|
21
|
+
{ cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
|
|
22
|
+
];
|
|
23
|
+
const ADAPTERS = {
|
|
24
|
+
claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
|
|
25
|
+
cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
|
|
26
|
+
copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
|
|
27
|
+
codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
|
|
28
|
+
goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
|
|
29
|
+
gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
|
|
30
|
+
opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
|
|
31
|
+
aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
|
|
32
|
+
qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
|
|
33
|
+
};
|
|
34
|
+
const REUSE_CATEGORIES = [
|
|
35
|
+
{ key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
|
|
36
|
+
{ key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
|
|
37
|
+
{ key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
|
|
38
|
+
{ key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
|
|
39
|
+
{ key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
|
|
40
|
+
{ key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
|
|
41
|
+
{ key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
|
|
42
|
+
{ key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
|
|
43
|
+
{ key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
|
|
44
|
+
{ key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
|
|
45
|
+
{ key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
|
|
46
|
+
{ key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
|
|
47
|
+
{ key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
|
|
48
|
+
{ key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
|
|
49
|
+
{ key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
|
|
50
|
+
];
|
|
51
|
+
const REUSE_CHECKLIST = [
|
|
52
|
+
'라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
|
|
53
|
+
'유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
|
|
54
|
+
'보안: 알려진 취약점이 없는가 (npm audit / advisory)',
|
|
55
|
+
'적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
|
|
56
|
+
'통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
|
|
57
|
+
'제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
module.exports = { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST };
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -3202,5 +3202,52 @@ total++;
|
|
|
3202
3202
|
if (!ok) failed++;
|
|
3203
3203
|
}
|
|
3204
3204
|
|
|
3205
|
+
// 1.9.295 회귀 (UR-0025 4단계): lib/catalogs.js 모듈 분리 — 단일출처 + 부작용 0 + 소비 명령 회귀
|
|
3206
|
+
total++;
|
|
3207
|
+
{
|
|
3208
|
+
let ok = false;
|
|
3209
|
+
try {
|
|
3210
|
+
const reg = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
3211
|
+
const dataOk = reg.CAPABILITY_SURFACE && Object.keys(reg.CAPABILITY_SURFACE).length === 6 &&
|
|
3212
|
+
Array.isArray(reg.POWERFUL_COMMANDS) && reg.POWERFUL_COMMANDS.length === 7 &&
|
|
3213
|
+
reg.ADAPTERS && Object.keys(reg.ADAPTERS).length === 9 &&
|
|
3214
|
+
Array.isArray(reg.REUSE_CATEGORIES) && reg.REUSE_CATEGORIES.length === 15 &&
|
|
3215
|
+
Array.isArray(reg.REUSE_CHECKLIST) && reg.REUSE_CHECKLIST.length === 6;
|
|
3216
|
+
const harnessSrc = fs.readFileSync(path.resolve(__dirname, '..', 'bin', 'harness.js'), 'utf8');
|
|
3217
|
+
const movedOut = !/const CAPABILITY_SURFACE = \{/.test(harnessSrc) && !/const ADAPTERS = \{/.test(harnessSrc) && /require\('\.\.\/lib\/catalogs'\)/.test(harnessSrc);
|
|
3218
|
+
// 소비 명령 회귀: capabilities + reuse-check (카탈로그 require 후 동작)
|
|
3219
|
+
const cap = cp.spawnSync(process.execPath, [CLI, 'capabilities', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
3220
|
+
const reuse = cp.spawnSync(process.execPath, [CLI, 'reuse-check', 'JWT 인증', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
3221
|
+
const cmdsOk = cap.status === 0 && reuse.status === 0 && /auth/.test(reuse.stdout);
|
|
3222
|
+
ok = dataOk && movedOut && cmdsOk;
|
|
3223
|
+
} catch {}
|
|
3224
|
+
console.log(ok ? '✓ B(1.9.295) lib/catalogs 모듈 분리: 단일출처 + 인라인 제거 + capabilities/reuse-check 동작 (UR-0025)' : '✗ catalogs 모듈 분리 실패');
|
|
3225
|
+
if (!ok) failed++;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// 1.9.296 회귀 (UR-0030): leerness about — 정체성(AI 운영 레이어) verb + MCP + README 섹션
|
|
3229
|
+
total++;
|
|
3230
|
+
{
|
|
3231
|
+
let ok = false;
|
|
3232
|
+
try {
|
|
3233
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'about', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
3234
|
+
const j = JSON.parse(r.stdout);
|
|
3235
|
+
const structOk = r.status === 0 && /운영 레이어/.test(j.identity) && Array.isArray(j.layers) && j.layers.length === 5 &&
|
|
3236
|
+
j.layers.every(l => l.key && l.ko && l.desc) && j.complements && j.surface && typeof j.surface.mcpTools === 'number' &&
|
|
3237
|
+
j.surface.runtimeDeps === 0 && /AGENTS\.md/.test(j.complements);
|
|
3238
|
+
// MCP tools/list 에 leerness_about 노출 (81 도구)
|
|
3239
|
+
const ml = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { cwd: tmp, encoding: 'utf8', timeout: 15000, input: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n' });
|
|
3240
|
+
const names = JSON.parse(ml.stdout.split('\n').filter(Boolean)[0]).result.tools.map(t => t.name);
|
|
3241
|
+
const mcpOk = names.includes('leerness_about') && names.length >= 81;
|
|
3242
|
+
// README 정체성 섹션: readme sync 후 tmp/README.md 에 섹션 생성 확인
|
|
3243
|
+
cp.spawnSync(process.execPath, [CLI, 'readme', 'sync', tmp], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
3244
|
+
const rd = fs.existsSync(path.join(tmp, 'README.md')) ? fs.readFileSync(path.join(tmp, 'README.md'), 'utf8') : '';
|
|
3245
|
+
const readmeOk = /정체성 — AI 에이전트 운영 레이어/.test(rd);
|
|
3246
|
+
ok = structOk && mcpOk && readmeOk;
|
|
3247
|
+
} catch {}
|
|
3248
|
+
console.log(ok ? '✓ B(1.9.296) leerness about: 정체성 verb + MCP leerness_about(81) + README 섹션 (UR-0030)' : '✗ about 정체성 verb 실패');
|
|
3249
|
+
if (!ok) failed++;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3205
3252
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
3206
3253
|
if (failed > 0) process.exit(1);
|