leerness 1.5.0 → 1.6.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.
- package/README.md +27 -0
- package/bin/harness.js +145 -14
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -152,6 +152,33 @@ AI는 의미 있는 세션이 끝날 때 아래를 정리해야 합니다.
|
|
|
152
152
|
- 추가로 진행하면 좋은 추천 방향
|
|
153
153
|
- 다음 세션에서 바로 수행할 단 하나의 정확한 작업
|
|
154
154
|
|
|
155
|
+
## Leerness 최신 버전 유지
|
|
156
|
+
|
|
157
|
+
AI 에이전트가 작업을 시작하기 전 또는 마이그레이션/배포/릴리즈 작업을 수행하기 전, 현재 프로젝트의 Leerness 버전과 npm registry의 최신 버전을 비교할 수 있습니다.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
leerness self check .
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
이 명령은 내부적으로 아래 기준을 확인합니다.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm view leerness version
|
|
167
|
+
leerness --version
|
|
168
|
+
cat .harness/HARNESS_VERSION
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
최신 버전이 감지되면 바로 덮어쓰지 않고 안전한 마이그레이션 순서를 안내합니다.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npx --yes leerness@<latest> migrate . --dry-run
|
|
175
|
+
npx --yes leerness@<latest> migrate .
|
|
176
|
+
npx --yes leerness@<latest> verify .
|
|
177
|
+
npx --yes leerness@<latest> debug .
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
관련 방향지시는 `.harness/leerness-maintenance.md`에 생성됩니다. npm 조회 실패나 고정 버전 사용도 작업 로그와 세션 인수인계에 기록하도록 설계했습니다.
|
|
181
|
+
|
|
155
182
|
## 라이선스
|
|
156
183
|
|
|
157
184
|
MIT
|
package/bin/harness.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
const childProcess = require('child_process');
|
|
8
8
|
|
|
9
|
-
const VERSION = '1.
|
|
9
|
+
const VERSION = '1.6.0';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const MIGRATED = '<!-- leerness:migrated-legacy -->';
|
|
12
12
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
@@ -61,11 +61,6 @@ function detectLanguage(root){
|
|
|
61
61
|
return 'en';
|
|
62
62
|
}
|
|
63
63
|
function readConfiguredLanguage(root){
|
|
64
|
-
|
|
65
|
-
const pf=path.join(root,'.harness/plan.md');
|
|
66
|
-
if(exists(pf)){ const b=read(pf); if(b.includes('## Milestones')) ok('plan.md milestones 확인'); else { warnings++; warn('plan.md에 Milestones 섹션이 없습니다.'); } }
|
|
67
|
-
const gf=path.join(root,'.harness/guideline.md');
|
|
68
|
-
if(exists(gf)){ const b=read(gf); if(b.includes('plan.md')&&b.includes('progress-tracker.md')) ok('guideline.md가 plan/progress를 참조합니다.'); else { failures++; fail('guideline.md가 plan.md/progress-tracker.md를 참조하지 않습니다.'); } }
|
|
69
64
|
const mf=path.join(root,'.harness/manifest.json');
|
|
70
65
|
if(exists(mf)){ const j=parseJsonSafe(read(mf),{}); if(j.language) return normalizeLanguage(j.language); }
|
|
71
66
|
const lp=path.join(root,'.harness/LANGUAGE');
|
|
@@ -158,7 +153,8 @@ Before editing, route the task. Read .harness/language-policy.md, .harness/conte
|
|
|
158
153
|
3. .harness/project-brief.md
|
|
159
154
|
4. .harness/current-state.md
|
|
160
155
|
5. .harness/language-policy.md
|
|
161
|
-
6. .harness/
|
|
156
|
+
6. .harness/leerness-maintenance.md
|
|
157
|
+
7. .harness/context-routing.md
|
|
162
158
|
7. .harness/writeback-policy.md
|
|
163
159
|
8. .harness/task-type-map.md
|
|
164
160
|
9. .harness/context-map.md
|
|
@@ -171,6 +167,13 @@ Before editing, route the task. Read .harness/language-policy.md, .harness/conte
|
|
|
171
167
|
- Use the configured project language consistently.
|
|
172
168
|
- Preserve code names, commands, file paths, environment variables, and API field names exactly.
|
|
173
169
|
|
|
170
|
+
## Leerness Version Maintenance
|
|
171
|
+
- Before long-running development, release, migration, or harness-related work, check .harness/leerness-maintenance.md.
|
|
172
|
+
- Prefer leerness self check . when Leerness is already available.
|
|
173
|
+
- If the installed package may be old, run npm view leerness version and compare it with leerness --version and .harness/HARNESS_VERSION.
|
|
174
|
+
- If npm reports a newer Leerness version, run npx --yes leerness@<latest> migrate . --dry-run first, inspect changes, then run the migration only when it is safe.
|
|
175
|
+
- Record version checks, fallback pinned versions, failed npm lookups, and migration decisions in task-log.md and session-handoff.md.
|
|
176
|
+
|
|
174
177
|
## Task Routing
|
|
175
178
|
- Feature/API work: architecture.md, feature-contracts.md, context-map.md, skills/feature-implementation.md.
|
|
176
179
|
- UI/design work: design-system.md, feature-contracts.md, skills/ui-consistency.md.
|
|
@@ -260,6 +263,55 @@ At the end of each session, list:
|
|
|
260
263
|
'.env.example': `# Leerness examples only. Copy to .env.local and fill locally. Never commit real secrets.\n`,
|
|
261
264
|
'.harness/LANGUAGE': '{{LANGUAGE}}\n',
|
|
262
265
|
'.harness/language-policy.md': '{{LANGUAGE_POLICY}}',
|
|
266
|
+
'.harness/leerness-maintenance.md': `${MARK}
|
|
267
|
+
---
|
|
268
|
+
leernessRole: leerness-maintenance
|
|
269
|
+
readWhen: [new-session, long-running-work, migration, release, debug, harness-related-work]
|
|
270
|
+
updateWhen: [version-check, migration-decision, npm-lookup-failure, pinned-version-fallback, leerness-upgrade]
|
|
271
|
+
doNotStore: [npm-token, github-token, credentials, cookies, secrets]
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
# Leerness Maintenance Policy
|
|
275
|
+
|
|
276
|
+
## Goal
|
|
277
|
+
|
|
278
|
+
Keep the project on an appropriate Leerness version so AI agents use the latest routing, migration, planning, and session-close rules.
|
|
279
|
+
|
|
280
|
+
## Required Check
|
|
281
|
+
|
|
282
|
+
Before major work, migration, release, or harness debugging:
|
|
283
|
+
|
|
284
|
+
1. Check the project version from .harness/HARNESS_VERSION.
|
|
285
|
+
2. Check the currently executable CLI with leerness --version when available.
|
|
286
|
+
3. Check npm registry with npm view leerness version.
|
|
287
|
+
4. If npm is unavailable, use the latest pinned version recorded in this project and record the failed lookup.
|
|
288
|
+
5. If a newer version exists, run dry-run migration first.
|
|
289
|
+
|
|
290
|
+
## Safe Migration Flow
|
|
291
|
+
|
|
292
|
+
Commands:
|
|
293
|
+
|
|
294
|
+
npm view leerness version
|
|
295
|
+
leerness --version
|
|
296
|
+
npx --yes leerness@<latest> migrate . --dry-run
|
|
297
|
+
npx --yes leerness@<latest> migrate .
|
|
298
|
+
npx --yes leerness@<latest> verify .
|
|
299
|
+
npx --yes leerness@<latest> debug .
|
|
300
|
+
|
|
301
|
+
## Non-Destructive Rule
|
|
302
|
+
|
|
303
|
+
- Do not overwrite project memory files unless the user explicitly requests it.
|
|
304
|
+
- Do not replace .env.example or .gitignore; merge only missing Leerness entries.
|
|
305
|
+
- Record unresolved risks in session-handoff.md.
|
|
306
|
+
|
|
307
|
+
## Writeback
|
|
308
|
+
|
|
309
|
+
After checking or migrating Leerness, update:
|
|
310
|
+
|
|
311
|
+
- .harness/task-log.md
|
|
312
|
+
- .harness/current-state.md when it changes the next work state
|
|
313
|
+
- .harness/session-handoff.md
|
|
314
|
+
`,
|
|
263
315
|
'.harness/HARNESS_VERSION': '{{VERSION}}\n',
|
|
264
316
|
'.harness/manifest.json': '{{MANIFEST}}\n',
|
|
265
317
|
'.harness/skills-lock.json': '{{SKILLS_LOCK}}\n',
|
|
@@ -486,7 +538,7 @@ Run or follow: leerness plan sync
|
|
|
486
538
|
};
|
|
487
539
|
|
|
488
540
|
const memoryFiles = new Set(['.harness/plan.md','.harness/guideline.md','.harness/project-brief.md','.harness/current-state.md','.harness/architecture.md','.harness/context-map.md','.harness/decisions.md','.harness/task-log.md','.harness/constraints.md','.harness/guardrails.md','.harness/design-system.md','.harness/feature-contracts.md','.harness/testing-strategy.md','.harness/review-checklist.md','.harness/release-checklist.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/language-policy.md','.harness/debug-guide.md','.harness/skill-index.md','.harness/secret-policy.md']);
|
|
489
|
-
const refreshableFiles = new Set(['AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/AX_PLAN_GUIDE.md','.harness/AX_SKILL_LIBRARY_GUIDE.md','.harness/AX_MIGRATION_GUIDE.md','.harness/AX_NEW_PROJECT_GUIDE.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md','.harness/templates/end-of-session-report.md','.harness/debug-guide.md','.harness/language-policy.md','.harness/LANGUAGE','.harness/manifest.json','.harness/HARNESS_VERSION']);
|
|
541
|
+
const refreshableFiles = new Set(['AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/AX_PLAN_GUIDE.md','.harness/AX_SKILL_LIBRARY_GUIDE.md','.harness/AX_MIGRATION_GUIDE.md','.harness/AX_NEW_PROJECT_GUIDE.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md','.harness/templates/end-of-session-report.md','.harness/debug-guide.md','.harness/language-policy.md','.harness/leerness-maintenance.md','.harness/LANGUAGE','.harness/manifest.json','.harness/HARNESS_VERSION']);
|
|
490
542
|
function uniqueLinesAppend(current, addition){
|
|
491
543
|
const lines=current.split(/\r?\n/); const seen=new Set(lines.map(x=>x.trim()).filter(Boolean));
|
|
492
544
|
const add=addition.split(/\r?\n/).filter(line=>{ const t=line.trim(); if(!t||seen.has(t)) return false; seen.add(t); return true; });
|
|
@@ -513,7 +565,7 @@ function writeCoreSafely(root,file,body,opts={}){
|
|
|
513
565
|
}
|
|
514
566
|
if(dryRun) info('[dry-run] 보존: '+file+' (덮어쓰려면 --force)'); else ok('보존: '+file+' (덮어쓰려면 --force)'); return 'preserved';
|
|
515
567
|
}
|
|
516
|
-
function manifest(root,selectedSkills,language){ return JSON.stringify({name:projectName(root),harnessVersion:VERSION,language,languageName:languageName(language),installedAt:now(),managedFiles:Object.keys(coreFiles),selectedSkills,nonDestructiveMigration:true,taskStatuses:['requested','planned','in-progress','waiting','on-hold','blocked','incomplete','done','dropped'],planEnabled:true},null,2); }
|
|
568
|
+
function manifest(root,selectedSkills,language){ return JSON.stringify({name:projectName(root),harnessVersion:VERSION,language,languageName:languageName(language),installedAt:now(),managedFiles:Object.keys(coreFiles),selectedSkills,nonDestructiveMigration:true,taskStatuses:['requested','planned','in-progress','waiting','on-hold','blocked','incomplete','done','dropped'],planEnabled:true, leernessMaintenanceEnabled:true},null,2); }
|
|
517
569
|
function skillsLock(root,selectedSkills){ const lock={harnessVersion:VERSION,installedAt:now(),installedSkills:{}}; for(const name of selectedSkills){ const meta=getSkillMeta(name); if(meta) lock.installedSkills[name]={version:meta.version,source:'bundled',title:meta.title,displayNameKo:meta.displayNameKo||meta.title,lastUpdated:meta.lastUpdated,verificationStatus:(meta.verification||{}).status||'unknown'}; } return JSON.stringify(lock,null,2); }
|
|
518
570
|
function makeContext(root,legacyText,selectedSkills,language){ const lang=normalizeLanguage(language||readConfiguredLanguage(root)||detectLanguage(root)); return { PROJECT:projectName(root), DATE:today(), VERSION, LANGUAGE:lang, LANGUAGE_NAME:languageName(lang), LANGUAGE_POLICY:languagePolicyBody(lang), LEGACY_AGENT:legacyBlock('agent instructions',pick(legacyText,['AGENTS.md','AGENT.md','CLAUDE.md','.cursorrules','.cursor/rules/project-rules.mdc','.cursor/rules/leerness.mdc','.github/copilot-instructions.md'])), LEGACY_PLAN:legacyBlock('plan context',pick(legacyText,['PLAN.md','plan.md','.harness/plan.md'])), LEGACY_BRIEF:legacyBlock('project context',pick(legacyText,['PROJECT_CONTEXT.md','CONTEXT.md','docs/guideline.md','AI_HARNESS.md','HARNESS.md'])), LEGACY_STATE:legacyBlock('state',pick(legacyText,['CURRENT_STATE.md','TASK_LOG.md','docs/history.md'])), LEGACY_ARCH:legacyBlock('architecture',pick(legacyText,['ARCHITECTURE.md'])), LEGACY_DECISIONS:legacyBlock('decisions',pick(legacyText,['DECISIONS.md'])), MANIFEST:manifest(root,selectedSkills,lang), SKILLS_LOCK:skillsLock(root,selectedSkills) }; }
|
|
519
571
|
|
|
@@ -728,12 +780,12 @@ function taskCommand(args,flags){
|
|
|
728
780
|
}
|
|
729
781
|
function debugHarness(root){
|
|
730
782
|
root=path.resolve(root||process.cwd()); banner(); let failures=0,warnings=0;
|
|
731
|
-
const required=['AGENTS.md','.harness/plan.md','.harness/guideline.md','.harness/language-policy.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/session-close-policy.md','.harness/progress-tracker.md','.harness/anti-lazy-work-policy.md','.harness/debug-guide.md'];
|
|
783
|
+
const required=['AGENTS.md','.harness/plan.md','.harness/guideline.md','.harness/language-policy.md','.harness/leerness-maintenance.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/session-close-policy.md','.harness/progress-tracker.md','.harness/anti-lazy-work-policy.md','.harness/debug-guide.md'];
|
|
732
784
|
for(const f of required){ if(exists(path.join(root,f))) ok('존재: '+f); else { failures++; fail('누락: '+f); } }
|
|
733
785
|
const ag=path.join(root,'AGENTS.md');
|
|
734
786
|
if(exists(ag)){
|
|
735
787
|
const body=read(ag);
|
|
736
|
-
for(const term of ['plan.md','guideline.md','language-policy.md','context-routing.md','writeback-policy.md','progress-tracker.md','anti-lazy-work-policy.md','End-of-Session Contract']){
|
|
788
|
+
for(const term of ['plan.md','guideline.md','language-policy.md','leerness-maintenance.md','context-routing.md','writeback-policy.md','progress-tracker.md','anti-lazy-work-policy.md','End-of-Session Contract']){
|
|
737
789
|
if(body.includes(term)) ok('AGENTS 방향지시 포함: '+term); else { failures++; fail('AGENTS 방향지시 누락: '+term); }
|
|
738
790
|
}
|
|
739
791
|
}
|
|
@@ -783,9 +835,88 @@ function closeSession(root){
|
|
|
783
835
|
}
|
|
784
836
|
function sessionCommand(args){ const sub=args[1]||'close'; if(sub==='close'||sub==='handoff'||sub==='end') return closeSession(args[2]||process.cwd()); fail('알 수 없는 session 명령: '+sub); }
|
|
785
837
|
|
|
786
|
-
|
|
838
|
+
|
|
839
|
+
function compareVersions(a,b){
|
|
840
|
+
const pa=String(a||'0.0.0').trim().replace(/^v/,'').split('.').map(x=>parseInt(x,10)||0);
|
|
841
|
+
const pb=String(b||'0.0.0').trim().replace(/^v/,'').split('.').map(x=>parseInt(x,10)||0);
|
|
842
|
+
for(let i=0;i<Math.max(pa.length,pb.length,3);i++){ const x=pa[i]||0,y=pb[i]||0; if(x>y) return 1; if(x<y) return -1; }
|
|
843
|
+
return 0;
|
|
844
|
+
}
|
|
845
|
+
function projectHarnessVersion(root){ const p=path.join(root,'.harness/HARNESS_VERSION'); return exists(p)?read(p).trim():null; }
|
|
846
|
+
function npmLatestLeerness(timeoutMs=15000){
|
|
847
|
+
try{
|
|
848
|
+
const out=childProcess.execFileSync('npm',['view','leerness','version','--silent'],{encoding:'utf8',timeout:timeoutMs,stdio:['ignore','pipe','pipe']}).trim();
|
|
849
|
+
return {ok:true,version:out,error:null};
|
|
850
|
+
}catch(e){ return {ok:false,version:null,error:(e.stderr&&String(e.stderr).trim())||e.message}; }
|
|
851
|
+
}
|
|
852
|
+
function appendTaskLog(root,lines){
|
|
853
|
+
const p=path.join(root,'.harness/task-log.md'); if(!exists(p)) return;
|
|
854
|
+
const body=read(p).replace(/\s*$/,'\n')+`\n## ${today()} — Leerness maintenance\n`+lines.map(x=>'- '+x).join('\n')+'\n';
|
|
855
|
+
write(p,body);
|
|
856
|
+
}
|
|
857
|
+
function selfCheck(root,flags={}){
|
|
858
|
+
root=path.resolve(root||process.cwd()); banner();
|
|
859
|
+
const projectVersion=projectHarnessVersion(root);
|
|
860
|
+
log('Leerness self check');
|
|
861
|
+
log(' Project path: '+root);
|
|
862
|
+
log(' CLI version: '+VERSION);
|
|
863
|
+
log(' Project HARNESS_VERSION: '+(projectVersion||'not installed'));
|
|
864
|
+
log(' npm check command: npm view leerness version');
|
|
865
|
+
const res=npmLatestLeerness(flags.timeout?Number(flags.timeout):15000);
|
|
866
|
+
if(!res.ok){
|
|
867
|
+
warn('npm 최신 버전 확인 실패: '+res.error);
|
|
868
|
+
warn('네트워크 또는 npm registry 문제일 수 있습니다. 고정 버전으로 검증하고, 실패 사실을 session-handoff에 기록하세요.');
|
|
869
|
+
appendTaskLog(root,[`npm view leerness version failed: ${res.error}`,`CLI version used: ${VERSION}`,`Project HARNESS_VERSION: ${projectVersion||'not installed'}`]);
|
|
870
|
+
process.exitCode=flags.strict?1:0;
|
|
871
|
+
return {latest:null,projectVersion,cliVersion:VERSION,needsMigration:false};
|
|
872
|
+
}
|
|
873
|
+
const latest=res.version;
|
|
874
|
+
log(' npm latest: '+latest);
|
|
875
|
+
const newerThanCli=compareVersions(latest,VERSION)>0;
|
|
876
|
+
const newerThanProject=projectVersion?compareVersions(latest,projectVersion)>0:true;
|
|
877
|
+
const needs=Boolean(newerThanCli||newerThanProject||projectVersion!==VERSION);
|
|
878
|
+
if(!needs){ ok('Leerness is current for this project.'); }
|
|
879
|
+
else{
|
|
880
|
+
warn('Leerness update/migration review recommended.');
|
|
881
|
+
log('');
|
|
882
|
+
log('Recommended safe flow:');
|
|
883
|
+
log(` npx --yes leerness@${latest} migrate "${root}" --dry-run`);
|
|
884
|
+
log(` npx --yes leerness@${latest} migrate "${root}"`);
|
|
885
|
+
log(` npx --yes leerness@${latest} verify "${root}"`);
|
|
886
|
+
log(` npx --yes leerness@${latest} debug "${root}"`);
|
|
887
|
+
}
|
|
888
|
+
appendTaskLog(root,[`npm latest checked: ${latest}`,`CLI version used: ${VERSION}`,`Project HARNESS_VERSION: ${projectVersion||'not installed'}`,`Migration recommended: ${needs?'yes':'no'}`]);
|
|
889
|
+
return {latest,projectVersion,cliVersion:VERSION,needsMigration:needs};
|
|
890
|
+
}
|
|
891
|
+
function selfMigrate(root,flags={}){
|
|
892
|
+
root=path.resolve(root||process.cwd()); const r=selfCheck(root,flags); if(!r.latest) return;
|
|
893
|
+
if(!r.needsMigration){ ok('마이그레이션 필요 없음'); return; }
|
|
894
|
+
const dry=flags['dry-run']||flags.dryRun||!flags.execute;
|
|
895
|
+
const args=['--yes',`leerness@${r.latest}`,'migrate',root];
|
|
896
|
+
if(dry) args.push('--dry-run');
|
|
897
|
+
if(!flags.execute){
|
|
898
|
+
warn('기본값은 실행하지 않습니다. 실제 마이그레이션은 --execute를 붙이세요.');
|
|
899
|
+
log('Would run: npx '+args.map(x=>JSON.stringify(x)).join(' '));
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
info('실행: npx '+args.join(' '));
|
|
903
|
+
const cp=childProcess.spawnSync('npx',args,{stdio:'inherit',env:process.env});
|
|
904
|
+
if(cp.status!==0){ fail('마이그레이션 실행 실패: exit '+cp.status); process.exitCode=cp.status||1; return; }
|
|
905
|
+
ok('마이그레이션 실행 완료');
|
|
906
|
+
appendTaskLog(root,[`Executed npx ${args.join(' ')}`]);
|
|
907
|
+
}
|
|
908
|
+
function selfCommand(args,flags){
|
|
909
|
+
const sub=args[1]||'check';
|
|
910
|
+
const root=flags.path||args[2]||process.cwd();
|
|
911
|
+
if(sub==='check'||sub==='status'||sub==='version') return selfCheck(root,flags);
|
|
912
|
+
if(sub==='migrate'||sub==='upgrade') return selfMigrate(root,flags);
|
|
913
|
+
if(sub==='report') return routeCommand('harness-maintenance');
|
|
914
|
+
fail('알 수 없는 self 명령: '+sub); process.exitCode=1;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const routeData={planning:{read:['plan.md','progress-tracker.md','project-brief.md','current-state.md','guideline.md'],update:['plan.md','progress-tracker.md','current-state.md','session-handoff.md']},feature:{read:['plan.md','progress-tracker.md','project-brief.md','current-state.md','architecture.md','context-map.md','feature-contracts.md'],update:['current-state.md','task-log.md','session-handoff.md','feature-contracts.md']},ui:{read:['design-system.md','feature-contracts.md','context-map.md'],update:['design-system.md','feature-contracts.md','current-state.md','task-log.md']},debugging:{read:['current-state.md','task-log.md','feature-contracts.md','testing-strategy.md'],update:['task-log.md','current-state.md','session-handoff.md']},refactor:{read:['architecture.md','decisions.md','guardrails.md'],update:['architecture.md','decisions.md','task-log.md']},release:{read:['plan.md','progress-tracker.md','release-checklist.md','testing-strategy.md','current-state.md','decisions.md','secret-policy.md'],update:['release-checklist.md','task-log.md','current-state.md','session-handoff.md']},migration:{read:['AX_MIGRATION_GUIDE.md','context-routing.md','writeback-policy.md'],update:['Only missing files by default','Use --force only when requested']},'new-install':{read:['AX_NEW_PROJECT_GUIDE.md','AX_PLAN_GUIDE.md','actual project files'],update:['plan.md','project-brief.md','architecture.md','context-map.md','design-system.md','feature-contracts.md','release-checklist.md']},'skill-library':{read:['AX_SKILL_LIBRARY_GUIDE.md','skill-index.md','secret-policy.md'],update:['skill-index.md','skills-lock.json','task-log.md']},documentation:{read:['writeback-policy.md','context-routing.md'],update:['specific memory file','task-log.md','session-handoff.md']},debug:{read:['debug-guide.md','AGENTS.md','plan.md','guideline.md','language-policy.md','leerness-maintenance.md','context-routing.md','writeback-policy.md','progress-tracker.md','session-close-policy.md'],update:['debug findings in task-log.md','progress-tracker.md when user-requested debug work changes']},'harness-maintenance':{read:['leerness-maintenance.md','HARNESS_VERSION','manifest.json','task-log.md','session-handoff.md'],update:['task-log.md','current-state.md','session-handoff.md','HARNESS_VERSION after safe migration']},'session-close':{read:['session-close-policy.md','plan.md','guideline.md','progress-tracker.md','current-state.md','task-log.md','session-handoff.md','anti-lazy-work-policy.md'],update:['session-handoff.md','progress-tracker.md','current-state.md','task-log.md','relevant changed memory files']}};
|
|
787
918
|
function routePath(x){ if(String(x).includes('/')||String(x).endsWith('.md')&&['AGENTS.md','CLAUDE.md','README.md'].includes(String(x))) return String(x); if(String(x).includes(' ')) return String(x); return '.harness/'+x; }
|
|
788
919
|
function routeCommand(task){ banner(); if(!task||task==='list'){ log('사용 가능한 task type: '+Object.keys(routeData).join(', ')); return; } const r=routeData[task]; if(!r){ fail('알 수 없는 task type: '+task); process.exitCode=1; return; } log('Task route: '+task); log('\nRead before work:'); r.read.forEach(x=>log(' - '+routePath(x))); log('\nUpdate after work:'); r.update.forEach(x=>log(' - '+routePath(x))); }
|
|
789
|
-
function help(){ log(['Leerness v'+VERSION,'','Usage:',' leerness init [path] [--yes] [--language auto|ko|en] [--skills recommended|all|office,commerce-api] [--force]',' leerness migrate [path] [--dry-run] [--language auto|ko|en] [--force]',' leerness status [path]',' leerness verify [path]',' leerness debug [path]',' leerness route <planning|feature|ui|debugging|refactor|release|migration|new-install|skill-library|documentation|debug|session-close>','','Plan:',' leerness plan show [path]',' leerness plan init [path] --goal "project goal"',' leerness plan add "milestone or scope" [--status planned]',' leerness plan drop "item" --reason "user excluded"',' leerness plan update M-0002 --status in-progress --progress 40',' leerness plan progress [path]',' leerness plan sync [path]','','Tasks:',' leerness task list [--status planned,waiting,on-hold]',' leerness task add "request" [--status planned]',' leerness task update T-0002 --status in-progress',' leerness task drop T-0002 --reason "user dropped"','','Session:',' leerness session close [path]','','Skills:',' leerness skill list',' leerness skill info <name>',' leerness skill add <name> [--path <project>]',' leerness skill remove <name> [--path <project>]',' leerness skill learn <name> --from <validated-skill-path>','','Skill library:',' leerness library guide [path]',' leerness library validate <path> [--strict-ai]',' leerness library verify <path> --ai --reviewer leerness-ai',' leerness library build <path> [--package leerness-skill-name]',' leerness library publish <built-library> --target npm|git [--execute]',''].join('\n')); }
|
|
790
|
-
async function main(){ const parsed=parseArgs(process.argv.slice(2)); const args=parsed.positionals, flags=parsed.flags; if(flags.version||flags.v){ log(VERSION); return; } if(flags.help||flags.h){ help(); return; } const cmd=args[0]||'init'; if(cmd==='init') return init(args[1]||process.cwd(),flags); if(cmd==='migrate') return migrate(args[1]||process.cwd(),flags); if(cmd==='status') return status(args[1]||process.cwd()); if(cmd==='verify') return verify(args[1]||process.cwd()); if(cmd==='route') return routeCommand(args[1]||'list'); if(cmd==='debug') return debugHarness(args[1]||process.cwd()); if(cmd==='plan') return planCommand(args,flags); if(cmd==='task') return taskCommand(args,flags); if(cmd==='session') return sessionCommand(args); if(cmd==='skill') return skillCommand(args,flags); if(cmd==='library') return libraryCommand(args,flags); help(); process.exitCode=1; }
|
|
920
|
+
function help(){ log(['Leerness v'+VERSION,'','Usage:',' leerness init [path] [--yes] [--language auto|ko|en] [--skills recommended|all|office,commerce-api] [--force]',' leerness migrate [path] [--dry-run] [--language auto|ko|en] [--force]',' leerness status [path]',' leerness verify [path]',' leerness debug [path]',' leerness route <planning|feature|ui|debugging|refactor|release|migration|new-install|skill-library|documentation|debug|harness-maintenance|session-close>','','Plan:',' leerness plan show [path]',' leerness plan init [path] --goal "project goal"',' leerness plan add "milestone or scope" [--status planned]',' leerness plan drop "item" --reason "user excluded"',' leerness plan update M-0002 --status in-progress --progress 40',' leerness plan progress [path]',' leerness plan sync [path]','','Tasks:',' leerness task list [--status planned,waiting,on-hold]',' leerness task add "request" [--status planned]',' leerness task update T-0002 --status in-progress',' leerness task drop T-0002 --reason "user dropped"','','Session:',' leerness session close [path]','','Leerness maintenance:',' leerness self check [path]',' leerness self migrate [path] [--dry-run] [--execute]',' leerness self report [path]','','Skills:',' leerness skill list',' leerness skill info <name>',' leerness skill add <name> [--path <project>]',' leerness skill remove <name> [--path <project>]',' leerness skill learn <name> --from <validated-skill-path>','','Skill library:',' leerness library guide [path]',' leerness library validate <path> [--strict-ai]',' leerness library verify <path> --ai --reviewer leerness-ai',' leerness library build <path> [--package leerness-skill-name]',' leerness library publish <built-library> --target npm|git [--execute]',''].join('\n')); }
|
|
921
|
+
async function main(){ const parsed=parseArgs(process.argv.slice(2)); const args=parsed.positionals, flags=parsed.flags; if(flags.version||flags.v){ log(VERSION); return; } if(flags.help||flags.h){ help(); return; } const cmd=args[0]||'init'; if(cmd==='init') return init(args[1]||process.cwd(),flags); if(cmd==='migrate') return migrate(args[1]||process.cwd(),flags); if(cmd==='status') return status(args[1]||process.cwd()); if(cmd==='verify') return verify(args[1]||process.cwd()); if(cmd==='route') return routeCommand(args[1]||'list'); if(cmd==='debug') return debugHarness(args[1]||process.cwd()); if(cmd==='plan') return planCommand(args,flags); if(cmd==='task') return taskCommand(args,flags); if(cmd==='session') return sessionCommand(args); if(cmd==='self') return selfCommand(args,flags); if(cmd==='skill') return skillCommand(args,flags); if(cmd==='library') return libraryCommand(args,flags); help(); process.exitCode=1; }
|
|
791
922
|
main().catch(err=>{ fail(err.stack||err.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Leerness:
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Leerness: AI 에이전트가 npm 최신 버전 확인, 안전 마이그레이션 판단, 계획/진행률/세션 인수인계를 수행하도록 돕는 AX 최적화 개발 하네스.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"leerness",
|
|
7
7
|
"ai",
|
|
@@ -43,7 +43,12 @@
|
|
|
43
43
|
"guideline-policy",
|
|
44
44
|
"planning-router",
|
|
45
45
|
"project-roadmap",
|
|
46
|
-
"scope-drop"
|
|
46
|
+
"scope-drop",
|
|
47
|
+
"version-maintenance",
|
|
48
|
+
"npm-view",
|
|
49
|
+
"safe-migration",
|
|
50
|
+
"self-check",
|
|
51
|
+
"harness-upgrade"
|
|
47
52
|
],
|
|
48
53
|
"bin": {
|
|
49
54
|
"leerness": "./bin/harness.js"
|
|
@@ -57,7 +62,7 @@
|
|
|
57
62
|
"LICENSE"
|
|
58
63
|
],
|
|
59
64
|
"scripts": {
|
|
60
|
-
"test": "node ./bin/harness.js --help && node ./bin/harness.js init --yes --language ko --skills office,commerce-api,ai-verified-skill-publisher ./tmp-harness-test && node ./bin/harness.js plan add \"테스트 계획 항목\" --status planned --path ./tmp-harness-test && node ./bin/harness.js plan progress ./tmp-harness-test && node ./bin/harness.js
|
|
65
|
+
"test": "node ./bin/harness.js --help && node ./bin/harness.js init --yes --language ko --skills office,commerce-api,ai-verified-skill-publisher ./tmp-harness-test && node ./bin/harness.js plan add \"테스트 계획 항목\" --status planned --path ./tmp-harness-test && node ./bin/harness.js plan progress ./tmp-harness-test && node ./bin/harness.js task add \"테스트 예정 작업\" --status planned --path ./tmp-harness-test && node ./bin/harness.js route harness-maintenance && node ./bin/harness.js self report ./tmp-harness-test && node ./bin/harness.js session close ./tmp-harness-test && node ./bin/harness.js debug ./tmp-harness-test && node ./bin/harness.js verify ./tmp-harness-test",
|
|
61
66
|
"prepack": "node ./bin/harness.js --version"
|
|
62
67
|
},
|
|
63
68
|
"repository": {
|