leerness 1.36.36 → 1.36.38
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 +19 -0
- package/README.md +4 -4
- package/bin/leerness.js +122 -18
- package/package.json +1 -1
- package/scripts/e2e.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.38 — 2026-07-16 — 분석 정직화 — retro 기간필터·신호 정규식·태그 계보·usage 귀속 (codex 4차 #2/#4/#9/#10)
|
|
4
|
+
|
|
5
|
+
- **#2 `retro --days`**: cutoff 를 계산만 하고 미적용 — 주간 회고가 전기간 집계였다. → 날짜 헤딩 섹션 필터(`_filterDatedSections`)를 task-log/evidence/decisions-신호에 적용(결정 블록 수는 "누적" 라벨대로 누적 유지). 실측: days=1 에서 2020 데이터 제외.
|
|
6
|
+
- **#10 신호 정규식**: JS `\b` 가 한글 경계를 못 만들어 한글 fix 신호 전멸(FN) + `pass`가 password/bypass 안에서, `done` 이 undone 안에서 매치(FP). → 한글 리터럴 카운트 + ASCII lookaround 토큰 경계. 실측: "수정 롤백 재발 password bypass undone" → fix 3 · pass 0.
|
|
7
|
+
- **#4 태그 계보**: round-history/milestones 가 `v1.9.*` 하드코드 — 현 계보(v1.36.x) 이력 0 으로 집계하면서 **"모든 마일스톤 달성 (500+)" 허위 축하**까지 출력. → `v*` 전체 semver 수집 + 이력 0 이면 "태그 이력 없음" 정직 표기. 실측: v1.36.36/37 태그 → roundCount 2.
|
|
8
|
+
- **#9 usage 귀속**: `memory status <B>` 처럼 subcommand 대상이 args[2] 인 명령의 사용량이 cwd(A)에 오귀속. → args[2] 대상 해석 추가. 실측: A 0 · B 귀속.
|
|
9
|
+
- **검증**: selftest 309/309(필터/신호/태그/귀속/배선 행위검사), 4건 원 재현 before/after, exit 전파형 게이트 e2e, 클린룸. 이연(codex 4차 잔여): #3 MCP 파라미터 검증 · #5 decisions 이중 소스 · #6 llm-bench 검증 · #7 benchmark 빈 디렉토리 점수 · #8 pulse/health 불일치.
|
|
10
|
+
|
|
11
|
+
## 1.36.37 — 2026-07-16 — managed 파일 --force 병합 — 마이그레이션 보존의 마지막 구멍 (1.9.441 드리프트 경로 실측)
|
|
12
|
+
|
|
13
|
+
도그푸딩에서 발견된 실제 드리프트 세대(1.9.441 — auto-macro 가 쓰는 버전)로 마이그레이션 경로를 전수 재검(11종 마커, 기본→반복→--force): 기본/반복은 유실 0 이었으나 **--force 반복에서 CLAUDE.md 사용자 커스텀 1종 유실** — `writeIfSafe` 에서 force 가 managed 병합(1.36.28 라인-diff 재설계)을 우회해 raw 덮어쓰기했기 때문(1.36.33 `_USER_STATE` 는 .harness 상태 파일만 커버).
|
|
14
|
+
|
|
15
|
+
- **수정**: managed 파일(CLAUDE/AGENTS/cursor/copilot 지침)은 `--force` 여도 **항상 라인-diff 병합** — 병합이 곧 "템플릿 최신화 + 커스텀 보존"이라 raw 교체가 필요한 경우가 없다. 비-managed 파일의 force/preserve 동작은 불변.
|
|
16
|
+
- **실측**: 1.9.441→1.36.37 기본 migrate 유실 0/11 · 반복+2×--force 후 CLAUDE 커스텀+task 모두 생존 · 새 CLI 로 task/rule/requests 정상 판독.
|
|
17
|
+
- 이로써 마이그레이션 보존 3부작 완결: 반복 병합(1.36.28) → --force 상태 파일(1.36.33) → **--force managed 파일(1.36.37)**.
|
|
18
|
+
- **검증**: selftest 308/308(force+managed 병합 / 비-managed force 교체 / 보존 3동작 + rules 복구·보존), exit 전파형 게이트 e2e, 게시본 클린룸.
|
|
19
|
+
|
|
20
|
+
**rules 손상 행 무언 삭제 방지 (codex 4차 헌트 #1 High, 동일 릴리스 합류)**: 수동 편집/중단된 쓰기로 셀이 부족해진 룰 행을 `readRules` 가 버리고 다음 `writeRules`(rule verify 등)가 **그 룰을 조용히 삭제**했다(원 재현: 4셀 절단 → verify 후 소실). → 3~5셀 행은 누락 셀 기본값으로 **복구**(룰 기능 유지), 3셀 미만 파싱 불가 행은 **원문 보존**(경고 표식과 함께 — 수동 복구 대상). 실측: 절단 행 복구·기능 정상 + 파싱 불가 행 잔존. codex 4차 잔여 9건(retro --days 미적용 · v1.9.* 태그 하드코드 · MCP 파라미터 검증 · 이중 소스 등)은 다음 라운드.
|
|
21
|
+
|
|
3
22
|
## 1.36.36 — 2026-07-15 — `leerness anchors` — 정체성앵커 초안 합성 (도그푸딩 재실측 후속)
|
|
4
23
|
|
|
5
24
|
**배경(실측)**: 1.36.19 에서 앵커 미작성 감지를 출하했지만, 실프로젝트 7곳 재순회 결과 **brief 미작성 4/7 · plan Goal 미작성 6/7** — 감지·노출만으론 전환이 안 된다(반례: AEGIRINE 0/0 은 전환 가능함의 증거). 마찰 지점은 "빈 문서를 처음부터 써야 함" — 실신호 초안으로 제거.
|
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.
|
|
124
|
+
이 프로젝트는 Leerness v1.36.38 하네스를 사용합니다. 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.
|
|
178
|
+
Leerness v1.36.38는 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.36는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
196
196
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
197
197
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push → 7) session close → 8) 다음 라운드 예약.
|
|
198
198
|
|
|
199
|
-
현재 누적: **70 라운드 (1.9.40 → 1.36.
|
|
199
|
+
현재 누적: **70 라운드 (1.9.40 → 1.36.38)** · 매 라운드 GitHub release/태그 생성 · _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.
|
|
237
|
+
Last synced by Leerness v1.36.38: 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.
|
|
37
|
+
const VERSION = '1.36.38';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -719,12 +719,15 @@ function managedMerge(file, next, previous, archiveDir) {
|
|
|
719
719
|
function writeIfSafe(root, file, content, opts = {}) {
|
|
720
720
|
const p = path.join(root, file);
|
|
721
721
|
const already = exists(p);
|
|
722
|
-
|
|
723
|
-
|
|
722
|
+
// 1.36.37 (1.9.441 드리프트 경로 실측): managed 파일(CLAUDE/AGENTS 등)은 --force 여도 항상 라인-diff 병합 —
|
|
723
|
+
// 병합이 곧 "템플릿 최신화 + 사용자 커스텀 보존"이라 raw 덮어쓰기가 필요한 경우가 없고, 종전엔 force 가
|
|
724
|
+
// 병합을 우회해 CLAUDE.md 커스텀 지시가 archive 에만 남았다(1.36.28 보존 재설계의 force 구멍).
|
|
725
|
+
if (already && opts.mergeManaged) {
|
|
724
726
|
const prev = read(p);
|
|
725
727
|
writeUtf8(p, managedMerge(file, content, prev, opts.archiveDir));
|
|
726
728
|
return { action: 'merged', file };
|
|
727
729
|
}
|
|
730
|
+
if (already && !opts.force) return { action: 'preserved', file };
|
|
728
731
|
writeUtf8(p, content);
|
|
729
732
|
return { action: already ? 'updated' : 'created', file };
|
|
730
733
|
}
|
|
@@ -2131,7 +2134,7 @@ function _computeRoundHistory(root) {
|
|
|
2131
2134
|
};
|
|
2132
2135
|
try {
|
|
2133
2136
|
// git tag --list 'v1.9.*' --sort=creatordate (시간순)
|
|
2134
|
-
const r = cp.spawnSync('git', ['tag', '--list', '
|
|
2137
|
+
const r = cp.spawnSync('git', ['tag', '--list', 'v*', '--sort=creatordate', '--format=%(refname:short)|%(creatordate:iso8601)'], {
|
|
2135
2138
|
cwd: root, encoding: 'utf8', timeout: 5000
|
|
2136
2139
|
});
|
|
2137
2140
|
if (r.status !== 0 || !r.stdout) return result;
|
|
@@ -2176,7 +2179,7 @@ function _computeMilestones(root) {
|
|
|
2176
2179
|
baselineAt: null
|
|
2177
2180
|
};
|
|
2178
2181
|
try {
|
|
2179
|
-
const r = cp.spawnSync('git', ['tag', '--list', '
|
|
2182
|
+
const r = cp.spawnSync('git', ['tag', '--list', 'v*', '--sort=creatordate', '--format=%(refname:short)|%(creatordate:iso8601)'], {
|
|
2180
2183
|
cwd: root, encoding: 'utf8', timeout: 5000
|
|
2181
2184
|
});
|
|
2182
2185
|
if (r.status !== 0 || !r.stdout) return result;
|
|
@@ -2251,7 +2254,7 @@ function milestonesCmd(root) {
|
|
|
2251
2254
|
log(` • ETA: ${data.next.etaDate} (~${data.next.etaDays}일 후, 현재 속도 기준)`);
|
|
2252
2255
|
}
|
|
2253
2256
|
} else {
|
|
2254
|
-
log(gr(` 🎉 모든 마일스톤 달성 (500+)`));
|
|
2257
|
+
log(data && (data.roundCount || data.totalRounds) ? gr(` 🎉 모든 마일스톤 달성 (500+)`) : dm(` (이 저장소 계보의 릴리스 태그 이력 없음)`)); // 1.36.38 (#4): 이력 0 인데 달성 축하 금지
|
|
2255
2258
|
}
|
|
2256
2259
|
}
|
|
2257
2260
|
|
|
@@ -3196,6 +3199,64 @@ function _selfTestCases() {
|
|
|
3196
3199
|
const wired = typeof anchorsCmd === 'function' && s.includes("cmd === 'anchors'") && s.includes('→ leerness anchors draft');
|
|
3197
3200
|
return draftOk && noInvent && secOk && noSec && wired;
|
|
3198
3201
|
} },
|
|
3202
|
+
{ name: 'managed 파일 force 병합 (1.36.37, 1.9.441 드리프트 실측): writeIfSafe 가 --force 여도 managed 는 라인-diff 병합 — CLAUDE 커스텀 보존 (행위검사)', run: () => {
|
|
3203
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_wif_'));
|
|
3204
|
+
let ok1 = false, ok2 = false, ok3 = false;
|
|
3205
|
+
try {
|
|
3206
|
+
const f = 'CLAUDE.md';
|
|
3207
|
+
fs.writeFileSync(path.join(tmp, f), '# T\nline A\nMY CUSTOM X\n');
|
|
3208
|
+
// force + mergeManaged → 병합 (커스텀 보존)
|
|
3209
|
+
const r1 = writeIfSafe(tmp, f, '# T\nline A\nline B\n', { force: true, mergeManaged: true });
|
|
3210
|
+
const t1 = fs.readFileSync(path.join(tmp, f), 'utf8');
|
|
3211
|
+
ok1 = r1.action === 'merged' && t1.includes('MY CUSTOM X') && t1.includes('line B');
|
|
3212
|
+
// 비-managed + force → 통째 교체(기존 동작 유지)
|
|
3213
|
+
fs.writeFileSync(path.join(tmp, 'g.md'), 'OLD');
|
|
3214
|
+
const r2 = writeIfSafe(tmp, 'g.md', 'NEW', { force: true });
|
|
3215
|
+
ok2 = r2.action === 'updated' && fs.readFileSync(path.join(tmp, 'g.md'), 'utf8') === 'NEW';
|
|
3216
|
+
// 비-managed + no-force → 보존
|
|
3217
|
+
const r3 = writeIfSafe(tmp, 'g.md', 'NEWER', {});
|
|
3218
|
+
ok3 = r3.action === 'preserved' && fs.readFileSync(path.join(tmp, 'g.md'), 'utf8') === 'NEW';
|
|
3219
|
+
} catch {} finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
3220
|
+
return ok1 && ok2 && ok3;
|
|
3221
|
+
} },
|
|
3222
|
+
{ name: 'rules 손상 행 무언삭제 방지 (1.36.37, codex 4차 #1 High): 3~5셀 복구 + 3셀 미만 원문 보존 (행위검사)', run: () => {
|
|
3223
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_rl_'));
|
|
3224
|
+
let salvage = false, preserve = false;
|
|
3225
|
+
try {
|
|
3226
|
+
fs.mkdirSync(path.join(tmp, '.harness'), { recursive: true });
|
|
3227
|
+
const f = path.join(tmp, '.harness', 'rules.md');
|
|
3228
|
+
// 4셀 행(status/lastVerified 누락) + 2셀 행(파싱 불가)
|
|
3229
|
+
fs.writeFileSync(f, _rulesHeader() + '\n| R-0001 | every-session | 정상 룰 | 2026-07-16 | active | - |\n| R-0002 | every-commit | 절단 룰 | 2026-07-16 |\n| R-0003 | broken |\n');
|
|
3230
|
+
const rules = readRules(tmp);
|
|
3231
|
+
salvage = rules.length === 2 && rules.some(r => r.id === 'R-0002' && r.status === 'active');
|
|
3232
|
+
writeRules(tmp, rules);
|
|
3233
|
+
const t = fs.readFileSync(f, 'utf8');
|
|
3234
|
+
preserve = t.includes('R-0002') && t.includes('R-0003') && t.includes('파싱 불가');
|
|
3235
|
+
} catch {} finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
3236
|
+
return salvage && preserve;
|
|
3237
|
+
} },
|
|
3238
|
+
{ name: '분석 정직화 3종 (1.36.38, codex 4차 #2/#4/#9/#10): retro 기간필터+신호 정규식 / 태그 계보 v* / usage 귀속 — 행위검사', run: () => {
|
|
3239
|
+
// #2: _filterDatedSections — cutoff 이후만
|
|
3240
|
+
const t = '# L\n\n## 2020-01-01\n- ancient fix\n\n## 2099-01-01\n- future 수정\n';
|
|
3241
|
+
const f1 = _filterDatedSections(t, '2026-01-01');
|
|
3242
|
+
const filterOk = !f1.includes('ancient') && f1.includes('future') && _filterDatedSections(t, null) === t;
|
|
3243
|
+
// #10: 한글 리터럴 + ASCII 토큰 경계 (password/bypass/undone FP 차단)
|
|
3244
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_rt_'));
|
|
3245
|
+
let sigOk = false;
|
|
3246
|
+
try {
|
|
3247
|
+
fs.mkdirSync(path.join(tmp, '.harness'), { recursive: true });
|
|
3248
|
+
fs.writeFileSync(path.join(tmp, '.harness', 'task-log.md'), '# T\n\n## 2026-07-16\n- 수정 롤백 재발 password bypass undone\n');
|
|
3249
|
+
const agg = _retroAggregate(tmp);
|
|
3250
|
+
sigOk = agg.fixSignals === 3 && agg.passSignals === 0;
|
|
3251
|
+
} catch {} finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
3252
|
+
// #4/#9 배선 소스가드
|
|
3253
|
+
const s = read(__filename);
|
|
3254
|
+
const tagOk = s.includes("'--list', 'v*'") && !s.includes("'--list', 'v1.9" + ".*'");
|
|
3255
|
+
const celebOk = (s.match(/이 저장소 계보의 릴리스 태그 이력 없음/g) || []).length >= 2;
|
|
3256
|
+
const usageOk = s.includes("else if (args[2] && !args[2].startsWith('-') && exists(path.join(absRoot(args[2]), '.harness')))");
|
|
3257
|
+
const cutoffWired = s.includes('_retroAggregate(root, cutoff)');
|
|
3258
|
+
return filterOk && sigOk && tagOk && celebOk && usageOk && cutoffWired;
|
|
3259
|
+
} },
|
|
3199
3260
|
{ name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
|
|
3200
3261
|
const d = LENS_CATALOG.debug;
|
|
3201
3262
|
if (!d) return false;
|
|
@@ -5778,7 +5839,7 @@ function roundHistoryCmd(root) {
|
|
|
5778
5839
|
if (data.nextMilestone != null) {
|
|
5779
5840
|
log(yl(` 🎯 다음 마일스톤: R${data.nextMilestone} (${data.roundsToNextMilestone} 라운드 남음)`));
|
|
5780
5841
|
} else {
|
|
5781
|
-
log(gr(` 🎉 모든 마일스톤 달성 (500+)`));
|
|
5842
|
+
log(data && (data.roundCount || data.totalRounds) ? gr(` 🎉 모든 마일스톤 달성 (500+)`) : dm(` (이 저장소 계보의 릴리스 태그 이력 없음)`)); // 1.36.38 (#4): 이력 0 인데 달성 축하 금지
|
|
5782
5843
|
}
|
|
5783
5844
|
log('');
|
|
5784
5845
|
log(` 최근 10 tags:`);
|
|
@@ -14017,12 +14078,26 @@ function writeSessionCounter(root, c) { writeUtf8(sessionCounterPath(root), JSON
|
|
|
14017
14078
|
|
|
14018
14079
|
// 1.9.324 (UR-0025): _countDatedBlocks / _extractDecisionBlocks → lib/pure-utils.js 로 이동 (순수 메모리 MD 파서, require 사용).
|
|
14019
14080
|
|
|
14020
|
-
|
|
14081
|
+
// 1.36.38 (codex 4차 #2): 날짜 헤딩(## YYYY-MM-DD / ### YYYY-MM-DD …) 섹션 문서를 cutoff 이후만 남김.
|
|
14082
|
+
// 날짜 없는 프리앰블은 유지(헤더/템플릿 — 신호 카운트에 거의 무기여). cutoff 없으면 원문 그대로(무회귀).
|
|
14083
|
+
function _filterDatedSections(text, cutoff) {
|
|
14084
|
+
if (!cutoff || !text) return text || '';
|
|
14085
|
+
const parts = String(text).split(/(?=^#{2,3} \d{4}-\d{2}-\d{2})/m);
|
|
14086
|
+
return parts.filter(p => {
|
|
14087
|
+
const m = p.match(/^#{2,3} (\d{4}-\d{2}-\d{2})/);
|
|
14088
|
+
return !m || m[1] >= cutoff;
|
|
14089
|
+
}).join('');
|
|
14090
|
+
}
|
|
14091
|
+
|
|
14092
|
+
function _retroAggregate(root, cutoff) {
|
|
14021
14093
|
root = absRoot(root);
|
|
14022
14094
|
const rows = readProgressRows(root);
|
|
14023
14095
|
const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
14024
|
-
|
|
14025
|
-
|
|
14096
|
+
// 1.36.38 (#2): --days 의 cutoff 가 계산만 되고 미적용 → 주간 회고가 전기간 집계였다. 기간 신호(tlog/evidence)에 적용.
|
|
14097
|
+
// decisions 블록 수는 "누적" 라벨 그대로 누적 유지, 신호 카운트용 텍스트만 기간 필터.
|
|
14098
|
+
const tlog = _filterDatedSections(exists(taskLogPath(root)) ? read(taskLogPath(root)) : '', cutoff);
|
|
14099
|
+
const evidence = _filterDatedSections(exists(evidencePath(root)) ? read(evidencePath(root)) : '', cutoff);
|
|
14100
|
+
const _decisionsSig = _filterDatedSections(decisions, cutoff);
|
|
14026
14101
|
const handoff = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
14027
14102
|
|
|
14028
14103
|
// 1) 작업 상태 분포
|
|
@@ -14064,9 +14139,18 @@ function _retroAggregate(root) {
|
|
|
14064
14139
|
const durations = [];
|
|
14065
14140
|
for (const m of evidence.matchAll(/exit=\d+\s*\((\d+)ms\)/g)) durations.push(parseInt(m[1], 10));
|
|
14066
14141
|
|
|
14067
|
-
// 5) 실패→성공 시그널 — task-log/evidence/decisions에서
|
|
14068
|
-
|
|
14069
|
-
|
|
14142
|
+
// 5) 실패→성공 시그널 — task-log/evidence/decisions(기간 필터본)에서 카운트.
|
|
14143
|
+
// 1.36.38 (codex 4차 #10): JS \b 는 한글 경계를 못 만들어 한글 신호가 전멸(FN)했고, 'pass' 가 password/bypass 안에서,
|
|
14144
|
+
// 'done' 이 undone 안에서 매치(FP)했다. → 한글은 리터럴 카운트, ASCII 는 lookaround 토큰 경계.
|
|
14145
|
+
const _sigText = tlog + evidence + _decisionsSig;
|
|
14146
|
+
const fixSignals = [
|
|
14147
|
+
...(_sigText.match(/(?<![a-z])(fix(?:ed)?|bug|incomplete)(?![a-z])/gi) || []),
|
|
14148
|
+
...(_sigText.match(/수정|롤백|재발/g) || []),
|
|
14149
|
+
];
|
|
14150
|
+
const passSignals = [
|
|
14151
|
+
...(_sigText.match(/✓|통과/g) || []),
|
|
14152
|
+
...(_sigText.match(/(?<![a-z])(pass(?:ed)?|completed|done)(?![a-z])/gi) || []),
|
|
14153
|
+
];
|
|
14070
14154
|
|
|
14071
14155
|
// 6) 룰 활용
|
|
14072
14156
|
const rules = exists(rulesPath(root)) ? readRules(root) : [];
|
|
@@ -14166,7 +14250,7 @@ function retroCmd(root) {
|
|
|
14166
14250
|
if (!Number.isFinite(days)) { failJson(has('--json'), 'invalid_arg', _uiLang(root) === 'en' ? '--days must be a number' : '--days 는 숫자여야 합니다'); return; }
|
|
14167
14251
|
days = Math.max(0, Math.min(days, 36500));
|
|
14168
14252
|
const cutoff = new Date(Date.now() - days * 86400 * 1000).toISOString().slice(0, 10);
|
|
14169
|
-
const agg = _retroAggregate(root);
|
|
14253
|
+
const agg = _retroAggregate(root, cutoff); // 1.36.38 (#2): 기간 필터 적용
|
|
14170
14254
|
// 1.9.16: --json
|
|
14171
14255
|
if (has('--json')) {
|
|
14172
14256
|
log(JSON.stringify({ project: path.basename(root), days, cutoff, summary: _retroOneLine(agg), data: agg }, null, 2));
|
|
@@ -14270,7 +14354,7 @@ function insightsCmd(root) {
|
|
|
14270
14354
|
if (has('--all-apps') || arg('--include', null)) {
|
|
14271
14355
|
return _insightsWorkspace(root);
|
|
14272
14356
|
}
|
|
14273
|
-
const agg = _retroAggregate(root);
|
|
14357
|
+
const agg = _retroAggregate(root); // insights 는 누적 지표 명령 — 기간 필터 없음(retro 만 --days). 1.36.38 광역치환 오적용 교정.
|
|
14274
14358
|
// 1.9.16: --json
|
|
14275
14359
|
if (has('--json')) {
|
|
14276
14360
|
const sc = readSessionCounter(root);
|
|
@@ -15164,16 +15248,34 @@ function readRules(root) {
|
|
|
15164
15248
|
if (!/^\| R-\d{4,} \|/.test(line)) continue;
|
|
15165
15249
|
// 1.9.399 (7번째 버그헌트 P1-A, UR-0104): 비이스케이프 파이프 분리 + 복원 — rule 텍스트의 '|'가 컬럼 밀림/멱등성 무력화를 못 일으킴.
|
|
15166
15250
|
const cells = line.split(/(?<!\\)\|/).slice(1, -1).map(s => _cellUnescape(s).trim());
|
|
15167
|
-
|
|
15168
|
-
|
|
15251
|
+
// 1.36.38 (codex 4차 #1, High): 셀 부족 행을 버리면 다음 writeRules 가 그 룰을 무언 삭제했다(수동 편집/중단된 쓰기에서 발생).
|
|
15252
|
+
// 3~5셀은 복구(누락 셀 기본값 — 룰 기능 유지), 3셀 미만만 파싱 불가로 남긴다(writeRules 가 원문 보존).
|
|
15253
|
+
if (cells.length < 3) continue;
|
|
15254
|
+
rules.push({ id: cells[0], trigger: cells[1], rule: cells[2], added: cells[3] || '-', status: cells[4] || 'active', lastVerified: cells[5] || '-' });
|
|
15169
15255
|
}
|
|
15170
15256
|
return rules;
|
|
15171
15257
|
}
|
|
15172
15258
|
|
|
15259
|
+
// 1.36.38 (#1): 파싱 불가(3셀 미만) 룰-형태 행 수집 — writeRules 가 삭제하지 않고 원문 보존하기 위함.
|
|
15260
|
+
function _unparsableRuleLines(root) {
|
|
15261
|
+
const f = rulesPath(root);
|
|
15262
|
+
if (!exists(f)) return [];
|
|
15263
|
+
const out = [];
|
|
15264
|
+
for (const line of read(f).split('\n')) {
|
|
15265
|
+
if (!/^\| R-\d{4,} \|/.test(line)) continue;
|
|
15266
|
+
const cells = line.split(/(?<!\\)\|/).slice(1, -1);
|
|
15267
|
+
if (cells.length < 3) out.push(line);
|
|
15268
|
+
}
|
|
15269
|
+
return out;
|
|
15270
|
+
}
|
|
15271
|
+
|
|
15173
15272
|
function writeRules(root, rules) {
|
|
15174
15273
|
// 1.9.399 (7번째 버그헌트 P1-A, UR-0104): 셀 안전화 — rule 텍스트의 파이프/개행 차단(컬럼 밀림·중복 룰 무한생성 방지).
|
|
15175
15274
|
const body = rules.map(r => `| ${_cellSafe(r.id)} | ${_cellSafe(r.trigger)} | ${_cellSafe(r.rule)} | ${_cellSafe(r.added)} | ${_cellSafe(r.status)} | ${_cellSafe(r.lastVerified || '-')} |`).join('\n');
|
|
15176
|
-
|
|
15275
|
+
// 1.36.38 (#1): 파싱 불가 행은 무언 삭제 대신 원문 보존 + 경고 표식 (수동 복구 대상)
|
|
15276
|
+
const damaged = _unparsableRuleLines(root).filter(l => !rules.some(r => l.startsWith(`| ${r.id} `)));
|
|
15277
|
+
const tail = damaged.length ? '\n<!-- ⚠ 아래 행은 손상돼 파싱 불가 — 삭제하지 않고 보존함. 수동 복구 후 위 표로 병합하세요 -->\n' + damaged.join('\n') + '\n' : '';
|
|
15278
|
+
writeUtf8(rulesPath(root), _rulesHeader() + '\n' + body + (body ? '\n' : '') + tail);
|
|
15177
15279
|
}
|
|
15178
15280
|
|
|
15179
15281
|
function nextRuleId(root) {
|
|
@@ -21585,6 +21687,8 @@ async function main() {
|
|
|
21585
21687
|
let root;
|
|
21586
21688
|
if (_pathArg) root = absRoot(_pathArg);
|
|
21587
21689
|
else if (args[1] && !args[1].startsWith('-') && exists(path.join(absRoot(args[1]), '.harness'))) root = absRoot(args[1]);
|
|
21690
|
+
// 1.36.38 (codex 4차 #9): subcommand 형(예: memory status <B>)은 대상 경로가 args[2] — 종전엔 cwd(A)에 오귀속됐다.
|
|
21691
|
+
else if (args[2] && !args[2].startsWith('-') && exists(path.join(absRoot(args[2]), '.harness'))) root = absRoot(args[2]);
|
|
21588
21692
|
else root = absRoot(process.cwd());
|
|
21589
21693
|
if (exists(path.join(root, '.harness'))) _bumpUsage(root, cmd);
|
|
21590
21694
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.38",
|
|
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",
|
package/scripts/e2e.js
CHANGED
|
@@ -2166,8 +2166,10 @@ total++;
|
|
|
2166
2166
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
2167
2167
|
cp.spawnSync(process.execPath, [CLI, 'plan', 'add', '캐시 helper', '--status', 'done', '--path', tmpR], { stdio: 'ignore', timeout: 10000 });
|
|
2168
2168
|
cp.spawnSync(process.execPath, [CLI, 'plan', 'add', '인증 helper', '--status', 'in-progress', '--path', tmpR], { stdio: 'ignore', timeout: 10000 });
|
|
2169
|
-
|
|
2170
|
-
|
|
2169
|
+
// 1.36.38: retro 가 --days 기간필터를 실제 적용하므로 픽스처 날짜는 오늘(창 안) — 고정 과거 날짜는 필터에 걸러지는 게 의도된 동작.
|
|
2170
|
+
const _todayR = new Date().toISOString().slice(0, 10);
|
|
2171
|
+
fs.appendFileSync(path.join(tmpR, '.harness/decisions.md'), `\n### ${_todayR} — 캐시 차등 TTL 결정\n- Reason: ...\n`);
|
|
2172
|
+
fs.appendFileSync(path.join(tmpR, '.harness/review-evidence.md'), `\n## ${_todayR} verify-code\nexit=0 (250ms)\nexit=0 (180ms)\nexit=0 (120ms)\nexit=0 (90ms)\n`);
|
|
2171
2173
|
const r = cp.spawnSync(process.execPath, [CLI, 'retro', tmpR], { encoding: 'utf8', timeout: 15000 });
|
|
2172
2174
|
const ok = r.status === 0 && /한 줄 요약/.test(r.stdout) && /작업 상태 분포/.test(r.stdout) && /다음 우선 작업/.test(r.stdout) && /검증 시간 추세/.test(r.stdout);
|
|
2173
2175
|
console.log(ok ? '✓ B(1.9.13) retro: 한 줄 요약 + 다음 우선 작업 + 검증 시간 추세' : '✗ retro 실패');
|