leerness 1.36.43 → 1.36.44

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.44 — 2026-07-16 — enforce 적대적 하드닝 — FP/우회 자체 헌트 (anti-cheat 출하 후속 규율)
4
+
5
+ 1.36.43 enforce 는 anti-cheat 다 — 출하 규율(bypass+FP 헌터)에 따라 자체 적대 헌트를 돌린 결과 3건:
6
+
7
+ - **FP(실차단 버그) — `.harness` 없는 체크아웃 오차단**: 워크트리/`.harness` 없는 브랜치에서 커밋이 차단됐다(강제 대상이 아닌 상태). → 훅이 `.harness` 부재 시 체인만 실행하고 통과. 실측: `.harness` 없는 워크트리 커밋 0.
8
+ - **worktree 훅 경로**: `.git` 이 파일인 워크트리에서 `install` 이 경로를 오해석할 수 있던 것 → `git rev-parse --git-common-dir` 로 해석(본체/워크트리 모두 정답). 실측: 워크트리에서 install → 공용 훅에 기록.
9
+ - **`--no-verify` 우회 — 정직 처분**: git 설계상 클라이언트 차단 불가. → (a) **`leerness enforce audit`**: 최근 30 커밋 시각 vs handoff 창 대조로 우회 의심 사후 감지(advisory, 의심 시 exit 1). 실측: `--no-verify` 커밋만 정확히 표기, 창 안 커밋 무의심. (b) status 에 한계 명시 — 우회 불가 하드 레이어는 CI(`ci init` + branch protection)임을 정직하게 안내.
10
+ - **검증**: selftest 314/314, 하드닝 3건 원 재현 before/after + 본체 강제 무회귀, exit 전파형 게이트 e2e, 클린룸.
11
+
3
12
  ## 1.36.43 — 2026-07-16 — `leerness enforce` — 사용 강제를 문서에서 커밋 관문으로 (사용자 명시: codex goal 모드가 leerness 미참조)
4
13
 
5
14
  **배경(사용자 실측 버그)**: codex 의 goal 모드처럼 AGENTS.md/CLAUDE.md 를 읽지 않는 에이전트 경로에선 하네스 문서 수준의 지시가 무력하다. 실효 강제는 **어떤 에이전트/모드든 통과해야 하는 보편 관문 = git pre-commit** 에서.
package/README.md CHANGED
@@ -121,7 +121,7 @@ MIT
121
121
  <!-- leerness:project-readme:start -->
122
122
  ## Leerness Project Harness
123
123
 
124
- 이 프로젝트는 Leerness v1.36.43 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
124
+ 이 프로젝트는 Leerness v1.36.44 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
125
 
126
126
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
127
127
 
@@ -175,7 +175,7 @@ leerness memory restore decision <date|title>
175
175
 
176
176
  ### MCP server (외부 AI 통합)
177
177
 
178
- Leerness v1.36.43는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
178
+ Leerness v1.36.44는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
179
 
180
180
  ```jsonc
181
181
  // 카테고리별
@@ -196,7 +196,7 @@ Leerness v1.36.43는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
196
196
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
197
197
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
198
198
 
199
- 현재 누적: **v1.9.x → 1.36.43 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
199
+ 현재 누적: **v1.9.x → 1.36.44 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
200
 
201
201
  ### 성능 가이드
202
202
 
@@ -234,6 +234,6 @@ leerness release pack --close --auto-main-push
234
234
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
235
235
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
236
236
 
237
- Last synced by Leerness v1.36.43: 2026-07-16
237
+ Last synced by Leerness v1.36.44: 2026-07-16
238
238
  <!-- leerness:project-readme:end -->
239
239
 
package/bin/leerness.js CHANGED
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
34
34
  const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
35
35
  const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
36
36
 
37
- const VERSION = '1.36.43';
37
+ const VERSION = '1.36.44';
38
38
 
39
39
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
40
40
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -3345,6 +3345,14 @@ function _selfTestCases() {
3345
3345
  } catch { behavOk = false; }
3346
3346
  return wired && selfContained && chainOk && initAuto && behavOk;
3347
3347
  } },
3348
+ { name: 'enforce 하드닝 (1.36.44, 적대적 자체헌트): .harness 無 체크아웃 FP 방지 + worktree 훅경로 + --no-verify 사후감지 audit + 한계 정직 (소스가드)', run: () => {
3349
+ const s = read(__filename);
3350
+ const fpFix = s.includes("if [ ! -d \".harness\" ]; then") && s.includes('오차단(FP) 방지');
3351
+ const wtOk = s.includes("'rev-parse', '--git-common-dir'");
3352
+ const auditOk = s.includes("sub === 'audit'") && s.includes('우회 의심') && s.includes("'git_log_failed'");
3353
+ const honestOk = s.includes('git commit --no-verify 는 git 설계상 훅을 스킵');
3354
+ return fpFix && wtOk && auditOk && honestOk;
3355
+ } },
3348
3356
  { name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
3349
3357
  const d = LENS_CATALOG.debug;
3350
3358
  if (!d) return false;
@@ -5031,7 +5039,17 @@ function _enforceCfg(root) {
5031
5039
  try { if (exists(_enforceCfgPath(root))) return Object.assign(def, JSON.parse(read(_enforceCfgPath(root)))); } catch {}
5032
5040
  return def;
5033
5041
  }
5034
- function _gitHookPath(root) { return path.join(root, '.git', 'hooks', 'pre-commit'); }
5042
+ function _gitHookPath(root) {
5043
+ // 1.36.44 (하드닝): worktree 에선 .git 이 파일 — 훅은 공용 디렉토리에 산다. git 에게 물어 해석(양쪽 다 정답).
5044
+ try {
5045
+ const r = cp.spawnSync('git', ['-C', root, 'rev-parse', '--git-common-dir'], { encoding: 'utf8', timeout: 5000 });
5046
+ if (r.status === 0 && r.stdout.trim()) {
5047
+ const gd = r.stdout.trim();
5048
+ return path.join(path.isAbsolute(gd) ? gd : path.join(root, gd), 'hooks', 'pre-commit');
5049
+ }
5050
+ } catch {}
5051
+ return path.join(root, '.git', 'hooks', 'pre-commit');
5052
+ }
5035
5053
  function enforceCmd(root, sub) {
5036
5054
  root = absRoot(root);
5037
5055
  const json = has('--json');
@@ -5057,6 +5075,11 @@ function enforceCmd(root, sub) {
5057
5075
  '#!/bin/sh',
5058
5076
  _ENFORCE_MARK + ' — leerness 사용 강제 (제거: leerness enforce remove)',
5059
5077
  'if [ "$LEERNESS_ENFORCE_BYPASS" = "1" ]; then exit 0; fi',
5078
+ '# 1.36.44: .harness 없는 체크아웃(워크트리/구브랜치)은 강제 대상 아님 — 오차단(FP) 방지',
5079
+ 'if [ ! -d ".harness" ]; then',
5080
+ ' if [ -f "$(dirname "$0")/pre-commit.pre-leerness" ]; then exec sh "$(dirname "$0")/pre-commit.pre-leerness" "$@"; fi',
5081
+ ' exit 0',
5082
+ 'fi',
5060
5083
  `LH=".harness/last-handoff.json"`,
5061
5084
  `WIN_MIN=${windowHours * 60}`,
5062
5085
  'if [ ! -f "$LH" ] || [ -z "$(find "$LH" -mmin -"$WIN_MIN" 2>/dev/null)" ]; then',
@@ -5099,6 +5122,34 @@ function enforceCmd(root, sub) {
5099
5122
  if (!json) ok(`enforce check 통과 (handoff ${lastAt ? Math.round(ageH * 10) / 10 + 'h 전' : '-'}${cfg.strict ? ' + gate' : ''})`);
5100
5123
  return;
5101
5124
  }
5125
+ if (sub === 'audit') {
5126
+ // 1.36.44 (하드닝): `git commit --no-verify` 는 git 설계상 훅을 스킵한다(클라이언트 차단 불가) — 사후 감지로 보완.
5127
+ // 최근 커밋 시각이 어떤 handoff 창(커밋 전 windowHours 내 handoff)에도 안 들면 우회 의심으로 표기 (advisory).
5128
+ if (!exists(path.join(root, '.harness'))) { failJson(json, 'harness_missing', `leerness 미설치: ${root}`); return; }
5129
+ const cfg2 = _enforceCfg(root);
5130
+ let handoffs = [];
5131
+ try { const j = JSON.parse(read(_lastHandoffPath(root))); handoffs = (j.history || []).map(t => new Date(t).getTime()).filter(n => !Number.isNaN(n)); } catch {}
5132
+ const gl = cp.spawnSync('git', ['-C', root, 'log', '-n', '30', '--pretty=format:%H|%ct|%s'], { encoding: 'utf8', timeout: 10000 });
5133
+ if (gl.status !== 0) { failJson(json, 'git_log_failed', 'git log 실패 — git 저장소인지 확인'); return; }
5134
+ const winMs = cfg2.windowHours * 3600000;
5135
+ const suspects = [];
5136
+ for (const line of (gl.stdout || '').split('\n').filter(Boolean)) {
5137
+ const [h, ct, ...s2] = line.split('|');
5138
+ const t = parseInt(ct, 10) * 1000;
5139
+ const covered = handoffs.some(hh => hh <= t && (t - hh) <= winMs);
5140
+ if (!covered) suspects.push({ commit: h.slice(0, 8), at: new Date(t).toISOString(), subject: s2.join('|').slice(0, 80) });
5141
+ }
5142
+ if (json) { log(JSON.stringify({ checked: (gl.stdout || '').split('\n').filter(Boolean).length, windowHours: cfg2.windowHours, suspects }, null, 2)); if (suspects.length) process.exitCode = 1; return; }
5143
+ log(`# leerness enforce audit — --no-verify 우회 사후 감지 (advisory)`);
5144
+ log(` 검사: 최근 ${(gl.stdout || '').split('\n').filter(Boolean).length}개 커밋 vs handoff 창(${cfg2.windowHours}h)`);
5145
+ if (!suspects.length) ok(' 우회 의심 커밋 없음 — 전부 handoff 창 안');
5146
+ else {
5147
+ warn(` 우회 의심 ${suspects.length}건 (handoff 창 밖 커밋 — --no-verify 또는 enforce 설치 전 커밋일 수 있음):`);
5148
+ for (const s2 of suspects.slice(0, 8)) log(` · ${s2.commit} ${s2.at.slice(0, 16)} ${s2.subject}`);
5149
+ process.exitCode = 1;
5150
+ }
5151
+ return;
5152
+ }
5102
5153
  if (sub === 'remove') {
5103
5154
  if (exists(hookP) && read(hookP).includes(_ENFORCE_MARK)) {
5104
5155
  if (exists(chainedP)) { fs.copyFileSync(chainedP, hookP); fs.unlinkSync(chainedP); try { fs.chmodSync(hookP, 0o755); } catch {} }
@@ -5114,6 +5165,8 @@ function enforceCmd(root, sub) {
5114
5165
  log(`# leerness enforce — 사용 강제 (git pre-commit)`);
5115
5166
  log(` 상태: ${installed ? `🟢 설치됨 (window ${cfg.windowHours}h${cfg.strict ? ' · strict=gate' : ''})` : '⚪ 미설치'}`);
5116
5167
  log(` ${installed ? '해제: leerness enforce remove' : '설치: leerness enforce install [--window 24] [--strict]'}`);
5168
+ // 1.36.44 (정직성): 한계 명시 — 과장 금지
5169
+ log(dm(` ⓘ 한계: git commit --no-verify 는 git 설계상 훅을 스킵 — 사후 감지는 leerness enforce audit, 우회 불가 하드 레이어는 CI(leerness ci init + branch protection)`));
5117
5170
  }
5118
5171
 
5119
5172
  // 1.36.36 (도그푸딩 실측 후속): `leerness anchors [status|draft [--apply]]` — 정체성앵커(brief Purpose / plan Goal) 미작성 전환 지원.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.43",
3
+ "version": "1.36.44",
4
4
  "description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
5
5
  "keywords": [
6
6
  "leerness",