leerness 1.9.9 → 1.9.10
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 +36 -0
- package/bin/harness.js +173 -12
- package/package.json +1 -1
- package/scripts/e2e.js +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.10 — 2026-05-12
|
|
4
|
+
|
|
5
|
+
**leerness-skillpack 분리 + release publish 강화 (git remote 자동 감지 + GitHub Release + gh-pages 배포)**.
|
|
6
|
+
|
|
7
|
+
### Changed — 스킬 카탈로그 동적 로드
|
|
8
|
+
|
|
9
|
+
- `leerness-skillpack`이 npm에 별도 패키지로 분리됨. leerness 본 패키지는 `_tryLoadSkillpack()`으로 다음 순서로 동적 로드:
|
|
10
|
+
1. `require('leerness-skillpack/catalog.json')` 시도
|
|
11
|
+
2. `<cwd>/node_modules/leerness-skillpack/catalog.json` 탐색
|
|
12
|
+
3. `npm root -g`의 `leerness-skillpack/catalog.json` 탐색
|
|
13
|
+
4. `LEERNESS_SKILLPACK_PATH` 환경변수 경로
|
|
14
|
+
5. 모두 실패 시 leerness 본 패키지의 내장 fallback (1.9.x 호환 유지)
|
|
15
|
+
- `leerness init` 출력에 `Skill catalog source: skillpack v1.0.0 | builtin (fallback)` 안내.
|
|
16
|
+
- `leerness skill list` 헤더에 카탈로그 출처 + 출처 컬럼에 `skillpack` / `builtin` / `user` 표시.
|
|
17
|
+
|
|
18
|
+
### Added — release publish 강화
|
|
19
|
+
|
|
20
|
+
- `detectGitRemote(root)`: 현재 디렉토리의 `git remote -v origin` 자동 감지 + GitHub owner/repo 추출.
|
|
21
|
+
- `leerness release publish` 신규 플래그:
|
|
22
|
+
- `--auto` — remote 있으면 자동 `git push` (편의)
|
|
23
|
+
- `--gh-release` — gh CLI로 GitHub Release 자동 생성 (`v<version>` 태그 + 자동 노트 + tarball 첨부)
|
|
24
|
+
- `--gh-pages` — `gh-pages` branch에 정적 파일 자동 배포 (orphan 또는 기존 branch). 기본 소스는 `roadmap.html`, `--gh-pages-src <file>` 또는 `--roadmap <file>`로 지정.
|
|
25
|
+
- `--pack` — npm pack만 명시적 실행
|
|
26
|
+
- `gh-pages` 배포는 임시 git worktree로 처리해 현재 작업 트리에 영향 없음. 배포 후 `https://<owner>.github.io/<repo>/` URL 안내.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
기존 1.9.x 사용자는 `npx leerness@latest update . --yes`로 즉시 자동 마이그레이션됩니다. leerness-skillpack은 선택 설치:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install leerness-skillpack # 본 카탈로그 사용
|
|
34
|
+
# 또는 그대로 두면 leerness 내장 fallback이 동작 (기존과 동일)
|
|
35
|
+
```
|
|
36
|
+
|
|
3
37
|
## 1.9.9 — 2026-05-12
|
|
4
38
|
|
|
39
|
+
- 1.9.9 빌드 + GitHub 배포
|
|
40
|
+
|
|
5
41
|
**1.9.8 시연 중 자체 도그푸드(dogfood)로 빌드된 패치 — 룰 시스템이 정확히 작동한 증거**.
|
|
6
42
|
|
|
7
43
|
### Fixed
|
package/bin/harness.js
CHANGED
|
@@ -6,12 +6,64 @@ const path = require('path');
|
|
|
6
6
|
const cp = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const VERSION = '1.9.
|
|
9
|
+
const VERSION = '1.9.10';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// 1.9.10: leerness-skillpack 동적 로드 (선택). 없으면 BUILTIN 사용.
|
|
15
|
+
function _tryLoadSkillpack() {
|
|
16
|
+
// 1) 정상 require resolution
|
|
17
|
+
try { return { src: 'require', data: require('leerness-skillpack/catalog.json') }; } catch {}
|
|
18
|
+
// 2) cwd/node_modules
|
|
19
|
+
try {
|
|
20
|
+
const f = path.join(process.cwd(), 'node_modules/leerness-skillpack/catalog.json');
|
|
21
|
+
if (fs.existsSync(f)) return { src: 'cwd', data: JSON.parse(fs.readFileSync(f, 'utf8')) };
|
|
22
|
+
} catch {}
|
|
23
|
+
// 3) npm global root
|
|
24
|
+
try {
|
|
25
|
+
const root = cp.execSync('npm root -g', { encoding: 'utf8', timeout: 4000 }).trim();
|
|
26
|
+
const f = path.join(root, 'leerness-skillpack/catalog.json');
|
|
27
|
+
if (fs.existsSync(f)) return { src: 'global', data: JSON.parse(fs.readFileSync(f, 'utf8')) };
|
|
28
|
+
} catch {}
|
|
29
|
+
// 4) 환경변수 명시 경로
|
|
30
|
+
if (process.env.LEERNESS_SKILLPACK_PATH) {
|
|
31
|
+
try {
|
|
32
|
+
const f = path.resolve(process.env.LEERNESS_SKILLPACK_PATH);
|
|
33
|
+
const target = f.endsWith('.json') ? f : path.join(f, 'catalog.json');
|
|
34
|
+
if (fs.existsSync(target)) return { src: 'env', data: JSON.parse(fs.readFileSync(target, 'utf8')) };
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let SKILLPACK_SOURCE = 'builtin';
|
|
41
|
+
let SKILLPACK_META = null;
|
|
42
|
+
function _loadSkillCatalog() {
|
|
43
|
+
const sp = _tryLoadSkillpack();
|
|
44
|
+
if (sp && sp.data && Array.isArray(sp.data.skills)) {
|
|
45
|
+
SKILLPACK_SOURCE = sp.src;
|
|
46
|
+
SKILLPACK_META = { name: sp.data.name, version: sp.data.version };
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const s of sp.data.skills) {
|
|
49
|
+
out[s.id] = {
|
|
50
|
+
displayNameKo: s.displayNameKo,
|
|
51
|
+
version: s.version,
|
|
52
|
+
lastUpdated: s.lastUpdated,
|
|
53
|
+
verification: s.verification,
|
|
54
|
+
capabilities: s.capabilities,
|
|
55
|
+
_source: 'skillpack'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
SKILLPACK_SOURCE = 'builtin';
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of Object.entries(BUILTIN_CATALOG)) out[k] = { ...v, _source: 'builtin' };
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const BUILTIN_CATALOG = {
|
|
15
67
|
'office': { displayNameKo: '마이크로소프트 오피스 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Word/Excel/PowerPoint 문서 자동화', '템플릿 기반 문서 생성', '표/차트/요약 문서화', '민감정보 제외 규칙 적용'] },
|
|
16
68
|
'commerce-api': { displayNameKo: '커머스 API 연동 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['쿠팡·롯데온·스마트스토어 API 연동 설계', '주문/상품/매출 동기화', '환경변수 기반 인증 분리', '레이트리밋/재시도/오류 처리'] },
|
|
17
69
|
'crawling': { displayNameKo: '크롤링·브라우저 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Playwright 기반 자동화', '다운로드/로그인 세션 처리', '스크린샷 기반 실패 진단', '약관/권한/차단 위험 점검'] },
|
|
@@ -22,6 +74,9 @@ const skillCatalog = {
|
|
|
22
74
|
'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] }
|
|
23
75
|
};
|
|
24
76
|
|
|
77
|
+
// 1.9.10: skillCatalog는 skillpack 우선, fallback builtin. _loadSkillCatalog 호출은 BUILTIN_CATALOG 정의 후.
|
|
78
|
+
const skillCatalog = _loadSkillCatalog();
|
|
79
|
+
|
|
25
80
|
const routes = {
|
|
26
81
|
planning: { read: ['.harness/plan.md','.harness/progress-tracker.md','.harness/project-brief.md','.harness/current-state.md','.harness/guideline.md'], update: ['.harness/plan.md','.harness/progress-tracker.md','.harness/current-state.md','.harness/session-handoff.md'] },
|
|
27
82
|
feature: { read: ['.harness/plan.md','.harness/current-state.md','.harness/architecture.md','.harness/context-map.md','.harness/feature-contracts.md','.harness/skills/feature-implementation/README.md','.harness/reuse-map.md'], update: ['.harness/progress-tracker.md','.harness/feature-contracts.md','.harness/current-state.md','.harness/task-log.md','.harness/session-handoff.md'] },
|
|
@@ -335,6 +390,9 @@ async function install(root, opts = {}) {
|
|
|
335
390
|
log(`Target: ${root}`);
|
|
336
391
|
log(`Language: ${lang}`);
|
|
337
392
|
log(`Skills: ${skills.length ? skills.join(', ') : 'none'}`);
|
|
393
|
+
// 1.9.10: 스킬 카탈로그 출처 안내
|
|
394
|
+
if (SKILLPACK_SOURCE === 'builtin') log(`Skill catalog source: builtin (leerness-skillpack 미설치 — \`npm i leerness-skillpack\`로 확장 가능)`);
|
|
395
|
+
else log(`Skill catalog source: ${SKILLPACK_SOURCE} (leerness-skillpack${SKILLPACK_META ? ` v${SKILLPACK_META.version}` : ''})`);
|
|
338
396
|
const files = coreFiles(root, lang, skills);
|
|
339
397
|
const backup = createBackup(root, opts.force ? 'force' : (opts.migration ? 'migration' : 'init'), files, opts.dry);
|
|
340
398
|
if (opts.dry) {
|
|
@@ -430,7 +488,8 @@ function saveUserSkill(root, id, data) {
|
|
|
430
488
|
|
|
431
489
|
function listAllSkills(root) {
|
|
432
490
|
const out = {};
|
|
433
|
-
|
|
491
|
+
// 1.9.10: skillCatalog의 _source('skillpack' 또는 'builtin')를 보존
|
|
492
|
+
for (const [k, v] of Object.entries(skillCatalog)) out[k] = { ...v, _source: v._source || 'builtin' };
|
|
434
493
|
if (root) {
|
|
435
494
|
const dir = userSkillsDir(root);
|
|
436
495
|
if (exists(dir)) {
|
|
@@ -448,6 +507,8 @@ function listAllSkills(root) {
|
|
|
448
507
|
|
|
449
508
|
function skillList(root) {
|
|
450
509
|
const all = listAllSkills(root);
|
|
510
|
+
if (SKILLPACK_SOURCE !== 'builtin') log(`# skillpack 출처: ${SKILLPACK_SOURCE}${SKILLPACK_META ? ` (${SKILLPACK_META.name} v${SKILLPACK_META.version})` : ''}`);
|
|
511
|
+
else log('# skillpack 미설치 — builtin fallback 사용 (leerness 본 패키지 내장 카탈로그)');
|
|
451
512
|
log('| ID | 한글명 | 출처 | 능력(요약) | 사용횟수 | 최종 |');
|
|
452
513
|
log('|---|---|---|---|---|---|');
|
|
453
514
|
for (const [id, v] of Object.entries(all)) {
|
|
@@ -1528,21 +1589,121 @@ function releaseNote(root, text) {
|
|
|
1528
1589
|
ok(`CHANGELOG.md 갱신: [${version}] ${text}`);
|
|
1529
1590
|
}
|
|
1530
1591
|
|
|
1592
|
+
// 1.9.10: git remote 자동 감지 + gh-release + gh-pages 배포
|
|
1593
|
+
function detectGitRemote(root) {
|
|
1594
|
+
const r = cp.spawnSync('git', ['remote', 'get-url', 'origin'], { cwd: root, encoding: 'utf8', shell: true });
|
|
1595
|
+
if (r.status !== 0) return null;
|
|
1596
|
+
const url = (r.stdout || '').trim();
|
|
1597
|
+
if (!url) return null;
|
|
1598
|
+
// owner/repo 추출
|
|
1599
|
+
const m = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?/);
|
|
1600
|
+
return { url, host: m ? 'github' : 'unknown', owner: m ? m[1] : null, repo: m ? m[2] : null };
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function getCurrentVersion(root) {
|
|
1604
|
+
const pkgF = path.join(root, 'package.json');
|
|
1605
|
+
if (!exists(pkgF)) return null;
|
|
1606
|
+
try { return JSON.parse(read(pkgF)).version || null; } catch { return null; }
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function deployGhPages(root, sourceFile) {
|
|
1610
|
+
const remote = detectGitRemote(root);
|
|
1611
|
+
if (!remote || remote.host !== 'github') { fail('GitHub remote가 없습니다 — gh-pages 배포 불가'); process.exitCode = 1; return; }
|
|
1612
|
+
const src = path.resolve(root, sourceFile);
|
|
1613
|
+
if (!exists(src)) { fail(`소스 파일 없음: ${src}`); process.exitCode = 1; return; }
|
|
1614
|
+
log(`# gh-pages deploy`);
|
|
1615
|
+
log(`Source: ${rel(root, src)}`);
|
|
1616
|
+
log(`Target: gh-pages branch of ${remote.owner}/${remote.repo}`);
|
|
1617
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1618
|
+
const wt = path.join(root, '.harness/cache', `ghpages-${stamp}`);
|
|
1619
|
+
mkdirp(path.dirname(wt));
|
|
1620
|
+
// worktree (기존 gh-pages 있으면 fetch, 없으면 orphan)
|
|
1621
|
+
const fetchR = cp.spawnSync('git', ['fetch', 'origin', 'gh-pages'], { cwd: root, encoding: 'utf8', shell: true });
|
|
1622
|
+
const hasBranch = fetchR.status === 0;
|
|
1623
|
+
let wtArgs;
|
|
1624
|
+
if (hasBranch) wtArgs = ['worktree', 'add', wt, 'origin/gh-pages'];
|
|
1625
|
+
else wtArgs = ['worktree', 'add', '--orphan', '-b', 'gh-pages', wt];
|
|
1626
|
+
const wtR = cp.spawnSync('git', wtArgs, { cwd: root, encoding: 'utf8', shell: true });
|
|
1627
|
+
if (wtR.status !== 0) { fail('worktree 생성 실패: ' + (wtR.stderr || '').slice(0, 200)); process.exitCode = 1; return; }
|
|
1628
|
+
try {
|
|
1629
|
+
// orphan인 경우 초기화
|
|
1630
|
+
if (!hasBranch) {
|
|
1631
|
+
cp.spawnSync('git', ['rm', '-rf', '.'], { cwd: wt, encoding: 'utf8', shell: true });
|
|
1632
|
+
}
|
|
1633
|
+
// 소스 복사 (index.html로 이름 변경)
|
|
1634
|
+
const destName = path.basename(src) === 'index.html' ? 'index.html' : 'index.html';
|
|
1635
|
+
fs.copyFileSync(src, path.join(wt, destName));
|
|
1636
|
+
// 원본 파일명도 보존
|
|
1637
|
+
if (path.basename(src) !== 'index.html') fs.copyFileSync(src, path.join(wt, path.basename(src)));
|
|
1638
|
+
cp.spawnSync('git', ['add', '-A'], { cwd: wt, encoding: 'utf8' });
|
|
1639
|
+
const commit = cp.spawnSync('git', ['commit', '-m', `deploy: ${path.basename(src)} ${stamp}`], { cwd: wt, encoding: 'utf8' });
|
|
1640
|
+
if (commit.status !== 0 && !/nothing to commit/.test(commit.stdout || '')) {
|
|
1641
|
+
fail('commit 실패: ' + (commit.stdout || commit.stderr || '').slice(0, 200));
|
|
1642
|
+
process.exitCode = 1;
|
|
1643
|
+
} else {
|
|
1644
|
+
const pushR = cp.spawnSync('git', ['push', 'origin', 'gh-pages'], { cwd: wt, encoding: 'utf8' });
|
|
1645
|
+
if (pushR.status !== 0) { fail('push 실패: ' + (pushR.stderr || '').slice(0, 200)); process.exitCode = 1; }
|
|
1646
|
+
else ok(`gh-pages push 완료 → https://${remote.owner}.github.io/${remote.repo}/`);
|
|
1647
|
+
}
|
|
1648
|
+
} finally {
|
|
1649
|
+
cp.spawnSync('git', ['worktree', 'remove', '--force', wt], { cwd: root, encoding: 'utf8', shell: true });
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1531
1653
|
function releasePublish(root) {
|
|
1532
1654
|
root = absRoot(root);
|
|
1533
1655
|
const dryRun = has('--dry-run');
|
|
1534
1656
|
log('# release publish');
|
|
1535
1657
|
log(`Mode: ${dryRun ? 'dry-run' : 'live'}`);
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
if (
|
|
1658
|
+
|
|
1659
|
+
// 1. git remote 자동 감지 (1.9.10)
|
|
1660
|
+
const remote = detectGitRemote(root);
|
|
1661
|
+
if (remote) log(`Git remote (origin): ${remote.host === 'github' ? `${remote.owner}/${remote.repo}` : remote.url}`);
|
|
1662
|
+
else log('Git remote: 없음');
|
|
1663
|
+
|
|
1664
|
+
// 2. npm pack (필요한 경우 — pack-only도 의미 있음)
|
|
1665
|
+
if (has('--pack') || has('--npm-publish') || (!has('--git-push') && !has('--gh-release') && !has('--gh-pages'))) {
|
|
1666
|
+
const packR = cp.spawnSync('npm', ['pack'], { cwd: root, encoding: 'utf8', shell: true });
|
|
1667
|
+
if (packR.status !== 0) { fail('npm pack 실패'); log(packR.stderr); process.exitCode = 1; return; }
|
|
1668
|
+
ok('npm pack 완료');
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// 3. git push (--git-push 또는 --auto + remote 있을 때)
|
|
1672
|
+
if (has('--git-push') || (has('--auto') && remote)) {
|
|
1540
1673
|
log('git push:');
|
|
1541
1674
|
const r1 = cp.spawnSync('git', ['push'], { cwd: root, encoding: 'utf8', shell: true });
|
|
1542
|
-
log(r1.stdout || r1.stderr || '(no output)');
|
|
1675
|
+
log((r1.stdout || r1.stderr || '').slice(-200) || '(no output)');
|
|
1543
1676
|
const r2 = cp.spawnSync('git', ['push', '--tags'], { cwd: root, encoding: 'utf8', shell: true });
|
|
1544
|
-
log(r2.stdout || r2.stderr || '(no output)');
|
|
1677
|
+
log((r2.stdout || r2.stderr || '').slice(-200) || '(no output)');
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// 4. GitHub Release (--gh-release, gh CLI 사용)
|
|
1681
|
+
if (has('--gh-release')) {
|
|
1682
|
+
if (!remote || remote.host !== 'github') { warn('--gh-release: GitHub remote 없음 — 스킵'); }
|
|
1683
|
+
else {
|
|
1684
|
+
const v = getCurrentVersion(root);
|
|
1685
|
+
if (!v) { warn('--gh-release: package.json#version 없음 — 스킵'); }
|
|
1686
|
+
else {
|
|
1687
|
+
const tag = `v${v}`;
|
|
1688
|
+
const ghArgs = ['release', 'create', tag, '--generate-notes', '--title', `${remote.repo} ${tag}`];
|
|
1689
|
+
const tarball = path.join(root, `${JSON.parse(read(path.join(root, 'package.json'))).name}-${v}.tgz`);
|
|
1690
|
+
if (exists(tarball)) ghArgs.push(tarball);
|
|
1691
|
+
log(`gh ${ghArgs.join(' ')}`);
|
|
1692
|
+
const ghR = cp.spawnSync('gh', ghArgs, { cwd: root, encoding: 'utf8', shell: true });
|
|
1693
|
+
log((ghR.stdout || ghR.stderr || '').slice(-300) || '(no output)');
|
|
1694
|
+
if (ghR.status !== 0) warn('gh release 생성 실패 (이미 존재할 수 있음)');
|
|
1695
|
+
else ok(`GitHub Release 생성: ${tag}`);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1545
1698
|
}
|
|
1699
|
+
|
|
1700
|
+
// 5. gh-pages 배포 (--gh-pages)
|
|
1701
|
+
if (has('--gh-pages')) {
|
|
1702
|
+
const src = arg('--gh-pages-src', null) || arg('--roadmap', null) || 'roadmap.html';
|
|
1703
|
+
deployGhPages(root, src);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// 6. npm publish (--npm-publish)
|
|
1546
1707
|
if (has('--npm-publish')) {
|
|
1547
1708
|
const args = dryRun ? ['publish', '--dry-run'] : ['publish', '--access', 'public'];
|
|
1548
1709
|
log('npm ' + args.join(' '));
|
|
@@ -2078,7 +2239,7 @@ function help() {
|
|
|
2078
2239
|
leerness rule list|verify|pause <id>|resume <id>|remove <id>|stop|resume-all
|
|
2079
2240
|
leerness release bump [--patch|--minor|--major] # package.json 자동 bump (1.9.8)
|
|
2080
2241
|
leerness release note "<내용>" # CHANGELOG.md 자동 추가 (1.9.8)
|
|
2081
|
-
leerness release publish [--dry-run] [--git-push] [--npm-publish] # 통합 배포 (1.9.8)\n leerness impact <target> [--all] # 변경 전 영향 분석 (기본 strong, --all로 weak 포함)\n leerness reuse find <query> # 기존 자원 검색 (재귀 안내)\n leerness reuse register <name> --where <p> --kind component|hook|util|api [--note ...]\n leerness ui consistency [path] [--strict] [--fail-on-violation]\n leerness graph [path] [--out <file>] # mermaid 의존성 그래프\n leerness guide [target] # impact + reuse + ui consistency 통합 가이드\n`);
|
|
2242
|
+
leerness release publish [--dry-run] [--pack] [--git-push] [--gh-release] [--gh-pages] [--gh-pages-src file] [--npm-publish] [--auto] # 통합 배포 (1.9.8 + 1.9.10)\n leerness impact <target> [--all] # 변경 전 영향 분석 (기본 strong, --all로 weak 포함)\n leerness reuse find <query> # 기존 자원 검색 (재귀 안내)\n leerness reuse register <name> --where <p> --kind component|hook|util|api [--note ...]\n leerness ui consistency [path] [--strict] [--fail-on-violation]\n leerness graph [path] [--out <file>] # mermaid 의존성 그래프\n leerness guide [target] # impact + reuse + ui consistency 통합 가이드\n`);
|
|
2082
2243
|
}
|
|
2083
2244
|
|
|
2084
2245
|
async function main() {
|
|
@@ -2127,9 +2288,9 @@ async function main() {
|
|
|
2127
2288
|
if (cmd === 'rule' && args[1] === 'stop') return ruleStop(arg('--path', process.cwd()));
|
|
2128
2289
|
if (cmd === 'rule' && args[1] === 'resume-all') return ruleResumeAll(arg('--path', process.cwd()));
|
|
2129
2290
|
if (cmd === 'rule' && args[1] === 'verify') return ruleVerifyCmd(arg('--path', process.cwd()));
|
|
2130
|
-
if (cmd === 'release' && args[1] === 'bump') return releaseBump(arg('--path', process.cwd()));
|
|
2291
|
+
if (cmd === 'release' && args[1] === 'bump') return releaseBump(args[2] || arg('--path', process.cwd()));
|
|
2131
2292
|
if (cmd === 'release' && args[1] === 'note') return releaseNote(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
2132
|
-
if (cmd === 'release' && args[1] === 'publish') return releasePublish(arg('--path', process.cwd()));
|
|
2293
|
+
if (cmd === 'release' && args[1] === 'publish') return releasePublish(args[2] || arg('--path', process.cwd()));
|
|
2133
2294
|
if (cmd === 'impact') return impactCmd(arg('--path', process.cwd()), args[1]);
|
|
2134
2295
|
if (cmd === 'reuse' && args[1] === 'find') return reuseFind(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
2135
2296
|
if (cmd === 'reuse' && args[1] === 'register') return reuseRegister(arg('--path', process.cwd()), args[2]);
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -235,6 +235,47 @@ total++;
|
|
|
235
235
|
if (!(strongOK && weakHint)) failed++;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
// 1.9.10 A: skillpack 동적 로드 (LEERNESS_SKILLPACK_PATH로 시뮬)
|
|
239
|
+
total++;
|
|
240
|
+
{
|
|
241
|
+
const skillpackDir = path.resolve(__dirname, '..', '..', 'leerness-skillpack');
|
|
242
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'list', '--path', tmp], {
|
|
243
|
+
encoding: 'utf8',
|
|
244
|
+
env: Object.assign({}, process.env, { LEERNESS_SKILLPACK_PATH: skillpackDir })
|
|
245
|
+
});
|
|
246
|
+
const ok = r.status === 0 && /skillpack 출처: env/.test(r.stdout) && /\| skillpack \|/.test(r.stdout);
|
|
247
|
+
console.log(ok ? '✓ B(1.9.10) skillpack 동적 로드 (env path)' : '✗ skillpack 로드 실패');
|
|
248
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
249
|
+
}
|
|
250
|
+
// 1.9.10 A: skillpack 없을 때 builtin fallback
|
|
251
|
+
total++;
|
|
252
|
+
{
|
|
253
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'list', '--path', tmp], {
|
|
254
|
+
encoding: 'utf8',
|
|
255
|
+
env: Object.assign({}, process.env, { LEERNESS_SKILLPACK_PATH: '' })
|
|
256
|
+
});
|
|
257
|
+
const ok = r.status === 0 && /builtin fallback/.test(r.stdout) && /\| builtin \|/.test(r.stdout);
|
|
258
|
+
console.log(ok ? '✓ B(1.9.10) builtin fallback (skillpack 없을 때)' : '✗ builtin fallback 실패');
|
|
259
|
+
if (!ok) failed++;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 1.9.10 B: detectGitRemote (가짜 git remote 시뮬은 어려움 — 실제 git 명령으로 확인)
|
|
263
|
+
total++;
|
|
264
|
+
{
|
|
265
|
+
// tmp는 git init이 없음 → detectGitRemote는 null → publish 호출 시 'Git remote: 없음' 출력
|
|
266
|
+
// 시뮬: tmp에 git init + remote add
|
|
267
|
+
cp.spawnSync('git', ['init'], { cwd: tmp, encoding: 'utf8', shell: true });
|
|
268
|
+
cp.spawnSync('git', ['remote', 'add', 'origin', 'https://github.com/test/repo.git'], { cwd: tmp, encoding: 'utf8', shell: true });
|
|
269
|
+
// package.json도 필요
|
|
270
|
+
if (!fs.existsSync(path.join(tmp, 'package.json'))) {
|
|
271
|
+
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({ name: 'e2e-test', version: '0.1.0' }));
|
|
272
|
+
}
|
|
273
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'release', 'publish', tmp, '--dry-run'], { encoding: 'utf8' });
|
|
274
|
+
const ok = /Git remote \(origin\): test\/repo/.test(r.stdout);
|
|
275
|
+
console.log(ok ? '✓ B(1.9.10) detectGitRemote: github owner/repo 추출' : `✗ remote 감지 실패\n${r.stdout.slice(0, 500)}`);
|
|
276
|
+
if (!ok) failed++;
|
|
277
|
+
}
|
|
278
|
+
|
|
238
279
|
// 1.9.8: rule add/list/pause/resume/remove
|
|
239
280
|
total++;
|
|
240
281
|
{
|