leerness 1.9.274 → 1.9.275

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.275 — 2026-06-03 — UR-0026: 릴리스 채널 (npm dist-tag 안정/실험)
4
+
5
+ **🚦 잦은 1.9.x 릴리스에 안정(latest)/실험(next) 채널 + 버전 고정 안내 — 사용자가 안정성을 제어 (GPT-5.5 리뷰).**
6
+
7
+ ### 배경
8
+ GPT-5.5 리뷰: 릴리스가 매우 잦아 사용자가 안정/실험 버전을 구분하기 어려움(UR-0026). npm dist-tag 로 채널을 분리하고, 운영 코드는 버전 고정을 안내.
9
+
10
+ ### 구현
11
+ 1. **`_resolveNpmTag(explicit, env)` 순수 함수** — `--npm-tag` / `LEERNESS_NPM_TAG` / 기본 `latest`. 형식 검증(소문자/숫자/-, ≤39자), 잘못된 값은 latest 폴백.
12
+ 2. **publish 경로 dist-tag 지원** — `_publishToNpm` + `release publish --npm-publish` 가 `--tag <tag>` 적용. `release sync-main --npm-tag next` 또는 `LEERNESS_NPM_TAG=next` 로 실험 채널 publish 가능 (기본은 latest 안정).
13
+ 3. **`leerness release channel [--json]` 신규** — 현재 버전/기본 publish 태그/정책(latest=안정, next=실험) + 버전 고정 안내. 온라인 시 `npm view dist-tags` 회수(offline 시 생략).
14
+ 4. **README 릴리스 채널 섹션(한/영)** — `npm i leerness` / `@next` / `@<버전>` 설치 가이드.
15
+ 5. **selftest 25→26 + e2e 222→223** — `_resolveNpmTag` 검증 + `release channel` JSON/env 반영.
16
+
17
+ ### 검증
18
+ - **selftest 26/26 PASS** · **E2E 223/223 PASS** (회귀 0). 기본 publish 채널은 latest 유지(기존 동작 불변).
19
+
3
20
  ## 1.9.274 — 2026-06-03 — UR-0025 1단계: bin/harness.js 모듈 분리 시작 (lib/ 순수 유틸 추출)
4
21
 
5
22
  **🧩 단일 대형 파일(1.2MB) 모듈 분리의 비파괴 1단계 — `lib/` 모듈화 패턴 확립 + 순수 유틸 7종 추출 (GPT-5.5 리뷰).**
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
  > **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
5
5
 
6
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.274-green)]() [![tests](https://img.shields.io/badge/e2e-222%2F222-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-75-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-6-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.275-green)]() [![tests](https://img.shields.io/badge/e2e-223%2F223-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-75-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-6-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
7
7
 
8
8
  ```
9
9
  ╔══════════════════════════════════════════════════════════════╗
@@ -188,6 +188,17 @@ leerness capabilities # 권한 표면 + opt-out + 주의 명령 (--json
188
188
  - 회사/운영 코드에서는 먼저 한 프로젝트에서 `init` 후 `git diff` 로 검토하고 커밋하세요.
189
189
  - 자세히: [SECURITY.md](SECURITY.md)
190
190
 
191
+ ### 릴리스 채널 (안정 vs 실험)
192
+
193
+ leerness는 활발히 개발됩니다(잦은 1.9.x). 채널을 골라 안정성을 제어하세요:
194
+
195
+ ```bash
196
+ npm i leerness # latest (안정) — 일반 사용자 기본
197
+ npm i leerness@next # next (실험) — 조기 검증용
198
+ npm i leerness@1.9.275 # 버전 고정 — 재현성 (운영 코드 권장)
199
+ leerness release channel # 현재 채널/정책 확인 (--json)
200
+ ```
201
+
191
202
  ---
192
203
 
193
204
  <a id="english"></a>
@@ -356,6 +367,17 @@ leerness capabilities # capability surface + opt-out + cautious command
356
367
  - For company/production code, trial `init` in one project, review with `git diff`, then commit.
357
368
  - Details: [SECURITY.md](SECURITY.md)
358
369
 
370
+ ### Release channels (stable vs experimental)
371
+
372
+ leerness is actively developed (frequent 1.9.x). Pick a channel to control stability:
373
+
374
+ ```bash
375
+ npm i leerness # latest (stable) — default for most users
376
+ npm i leerness@next # next (experimental) — early validation
377
+ npm i leerness@1.9.275 # pin a version — reproducibility (recommended for prod)
378
+ leerness release channel # show current channel/policy (--json)
379
+ ```
380
+
359
381
  ---
360
382
 
361
383
  ## 🔧 Environment variables / 환경변수
@@ -402,7 +424,7 @@ MIT — © leerness contributors
402
424
  <!-- leerness:project-readme:start -->
403
425
  ## Leerness Project Harness
404
426
 
405
- 이 프로젝트는 Leerness v1.9.274 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
427
+ 이 프로젝트는 Leerness v1.9.275 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
406
428
 
407
429
  ### Core Commands
408
430
 
@@ -444,7 +466,7 @@ leerness memory restore decision <date|title>
444
466
 
445
467
  ### MCP server (외부 AI 통합)
446
468
 
447
- Leerness v1.9.274는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
469
+ Leerness v1.9.275는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
448
470
 
449
471
  ```jsonc
450
472
  // 카테고리별
@@ -465,7 +487,7 @@ Leerness v1.9.274는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
465
487
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
466
488
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
467
489
 
468
- 현재 누적: **70 라운드 (1.9.40 → 1.9.274)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
490
+ 현재 누적: **70 라운드 (1.9.40 → 1.9.275)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
469
491
 
470
492
  ### 성능 가이드 (1.9.140 측정)
471
493
 
@@ -503,6 +525,6 @@ leerness release pack --close --auto-main-push
503
525
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
504
526
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
505
527
 
506
- Last synced by Leerness v1.9.274: 2026-06-03
528
+ Last synced by Leerness v1.9.275: 2026-06-03
507
529
  <!-- leerness:project-readme:end -->
508
530
 
package/bin/harness.js CHANGED
@@ -9,7 +9,7 @@ const readline = require('readline');
9
9
  // 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
10
10
  const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp } = require('../lib/pure-utils');
11
11
 
12
- const VERSION = '1.9.274';
12
+ const VERSION = '1.9.275';
13
13
 
14
14
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
15
15
  // leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
@@ -2912,6 +2912,7 @@ function _selfTestCases() {
2912
2912
  { name: 'ROLE_CATALOG + _normalizeRole: 7종 + 한국어 별칭 (1.9.270)', run: () => { const keys = Object.keys(ROLE_CATALOG); return keys.length === 7 && keys.includes('coder') && keys.includes('reviewer') && _normalizeRole('코딩') === 'coder' && _normalizeRole('검수자') === 'reviewer' && _normalizeRole('지휘관') === 'commander'; } },
2913
2913
  { name: '_pickModel: top/code/fast 등급 선택 (1.9.270)', run: () => { return _pickModel('codex', 'code') === 'gpt-5-codex' && _pickModel('claude', 'top') === 'claude-opus-4-7' && /haiku/.test(_pickModel('claude', 'fast')); } },
2914
2914
  { name: 'CAPABILITY_SURFACE: 6 영역 + risk/optOut + 주의명령 (1.9.272)', run: () => { const ks = Object.keys(CAPABILITY_SURFACE); return ks.length === 6 && ks.includes('automationBridges') && Object.values(CAPABILITY_SURFACE).every(v => ['low','medium','high'].includes(v.risk) && v.optOut && v.desc) && POWERFUL_COMMANDS.length >= 5; } },
2915
+ { name: '_resolveNpmTag: latest 기본 + next 허용 + 잘못된 형식 폴백 (1.9.275)', run: () => _resolveNpmTag(null, {}) === 'latest' && _resolveNpmTag('next', {}) === 'next' && _resolveNpmTag(null, { LEERNESS_NPM_TAG: 'next' }) === 'next' && _resolveNpmTag('bad tag!', {}) === 'latest' && _resolveNpmTag('Beta', {}) === 'beta' },
2915
2916
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
2916
2917
  ];
2917
2918
  }
@@ -3569,6 +3570,7 @@ function commandsCmd(root) {
3569
3570
  { cmd: 'agents list|check|quota|dispatch|multi', desc: '외부 AI CLI 오케스트레이션 (dispatch --role 모델 라우팅 1.9.270)' },
3570
3571
  { cmd: 'roles list|set|unset|catalog|suggest|verify', desc: '모델별 역할 부여 (코딩/검수/지휘/디자인/디버그/설계/분배) — 1.9.270' },
3571
3572
  { cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (무엇을 하는지 + opt-out + 주의 명령) — 1.9.272' },
3573
+ { cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275' },
3572
3574
  { cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
3573
3575
  { cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
3574
3576
  { cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
@@ -14164,6 +14166,34 @@ function releaseCleanupCmd(root) {
14164
14166
  // - NPM_TOKEN 또는 LEERNESS_NPM_TOKEN 환경변수에서만 읽음 (값 절대 로그 X)
14165
14167
  // - 임시 .npmrc 파일 (mkdtempSync) 생성 → publish → 즉시 삭제 (finally)
14166
14168
  // - 이미 publish된 버전 자동 detect → skip (중복 publish 차단)
14169
+ // 1.9.275 (UR-0026, GPT-5.5 리뷰): npm dist-tag 결정 — latest(안정, 기본) / next(실험). 잘못된 형식은 latest 폴백. 순수 함수.
14170
+ function _resolveNpmTag(explicit, env) {
14171
+ env = env || process.env;
14172
+ const raw = String(explicit || env.LEERNESS_NPM_TAG || 'latest').trim().toLowerCase();
14173
+ return /^[a-z][a-z0-9-]{0,38}$/.test(raw) ? raw : 'latest';
14174
+ }
14175
+ // 1.9.275 (UR-0026): leerness release channel — 안정/실험 채널 정책 + 버전 고정 안내 (npm dist-tag).
14176
+ function releaseChannelCmd(root) {
14177
+ root = absRoot(root || process.cwd());
14178
+ const json = has('--json');
14179
+ let v = null; try { v = JSON.parse(read(path.join(root, 'package.json'))).version; } catch {}
14180
+ const tag = _resolveNpmTag(arg('--npm-tag', null));
14181
+ let distTags = null;
14182
+ if (!has('--offline') && process.env.LEERNESS_OFFLINE !== '1') {
14183
+ try { const r = cp.spawnSync('npm', ['view', 'leerness', 'dist-tags', '--json'], { encoding: 'utf8', timeout: 10000, shell: true }); if (r.status === 0 && r.stdout) distTags = JSON.parse(r.stdout); } catch {}
14184
+ }
14185
+ if (json) { log(JSON.stringify({ version: v, defaultPublishTag: tag, distTags, policy: { stable: 'latest', experimental: 'next' } }, null, 2)); return; }
14186
+ log(`# leerness release channel (1.9.275, UR-0026)`);
14187
+ log(`현재 버전: ${v || '?'} · 기본 publish 태그: ${tag}`);
14188
+ if (distTags) { log('npm dist-tags:'); for (const [k, val] of Object.entries(distTags)) log(` ${k}: ${val}`); }
14189
+ log('');
14190
+ log(`## 채널 정책 (안정 vs 실험)`);
14191
+ log(` • latest (안정) — npm i leerness ← 일반 사용자 기본`);
14192
+ log(` • next (실험) — npm i leerness@next ← 조기 검증용`);
14193
+ log(` • 버전 고정 — npm i leerness@${v || '1.9.275'} ← 재현성 (권장: 운영 코드)`);
14194
+ log('');
14195
+ log(` 실험 채널 publish: leerness release sync-main --npm-tag next (또는 LEERNESS_NPM_TAG=next)`);
14196
+ }
14167
14197
  function _publishToNpm(root, opts = {}) {
14168
14198
  root = absRoot(root || process.cwd());
14169
14199
  // .env 자동 로드 — root + 상위 3단계까지 탐색 (워크스페이스 root 의 .env 도 인식).
@@ -14221,10 +14251,12 @@ function _publishToNpm(root, opts = {}) {
14221
14251
  // 3) npm publish (--userconfig 로 임시 .npmrc 사용, --access public)
14222
14252
  // 1.9.178+: 2FA OTP 지원 — LEERNESS_NPM_OTP 환경변수 또는 --npm-otp 인자
14223
14253
  const otp = process.env.LEERNESS_NPM_OTP || arg('--npm-otp', null) || opts.otp;
14224
- const baseArgs = ['publish', '--userconfig', tmpNpmrc, '--access', 'public'];
14254
+ // 1.9.275 (UR-0026): dist-tag latest(안정)/next(실험). 기본 latest.
14255
+ const npmTag = _resolveNpmTag(opts.tag || arg('--npm-tag', null));
14256
+ const baseArgs = ['publish', '--userconfig', tmpNpmrc, '--access', 'public', '--tag', npmTag];
14225
14257
  if (otp) baseArgs.push(`--otp=${otp}`);
14226
14258
  const args = opts.dryRun ? [...baseArgs, '--dry-run'] : baseArgs;
14227
- log(` ${opts.dryRun ? '(dry-run) ' : ''}npm publish 시도 중...`);
14259
+ log(` ${opts.dryRun ? '(dry-run) ' : ''}npm publish 시도 중... (dist-tag: ${npmTag})`);
14228
14260
  const pubR = cp.spawnSync('npm', args, {
14229
14261
  cwd: root, encoding: 'utf8', shell: true, timeout: 60000,
14230
14262
  env: { ...process.env, npm_config_loglevel: 'warn' }
@@ -14414,7 +14446,9 @@ function releasePublish(root) {
14414
14446
 
14415
14447
  // 6. npm publish (--npm-publish)
14416
14448
  if (has('--npm-publish')) {
14417
- const args = dryRun ? ['publish', '--dry-run'] : ['publish', '--access', 'public'];
14449
+ // 1.9.275 (UR-0026): dist-tag 지원 (latest/next)
14450
+ const npmTag = _resolveNpmTag(arg('--npm-tag', null));
14451
+ const args = dryRun ? ['publish', '--dry-run', '--tag', npmTag] : ['publish', '--access', 'public', '--tag', npmTag];
14418
14452
  log('npm ' + args.join(' '));
14419
14453
  const r = cp.spawnSync('npm', args, { cwd: root, encoding: 'utf8', shell: true });
14420
14454
  log((r.stdout || '').split('\n').slice(-5).join('\n'));
@@ -20819,6 +20853,8 @@ async function main() {
20819
20853
  if (cmd === 'release' && args[1] === 'sync-main') return releaseSyncMainCmd(args[2] || arg('--path', process.cwd()));
20820
20854
  // 1.9.235: leerness release cleanup — local release/* branches 정리 (merged 만, --apply 시 실 삭제)
20821
20855
  if (cmd === 'release' && args[1] === 'cleanup') return releaseCleanupCmd((args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()));
20856
+ // 1.9.275 (UR-0026): leerness release channel — 안정/실험 채널 정책 (npm dist-tag)
20857
+ if (cmd === 'release' && args[1] === 'channel') return releaseChannelCmd((args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()));
20822
20858
  // 1.9.141: feature causality graph
20823
20859
  if (cmd === 'feature' && args[1] === 'add') return featureAddCmd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('--')).join(' '));
20824
20860
  if (cmd === 'feature' && args[1] === 'link') return featureLinkCmd(arg('--path', process.cwd()), args[2]);
@@ -20962,5 +20998,7 @@ module.exports = {
20962
20998
  // 1.9.270: agent roles — 모델별 역할 부여 (사용자 명시) — 단위 테스트
20963
20999
  ROLE_CATALOG, _normalizeRole, _pickModel, _rolesFile, _loadRoles, _saveRoles, _resolveRole, _suggestRoles, rolesCmd, _dispatchCommand,
20964
21000
  // 1.9.272: 권한/보안 표면 공개 (GPT-5.5 리뷰) — 단위 테스트
20965
- CAPABILITY_SURFACE, POWERFUL_COMMANDS, capabilitiesCmd
21001
+ CAPABILITY_SURFACE, POWERFUL_COMMANDS, capabilitiesCmd,
21002
+ // 1.9.275: 릴리스 채널 (npm dist-tag, GPT-5.5 리뷰) — 단위 테스트
21003
+ _resolveNpmTag, releaseChannelCmd
20966
21004
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.274",
3
+ "version": "1.9.275",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -2801,5 +2801,19 @@ total++;
2801
2801
  if (!ok) failed++;
2802
2802
  }
2803
2803
 
2804
+ // 1.9.275 회귀 (UR-0026): release channel — 안정/실험 채널 정책 (npm dist-tag)
2805
+ total++;
2806
+ {
2807
+ const r = cp.spawnSync(process.execPath, [CLI, 'release', 'channel', tmp, '--json', '--offline'], { encoding: 'utf8', timeout: 15000 });
2808
+ let jsonOk = false;
2809
+ try { const j = JSON.parse(r.stdout); jsonOk = j.defaultPublishTag === 'latest' && j.policy && j.policy.stable === 'latest' && j.policy.experimental === 'next'; } catch {}
2810
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'release', 'channel', tmp, '--json', '--offline'], { encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NPM_TAG: 'next' } });
2811
+ let nextOk = false;
2812
+ try { nextOk = JSON.parse(r2.stdout).defaultPublishTag === 'next'; } catch {}
2813
+ const ok = jsonOk && nextOk;
2814
+ console.log(ok ? '✓ B(1.9.275) release channel: latest 기본 + LEERNESS_NPM_TAG=next 반영 + 정책' : `✗ release channel 실패 (json=${jsonOk} next=${nextOk})`);
2815
+ if (!ok) failed++;
2816
+ }
2817
+
2804
2818
  console.log(`\nE2E result: ${total - failed}/${total} passed`);
2805
2819
  if (failed > 0) process.exit(1);