leerness 1.36.61 → 1.36.63
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 +18 -0
- package/README.md +5 -6
- package/bin/leerness.js +164 -24
- package/lib/pure-utils.js +4 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.63 — 2026-07-22 — F-05 시리즈 완결: README 섹션·헤더·런타임 기본값 en (5회차) — R-0001 검수 8회전
|
|
4
|
+
|
|
5
|
+
**en init 한글 잔존 53/61 → 6/61** — 잔여 6개 전부 스킬팩 한국어 콘텐츠(데이터, 의도적 제외). 감사 F-05 종결.
|
|
6
|
+
|
|
7
|
+
- **README 관리 섹션 en**(정체성/5계층/명령/CRUD/MCP/빠른시작/planning + 검수 #3 보강: list 예시·release/autonomous 절 + `--next-action`→`--next` 오기 정정 — KO 블록도 동일 오기였음). syncReadme 가 프로젝트 언어 감지.
|
|
8
|
+
- **rules/progress 헤더 en**: 파서(readRules/readProgressRows)가 라인앵커+위치 기반임을 확인 후 frontmatter/라벨만 en. rule add 시 en 헤더 유지(writeRules 언어 해석), (검수 #1) 손상 헤더 복구도 프로젝트 언어로.
|
|
9
|
+
- **런타임 기본값 언어 인지**: 시드 task·task/plan add 기본 nextAction(`write the next action`)·(검수 #2) plan Done-When `(unset)`·task/plan drop 사유 — lazy detect 가 en 기본값도 trivial 로 인식(감지 동수 유지).
|
|
10
|
+
- (검수 #4) _mergeReadmeSection 말단 개행 정규화 — 재init 바이트 멱등.
|
|
11
|
+
- 검증: en 파서 전 명령 무회귀(검수: lazy/drift/audit KO=EN 동수), ko 무회귀, selftest 332, 게이트.
|
|
12
|
+
|
|
13
|
+
## 1.36.62 — 2026-07-21 — commands/AX/잔여 참조층 en 완역 (F-05 4회차) — R-0001 검수 7회전
|
|
14
|
+
|
|
15
|
+
en init 한글 잔존 23/61 → **9/61**. 잔여 9개는 성격이 다른 대상(스킬 카탈로그 한국어 데이터 5·파서-결합 헤더 2·README 관리 섹션·skill-index 데이터 컬럼) — 별도 판단 대상으로 명시.
|
|
16
|
+
|
|
17
|
+
- **en 오버라이드 14종**: .claude/commands 5·skills/leerness.md·AX 가이드 4·test-evidence/review-evidence·skill-index·context-routing·feature-graph.
|
|
18
|
+
- (검수 #1) **/update 한국어 되덮기**: auto-update 훅 설치기가 en 템플릿을 한국어로 재작성 — 언어 인지 렌더. (검수 #2) **feature add 가 en 그래프를 한국어로 회귀+frontmatter 소실**: _writeFeatureGraph 가 매 저장마다 ko 템플릿 전체 재생성 — 기존 서두를 '## Nodes' 까지 보존. (검수 #3) 전환 누락 4종: AX_PLAN/test-evidence 는 managed 편입, review-evidence 는 _USER_STATE 편입(검증 이력=사용자 데이터), feature-graph 는 #2 경로가 커버. (검수 #4) verify-code 증거 마커 en 프로젝트에서 `(auto)`.
|
|
19
|
+
- 검증: en /update·feature add 후 그래프 무한글+frontmatter 보존+파싱 실측, ko 15종 바이트 무회귀(검수), selftest 332.
|
|
20
|
+
|
|
3
21
|
## 1.36.61 — 2026-07-21 — .harness 정책·시드 문서군 en 완역 (F-05 3회차) — R-0001 검수 6회전
|
|
4
22
|
|
|
5
23
|
en init 한글 잔존 53/61 → **23/61** (1·3회차 누적). 검수 계약 대조: 6개 대표 문서 규칙 전량 보존 확인.
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ MIT
|
|
|
122
122
|
<!-- leerness:project-readme:start -->
|
|
123
123
|
## Leerness Project Harness
|
|
124
124
|
|
|
125
|
-
이 프로젝트는 Leerness v1.36.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.63 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -164,7 +164,7 @@ leerness decision list --query "키워드" # 1.9.139
|
|
|
164
164
|
leerness rule add "매 commit마다 changelog 갱신" --trigger every-commit
|
|
165
165
|
leerness rule list
|
|
166
166
|
# Plan (milestones)
|
|
167
|
-
leerness plan add "M-XXXX 계획" --next
|
|
167
|
+
leerness plan add "M-XXXX 계획" --next "다음 단계"
|
|
168
168
|
leerness plan list
|
|
169
169
|
# Lessons (영구 교훈)
|
|
170
170
|
leerness lesson save "교훈 본문" --tag perf
|
|
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
|
|
|
176
176
|
|
|
177
177
|
### MCP server (외부 AI 통합)
|
|
178
178
|
|
|
179
|
-
Leerness v1.36.
|
|
179
|
+
Leerness v1.36.63는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
180
180
|
|
|
181
181
|
```jsonc
|
|
182
182
|
// 카테고리별
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.61는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
197
197
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
198
198
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
|
|
199
199
|
|
|
200
|
-
현재 누적: **v1.9.x → 1.36.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.63 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,6 +235,5 @@ leerness release pack --close --auto-main-push
|
|
|
235
235
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
236
236
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
237
237
|
|
|
238
|
-
Last synced by Leerness v1.36.
|
|
238
|
+
Last synced by Leerness v1.36.63: 2026-07-22
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
|
240
|
-
|
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.63';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -305,7 +305,93 @@ function ask(question) {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// 1.9.140: managedReadmeBlock 자동 풍부화 — 매 release pack 마다 명령/MCP/성능/가이드 갱신
|
|
308
|
-
function managedReadmeBlock(project) {
|
|
308
|
+
function managedReadmeBlock(project, lang = 'ko') {
|
|
309
|
+
// 1.36.63 (F-05 5회차): en 프로젝트 README 관리 섹션 완전 영어 — 계약 동등(정체성/5계층/명령/CRUD/MCP/빠른시작/planning files)
|
|
310
|
+
if (lang === 'en') {
|
|
311
|
+
return [
|
|
312
|
+
README_START,
|
|
313
|
+
'## Leerness Project Harness',
|
|
314
|
+
'',
|
|
315
|
+
`This project uses the Leerness v${VERSION} harness. AI agents must load context with \`leerness handoff\` before working, and run \`leerness check\` / \`leerness audit\` / \`leerness session close\` afterwards.`,
|
|
316
|
+
'',
|
|
317
|
+
'### Identity — an operating layer for AI agents',
|
|
318
|
+
'',
|
|
319
|
+
'Leerness is **not an executor or coding agent** — it is a **universal operating layer** that sits on top of any AI coding agent (Claude Code · Codex · Cursor · Goose, …). It provides five shared layers:',
|
|
320
|
+
'',
|
|
321
|
+
'- **Memory** — persists project state/decisions/progress into `.harness/`',
|
|
322
|
+
'- **Policy** — 8-tier permission levels + enforce (read-only→publish), MCP call gate',
|
|
323
|
+
'- **Handoff** — standard context transfer between agents + one-call onboarding via `get_project_context`',
|
|
324
|
+
'- **Verification** — evidence-based completion checks that catch false "done" claims (advisory; blocking when required via the CI gate)',
|
|
325
|
+
'- **Audit** — automatic drift/idempotency/secret/encoding audits (self-heal: drift·idempotency --auto-fix, encoding --apply; secrets are detection-only)',
|
|
326
|
+
'',
|
|
327
|
+
'It **complements AGENTS.md** (static instructions), never replaces it — static rules live in AGENTS.md; dynamic state, verification, and handoff live in leerness. Identity lookup: `leerness about` (MCP `leerness_about`).',
|
|
328
|
+
'',
|
|
329
|
+
'### Core Commands',
|
|
330
|
+
'',
|
|
331
|
+
'```bash',
|
|
332
|
+
'leerness handoff . # auto-load session context',
|
|
333
|
+
'leerness status . # install status',
|
|
334
|
+
'leerness verify . # required-file verification',
|
|
335
|
+
'leerness audit . # consistency / plan-progress audit',
|
|
336
|
+
'leerness scan secrets . # secret pattern scan',
|
|
337
|
+
'leerness encoding check . # UTF-8 / BOM / NUL / .bat encoding',
|
|
338
|
+
'leerness lazy detect . # lazy-work (false-done) evaluation',
|
|
339
|
+
'leerness memory search "key" # search decisions/history',
|
|
340
|
+
'leerness session close . # close session + auto handoff',
|
|
341
|
+
'leerness update . # auto version detect + migrate',
|
|
342
|
+
'```',
|
|
343
|
+
'',
|
|
344
|
+
'### Memory Surface CRUD (5 surfaces × add/list/drop)',
|
|
345
|
+
'',
|
|
346
|
+
'```bash',
|
|
347
|
+
'leerness task add "task title"',
|
|
348
|
+
'leerness task list --json',
|
|
349
|
+
'leerness decision add "decision title" --reason "why"',
|
|
350
|
+
'leerness decision list --query "keyword"',
|
|
351
|
+
'leerness rule add "update the changelog every commit" --trigger every-commit',
|
|
352
|
+
'leerness rule list',
|
|
353
|
+
'leerness plan add "M-XXXX plan" --next "next step"',
|
|
354
|
+
'leerness plan list',
|
|
355
|
+
'leerness lesson save "lesson body" --tag perf',
|
|
356
|
+
'leerness lesson list --query "keyword"',
|
|
357
|
+
'leerness memory archive list . --query "keyword"',
|
|
358
|
+
'leerness memory restore decisions <date|title>',
|
|
359
|
+
'```',
|
|
360
|
+
'',
|
|
361
|
+
'### Release automation & autonomous mode',
|
|
362
|
+
'',
|
|
363
|
+
'```bash',
|
|
364
|
+
'leerness release pack --close --auto-main-push # one-shot release automation (incl. main push)',
|
|
365
|
+
'```',
|
|
366
|
+
'',
|
|
367
|
+
'Send the `<<autonomous-loop-dynamic>>` signal and the AI will: pick the next round → change code → update regression tests → pass the full e2e suite → npm publish + git tag → main push → session close → schedule the next round.',
|
|
368
|
+
'',
|
|
369
|
+
'### MCP server (external AI integration)',
|
|
370
|
+
'',
|
|
371
|
+
`Leerness v${VERSION} ships a stdio JSON-RPC MCP server — exposing **${_mcpToolCount()} tools** to Claude Code · Cursor · Codex CLI and others (\`leerness mcp serve\`; \`--profile core\` = 20 essentials).`,
|
|
372
|
+
'',
|
|
373
|
+
'### Quick start',
|
|
374
|
+
'',
|
|
375
|
+
'```bash',
|
|
376
|
+
'npm install -g leerness',
|
|
377
|
+
'cd my-project && leerness init . --yes --skills recommended',
|
|
378
|
+
'leerness handoff . # at every session start',
|
|
379
|
+
'leerness session close . # at every session end',
|
|
380
|
+
'```',
|
|
381
|
+
'',
|
|
382
|
+
'### Planning Files',
|
|
383
|
+
'',
|
|
384
|
+
'- `.harness/plan.md`: overall goal, milestones, dropped scope',
|
|
385
|
+
'- `.harness/progress-tracker.md`: per-request status and evidence',
|
|
386
|
+
'- `.harness/current-state.md`: what to continue right now',
|
|
387
|
+
'- `.harness/session-handoff.md`: next-session handoff (auto-written)',
|
|
388
|
+
'- `.harness/lessons.md` / `decisions.md` / `rules.md`: permanent memory (5 surfaces)',
|
|
389
|
+
'',
|
|
390
|
+
`Last synced by Leerness v${VERSION}: ${today()}`,
|
|
391
|
+
README_END,
|
|
392
|
+
''
|
|
393
|
+
].join('\n');
|
|
394
|
+
}
|
|
309
395
|
return [
|
|
310
396
|
README_START,
|
|
311
397
|
'## Leerness Project Harness',
|
|
@@ -352,7 +438,7 @@ function managedReadmeBlock(project) {
|
|
|
352
438
|
'leerness rule add "매 commit마다 changelog 갱신" --trigger every-commit',
|
|
353
439
|
'leerness rule list',
|
|
354
440
|
'# Plan (milestones)',
|
|
355
|
-
'leerness plan add "M-XXXX 계획" --next
|
|
441
|
+
'leerness plan add "M-XXXX 계획" --next "다음 단계"',
|
|
356
442
|
'leerness plan list',
|
|
357
443
|
'# Lessons (영구 교훈)',
|
|
358
444
|
'leerness lesson save "교훈 본문" --tag perf',
|
|
@@ -456,7 +542,7 @@ function coreFiles(root, lang = 'ko', selectedSkills = [], opts = {}) {
|
|
|
456
542
|
'.harness/skills-lock.json': skillLock(selectedSkills),
|
|
457
543
|
'.harness/project-brief.md': fm('project-brief', ['프로젝트 목적 확인','신규 기능 판단','계획 수립'], ['프로젝트 목적 변경','사용자/범위 변경'], `# Project Brief\n\n## Project\n${project}\n\n## Purpose\n- 이 프로젝트의 목적을 실제 내용으로 업데이트하세요.\n\n## Users\n-\n\n## Success Criteria\n-\n`),
|
|
458
544
|
'.harness/plan.md': fm('plan', ['작업 시작 전','새 요청 접수','범위 변경','신규 프로젝트 감지'], ['계획 추가/수정/드랍','milestone 변경','목표 변경'], `# Plan\n\n## Goal\n- 사용자 목적을 기준으로 전체 계획을 유지합니다.\n\n## Scope\n- 포함 범위를 기록합니다.\n\n## Out of Scope / Dropped\n| ID | Item | Reason | Date |\n|---|---|---|---|\n\n## Milestones\n\n### M-0001. 프로젝트 계획 정리\nStatus: planned\nProgress: 0%\n\nTasks:\n- [ ] project-brief.md를 실제 프로젝트 목적에 맞게 작성\n- [ ] context-map.md를 실제 파일 구조에 맞게 작성\n`),
|
|
459
|
-
'.harness/progress-tracker.md': _canonicalProgressHeader() + '\n', // 1.9.293: _canonicalProgressHeader 단일 출처
|
|
545
|
+
'.harness/progress-tracker.md': _canonicalProgressHeader(lang) + '\n', // 1.9.293: _canonicalProgressHeader 단일 출처 · 1.36.63: lang 전달
|
|
460
546
|
'.harness/guideline.md': fm('guideline', ['구현 전 품질 기준 확인','계획 이행 기준 확인'], ['개발 기준 변경','검증 루틴 변경'], `# Guideline\n\n## Operating Principle\n- plan.md의 목표와 범위를 기준으로 작업합니다.\n- progress-tracker.md의 요청 상태를 기준으로 완료/미완료를 판단합니다.\n- guideline.md에는 진행률 수치를 직접 기록하지 않습니다. 진행률은 plan.md/progress-tracker.md가 단일 출처입니다.\n\n## Quality Gate\n- 변경 전 관련 route를 확인합니다 (\`leerness route <task-type>\`).\n- 변경 후 \`leerness verify\`, \`leerness audit\`, \`leerness check\`을 실행합니다.\n- 완료 선언 전 \`leerness lazy detect\`을 실행합니다.\n- 세션 종료 시 \`leerness session close\`를 실행합니다.\n`),
|
|
461
547
|
'.harness/plan-progress-boundary.md': fm('plan-progress-boundary', ['계획과 진행률이 중복될 때','작업 추적 구조 변경'], ['역할 분리 기준 변경'], `# Plan / Progress Boundary\n\n## plan.md\n- 전체 목표, milestone, 포함/제외 범위, 계획 변경 이력.\n\n## progress-tracker.md\n- 사용자 요청 단위의 상태, 증거, 다음 액션.\n- ID 규칙: T-0001부터 단조 증가. plan add 시 부여되는 ID는 plan/progress 양쪽에서 고유합니다.\n\n## guideline.md\n- plan/progress를 수행할 때 지켜야 할 실행 기준.\n`),
|
|
462
548
|
'.harness/current-state.md': fm('current-state', ['세션 시작','작업 이어받기'], ['현재 상태 변경','다음 작업 변경'], `# Current State\n\nUpdated: ${today()}\n\n## Now\n-\n\n## Next\n-\n\n## Blockers\n-\n`),
|
|
@@ -639,7 +725,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
639
725
|
- ⚠ contract verify 생략 → 사양 불일치 BUG가 사용자에게 노출
|
|
640
726
|
`),
|
|
641
727
|
'.harness/anti-lazy-work-policy.md': fm('anti-lazy-work-policy', ['완료 선언 전'], ['게으른 작업 방지 기준 변경'], `# Anti Lazy Work Policy\n\n## Rules\n1. **증거 없는 완료 금지**: \"완료\"를 선언하려면 progress-tracker의 evidence 컬럼에 명령 출력/테스트 결과/스크린샷 경로 등이 있어야 합니다.\n2. **빈 핸드오프 금지**: 세션 종료 시 session-handoff.md의 Completed/In Progress/Next Exact Step이 모두 비어 있으면 close가 \"insufficient\" 상태로 표시됩니다.\n3. **부분 구현 자기보고**: 완전 구현이 아니면 status를 \`incomplete\`로, Next Exact Step에 \"무엇을 추가해야 끝나는지\" 한 줄을 적습니다.\n4. **검증 기록**: typecheck/lint/test 결과를 review-evidence.md에 누적 기록합니다.\n5. **TODO 표지**: 코드에 \`TODO\`/\`FIXME\`/\`XXX\`를 새로 도입하면 progress-tracker에 동일 ID로 추적합니다.\n6. **거짓 완료 자동 감지**: \`leerness lazy detect\`는 다음을 자동 점검합니다.\n - progress-tracker에 done인데 evidence가 비어있는 row\n - session-handoff의 Completed가 비어있고 Next Exact Step도 비어있음\n - 코드에 새 TODO/FIXME 추가 + progress-tracker에 추적 항목 없음\n - test 명령 실행 흔적 없음 (review-evidence.md 또는 task-log.md에 명령 기록)\n7. **품질 렌즈 자가질문**: 완료 선언 전 \`leerness lens\`의 분야별 질문에 스스로 답합니다 — 코드: "선임 개발자가 이 코드를 보고 복잡하다고 느끼지 않을까?" / 디자인: "선임 디자이너와 일반 사용자가 봤을 때 이쁘고 편하고 직관적인가?". "그렇다(통과)"라고 답할 수 없으면 완료가 아닙니다. 분야를 바꾸면 인과관계로 연결된 분야(\`lens\` 출력의 ↔ 인과)의 질문도 다시 확인합니다.\n`),
|
|
642
|
-
'.harness/rules.md': _rulesHeader() + '\n',
|
|
728
|
+
'.harness/rules.md': _rulesHeader(lang) + '\n', // 1.36.63: lang 전달
|
|
643
729
|
'.harness/session-handoff.md': fm('session-handoff', ['세션 시작','다음 작업 이어받기'], ['세션 종료'], `# Session Handoff\n\nLast generated: (자동)\n\n## Completed\n-\n\n## In Progress\n-\n\n## Incomplete / Waiting / On Hold / Blocked\n-\n\n## Dropped\n-\n\n## Verification\n-\n\n## Recommended Direction\n-\n\n## Next Exact Step\n-\n`),
|
|
644
730
|
'.harness/leerness-maintenance.md': fm('leerness-maintenance', ['작업 시작','마이그레이션/릴리즈 전'], ['버전 정책 변경'], `# Leerness Maintenance\n\nAI agents should check:\n\n\`\`\`bash\nleerness --version\nleerness self check .\nleerness update --check # 24h 캐시 자동 감지\nleerness update --yes # 새 버전 발견 시 자동 마이그레이션\ncat .harness/HARNESS_VERSION\nnpm view leerness version\n\`\`\`\n`),
|
|
645
731
|
'.harness/language-policy.md': fm('language-policy', ['문서 작성 전'], ['언어 변경'], `# Language Policy\n\nSelected language: ${lang}\n\n모든 Leerness 노트, 스킬 노트, 세션 보고, 작업 목록은 위 언어를 기본으로 사용합니다 (사용자가 다른 언어를 명시 요청 시 예외).\n`),
|
|
@@ -667,6 +753,22 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
667
753
|
if (lang === 'en') {
|
|
668
754
|
_files['AGENTS.md'] = `${MARK}\n# Leerness Agent Instructions\n\n## ⭐ First action every session\n**Read \`.harness/session-workflow.md\` first and follow its 6-step workflow**: analyze request → plan → distribute → sub-agent work → integrated verification → close. Keep the same flow regardless of round length/complexity — that is what prevents drift.\n\n## Static vs dynamic — the leerness boundary\n**AGENTS.md = static project instructions** (coding rules, test commands, prohibitions, deploy steps — rarely change).\n**leerness = dynamic work state, memory, verification, handoff** (current goal, changed files, failed attempts, verification results, next-agent handoff — change every task).\n- Put rules/commands/prohibitions here in AGENTS.md.\n- Dynamic state (decisions/lessons/plan/progress/verification/handoff) is recorded by leerness in the **default workspace \`.harness/\`** (decisions.md / lessons.md / plan.md / progress-tracker.md / session-handoff.md) via \`leerness handoff\`, \`decision add\`, \`lesson save\`, etc.\n- (Optional) the JSON state substrate of \`leerness state show|start|record|verify|handoff\` (or MCP \`leerness_state_*\`) lives in \`.leerness/\` — the cross-agent handoff standard, separate from the main workspace.\n- leerness **complements** AGENTS.md, it does not replace it. Static instructions here, dynamic state in leerness.\n\n## Mandatory read order (session start)\n1. **.harness/session-workflow.md** (6-step workflow — highest priority)\n2. .harness/context-routing.md\n3. .harness/session-handoff.md\n4. .harness/current-state.md\n5. .harness/plan.md\n6. .harness/progress-tracker.md\n7. .harness/guideline.md\n8. .harness/protected-files.md\n9. .harness/writeback-policy.md\n10. .harness/anti-lazy-work-policy.md\n11. **.harness/rules.md** (user-defined standing rules — follow every session)\n\n## Required behavior\n- Run \`leerness handoff .\` at session start to load context (handoff prints active rules automatically).\n- **Ask-on-ambiguity duty**: if the user's request has parts open to interpretation (vague qualifiers, pronouns, multiple alternatives, unclear scope), **ask the user instead of guessing**. Ask the questions produced by \`leerness clarify "<request>"\` verbatim. Even without detected signals, when your own judgment is split, asking wins.\n- **Preview-approval duty (new features)**: when the user asks for a new feature, **do not write code first**. ① Register a preview with \`leerness preview add "<feature>" --design "<design/UX>" --features "<list>"\`, ② present it and ask for approval or revisions, ③ on approval run \`leerness preview approve <P-ID>\`; on revision \`leerness preview revise <P-ID> --note "..."\` and re-present. **Never write the feature's code before approve.**\n- Classify work with \`leerness route <task-type>\` (planning, feature, bugfix, refactor, research, consistency, release, migration, session-start, session-close, harness-maintenance).\n- Never delete protected files/managed sections — merge, archive, or mark deprecated instead.\n- After meaningful changes update progress-tracker, current-state, task-log, session-handoff.\n- Before claiming completion, self-verify with \`leerness check .\` or \`leerness lazy detect .\` and answer the \`leerness lens\` self-questions per domain (code: "would a senior developer find this needlessly complex?" / design: "would a senior designer and an ordinary user find this pretty and intuitive?").\n- Before changes run the guards: \`leerness scan secrets .\`, \`leerness encoding check .\`.\n- Before duplicating a capability check design-system.md, consistency-policy.md, reuse-map.md.\n- Close every session with \`leerness session close .\` — 9 categories (done/in-progress/incomplete/planned/waiting/on-hold/blocked/dropped/verification) + active-rule verification.\n- Updates: \`leerness update --check\` (detect) → \`leerness update --yes\` (auto-migrate).\n\n## Natural-language retro/insights/brainstorm\n| User phrase | Run immediately |\n|---|---|\n| "retrospective / look back / wrap up" | \`leerness retro\` |\n| "retro for last N days" | \`leerness retro --days N\` |\n| "stats / cumulative metrics / insights" | \`leerness insights\` |\n| "brainstorm about X / materials on X / review before starting X" | \`leerness brainstorm "X"\` |\n\nsession close prints a one-line summary automatically every time and runs a deep retrospective every 5 sessions; call the commands immediately when the user asks explicitly.\n\n## Natural-language standing rules\nWhen the user states a standing rule ("do Y every X"), register it immediately:\n\n| User phrase | Run immediately |\n|---|---|\n| "bump the version every update" | \`leerness rule add "bump version (patch)" --trigger every-update\` |\n| "add patch notes every commit" | \`leerness rule add "add patch notes" --trigger every-commit\` |\n| "deploy at session close" | \`leerness rule add "deploy (release publish)" --trigger session-close\` |\n| "pause/stop rule X" | \`leerness rule pause <ID>\` (find the ID with \`rule list\`) |\n| "remove rule X" | \`leerness rule remove <ID>\` |\n| "stop all rules" | \`leerness rule stop\` |\n| "resume rules" | \`leerness rule resume-all\` or \`leerness rule resume <ID>\` |\n\nAfter registering, report the result (ID + trigger + description) and apply it every session until the user explicitly says stop/remove.\n\n## Automatic rule verification\n- **every-update / version rules**: checks package.json version change.\n- **CHANGELOG / patch-note rules**: checks CHANGELOG.md mtime.\n- **test / verify rules**: checks today's verify traces in review-evidence.md.\n- **deploy / publish / push rules**: not auto-verifiable → guides \`leerness release publish\`.\n\nAutomate verifiable rules with \`leerness release bump\`, \`leerness release note "..."\`, and \`leerness release publish\`.\n`;
|
|
669
755
|
_files['CLAUDE.md'] = `${MARK}\n# Claude Code Instructions\n\nFollow AGENTS.md. Always run \`leerness handoff .\` at the start and \`leerness session close .\` before ending a session.\n\n**⭐ First action every session**: follow the 6-step workflow in \`.harness/session-workflow.md\` (analyze → plan → distribute → sub-agent → verify → close). On drift critical, recover with \`leerness drift check --auto-fix\`.\n\nProtected files must not be deleted. Read .harness/anti-lazy-work-policy.md before claiming completion.\n\n## Natural-language standing rules\nWhen the user states "do Y every X", immediately run \`leerness rule add "Y" --trigger every-X\`. Registered rules are printed by every \`handoff\` and verified by \`session close\`. Only \`rule pause/remove\` when the user explicitly says stop/remove.\n\nSee the "Natural-language standing rules" table in AGENTS.md for the full mapping.\n`;
|
|
756
|
+
// 1.36.62 (F-05 4회차): commands/AX/잔여 참조층 en 완역
|
|
757
|
+
_files['.harness/test-evidence-policy.md'] = fm('test-evidence-policy', ['recording verification results'], ['verification format changes'], `# Test Evidence Policy\n\nEvery verification is appended to \`.harness/review-evidence.md\`.\n\n## Format\n\`\`\`\n## YYYY-MM-DD HH:MM\nTask: T-XXXX\nCommand: <command>\nExit: <code>\nNote: <key result summary>\nArtifacts: <screenshot/log paths>\n\`\`\`\n`, 'en');
|
|
758
|
+
_files['.harness/review-evidence.md'] = fm('review-evidence', ['progress reports', 'release review'], ['verification results recorded'], `# Review Evidence\n\nVerification command/result history. Append-only.\n`, 'en');
|
|
759
|
+
_files['.harness/AX_PLAN_GUIDE.md'] = fm('ax-plan-guide', ['planning/plan changes', 'new project'], ['plan guide changes'], `# AX Plan Guide\n\n1. Check whether the user's request is within the current plan.md scope.\n2. If it is new scope, add it to both plan.md (milestone) and progress-tracker.md (T-id).\n3. When the user drops scope, mark it dropped instead of deleting.\n4. For new projects, fill plan.md/project-brief.md before coding.\n`, 'en');
|
|
760
|
+
_files['.harness/AX_MIGRATION_GUIDE.md'] = fm('ax-migration-guide', ['before migration'], ['migration policy changes'], `# AX Migration Guide\n\n- Back up before changes (\`.harness/archive/\`).\n- Preserve existing project memory (preserve-by-default).\n- Merge .env.example/.gitignore line by line.\n- Never delete protected files.\n- Migration report: \`.harness/migration-report.md\`.\n- Automatic: \`leerness update --yes\` performs backup, merge, and verification in one step.\n`, 'en');
|
|
761
|
+
_files['.harness/AX_NEW_PROJECT_GUIDE.md'] = fm('ax-new-project-guide', ['new project detected'], ['fresh-install policy changes'], `# AX New Project Guide\n\nBefore coding, ask or infer the project goal, users, scope, out-of-scope, stack, deployment target, and milestones. Then fill plan.md and project-brief.md.\n`, 'en');
|
|
762
|
+
_files['.harness/AX_SKILL_LIBRARY_GUIDE.md'] = fm('ax-skill-library-guide', ['skill learning/verification/upload'], ['skill policy changes'], `# AX Skill Library Guide\n\nValidated skills require metadata, sensitive data scan, AI verification, dry-run publish, and explicit execute approval.\n`, 'en');
|
|
763
|
+
_files['.harness/skill-index.md'] = fm('skill-index', ['choosing skills per task'], ['skills added/removed'], `# Skill Index\n\n| ID | Korean Name | Capabilities | Last Updated | Verification |\n|---|---|---|---|---|\n${skillRows}\n`, 'en');
|
|
764
|
+
_files['.harness/context-routing.md'] = fm('context-routing', ['before all work', 'judging task type'], ['task types added', 'reference files change'], `# Context Routing\n\n${Object.entries(routes).map(([k, v]) => `## ${k}\nRead:\n${v.read.map(x => '- ' + x).join('\n')}\n\nUpdate:\n${v.update.map(x => '- ' + x).join('\n')}`).join('\n\n')}\n`, 'en');
|
|
765
|
+
_files['.harness/feature-graph.md'] = fm('feature-graph', ['before adding features', 'before data-format changes', 'before external-API mapping work'], ['feature register / link / impact recall'], `# Feature Graph\n\n> **Purpose**: track each feature's causal relations so impact scope is known before writing code.\n> Before new features, format changes, or external-API mapping, run \`leerness feature impact <id>\`.\n> handoff auto-matches the current task keywords and recalls affected features.\n\n## How to use\n\n\`\`\`bash\nleerness feature add "User Auth" # F-0001 auto-assigned\nleerness feature link F-0002 --depends-on F-0001 # dependency\nleerness feature link F-0001 --affects F-0002,F-0005 # impact (multiple)\nleerness feature link F-0001 --co-changes-with F-0011 # must change together\nleerness feature impact F-0001 # full transitive impact\nleerness feature list --json # graph JSON\nleerness feature show F-0001 # single detail\n\`\`\`\n\n## Nodes\n\n`, 'en');
|
|
766
|
+
_files['.claude/commands/handoff.md'] = `# /handoff\n\nLoad the current project's context.\n\n\`\`\`\n!leerness handoff .\n\`\`\`\n`;
|
|
767
|
+
_files['.claude/commands/session-close.md'] = `# /session-close\n\nGenerate the end-of-session report and update session-handoff.md.\n\n\`\`\`\n!leerness session close .\n\`\`\`\n`;
|
|
768
|
+
_files['.claude/commands/audit.md'] = `# /audit\n\nCheck plan-progress alignment, design/reuse consistency, secrets, and encoding in one pass.\n\n\`\`\`\n!leerness audit .\n!leerness scan secrets .\n!leerness encoding check .\n\`\`\`\n`;
|
|
769
|
+
_files['.claude/commands/lazy-detect.md'] = `# /lazy-detect\n\nRun the lazy-work (false-done) automatic evaluation.\n\n\`\`\`\n!leerness lazy detect .\n\`\`\`\n`;
|
|
770
|
+
_files['.claude/commands/update.md'] = `# /update\n\nRun the leerness auto-update (detect → migrate → verify).\n\n\`\`\`\n!leerness update --yes\n\`\`\`\n`;
|
|
771
|
+
_files['.claude/skills/leerness.md'] = `---\nname: leerness\ndescription: Leerness harness commands - handoff, audit, scan secrets, encoding check, lazy detect, session close, update. Use when the user asks to load project context, verify work quality, scan secrets, check encoding, or end a session.\n---\n\n# leerness skill\n\n## When to use\n- When the user asks to load project context\n- When self-verification is requested before claiming completion\n- When ending a session or asked for a handoff\n- When secret/encoding checks are requested\n- When applying a new leerness version\n\n## Commands\n\n\`\`\`bash\nleerness handoff . # load context\nleerness check . # pre-action check\nleerness audit . # consistency/plan-alignment audit\nleerness scan secrets . # secret pattern scan\nleerness encoding check . # UTF-8/BOM/NUL\nleerness lazy detect . # lazy-work evaluation\nleerness memory search "key" # search decisions/history\nleerness session close . # close report + auto handoff\nleerness update --yes # auto-update\n\`\`\`\n`;
|
|
670
772
|
// 1.36.61 (F-05 3회차): .harness 정책·시드 문서군 en 완역 — 지시 5종(1.36.59)에 이어 read-order 가 가리키는 참조층.
|
|
671
773
|
_files['.harness/project-brief.md'] = fm('project-brief', ['confirming project purpose', 'judging new features', 'planning'], ['project purpose changes', 'users/scope change'], `# Project Brief\n\n## Project\n${project}\n\n## Purpose\n- Update this with the real purpose of the project.\n\n## Users\n-\n\n## Success Criteria\n-\n`, 'en');
|
|
672
774
|
_files['.harness/plan.md'] = fm('plan', ['before starting work', 'new request received', 'scope change', 'new project detected'], ['plan add/update/drop', 'milestone changes', 'goal changes'], `# Plan\n\n## Goal\n- Maintain the overall plan around the user's purpose.\n\n## Scope\n- Record what is in scope.\n\n## Out of Scope / Dropped\n| ID | Item | Reason | Date |\n|---|---|---|---|\n\n## Milestones\n\n### M-0001. Organize the project plan\nStatus: planned\nProgress: 0%\n\nTasks:\n- [ ] Fill project-brief.md with the real project purpose\n- [ ] Fill context-map.md with the real file structure\n`, 'en');
|
|
@@ -845,7 +947,8 @@ function syncReadme(root) {
|
|
|
845
947
|
const p = path.join(root, 'README.md');
|
|
846
948
|
const existing = exists(p) ? read(p) : '';
|
|
847
949
|
// 1.9.40: 자체 README도 동기화 — version 배지, e2e 카운트, package.json#version 일관성
|
|
848
|
-
|
|
950
|
+
const _rmLang = (() => { try { return read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase() === 'en' ? 'en' : 'ko'; } catch { return 'ko'; } })(); // 1.36.63 (F-05 5회차)
|
|
951
|
+
let updated = mergeReadmeSection(existing, managedReadmeBlock(detectProjectName(root), _rmLang));
|
|
849
952
|
try {
|
|
850
953
|
// package.json#version 또는 .harness/HARNESS_VERSION을 참조하여 README 배지 자동 갱신
|
|
851
954
|
const pkgPath = path.join(root, 'package.json');
|
|
@@ -1042,6 +1145,7 @@ async function install(root, opts = {}) {
|
|
|
1042
1145
|
// (사용자 상태 문서 plan/brief/decisions 등은 제외 — 구조-인지 전략 필요, 데이터라 raw 교체 금지)
|
|
1043
1146
|
'.harness/guideline.md','.harness/plan-progress-boundary.md','.harness/guardrails.md',
|
|
1044
1147
|
'.harness/consistency-policy.md','.harness/testing-strategy.md','.harness/review-checklist.md',
|
|
1148
|
+
'.harness/AX_PLAN_GUIDE.md','.harness/test-evidence-policy.md', // 1.36.62 (검수 #3): 누락 편입 (review-evidence/feature-graph 는 사용자 데이터 — _USER_STATE)
|
|
1045
1149
|
'.harness/release-checklist.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md',
|
|
1046
1150
|
'.harness/secret-policy.md','.harness/encoding-policy.md','.harness/language-policy.md',
|
|
1047
1151
|
'.harness/HARNESS_VERSION','.harness/manifest.json','.harness/LANGUAGE','.harness/skills-lock.json',
|
|
@@ -1067,7 +1171,7 @@ async function install(root, opts = {}) {
|
|
|
1067
1171
|
let _done = 0;
|
|
1068
1172
|
// 1.36.33 (codex 3차 #1, P1 데이터 유실): --force 가 사용자 상태 파일(진행 task/plan/결정/로그/인계 등)까지 새 템플릿으로
|
|
1069
1173
|
// 덮어써 활성 기록이 archive 에만 남았다. force 는 가이드/템플릿 문서에만 적용 — 상태 파일은 force 여도 보존.
|
|
1070
|
-
const _USER_STATE = new Set(['.harness/progress-tracker.md', '.harness/plan.md', '.harness/task-log.md', '.harness/decisions.md', '.harness/current-state.md', '.harness/session-handoff.md', '.harness/rules.md', '.harness/feature-graph.md', '.harness/reuse-map.md', '.harness/feature-contracts.md', '.harness/project-brief.md', '.harness/architecture.md', '.harness/context-map.md', '.harness/design-system.md']);
|
|
1174
|
+
const _USER_STATE = new Set(['.harness/progress-tracker.md', '.harness/plan.md', '.harness/task-log.md', '.harness/decisions.md', '.harness/current-state.md', '.harness/session-handoff.md', '.harness/rules.md', '.harness/feature-graph.md', '.harness/reuse-map.md', '.harness/feature-contracts.md', '.harness/project-brief.md', '.harness/architecture.md', '.harness/context-map.md', '.harness/design-system.md', '.harness/review-evidence.md']); // 1.36.62 (검수 #3): 검증 이력도 사용자 데이터
|
|
1071
1175
|
for (const [f, c] of Object.entries(files)) {
|
|
1072
1176
|
const existsNow = exists(path.join(root, f));
|
|
1073
1177
|
const mergeManaged = managedOverwrite.has(f);
|
|
@@ -1237,7 +1341,8 @@ async function install(root, opts = {}) {
|
|
|
1237
1341
|
const linked = rows.some(r => /M-0001/.test(r.evidence));
|
|
1238
1342
|
if (!linked) {
|
|
1239
1343
|
const tid = nextId(root, 'T');
|
|
1240
|
-
|
|
1344
|
+
const _en0 = _uiLang(root) === 'en'; // 1.36.63: 시드 task 도 프로젝트 언어
|
|
1345
|
+
upsertProgress(root, { id: tid, status: 'planned', request: _en0 ? 'Organize the project plan' : '프로젝트 계획 정리', evidence: 'init default plan:M-0001', nextAction: _en0 ? 'Fill project-brief.md with the real purpose' : 'project-brief.md를 실제 목적으로 업데이트' });
|
|
1241
1346
|
}
|
|
1242
1347
|
}
|
|
1243
1348
|
} catch {}
|
|
@@ -3036,7 +3141,7 @@ function _selfTestCases() {
|
|
|
3036
3141
|
{ name: '_cliBootstrap: CLI 부작용 require.main 가드 격리 (Codex #4 UR-0037 1.9.290)', run: () => { if (typeof _cliBootstrap !== 'function' || typeof _ensureStdoutEncoding !== 'function') return false; const src = read(__filename); const guarded = /if \(require\.main === module\) _cliBootstrap\(\);/.test(src); const inFn = /function _cliBootstrap\(\)\s*\{[\s\S]*?removeAllListeners\('warning'\)[\s\S]*?NODE_OPTIONS[\s\S]*?_ensureStdoutEncoding\(\);[\s\S]*?\n\}/.test(src); const noTopIife = !/\}\)\(\);\s*\n\n\/\/ 1\.9\.184/.test(src); return guarded && inFn && noTopIife; } },
|
|
3037
3142
|
{ name: 'lib/agent-registry: EXTERNAL_AGENTS/AGENT_SLASH_COMMANDS 모듈 단일출처 분리 (UR-0025 1.9.291)', run: () => { const m = require('../lib/agent-registry'); return m.EXTERNAL_AGENTS === EXTERNAL_AGENTS && m.AGENT_SLASH_COMMANDS === AGENT_SLASH_COMMANDS && m.EXTERNAL_AGENTS.length === 10 && Object.keys(m.AGENT_SLASH_COMMANDS).length === 9 && !/const EXTERNAL_AGENTS = \[/.test(read(__filename)); } },
|
|
3038
3143
|
{ name: 'get_project_context: MCP 시맨틱 verb 등록 + CLI context 디스패치 (UR-0031 1.9.292)', run: () => { const src = read(__filename); const mcpDef = require('../lib/mcp-tools').some(t => t.name === 'leerness_get_project_context'); const mcpCase = /case 'leerness_get_project_context':[\s\S]*?cliArgs = \['context'/.test(src); const cliDisp = /if \(cmd === 'context'\)\s+return contextCmd/.test(src); return typeof contextCmd === 'function' && mcpDef && mcpCase && cliDisp && _mcpToolCount() >= 80; } },
|
|
3039
|
-
{ name: '_canonicalProgressHeader + idempotency auto-fix (근본 복제버그 fix 1.9.293)', run: () => { const h = _canonicalProgressHeader(); const headerOk = /leernessRole: progress-tracker/.test(h) && /\| ID \| Status \| Request \|/.test(h) && /\|---\|/.test(h); const src = read(__filename); const fnOk = typeof _autoFixIdempotency === 'function'; const noWholeTextFallback =
|
|
3144
|
+
{ name: '_canonicalProgressHeader + idempotency auto-fix (근본 복제버그 fix 1.9.293)', run: () => { const h = _canonicalProgressHeader(); const headerOk = /leernessRole: progress-tracker/.test(h) && /\| ID \| Status \| Request \|/.test(h) && /\|---\|/.test(h); const src = read(__filename); const fnOk = typeof _autoFixIdempotency === 'function'; const noWholeTextFallback = src.includes('return _canonicalProgressHeader(_phLang);') && !/if \(idx < 0\) return text;/.test(src); const driftWired = /_autoFixIdempotency\(root\)/.test(src) && /idempotency 중복/.test(src); return headerOk && fnOk && noWholeTextFallback && driftWired; } },
|
|
3040
3145
|
{ name: 'lib/role-catalog: ROLE/PROVIDER/ALIASES/PROMPTS 모듈 단일출처 분리 (UR-0025 1.9.294)', run: () => { const m = require('../lib/role-catalog'); return m.ROLE_CATALOG === ROLE_CATALOG && m._PROVIDER_MODEL_CATALOG === _PROVIDER_MODEL_CATALOG && m._ROLE_ALIASES === _ROLE_ALIASES && m._AGENT_ROLE_PROMPTS === _AGENT_ROLE_PROMPTS && Object.keys(m.ROLE_CATALOG).length === 7 && Object.keys(m._PROVIDER_MODEL_CATALOG).length === 10 && !/const ROLE_CATALOG = \{/.test(read(__filename)); } },
|
|
3041
3146
|
{ name: 'lib/catalogs: CAPABILITY/ADAPTERS/REUSE 모듈 단일출처 분리 (UR-0025 1.9.295) + i18n en(1.31.3)', run: () => { const m = require('../lib/catalogs'); const _H = /[가-힣]/; const cs = Object.values(m.CAPABILITY_SURFACE); const i18nOk = cs.length === 6 && cs.every(v => typeof v.descEn === 'string' && v.descEn.length > 0 && !_H.test(v.descEn) && typeof v.optOutEn === 'string' && !_H.test(v.optOutEn)) && m.POWERFUL_COMMANDS.every(c => typeof c.noteEn === 'string' && c.noteEn.length > 0 && !_H.test(c.noteEn)); return m.CAPABILITY_SURFACE === CAPABILITY_SURFACE && m.ADAPTERS === ADAPTERS && m.REUSE_CATEGORIES === REUSE_CATEGORIES && m.REUSE_CHECKLIST === REUSE_CHECKLIST && m.POWERFUL_COMMANDS === POWERFUL_COMMANDS && Object.keys(m.CAPABILITY_SURFACE).length === 6 && i18nOk && !/const CAPABILITY_SURFACE = \{/.test(read(__filename)); } },
|
|
3042
3147
|
{ name: 'about: 정체성 verb(AI 운영 레이어) + MCP leerness_about 등록 (UR-0030 1.9.296)', run: () => { const id = _leernessIdentity(); const src = read(__filename); return typeof aboutCmd === 'function' && /운영 레이어/.test(id.identity) && id.layers.length === 5 && id.surface.mcpTools >= 81 && require('../lib/mcp-tools').some(t => t.name === 'leerness_about') && /case 'leerness_about':/.test(src) && /cmd === 'about' \|\| cmd === 'identity'/.test(src); } },
|
|
@@ -3720,7 +3825,7 @@ function _selfTestCases() {
|
|
|
3720
3825
|
{ name: 'UR-0083(4th외부평가 9.3): auto-update hook 비침투 (update --quiet 모드 + hook --check --quiet + 업그레이드)', run: () => { const src = read(__filename); const quietMode = /const quiet = !!opts\.quiet \|\| has\('--quiet'\)/.test(src); const hookQuiet = src.includes("command: 'leerness update --check --quiet'"); const upgrade = /includes\('leerness update --check'\) && !h\.command\.includes\('--quiet'\)/.test(src); return quietMode && hookQuiet && upgrade; } },
|
|
3721
3826
|
{ name: 'CV-6/UR-0081: 시크릿 스캐너 FP/FN — _isPlaceholderSecret + _looksSecretLike 행위', run: () => { if (typeof _isPlaceholderSecret !== 'function' || typeof _looksSecretLike !== 'function') return false; const fp = _isPlaceholderSecret('change-me') && _isPlaceholderSecret('your-api-key-here') && _isPlaceholderSecret('<token>') && _isPlaceholderSecret('') && !_isPlaceholderSecret('hunter2realpass'); const fn = _looksSecretLike('secret123') && _looksSecretLike('a'.repeat(24)) && !_looksSecretLike('processEnv') && !_looksSecretLike('reqBodyPassword'); return fp && fn; } },
|
|
3722
3827
|
{ name: 'UR-0025: _mergeLines/_mergeEnvLines 순수 코어 모듈 분리 + 행위 (1.9.367)', run: () => { if (typeof _mergeLines !== 'function' || typeof _mergeEnvLines !== 'function') return false; const m = require('../lib/pure-utils'); const moved = m._mergeLines === _mergeLines && m._mergeEnvLines === _mergeEnvLines; const ml = _mergeLines('a\n', ['a', 'b']) === 'a\nb\n'; const meKeep = _mergeEnvLines('FOO=keep\n', ['FOO=new']) === 'FOO=keep\n'; const meAdd = _mergeEnvLines('FOO=keep\n', ['BAR=add']).includes('BAR=add'); return moved && ml && meKeep && meAdd; } },
|
|
3723
|
-
{ name: 'UR-0025: _mergeReadmeSection/_managedMerge + MERGE_OVERWRITE_FILES 모듈 분리 + 행위 (1.9.368)', run: () => { const m = require('../lib/pure-utils'); const c = require('../lib/catalogs'); if (typeof _mergeReadmeSection !== 'function' || typeof _managedMerge !== 'function') return false; const moved = m._mergeReadmeSection === _mergeReadmeSection && m._managedMerge === _managedMerge && MERGE_OVERWRITE_FILES === c.MERGE_OVERWRITE_FILES; const rd = _mergeReadmeSection('', 'BLOCK', '<s>', '<e>') === '# Project\n\nBLOCK'; const mm = _managedMerge('a.md', 'NEW', 'OLD', '.h', new Set()).includes('migration-preserved'); const ow = _managedMerge('.harness/manifest.json', 'NEW', 'OLD', '.h', c.MERGE_OVERWRITE_FILES) === 'NEW'; const same = _managedMerge('a.md', 'X', 'X', '.h', new Set()) === 'X'; return moved && rd && mm && ow && same; } },
|
|
3828
|
+
{ name: 'UR-0025: _mergeReadmeSection/_managedMerge + MERGE_OVERWRITE_FILES 모듈 분리 + 행위 (1.9.368)', run: () => { const m = require('../lib/pure-utils'); const c = require('../lib/catalogs'); if (typeof _mergeReadmeSection !== 'function' || typeof _managedMerge !== 'function') return false; const moved = m._mergeReadmeSection === _mergeReadmeSection && m._managedMerge === _managedMerge && MERGE_OVERWRITE_FILES === c.MERGE_OVERWRITE_FILES; const rd = _mergeReadmeSection('', 'BLOCK', '<s>', '<e>') === '# Project\n\nBLOCK\n'; const mm = _managedMerge('a.md', 'NEW', 'OLD', '.h', new Set()).includes('migration-preserved'); const ow = _managedMerge('.harness/manifest.json', 'NEW', 'OLD', '.h', c.MERGE_OVERWRITE_FILES) === 'NEW'; const same = _managedMerge('a.md', 'X', 'X', '.h', new Set()) === 'X'; return moved && rd && mm && ow && same; } },
|
|
3724
3829
|
{ name: 'UR-0025: _parseSkillsValue(catalog 주입) + MINIMAL_SKIP_KEYS 모듈 분리 + 행위 (1.9.369)', run: () => { const m = require('../lib/pure-utils'); const c = require('../lib/catalogs'); if (typeof _parseSkillsValue !== 'function') return false; const moved = m._parseSkillsValue === _parseSkillsValue && MINIMAL_SKIP_KEYS === c.MINIMAL_SKIP_KEYS; const cat = { office: {}, foo: {} }; const empty = _parseSkillsValue('', cat).length === 0; const all = _parseSkillsValue('all', cat).length === 2; const rec = _parseSkillsValue('recommended', cat).includes('office'); const csv = JSON.stringify(_parseSkillsValue('office,bar', cat)) === JSON.stringify(['office']); return moved && empty && all && rec && csv && MINIMAL_SKIP_KEYS.has('.claude/skills/leerness.md'); } },
|
|
3725
3830
|
{ name: 'UR-0025: _parseArchiveBlocks/_parseSkillCatalog 순수 파서 모듈 분리 + 행위 (1.9.370)', run: () => { const m = require('../lib/pure-utils'); if (typeof _parseArchiveBlocks !== 'function' || typeof _parseSkillCatalog !== 'function') return false; const moved = m._parseArchiveBlocks === _parseArchiveBlocks && m._parseSkillCatalog === _parseSkillCatalog; const ab = _parseArchiveBlocks('## 제거 2026-01-01 (target: ' + '"T-1")\n### 헤더\n'); const abOk = ab.length === 1 && ab[0].date === '2026-01-01' && ab[0].target === 'T-1' && ab[0].originalHeader === '헤더'; const md = _parseSkillCatalog('- [nm](https://x/SKILL.md) — d', ''); const mdOk = md.length === 1 && md[0].name === 'nm' && md[0].format === 'markdown'; const js = _parseSkillCatalog('{' + '"skills":[{"id":"a","url":"u"}]}', ''); const jsOk = js.length === 1 && js[0].name === 'a' && js[0].format === 'json'; return moved && abOk && mdOk && jsOk; } },
|
|
3726
3831
|
{ name: 'UR-0073 Phase A: team 정의 레지스트리 (_renderTeamsMd + canonical load/save) 행위 (1.9.371)', run: () => { const m = require('../lib/pure-utils'); if (typeof teamCmd !== 'function' || typeof _renderTeamsMd !== 'function' || m._renderTeamsMd !== _renderTeamsMd) return false; const md = _renderTeamsMd([{ id: 't1', name: 'N', personas: ['security'], members: ['claude'], schedule: 'daily', status: 'active' }]); const mdOk = md.includes('## t1') && md.includes('security') && md.includes('daily') && md.includes('정의 전용'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_team_')); let rtOk = false; try { _saveTeams(tmp, [{ id: 'x', name: 'X', personas: [], members: [], schedule: 'manual', status: 'active' }]); const loaded = _loadTeams(tmp); rtOk = loaded.length === 1 && loaded[0].id === 'x' && fs.existsSync(path.join(tmp, '.harness', 'teams.json')) && fs.existsSync(path.join(tmp, '.harness', 'teams.md')); } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } return mdOk && rtOk; } },
|
|
@@ -8924,7 +9029,12 @@ function readProgressRows(root) {
|
|
|
8924
9029
|
return rows;
|
|
8925
9030
|
}
|
|
8926
9031
|
// 1.9.293: progress-tracker 정식 헤더 (frontmatter + 표 헤더 + 분리자). coreFiles 와 단일 출처.
|
|
8927
|
-
function _canonicalProgressHeader() {
|
|
9032
|
+
function _canonicalProgressHeader(lang = 'ko') {
|
|
9033
|
+
// 1.36.63 (F-05 5회차): en frontmatter — 표 헤더/파서는 원래 영어·위치 기반이라 동작 무변
|
|
9034
|
+
if (lang === 'en') {
|
|
9035
|
+
return fm('progress-tracker', ['session start', 'session close', 'checking request status'], ['task status changes', 'verification results added', 'user request dropped'],
|
|
9036
|
+
`# Progress Tracker\n\nStatus values: requested, planned, in-progress, waiting, on-hold, blocked, incomplete, done, dropped\n\n| ID | Status | Request | Evidence | Next Action | Updated |\n|---|---|---|---|---|---|\n`, 'en').trimEnd();
|
|
9037
|
+
}
|
|
8928
9038
|
return fm('progress-tracker', ['세션 시작', '세션 종료', '사용자 요청 상태 확인'], ['작업 상태 변경', '검증 결과 추가', '사용자 요청 드랍'],
|
|
8929
9039
|
`# Progress Tracker\n\nStatus values: requested, planned, in-progress, waiting, on-hold, blocked, incomplete, done, dropped\n\n| ID | Status | Request | Evidence | Next Action | Updated |\n|---|---|---|---|---|---|\n`).trimEnd();
|
|
8930
9040
|
}
|
|
@@ -8933,7 +9043,11 @@ function progressHeader(root) {
|
|
|
8933
9043
|
const idx = text.indexOf('|---|');
|
|
8934
9044
|
// 1.9.293 (근본 버그 fix): 분리자 없음(손상/헤더유실) 시 전체 텍스트 반환은 writeProgressRows 가 행을 복제 →
|
|
8935
9045
|
// 세션마다 중복 누적의 원인이었음. 정식 헤더를 재구성해 반환 → 다음 write 시 자동 복구.
|
|
8936
|
-
if (idx < 0)
|
|
9046
|
+
if (idx < 0) {
|
|
9047
|
+
// 1.36.63 (검수 #1): 손상 헤더 복구도 프로젝트 언어로 — en 프로젝트가 ko frontmatter 로 회귀하지 않게
|
|
9048
|
+
let _phLang = 'ko'; try { _phLang = read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase() === 'en' ? 'en' : 'ko'; } catch {}
|
|
9049
|
+
return _canonicalProgressHeader(_phLang);
|
|
9050
|
+
}
|
|
8937
9051
|
return text.slice(0, text.indexOf('\n', idx)).trimEnd();
|
|
8938
9052
|
}
|
|
8939
9053
|
function writeProgressRows(root, header, rows) {
|
|
@@ -9023,9 +9137,9 @@ function planListCmd(root, opts = {}) {
|
|
|
9023
9137
|
function planAdd(root, text) {
|
|
9024
9138
|
if (!_requireInit(root, 'plan add')) return; // 1.9.311 (UR-0047): init 가드
|
|
9025
9139
|
if (!_validateChoice(arg('--status', null), TASK_STATUSES, 'plan status')) { process.exitCode = 1; return; } // 1.9.310 (UR-0046)
|
|
9026
|
-
const status = arg('--status','planned'), progress = arg('--progress','0'), nextAction = arg('--next', '다음 액션 작성');
|
|
9140
|
+
const status = arg('--status','planned'), progress = arg('--progress','0'), nextAction = arg('--next', _uiLang(root) === 'en' ? 'write the next action' : '다음 액션 작성'); // 1.36.63
|
|
9027
9141
|
// 1.14.2 (Karpathy 원칙4 "성공기준 정의", UR-0032): --done-when 으로 검증가능 완료조건을 milestone 에 기록. 미지정 시 (미정) — plan show/audit 가 환기.
|
|
9028
|
-
const doneWhen = _lineSafe(arg('--done-when', '') || '(미정)');
|
|
9142
|
+
const doneWhen = _lineSafe(arg('--done-when', '') || (_uiLang(root) === 'en' ? '(unset)' : '(미정)')); // 1.36.63 (검수 #2)
|
|
9029
9143
|
// 1.9.303 (UR-0043): M-id append + T-id upsert 를 하나의 락으로 — 동시 plan add ID 충돌 방지.
|
|
9030
9144
|
const { id, tid } = _withLock(progressPath(root), () => {
|
|
9031
9145
|
const id = nextId(root, 'M');
|
|
@@ -9041,7 +9155,7 @@ function planAdd(root, text) {
|
|
|
9041
9155
|
}
|
|
9042
9156
|
function planDrop(root, text) {
|
|
9043
9157
|
const id = nextId(root, 'D');
|
|
9044
|
-
const reason = arg('--reason', '사용자 요청으로 제외');
|
|
9158
|
+
const reason = arg('--reason', _uiLang(root) === 'en' ? 'dropped by user request' : '사용자 요청으로 제외'); // 1.36.63 (검수 #2)
|
|
9045
9159
|
// 1.10.4 (13th 버그헌트 P3, UR-0170): text/reason 의 파이프(|)·개행이 plan.md 마크다운 표 칼럼을 깨뜨림 → _cellSafe 셀 안전화(task/rule UR-0104 와 동일).
|
|
9046
9160
|
const safeText = _cellSafe(text); const safeReason = _cellSafe(reason);
|
|
9047
9161
|
const planFile = planPath(root); let p = exists(planFile) ? read(planFile) : '';
|
|
@@ -9209,7 +9323,7 @@ function taskAdd(root, text) {
|
|
|
9209
9323
|
// 1.9.303 (UR-0043): ID 할당 + write 를 하나의 락으로 — 동시 task add 의 ID 충돌(lost-update) 방지.
|
|
9210
9324
|
const id = _withLock(progressPath(root), () => {
|
|
9211
9325
|
const newId = nextId(root, 'T');
|
|
9212
|
-
upsertProgress(root, { id: newId, status: _normTaskStatus(arg('--status','requested')), request: text, evidence: arg('--evidence','user-request'), nextAction: arg('--next','다음 액션 작성') });
|
|
9326
|
+
upsertProgress(root, { id: newId, status: _normTaskStatus(arg('--status','requested')), request: text, evidence: arg('--evidence','user-request'), nextAction: arg('--next', _uiLang(root) === 'en' ? 'write the next action' : '다음 액션 작성') }); // 1.36.63: 기본값 언어 인지
|
|
9213
9327
|
return newId;
|
|
9214
9328
|
});
|
|
9215
9329
|
// 1.9.413 (6th외부평가 codex P2, UR-0101): --json 시 구조화 출력(코어 데이터는 이미 영속). 사람용 ok + cosmetic roadmap/interactive review 는 스킵.
|
|
@@ -9305,7 +9419,8 @@ function taskDrop(root, id) {
|
|
|
9305
9419
|
const rows = readProgressRows(root);
|
|
9306
9420
|
// 1.9.396 (6번째 외부평가/codex P1-B): 없는 task drop 시 가짜 row(request undefined) 생성 = 데이터 손상 → task update 와 동일하게 존재 확인 후 fail(no-op).
|
|
9307
9421
|
if (!rows.find(r => r.id === id)) { failJson(has('--json'), 'task_not_found', `task ${id} not found in progress-tracker.md`); return; } // codex P2: --json 에러 구조화
|
|
9308
|
-
|
|
9422
|
+
const _tdEn = _uiLang(root) === 'en'; // 1.36.63 (검수 #2)
|
|
9423
|
+
upsertProgress(root, { id, status: 'dropped', evidence: arg('--reason', _tdEn ? 'dropped by user request' : '사용자 요청으로 제외'), nextAction: _tdEn ? 'none' : '없음' });
|
|
9309
9424
|
ok(`task dropped: ${id}`);
|
|
9310
9425
|
_autoRoadmap(absRoot(root), 'data-change');
|
|
9311
9426
|
}
|
|
@@ -10251,7 +10366,7 @@ function lazyDetect(root, opts = {}) {
|
|
|
10251
10366
|
}
|
|
10252
10367
|
// 1.35.16 (lazy 헌트 우회 #9): status 대소문자무관(Blocked) + nextAction trivial 토큰(빈값/-/n/a/none/tbd/점만) 도 blocker_no_next_action 으로 감지(기존 '없음'/'다음 액션 작성' 만).
|
|
10253
10368
|
const blockers = rows.filter(r => /^blocked$/i.test(r.status || ''));
|
|
10254
|
-
for (const b of blockers) { const _na = (b.nextAction || '').trim(); if (!_na || _na === '없음' || /다음 액션
|
|
10369
|
+
for (const b of blockers) { const _na = (b.nextAction || '').trim(); if (!_na || _na === '없음' || /다음 액션 작성|write the next action/i.test(b.nextAction) || /^(-+|n\/?a|none|nil|tbd|todo|wip|x|\.+|\?+)$/i.test(_na)) { // 1.36.63: en 기본값도 trivial 인식
|
|
10255
10370
|
issues++; _warn(`blocker without nextAction: ${b.id}`,
|
|
10256
10371
|
{ kind: 'blocker_no_next_action', severity: 'warn', taskId: b.id });
|
|
10257
10372
|
} }
|
|
@@ -16047,7 +16162,20 @@ function rulesPath(root) { return path.join(root, '.harness/rules.md'); }
|
|
|
16047
16162
|
function rulesArchivePath(root) { return path.join(root, '.harness/rules.archive.md'); }
|
|
16048
16163
|
function rulesCachePath(root) { return path.join(root, '.harness/cache/rule-state.json'); }
|
|
16049
16164
|
|
|
16050
|
-
function _rulesHeader() {
|
|
16165
|
+
function _rulesHeader(lang = 'ko') {
|
|
16166
|
+
// 1.36.63 (F-05 5회차): en 프로젝트 rules 헤더 영어 — 파서(readRules)는 '| R-' 라인앵커+위치 기반이라 라벨 무관
|
|
16167
|
+
if (lang === 'en') {
|
|
16168
|
+
return [
|
|
16169
|
+
'---', 'leernessRole: rules', 'readWhen:', ' - session start (handoff)', ' - before every task', ' - before completing every task', ' - session close',
|
|
16170
|
+
'updateWhen:', ' - user states a new natural-language rule', ' - user asks to pause/remove a rule',
|
|
16171
|
+
'doNotStore:', ' - real tokens', ' - passwords', ' - production cookies', ' - raw sensitive personal data', '---',
|
|
16172
|
+
'<!-- leerness:managed -->', '# User Rules', '',
|
|
16173
|
+
'Standing user-defined rules the AI agent must follow every session and every task.',
|
|
16174
|
+
'All active rules are auto-surfaced and verified each session until the user explicitly says stop/remove.',
|
|
16175
|
+
'', '## Active Rules', '',
|
|
16176
|
+
'| ID | Trigger | Rule | Added | Status | Last Verified |', '|---|---|---|---|---|---|'
|
|
16177
|
+
].join('\n');
|
|
16178
|
+
}
|
|
16051
16179
|
return [
|
|
16052
16180
|
'---',
|
|
16053
16181
|
'leernessRole: rules',
|
|
@@ -16113,7 +16241,9 @@ function writeRules(root, rules) {
|
|
|
16113
16241
|
// 1.36.38 (#1): 파싱 불가 행은 무언 삭제 대신 원문 보존 + 경고 표식 (수동 복구 대상)
|
|
16114
16242
|
const damaged = _unparsableRuleLines(root).filter(l => !rules.some(r => l.startsWith(`| ${r.id} `)));
|
|
16115
16243
|
const tail = damaged.length ? '\n<!-- ⚠ 아래 행은 손상돼 파싱 불가 — 삭제하지 않고 보존함. 수동 복구 후 위 표로 병합하세요 -->\n' + damaged.join('\n') + '\n' : '';
|
|
16116
|
-
|
|
16244
|
+
// 1.36.63: 저장 시 헤더 언어 = 프로젝트 언어 (en rules.md 가 rule add 로 ko 회귀하지 않게)
|
|
16245
|
+
let _rlLang = 'ko'; try { _rlLang = read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase() === 'en' ? 'en' : 'ko'; } catch {}
|
|
16246
|
+
writeUtf8(rulesPath(root), _rulesHeader(_rlLang) + '\n' + body + (body ? '\n' : '') + tail);
|
|
16117
16247
|
}
|
|
16118
16248
|
|
|
16119
16249
|
function nextRuleId(root) {
|
|
@@ -16994,7 +17124,7 @@ function verifyCodeCmd(root) {
|
|
|
16994
17124
|
}
|
|
16995
17125
|
const evBlock = [
|
|
16996
17126
|
``,
|
|
16997
|
-
`## ${now().slice(0, 16)} verify-code (자동)`,
|
|
17127
|
+
`## ${now().slice(0, 16)} verify-code ${_uiLang(root) === 'en' ? '(auto)' : '(자동)'}`, // 1.36.62 (검수 #4): en 프로젝트 증거 마커 영어 (파서 비의존, 표기용)
|
|
16998
17128
|
`Command: leerness verify-code`,
|
|
16999
17129
|
`Tasks: ${tasks.map(t => t.name).join(', ')}`,
|
|
17000
17130
|
...results.map(r => `- ${r.name}: exit=${r.exit} (${r.durMs}ms) — \`${r.cmd}\``),
|
|
@@ -17128,7 +17258,14 @@ function _ensureFeatureGraph(root) {
|
|
|
17128
17258
|
}
|
|
17129
17259
|
function _writeFeatureGraph(root, nodes) {
|
|
17130
17260
|
const p = _ensureFeatureGraph(root);
|
|
17131
|
-
|
|
17261
|
+
// 1.36.62 (검수 #2): 기존 파일의 서두(frontmatter+가이드, en 포함)를 '## Nodes' 까지 보존 —
|
|
17262
|
+
// 종전엔 매 저장마다 한국어 템플릿으로 전체 재생성해 en 그래프가 한국어로 회귀하고 frontmatter 가 사라졌다.
|
|
17263
|
+
let header = _featureGraphTemplate();
|
|
17264
|
+
try {
|
|
17265
|
+
const cur = read(p);
|
|
17266
|
+
const i = cur.indexOf('## Nodes');
|
|
17267
|
+
if (i >= 0) header = cur.slice(0, i + '## Nodes'.length) + '\n\n';
|
|
17268
|
+
} catch {}
|
|
17132
17269
|
const body = nodes.map(_featureBlock).join('');
|
|
17133
17270
|
writeUtf8(p, header + body);
|
|
17134
17271
|
}
|
|
@@ -17534,8 +17671,11 @@ function autoUpdateInstall(root) {
|
|
|
17534
17671
|
settings.hooks.SessionStart.push({ matcher: 'startup|clear|compact', command: 'leerness hook session-start' });
|
|
17535
17672
|
}
|
|
17536
17673
|
writeUtf8(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
17537
|
-
|
|
17538
|
-
|
|
17674
|
+
// 1.36.62 (검수 #1): en 프로젝트의 /update 를 한국어로 되덮던 것 — 언어 인지 렌더
|
|
17675
|
+
const _updLang = (() => { try { return read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase(); } catch { return 'ko'; } })();
|
|
17676
|
+
writeUtf8(path.join(root, '.claude/commands/update.md'), _updLang === 'en'
|
|
17677
|
+
? `# /update\n\nRun the leerness auto-update (detect → migrate → verify).\n\n\`\`\`\n!leerness update --yes\n\`\`\`\n\nCheck only:\n\n\`\`\`\n!leerness update --check\n\`\`\`\n`
|
|
17678
|
+
: `# /update\n\nleerness 자동 업데이트 (감지 → 마이그레이션 → 검증).\n\n\`\`\`\n!leerness update --yes\n\`\`\`\n\n체크만:\n\n\`\`\`\n!leerness update --check\n\`\`\`\n`);
|
|
17539
17679
|
ok('auto-update SessionStart hook installed (.claude/settings.local.json)');
|
|
17540
17680
|
if (removedLegacy) ok(`legacy hook 제거: ${removedLegacy}건 (leerness-plus → leerness 통합)`);
|
|
17541
17681
|
if (upgradedQuiet) ok(`hook 업그레이드: ${upgradedQuiet}건 → --quiet (up-to-date 시 무음, 1.9.364)`);
|
package/lib/pure-utils.js
CHANGED
|
@@ -758,10 +758,12 @@ function _mergeEnvLines(currentText, lines) {
|
|
|
758
758
|
|
|
759
759
|
// 1.9.368 (UR-0025): README 관리섹션 머지 순수 코어 — 마커 사이 교체, 없으면 append. 마커는 인자로 주입(harness 상수 비결합).
|
|
760
760
|
function _mergeReadmeSection(existing, block, START, END) {
|
|
761
|
-
|
|
761
|
+
// 1.36.63 (검수 #4): 블록 말단 개행을 정규화해 재실행 바이트 멱등 — 종전엔 1차 재init 가 빈 줄 1개를 더했다
|
|
762
|
+
block = String(block).trimEnd();
|
|
763
|
+
if (!existing) return `# Project\n\n${block}\n`;
|
|
762
764
|
const s = existing.indexOf(START); const e = existing.indexOf(END);
|
|
763
765
|
if (s >= 0 && e >= s) return existing.slice(0, s).trimEnd() + '\n\n' + block + '\n' + existing.slice(e + END.length).trimStart();
|
|
764
|
-
return existing.trimEnd() + '\n\n' + block;
|
|
766
|
+
return existing.trimEnd() + '\n\n' + block + '\n';
|
|
765
767
|
}
|
|
766
768
|
// 1.9.368 (UR-0025): 관리 파일 마이그레이션 머지 순수 코어 — 이전 내용을 migration-preserved 블록으로 보존(데이터/인덱스 파일은 overwrite).
|
|
767
769
|
// archiveRel(사전 계산된 표시 경로) + overwriteSet 을 인자로 주입 → path/process/상수 비결합(순수).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.63",
|
|
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",
|