leerness 1.36.59 → 1.36.61
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 +73 -8
- package/lib/audit.js +3 -3
- package/lib/pure-utils.js +20 -5
- package/package.json +1 -1
- package/scripts/e2e.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.61 — 2026-07-21 — .harness 정책·시드 문서군 en 완역 (F-05 3회차) — R-0001 검수 6회전
|
|
4
|
+
|
|
5
|
+
en init 한글 잔존 53/61 → **23/61** (1·3회차 누적). 검수 계약 대조: 6개 대표 문서 규칙 전량 보존 확인.
|
|
6
|
+
|
|
7
|
+
- **en 오버라이드 27종**: guideline/guardrails/anti-lazy/writeback/session-close/secret/encoding/consistency/testing/checklists/plan·brief·current-state·decisions·task-log·handoff 시드 등 — read-order 참조층 전체.
|
|
8
|
+
- **자가 선검**: en brief placeholder 문구가 감지 regex 와 미스매치(작성됨 오판) → 문구 정합 (anchors status true/true 실측).
|
|
9
|
+
- (검수 High) **정책문서 12종 managed 편입** — 언어 전환 시 구 언어로 방치되던 것 (사용자 상태 문서는 의도 제외). 실측: en→ko 전환 시 guardrails 한국어 재생성 + 커스텀 1회 + 영어 잔재 0.
|
|
10
|
+
- (검수 Medium) **en 센티널 인식**: audit/lazy detect 의 `(자동)`, design-token `(실제 값으로 업데이트)` 만 알던 감시자들에 `(auto)`/`(update with real value)` 병기 — KO/EN audit 발견 동수(5=5) 회복, --fix 양 언어.
|
|
11
|
+
- 검증: e2e en 케이스 10종 무한글로 확대, selftest 332, 검수 통과 프로브(파서 소비자 전수·integrity en 12종·전환 왕복).
|
|
12
|
+
|
|
13
|
+
## 1.36.60 — 2026-07-21 — 언어 전환 현지화 병합 (F-05 2회차, 전 검수 High 해소) — R-0001 검수 5회전
|
|
14
|
+
|
|
15
|
+
KO→EN(역방향 포함) 전환 시 지시 레이어가 새 언어로 재생성되고 사용자 커스텀만 정확히 1회 이월된다.
|
|
16
|
+
|
|
17
|
+
- **차감 병합**: _managedMerge 에 altTemplate — 구 언어 canonical 라인은 커스텀이 아니므로 미이월 (종전: KO 템플릿 통째 Preserved 이월). session-workflow.md managed 편입(전환 시에도 낡은 언어로 방치되던 것). 보존 래퍼 문구 현지화(en 프로젝트에 한글 래퍼가 새 한글원이 되던 것).
|
|
18
|
+
- **검수 2차 High 반영 — 전환-감지 게이트 + 출현-횟수 차감**: 무조건 차감은 동일-언어 재init 에서 "반대-언어 템플릿과 우연히 일치하는 정당한 커스텀"을 유실시켰다(실측) — 이전 저장 언어를 포착해 실제 전환일 때만 차감 + Set 대신 출현 1회분만 소비(복제 커스텀 생존).
|
|
19
|
+
- (검수 #2) adapter 경로에 래퍼 언어 전달(전환-감지 없는 경로라 altTemplate 은 의도적 미적용). (검수 #3) 구버전 workflow 의 자동생성 이력 로그(MCP 도구-카운트/마일스톤 불릿)는 좁은 시그니처로 필터 — 낡은 모순 잔재 방지, 사용자 산문 보존.
|
|
20
|
+
- 실측: 동일-언어 커스텀 생존 / 전환 차감 유지(잔존 한글=커스텀 1건) / 이력 필터+커스텀 보존. selftest 332(+1), 검수 통과 프로브(migrate/update 동일 경로·EN 래퍼 혼재 무중복·5종 바이트 멱등).
|
|
21
|
+
|
|
3
22
|
## 1.36.59 — 2026-07-21 — en 지시-레이어 완전 번역 (외부감사 F-05 시리즈 1회차) — R-0001 검수 4회전
|
|
4
23
|
|
|
5
24
|
실측 베이스라인: en init 산출물 61개 중 한글 잔존 53개. 1회차 = en 에이전트가 "지시"로 읽는 최고 영향 5종.
|
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.61 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -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.61는 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.59는 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.61 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,6 +235,6 @@ 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.61: 2026-07-22
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
|
240
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.61';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -667,6 +667,34 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
667
667
|
if (lang === 'en') {
|
|
668
668
|
_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
669
|
_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`;
|
|
670
|
+
// 1.36.61 (F-05 3회차): .harness 정책·시드 문서군 en 완역 — 지시 5종(1.36.59)에 이어 read-order 가 가리키는 참조층.
|
|
671
|
+
_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
|
+
_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');
|
|
673
|
+
_files['.harness/guideline.md'] = fm('guideline', ['checking quality bar before implementing', 'checking plan-execution criteria'], ['dev standards change', 'verification routine changes'], `# Guideline\n\n## Operating Principle\n- Work against the goal and scope in plan.md.\n- Judge done/not-done by request status in progress-tracker.md.\n- Never record progress percentages here — plan.md/progress-tracker.md are the single source.\n\n## Quality Gate\n- Check the relevant route before changes (\`leerness route <task-type>\`).\n- After changes run \`leerness verify\`, \`leerness audit\`, \`leerness check\`.\n- Before claiming completion run \`leerness lazy detect\`.\n- Close every session with \`leerness session close\`.\n`, 'en');
|
|
674
|
+
_files['.harness/plan-progress-boundary.md'] = fm('plan-progress-boundary', ['when plan and progress overlap', 'work-tracking structure changes'], ['role-separation criteria change'], `# Plan / Progress Boundary\n\n## plan.md\n- Overall goal, milestones, in/out of scope, plan-change history.\n\n## progress-tracker.md\n- Per-request status, evidence, next action.\n- ID rule: T-0001 monotonically increasing; IDs from plan add are unique across plan/progress.\n\n## guideline.md\n- The execution standards to follow while doing plan/progress work.\n`, 'en');
|
|
675
|
+
_files['.harness/current-state.md'] = fm('current-state', ['session start', 'taking over work'], ['current state changes', 'next work changes'], `# Current State\n\nUpdated: ${today()}\n\n## Now\n-\n\n## Next\n-\n\n## Blockers\n-\n`, 'en');
|
|
676
|
+
_files['.harness/writeback-policy.md'] = fm('writeback-policy', ['before finishing work', 'deciding what to update'], ['record targets change'], `# Writeback Policy\n\n- plan.md: user purpose, milestones, scope additions/exclusions\n- progress-tracker.md: per-request status and evidence (updated in place)\n- current-state.md: current state and next work\n- task-log.md: execution history (auto-appended)\n- session-handoff.md: next-session handoff (written by \`session close\`)\n- decisions.md: hard-to-reverse decisions\n- design-system.md: UI/UX/component standards\n- feature-contracts.md: input/output/state/error contracts\n- review-evidence.md: verification results (auto-accumulated)\n`, 'en');
|
|
677
|
+
_files['.harness/task-type-map.md'] = fm('task-type-map', ['classifying user requests'], ['task types added'], `# Task Type Map\n\n| User Request | Task Type | Route |\n|---|---|---|\n| plan this / draft a roadmap | planning | leerness route planning |\n| implement / build a feature | feature | leerness route feature |\n| fix a bug | bugfix | leerness route bugfix |\n| refactor / clean up | refactor | leerness route refactor |\n| research / compare | research | leerness route research |\n| unify design / consistency | consistency | leerness route consistency |\n| deploy / release | release | leerness route release |\n| migration | migration | leerness route migration |\n| session start / resume | session-start | leerness route session-start |\n| session end | session-close | leerness route session-close |\n`, 'en');
|
|
678
|
+
_files['.harness/protected-files.md'] = fm('protected-files', ['before deleting/cleaning/migrating files'], ['protected targets change'], `# Protected Files\n\nAI agents must not delete or reset these files without explicit user approval.\n\n- .harness/\n- .harness/skills/\n- .harness/library/\n- AGENTS.md\n- CLAUDE.md\n- .cursor/rules/leerness.mdc\n- .github/copilot-instructions.md\n- .claude/commands/\n- .claude/skills/\n- README.md Leerness managed section\n\nUse merge, archive, or deprecated markers instead of deletion.\n`, 'en');
|
|
679
|
+
_files['.harness/guardrails.md'] = fm('guardrails', ['before all work', 'before security/permissions/refactoring'], ['prohibition rules change'], `# Guardrails\n\n- Never store tokens/keys/passwords — record only environment-variable names.\n- No unrequested large refactors (user pre-approval when changing 5+ files).\n- Record the impact of API/DB/env-var changes in task-log.\n- Never delete Leerness protected files/managed sections.\n- Keep Korean text as UTF-8 without BOM.\n- Destructive Git operations (\`git reset --hard\`, \`git push --force\`, …) only after explicit user approval.\n`, 'en');
|
|
680
|
+
_files['.harness/consistency-policy.md'] = fm('consistency-policy', ['before duplicating UI/features', 'judging reuse'], ['consistency policy changes'], `# Consistency Policy\n\nBefore building something that does the same job, find and reuse/extend/connect the existing implementation.\n\n## Recursive Reuse Rule\n1. Find existing elements with the same capability.\n2. Check whether self-reference/base rules/recursive flow is needed.\n3. Reuse or extend the existing element.\n4. If you must build new, record why in reuse-map.md.\n\n## Audit Trigger\n\`leerness audit\` checks:\n- duplicate design-guide files\n- undefined design-system.md tokens\n- empty reuse-map.md with 3+ components/utils found\n- plan vs progress alignment\n`, 'en');
|
|
681
|
+
_files['.harness/testing-strategy.md'] = fm('testing-strategy', ['before verification', 'before release'], ['test strategy changes'], `# Testing Strategy\n\n- Typecheck (\`tsc --noEmit\` or equivalent)\n- Lint (\`npm run lint\` etc.)\n- Unit/Integration/E2E\n- Manual smoke test\n- Browser/UI smoke (on frontend changes)\n\n## Evidence Format\nEach completed task must reference an evidence ID stored in .harness/review-evidence.md.\n`, 'en');
|
|
682
|
+
_files['.harness/review-checklist.md'] = fm('review-checklist', ['before PR/review'], ['review criteria change'], `# Review Checklist\n\n- [ ] Aligned with the plan\n- [ ] progress-tracker updated\n- [ ] No protected files deleted\n- [ ] Design/feature reuse checked\n- [ ] No secrets in code (\`leerness scan secrets\`)\n- [ ] Korean encoding OK (\`leerness encoding check\`)\n- [ ] Lazy-work check passed (\`leerness lazy detect\`)\n`, 'en');
|
|
683
|
+
_files['.harness/release-checklist.md'] = fm('release-checklist', ['before deploy'], ['deploy conditions/env vars/rollback change'], `# Release Checklist\n\n- [ ] \`leerness verify .\`\n- [ ] \`leerness audit .\`\n- [ ] \`leerness scan secrets .\`\n- [ ] \`leerness encoding check .\`\n- [ ] project typecheck/lint/test\n- [ ] env vars (.env.example) synced\n- [ ] rollback path confirmed\n- [ ] CHANGELOG updated\n`, 'en');
|
|
684
|
+
_files['.harness/session-close-policy.md'] = fm('session-close-policy', ['before session end'], ['session-close format changes'], `# Session Close Policy\n\nEvery session must list:\n- Completed\n- In progress\n- Incomplete\n- Planned\n- Waiting\n- On hold\n- Blocked\n- Dropped\n- Verification (commands run, results)\n- Recommended next direction\n- Next exact step\n\n\`leerness session close\` extracts the 9 categories automatically and writes the handoff block for the next session into session-handoff.md.\n`, 'en');
|
|
685
|
+
_files['.harness/anti-lazy-work-policy.md'] = fm('anti-lazy-work-policy', ['before claiming completion'], ['lazy-work prevention criteria change'], `# Anti Lazy Work Policy\n\n## Rules\n1. **No completion without evidence**: claiming "done" requires command output/test results/screenshot paths in progress-tracker's evidence column.\n2. **No empty handoff**: if Completed/In Progress/Next Exact Step are all empty at session end, close is marked "insufficient".\n3. **Honest partial implementation**: if not fully implemented, set status \`incomplete\` and write one line in Next Exact Step describing what remains.\n4. **Verification records**: accumulate typecheck/lint/test results in review-evidence.md.\n5. **TODO tracking**: any new \`TODO\`/\`FIXME\`/\`XXX\` in code gets a same-ID row in progress-tracker.\n6. **Automatic false-done detection**: \`leerness lazy detect\` checks done rows without evidence, empty handoffs, untracked new TODOs, and missing test-run traces.\n7. **Quality-lens self-questions**: before claiming done, answer \`leerness lens\` per domain — code: "would a senior developer find this needlessly complex?" / design: "would a senior designer and an ordinary user find it pretty, comfortable, intuitive?". If you cannot answer "yes (pass)", it is not done. When changing one domain, re-check the causally linked domains (↔ in \`lens\` output).\n`, 'en');
|
|
686
|
+
_files['.harness/session-handoff.md'] = fm('session-handoff', ['session start', 'taking over next work'], ['session end'], `# Session Handoff\n\nLast generated: (auto)\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`, 'en');
|
|
687
|
+
_files['.harness/leerness-maintenance.md'] = fm('leerness-maintenance', ['work start', 'before migration/release'], ['version policy changes'], `# Leerness Maintenance\n\nAI agents should check:\n\n\`\`\`bash\nleerness --version\nleerness self check .\nleerness update --check # auto-detect with 24h cache\nleerness update --yes # auto-migrate when a new version is found\ncat .harness/HARNESS_VERSION\nnpm view leerness version\n\`\`\`\n`, 'en');
|
|
688
|
+
_files['.harness/language-policy.md'] = fm('language-policy', ['before writing documents'], ['language changes'], `# Language Policy\n\nSelected language: ${lang}\n\nAll Leerness notes, skill notes, session reports, and task lists default to the language above (except when the user explicitly requests another language).\n`, 'en');
|
|
689
|
+
_files['.harness/secret-policy.md'] = fm('secret-policy', ['before skill/deploy/config changes'], ['sensitive-data policy changes'], `# Secret Policy\n\n## Rules\n- Record only environment-variable names; move values to .env.local, CI secrets, or a cloud secret manager.\n- Never leave tokens/passwords/cookies/IDs/card numbers in plaintext in code/docs/logs.\n- Run \`leerness scan secrets .\` before changes.\n\n## Patterns scanned\n- AWS Access Key (\`AKIA[0-9A-Z]{16}\`)\n- GitHub PAT (\`ghp_[A-Za-z0-9]{36}\`)\n- OpenAI key (\`sk-[A-Za-z0-9]{20,}\`)\n- Anthropic key (\`sk-ant-[A-Za-z0-9-]{20,}\`)\n- Google API key, Slack token, generic private key, hardcoded password\n`, 'en');
|
|
690
|
+
_files['.harness/encoding-policy.md'] = fm('encoding-policy', ['before creating files', 'garbled-Korean reports', 'before deploy'], ['encoding policy changes'], `# Encoding Policy\n\n## Rules\n- All text files are **UTF-8 without BOM**.\n- Windows .bat files start with \`chcp 65001 >nul\`.\n- PowerShell .ps1 files start with \`[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\`.\n- Python files may need \`# -*- coding: utf-8 -*-\` (for Python 2 compat).\n- LF line endings recommended (unify via .gitattributes on Windows too).\n\n## Auto check\n\`leerness encoding check\` verifies BOM, NUL, chcp 65001 in .bat, and Korean round-trip.\n`, 'en');
|
|
691
|
+
_files['.harness/architecture.md'] = fm('architecture', ['feature work', 'refactoring', 'migration'], ['structure changes'], `# Architecture\n\n## Overview\n- Record the real structure.\n\n## Data Flow\n-\n\n## External Dependencies\n-\n`, 'en');
|
|
692
|
+
_files['.harness/context-map.md'] = fm('context-map', ['exploring related files', 'before implementing features'], ['file structure changes'], `# Context Map\n\n| Area | Files | Notes |\n|---|---|---|\n| App | src/** | update with real paths |\n| Tests | tests/** | verification paths |\n`, 'en');
|
|
693
|
+
_files['.harness/decisions.md'] = fm('decisions', ['checking design decisions'], ['important decisions made'], `# Decisions\n\n## Template (example — add real decisions below, outside the code block)\n\n\`\`\`md\n### ${today()} — Decision title\n- Decision:\n- Reason:\n- Alternatives:\n- Impact:\n\`\`\`\n`, 'en');
|
|
694
|
+
_files['.harness/task-log.md'] = fm('task-log', ['checking work history'], ['after every meaningful task'], `# Task Log\n\n## ${today()}\n- Leerness v${VERSION} initialized.\n`, 'en');
|
|
695
|
+
_files['.harness/design-system.md'] = fm('design-system', ['UI changes', 'adding components', 'merging design guides'], ['design standards change', 'reusable patterns found'], `# Design System\n\n## Canonical File\nThis file is the canonical design guide (over designguide.md, design-guide.md, etc.).\n\n## Tokens\n| Token | Value | Notes |\n|---|---|---|\n| color.primary | (update with real value) | |\n| color.surface | | |\n| spacing.unit | | |\n| typography.body | | |\n\n## Reusable Patterns\n| Pattern | Where | Reuse Rule |\n|---|---|---|\n`, 'en');
|
|
696
|
+
_files['.harness/reuse-map.md'] = fm('reuse-map', ['before creating new components/APIs/helpers', 'duplicate capability detected'], ['reusable elements added'], `# Reuse Map\n\n| Capability | Existing Element | Reuse Method | Notes |\n|---|---|---|---|\n`, 'en');
|
|
697
|
+
_files['.harness/feature-contracts.md'] = fm('feature-contracts', ['before implementing/changing features'], ['feature IO/state/error changes'], `# Feature Contracts\n\n## Template\n- Feature:\n- Input:\n- Output:\n- States:\n- Errors:\n- Related files:\n- Test evidence ID:\n`, 'en');
|
|
670
698
|
_files['.cursor/rules/leerness.mdc'] = `${MARK}\n---\nalwaysApply: true\n---\nFollow AGENTS.md and .harness/context-routing.md.\nRun: \`leerness handoff .\` at session start.\nRun: \`leerness session close .\` at session end.\nPreserve Leerness protected files.\n`;
|
|
671
699
|
_files['.github/copilot-instructions.md'] = `${MARK}\n# Copilot Instructions\n\nUse AGENTS.md and .harness/ as project memory.\nDo not remove protected Leerness files.\nBefore completion, ensure plan.md, progress-tracker.md, current-state.md, session-handoff.md are updated.\n`;
|
|
672
700
|
_files['.harness/session-workflow.md'] = fm('session-workflow', ['session start', 'new user request arrives', 'before distributing complex work'], ['workflow step changes'], `# Session Workflow — 6 steps of AI harness engineering\n\n> **At the start of every session the main agent reads this document first and follows the 6 steps as written.**\n> Same flow regardless of round length/complexity — that is what prevents drift.\n\n## Step 1. Analyze the request + check the environment\n\`\`\`bash\nleerness handoff . # load context + automatic drift warning\nleerness drift check . # signals + 4-level verdict\n\`\`\`\n- Decompose the request (5W1H). If ambiguous, ask clarifying questions (except autonomous mode).\n- On drift critical, run \`leerness session close .\` or \`drift check --auto-fix\` first.\n\n## Step 2. Plan\n- 3+ steps → use TodoWrite or \`leerness plan add\`.\n- New capability → search existing assets first via \`leerness reuse-map\` / \`reuse find <query>\`.\n- Multiple modules → define an integration spec up front (e.g. TICK_SPEC.md).\n\n## Step 3. Distribute — sub-agent mapping\n\`\`\`bash\nleerness agents list # ready CLIs\nleerness agents quota # limits\nleerness agents dispatch "<task>" --to <id>\n\`\`\`\n- Best sub-agent by task type: text/translation/analysis → claude · deep code reasoning → codex · direct file edits → agy --yolo · security review → \`leerness review --persona security\`.\n- **Conflict-prevention rules (mandatory)**: give each sub-agent *the exact files only it may modify*; require mtime-verification reports (concurrent writes risk last-writer-wins); verify against the spec afterwards with \`leerness contract verify\`.\n\n## Step 4. Sub-agent work + individual self-verification\n- Each sub-agent reports only after passing its own module tests.\n- Report format: line counts, tests N/N PASS, issues found, mtime verification.\n\n## Step 5. Integrated verification\n\`\`\`bash\nleerness contract verify SPEC.md src/<mod>.js\nleerness verify-claim T-XXX --run-tests --strict-claims\nleerness review <file> --persona security,performance,ux\n\`\`\`\n- The main agent writes and runs its own integration scenario (independent verification).\n- Cross-check that sub-agent verification and main verification *agree*.\n\n## Step 6. Close + handoff + next-round suggestion\n\`\`\`bash\nleerness session close . # --suggest on by default\nleerness session close . --no-suggest\n\n# or separately:\nleerness skill suggest .\nleerness drift check .\nleerness audit . --fix\n\`\`\`\n\n## 🧠 Memory CRUD Quick Reference\n\n| Surface | CREATE | READ | DELETE | RESTORE |\n|---|---|---|---|---|\n| **tasks** | task add | task list --json | task drop | task update |\n| **decisions** | decision add | decision list --json | decision drop | memory restore decisions |\n| **lessons** | lesson save | lesson list [--tag] | lesson drop | memory restore lessons |\n| **plan** | plan add | plan list --json | plan remove | memory restore plan |\n| **rules** | rule add | rule list --json | rule remove | (rule pause/resume) |\n\n\`\`\`bash\nleerness memory status [--json]\nleerness memory archive list [--surface s]\nleerness memory restore <surface> <target>\n\`\`\`\n\n**Recovering a wrongly saved item**:\n1. \`memory archive list\` — list restore candidates\n2. \`memory restore decisions "PostgreSQL"\` — archive → active\n3. handoff automatically notes archive activity within 24h every session\n\n## Auto-recovery & security\n- A skipped session close triggers drift critical at the next session start — recover with \`drift check --auto-fix\` (runs session close automatically on critical).\n- handoff auto-recalls past lessons (by current task keywords) and auto-suggests installed skills.\n- handoff prints a 1–2 line security summary (.env ↔ .env.example sync + .gitignore secret gaps); a missing .env in .gitignore is 🚨 CRITICAL — \`LEERNESS_AUTO_SECURITY_FIX=1\` runs \`audit --fix\` automatically.\n- \`leerness health\` gives a one-line combined check (drift + security + skills + usage + tasks).\n\n---\n\n## Quick checklist (before the session ends)\n- [ ] This round's tasks are registered in plan/progress-tracker (or task sync)\n- [ ] Every done item has evidence attached (verify-claim PASS)\n- [ ] contract verify PASS when sub-agents were used\n- [ ] drift score ≤ 30 — \`leerness drift check\`\n- [ ] session close was run\n- [ ] \`leerness health\` overall check\n- [ ] If .env is used: .gitignore secret patterns OK + .env.example synced\n- [ ] On security critical: \`LEERNESS_AUTO_SECURITY_FIX=1\` or \`audit --fix\`\n\n## Anti-patterns (drift signals)\n- ⚠ "work is done, just report and stop" → missed session close → drift critical next session\n- ⚠ "only TodoWrite updated, leerness unused" → \`task sync --from\` or \`task add\` is mandatory\n- ⚠ distributing to sub-agents without explicit file paths → concurrent-write conflicts\n- ⚠ self-reported "tests ran, PASS" only → verify-claim --run-tests not executed\n- ⚠ skipping contract verify → spec-mismatch bugs reach the user\n`, 'en');
|
|
@@ -738,9 +766,9 @@ function createBackup(root, reason, files, dry = false) {
|
|
|
738
766
|
}
|
|
739
767
|
|
|
740
768
|
// 1.9.1 P2 / 1.9.368 (UR-0025): MERGE_OVERWRITE_FILES → lib/catalogs, managedMerge 순수 코어 → lib/pure-utils. 얇은 래퍼.
|
|
741
|
-
function managedMerge(file, next, previous, archiveDir) {
|
|
769
|
+
function managedMerge(file, next, previous, archiveDir, mergeOpts = {}) {
|
|
742
770
|
const archiveRel = archiveDir ? path.relative(process.cwd(), archiveDir).replace(/\\/g, '/') : '.harness/archive';
|
|
743
|
-
return _managedMerge(file, next, previous, archiveRel, MERGE_OVERWRITE_FILES);
|
|
771
|
+
return _managedMerge(file, next, previous, archiveRel, MERGE_OVERWRITE_FILES, mergeOpts); // 1.36.60: altTemplate/lang 전달
|
|
744
772
|
}
|
|
745
773
|
|
|
746
774
|
function writeIfSafe(root, file, content, opts = {}) {
|
|
@@ -751,7 +779,7 @@ function writeIfSafe(root, file, content, opts = {}) {
|
|
|
751
779
|
// 병합을 우회해 CLAUDE.md 커스텀 지시가 archive 에만 남았다(1.36.28 보존 재설계의 force 구멍).
|
|
752
780
|
if (already && opts.mergeManaged) {
|
|
753
781
|
const prev = read(p);
|
|
754
|
-
writeUtf8(p, managedMerge(file, content, prev, opts.archiveDir));
|
|
782
|
+
writeUtf8(p, managedMerge(file, content, prev, opts.archiveDir, { altTemplate: opts.altTemplate, lang: opts.lang }));
|
|
755
783
|
return { action: 'merged', file };
|
|
756
784
|
}
|
|
757
785
|
if (already && !opts.force) return { action: 'preserved', file };
|
|
@@ -988,6 +1016,17 @@ async function install(root, opts = {}) {
|
|
|
988
1016
|
// 1.9.10: 스킬 카탈로그 출처 안내
|
|
989
1017
|
// 1.9.184 (사용자 명시): leerness-skillpack 미사용 정책 — 안내 메시지 제거. builtin catalog 만 사용.
|
|
990
1018
|
const files = coreFiles(root, lang, skills, { minimal: !!opts.minimal });
|
|
1019
|
+
// 1.36.60 (F-05 2회차, 검수 #1 High): 반대-언어 canonical 템플릿 — 언어 전환 재init/마이그레이션 시
|
|
1020
|
+
// 구 언어 템플릿 라인을 "사용자 커스텀"으로 오인해 통째로 Preserved 에 이월하던 것을 차감 병합으로 해소.
|
|
1021
|
+
// (검수 2차 High): 차감은 "실제 언어 전환일 때만" — 동일-언어 재init 에 무조건 적용하면 반대-언어 템플릿과
|
|
1022
|
+
// 우연히 일치하는 정당한 커스텀이 유실된다. 이전 저장 언어를 쓰기 전에 포착해 전환 여부로 게이트.
|
|
1023
|
+
let _prevLang = null;
|
|
1024
|
+
try { _prevLang = read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase() || null; } catch {}
|
|
1025
|
+
if (!_prevLang) { try { _prevLang = String(JSON.parse(read(path.join(root, '.harness', 'manifest.json'))).language || '').toLowerCase() || null; } catch {} }
|
|
1026
|
+
const _langSwitched = !!(_prevLang && (_prevLang === 'ko' || _prevLang === 'en') && _prevLang !== lang);
|
|
1027
|
+
const _altLang = lang === 'en' ? 'ko' : 'en';
|
|
1028
|
+
let _altFiles = {};
|
|
1029
|
+
if (_langSwitched) { try { _altFiles = coreFiles(root, _altLang, skills, { minimal: !!opts.minimal }); } catch {} }
|
|
991
1030
|
const backup = createBackup(root, opts.force ? 'force' : (opts.migration ? 'migration' : 'init'), files, opts.dry);
|
|
992
1031
|
if (opts.dry) {
|
|
993
1032
|
log(`Backup target: ${backup.archiveDir}`);
|
|
@@ -998,6 +1037,13 @@ async function install(root, opts = {}) {
|
|
|
998
1037
|
}
|
|
999
1038
|
const managedOverwrite = new Set([
|
|
1000
1039
|
'AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md',
|
|
1040
|
+
'.harness/session-workflow.md', // 1.36.60 (검수 #1): managed 편입 — 종전엔 재init 시 구 언어/구 버전 그대로 방치
|
|
1041
|
+
// 1.36.61 (검수 High): 순수 정책-문서 전체를 managed 병합 경로에 — 언어 전환 시 구 언어로 방치되던 나머지
|
|
1042
|
+
// (사용자 상태 문서 plan/brief/decisions 등은 제외 — 구조-인지 전략 필요, 데이터라 raw 교체 금지)
|
|
1043
|
+
'.harness/guideline.md','.harness/plan-progress-boundary.md','.harness/guardrails.md',
|
|
1044
|
+
'.harness/consistency-policy.md','.harness/testing-strategy.md','.harness/review-checklist.md',
|
|
1045
|
+
'.harness/release-checklist.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md',
|
|
1046
|
+
'.harness/secret-policy.md','.harness/encoding-policy.md','.harness/language-policy.md',
|
|
1001
1047
|
'.harness/HARNESS_VERSION','.harness/manifest.json','.harness/LANGUAGE','.harness/skills-lock.json',
|
|
1002
1048
|
'.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md',
|
|
1003
1049
|
'.harness/leerness-maintenance.md','.harness/protected-files.md','.harness/AX_MIGRATION_GUIDE.md',
|
|
@@ -1032,7 +1078,7 @@ async function install(root, opts = {}) {
|
|
|
1032
1078
|
actions.push({ file:f, action });
|
|
1033
1079
|
continue;
|
|
1034
1080
|
}
|
|
1035
|
-
const r = writeIfSafe(root, f, c, { force: effForce, mergeManaged, archiveDir: backup.archiveDir });
|
|
1081
|
+
const r = writeIfSafe(root, f, c, { force: effForce, mergeManaged, archiveDir: backup.archiveDir, altTemplate: _altFiles[f], lang });
|
|
1036
1082
|
actions.push(r);
|
|
1037
1083
|
_done++;
|
|
1038
1084
|
drawProgress(_done, f);
|
|
@@ -5203,6 +5249,25 @@ function _selfTestCases() {
|
|
|
5203
5249
|
const s = read(__filename);
|
|
5204
5250
|
const wired = s.includes("_profile === 'core' ? _ALL_TOOLS.filter") && s.includes("Unknown tool: ${name} (core profile");
|
|
5205
5251
|
return allExist && loop && cleanSer && wired;
|
|
5252
|
+
} },
|
|
5253
|
+
{ name: '언어 전환 병합 (1.36.60, F-05 2회차 검수 High): altTemplate 차감 — 구 언어 템플릿을 커스텀 오인 이월 차단 + 커스텀 보존 + en 래퍼 — 순수 행위검사', run: () => {
|
|
5254
|
+
const p = require('../lib/pure-utils');
|
|
5255
|
+
const koT = '# 지시\n한국어 규칙 1\n한국어 규칙 2\n';
|
|
5256
|
+
const enT = '# Instructions\nEnglish rule 1\nEnglish rule 2\n';
|
|
5257
|
+
const prev = koT + 'MY CUSTOM LINE\n';
|
|
5258
|
+
// 전환: KO(prev) → EN(next), altTemplate=koT — 한국어 템플릿 라인은 이월 안 되고 커스텀만
|
|
5259
|
+
const m = p._managedMerge('AGENTS.md', enT, prev, '.harness/archive', null, { altTemplate: koT, lang: 'en' });
|
|
5260
|
+
const noKoTemplate = !m.includes('한국어 규칙 1') && !m.includes('한국어 규칙 2');
|
|
5261
|
+
const customKept = (m.match(/MY CUSTOM LINE/g) || []).length === 1;
|
|
5262
|
+
const enWrapper = m.includes('carried over from the previous version') && !/[가-힣]/.test(m.replace('MY CUSTOM LINE', ''));
|
|
5263
|
+
// altTemplate 없이(구 동작): 커스텀 오인 이월 — 회귀 방지용 대조군 (구 언어 라인이 이월됨을 확인)
|
|
5264
|
+
const m2 = p._managedMerge('AGENTS.md', enT, prev, '.harness/archive', null, {});
|
|
5265
|
+
const control = m2.includes('한국어 규칙 1');
|
|
5266
|
+
// 멱등: 전환 결과에 같은 병합 재적용 시 커스텀 중복 없음
|
|
5267
|
+
const m3 = p._managedMerge('AGENTS.md', enT, m, '.harness/archive', null, { altTemplate: koT, lang: 'en' });
|
|
5268
|
+
const idem = (m3.match(/MY CUSTOM LINE/g) || []).length === 1;
|
|
5269
|
+
const wired = read(__filename).includes('altTemplate: _altFiles[f]') && read(__filename).includes("'.harness/session-workflow.md', // 1.36.60");
|
|
5270
|
+
return noKoTemplate && customKept && enWrapper && control && idem && wired;
|
|
5206
5271
|
} }
|
|
5207
5272
|
];
|
|
5208
5273
|
}
|
|
@@ -10091,7 +10156,7 @@ function lazyDetect(root, opts = {}) {
|
|
|
10091
10156
|
if (rows.length === 0) { issues++; _warn('progress-tracker is empty (no tasks tracked)',
|
|
10092
10157
|
{ kind: 'progress_empty', severity: 'warn' }); }
|
|
10093
10158
|
const handoff = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
10094
|
-
if (!handoff.includes('Last generated:') || handoff.includes('Last generated: (자동)')) {
|
|
10159
|
+
if (!handoff.includes('Last generated:') || handoff.includes('Last generated: (자동)') || handoff.includes('Last generated: (auto)')) { // 1.36.61 (검수): en 센티널 병기
|
|
10095
10160
|
issues++; _warn('session-handoff.md never auto-generated',
|
|
10096
10161
|
{ kind: 'handoff_never_generated', severity: 'warn' });
|
|
10097
10162
|
}
|
|
@@ -17220,7 +17285,7 @@ function uiConsistency(root) {
|
|
|
17220
17285
|
if (!m) continue;
|
|
17221
17286
|
const key = m[1].trim();
|
|
17222
17287
|
const val = m[2].trim();
|
|
17223
|
-
if (key === 'Token' || /^-+$/.test(key) || val === 'Value' || /실제 값으로
|
|
17288
|
+
if (key === 'Token' || /^-+$/.test(key) || val === 'Value' || /실제 값으로 업데이트|update with real value/i.test(val) || !val) continue; // 1.36.61: en placeholder 병기
|
|
17224
17289
|
tokens[key] = val;
|
|
17225
17290
|
}
|
|
17226
17291
|
const tokenSet = new Set(Object.values(tokens).map(v => v.toLowerCase()));
|
|
@@ -20367,7 +20432,7 @@ function adapterCmd(root, tool, opts = {}) {
|
|
|
20367
20432
|
const results = [];
|
|
20368
20433
|
for (const k of a.keys) {
|
|
20369
20434
|
if (files[k] == null) continue;
|
|
20370
|
-
results.push(writeIfSafe(root, k, files[k], { mergeManaged: managedOverwrite.has(k) }));
|
|
20435
|
+
results.push(writeIfSafe(root, k, files[k], { mergeManaged: managedOverwrite.has(k), lang: lang === 'en' ? 'en' : 'ko' })); // 1.36.60 (검수 2차 #2): 래퍼 언어 전달 (altTemplate 은 전환-감지 없는 이 경로에선 미적용 — High 회귀 방지)
|
|
20371
20436
|
}
|
|
20372
20437
|
if (a.mcp) results.push(_mergeMcpJson(root));
|
|
20373
20438
|
if (json) { log(JSON.stringify({ adapter: tool, files: results }, null, 2)); return; }
|
package/lib/audit.js
CHANGED
|
@@ -55,7 +55,7 @@ function audit(root, opts = {}, deps = {}) {
|
|
|
55
55
|
const naMarker = '<!-- leerness:na';
|
|
56
56
|
const ds = exists(path.join(root,'.harness/design-system.md')) ? read(path.join(root,'.harness/design-system.md')) : '';
|
|
57
57
|
if (ds.includes(naMarker)) ok('design-system.md marked NA (skipped)');
|
|
58
|
-
else if (!/\| color\.primary \|/.test(ds) || /\(실제 값으로 업데이트\)
|
|
58
|
+
else if (!/\| color\.primary \|/.test(ds) || /\(실제 값으로 업데이트\)|\(update with real value\)/i.test(ds)) { warnings++; warn('design-system.md tokens not customized'); _finding('design_system_default', 'warn', 'design-system.md tokens not customized'); } // 1.36.61: en placeholder 병기
|
|
59
59
|
else ok('design-system tokens populated');
|
|
60
60
|
const reuse = exists(path.join(root,'.harness/reuse-map.md')) ? read(path.join(root,'.harness/reuse-map.md')) : '';
|
|
61
61
|
const reuseLines = reuse.split('\n').filter(l => l.startsWith('|') && !/Capability|---/.test(l)).length;
|
|
@@ -87,12 +87,12 @@ function audit(root, opts = {}, deps = {}) {
|
|
|
87
87
|
}
|
|
88
88
|
else if (milestoneIds.length) ok('all milestones linked in progress-tracker');
|
|
89
89
|
const handoff = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
90
|
-
if (handoff.includes('Last generated: (자동)')) {
|
|
90
|
+
if (handoff.includes('Last generated: (자동)') || handoff.includes('Last generated: (auto)')) { // 1.36.61: en 센티널 병기
|
|
91
91
|
warnings++; warn('session-handoff.md never auto-generated (run: leerness session close .)');
|
|
92
92
|
_finding('handoff_not_generated', 'warn', 'session-handoff.md never auto-generated');
|
|
93
93
|
// 1.9.35 #5: --fix → session-handoff.md 자동 생성 마커 갱신
|
|
94
94
|
if (fix) {
|
|
95
|
-
const stamped = handoff.replace(
|
|
95
|
+
const stamped = handoff.replace(/Last generated: \((자동|auto)\)/, `Last generated: ${today()} (leerness audit --fix)`); // 1.36.61: 양 언어 --fix
|
|
96
96
|
writeUtf8(handoffPath(root), stamped);
|
|
97
97
|
ok(' ↳ fixed: session-handoff.md timestamp 갱신');
|
|
98
98
|
fixed++;
|
package/lib/pure-utils.js
CHANGED
|
@@ -352,7 +352,7 @@ function _roadmapParseTokens(text) {
|
|
|
352
352
|
const m = line.match(/^\|\s*([\w.\-]+)\s*\|\s*([^|]+?)\s*\|/);
|
|
353
353
|
if (!m) continue;
|
|
354
354
|
const key = m[1].trim(), val = m[2].trim();
|
|
355
|
-
if (!key || !val || key === 'Token' || /^-+$/.test(key) || val === 'Value' || /\(실제 값으로 업데이트\)
|
|
355
|
+
if (!key || !val || key === 'Token' || /^-+$/.test(key) || val === 'Value' || /\(실제 값으로 업데이트\)|\(update with real value\)/i.test(val)) continue; // 1.36.61: en placeholder 병기
|
|
356
356
|
if (val.length > 80) continue;
|
|
357
357
|
tokens[key] = val;
|
|
358
358
|
}
|
|
@@ -765,10 +765,16 @@ function _mergeReadmeSection(existing, block, START, END) {
|
|
|
765
765
|
}
|
|
766
766
|
// 1.9.368 (UR-0025): 관리 파일 마이그레이션 머지 순수 코어 — 이전 내용을 migration-preserved 블록으로 보존(데이터/인덱스 파일은 overwrite).
|
|
767
767
|
// archiveRel(사전 계산된 표시 경로) + overwriteSet 을 인자로 주입 → path/process/상수 비결합(순수).
|
|
768
|
-
function _managedMerge(file, next, previous, archiveRel, overwriteSet) {
|
|
768
|
+
function _managedMerge(file, next, previous, archiveRel, overwriteSet, opts = {}) {
|
|
769
769
|
if (!previous || previous.trim() === next.trim()) return next;
|
|
770
770
|
const tag = '<!-- leerness:migration-preserved -->';
|
|
771
771
|
if (overwriteSet && overwriteSet.has(String(file).replace(/\\/g, '/'))) return next;
|
|
772
|
+
// 1.36.60 (F-05 2회차, 검수 High): 언어 전환 시 구 언어 canonical 템플릿(altTemplate)의 라인도 차감 —
|
|
773
|
+
// 종전엔 KO→EN 재init 에서 KO 템플릿 전체가 "커스텀"으로 오인돼 Preserved 에 통째 이월됐다.
|
|
774
|
+
// (검수 2차 High): 차감은 "출현 횟수" 기반 — canonical 1회분만 소비해, 사용자가 의도적으로 템플릿 라인을
|
|
775
|
+
// 복제한 커스텀(2번째 출현)은 생존한다. Set 차감은 그 복제본까지 지웠다.
|
|
776
|
+
const altCounts = new Map();
|
|
777
|
+
for (const l of String(opts.altTemplate || '').split('\n').map(x => x.trim()).filter(Boolean)) altCounts.set(l, (altCounts.get(l) || 0) + 1);
|
|
772
778
|
// 1.36.28 (사용자 보고 — 반복 마이그레이션 데이터 유실): 종전 <details> 중첩 설계는 previous 에 preserved 태그가
|
|
773
779
|
// 있으면 통째로 next 만 반환 → 2번째 마이그레이션부터 사용자가 CLAUDE/AGENTS 에 직접 쓴 커스텀 지시가 조용히
|
|
774
780
|
// 사라졌다(매 버전 AI 마이그레이션 흐름에서 상시 발화). 재설계: 라인-집합 diff 로 "새 템플릿에 없는 라인"만
|
|
@@ -780,17 +786,26 @@ function _managedMerge(file, next, previous, archiveRel, overwriteSet) {
|
|
|
780
786
|
for (const raw of previous.split('\n')) {
|
|
781
787
|
const l = raw.trim();
|
|
782
788
|
if (!l || nextLines.has(l)) continue;
|
|
783
|
-
|
|
789
|
+
const _ac = altCounts.get(l);
|
|
790
|
+
if (_ac > 0) { altCounts.set(l, _ac - 1); continue; } // canonical 출현분만 소비 (복제 커스텀 생존)
|
|
791
|
+
// 1.36.60 (검수 2차 #3): 구버전 workflow 의 자동생성 이력 로그(도구-카운트/마일스톤 불릿)는 커스텀이 아니라
|
|
792
|
+
// 릴리스마다 낡는 생성물 — 좁은 시그니처만 필터 (사용자 산문이 이 패턴과 겹칠 확률은 무시 가능)
|
|
793
|
+
if (String(file).includes('session-workflow') && (/^- .*MCP \*\*\d+\s*도구/.test(l) || /^- MCP \*\*\d+/.test(l) || /^- 🏆 마일스톤/.test(l) || /^- 🎉 \*\*\d+ 라운드/.test(l))) continue;
|
|
794
|
+
// preserved 래퍼 자체의 보일러플레이트는 이월 대상 아님 (구 <details> 형식 + 신 평문 형식 + en 래퍼 모두)
|
|
784
795
|
if (l === tag || l === '---' || l === '<details>' || l === '</details>') continue;
|
|
785
796
|
if (/^```/.test(l) || /^<summary>/.test(l)) continue;
|
|
786
|
-
if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서
|
|
797
|
+
if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진|^> Custom user\/project content carried over/.test(l)) continue;
|
|
787
798
|
if (seen.has(l)) continue;
|
|
788
799
|
seen.add(l);
|
|
789
800
|
custom.push(raw.replace(/\\`\\`\\`/g, '```')); // 구 형식에서 이스케이프된 펜스 복원
|
|
790
801
|
}
|
|
791
802
|
if (!custom.length) return next;
|
|
792
803
|
const ar = archiveRel || '.harness/archive';
|
|
793
|
-
|
|
804
|
+
// 1.36.60 (검수): 보존 래퍼 문구도 프로젝트 언어를 따른다 — en 프로젝트에 한글 래퍼가 새 한글원을 만들던 것 해소
|
|
805
|
+
const note = opts.lang === 'en'
|
|
806
|
+
? `> Custom user/project content carried over from the previous version — re-carried automatically on every migration. Full original backup: \`${ar}\``
|
|
807
|
+
: `> 이전 버전에서 이어진 사용자/프로젝트 커스텀 내용 — 마이그레이션마다 자동 이월됩니다. 전체 원본 백업: \`${ar}\``;
|
|
808
|
+
return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\n${note}\n\n` + custom.join('\n') + '\n';
|
|
794
809
|
}
|
|
795
810
|
|
|
796
811
|
// 1.9.369 (UR-0025): --skills 값 파싱 순수 코어 — catalog 주입(harness skillCatalog 비결합). all/recommended/csv 처리 + catalog 필터.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.61",
|
|
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
|
@@ -7083,7 +7083,9 @@ total++;
|
|
|
7083
7083
|
const H = /[가-힣]/;
|
|
7084
7084
|
const de = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-en5-')); _dirs.push(de);
|
|
7085
7085
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'init', de, '--yes', '--language', 'en', '--no-env'], { encoding: 'utf8', timeout: 30000 });
|
|
7086
|
-
|
|
7086
|
+
// 1.36.61 (3회차): 정책·시드 문서군까지 확대 — 대표 10종 무한글
|
|
7087
|
+
const five = ['AGENTS.md', 'CLAUDE.md', '.harness/session-workflow.md', '.cursor/rules/leerness.mdc', '.github/copilot-instructions.md',
|
|
7088
|
+
'.harness/guideline.md', '.harness/guardrails.md', '.harness/anti-lazy-work-policy.md', '.harness/plan.md', '.harness/secret-policy.md'];
|
|
7087
7089
|
const enOk = r1.status === 0 && five.every(p => { const t = fs.readFileSync(path.join(de, p), 'utf8'); return t.length > 100 && !H.test(t); });
|
|
7088
7090
|
// en 지시 본문의 핵심 계약 존재(번역이 빈 껍데기가 아님) — 검수 #2 반영: lens 질문/체크리스트/안티패턴/자동화 지시까지
|
|
7089
7091
|
const ag = fs.readFileSync(path.join(de, 'AGENTS.md'), 'utf8');
|