leerness 1.15.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,84 @@
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
+
50
+ ## 1.16.0 — 2026-06-09 — 🛡️ [안정화/Stable] 16번째 버그헌트 안정 minor
51
+
52
+ **🛡️ 안정화(Stable) minor.** 16번째 멀티에이전트 버그헌트(8건 발굴·전부 직접 재현)의 코어 수정(1.15.1)을 검증·통합해 npm 공개. R-0011 정책의 7번째 minor. **이 릴리스 영상부터 개선된 "문제→해소" 디자인**(짧은 인트로 + 이전/이제 모션 + 의미 보존 하이라이트)으로 제작됩니다.
53
+
54
+ ### 이번 minor 통합 (1.15.1 — 전부 재현 검증)
55
+ - **🔴 시크릿 스캐너 FN 차단**: 한 파일에 같은 패턴 시크릿이 2개 이상이면 첫 번째만 보고하고 나머지를 놓치던 문제(루프 break) 수정 → 모두 탐지.
56
+ - **🟠 task/rule list 표 정렬**: 제목에 파이프(`|`)가 있으면 마크다운 표 칼럼이 깨지던 표시 버그 → `_cellSafe` 적용.
57
+ - **📖 문서 정합**: `encoding check` 도움말이 실제 미검사 항목(CRLF)을 광고하던 것 정정.
58
+ - (사이트 영상 파이프라인 4버그 — 큐 키/빈 제목/빈 WAV 통과/check-only 부작용 — 도 동시 수정.)
59
+
60
+ ### 검증 (회귀 0)
61
+ - **selftest 214 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump. 시크릿 멀티매치·표 파이프·도움말 정합 행위 재현.
62
+
63
+ ### 안정화 표시 (R-0006)
64
+ CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
65
+
66
+ ## 1.15.1 — 2026-06-09 — 16번째 버그헌트: 시크릿 스캐너 FN + 표 파이프 + 문서정합
67
+
68
+ **🔬 멀티에이전트 버그헌트(신규코드 0건·코어 4건·사이트 4건 발굴) → 8건 전부 직접 재현 후 진짜만 수정.** 맹신 X: 각 지적을 temp 에서 직접 재현하고, 의도된 동작은 수정 제외.
69
+
70
+ ### 수정 (코어 3건 — 전부 재현 검증)
71
+ - **🔴 시크릿 스캐너 FN (F1)**: 한 파일에 같은 패턴의 시크릿이 2개 이상(예: `secret:` 와 `api_key:` 둘 다 'Hardcoded password' 패턴)이면 **첫 번째만 잡고 나머지 누락**(루프 `break`). break 제거 + zero-width 가드 → 모두 탐지.
72
+ - **🟠 task/rule list 표 깨짐 (F2)**: 항목 제목에 파이프(`|`)가 있으면 마크다운 표 칼럼이 어긋남(저장은 안전했으나 list **표시**가 raw). 표시도 `_cellSafe` 적용(`\|`).
73
+ - **문서정합 (F3)**: `encoding check` 도움말이 'CRLF 검사'를 광고했으나 실제 미검사(정책은 BOM/NUL/.bat/roundtrip). 도움말을 실제 동작과 일치하게 정정.
74
+
75
+ ### 의도된 동작(수정 안 함 — 맹신 X)
76
+ - `status --json` 가 `healthy:false` 여도 exit 0: **설계상 정보성 명령**(`scope:'install'` + `healthyMeaning` 명시, 게이트는 `verify`/`gate`). 버그 아님.
77
+
78
+ ### 검증 (회귀 0)
79
+ - **selftest 213→214** · **E2E 365/365** · F1(2건 탐지)·F2(파이프 `\|` 이스케이프)·F3(help CRLF 제거) 행위 재현. (사이트 파이프라인 4건은 leerness-site 에서 별도 수정.)
80
+ - patch(1.15.1) — npm 미배포(R-0011, GitHub).
81
+
3
82
  ## 1.15.0 — 2026-06-09 — 🛡️ [안정화/Stable] Karpathy 가이드라인 정렬 3부작 안정 minor
4
83
 
5
84
  **🛡️ 안정화(Stable) minor.** Andrej Karpathy 코딩 가이드라인(생각하고 코딩 / 단순성 / 외과적 변경 / 목표 주도) 대비 외부 에이전트 검토에서 도출한 정렬 작업(1.14.1~1.14.3)을 검증·통합해 npm 공개. R-0011 정책의 6번째 minor. 영상은 HyperFrames 파이프라인 제작.
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.15.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)** — 근거 기반 완료 검증으로 허위 완료 차단
@@ -208,7 +208,7 @@ leerness status . # 설치 상태
208
208
  leerness verify . # 필수 파일 검증
209
209
  leerness audit . # 일관성·계획-진행 정렬 감사
210
210
  leerness scan secrets . # 시크릿 패턴 스캔
211
- leerness encoding check . # UTF-8 / BOM / CRLF 검사
211
+ leerness encoding check . # UTF-8 / BOM / NUL / .bat 인코딩 검사
212
212
  leerness lazy detect . # 게으름 방지 자동 평가
213
213
  leerness memory search "키" # 결정/이력 검색
214
214
  leerness session close . # 세션 종료 + handoff 자동 작성
@@ -240,7 +240,7 @@ leerness memory restore decision <date|title>
240
240
 
241
241
  ### MCP server (외부 AI 통합)
242
242
 
243
- Leerness v1.15.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.15.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.15.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.15.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.15.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)** — 근거 기반 완료 검증으로 허위 완료 차단',
@@ -297,7 +297,7 @@ function managedReadmeBlock(project) {
297
297
  'leerness verify . # 필수 파일 검증',
298
298
  'leerness audit . # 일관성·계획-진행 정렬 감사',
299
299
  'leerness scan secrets . # 시크릿 패턴 스캔',
300
- 'leerness encoding check . # UTF-8 / BOM / CRLF 검사',
300
+ 'leerness encoding check . # UTF-8 / BOM / NUL / .bat 인코딩 검사',
301
301
  'leerness lazy detect . # 게으름 방지 자동 평가',
302
302
  'leerness memory search "키" # 결정/이력 검색',
303
303
  'leerness session close . # 세션 종료 + handoff 자동 작성',
@@ -624,7 +624,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
624
624
  '.claude/commands/audit.md': `# /audit\n\n계획-진행 정렬, 디자인/재사용 일관성, 시크릿/인코딩을 일괄 점검합니다.\n\n\`\`\`\n!leerness audit .\n!leerness scan secrets .\n!leerness encoding check .\n\`\`\`\n`,
625
625
  '.claude/commands/lazy-detect.md': `# /lazy-detect\n\n게으름 방지 자동 평가를 실행합니다.\n\n\`\`\`\n!leerness lazy detect .\n\`\`\`\n`,
626
626
  '.claude/commands/update.md': `# /update\n\nleerness 자동 업데이트를 실행합니다 (감지 → 마이그레이션 → 검증).\n\n\`\`\`\n!leerness update --yes\n\`\`\`\n`,
627
- '.claude/skills/leerness.md': `---\nname: leerness\ndescription: Leerness harness commands - handoff, audit, scan secrets, encoding check, lazy detect, session close, update. Use when the user asks to load project context, verify work quality, scan secrets, check encoding, or end a session.\n---\n\n# leerness skill\n\n## When to use\n- 사용자가 프로젝트 컨텍스트를 로드해달라고 할 때\n- 완료 선언 전 자기 검증을 요청할 때\n- 세션을 종료하거나 인수인계를 요청할 때\n- 시크릿/한글 인코딩 점검을 요청할 때\n- 새 leerness 버전 적용을 요청할 때\n\n## Commands\n\n\`\`\`bash\nleerness handoff . # 컨텍스트 로드\nleerness check . # pre-action 체크\nleerness audit . # 일관성/계획 정렬 감사\nleerness scan secrets . # 시크릿 패턴 스캔\nleerness encoding check . # UTF-8/BOM/CRLF\nleerness lazy detect . # 게으름 평가\nleerness memory search "key" # 결정/이력 검색\nleerness session close . # 종료 보고 + handoff 자동 생성\nleerness update --yes # 자동 업데이트\n\`\`\`\n`,
627
+ '.claude/skills/leerness.md': `---\nname: leerness\ndescription: Leerness harness commands - handoff, audit, scan secrets, encoding check, lazy detect, session close, update. Use when the user asks to load project context, verify work quality, scan secrets, check encoding, or end a session.\n---\n\n# leerness skill\n\n## When to use\n- 사용자가 프로젝트 컨텍스트를 로드해달라고 할 때\n- 완료 선언 전 자기 검증을 요청할 때\n- 세션을 종료하거나 인수인계를 요청할 때\n- 시크릿/한글 인코딩 점검을 요청할 때\n- 새 leerness 버전 적용을 요청할 때\n\n## Commands\n\n\`\`\`bash\nleerness handoff . # 컨텍스트 로드\nleerness check . # pre-action 체크\nleerness audit . # 일관성/계획 정렬 감사\nleerness scan secrets . # 시크릿 패턴 스캔\nleerness encoding check . # UTF-8/BOM/NUL\nleerness lazy detect . # 게으름 평가\nleerness memory search "key" # 결정/이력 검색\nleerness session close . # 종료 보고 + handoff 자동 생성\nleerness update --yes # 자동 업데이트\n\`\`\`\n`,
628
628
  };
629
629
  // 1.9.276: minimal 모드 — 코어가 요구하지 않는 파일 제외 (verify 필수 파일은 유지).
630
630
  if (opts.minimal) { for (const k of MINIMAL_SKIP_KEYS) delete _files[k]; }
@@ -3539,6 +3539,24 @@ function _selfTestCases() {
3539
3539
  const dw = (b.match(/^Done-When:\s*(.+)$/m) || [])[1];
3540
3540
  return wired && dw === '로그인 e2e 테스트 통과';
3541
3541
  } },
3542
+ { name: '16th 버그헌트 F1/F2: scan secrets 패턴당 멀티매치(break 제거) + task/rule list 파이프 셀안전 (1.15.1)', run: () => {
3543
+ const src = read(__filename);
3544
+ const f1 = src.includes('같은 패턴이 한 파일에 여러 번') && src.includes('if (re.lastIndex === m.index) re.lastIndex++;');
3545
+ const f2 = src.includes('_cellSafe(r.request)') && src.includes('_cellSafe(r.rule)');
3546
+ return f1 && f2;
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
+ } },
3542
3560
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3543
3561
  ];
3544
3562
  }
@@ -6589,7 +6607,7 @@ function taskList(root) {
6589
6607
  if (!filtered.length) return log('(no tasks)');
6590
6608
  log('| ID | Status | Request | Evidence | Next Action | Updated |');
6591
6609
  log('|---|---|---|---|---|---|');
6592
- for (const r of filtered) log(`| ${r.id} | ${r.status} | ${r.request} | ${r.evidence} | ${r.nextAction} | ${r.updated} |`);
6610
+ for (const r of filtered) log(`| ${r.id} | ${r.status} | ${_cellSafe(r.request)} | ${_cellSafe(r.evidence)} | ${_cellSafe(r.nextAction)} | ${r.updated} |`); // 16th 버그헌트 F2: 표시도 _cellSafe — 파이프(|)가 칼럼 깨던 것 차단(저장은 안전했으나 list 표시가 raw)
6593
6611
  }
6594
6612
  // 1.9.310 (UR-0046, 설치리뷰 3중수렴): CLI/MCP 입력 스키마 검증 — 무효 status/trigger 거부(--force 우회).
6595
6613
  // 이전: task --status nonsense / rule --trigger 오타가 그대로 등록돼 상태/정책 신뢰성 훼손.
@@ -7394,7 +7412,8 @@ function _collectSecretFindings(root) {
7394
7412
  if (valueGroup != null && requireSecretLike && !_looksSecretLike(val)) { if (re.lastIndex === m.index) re.lastIndex++; continue; }
7395
7413
  const line = text.slice(0, m.index).split('\n').length;
7396
7414
  findings.push({ file: fileRel, line, name, snippet: m[0].slice(0, 32), gitignored });
7397
- break;
7415
+ // 16th 버그헌트 F1: break 제거 — 같은 패턴이 한 파일에 여러 번(예: secret: + api_key: 둘 다 'Hardcoded password') 나오면 모두 보고(보안 FN 차단). zero-width 매치는 lastIndex 전진으로 무한루프 방지.
7416
+ if (re.lastIndex === m.index) re.lastIndex++;
7398
7417
  }
7399
7418
  }
7400
7419
  }
@@ -7629,7 +7648,8 @@ function preCheck(root) {
7629
7648
 
7630
7649
  function memorySearch(root, query) {
7631
7650
  root = absRoot(root);
7632
- 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; }
7633
7653
  // 1.13.1 (15th 블라인드 리뷰 P1, Sonnet): lessons.md + rules.md 누락 수정 — memory search 가 5종 메모리 표면을 표방하나 lesson/rule 을 검색 못 해(lesson add/rule add 로 저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음.
7634
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'];
7635
7655
  const re = new RegExp(query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i');
@@ -7639,8 +7659,8 @@ function memorySearch(root, query) {
7639
7659
  const lines = read(p).split('\n');
7640
7660
  const hits = lines.map((line, i) => ({ line, i })).filter(x => re.test(x.line));
7641
7661
  if (hits.length) {
7642
- log(`\n# ${f}`);
7643
- 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() }); }
7644
7664
  total += hits.length;
7645
7665
  }
7646
7666
  }
@@ -7661,8 +7681,8 @@ function memorySearch(root, query) {
7661
7681
  const lines = txt.split('\n');
7662
7682
  const hits = lines.map((line, i) => ({ line, i })).filter(x => re.test(x.line));
7663
7683
  if (hits.length) {
7664
- log(`\n# ${rel(root, p)}`);
7665
- 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) }); }
7666
7686
  total += hits.length;
7667
7687
  }
7668
7688
  }
@@ -7670,6 +7690,7 @@ function memorySearch(root, query) {
7670
7690
  walkCodeDir(dp);
7671
7691
  }
7672
7692
  }
7693
+ if (jsonMode) { log(JSON.stringify({ version: VERSION, query, total, includeCode: has('--include-code'), results }, null, 2)); return; }
7673
7694
  if (total === 0) log('(no matches)');
7674
7695
  else log(`\n${total} matches${has('--include-code') ? ' (소스 코드 포함)' : ''}`);
7675
7696
  }
@@ -11702,13 +11723,21 @@ async function selfCheck(root) {
11702
11723
  // 1.9.2: 게이트 5종 한번에 실행 (verify + audit + scan secrets + encoding check + lazy detect).
11703
11724
  function gate(root) {
11704
11725
  root = absRoot(root);
11705
- log('# leerness gate (5 checks)');
11726
+ const jsonMode = has('--json'); // 외부리뷰 C2: --json 일관성 — 이전엔 텍스트 헤더+단계별 JSON 혼재로 파싱 불가. 단일 객체로 집계.
11727
+ const checks = [];
11706
11728
  let bad = 0;
11729
+ if (!jsonMode) log('# leerness gate (5 checks)');
11707
11730
  function step(label, fn) {
11708
- log(`\n## ${label}`);
11709
11731
  const code0 = process.exitCode || 0;
11710
- try { fn(); } catch (e) { fail(`${label} threw: ${e.message}`); bad++; }
11711
- 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 } : {}) });
11712
11741
  process.exitCode = 0;
11713
11742
  }
11714
11743
  step('verify', () => verify(root));
@@ -11716,6 +11745,7 @@ function gate(root) {
11716
11745
  step('scan secrets', () => scanSecrets(root));
11717
11746
  step('encoding check', () => encodingCheck(root));
11718
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; }
11719
11749
  log(`\n# gate summary: ${bad} 단계 실패`);
11720
11750
  if (bad) process.exitCode = 1;
11721
11751
  else ok('all gates passed');
@@ -12937,7 +12967,7 @@ function ruleList(root) {
12937
12967
  if (!rules.length) return ok('등록된 룰 없음');
12938
12968
  log('| ID | Trigger | Rule | Status | Last Verified |');
12939
12969
  log('|---|---|---|---|---|');
12940
- for (const r of rules) log(`| ${r.id} | ${r.trigger} | ${r.rule} | ${r.status} | ${r.lastVerified} |`);
12970
+ for (const r of rules) log(`| ${r.id} | ${_cellSafe(r.trigger)} | ${_cellSafe(r.rule)} | ${r.status} | ${r.lastVerified} |`); // 16th 버그헌트 F2: 파이프 칼럼 깨짐 차단(표시 _cellSafe)
12941
12971
  }
12942
12972
 
12943
12973
  function ruleRemove(root, id) {
@@ -16623,7 +16653,7 @@ function _leernessIdentity() {
16623
16653
  isNot: '실행기/코딩 에이전트가 아님 — 어떤 에이전트 위에도 얹는 공통 운영 계층',
16624
16654
  tagline: '어떤 AI 코딩 에이전트에도 적용되는 범용 운영 레이어 — 기억·정책·인수인계·검증·감사',
16625
16655
  layers: [
16626
- { key: 'memory', ko: '기억', desc: '프로젝트 상태/결정/진행을 .leerness/ 에 영속화 (state start|record|verify|handoff)' },
16656
+ { key: 'memory', ko: '기억', desc: '프로젝트 상태/결정/진행을 .harness/ 에 영속화 (task/decision/lesson/plan; 선택 state substrate 는 .leerness/)' },
16627
16657
  { key: 'policy', ko: '정책', desc: '8단계 권한 등급 + enforce (read-only→publish), MCP 호출 게이트' },
16628
16658
  { key: 'handoff', ko: '인수인계', desc: '에이전트 간 컨텍스트 표준 전달 (Claude→Codex→Goose), get_project_context 1콜 온보딩' },
16629
16659
  { key: 'verification', ko: '검증', desc: '근거 기반 완료 검증 (verify-claim --require-evidence) — 허위 완료 차단' },
@@ -19498,6 +19528,14 @@ async function main() {
19498
19528
  }
19499
19529
  // 1.9.306 (UR-0045): 명시적 help 요청은 exit 0, 그 외 미인식 명령은 안내 + exit 1 (실패를 성공으로 오판 방지).
19500
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; }
19501
19539
  // 1.9.437 (11th 외부평가 Codex P2, UR-0138): --json 모드 unknown command 도 순수 JSON.
19502
19540
  failJson(has('--json'), 'unknown_command', `알 수 없는 명령: ${cmd} (leerness --help 로 전체 명령 확인)`);
19503
19541
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",