leerness 1.36.43 → 1.36.45

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.45 — 2026-07-16 — `adapter codex --global` — goal 모드까지 닿는 조건부 전역 지침 (사용자 버그 후속 2층)
4
+
5
+ 1.36.43 enforce(커밋 관문)에 이은 **지침층 보완**: codex 는 프로젝트 AGENTS.md 를 안 읽는 모드(goal 모드 — 사용자 실측)에서도 **전역 `~/.codex/AGENTS.md` 는 읽는다**. 여기에 조건부 블록을 설치해 커버.
6
+
7
+ - **`leerness adapter codex --global [--remove]`**: 마커 블록(`leerness:codex-global`)을 전역 AGENTS.md 에 병합 — 핵심은 **조건부**("현재 프로젝트 루트에 `.harness/` 있을 때만 발동, 없으면 무시") 라 비-leerness 프로젝트 무영향. handoff→verify-claim→session close 3계약 안내.
8
+ - 사용자 전역 내용 보존(마커 병합) · 멱등(재실행 중복 0) · `--remove` 로 흔적 없이 해제 — 전부 실측.
9
+ - **goal 모드 대응 스택 완성**: ① 프로젝트 AGENTS.md(읽는 모드) → ② 전역 조건부 지침(안 읽는 모드, 본 릴리스) → ③ pre-commit 강제(1.36.43 — 지침 자체를 무시해도 커밋 차단) → ④ enforce audit + CI gate(우회 감지·하드 차단).
10
+ - **검증**: selftest 315/315, 설치/보존/멱등/제거 사슬 실측, exit 전파형 게이트 e2e, 클린룸.
11
+
12
+ ## 1.36.44 — 2026-07-16 — enforce 적대적 하드닝 — FP/우회 자체 헌트 (anti-cheat 출하 후속 규율)
13
+
14
+ 1.36.43 enforce 는 anti-cheat 다 — 출하 규율(bypass+FP 헌터)에 따라 자체 적대 헌트를 돌린 결과 3건:
15
+
16
+ - **FP(실차단 버그) — `.harness` 없는 체크아웃 오차단**: 워크트리/`.harness` 없는 브랜치에서 커밋이 차단됐다(강제 대상이 아닌 상태). → 훅이 `.harness` 부재 시 체인만 실행하고 통과. 실측: `.harness` 없는 워크트리 커밋 0.
17
+ - **worktree 훅 경로**: `.git` 이 파일인 워크트리에서 `install` 이 경로를 오해석할 수 있던 것 → `git rev-parse --git-common-dir` 로 해석(본체/워크트리 모두 정답). 실측: 워크트리에서 install → 공용 훅에 기록.
18
+ - **`--no-verify` 우회 — 정직 처분**: git 설계상 클라이언트 차단 불가. → (a) **`leerness enforce audit`**: 최근 30 커밋 시각 vs handoff 창 대조로 우회 의심 사후 감지(advisory, 의심 시 exit 1). 실측: `--no-verify` 커밋만 정확히 표기, 창 안 커밋 무의심. (b) status 에 한계 명시 — 우회 불가 하드 레이어는 CI(`ci init` + branch protection)임을 정직하게 안내.
19
+ - **검증**: selftest 314/314, 하드닝 3건 원 재현 before/after + 본체 강제 무회귀, exit 전파형 게이트 e2e, 클린룸.
20
+
3
21
  ## 1.36.43 — 2026-07-16 — `leerness enforce` — 사용 강제를 문서에서 커밋 관문으로 (사용자 명시: codex goal 모드가 leerness 미참조)
4
22
 
5
23
  **배경(사용자 실측 버그)**: 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.45 하네스를 사용합니다. 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.45는 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.45 릴리스 태그 이력** (수백 라운드) · _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.45: 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.45';
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,22 @@ 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
+ } },
3356
+ { name: 'codex 전역 조건부 지침 (1.36.45, goal 모드 커버): adapter codex --global — 마커 병합/보존/멱등/remove (소스가드)', run: () => {
3357
+ const s = read(__filename);
3358
+ const wired = s.includes("tool === 'codex' && has('--global')") && s.includes('leerness:codex-global:start');
3359
+ const conditional = s.includes('`.harness/` 디렉토리가 있을 때만');
3360
+ const mergeOk = s.includes('_mergeReadmeSection(existing, block, START, END)');
3361
+ const removeOk = /has\('--remove'\)[\s\S]{0,400}codex 전역 leerness 블록 제거/.test(s);
3362
+ return wired && conditional && mergeOk && removeOk;
3363
+ } },
3348
3364
  { name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
3349
3365
  const d = LENS_CATALOG.debug;
3350
3366
  if (!d) return false;
@@ -5031,7 +5047,17 @@ function _enforceCfg(root) {
5031
5047
  try { if (exists(_enforceCfgPath(root))) return Object.assign(def, JSON.parse(read(_enforceCfgPath(root)))); } catch {}
5032
5048
  return def;
5033
5049
  }
5034
- function _gitHookPath(root) { return path.join(root, '.git', 'hooks', 'pre-commit'); }
5050
+ function _gitHookPath(root) {
5051
+ // 1.36.44 (하드닝): worktree 에선 .git 이 파일 — 훅은 공용 디렉토리에 산다. git 에게 물어 해석(양쪽 다 정답).
5052
+ try {
5053
+ const r = cp.spawnSync('git', ['-C', root, 'rev-parse', '--git-common-dir'], { encoding: 'utf8', timeout: 5000 });
5054
+ if (r.status === 0 && r.stdout.trim()) {
5055
+ const gd = r.stdout.trim();
5056
+ return path.join(path.isAbsolute(gd) ? gd : path.join(root, gd), 'hooks', 'pre-commit');
5057
+ }
5058
+ } catch {}
5059
+ return path.join(root, '.git', 'hooks', 'pre-commit');
5060
+ }
5035
5061
  function enforceCmd(root, sub) {
5036
5062
  root = absRoot(root);
5037
5063
  const json = has('--json');
@@ -5057,6 +5083,11 @@ function enforceCmd(root, sub) {
5057
5083
  '#!/bin/sh',
5058
5084
  _ENFORCE_MARK + ' — leerness 사용 강제 (제거: leerness enforce remove)',
5059
5085
  'if [ "$LEERNESS_ENFORCE_BYPASS" = "1" ]; then exit 0; fi',
5086
+ '# 1.36.44: .harness 없는 체크아웃(워크트리/구브랜치)은 강제 대상 아님 — 오차단(FP) 방지',
5087
+ 'if [ ! -d ".harness" ]; then',
5088
+ ' if [ -f "$(dirname "$0")/pre-commit.pre-leerness" ]; then exec sh "$(dirname "$0")/pre-commit.pre-leerness" "$@"; fi',
5089
+ ' exit 0',
5090
+ 'fi',
5060
5091
  `LH=".harness/last-handoff.json"`,
5061
5092
  `WIN_MIN=${windowHours * 60}`,
5062
5093
  'if [ ! -f "$LH" ] || [ -z "$(find "$LH" -mmin -"$WIN_MIN" 2>/dev/null)" ]; then',
@@ -5099,6 +5130,34 @@ function enforceCmd(root, sub) {
5099
5130
  if (!json) ok(`enforce check 통과 (handoff ${lastAt ? Math.round(ageH * 10) / 10 + 'h 전' : '-'}${cfg.strict ? ' + gate' : ''})`);
5100
5131
  return;
5101
5132
  }
5133
+ if (sub === 'audit') {
5134
+ // 1.36.44 (하드닝): `git commit --no-verify` 는 git 설계상 훅을 스킵한다(클라이언트 차단 불가) — 사후 감지로 보완.
5135
+ // 최근 커밋 시각이 어떤 handoff 창(커밋 전 windowHours 내 handoff)에도 안 들면 우회 의심으로 표기 (advisory).
5136
+ if (!exists(path.join(root, '.harness'))) { failJson(json, 'harness_missing', `leerness 미설치: ${root}`); return; }
5137
+ const cfg2 = _enforceCfg(root);
5138
+ let handoffs = [];
5139
+ try { const j = JSON.parse(read(_lastHandoffPath(root))); handoffs = (j.history || []).map(t => new Date(t).getTime()).filter(n => !Number.isNaN(n)); } catch {}
5140
+ const gl = cp.spawnSync('git', ['-C', root, 'log', '-n', '30', '--pretty=format:%H|%ct|%s'], { encoding: 'utf8', timeout: 10000 });
5141
+ if (gl.status !== 0) { failJson(json, 'git_log_failed', 'git log 실패 — git 저장소인지 확인'); return; }
5142
+ const winMs = cfg2.windowHours * 3600000;
5143
+ const suspects = [];
5144
+ for (const line of (gl.stdout || '').split('\n').filter(Boolean)) {
5145
+ const [h, ct, ...s2] = line.split('|');
5146
+ const t = parseInt(ct, 10) * 1000;
5147
+ const covered = handoffs.some(hh => hh <= t && (t - hh) <= winMs);
5148
+ if (!covered) suspects.push({ commit: h.slice(0, 8), at: new Date(t).toISOString(), subject: s2.join('|').slice(0, 80) });
5149
+ }
5150
+ 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; }
5151
+ log(`# leerness enforce audit — --no-verify 우회 사후 감지 (advisory)`);
5152
+ log(` 검사: 최근 ${(gl.stdout || '').split('\n').filter(Boolean).length}개 커밋 vs handoff 창(${cfg2.windowHours}h)`);
5153
+ if (!suspects.length) ok(' 우회 의심 커밋 없음 — 전부 handoff 창 안');
5154
+ else {
5155
+ warn(` 우회 의심 ${suspects.length}건 (handoff 창 밖 커밋 — --no-verify 또는 enforce 설치 전 커밋일 수 있음):`);
5156
+ for (const s2 of suspects.slice(0, 8)) log(` · ${s2.commit} ${s2.at.slice(0, 16)} ${s2.subject}`);
5157
+ process.exitCode = 1;
5158
+ }
5159
+ return;
5160
+ }
5102
5161
  if (sub === 'remove') {
5103
5162
  if (exists(hookP) && read(hookP).includes(_ENFORCE_MARK)) {
5104
5163
  if (exists(chainedP)) { fs.copyFileSync(chainedP, hookP); fs.unlinkSync(chainedP); try { fs.chmodSync(hookP, 0o755); } catch {} }
@@ -5114,6 +5173,8 @@ function enforceCmd(root, sub) {
5114
5173
  log(`# leerness enforce — 사용 강제 (git pre-commit)`);
5115
5174
  log(` 상태: ${installed ? `🟢 설치됨 (window ${cfg.windowHours}h${cfg.strict ? ' · strict=gate' : ''})` : '⚪ 미설치'}`);
5116
5175
  log(` ${installed ? '해제: leerness enforce remove' : '설치: leerness enforce install [--window 24] [--strict]'}`);
5176
+ // 1.36.44 (정직성): 한계 명시 — 과장 금지
5177
+ log(dm(` ⓘ 한계: git commit --no-verify 는 git 설계상 훅을 스킵 — 사후 감지는 leerness enforce audit, 우회 불가 하드 레이어는 CI(leerness ci init + branch protection)`));
5117
5178
  }
5118
5179
 
5119
5180
  // 1.36.36 (도그푸딩 실측 후속): `leerness anchors [status|draft [--apply]]` — 정체성앵커(brief Purpose / plan Goal) 미작성 전환 지원.
@@ -19729,6 +19790,39 @@ function adapterCmd(root, tool, opts = {}) {
19729
19790
  log(`\n 사용: leerness adapter <tool> [--dry-run]`);
19730
19791
  return;
19731
19792
  }
19793
+ // 1.36.45 (사용자 버그 후속 — codex goal 모드가 프로젝트 문서를 안 읽음): codex 는 전역 ~/.codex/AGENTS.md 를
19794
+ // 모든 모드에서 읽는다 — 조건부("`.harness/` 있는 프로젝트에서만") 전역 지침을 설치해 goal 모드까지 커버.
19795
+ // 비-leerness 프로젝트 무영향(조건부) + 마커 블록 병합(사용자 전역 내용 보존) + --remove 로 깨끗이 해제.
19796
+ if (tool === 'codex' && has('--global')) {
19797
+ const gf = path.join(os.homedir(), '.codex', 'AGENTS.md');
19798
+ const START = '<!-- leerness:codex-global:start -->';
19799
+ const END = '<!-- leerness:codex-global:end -->';
19800
+ const existing = exists(gf) ? read(gf) : '';
19801
+ if (has('--remove')) {
19802
+ const s = existing.indexOf(START), e = existing.indexOf(END);
19803
+ if (s >= 0 && e >= s) { writeUtf8(gf, (existing.slice(0, s) + existing.slice(e + END.length)).replace(/\n{3,}/g, '\n\n').trim() + '\n'); ok('codex 전역 leerness 블록 제거'); }
19804
+ else log('전역 블록 없음 — 변경 없음');
19805
+ return;
19806
+ }
19807
+ const block = [
19808
+ START,
19809
+ '# leerness — 조건부 지침 (이 블록은 `leerness adapter codex --global` 이 관리)',
19810
+ '',
19811
+ '**현재 작업 중인 프로젝트 루트에 `.harness/` 디렉토리가 있을 때만** 아래를 따른다 (없으면 이 블록 전체 무시):',
19812
+ '1. 작업 시작: `leerness handoff .` — 프로젝트 컨텍스트/활성 룰 적재',
19813
+ '2. 완료 주장 전: `leerness verify-claim <T-ID>` — 증거 검증 (거짓 완료 차단)',
19814
+ '3. 세션 종료: `leerness session close .` — 마감 보고 + 인수인계',
19815
+ '',
19816
+ '이 지침은 모든 Codex 모드(goal 모드 포함)에 적용된다 — 프로젝트 AGENTS.md 를 읽지 않는 모드를 위한 전역 안전망.',
19817
+ END,
19818
+ ].join('\n');
19819
+ mkdirp(path.dirname(gf));
19820
+ writeUtf8(gf, _mergeReadmeSection(existing, block, START, END));
19821
+ ok(`codex 전역 지침 설치 — ${gf}`);
19822
+ log(' 조건부: .harness/ 있는 프로젝트에서만 발동 (goal 모드 포함 전 모드 커버)');
19823
+ log(' 해제: leerness adapter codex --global --remove');
19824
+ return;
19825
+ }
19732
19826
  const a = ADAPTERS[tool];
19733
19827
  if (!a) return fail(`알 수 없는 adapter: ${tool} (가능: ${Object.keys(ADAPTERS).join(', ')})`);
19734
19828
  const lang = exists(path.join(root, '.harness/LANGUAGE')) ? read(path.join(root, '.harness/LANGUAGE')).trim() : 'ko';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.43",
3
+ "version": "1.36.45",
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",