leerness 1.9.399 β†’ 1.9.400

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.400 β€” 2026-06-07 πŸŽ‰ β€” anti-laziness λͺ…λ Ή --json μ—λŸ¬ ꡬ쑰화 (7번째 λ²„κ·Έν—ŒνŠΈ P1-B, UR-0105)
4
+
5
+ **πŸ”Œ verify-claim / optimism-check / honesty-check 의 `--json` μ—λŸ¬λ„ ꡬ쑰화 JSON β€” AI μ—μ΄μ „νŠΈ self-gate 검증 λͺ…λ Ήμ˜ μžλ™ν™” μ‹ λ’°μ„±.**
6
+
7
+ ### λ°°κ²½ (7번째 λ²„κ·Έν—ŒνŠΈ P1-B Β· failJson νŒ¨ν„΄ ν™•λŒ€)
8
+ λ²„κ·Έν—ŒνŠΈ: μœ„ 3개 anti-laziness 검증 λͺ…λ Ή(AI μ—μ΄μ „νŠΈκ°€ 자기 검증에 μ‚¬μš©)이 μ—†λŠ” T-ID 에 `--json` 으둜 호좜 μ‹œ `βœ— ... μ—†μŒ` ν…μŠ€νŠΈλ₯Ό stdout 에 좜λ ₯ β†’ JSON.parse ν¬λž˜μ‹œ. 1.9.398 의 failJson 헬퍼λ₯Ό 이듀에 ν™•λŒ€ 적용.
9
+
10
+ ### κ΅¬ν˜„
11
+ - verify-claim / optimism-check: missing_args / not_found λ₯Ό `failJson(_j, ...)` 둜.
12
+ - honesty-check: T-ID not_found λ₯Ό failJson 둜.
13
+ - μ‚¬λžŒμš© 좜λ ₯(--json 없을 λ•Œ) 무변경.
14
+
15
+ ### 검증 (νšŒκ·€ 0)
16
+ - **selftest 145β†’146 PASS** (3개 λͺ…λ Ή failJson 와이어 μ†ŒμŠ€ 확인).
17
+ - **E2E 338β†’339 PASS** (3개 --json μ—λŸ¬ β†’ {ok:false,code:not_found} exit1 + μ‚¬λžŒμš© βœ— ν…μŠ€νŠΈ 보쑴).
18
+
3
19
  ## 1.9.399 β€” 2026-06-07 β€” ν…Œμ΄λΈ”μ…€ injection 차단: task/rule νŒŒμ΄ν”„Β·κ°œν–‰ (7번째 λ²„κ·Έν—ŒνŠΈ P1-A, UR-0104)
4
20
 
5
21
  **πŸ›‘ 데이터 무결성 β€” task/rule ν…μŠ€νŠΈμ˜ νŒŒμ΄ν”„(|)Β·κ°œν–‰(\\n)이 progress-tracker/rules.md ν‘œλ₯Ό μ†μƒΒ·κ°€μ§œν–‰ μ£Όμž…Β·λ©±λ“±μ„± 무λ ₯ν™”ν•˜λ˜ 것을 차단(μ…€ μ΄μŠ€μΌ€μ΄ν”„).**
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
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.399-green)]() [![tests](https://img.shields.io/badge/e2e-338%2F338-success)]() [![selftest](https://img.shields.io/badge/selftest-145-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-85-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.400-green)]() [![tests](https://img.shields.io/badge/e2e-339%2F339-success)]() [![selftest](https://img.shields.io/badge/selftest-146-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-85-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
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.399 ν•˜λ„€μŠ€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. AI μ—μ΄μ „νŠΈλŠ” μž‘μ—… μ „ `leerness handoff`둜 μ»¨ν…μŠ€νŠΈλ₯Ό μ μž¬ν•˜κ³ , μž‘μ—… ν›„ `leerness check`/`leerness audit`/`leerness session close`λ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.
474
+ 이 ν”„λ‘œμ νŠΈλŠ” Leerness v1.9.400 ν•˜λ„€μŠ€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. AI μ—μ΄μ „νŠΈλŠ” μž‘μ—… μ „ `leerness handoff`둜 μ»¨ν…μŠ€νŠΈλ₯Ό μ μž¬ν•˜κ³ , μž‘μ—… ν›„ `leerness check`/`leerness audit`/`leerness session close`λ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.
475
475
 
476
476
  ### 정체성 β€” AI μ—μ΄μ „νŠΈ 운영 λ ˆμ΄μ–΄ (UR-0030)
477
477
 
@@ -525,7 +525,7 @@ leerness memory restore decision <date|title>
525
525
 
526
526
  ### MCP server (μ™ΈλΆ€ AI 톡합)
527
527
 
528
- Leerness v1.9.399λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Code Β· Cursor Β· Codex CLI λ“± μ™ΈλΆ€ AI에 **85개 도ꡬ**λ₯Ό λ…ΈμΆœ:
528
+ Leerness v1.9.400λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Code Β· Cursor Β· Codex CLI λ“± μ™ΈλΆ€ AI에 **85개 도ꡬ**λ₯Ό λ…ΈμΆœ:
529
529
 
530
530
  ```jsonc
531
531
  // μΉ΄ν…Œκ³ λ¦¬λ³„
@@ -546,7 +546,7 @@ Leerness v1.9.399λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Cod
546
546
  `<<autonomous-loop-dynamic>>` μ‹ ν˜Έλ§Œ 보내면 AIκ°€:
547
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) λ‹€μŒ λΌμš΄λ“œ μ˜ˆμ•½.
548
548
 
549
- ν˜„μž¬ λˆ„μ : **70 λΌμš΄λ“œ (1.9.40 β†’ 1.9.399)** Β· λ§€ λΌμš΄λ“œ GitHub release/νƒœκ·Έ 생성 Β· _reports/λŠ” λΉ„κ³΅κ°œ 보쑴.
549
+ ν˜„μž¬ λˆ„μ : **70 λΌμš΄λ“œ (1.9.40 β†’ 1.9.400)** Β· λ§€ λΌμš΄λ“œ GitHub release/νƒœκ·Έ 생성 Β· _reports/λŠ” λΉ„κ³΅κ°œ 보쑴.
550
550
 
551
551
  ### μ„±λŠ₯ κ°€μ΄λ“œ (1.9.140 μΈ‘μ •)
552
552
 
@@ -584,6 +584,6 @@ leerness release pack --close --auto-main-push
584
584
  - `.harness/session-handoff.md`: λ‹€μŒ μ„Έμ…˜ μΈμˆ˜μΈκ³„ (μžλ™ μž‘μ„±)
585
585
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영ꡬ λ©”λͺ¨λ¦¬ (5 surface)
586
586
 
587
- Last synced by Leerness v1.9.399: 2026-06-06
587
+ Last synced by Leerness v1.9.400: 2026-06-06
588
588
  <!-- leerness:project-readme:end -->
589
589
 
package/bin/harness.js CHANGED
@@ -31,7 +31,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
31
31
  // 1.9.295 (UR-0025 4단계): 정적 데이터 μΉ΄νƒˆλ‘œκ·Έ λͺ¨λ“ˆ 뢄리 (λΉ„νŒŒκ΄΄, require-based).
32
32
  const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_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 뢄리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
33
33
 
34
- const VERSION = '1.9.399';
34
+ const VERSION = '1.9.400';
35
35
 
36
36
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI μ „μš© λΆ€μž‘μš©μ€ require μ‹œ μ‹€ν–‰ν•˜μ§€ μ•ŠλŠ”λ‹€.
37
37
  // 이전: warning listener 제거 / NODE_OPTIONS λ³€κ²½ / chcp IIFE κ°€ top-level μ¦‰μ‹œ μ‹€ν–‰ β†’ require('harness') μ‹œ 호슀트 ν”„λ‘œμ„ΈμŠ€ μ˜€μ—Ό.
@@ -3004,6 +3004,7 @@ function _selfTestCases() {
3004
3004
  { name: '6번째 외뢀평가/codex P1-A (UR-0098): install-safety λ ˆμ‹œν”Ό μ…Έ-무관 + hardeningNote (1.9.397)', run: () => { if (typeof installSafetyCmd !== 'function') return false; const save = process.argv; const _w = process.stdout.write; let out = ''; try { process.argv = ['node', 'h', 'install-safety', '--json']; process.stdout.write = s => { out += s; return true; }; installSafetyCmd({ json: true }); } catch {} finally { process.stdout.write = _w; process.argv = save; } let j; try { j = JSON.parse(out); } catch {} const noPosixPrefix = !!j && Array.isArray(j.safeInstall) && !j.safeInstall.some(x => /^npm_config_\w+=/.test(String(x).trim())); const crossShell = !!j && j.safeInstall.filter(x => String(x).includes('npx --yes')).length >= 2; const noteOk = !!j && typeof j.hardeningNote === 'string' && j.hardeningNote.includes('PowerShell'); return noPosixPrefix && crossShell && noteOk; } },
3005
3005
  { name: '6번째 외뢀평가/codex P1-C (UR-0099): --json μ—λŸ¬ 경둜 ꡬ쑰화 failJson + 와이어 (1.9.398)', run: () => { const io = require('../lib/io'); if (io.failJson !== failJson) return false; const _w = process.stdout.write; const saved = process.exitCode; let jOut = '', hOut = ''; let jExit = 0; try { process.stdout.write = s => { jOut += s; return true; }; process.exitCode = 0; failJson(true, 'tc', 'm'); jExit = process.exitCode; process.stdout.write = s => { hOut += s; return true; }; process.exitCode = 0; failJson(false, 'c', 'humanmsg'); } catch {} finally { process.stdout.write = _w; process.exitCode = saved; } let pj; try { pj = JSON.parse(jOut); } catch {} const jsonOk = !!pj && pj.ok === false && pj.code === 'tc' && pj.error === 'm' && jExit === 1; const humanOk = hOut.includes('βœ—') && hOut.includes('humanmsg') && !hOut.includes('{'); const src = read(__filename); const wired = src.includes("failJson(_j, 'missing_args'") && src.includes("failJson(_j, 'spec_not_found'"); return jsonOk && humanOk && wired; } },
3006
3006
  { name: '7번째 λ²„κ·Έν—ŒνŠΈ P1-A (UR-0104): ν…Œμ΄λΈ”μ…€ μ•ˆμ „ν™” _cellSafe/_cellUnescape (νŒŒμ΄ν”„/κ°œν–‰ injection 차단) (1.9.399)', run: () => { const m = require('../lib/pure-utils'); if (m._cellSafe !== _cellSafe || m._cellUnescape !== _cellUnescape) return false; const safe = _cellSafe('fix | bug\nrow2'); const noRaw = !/(?<!\\)\|/.test(safe) && !/[\r\n]/.test(safe); const pipeRt = _cellUnescape(_cellSafe('a | b | c')) === 'a | b | c'; const nlGone = _cellSafe('a\nb') === 'a b'; const src = read(__filename); const wired = src.includes('_cellSafe(r.request)') && src.includes('_cellSafe(r.rule)'); return noRaw && pipeRt && nlGone && wired; } },
3007
+ { name: '7번째 λ²„κ·Έν—ŒνŠΈ P1-B (UR-0105): verify-claim/optimism-check/honesty-check --json μ—λŸ¬ ꡬ쑰화 (1.9.400)', run: () => { const src = read(__filename); const vc = /function verifyClaimCmd[\s\S]{0,400}?failJson\(_j, 'not_found'/.test(src); const oc = /function optimismCheckCmd[\s\S]{0,400}?failJson\(_j, 'not_found'/.test(src); const hc = /function honestyCheckCmd[\s\S]{0,900}?failJson\(has\('--json'\), 'not_found'/.test(src); return vc && oc && hc; } },
3007
3008
  { name: 'VERSION ν˜•μ‹ (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3008
3009
  ];
3009
3010
  }
@@ -9235,10 +9236,11 @@ function _gitChangedFiles(root) {
9235
9236
  // _claimFileInGit β†’ lib/analyzers.js (1.9.304 UR-0025)
9236
9237
  function verifyClaimCmd(root, taskId) {
9237
9238
  root = absRoot(root);
9238
- if (!taskId) return fail('verify-claim <T-ID> ν•„μš”. 예: leerness verify-claim T-0008');
9239
+ const _j = has('--json'); // 1.9.400 (7번째 λ²„κ·Έν—ŒνŠΈ P1-B, UR-0105): --json μ—λŸ¬λ„ ꡬ쑰화
9240
+ if (!taskId) return failJson(_j, 'missing_args', 'verify-claim <T-ID> ν•„μš”. 예: leerness verify-claim T-0008');
9239
9241
  const rows = readProgressRows(root);
9240
9242
  const row = rows.find(r => r.id === taskId);
9241
- if (!row) return fail(`progress-tracker.md에 ${taskId} μ—†μŒ.`);
9243
+ if (!row) return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} μ—†μŒ.`);
9242
9244
 
9243
9245
  const evidence = row.evidence || '';
9244
9246
  // 1.9.20: 파일 경둜 μΆ”μΆœ β€” 도메인 폴더 μžλ™ 인식 + 루트 λ©”νƒ€νŒŒμΌ
@@ -9994,7 +9996,7 @@ function honestyCheckCmd(root, arg1) {
9994
9996
  if (textArg) { subject = String(textArg); sourceLabel = '--text'; }
9995
9997
  else if (arg1 && !arg1.startsWith('-')) {
9996
9998
  const row = readProgressRows(root).find(r => r.id === arg1);
9997
- if (!row) { fail(`progress-tracker.md에 ${arg1} μ—†μŒ.`); process.exitCode = 1; return; }
9999
+ if (!row) { failJson(has('--json'), 'not_found', `progress-tracker.md에 ${arg1} μ—†μŒ.`); return; } // 1.9.400 (UR-0105): --json μ—λŸ¬ ꡬ쑰화
9998
10000
  subject = row.evidence || ''; sourceLabel = `${arg1} evidence`;
9999
10001
  } else { fail('μ‚¬μš©λ²•: leerness honesty-check <T-ID> λ˜λŠ” leerness honesty-check --text "<μ£Όμž₯>"'); process.exitCode = 1; return; }
10000
10002
  const r = _epistemicHonestyCheck(subject);
@@ -10012,10 +10014,11 @@ function honestyCheckCmd(root, arg1) {
10012
10014
  }
10013
10015
  function optimismCheckCmd(root, taskId) {
10014
10016
  root = absRoot(root || process.cwd());
10015
- if (!taskId) return fail('optimism-check <T-ID> ν•„μš”. 예: leerness optimism-check T-0001');
10017
+ const _j = has('--json'); // 1.9.400 (7번째 λ²„κ·Έν—ŒνŠΈ P1-B, UR-0105): --json μ—λŸ¬λ„ ꡬ쑰화
10018
+ if (!taskId) return failJson(_j, 'missing_args', 'optimism-check <T-ID> ν•„μš”. 예: leerness optimism-check T-0001');
10016
10019
  const rows = readProgressRows(root);
10017
10020
  const row = rows.find(r => r.id === taskId);
10018
- if (!row) return fail(`progress-tracker.md에 ${taskId} μ—†μŒ.`);
10021
+ if (!row) return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} μ—†μŒ.`);
10019
10022
 
10020
10023
  const codeText = _scanCodeForPatterns(root);
10021
10024
  const suspects = _detectOptimism(row.evidence || '', codeText);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.399",
3
+ "version": "1.9.400",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -5584,5 +5584,23 @@ total++;
5584
5584
  if (!ok) failed++;
5585
5585
  }
5586
5586
 
5587
+ // 1.9.400 νšŒκ·€ (7번째 λ²„κ·Έν—ŒνŠΈ P1-B, UR-0105): verify-claim/optimism-check/honesty-check --json μ—λŸ¬κ°€ ꡬ쑰화 JSON + μ‚¬λžŒμš© 보쑴
5588
+ total++;
5589
+ {
5590
+ let ok = false;
5591
+ try {
5592
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-antilazyjson-'));
5593
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
5594
+ const jsonErr = (cmd) => { const r = cp.spawnSync(process.execPath, [CLI, cmd, 'T-9999', '--path', d, '--json'], { encoding: 'utf8', timeout: 15000 }); try { const j = JSON.parse(r.stdout); return j.ok === false && j.code === 'not_found' && r.status === 1; } catch { return false; } };
5595
+ const allJson = jsonErr('verify-claim') && jsonErr('optimism-check') && jsonErr('honesty-check');
5596
+ const hr = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-9999', '--path', d], { encoding: 'utf8', timeout: 15000 });
5597
+ const humanOk = hr.status === 1 && /βœ—/.test(hr.stdout || '') && !/^\s*\{/.test(hr.stdout || '');
5598
+ fs.rmSync(d, { recursive: true, force: true });
5599
+ ok = allJson && humanOk;
5600
+ } catch {}
5601
+ console.log(ok ? 'βœ“ B(1.9.400) 7thλ²„κ·Έν—ŒνŠΈ P1-B: anti-laziness(verify-claim/optimism/honesty) --json μ—λŸ¬ ꡬ쑰화 + μ‚¬λžŒμš© 보쑴 (UR-0105)' : 'βœ— anti-laziness --json μ—λŸ¬ μ‹€νŒ¨');
5602
+ if (!ok) failed++;
5603
+ }
5604
+
5587
5605
  console.log(`\nE2E result: ${total - failed}/${total} passed Β· ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
5588
5606
  if (failed > 0) process.exit(1);