leerness 1.9.177 β†’ 1.9.178

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 CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.178 β€” 2026-05-21
4
+
5
+ **πŸ“¦ μ‚¬μš©μž λͺ…μ‹œ: `release sync-main` μžλ™ npm publish β€” .env NPM_TOKEN μ‚¬μš©.**
6
+
7
+ 자율 λͺ¨λ“œ 108 λΌμš΄λ“œ. μ‚¬μš©μž λͺ…μ‹œ: *"NPM μ•‘μ„ΈμŠ€ 토큰 .env에 μž…λ ₯ν•΄λ’€μŒ. μ—…λ°μ΄νŠΈλ  λ•Œλ§ˆλ‹€ κΉƒν—ˆλΈŒμ²˜λŸΌ NPM에도 μžλ™ 배포"*.
8
+
9
+ ### 톡합 흐름
10
+ ```bash
11
+ $ leerness release sync-main .
12
+ # leerness release sync-main (1.9.140)
13
+ from: release/1.9.178 β†’ main
14
+ βœ“ main merged: release/1.9.178
15
+ βœ“ main pushed β†’ origin/main
16
+
17
+ πŸ“¦ npm publish μžλ™ trigger (1.9.178)
18
+ leerness@1.9.178
19
+ npm publish μ‹œλ„ 쀑...
20
+ βœ“ npm publish μ™„λ£Œ: leerness@1.9.178
21
+ ```
22
+
23
+ β†’ git push ν›„ NPM μžλ™ publish β€” git μ›Œν¬ν”Œλ‘œμ™€ NPM μ›Œν¬ν”Œλ‘œ μ™„μ „ 톡합.
24
+
25
+ ### λ³΄μ•ˆ 섀계 (3쀑 μ•ˆμ „λ§)
26
+ 1. **토큰 ν™˜κ²½λ³€μˆ˜λ§Œ μ‚¬μš©**: `process.env.LEERNESS_NPM_TOKEN || process.env.NPM_TOKEN` (κ°’ μ ˆλŒ€ 둜그 X)
27
+ 2. **μž„μ‹œ `.npmrc` 격리**: `mkdtempSync` β†’ `.npmrc` (mode 0o600 μ†Œμœ μžλ§Œ 읽기) β†’ publish β†’ **finally μ¦‰μ‹œ μ‚­μ œ**
28
+ 3. **`.env` μžλ™ 보호**: 1.9.75+ audit이 `.gitignore`에 `.env` 등둝 κ°•μ œ (μ‹œν¬λ¦Ώ λˆ„λ½ 감지)
29
+
30
+ ### 쀑볡 publish 차단
31
+ - `npm view <pkg>@<version> version` 사전 호좜
32
+ - 이미 publish된 버전이면 skip + μ•Œλ¦Ό (race condition도 후속 처리)
33
+
34
+ ### μΉœμ ˆν•œ μ—λŸ¬ μ•ˆλ‚΄
35
+ - `EPUBLISHCONFLICT` β†’ "이미 publish됨 β€” skip"
36
+ - `EAUTH / 401 / 403` β†’ "토큰 κΆŒν•œ λΆ€μ‘± λ˜λŠ” 만료 β€” .env NPM_TOKEN μž¬λ°œκΈ‰ ν•„μš”"
37
+ - `ENEEDAUTH` β†’ "인증 λ―Έμž‘λ™ β€” 토큰 ν˜•μ‹ 확인 (npm_xxxxx)"
38
+ - 기타 β†’ λ§ˆμ§€λ§‰ 3쀄 stderr λ…ΈμΆœ
39
+
40
+ ### Opt-out (3κ°€μ§€)
41
+ 1. `--no-npm` ν”Œλž˜κ·Έ
42
+ 2. `LEERNESS_NO_NPM_PUBLISH=1` ν™˜κ²½λ³€μˆ˜
43
+ 3. `--dry-run-npm` ν”Œλž˜κ·Έ (dry-run mode)
44
+
45
+ ### Observability
46
+ `_recordRun(kind: 'npm_publish')` β€” λͺ¨λ“  publish μ‹œλ„ (성곡/μ‹€νŒ¨) μžλ™ 기둝.
47
+
48
+ ### Verified
49
+ - e2e 217/217 baseline μœ μ§€ (LEERNESS_NO_NPM_PUBLISH=1 ν™˜κ²½)
50
+ - stress-v123: **22/22** (ν•¨μˆ˜ μ •μ˜ 6 + 쀑볡 차단 4 + release 톡합 3 + μΉœμ ˆν•œ μ•ˆλ‚΄ 3 + λˆ„μ  νšŒκ·€ 6)
51
+ - VERSION = 1.9.178 / autonomous-rounds = 108 / main μžλ™ push 39 λΌμš΄λ“œ 연속 + **NPM μžλ™ publish 1 λΌμš΄λ“œ μ‹œμž‘**
52
+
53
+ ---
54
+
3
55
  ## 1.9.177 β€” 2026-05-21
4
56
 
5
57
  **πŸ” `task add` μžλ™ review-request trigger β€” μ‚¬μš©μž λͺ…μ‹œ 1.9.176 μžλ™ν™”.**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI μ½”λ”© μ—μ΄μ „νŠΈμ˜ κ±°μ§“ μ™„λ£ŒΒ·μ€‘λ³΅Β·λ§κ°Β·μΆ©λŒμ„ λ§‰μ•„μ£ΌλŠ” κ²€μˆ˜Β·κΈ°μ–΅Β·ν˜‘μ—… CLI ν•˜λ„€μŠ€.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.177-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v122-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-107-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-38_rounds-success)]() [![auto-review](https://img.shields.io/badge/task_add-μžλ™_review_trigger-success)]() [![review-request](https://img.shields.io/badge/review--request-사전_κ²€ν† _9_μ‹ ν˜Έ-success)]() [![repl-slash](https://img.shields.io/badge/REPL_slash-:web%2F:pc%2F:lsp%2F:review-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.178-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v123-22%2F22-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-108-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-39_rounds-success)]() [![npm-auto](https://img.shields.io/badge/npm_auto--publish-NPM__TOKEN_톡합-success)]() [![auto-review](https://img.shields.io/badge/task_add-μžλ™_review_trigger-success)]() [![repl-slash](https://img.shields.io/badge/REPL_slash-:web%2F:pc%2F:lsp%2F:review-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  β•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β•‘
13
13
  β•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•‘
14
14
  β•‘ β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β•‘
15
- β•‘ v1.9.177 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.178 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ” task add μžλ™ review trigger β€” default 사전 κ²€ν†  β•‘
17
+ β•‘ πŸ“¦ release sync-main μžλ™ npm publish (NPM_TOKEN .env 톡합) β•‘
18
18
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
19
19
  ```
20
20
 
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.9.177';
9
+ const VERSION = '1.9.178';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -7806,6 +7806,94 @@ function releaseSyncMainCmd(root) {
7806
7806
  else ok(`main pushed β†’ ${remoteName}/main`);
7807
7807
  // 5) return to source branch (release μž‘μ—… 흐름 보쑴)
7808
7808
  cp.spawnSync('git', ['checkout', fromBranch], { cwd: root, encoding: 'utf8' });
7809
+
7810
+ // 1.9.178: μžλ™ npm publish (μ‚¬μš©μž λͺ…μ‹œ β€” .env NPM_TOKEN 있으면 μžλ™ 배포).
7811
+ // opt-out: --no-npm λ˜λŠ” LEERNESS_NO_NPM_PUBLISH=1
7812
+ // 토큰 λ―Έμ„€μ • μ‹œ μΉœμ ˆν•œ μ•ˆλ‚΄ ν›„ skip (μ‹€νŒ¨ X).
7813
+ if (!has('--no-npm') && process.env.LEERNESS_NO_NPM_PUBLISH !== '1') {
7814
+ try { _publishToNpm(root, { dryRun: has('--dry-run-npm') }); } catch (e) { warn('npm publish μ‹œλ„ μ‹€νŒ¨ (계속): ' + e.message); }
7815
+ }
7816
+ }
7817
+
7818
+ // 1.9.178: NPM μžλ™ 배포 (μ‚¬μš©μž λͺ…μ‹œ β€” release sync-main ν›„ μžλ™ trigger).
7819
+ // λ³΄μ•ˆ:
7820
+ // - NPM_TOKEN λ˜λŠ” LEERNESS_NPM_TOKEN ν™˜κ²½λ³€μˆ˜μ—μ„œλ§Œ 읽음 (κ°’ μ ˆλŒ€ 둜그 X)
7821
+ // - μž„μ‹œ .npmrc 파일 (mkdtempSync) 생성 β†’ publish β†’ μ¦‰μ‹œ μ‚­μ œ (finally)
7822
+ // - 이미 publish된 버전 μžλ™ detect β†’ skip (쀑볡 publish 차단)
7823
+ function _publishToNpm(root, opts = {}) {
7824
+ root = absRoot(root || process.cwd());
7825
+ // .env μžλ™ λ‘œλ“œ (μ‚¬μš©μž λͺ…μ‹œ νŒ¨ν„΄ β€” 1.9.153)
7826
+ try { _loadEnvFile(root); } catch {}
7827
+ const token = process.env.LEERNESS_NPM_TOKEN || process.env.NPM_TOKEN;
7828
+ if (!token) {
7829
+ log(` ⚠ npm μžλ™ 배포 μŠ€ν‚΅: NPM_TOKEN λ―Έμ„€μ • (.env 에 NPM_TOKEN=npm_xxxxx μΆ”κ°€)`);
7830
+ return;
7831
+ }
7832
+ // package.json 확인
7833
+ const pkgPath = path.join(root, 'package.json');
7834
+ if (!exists(pkgPath)) {
7835
+ log(` ⚠ npm μžλ™ 배포 μŠ€ν‚΅: package.json μ—†μŒ (${rel(root, pkgPath)})`);
7836
+ return;
7837
+ }
7838
+ let pkg;
7839
+ try { pkg = JSON.parse(read(pkgPath)); } catch (e) { warn(`package.json νŒŒμ‹± μ‹€νŒ¨: ${e.message}`); return; }
7840
+ if (!pkg.name || !pkg.version) {
7841
+ log(` ⚠ npm μžλ™ 배포 μŠ€ν‚΅: name/version λˆ„λ½`);
7842
+ return;
7843
+ }
7844
+ const pkgName = pkg.name;
7845
+ const pkgVersion = pkg.version;
7846
+ log('');
7847
+ log(`πŸ“¦ npm publish μžλ™ trigger (1.9.178)`);
7848
+ log(` ${pkgName}@${pkgVersion}`);
7849
+
7850
+ // 1) 이미 publish된 버전인지 확인 (npm view <pkg>@<version> version)
7851
+ try {
7852
+ const viewR = cp.spawnSync('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
7853
+ cwd: root, encoding: 'utf8', shell: true, timeout: 15000
7854
+ });
7855
+ if (viewR.status === 0 && (viewR.stdout || '').trim() === pkgVersion) {
7856
+ log(` βœ“ 이미 npm registry에 publish됨 β€” skip`);
7857
+ return;
7858
+ }
7859
+ } catch {} // λ„€νŠΈμ›Œν¬ μ‹€νŒ¨ μ‹œ κ·Έλƒ₯ publish μ‹œλ„
7860
+
7861
+ // 2) μž„μ‹œ .npmrc 생성 (토큰 λ…ΈμΆœ λ°©μ§€)
7862
+ let tmpDir;
7863
+ try {
7864
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-npmrc-'));
7865
+ const tmpNpmrc = path.join(tmpDir, '.npmrc');
7866
+ fs.writeFileSync(tmpNpmrc, `//registry.npmjs.org/:_authToken=${token}\n`, { mode: 0o600 });
7867
+ // 3) npm publish (--userconfig 둜 μž„μ‹œ .npmrc μ‚¬μš©, --access public)
7868
+ const args = opts.dryRun ? ['publish', '--userconfig', tmpNpmrc, '--access', 'public', '--dry-run'] :
7869
+ ['publish', '--userconfig', tmpNpmrc, '--access', 'public'];
7870
+ log(` ${opts.dryRun ? '(dry-run) ' : ''}npm publish μ‹œλ„ 쀑...`);
7871
+ const pubR = cp.spawnSync('npm', args, {
7872
+ cwd: root, encoding: 'utf8', shell: true, timeout: 60000,
7873
+ env: { ...process.env, npm_config_loglevel: 'warn' }
7874
+ });
7875
+ if (pubR.status === 0) {
7876
+ ok(`npm publish μ™„λ£Œ: ${pkgName}@${pkgVersion}`);
7877
+ try { _recordRun(root, { kind: 'npm_publish', package: pkgName, version: pkgVersion, dryRun: !!opts.dryRun, ok: true }); } catch {}
7878
+ } else {
7879
+ const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
7880
+ if (/EPUBLISHCONFLICT|already exists|cannot publish over/i.test(errOut)) {
7881
+ log(` βœ“ 이미 publish됨 (race condition) β€” skip`);
7882
+ } else if (/EAUTH|forbidden|401|403/i.test(errOut)) {
7883
+ warn(`npm publish μ‹€νŒ¨: 토큰 κΆŒν•œ λΆ€μ‘± λ˜λŠ” 만료 β€” .env NPM_TOKEN μž¬λ°œκΈ‰ ν•„μš”`);
7884
+ } else if (/ENEEDAUTH/i.test(errOut)) {
7885
+ warn(`npm publish μ‹€νŒ¨: 인증 λ―Έμž‘λ™ β€” 토큰 ν˜•μ‹ 확인 (npm_xxxxx)`);
7886
+ } else {
7887
+ warn(`npm publish μ‹€νŒ¨ (exit ${pubR.status}): ${errOut.split('\n').slice(0, 3).join(' ')}`);
7888
+ }
7889
+ try { _recordRun(root, { kind: 'npm_publish', package: pkgName, version: pkgVersion, ok: false, error: errOut.slice(0, 200) }); } catch {}
7890
+ }
7891
+ } finally {
7892
+ // 4) μž„μ‹œ .npmrc μ¦‰μ‹œ μ‚­μ œ (토큰 μž”μ‘΄ λ°©μ§€)
7893
+ if (tmpDir) {
7894
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
7895
+ }
7896
+ }
7809
7897
  }
7810
7898
 
7811
7899
  // 1.9.40: release pack β€” κ°€λ²Όμš΄ 톡합 λͺ…λ Ή (npm pack + self-host migrate + auto task + close + readme sync)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.177",
3
+ "version": "1.9.178",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",