leerness 1.36.45 → 1.36.47

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.47 — 2026-07-16 — anchors 초안 품질 + 재초안 — 실프로젝트 6곳 실적용에서 발견·즉시 교정
4
+
5
+ 사용자 승인 하에 실프로젝트 6곳(hive-analytics/auto-macro/cafe-macro/blog-macro/view-work/AEGIRINE)을 1.36.46 으로 **무유실 마이그레이션**(5표면 카운트 전량 보존, auto-macro 는 1.9.441→46 32버전 점프에 CLAUDE 커스텀 포함 무손실 — 보존 3부작의 실전 입증)하고 anchors 초안을 일괄 적용한 결과, 실데이터에서 초안 품질 결함 2종 발견:
6
+
7
+ - **어색문**: goal 문형의 조사 병기("…를(을) 동작하는…") + 160자 **구 중간 절단**("주문 목록 를(을)") → 단어/구두점 경계 절단(`…` 표시) + 조사 없는 문형("제품을 동작 상태로 유지·발전시킨다: <설명>")으로 교정.
8
+ - **재초안 불가**: 초안 적용 후엔 "작성됨" 판정이라 품질 개선을 전파할 수 없었음 → **draft 마커가 남아있는 섹션은 재초안 대상**(확정본 = 마커 제거본은 불가침 유지). 실측: 6곳 재적용 — draft 섹션만 갱신(2/2/1/2/1), 사용자 작성 2섹션 + AEGIRINE 완정본 0 접촉, 어색문 잔존 0.
9
+ - (부수: `_mdSectionBody` bin 임포트 누락을 라이브 실행이 즉시 노출 — selftest 는 anchorsCmd 미실행이라 못 잡음. 임포트 추가.)
10
+ - **검증**: selftest 316/316(문형·경계절단·재초안 배선 가드 보강), 실프로젝트 6곳 재적용 실측, exit 전파형 게이트 e2e, 클린룸.
11
+
12
+ ## 1.36.46 — 2026-07-16 — JSON 계약 완결 — persona/review/guide (codex 5차 #10 잔여 소진)
13
+
14
+ `--json` 을 광고하면서 마크다운/혼합 출력을 내던 마지막 3종 — MCP·자동화 소비자가 파싱 가능한 **단일 JSON 문서**로:
15
+
16
+ - **`persona show <id> --json`**: `{id, name, description, body}` (종전: 마크다운 방출) + 에러 경로 failJson 구조화.
17
+ - **`review <file> --persona … --json`**: `--emit json` 과 동치화 + **`prompts[]`(실제 리뷰 프롬프트 전문) 포함** — 종전 JSON 은 메타만 있어 소비자가 프롬프트를 재조립해야 했다.
18
+ - **`guide <target> --json`**: 하위 4섹션(impact/reuse/uiConsistency/lessons) 출력을 캡처해 `{target, query, date, sections}` 단일 객체로 집계 (종전: 사람용 헤딩+JSON 파편 혼합).
19
+ - 사람용 출력 3종 모두 무회귀. **이로써 codex 5차 헌트 10건 완전 소진(부분 이연 0).**
20
+ - **검증**: selftest 316/316, 3종 JSON.parse 실측 + 프롬프트 포함 확인, exit 전파형 게이트 e2e, 클린룸.
21
+
3
22
  ## 1.36.45 — 2026-07-16 — `adapter codex --global` — goal 모드까지 닿는 조건부 전역 지침 (사용자 버그 후속 2층)
4
23
 
5
24
  1.36.43 enforce(커밋 관문)에 이은 **지침층 보완**: codex 는 프로젝트 AGENTS.md 를 안 읽는 모드(goal 모드 — 사용자 실측)에서도 **전역 `~/.codex/AGENTS.md` 는 읽는다**. 여기에 조건부 블록을 설치해 커버.
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.45 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
124
+ 이 프로젝트는 Leerness v1.36.47 하네스를 사용합니다. 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.45는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
178
+ Leerness v1.36.47는 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.45는 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.45 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
199
+ 현재 누적: **v1.9.x → 1.36.47 릴리스 태그 이력** (수백 라운드) · _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.45: 2026-07-16
237
+ Last synced by Leerness v1.36.47: 2026-07-21
238
238
  <!-- leerness:project-readme:end -->
239
239
 
package/bin/leerness.js CHANGED
@@ -26,7 +26,7 @@ const { _isSecretKey, _isPlaceholderSecret, _looksSecretLike, _mergeLines, _merg
26
26
  _migrationGuideText, _parseContractSpec, _gitignoreMatch,
27
27
  _featureGraphTemplate, _parseFeatureGraph, _nextFeatureId, _featureBlock, _featureImpactBfs,
28
28
  _parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed, _minorKey, _shouldPublishNpm,
29
- _matchTool, _parsePackageJsonDeps, _parseRequirementsTxt, _buildGlossary, _renderGlossaryMd, _briefUnfilled, _planGoalUnfilled, _draftAnchors, _replaceMdSection } = require('../lib/pure-utils'); // 1.9.318~1.11.4 (UR-0025/.../0007 glossary): 순수 유틸 모듈 분리 · 1.36.36 anchors
29
+ _matchTool, _parsePackageJsonDeps, _parseRequirementsTxt, _buildGlossary, _renderGlossaryMd, _briefUnfilled, _planGoalUnfilled, _draftAnchors, _replaceMdSection, _mdSectionBody } = require('../lib/pure-utils'); // 1.9.318~1.11.4 (UR-0025/.../0007 glossary): 순수 유틸 모듈 분리 · 1.36.36 anchors
30
30
  // 1.9.304 (UR-0025): 순수 분석/검증 함수 모듈 분리.
31
31
  const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck } = require('../lib/analyzers');
32
32
  // 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
@@ -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.45';
37
+ const VERSION = '1.36.47';
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') 시 호스트 프로세스 오염.
@@ -3202,6 +3202,13 @@ function _selfTestCases() {
3202
3202
  if (typeof p._draftAnchors !== 'function' || typeof p._replaceMdSection !== 'function') return false;
3203
3203
  const d = p._draftAnchors({ pkgDescription: '결제 정산 서비스', readmeText: '# T\n\n웹훅 백엔드.\n', milestones: [{ id: 'M-0001', title: '베타', status: 'planned' }], tasks: [] });
3204
3204
  const draftOk = d.hasSignal && d.purpose.some(l => l.includes('결제 정산 서비스')) && d.goal.some(l => l.includes('M-0001')) && d.purpose[0].includes('draft:');
3205
+ // 1.36.47: 어색 조사 병기 소거 + 장문은 단어 경계 절단(…) — 실적용에서 발견된 품질 결함 회귀 방지
3206
+ const dLong = p._draftAnchors({ pkgDescription: '가나다 라마바사 아자차카 타파하 '.repeat(20) });
3207
+ const phraseOk = !dLong.goal.join('').includes('를(을)') && dLong.goal.some(l => l.includes('…')) && !/[가-힣]{2}…[가-힣]/.test(dLong.goal.join(''));
3208
+ // 재초안: draft 마커 잔존 섹션은 gap 취급 (배선 소스가드)
3209
+ const s47 = read(__filename);
3210
+ const redraftOk = s47.includes("_mdSectionBody(briefTxt, 'Purpose') || '').includes(_draftM)");
3211
+ if (!(phraseOk && redraftOk)) return false;
3205
3212
  // 무신호 시 발명 금지
3206
3213
  const empty = p._draftAnchors({});
3207
3214
  const noInvent = !empty.hasSignal && empty.purpose.length === 0 && empty.goal.length === 0;
@@ -3361,6 +3368,13 @@ function _selfTestCases() {
3361
3368
  const removeOk = /has\('--remove'\)[\s\S]{0,400}codex 전역 leerness 블록 제거/.test(s);
3362
3369
  return wired && conditional && mergeOk && removeOk;
3363
3370
  } },
3371
+ { name: 'JSON 계약 완결 (1.36.46, codex 5차 #10 잔여): persona show/review/guide --json 단일 JSON 문서 (소스가드)', run: () => {
3372
+ const s = read(__filename);
3373
+ const personaOk = s.includes("log(JSON.stringify({ id: p.id, name: p.name, description: p.description, body: p.body }, null, 2)); return;");
3374
+ const reviewOk = s.includes("has('--json') ? 'json' : arg('--emit', 'prompt')") && s.includes('prompts: personas.map(p => ({ personaId: p.id, prompt: _mkPrompt(p) }))');
3375
+ const guideOk = /guideCmd[\s\S]{0,600}if \(has\('--json'\)\) \{[\s\S]{0,900}sections/.test(s) && s.includes('uiConsistency: _cap(() => uiConsistency(root))');
3376
+ return personaOk && reviewOk && guideOk;
3377
+ } },
3364
3378
  { name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
3365
3379
  const d = LENS_CATALOG.debug;
3366
3380
  if (!d) return false;
@@ -5189,8 +5203,10 @@ function anchorsCmd(root, sub) {
5189
5203
  const briefTxt = exists(bf) ? read(bf) : '';
5190
5204
  const planTxt = exists(pf) ? read(pf) : '';
5191
5205
  const naM = '<!-- leerness:na';
5192
- const briefGap = !!briefTxt && !briefTxt.includes(naM) && _briefUnfilled(briefTxt);
5193
- const planGap = !!planTxt && !planTxt.includes(naM) && _planGoalUnfilled(planTxt);
5206
+ // 1.36.47: draft 마커가 남아있는 섹션(미확정 초안) 재초안 대상 — 초안 품질 개선을 기존 적용본에도 전파 가능.
5207
+ const _draftM = '<!-- draft: leerness anchors';
5208
+ const briefGap = !!briefTxt && !briefTxt.includes(naM) && (_briefUnfilled(briefTxt) || (_mdSectionBody(briefTxt, 'Purpose') || '').includes(_draftM));
5209
+ const planGap = !!planTxt && !planTxt.includes(naM) && (_planGoalUnfilled(planTxt) || (_mdSectionBody(planTxt, 'Goal') || '').includes(_draftM));
5194
5210
  if (!sub || sub === 'status') {
5195
5211
  if (json) { log(JSON.stringify({ briefUnfilled: briefGap, planGoalUnfilled: planGap }, null, 2)); return; }
5196
5212
  log(`# leerness anchors — 정체성앵커 상태`);
@@ -14124,9 +14140,11 @@ function personaCmd(root, sub, idOrName, ...rest) {
14124
14140
  return;
14125
14141
  }
14126
14142
  if (sub === 'show') {
14127
- if (!idOrName) { fail('persona show <id> 필요'); return process.exit(1); }
14143
+ if (!idOrName) { failJson(has('--json'), 'missing_arg', 'persona show <id> 필요'); return process.exit(1); }
14128
14144
  const p = _resolvePersona(root, idOrName);
14129
- if (!p) { fail(`페르소나 없음: ${idOrName}`); return process.exit(1); }
14145
+ if (!p) { failJson(has('--json'), 'not_found', `페르소나 없음: ${idOrName}`); return process.exit(1); }
14146
+ // 1.36.46 (codex 5차 #10 잔여): --json 은 단일 JSON 문서 (종전 마크다운 방출)
14147
+ if (has('--json')) { log(JSON.stringify({ id: p.id, name: p.name, description: p.description, body: p.body }, null, 2)); return; }
14130
14148
  log(`# ${p.name} (${p.id})`);
14131
14149
  log(`\n${p.description}\n`);
14132
14150
  log(`---\n${p.body}`);
@@ -14170,13 +14188,16 @@ function reviewCmd(root, target) {
14170
14188
  }
14171
14189
 
14172
14190
  // 출력 형식: emit
14173
- const emit = arg('--emit', 'prompt'); // prompt | md | json
14191
+ const emit = has('--json') ? 'json' : arg('--emit', 'prompt'); // prompt | md | json (1.36.46: --json ≡ --emit json)
14174
14192
 
14175
14193
  if (emit === 'json') {
14194
+ // 1.36.46 (codex 5차 #10 잔여): JSON 에 실제 산출물(프롬프트)까지 — 메타만 주던 종전은 소비자가 재조립해야 했다.
14195
+ const _mkPrompt = (p) => [`# Review Prompt — ${p.name} (${p.id})`, `## 대상: ${target} (${fileSize} bytes)`, `## 페르소나 활성화`, p.body, ``, `## 작업`, `아래 코드를 위 페르소나 관점에서 정밀 리뷰하라. 한국어 보고 ~600단어.`, ``, `## 코드`, '```javascript', fileContent, '```'].join('\n');
14176
14196
  log(JSON.stringify({
14177
14197
  file: target,
14178
14198
  filePath, fileSize,
14179
- personas: personas.map(p => ({ id: p.id, name: p.name }))
14199
+ personas: personas.map(p => ({ id: p.id, name: p.name })),
14200
+ prompts: personas.map(p => ({ personaId: p.id, prompt: _mkPrompt(p) }))
14180
14201
  }, null, 2));
14181
14202
  return;
14182
14203
  }
@@ -16796,6 +16817,23 @@ function graphCmd(root) {
16796
16817
 
16797
16818
  function guideCmd(root, target) {
16798
16819
  root = absRoot(root);
16820
+ // 1.36.46 (codex 5차 #10 잔여): --json 은 하위 4섹션 출력을 캡처해 단일 JSON 으로 집계 (종전: 사람용 헤딩+JSON 파편 혼합).
16821
+ if (has('--json')) {
16822
+ const q0 = target ? path.basename(target, path.extname(target)) : arg('--query', '');
16823
+ const _cap = (fn) => {
16824
+ const _w = process.stdout.write; let out = '';
16825
+ try { process.stdout.write = s => { out += s; return true; }; fn(); } catch (e) { out += `\n(error: ${e.message})`; } finally { process.stdout.write = _w; }
16826
+ return out.trim();
16827
+ };
16828
+ const sections = {
16829
+ impact: target ? _cap(() => impactCmd(root, target)) : null,
16830
+ reuse: q0 ? _cap(() => reuseFind(root, q0)) : null,
16831
+ uiConsistency: _cap(() => uiConsistency(root)),
16832
+ lessons: q0 ? _cap(() => { if (!process.argv.includes('--query')) process.argv.push('--query', q0); lessonsCmd(root); }) : null,
16833
+ };
16834
+ log(JSON.stringify({ target: target || null, query: q0 || null, date: today(), sections }, null, 2));
16835
+ return;
16836
+ }
16799
16837
  log(`# 변경 전 가이드 ${target ? `(target: ${target})` : ''}`);
16800
16838
  log(`Date: ${today()}\n`);
16801
16839
  if (target) {
package/lib/pure-utils.js CHANGED
@@ -1159,11 +1159,20 @@ function _draftAnchors(sig = {}) {
1159
1159
  if (!t || t.startsWith('#') || t.startsWith('![') || t.startsWith('[!') || t.startsWith('<')) continue;
1160
1160
  readmeLead = t; break;
1161
1161
  }
1162
+ // 1.36.47 (실적용 품질 결함): slice 가 구 중간을 절단해 "…주문 목록 를(을)…" 같은 어색문이 실프로젝트에 박혔다 —
1163
+ // 단어/구두점 경계 절단 + 조사 병기 없는 문형으로.
1164
+ const _cutAt = (s, n) => {
1165
+ s = String(s || '').trim();
1166
+ if (s.length <= n) return s;
1167
+ const head = s.slice(0, n);
1168
+ const brk = Math.max(head.lastIndexOf(' '), head.lastIndexOf(','), head.lastIndexOf('·'), head.lastIndexOf('。'), head.lastIndexOf('.'));
1169
+ return (brk > n * 0.5 ? head.slice(0, brk) : head).replace(/[\s,·]+$/, '') + '…';
1170
+ };
1162
1171
  if (desc) purpose.push(`- ${desc}`);
1163
- if (readmeLead && readmeLead !== desc) purpose.push(`- ${readmeLead.slice(0, 200)}`);
1172
+ if (readmeLead && readmeLead !== desc) purpose.push(`- ${_cutAt(readmeLead, 200)}`);
1164
1173
  const ms = (sig.milestones || []).filter(m => m && m.title).slice(0, 3);
1165
1174
  const activeTasks = (sig.tasks || []).filter(t => t && /in-progress|planned|requested/.test(String(t.status || ''))).slice(0, 3);
1166
- if (desc || readmeLead) goal.push(`- ${(desc || readmeLead).slice(0, 160)} 를(을) 동작하는 제품으로 유지·발전시킨다`);
1175
+ if (desc || readmeLead) goal.push(`- 제품을 동작 상태로 유지·발전시킨다: ${_cutAt(desc || readmeLead, 140)}`);
1167
1176
  for (const m of ms) goal.push(`- [${m.id || 'M'}] ${String(m.title).slice(0, 120)}${m.status ? ` (${m.status})` : ''}`);
1168
1177
  if (!goal.length && activeTasks.length) for (const t of activeTasks) goal.push(`- ${t.id}: ${String(t.request || '').slice(0, 100)}`);
1169
1178
  const marker = '<!-- draft: leerness anchors — 실신호 기반 초안. AI/사용자가 검토·수정 후 이 주석을 지우면 확정 -->';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.45",
3
+ "version": "1.36.47",
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",