leerness 1.7.0 → 1.8.0

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.
Files changed (3) hide show
  1. package/README.md +43 -90
  2. package/bin/harness.js +149 -21
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,19 +1,18 @@
1
1
  # Leerness
2
2
 
3
- Leerness는 AI 에이전트가 프로젝트의 목표, 계획, 작업 상태, 기술 결정, 디자인/기능 일관성, 검증된 스킬을 유지하며 개발하도록 돕는 AX 최적화 개발 하네스입니다.
3
+ Leerness는 AI 에이전트가 프로젝트의 목적, 계획, 진행 상태, 설계 결정, 디자인/기능 일관성, 검증된 스킬을 지속적으로 참조하며 개발하도록 돕는 AX 최적화 개발 하네스입니다.
4
4
 
5
5
  ## 주요 기능
6
6
 
7
- - `plan.md` 기반 전체 계획과 범위 관리
8
- - `progress-tracker.md` 기반 사용자 요청 단위 상태 추적
9
- - `context-routing.md` 기반 작업 유형별 참조 파일 안내
10
- - `writeback-policy.md` 기반 작업 문서 갱신 규칙
11
- - `session close` 기반 세션 종료 인수인계와 미완료 작업 노출
12
- - `protected-files.md` 기반 `.harness/` 핵심 문서 삭제 방지
13
- - `design-system.md`, `consistency-policy.md`, `reuse-map.md` 기반 디자인/기능 일관성 유지
14
- - `readme sync` 기반 루트 `README.md` Leerness 관리 섹션 최신화
15
- - 한글명과 가능한 작업이 표시되는 스킬 라이브러리
16
- - AI 검증 스킬 업로드를 위한 토큰 게이트와 dry-run 우선 정책
7
+ - `.harness/` 기반 장기 맥락 유지
8
+ - `plan.md`와 `progress-tracker.md` 기반 계획/작업 추적
9
+ - 언어 자동 감지 또는 선택 설치
10
+ - 스킬 라이브러리 선택 설치와 한글 스킬 정보 표시
11
+ - AI 검증 스킬의 라이브러리화, 병합, 마이그레이션, 배포 흐름 지원
12
+ - 기존 하네스/스킬/지침 파일 비파괴 마이그레이션
13
+ - `.env.example`, `.gitignore`, README managed section 병합 업데이트
14
+ - 디자인 가이드 병합, 재사용 맵, 일관성 정책 제공
15
+ - 세션 종료 인수인계와 미완료 작업 추적
17
16
 
18
17
  ## 설치
19
18
 
@@ -21,112 +20,66 @@ Leerness는 AI 에이전트가 프로젝트의 목표, 계획, 작업 상태,
21
20
  npx leerness init
22
21
  ```
23
22
 
24
- 언어 지정:
23
+ 언어와 스킬을 명시하려면:
25
24
 
26
25
  ```bash
27
- npx leerness init --language ko
28
- npx leerness init --language en
29
- npx leerness init --language auto
26
+ npx leerness init --language ko --skills recommended
27
+ npx leerness init --language en --skills office,commerce-api
30
28
  ```
31
29
 
32
- 추천 스킬 포함:
30
+ 기존 프로젝트 마이그레이션:
33
31
 
34
32
  ```bash
35
- npx leerness init --skills recommended
33
+ npx leerness migrate --dry-run
34
+ npx leerness migrate
36
35
  ```
37
36
 
38
- ## 핵심 명령어
37
+ ## 설치 시 선택 흐름
39
38
 
40
- ```bash
41
- leerness status .
42
- leerness verify .
43
- leerness debug .
44
- leerness route planning
45
- leerness route consistency
46
- leerness session close .
47
- ```
39
+ 대화형 터미널에서 `--yes`, `--language`, `--skills`를 생략하면 Leerness가 설치 언어와 스킬 라이브러리 설치 여부를 묻습니다.
40
+
41
+ - 언어: 자동 감지, 한국어, English
42
+ - 스킬: 기본 하네스만, 추천 스킬, 전체 스킬, 직접 입력
48
43
 
49
- 계획 관리:
44
+ CI 또는 자동화 환경에서는 프롬프트 없이 명시 옵션을 사용하세요.
50
45
 
51
46
  ```bash
52
- leerness plan show
53
- leerness plan init --goal "프로젝트 목적"
54
- leerness plan add "결제 기능 구현" --status planned
55
- leerness plan drop "관리자 대시보드" --reason "이번 버전 범위 제외"
56
- leerness plan progress
57
- leerness plan sync
47
+ npx leerness init --yes --language ko --skills recommended
58
48
  ```
59
49
 
60
- 작업 추적:
50
+ ## 비파괴 마이그레이션 정책
61
51
 
62
- ```bash
63
- leerness task list
64
- leerness task add "사용자 요청" --status requested
65
- leerness task update T-0001 --status in-progress
66
- leerness task drop T-0001 --reason "사용자가 제외"
67
- ```
52
+ Leerness는 마이그레이션 전에 기존 하네스, 스킬, 지침, README, 환경 예시 파일을 `.harness/archive/`에 백업합니다.
68
53
 
69
- README 동기화:
54
+ 덮어쓰기 위험이 있는 파일은 다음 정책을 따릅니다.
70
55
 
71
- ```bash
72
- leerness readme sync .
73
- ```
56
+ - 프로젝트 메모리 파일은 기본 보존
57
+ - `AGENTS.md`, `CLAUDE.md`, Copilot/Cursor 지침은 새 템플릿과 기존 내용을 병합
58
+ - `.env.example`, `.gitignore`는 기존 내용을 유지하고 필요한 줄만 추가
59
+ - README는 Leerness managed marker 구간만 갱신
60
+ - 작업 결과는 `.harness/migration-report.md`에 기록
74
61
 
75
- 디자인/기능 일관성:
62
+ ## 주요 명령어
76
63
 
77
64
  ```bash
78
- leerness consistency check .
79
- leerness consistency merge-design-guide .
80
- ```
65
+ leerness status .
66
+ leerness verify .
67
+ leerness debug .
68
+ leerness route <task-type>
69
+ leerness session close .
81
70
 
82
- 스킬:
71
+ leerness plan show
72
+ leerness plan add "작업명" --status planned
73
+ leerness task list
74
+ leerness task drop T-0001 --reason "사용자 요청으로 제외"
83
75
 
84
- ```bash
85
76
  leerness skill list
86
77
  leerness skill info commerce-api
87
- leerness skill add ai-verified-skill-publisher --path .
88
- ```
89
78
 
90
- 최신 버전 확인:
91
-
92
- ```bash
93
- leerness self check .
94
- ```
95
-
96
- ## 생성 구조
97
-
98
- ```text
99
- .harness/
100
- plan.md
101
- progress-tracker.md
102
- guideline.md
103
- plan-progress-boundary.md
104
- project-brief.md
105
- current-state.md
106
- context-routing.md
107
- writeback-policy.md
108
- protected-files.md
109
- consistency-policy.md
110
- design-system.md
111
- reuse-map.md
112
- session-close-policy.md
113
- anti-lazy-work-policy.md
114
- leerness-maintenance.md
115
- skills/
116
- library/
117
- archive/
79
+ leerness consistency check .
80
+ leerness readme sync .
118
81
  ```
119
82
 
120
- ## 계획과 진행률의 역할 분리
121
-
122
- - `plan.md`: 프로젝트 목표, 범위, milestone, 제외/드랍 항목, 계획 변경 이력
123
- - `progress-tracker.md`: 사용자 요청 단위 상태, 증거, 다음 액션
124
- - `guideline.md`: 계획과 진행률을 어떻게 따라야 하는지에 대한 실행 기준
125
-
126
- ## 보호 정책
127
-
128
- AI 에이전트는 `.harness/`, `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/leerness.mdc`, `.github/copilot-instructions.md`, 루트 `README.md`의 Leerness 관리 섹션을 임의 삭제하거나 초기화하지 않아야 합니다. 정리가 필요하면 삭제보다 병합, 아카이브, deprecated 표시를 우선합니다.
129
-
130
83
  ## 라이선스
131
84
 
132
85
  MIT
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
- const VERSION = '1.7.0';
9
+ const VERSION = '1.8.0';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -221,17 +221,116 @@ function coreFiles(root, lang = 'ko', selectedSkills = []) {
221
221
  '.harness/templates/decision.md': '# Decision\n\n## Decision\n\n## Reason\n\n## Alternatives\n\n## Impact\n'
222
222
  };
223
223
  }
224
- function parseSkills() {
225
- const v = arg('--skills', '');
224
+ function parseSkillsValue(v) {
226
225
  if (!v || v === true) return [];
227
226
  if (v === 'all') return Object.keys(skillCatalog);
228
227
  if (v === 'recommended') return ['office', 'commerce-api', 'ai-verified-skill-publisher'];
229
- return String(v).split(',').map(s => s.trim()).filter(Boolean);
228
+ return String(v).split(',').map(s => s.trim()).filter(Boolean).filter(s => skillCatalog[s]);
229
+ }
230
+ function parseSkills() { return parseSkillsValue(arg('--skills', '')); }
231
+ function detectLanguageValue(root, value = 'auto') {
232
+ const v = String(value || 'auto').toLowerCase();
233
+ if (v === 'ko' || v === 'en') return v;
234
+ const candidates = ['README.md', 'docs/guideline.md', '.harness/project-brief.md', '.harness/plan.md'];
235
+ let text = '';
236
+ for (const c of candidates) { const p = path.join(root, c); if (exists(p)) text += read(p).slice(0, 3000); }
237
+ return /[가-힣]/.test(text) ? 'ko' : 'en';
238
+ }
239
+ function ask(question) {
240
+ return new Promise(resolve => {
241
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
242
+ rl.question(question, answer => { rl.close(); resolve(String(answer || '').trim()); });
243
+ });
244
+ }
245
+ async function resolveInstallOptions(root, opts = {}) {
246
+ const explicitLang = arg('--language', null);
247
+ const explicitSkills = arg('--skills', null);
248
+ let lang = explicitLang ? detectLanguageValue(root, explicitLang) : detectLanguageValue(root, 'auto');
249
+ let skills = explicitSkills ? parseSkillsValue(explicitSkills) : [];
250
+ const shouldAsk = !has('--yes') && !opts.nonInteractive && process.stdin.isTTY && process.stdout.isTTY && !opts.migration;
251
+ if (shouldAsk && !explicitLang) {
252
+ log('\n설치 언어를 선택하세요.');
253
+ log('1) 자동 감지');
254
+ log('2) 한국어');
255
+ log('3) English');
256
+ const a = await ask('선택 [1]: ');
257
+ lang = a === '2' ? 'ko' : a === '3' ? 'en' : detectLanguageValue(root, 'auto');
258
+ }
259
+ if (shouldAsk && !explicitSkills) {
260
+ log('\n설치할 스킬 라이브러리를 선택하세요.');
261
+ log('0) 기본 하네스만 설치');
262
+ log('1) 추천 스킬 설치: office, commerce-api, ai-verified-skill-publisher');
263
+ log('2) 전체 스킬 설치');
264
+ log('3) 직접 입력');
265
+ skillList();
266
+ const a = await ask('선택 [1]: ');
267
+ if (!a || a === '1') skills = parseSkillsValue('recommended');
268
+ else if (a === '2') skills = parseSkillsValue('all');
269
+ else if (a === '3') skills = parseSkillsValue(await ask('스킬 ID를 쉼표로 입력: '));
270
+ else if (a === '0') skills = [];
271
+ }
272
+ return { lang, skills };
273
+ }
274
+ function copyRecursiveSafe(src, dst) {
275
+ if (!exists(src)) return;
276
+ if (src.includes(path.sep + '.harness' + path.sep + 'archive')) return;
277
+ const st = fs.statSync(src);
278
+ if (st.isDirectory()) {
279
+ mkdirp(dst);
280
+ for (const e of fs.readdirSync(src)) {
281
+ if (e === 'node_modules' || e === '.git') continue;
282
+ copyRecursiveSafe(path.join(src, e), path.join(dst, e));
283
+ }
284
+ } else {
285
+ mkdirp(path.dirname(dst));
286
+ fs.copyFileSync(src, dst);
287
+ }
288
+ }
289
+ function migrationCandidates(root, files) {
290
+ const fixed = [
291
+ 'AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.cursor/rules/project-rules.mdc',
292
+ '.github/copilot-instructions.md','README.md','.env.example','.gitignore',
293
+ 'docs/guideline.md','docs/history.md','guideline.md','history.md',
294
+ 'AI_HARNESS.md','HARNESS.md','PROJECT_CONTEXT.md','CONTEXT.md','ARCHITECTURE.md','DECISIONS.md','CURRENT_STATE.md','TASK_LOG.md',
295
+ '.harness','.ai','harness'
296
+ ];
297
+ const all = Array.from(new Set([...fixed, ...Object.keys(files)]));
298
+ return all.filter(f => exists(path.join(root, f)));
299
+ }
300
+ function createBackup(root, reason, files, dry = false) {
301
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
302
+ const ar = path.join(root, '.harness/archive', `leerness-${VERSION}-${stamp}`);
303
+ const candidates = migrationCandidates(root, files);
304
+ if (dry) return { archiveDir: ar, candidates };
305
+ mkdirp(ar);
306
+ const fileRoot = path.join(ar, 'files');
307
+ for (const f of candidates) copyRecursiveSafe(path.join(root, f), path.join(fileRoot, f === '.harness' ? '.harness-before-migration' : f));
308
+ write(path.join(ar, 'migration-manifest.json'), JSON.stringify({
309
+ version: VERSION,
310
+ reason,
311
+ createdAt: now(),
312
+ policy: 'backup-before-write; preserve-by-default; merge-managed-files; merge-env-and-gitignore',
313
+ candidates
314
+ }, null, 2) + '\n');
315
+ return { archiveDir: ar, candidates };
316
+ }
317
+ function managedMerge(file, next, previous, archiveDir) {
318
+ if (!previous || previous.trim() === next.trim()) return next;
319
+ const tag = '<!-- leerness:migration-preserved -->';
320
+ if (previous.includes(tag)) return next;
321
+ return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\nPrevious content was backed up before migration. Archive reference:\n\n\`${archiveDir ? path.relative(process.cwd(), archiveDir).replace(/\\/g, '/') : '.harness/archive'}\`\n\nThe content below is retained so AI agents can migrate useful custom instructions instead of losing them.\n\n<details>\n<summary>Previous ${file}</summary>\n\n\`\`\`md\n${previous.replace(/```/g, '\\`\\`\\`')}\n\`\`\`\n\n</details>\n`;
230
322
  }
231
323
  function writeIfSafe(root, file, content, opts = {}) {
232
324
  const p = path.join(root, file);
233
- if (exists(p) && !opts.force) return { action: 'preserved', file };
234
- write(p, content); return { action: exists(p) ? 'updated' : 'created', file };
325
+ const already = exists(p);
326
+ if (already && !opts.force && !opts.mergeManaged) return { action: 'preserved', file };
327
+ if (already && opts.mergeManaged && !opts.force) {
328
+ const prev = read(p);
329
+ write(p, managedMerge(file, content, prev, opts.archiveDir));
330
+ return { action: 'merged', file };
331
+ }
332
+ write(p, content);
333
+ return { action: already ? 'updated' : 'created', file };
235
334
  }
236
335
  function mergeLinesFile(p, lines) {
237
336
  const current = exists(p) ? read(p) : '';
@@ -239,22 +338,54 @@ function mergeLinesFile(p, lines) {
239
338
  for (const line of lines) if (!next.includes(line)) next += (next.endsWith('\n') || !next ? '' : '\n') + line + '\n';
240
339
  write(p, next);
241
340
  }
341
+ function writeMigrationReport(root, backup, actions) {
342
+ const p = path.join(root, '.harness/migration-report.md');
343
+ const rows = actions.map(a => `| ${a.file} | ${a.action} |`).join('\n');
344
+ write(p, `# Leerness Migration Report\n\nVersion: ${VERSION}\nDate: ${now()}\nBackup: ${rel(root, backup.archiveDir)}\n\n## Policy\n\n- Existing harness, skill, and instruction files are backed up before migration.\n- Project memory files are preserved by default.\n- Managed instruction files are merged with previous content instead of being blindly overwritten.\n- .env.example and .gitignore are line-merged only.\n\n## Backed Up Candidates\n\n${backup.candidates.map(x => '- ' + x).join('\n')}\n\n## File Actions\n\n| File | Action |\n|---|---|\n${rows}\n`);
345
+ }
242
346
  function syncReadme(root) {
243
347
  const p = path.join(root, 'README.md');
244
348
  const existing = exists(p) ? read(p) : '';
245
349
  write(p, mergeSection(existing, managedReadmeBlock(detectProjectName(root))));
246
350
  ok('README.md Leerness section synced');
247
351
  }
248
- function install(root, opts = {}) {
352
+ async function install(root, opts = {}) {
249
353
  root = absRoot(root); mkdirp(root);
250
- const lang = detectLanguage(root); const skills = parseSkills();
251
- log(`\nLeerness v${VERSION}`); log(`Target: ${root}`); log(`Language: ${lang}`);
252
- if (!opts.dry) archive(root, opts.force ? 'force' : 'safe');
354
+ const resolved = await resolveInstallOptions(root, opts);
355
+ const lang = resolved.lang;
356
+ const skills = resolved.skills;
357
+ log(`\nLeerness v${VERSION}`);
358
+ log(`Target: ${root}`);
359
+ log(`Language: ${lang}`);
360
+ log(`Skills: ${skills.length ? skills.join(', ') : 'none'}`);
253
361
  const files = coreFiles(root, lang, skills);
362
+ const backup = createBackup(root, opts.force ? 'force' : (opts.migration ? 'migration' : 'init'), files, opts.dry);
363
+ if (opts.dry) {
364
+ log(`Backup target: ${backup.archiveDir}`);
365
+ log('Files that would be backed up:');
366
+ backup.candidates.forEach(f => log('- ' + f));
367
+ } else {
368
+ ok(`backup created: ${rel(root, backup.archiveDir)}`);
369
+ }
370
+ const managedOverwrite = new Set([
371
+ 'AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md',
372
+ '.harness/HARNESS_VERSION','.harness/manifest.json','.harness/LANGUAGE','.harness/skills-lock.json',
373
+ '.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md',
374
+ '.harness/leerness-maintenance.md','.harness/protected-files.md','.harness/AX_MIGRATION_GUIDE.md',
375
+ '.harness/AX_NEW_PROJECT_GUIDE.md','.harness/AX_SKILL_LIBRARY_GUIDE.md'
376
+ ]);
377
+ const actions = [];
254
378
  for (const [f, c] of Object.entries(files)) {
255
- if (opts.dry) { log(`[dry-run] ${exists(path.join(root, f)) ? 'preserve/update' : 'create'} ${f}`); continue; }
256
- const allowOverwrite = ['AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md','.harness/HARNESS_VERSION','.harness/manifest.json','.harness/LANGUAGE','.harness/skills-lock.json','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/leerness-maintenance.md'].includes(f) || opts.force;
257
- const r = writeIfSafe(root, f, c, { force: allowOverwrite });
379
+ const existsNow = exists(path.join(root, f));
380
+ const mergeManaged = managedOverwrite.has(f);
381
+ if (opts.dry) {
382
+ const action = existsNow ? (mergeManaged || opts.force ? 'merge/update from backup-aware template' : 'preserve') : 'create';
383
+ log(`[dry-run] ${action}: ${f}`);
384
+ actions.push({ file:f, action });
385
+ continue;
386
+ }
387
+ const r = writeIfSafe(root, f, c, { force: opts.force, mergeManaged, archiveDir: backup.archiveDir });
388
+ actions.push(r);
258
389
  ok(`${r.action}: ${r.file}`);
259
390
  }
260
391
  if (!opts.dry) {
@@ -262,12 +393,9 @@ function install(root, opts = {}) {
262
393
  mergeLinesFile(path.join(root, '.env.example'), ['# Leerness uses environment variable names only. Do not store real secrets here.', 'LEERNESS_NPM_TOKEN=', 'LEERNESS_GITHUB_TOKEN=']);
263
394
  syncReadme(root);
264
395
  installSkills(root, skills);
396
+ writeMigrationReport(root, backup, actions);
265
397
  }
266
398
  }
267
- function archive(root, reason) {
268
- const ar = path.join(root, '.harness/archive', `leerness-${VERSION}-${new Date().toISOString().replace(/[:.]/g,'-')}`);
269
- mkdirp(ar); write(path.join(ar, 'migration-manifest.json'), JSON.stringify({ version: VERSION, reason, createdAt: now() }, null, 2) + '\n');
270
- }
271
399
  function installSkills(root, skills) {
272
400
  for (const name of skills) addSkill(root, name, true);
273
401
  }
@@ -317,12 +445,12 @@ function mergeDesign(root) { root = absRoot(root); const canonical = path.join(r
317
445
  function selfCheck(root) { let latest = 'unknown'; try { latest = cp.execSync('npm view leerness version', { encoding:'utf8', stdio:['ignore','pipe','ignore'], timeout:10000 }).trim(); } catch { latest = 'npm registry unavailable'; } const local = exists(path.join(root,'.harness/HARNESS_VERSION')) ? read(path.join(root,'.harness/HARNESS_VERSION')).trim() : 'not installed'; log(`CLI: ${VERSION}`); log(`Project: ${local}`); log(`NPM latest: ${latest}`); }
318
446
  function publishLibrary(targetPath, target) { const execute = has('--execute'); if (!execute) { log('dry-run only. Add --execute to publish.'); return; } const envs = target === 'npm' ? ['LEERNESS_NPM_TOKEN','NPM_TOKEN','NODE_AUTH_TOKEN'] : ['LEERNESS_GIT_TOKEN','LEERNESS_GITHUB_TOKEN','GITHUB_TOKEN','GH_TOKEN']; const found = envs.find(e => process.env[e]); if (!found && has('--no-prompt')) { fail('publish token not found'); process.exitCode = 1; return; } if (!found) { fail(`publish token required. Set one of: ${envs.join(', ')}`); process.exitCode = 1; return; } ok(`token provided by ${found}; publish command would execute for ${targetPath}`); }
319
447
  function help() { log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop [args]\n leerness skill list|info|add <name>\n leerness session close [path]\n`); }
320
- function main() {
448
+ async function main() {
321
449
  const args = nonFlagArgs(); const cmd = args[0] || 'init';
322
450
  if (has('--version') || has('-v')) return log(VERSION);
323
451
  if (has('--help') || has('-h')) return help();
324
- if (cmd === 'init') return install(args[1] || process.cwd(), { force:false, dry:false });
325
- if (cmd === 'migrate') return install(args[1] || process.cwd(), { force:has('--force'), dry:has('--dry-run') });
452
+ if (cmd === 'init') return await install(args[1] || process.cwd(), { force:false, dry:false, migration:false });
453
+ if (cmd === 'migrate') return await install(args[1] || process.cwd(), { force:has('--force'), dry:has('--dry-run'), migration:true });
326
454
  if (cmd === 'status') return status(args[1] || process.cwd());
327
455
  if (cmd === 'verify') return verify(args[1] || process.cwd());
328
456
  if (cmd === 'debug') return debug(args[1] || process.cwd());
@@ -341,4 +469,4 @@ function main() {
341
469
  if (cmd === 'library' && args[1] === 'publish') return publishLibrary(args[2] || '.', arg('--target','npm'));
342
470
  return help();
343
471
  }
344
- main();
472
+ main().catch(err => { fail(err && err.message ? err.message : String(err)); process.exitCode = 1; });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.7.0",
4
- "description": "Leerness: AI 에이전트의 장기 맥락, 계획, 작업 추적, 스킬 라이브러리, 디자인/기능 일관성, 세션 인수인계를 관리하는 AX 최적화 개발 하네스.",
3
+ "version": "1.8.0",
4
+ "description": "Leerness: 비파괴 마이그레이션, 언어/스킬 선택 설치, 계획/작업 추적, 스킬 라이브러리, 디자인/기능 일관성을 관리하는 AX 최적화 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
7
7
  "ai",