leerness 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.17.0 — 2026-06-09 — 🛡️ [안정화/Stable] 외부 클린룸 일관성 안정 minor
4
+
5
+ **🛡️ 안정화(Stable) minor.** 외부 클린룸 리뷰(게시본 무README 신규사용자 관점)에서 도출한 --json·CLI 일관성 개선(1.16.1~1.16.2)을 검증·통합해 npm 공개. R-0011 정책의 8번째 minor. 영상은 HyperFrames "문제→해소" 디자인.
6
+
7
+ ### 이번 minor 통합 (1.16.1~1.16.2)
8
+ - **gate `--json` 단일 객체화**: 텍스트+단계JSON 혼재로 파싱 불가하던 것 → `{ok,total,failed,checks}` 단일 JSON(CI/에이전트 소비 가능).
9
+ - **memory search `--json`**: 플래그 무시하던 것 → `{query,total,results}` 구조화.
10
+ - **명령그룹 무인자 일관화**: `rule`/`skill`/`feature`/`memory` 를 하위명령 없이 부르면 "알 수 없는 명령" 대신 **사용법 힌트**(decision/lesson 과 일관, `--json` 구조화).
11
+ - **문서 정합**: `about` 의 메모리 경로 `.leerness/` → `.harness/`(기본 워크스페이스) 정정.
12
+
13
+ ### 검증 (회귀 0)
14
+ - **selftest 216 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump. gate/memory --json valid JSON + bare-group 사용법 힌트 행위 재현. (맹신 X: scan --json exit·AKIA 는 비-버그로 판정해 제외.)
15
+
16
+ ### 안정화 표시 (R-0006)
17
+ CHANGELOG [안정화/Stable] · git tag (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
18
+
19
+ ## 1.16.2 — 2026-06-09 — CLI 일관성: 명령그룹 무인자 → 사용법 힌트 (외부클린룸 UR-0042)
20
+
21
+ **🧭 명령그룹을 하위명령 없이 부르면 친절한 사용법 안내.** 외부 클린룸 리뷰가 지적: `rule`/`skill`/`feature`/`memory` 를 하위명령 없이 부르면 "알 수 없는 명령"(유효 그룹인데 혼란)이 떴음 — `decision`/`lesson` 처럼 사용법 힌트로 통일.
22
+
23
+ ### 변경 (UR-0042)
24
+ - bare `rule`/`skill`/`feature`/`memory` → `subcommand_required` + 실제 하위명령 사용법 표시(예: `memory search "<키>" | memory status | …`). `--json` 도 구조화 출력.
25
+ - 기존 동작 보존: 하위명령이 있는 호출(`memory search` 등)은 그대로.
26
+
27
+ ### 검증 (회귀 0)
28
+ - **selftest 215→216** · **E2E 365/365** · 4개 그룹 bare 호출 → 사용법 힌트, `memory --json` → `{code:'subcommand_required'}` 행위 재현.
29
+ - patch(1.16.2) — npm 미배포(R-0011, GitHub). 잔여: bare `leerness` 자동 init·hook(UR-0041, 설계 재검토), `--language en` 런타임(UR-0042 잔여).
30
+
31
+ ## 1.16.1 — 2026-06-09 — 외부 클린룸 리뷰: --json 일관성 + 문서 정합
32
+
33
+ **🔬 외부 클린룸 리뷰(게시본 1.16.0 설치·README/소스 미참조·신규 사용자 관점, 2모델).** 발굴된 지적을 직접 재현해 진짜만 수정(맹신 X — 비-버그/의도된 동작은 제외).
34
+
35
+ ### 수정 (전부 재현 검증)
36
+ - **gate `--json` 단일 객체화 (C2)**: 이전엔 텍스트 헤더 + 단계별 JSON 이 섞여 파싱 불가. 이제 `{ok, total, failed, checks:[{name,ok}]}` 단일 객체(하위 출력 억제). CI/에이전트 소비 가능.
37
+ - **memory search `--json` (C3)**: `--json` 을 무시하고 텍스트만 내던 것 → `{query, total, results:[{file,line,text}]}` 구조화(전 명령 일관).
38
+ - **about 문서 정합 (C4)**: `about` 의 메모리 설명이 `.leerness/` 라 했으나 기본 워크스페이스는 `.harness/` → 정정(선택 state substrate 만 .leerness/).
39
+
40
+ ### 비-버그 판정 (맹신 X — 재현했으나 수정 안 함)
41
+ - **scan secrets `--json` exit 코드**: 리뷰는 exit 0 라 했으나 재현 결과 committed 발견 시 exit 1 정상(코드도 `if(committed.length) process.exitCode=1`). gitignored-only(안전) → exit 0 은 의도된 동작.
42
+ - **AWS AKIA 미탐지**: `...EXAMPLE` 포함은 placeholder 가드로 스킵(의도) — 실 키(EXAMPLE 없음)는 정상 탐지.
43
+
44
+ ### 잔여(백로그): bare `leerness`(무인자) 자동 init·hook / `--language en` 런타임 미적용 / 서브명령그룹 무인자 동작 일관화.
45
+
46
+ ### 검증 (회귀 0)
47
+ - **selftest 214→215** · **E2E 365/365** · gate/memory --json valid JSON + about .harness 행위 재현.
48
+ - patch(1.16.1) — npm 미배포(R-0011, GitHub).
49
+
3
50
  ## 1.16.0 — 2026-06-09 — 🛡️ [안정화/Stable] 16번째 버그헌트 안정 minor
4
51
 
5
52
  **🛡️ 안정화(Stable) minor.** 16번째 멀티에이전트 버그헌트(8건 발굴·전부 직접 재현)의 코어 수정(1.15.1)을 검증·통합해 npm 공개. R-0011 정책의 7번째 minor. **이 릴리스 영상부터 개선된 "문제→해소" 디자인**(짧은 인트로 + 이전/이제 모션 + 의미 보존 하이라이트)으로 제작됩니다.
package/README.md CHANGED
@@ -186,13 +186,13 @@ MIT
186
186
  <!-- leerness:project-readme:start -->
187
187
  ## Leerness Project Harness
188
188
 
189
- 이 프로젝트는 Leerness v1.16.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
189
+ 이 프로젝트는 Leerness v1.17.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
190
190
 
191
191
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
192
192
 
193
193
  Leerness 는 **실행기/코딩 에이전트가 아니라**, 어떤 AI 코딩 에이전트(Claude Code · Codex · Cursor · Goose 등) 위에도 얹는 **범용 운영 레이어**입니다. 5개 공통 계층을 제공합니다:
194
194
 
195
- - **기억(Memory)** — 프로젝트 상태/결정/진행을 `.leerness/` 에 영속화
195
+ - **기억(Memory)** — 프로젝트 상태/결정/진행을 `.harness/` 에 영속화
196
196
  - **정책(Policy)** — 8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트
197
197
  - **인수인계(Handoff)** — 에이전트 간 컨텍스트 표준 전달 + `get_project_context` 1콜 온보딩
198
198
  - **검증(Verification)** — 근거 기반 완료 검증으로 허위 완료 차단
@@ -240,7 +240,7 @@ leerness memory restore decision <date|title>
240
240
 
241
241
  ### MCP server (외부 AI 통합)
242
242
 
243
- Leerness v1.16.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
243
+ Leerness v1.17.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
244
244
 
245
245
  ```jsonc
246
246
  // 카테고리별
@@ -261,7 +261,7 @@ Leerness v1.16.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
261
261
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
262
262
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
263
263
 
264
- 현재 누적: **70 라운드 (1.9.40 → 1.16.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
264
+ 현재 누적: **70 라운드 (1.9.40 → 1.17.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
265
265
 
266
266
  ### 성능 가이드 (1.9.140 측정)
267
267
 
@@ -299,6 +299,6 @@ leerness release pack --close --auto-main-push
299
299
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
300
300
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
301
301
 
302
- Last synced by Leerness v1.16.0: 2026-06-09
302
+ Last synced by Leerness v1.17.0: 2026-06-09
303
303
  <!-- leerness:project-readme:end -->
304
304
 
package/bin/leerness.js CHANGED
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
32
32
  // 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
33
33
  const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
34
34
 
35
- const VERSION = '1.16.0';
35
+ const VERSION = '1.17.0';
36
36
 
37
37
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
38
38
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -281,7 +281,7 @@ function managedReadmeBlock(project) {
281
281
  '',
282
282
  'Leerness 는 **실행기/코딩 에이전트가 아니라**, 어떤 AI 코딩 에이전트(Claude Code · Codex · Cursor · Goose 등) 위에도 얹는 **범용 운영 레이어**입니다. 5개 공통 계층을 제공합니다:',
283
283
  '',
284
- '- **기억(Memory)** — 프로젝트 상태/결정/진행을 `.leerness/` 에 영속화',
284
+ '- **기억(Memory)** — 프로젝트 상태/결정/진행을 `.harness/` 에 영속화',
285
285
  '- **정책(Policy)** — 8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트',
286
286
  '- **인수인계(Handoff)** — 에이전트 간 컨텍스트 표준 전달 + `get_project_context` 1콜 온보딩',
287
287
  '- **검증(Verification)** — 근거 기반 완료 검증으로 허위 완료 차단',
@@ -3545,6 +3545,18 @@ function _selfTestCases() {
3545
3545
  const f2 = src.includes('_cellSafe(r.request)') && src.includes('_cellSafe(r.rule)');
3546
3546
  return f1 && f2;
3547
3547
  } },
3548
+ { name: '외부클린룸 C2/C3/C4: gate --json 단일객체 + memory search --json + about .harness 정합 (1.16.1)', run: () => {
3549
+ const src = read(__filename);
3550
+ const c2 = src.includes("const jsonMode = has('--json'); // 외부리뷰 C2") && src.includes('ok: bad === 0, total: checks.length, failed: bad, checks');
3551
+ const c3 = src.includes('// 외부리뷰 C3: --json 일관성') && src.includes('JSON.stringify({ version: VERSION, query, total, includeCode');
3552
+ const _badDir = '.leern' + 'ess/ 에 영속화 (state start'; // 자기참조 회피: 분할 — about state 줄이 .leerness 로 남아있으면 감지
3553
+ const c4 = src.includes('상태/결정/진행을 .harness/ 에 영속화 (task/decision') && !src.includes('상태/결정/진행을 ' + _badDir);
3554
+ return c2 && c3 && c4;
3555
+ } },
3556
+ { name: '외부클린룸 UR-0042: bare 명령그룹(rule/skill/feature/memory) → 사용법 힌트(unknown command 아님) (1.16.2)', run: () => {
3557
+ const src = read(__filename);
3558
+ return src.includes('const _GROUP_USAGE = {') && src.includes("if (_GROUP_USAGE[cmd] && !args[1])") && src.includes("'subcommand_required'");
3559
+ } },
3548
3560
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3549
3561
  ];
3550
3562
  }
@@ -7636,7 +7648,8 @@ function preCheck(root) {
7636
7648
 
7637
7649
  function memorySearch(root, query) {
7638
7650
  root = absRoot(root);
7639
- if (!query) { fail('query required (e.g., memory search "키워드")'); return; }
7651
+ const jsonMode = has('--json'); const results = []; // 외부리뷰 C3: --json 일관성(이전엔 --json 무시하고 텍스트만)
7652
+ if (!query) { failJson(jsonMode, 'query_required', 'query required (e.g., memory search "키워드")'); return; }
7640
7653
  // 1.13.1 (15th 블라인드 리뷰 P1, Sonnet): lessons.md + rules.md 누락 수정 — memory search 가 5종 메모리 표면을 표방하나 lesson/rule 을 검색 못 해(lesson add/rule add 로 저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음.
7641
7654
  const files = ['.harness/decisions.md','.harness/lessons.md','.harness/rules.md','.harness/task-log.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/plan.md','.harness/review-evidence.md','.harness/architecture.md'];
7642
7655
  const re = new RegExp(query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i');
@@ -7646,8 +7659,8 @@ function memorySearch(root, query) {
7646
7659
  const lines = read(p).split('\n');
7647
7660
  const hits = lines.map((line, i) => ({ line, i })).filter(x => re.test(x.line));
7648
7661
  if (hits.length) {
7649
- log(`\n# ${f}`);
7650
- for (const h of hits.slice(0, _parseLimit(arg('--limit','5'),5))) log(` L${h.i+1}: ${h.line.trim()}`);
7662
+ if (!jsonMode) log(`\n# ${f}`);
7663
+ for (const h of hits.slice(0, _parseLimit(arg('--limit','5'),5))) { if (!jsonMode) log(` L${h.i+1}: ${h.line.trim()}`); results.push({ file: f, line: h.i + 1, text: h.line.trim() }); }
7651
7664
  total += hits.length;
7652
7665
  }
7653
7666
  }
@@ -7668,8 +7681,8 @@ function memorySearch(root, query) {
7668
7681
  const lines = txt.split('\n');
7669
7682
  const hits = lines.map((line, i) => ({ line, i })).filter(x => re.test(x.line));
7670
7683
  if (hits.length) {
7671
- log(`\n# ${rel(root, p)}`);
7672
- for (const h of hits.slice(0, _parseLimit(arg('--limit','5'),5))) log(` L${h.i+1}: ${h.line.trim().slice(0, 160)}`);
7684
+ if (!jsonMode) log(`\n# ${rel(root, p)}`);
7685
+ for (const h of hits.slice(0, _parseLimit(arg('--limit','5'),5))) { if (!jsonMode) log(` L${h.i+1}: ${h.line.trim().slice(0, 160)}`); results.push({ file: rel(root, p), line: h.i + 1, text: h.line.trim().slice(0, 160) }); }
7673
7686
  total += hits.length;
7674
7687
  }
7675
7688
  }
@@ -7677,6 +7690,7 @@ function memorySearch(root, query) {
7677
7690
  walkCodeDir(dp);
7678
7691
  }
7679
7692
  }
7693
+ if (jsonMode) { log(JSON.stringify({ version: VERSION, query, total, includeCode: has('--include-code'), results }, null, 2)); return; }
7680
7694
  if (total === 0) log('(no matches)');
7681
7695
  else log(`\n${total} matches${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
7682
7696
  }
@@ -11709,13 +11723,21 @@ async function selfCheck(root) {
11709
11723
  // 1.9.2: 게이트 5종 한번에 실행 (verify + audit + scan secrets + encoding check + lazy detect).
11710
11724
  function gate(root) {
11711
11725
  root = absRoot(root);
11712
- log('# leerness gate (5 checks)');
11726
+ const jsonMode = has('--json'); // 외부리뷰 C2: --json 일관성 — 이전엔 텍스트 헤더+단계별 JSON 혼재로 파싱 불가. 단일 객체로 집계.
11727
+ const checks = [];
11713
11728
  let bad = 0;
11729
+ if (!jsonMode) log('# leerness gate (5 checks)');
11714
11730
  function step(label, fn) {
11715
- log(`\n## ${label}`);
11716
11731
  const code0 = process.exitCode || 0;
11717
- try { fn(); } catch (e) { fail(`${label} threw: ${e.message}`); bad++; }
11718
- if (process.exitCode && process.exitCode !== code0) bad++;
11732
+ if (!jsonMode) log(`\n## ${label}`);
11733
+ const orig = process.stdout.write;
11734
+ if (jsonMode) process.stdout.write = () => true; // 단계 하위출력 억제(JSON 오염 방지) — fn 은 동기, finally 로 복원
11735
+ let threw = null;
11736
+ try { fn(); } catch (e) { threw = e; } finally { if (jsonMode) process.stdout.write = orig; }
11737
+ const failed = threw != null || !!(process.exitCode && process.exitCode !== code0);
11738
+ if (threw && !jsonMode) fail(`${label} threw: ${threw.message}`);
11739
+ if (failed) bad++;
11740
+ checks.push({ name: label, ok: !failed, ...(threw ? { error: threw.message } : {}) });
11719
11741
  process.exitCode = 0;
11720
11742
  }
11721
11743
  step('verify', () => verify(root));
@@ -11723,6 +11745,7 @@ function gate(root) {
11723
11745
  step('scan secrets', () => scanSecrets(root));
11724
11746
  step('encoding check', () => encodingCheck(root));
11725
11747
  step('lazy detect', () => lazyDetect(root));
11748
+ if (jsonMode) { log(JSON.stringify({ version: VERSION, root, ok: bad === 0, total: checks.length, failed: bad, checks }, null, 2)); if (bad) process.exitCode = 1; return; }
11726
11749
  log(`\n# gate summary: ${bad} 단계 실패`);
11727
11750
  if (bad) process.exitCode = 1;
11728
11751
  else ok('all gates passed');
@@ -16630,7 +16653,7 @@ function _leernessIdentity() {
16630
16653
  isNot: '실행기/코딩 에이전트가 아님 — 어떤 에이전트 위에도 얹는 공통 운영 계층',
16631
16654
  tagline: '어떤 AI 코딩 에이전트에도 적용되는 범용 운영 레이어 — 기억·정책·인수인계·검증·감사',
16632
16655
  layers: [
16633
- { key: 'memory', ko: '기억', desc: '프로젝트 상태/결정/진행을 .leerness/ 에 영속화 (state start|record|verify|handoff)' },
16656
+ { key: 'memory', ko: '기억', desc: '프로젝트 상태/결정/진행을 .harness/ 에 영속화 (task/decision/lesson/plan; 선택 state substrate 는 .leerness/)' },
16634
16657
  { key: 'policy', ko: '정책', desc: '8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트' },
16635
16658
  { key: 'handoff', ko: '인수인계', desc: '에이전트 간 컨텍스트 표준 전달 (Claude→Codex→Goose), get_project_context 1콜 온보딩' },
16636
16659
  { key: 'verification', ko: '검증', desc: '근거 기반 완료 검증 (verify-claim --require-evidence) — 허위 완료 차단' },
@@ -19505,6 +19528,14 @@ async function main() {
19505
19528
  }
19506
19529
  // 1.9.306 (UR-0045): 명시적 help 요청은 exit 0, 그 외 미인식 명령은 안내 + exit 1 (실패를 성공으로 오판 방지).
19507
19530
  if (cmd === 'help' || cmd === 'commands' || cmd === '--help' || cmd === '-h') { help(); return; }
19531
+ // 1.16.2 (외부클린룸 UR-0042): 유효 명령그룹을 하위명령 없이 부르면 'unknown command'(혼란) 대신 사용법 힌트 — decision/lesson 과 일관.
19532
+ const _GROUP_USAGE = {
19533
+ rule: 'rule add "<텍스트>" --trigger <트리거> | rule list | rule pause/resume/remove <ID> | rule verify',
19534
+ skill: 'skill list | skill add <id> | skill use <id> | skill search "<키>" | skill match "<텍스트>"',
19535
+ feature: 'feature add "<이름>" | feature list | feature show <ID> | feature link <A> <B> | feature impact <ID>',
19536
+ memory: 'memory search "<키>" [--json] | memory status | memory archive | memory restore',
19537
+ };
19538
+ if (_GROUP_USAGE[cmd] && !args[1]) { failJson(has('--json'), 'subcommand_required', `${cmd} 하위명령 필요 — 사용법: leerness ${_GROUP_USAGE[cmd]}`); return; }
19508
19539
  // 1.9.437 (11th 외부평가 Codex P2, UR-0138): --json 모드 unknown command 도 순수 JSON.
19509
19540
  failJson(has('--json'), 'unknown_command', `알 수 없는 명령: ${cmd} (leerness --help 로 전체 명령 확인)`);
19510
19541
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",