leerness 1.31.0 → 1.32.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/CHANGELOG.md +72 -0
- package/README.md +4 -4
- package/bin/leerness.js +48 -39
- package/lib/catalogs.js +23 -22
- package/lib/pure-utils.js +15 -4
- package/package.json +1 -1
- package/scripts/e2e.js +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.32.0 — 2026-06-16 — 🛡️ [안정화/Stable] UR-0010 영어화 3종(install-safety+constraints+capabilities/team) 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — 직전 minor(1.31.0) 이후 누적된 UR-0010 CLI 영어화 패치 3건(1.31.1~1.31.3)을 검증·통합해 npm 공개.** R-0011 정책의 23번째 stable minor. 잔여 영어화 표면 4종(install-safety / constraints / capabilities / team reminder)을 라벨+카탈로그 데이터까지 완전 영어화. 한국어 우선 기본은 그대로.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 통합 (1.31.1~1.31.3)
|
|
8
|
+
- **🌐 install-safety 영어화 (1.31.1)**: `install-safety`(설치 안전 프로필) 출력 영어 opt-in — 프로필/의존성/install-script/offline/safeInstall/hardeningNote. 셸-무관 가드(`npx --yes`/PowerShell/no `npm_config_` prefix) 양 언어 보존.
|
|
9
|
+
- **🌐 constraints 영어화 (1.31.2)**: `constraints list/check/add` 라벨 + `lib/catalogs.js` `_DEFAULT_PLATFORM_CONSTRAINTS` 6 플랫폼 한국어 detail 9종 `detailEn` + `_matchConstraints(…, lang)` suggestion 영어. en `list` 에서 한글-only alias(`결제`)는 표시 숨김(매처는 유지 → 매칭 무회귀).
|
|
10
|
+
- **🌐 capabilities + team reminder 영어화 (1.31.3)**: `capabilities`(권한·보안 표면 공개) 라벨/원칙/principles + `CAPABILITY_SURFACE` 6표면 `descEn`/`optOutEn` + `POWERFUL_COMMANDS` 7 `noteEn`(보안 disclosure 정확 번역). `_teamHandoffReminders(…, lang)` 본문(`N member(s)`/`review needed`/`preview`) + handoff `_uiLang` 전달로 전체 배선 완성.
|
|
11
|
+
- **공통 패턴**: 카탈로그 데이터(detailEn/descEn/noteEn)까지 영어화해 "반쪽 영어(완전성 과장)" 회피 · 순수 함수 optional `lang` 인자(기본 ko, 기존 호출 무회귀) · ko 기본 verbatim 보존.
|
|
12
|
+
|
|
13
|
+
### 검증 (회귀 0)
|
|
14
|
+
- **selftest 257** (constraints/capability catalog 단일출처 + `_matchConstraints`/`_teamHandoffReminders` 행위 가드에 i18n 추가 — `*En` 존재·무한글 + ko 보존).
|
|
15
|
+
- **E2E 375** (B(1.31.1) install-safety en · B(1.31.2) constraints en · B(1.31.3) capabilities+team handoff 전체배선 — 한글 0 + ko 보존 행위 가드).
|
|
16
|
+
- **stable minor** — npm 공개 + annotated tag(Stable) + GitHub release `--latest` + npm dist-tag `stable`. 게시본 클린룸 재실증(install-safety/constraints/capabilities/team en 4표면).
|
|
17
|
+
- bin+package.json 동시 bump + 일치 가드 통과.
|
|
18
|
+
|
|
19
|
+
### 잔여 (UR-0010 백로그)
|
|
20
|
+
- `commands` 카탈로그(~85 desc, 큰 표면·다회차) · init en seed 템플릿(큰 표면).
|
|
21
|
+
|
|
22
|
+
## 1.31.3 — 2026-06-16 — 🌐 capabilities(권한·보안 표면) + team reminder 영어화 (UR-0010)
|
|
23
|
+
|
|
24
|
+
**🌐 `leerness capabilities`(권한·보안 표면 공개, 보안 투명성 플래그십) + handoff 팀 스케줄 reminder 본문 영어 opt-in.** UR-0010 잔여 표면 2종. 라벨뿐 아니라 카탈로그 데이터(표면 desc/opt-out, powerful 명령 note)까지 영어화. 한국어 기본 보존.
|
|
25
|
+
|
|
26
|
+
### 변경 (UR-0010)
|
|
27
|
+
- **capabilities 완전 영어화**: `capabilitiesCmd` 에 `_uiLang`/`_t` 도입 — 헤더·원칙·섹션 라벨(권한 표면/주의 명령) + `--json` principles 영어화. `lib/catalogs.js` `CAPABILITY_SURFACE` 6표면에 `descEn`/`optOutEn`, `POWERFUL_COMMANDS` 7건에 `noteEn` 추가(보안 disclosure 정확 번역). en 렌더는 `descEn||desc` 폴백, catalog 구조·소비처 무변경.
|
|
28
|
+
- **team reminder 본문 영어화**: `_teamHandoffReminders(teams, lang)` 에 optional `lang` 추가 — `N명`→`N member(s)`(단복수) · `검수필요`→`review needed` · `미리보기`→`preview`. 기본 ko(1-arg 호출 무회귀). handoff 호출부가 `_uiLang(root)` 전달 → 헤더(1.30.5 기영어화)+본문 라인 전체 배선 완성.
|
|
29
|
+
|
|
30
|
+
### 검증 (회귀 0)
|
|
31
|
+
- **selftest 257**: 기존 catalog 단일출처/`_teamHandoffReminders` 행위 가드에 i18n 추가(6표면 descEn/optOutEn·7 noteEn 영어+무한글 · team en 단복수/검수/preview + ko 보존).
|
|
32
|
+
- **행위**: `capabilities --language en` 한글 0 + 영어 표면 desc/note/principles · ko 보존 · `--json --language en` principles 영어 · `handoff --language en` 팀 라인 영어(2 members/review needed/preview) · ko 라인 보존(2명/검수필요/미리보기) — handoff 전체 배선 실증.
|
|
33
|
+
- **E2E 374→375**: 새 가드 B(1.31.3).
|
|
34
|
+
- patch(1.31.3) — npm 미배포(R-0011). bin+package.json 동시 bump + 일치 가드 통과.
|
|
35
|
+
|
|
36
|
+
### 잔여 (UR-0010 백로그)
|
|
37
|
+
- `commands` 카탈로그(~85 desc, 큰 표면·다회차) · init en seed 템플릿(큰 표면).
|
|
38
|
+
|
|
39
|
+
## 1.31.2 — 2026-06-16 — 🌐 constraints 출력 영어화 (UR-0010)
|
|
40
|
+
|
|
41
|
+
**🌐 `leerness constraints`(플랫폼/API 제약 사전 체크) 출력을 영어 opt-in 완전화.** UR-0010 CLI 영어화 잔여 표면. 라벨뿐 아니라 카탈로그 데이터(제약 detail)까지 영어화해 반쪽 영어(완전성 과장)를 회피. 한국어 기본 보존.
|
|
42
|
+
|
|
43
|
+
### 변경 (UR-0010)
|
|
44
|
+
- **constraints list/check/add 라벨 영어화**: "매칭된 플랫폼 없음" · "제안" · "N개 플랫폼 매칭 — 제약 사전 확인 필요" · "구현 전 위 제약 반영 설계 권장" · "platform 갱신" 을 `_t(ko,en)` 으로. `constraintsCmd` 에 함수레벨 `_L`/`_t` 도입(list/check/add 공유, 기존 help 블록 중복 제거).
|
|
45
|
+
- **카탈로그 detail 영어화(데이터)**: `lib/catalogs.js` `_DEFAULT_PLATFORM_CONSTRAINTS` 6 플랫폼의 한국어 제약 detail 9종에 `detailEn` 추가(stripe/openai/anthropic/github/discord/twitter). en 출력 시 `detailEn`, 없으면 `detail`(영어 원문) 사용. catalog 매칭 로직·구조 무변경(추가 필드).
|
|
46
|
+
- **suggestion 영어화**: `lib/pure-utils.js` `_matchConstraints(catalog, text, lang)` 에 optional `lang`('en') 추가 — generic API 키워드 no-match 제안을 영어로. 기본 ko(2-arg 호출 무회귀). `_checkRequestConstraints(root, text, lang)` 가 `_L` 전달.
|
|
47
|
+
- **alias 표시 정합**: en `list` 에서 한글-only alias(예: stripe 의 `결제`)는 표시에서 숨김(매처로는 catalog 에 유지 → 한국어 입력 매칭 무회귀).
|
|
48
|
+
|
|
49
|
+
### 검증 (회귀 0)
|
|
50
|
+
- **selftest 257**: 기존 constraints catalog/`_matchConstraints` 분리 가드에 i18n 행위 추가(stripe `detailEn` 존재+무한글 · en suggestion 무한글+비공백 · ko suggestion 한글 유지).
|
|
51
|
+
- **행위**: `constraints list/check --language en` 한글 0 + 영어 detail/라벨/suggestion · ko 기본 보존(필수/별도 detail) · 한국어 alias(`결제`) 매칭 무회귀(--json matched stripe).
|
|
52
|
+
- **E2E 373→374**: 새 가드 B(1.31.2).
|
|
53
|
+
- patch(1.31.2) — npm 미배포(R-0011). bin+package.json 동시 bump + 일치 가드 통과.
|
|
54
|
+
|
|
55
|
+
### 잔여 (UR-0010 백로그)
|
|
56
|
+
- `commands` 카탈로그(~85 desc, 큰 표면) · 팀 reminder 본문 · init en seed 템플릿.
|
|
57
|
+
|
|
58
|
+
## 1.31.1 — 2026-06-16 — 🌐 install-safety 출력 영어화 (UR-0010)
|
|
59
|
+
|
|
60
|
+
**🌐 `leerness install-safety`(설치 안전 프로필) 출력을 영어 opt-in.** UR-0010 CLI 영어화 잔여 표면. 한국어 기본 보존.
|
|
61
|
+
|
|
62
|
+
### 변경 (UR-0010)
|
|
63
|
+
- **install-safety 출력 영어화**: 프로필 헤더 · 버전/Node · 런타임 의존성(0 공급망 노출) · install-time 스크립트 · offline-first 동작 · 안전 설치 워크플로 + safeInstall 주석 + hardeningNote(셸별 가드)를 `t(ko,en)` 으로. 블록 로컬 `t()`/`_uiLang(arg('--path'))`. ko verbatim 보존.
|
|
64
|
+
- **셸-무관 가드 양 언어 보존(1.9.397/UR-0098)**: en 출력에서도 `safeInstall` 의 `npx --yes`(≥2) + `hardeningNote` 의 `PowerShell` 분기 + `npm_config_` POSIX prefix 부재 유지(셸-무관 레시피 회귀 0).
|
|
65
|
+
|
|
66
|
+
### 검증 (회귀 0)
|
|
67
|
+
- **selftest 257** (기존 install-safety 0-deps/0-script 사실 가드 + 셸-무관 레시피 가드 무회귀 — 기본 ko 유지).
|
|
68
|
+
- **행위**: `install-safety --language en` 영어(한글 0, Node 탐지) · `install-safety` ko 보존 · `--json --language en` 에서 npx --yes ×2 + PowerShell + no npm_config prefix.
|
|
69
|
+
- **E2E 372→373**: 새 가드 B(1.31.1).
|
|
70
|
+
- patch(1.31.1) — npm 미배포(R-0011). bin+package.json 동시 bump + 일치 가드 통과.
|
|
71
|
+
|
|
72
|
+
### 잔여 (UR-0010 백로그)
|
|
73
|
+
- `commands` 카탈로그(~85 desc, 큰 표면) · `constraints` · 팀 reminder 본문 · init en seed 템플릿.
|
|
74
|
+
|
|
3
75
|
## 1.31.0 — 2026-06-16 — 🛡️ [안정화/Stable] 14th리뷰 7/7 + 하위 프로젝트(detect/adopt) 안정 minor
|
|
4
76
|
|
|
5
77
|
**🛡️ 안정화(Stable) minor — 14번째 외부 리뷰 7건 전부 수정 + 사용자 명시 하위 프로젝트 기능을 npm 공개.** 직전 minor(1.30.0) 이후 누적된 패치 5건(1.30.1~1.30.5)을 검증·통합해 배포. R-0011 정책의 22번째 stable minor. 한국어 우선 기본은 그대로.
|
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ MIT
|
|
|
104
104
|
<!-- leerness:project-readme:start -->
|
|
105
105
|
## Leerness Project Harness
|
|
106
106
|
|
|
107
|
-
이 프로젝트는 Leerness v1.
|
|
107
|
+
이 프로젝트는 Leerness v1.32.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
108
108
|
|
|
109
109
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
110
110
|
|
|
@@ -158,7 +158,7 @@ leerness memory restore decision <date|title>
|
|
|
158
158
|
|
|
159
159
|
### MCP server (외부 AI 통합)
|
|
160
160
|
|
|
161
|
-
Leerness v1.
|
|
161
|
+
Leerness v1.32.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
162
162
|
|
|
163
163
|
```jsonc
|
|
164
164
|
// 카테고리별
|
|
@@ -179,7 +179,7 @@ Leerness v1.31.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
179
179
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
180
180
|
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) 다음 라운드 예약.
|
|
181
181
|
|
|
182
|
-
현재 누적: **70 라운드 (1.9.40 → 1.
|
|
182
|
+
현재 누적: **70 라운드 (1.9.40 → 1.32.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
183
183
|
|
|
184
184
|
### 성능 가이드 (1.9.140 측정)
|
|
185
185
|
|
|
@@ -217,6 +217,6 @@ leerness release pack --close --auto-main-push
|
|
|
217
217
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
218
218
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
219
219
|
|
|
220
|
-
Last synced by Leerness v1.
|
|
220
|
+
Last synced by Leerness v1.32.0: 2026-06-16
|
|
221
221
|
<!-- leerness:project-readme:end -->
|
|
222
222
|
|
package/bin/leerness.js
CHANGED
|
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
32
32
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
33
33
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
|
|
34
34
|
|
|
35
|
-
const VERSION = '1.
|
|
35
|
+
const VERSION = '1.32.0';
|
|
36
36
|
|
|
37
37
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
38
38
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -2863,7 +2863,7 @@ function _selfTestCases() {
|
|
|
2863
2863
|
{ name: 'get_project_context: MCP 시맨틱 verb 등록 + CLI context 디스패치 (UR-0031 1.9.292)', run: () => { const src = read(__filename); const mcpDef = require('../lib/mcp-tools').some(t => t.name === 'leerness_get_project_context'); const mcpCase = /case 'leerness_get_project_context':[\s\S]*?cliArgs = \['context'/.test(src); const cliDisp = /if \(cmd === 'context'\)\s+return contextCmd/.test(src); return typeof contextCmd === 'function' && mcpDef && mcpCase && cliDisp && _mcpToolCount() >= 80; } },
|
|
2864
2864
|
{ name: '_canonicalProgressHeader + idempotency auto-fix (근본 복제버그 fix 1.9.293)', run: () => { const h = _canonicalProgressHeader(); const headerOk = /leernessRole: progress-tracker/.test(h) && /\| ID \| Status \| Request \|/.test(h) && /\|---\|/.test(h); const src = read(__filename); const fnOk = typeof _autoFixIdempotency === 'function'; const noWholeTextFallback = /if \(idx < 0\) return _canonicalProgressHeader\(\);/.test(src); const driftWired = /_autoFixIdempotency\(root\)/.test(src) && /idempotency 중복/.test(src); return headerOk && fnOk && noWholeTextFallback && driftWired; } },
|
|
2865
2865
|
{ name: 'lib/role-catalog: ROLE/PROVIDER/ALIASES/PROMPTS 모듈 단일출처 분리 (UR-0025 1.9.294)', run: () => { const m = require('../lib/role-catalog'); return m.ROLE_CATALOG === ROLE_CATALOG && m._PROVIDER_MODEL_CATALOG === _PROVIDER_MODEL_CATALOG && m._ROLE_ALIASES === _ROLE_ALIASES && m._AGENT_ROLE_PROMPTS === _AGENT_ROLE_PROMPTS && Object.keys(m.ROLE_CATALOG).length === 7 && Object.keys(m._PROVIDER_MODEL_CATALOG).length === 10 && !/const ROLE_CATALOG = \{/.test(read(__filename)); } },
|
|
2866
|
-
{ name: 'lib/catalogs: CAPABILITY/ADAPTERS/REUSE 모듈 단일출처 분리 (UR-0025 1.9.295)', run: () => { const m = require('../lib/catalogs'); return m.CAPABILITY_SURFACE === CAPABILITY_SURFACE && m.ADAPTERS === ADAPTERS && m.REUSE_CATEGORIES === REUSE_CATEGORIES && m.REUSE_CHECKLIST === REUSE_CHECKLIST && m.POWERFUL_COMMANDS === POWERFUL_COMMANDS && Object.keys(m.CAPABILITY_SURFACE).length === 6 && !/const CAPABILITY_SURFACE = \{/.test(read(__filename)); } },
|
|
2866
|
+
{ name: 'lib/catalogs: CAPABILITY/ADAPTERS/REUSE 모듈 단일출처 분리 (UR-0025 1.9.295) + i18n en(1.31.3)', run: () => { const m = require('../lib/catalogs'); const _H = /[가-힣]/; const cs = Object.values(m.CAPABILITY_SURFACE); const i18nOk = cs.length === 6 && cs.every(v => typeof v.descEn === 'string' && v.descEn.length > 0 && !_H.test(v.descEn) && typeof v.optOutEn === 'string' && !_H.test(v.optOutEn)) && m.POWERFUL_COMMANDS.every(c => typeof c.noteEn === 'string' && c.noteEn.length > 0 && !_H.test(c.noteEn)); return m.CAPABILITY_SURFACE === CAPABILITY_SURFACE && m.ADAPTERS === ADAPTERS && m.REUSE_CATEGORIES === REUSE_CATEGORIES && m.REUSE_CHECKLIST === REUSE_CHECKLIST && m.POWERFUL_COMMANDS === POWERFUL_COMMANDS && Object.keys(m.CAPABILITY_SURFACE).length === 6 && i18nOk && !/const CAPABILITY_SURFACE = \{/.test(read(__filename)); } },
|
|
2867
2867
|
{ name: 'about: 정체성 verb(AI 운영 레이어) + MCP leerness_about 등록 (UR-0030 1.9.296)', run: () => { const id = _leernessIdentity(); const src = read(__filename); return typeof aboutCmd === 'function' && /운영 레이어/.test(id.identity) && id.layers.length === 5 && id.surface.mcpTools >= 81 && require('../lib/mcp-tools').some(t => t.name === 'leerness_about') && /case 'leerness_about':/.test(src) && /cmd === 'about' \|\| cmd === 'identity'/.test(src); } },
|
|
2868
2868
|
{ name: 'lib/mcp-tools: MCP 도구 정의 모듈 단일출처 (_mcpToolCount=모듈 length, Codex #5 영구해소) (UR-0025 1.9.297)', run: () => { const T = require('../lib/mcp-tools'); return Array.isArray(T) && T.length >= 81 && T.every(t => t.name && t.description && t.inputSchema) && T[0].name === 'leerness_handoff' && _mcpToolCount() === T.length && !/const TOOLS = \[/.test(read(__filename)); } },
|
|
2869
2869
|
{ name: 'writeUtf8: 원자적 쓰기(temp→rename) 손상방지 행위 (UR-0038 외부리뷰 / CV-5 행위화 1.9.366)', run: () => { if (typeof writeUtf8 !== 'function') return false; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_wu_')); try { const f = path.join(tmp, 'sub', 'a.txt'); writeUtf8(f, '한글 UTF-8 내용'); const okContent = read(f) === '한글 UTF-8 내용'; const noTmpLeft = fs.readdirSync(path.dirname(f)).every(n => !n.includes('.tmp-')); return okContent && noTmpLeft; } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } } },
|
|
@@ -2901,7 +2901,7 @@ function _selfTestCases() {
|
|
|
2901
2901
|
{ name: 'lib/pure-utils: project-brief config 분리(_BRIEF_FIELDS/_briefFilled) + 인라인 제거 (UR-0025 1.9.330)', run: () => { const m = require('../lib/pure-utils'); const cfgOk = Array.isArray(m._BRIEF_FIELDS) && m._BRIEF_FIELDS.length === 10 && m._BRIEF_FIELDS[0].key === 'intro'; const work = m._briefFilled({ intro: 'x', features: ['a'] }) === 2 && m._briefFilled({}) === 0; const src = read(__filename); const moved = m._briefFilled === _briefFilled && m._BRIEF_FIELDS === _BRIEF_FIELDS && !/^const _BRIEF_FIELDS = \[/m.test(src) && !/^function _briefFilled\(/m.test(src); return cfgOk && work && moved; } },
|
|
2902
2902
|
{ name: 'lib/pure-utils: brief 빌더 분리(_briefReadmeBlock/_briefBlueprint + BRIEF 마커, VERSION 주입) (UR-0025 1.9.331)', run: () => { const m = require('../lib/pure-utils'); const fnOk = typeof m._briefReadmeBlock === 'function' && typeof m._briefBlueprint === 'function' && m.BRIEF_START.includes('project-brief:start'); const b = { project: 'X', intro: 'i', features: ['f1'] }; const rb = m._briefReadmeBlock(b); const bp = m._briefBlueprint(b, '9.9.9'); const work = rb.includes(m.BRIEF_START) && rb.includes(m.BRIEF_END) && /f1/.test(rb) && /Blueprint/.test(bp) && /leerness v9\.9\.9/.test(bp); const src = read(__filename); const moved = m._briefBlueprint === _briefBlueprint && m.BRIEF_START === BRIEF_START && !/^function _briefReadmeBlock\(/m.test(src) && !/^function _briefBlueprint\(/m.test(src) && !/^const BRIEF_START =/m.test(src); return fnOk && work && moved; } },
|
|
2903
2903
|
{ name: 'lib/pure-utils: lessons.md 파서 분리(_parseLessonEntries) + 인라인 제거 (UR-0025 1.9.332)', run: () => { const m = require('../lib/pure-utils'); const r = m._parseLessonEntries('### 2026-06-05\n- Lesson: A\n- Tag: t\n\n### 2026-06-04\n- Lesson: B'); const work = r.length === 2 && r[0].text === 'A' && r[0].tag === 't' && r[1].tag === null && r[0].date === '2026-06-05'; const src = read(__filename); const moved = m._parseLessonEntries === _parseLessonEntries && !/^function _parseLessonEntries\(/m.test(src) && src.includes('_parseLessonEntries(read(mp))'); return work && moved; } },
|
|
2904
|
-
{ name: 'UR-0025 심층: constraints catalog→lib/catalogs + _matchConstraints→pure-utils 분리 (1.9.333)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c._DEFAULT_PLATFORM_CONSTRAINTS && Object.keys(c._DEFAULT_PLATFORM_CONSTRAINTS.platforms).length === 6 && !!c._DEFAULT_PLATFORM_CONSTRAINTS.platforms.stripe; const r = m._matchConstraints(c._DEFAULT_PLATFORM_CONSTRAINTS, 'stripe 결제'); const work = r.matched.length === 1 && r.matched[0].platform === 'stripe' && r.totalPlatforms === 6 && m._matchConstraints(null, 'x').matched.length === 0; const src = read(__filename); const moved = _DEFAULT_PLATFORM_CONSTRAINTS === c._DEFAULT_PLATFORM_CONSTRAINTS && _matchConstraints === m._matchConstraints && !/const _DEFAULT_PLATFORM_CONSTRAINTS = \{/.test(src) && src.includes('_matchConstraints(_loadPlatformConstraints(root), text)'); return catOk && work && moved; } },
|
|
2904
|
+
{ name: 'UR-0025 심층: constraints catalog→lib/catalogs + _matchConstraints→pure-utils 분리 (1.9.333) + i18n en(1.31.2)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c._DEFAULT_PLATFORM_CONSTRAINTS && Object.keys(c._DEFAULT_PLATFORM_CONSTRAINTS.platforms).length === 6 && !!c._DEFAULT_PLATFORM_CONSTRAINTS.platforms.stripe; const r = m._matchConstraints(c._DEFAULT_PLATFORM_CONSTRAINTS, 'stripe 결제'); const work = r.matched.length === 1 && r.matched[0].platform === 'stripe' && r.totalPlatforms === 6 && m._matchConstraints(null, 'x').matched.length === 0; const _H = /[가-힣]/; const enSug = (m._matchConstraints(c._DEFAULT_PLATFORM_CONSTRAINTS, 'generic api integration widget', 'en').suggestions || [])[0] || ''; const koSug = (m._matchConstraints(c._DEFAULT_PLATFORM_CONSTRAINTS, 'generic api integration widget', 'ko').suggestions || [])[0] || ''; const i18nOk = c._DEFAULT_PLATFORM_CONSTRAINTS.platforms.stripe.constraints.some(x => x.detailEn && !_H.test(x.detailEn)) && enSug.length > 0 && !_H.test(enSug) && _H.test(koSug); const src = read(__filename); const moved = _DEFAULT_PLATFORM_CONSTRAINTS === c._DEFAULT_PLATFORM_CONSTRAINTS && _matchConstraints === m._matchConstraints && !/const _DEFAULT_PLATFORM_CONSTRAINTS = \{/.test(src) && src.includes('_matchConstraints(_loadPlatformConstraints(root), text)'); return catOk && work && i18nOk && moved; } },
|
|
2905
2905
|
{ name: 'UR-0025 심층(Codex 위임·검증): intent domain catalog→lib/catalogs + _matchDomain→pure-utils 분리 (1.9.334)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c._DEFAULT_DOMAIN_CATALOG && Object.keys(c._DEFAULT_DOMAIN_CATALOG.domains).length === 5 && !!c._DEFAULT_DOMAIN_CATALOG.domains.game; const r = m._matchDomain(c._DEFAULT_DOMAIN_CATALOG, 'unity 게임'); const work = r.domain === 'game' && Array.isArray(r.components) && m._matchDomain(c._DEFAULT_DOMAIN_CATALOG, 'zzz없음').domain === null && m._matchDomain(null, 'x').domain === null; const src = read(__filename); const moved = _DEFAULT_DOMAIN_CATALOG === c._DEFAULT_DOMAIN_CATALOG && _matchDomain === m._matchDomain && !/const _DEFAULT_DOMAIN_CATALOG = \{/.test(src) && src.includes('_matchDomain(_loadDomainCatalog(root), text)'); return catOk && work && moved; } },
|
|
2906
2906
|
{ name: 'UR-0025 심층: LSP catalog→lib/catalogs(_LSP_LANG_PATTERNS) + _detectLspLang/_matchLspSymbols→pure-utils 분리 (1.9.335)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c._LSP_LANG_PATTERNS && Object.keys(c._LSP_LANG_PATTERNS).length === 5 && Array.isArray(c._LSP_LANG_PATTERNS.javascript); const langOk = m._detectLspLang('a.py') === 'python' && m._detectLspLang('b.go') === 'go' && m._detectLspLang('c.md') === 'javascript'; const sy = m._matchLspSymbols(c._LSP_LANG_PATTERNS, 'function alpha(){}\nclass Beta{}', 'javascript'); const work = sy.length === 2 && sy[0].name === 'alpha' && sy[0].kind === 'function' && sy[1].kind === 'class' && m._matchLspSymbols(null, 'x', 'javascript').length === 0; const src = read(__filename); const moved = _LSP_LANG_PATTERNS === c._LSP_LANG_PATTERNS && _detectLspLang === m._detectLspLang && _matchLspSymbols === m._matchLspSymbols && !/const _LSP_LANG_PATTERNS = \{/.test(src) && !/function _detectLspLang\(/.test(src); return catOk && langOk && work && moved; } },
|
|
2907
2907
|
{ name: 'UR-0025 심층(Codex 위임·검증): anti-laziness catalog→lib/catalogs(OPTIMISM_PATTERNS) + optimism 순수로직→pure-utils 분리 (1.9.336)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = Array.isArray(c.OPTIMISM_PATTERNS) && c.OPTIMISM_PATTERNS.length === 10 && c.OPTIMISM_PATTERNS[0].kind === 'API'; const ev = 'API 호출 완료, POST /users'; const sus = m._detectOptimism(c.OPTIMISM_PATTERNS, ev, 'function x(){}'); const conf = m._computeConfidence(c.OPTIMISM_PATTERNS, ev, 'function x(){}'); const work = sus.some(s => s.kind === 'API' && s.severity === 'high') && conf < 0.5 && m._computeConfidence(c.OPTIMISM_PATTERNS, '정리함', 'x') === 1 && m._detectOptimism(null, ev, 'x').length === 0 && m._extractUrlClaims('POST /a').length === 1 && m._verifyUrlClaim({ path: '/a' }, 'has /a') === true; const src = read(__filename); const moved = OPTIMISM_PATTERNS === c.OPTIMISM_PATTERNS && _puDetectOptimism === m._detectOptimism && !/const OPTIMISM_PATTERNS = \[/.test(src) && !/function _extractUrlClaims\(/.test(src); return catOk && work && moved; } },
|
|
@@ -2939,7 +2939,7 @@ function _selfTestCases() {
|
|
|
2939
2939
|
{ name: 'UR-0025: _parseArchiveBlocks/_parseSkillCatalog 순수 파서 모듈 분리 + 행위 (1.9.370)', run: () => { const m = require('../lib/pure-utils'); if (typeof _parseArchiveBlocks !== 'function' || typeof _parseSkillCatalog !== 'function') return false; const moved = m._parseArchiveBlocks === _parseArchiveBlocks && m._parseSkillCatalog === _parseSkillCatalog; const ab = _parseArchiveBlocks('## 제거 2026-01-01 (target: ' + '"T-1")\n### 헤더\n'); const abOk = ab.length === 1 && ab[0].date === '2026-01-01' && ab[0].target === 'T-1' && ab[0].originalHeader === '헤더'; const md = _parseSkillCatalog('- [nm](https://x/SKILL.md) — d', ''); const mdOk = md.length === 1 && md[0].name === 'nm' && md[0].format === 'markdown'; const js = _parseSkillCatalog('{' + '"skills":[{"id":"a","url":"u"}]}', ''); const jsOk = js.length === 1 && js[0].name === 'a' && js[0].format === 'json'; return moved && abOk && mdOk && jsOk; } },
|
|
2940
2940
|
{ name: 'UR-0073 Phase A: team 정의 레지스트리 (_renderTeamsMd + canonical load/save) 행위 (1.9.371)', run: () => { const m = require('../lib/pure-utils'); if (typeof teamCmd !== 'function' || typeof _renderTeamsMd !== 'function' || m._renderTeamsMd !== _renderTeamsMd) return false; const md = _renderTeamsMd([{ id: 't1', name: 'N', personas: ['security'], members: ['claude'], schedule: 'daily', status: 'active' }]); const mdOk = md.includes('## t1') && md.includes('security') && md.includes('daily') && md.includes('정의 전용'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_team_')); let rtOk = false; try { _saveTeams(tmp, [{ id: 'x', name: 'X', personas: [], members: [], schedule: 'manual', status: 'active' }]); const loaded = _loadTeams(tmp); rtOk = loaded.length === 1 && loaded[0].id === 'x' && fs.existsSync(path.join(tmp, '.harness', 'teams.json')) && fs.existsSync(path.join(tmp, '.harness', 'teams.md')); } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } return mdOk && rtOk; } },
|
|
2941
2941
|
{ name: 'UR-0073 Phase B: _composeTeamPlan dry-run 실행 계획 (멤버별 dispatch, 실행 없음) 행위 (1.9.372)', run: () => { const m = require('../lib/pure-utils'); if (typeof _composeTeamPlan !== 'function' || m._composeTeamPlan !== _composeTeamPlan) return false; const team = { id: 'rev', name: 'R', purpose: 'PR 리뷰', personas: ['security', 'perf'], members: ['claude', 'codex'], schedule: 'manual' }; const p1 = _composeTeamPlan(team, '점검'); const ok1 = p1.steps.length === 2 && p1.task === '점검' && p1.steps[0].member === 'claude' && p1.steps[0].suggestedCommand.includes('agents dispatch') && p1.steps[0].suggestedCommand.includes('--to claude') && p1.steps[0].dispatchPrompt.includes('security'); const p2 = _composeTeamPlan(team, null); const ok2 = p2.task === 'PR 리뷰'; const p3 = _composeTeamPlan({ id: 'e', personas: [], members: [] }, 'x'); const ok3 = p3.steps.length === 0 && p3.memberCount === 0; return ok1 && ok2 && ok3; } },
|
|
2942
|
-
{ name: 'UR-0073 Phase C: _teamHandoffReminders 스케줄 알림 (비-manual·active 만, 실행 트리거 아님) 행위 (1.9.373)', run: () => { const m = require('../lib/pure-utils'); if (typeof _teamHandoffReminders !== 'function' || m._teamHandoffReminders !== _teamHandoffReminders) return false; const r = _teamHandoffReminders([{ id: 'rev', schedule: 'every-session', status: 'active', members: ['a', 'b'] }, { id: 'man', schedule: 'manual', status: 'active' }, { id: 'paused', schedule: 'daily', status: 'paused' }]);
|
|
2942
|
+
{ name: 'UR-0073 Phase C: _teamHandoffReminders 스케줄 알림 (비-manual·active 만, 실행 트리거 아님) 행위 (1.9.373) + i18n en(1.31.3)', run: () => { const m = require('../lib/pure-utils'); if (typeof _teamHandoffReminders !== 'function' || m._teamHandoffReminders !== _teamHandoffReminders) return false; const r = _teamHandoffReminders([{ id: 'rev', schedule: 'every-session', status: 'active', members: ['a', 'b'] }, { id: 'man', schedule: 'manual', status: 'active' }, { id: 'paused', schedule: 'daily', status: 'paused' }]); const behaviorOk = r.length === 1 && r[0].includes('rev') && r[0].includes('every-session') && r[0].includes('team preview rev') && !r.join('|').includes('man') && !r.join('|').includes('paused'); const _H = /[가-힣]/; const en = _teamHandoffReminders([{ id: 'rev', schedule: 'every-session', status: 'active', members: ['a', 'b'], review: true }], 'en')[0] || ''; const ko = _teamHandoffReminders([{ id: 'rev', schedule: 'every-session', status: 'active', members: ['a', 'b'], review: true }])[0] || ''; const i18nOk = !_H.test(en) && /2 members/.test(en) && /review needed/.test(en) && /preview:/.test(en) && _H.test(ko) && /2명/.test(ko) && /검수필요/.test(ko); return behaviorOk && i18nOk; } },
|
|
2943
2943
|
{ name: 'UR-0074: _cadenceAssessment 릴리스 빈도 평가 (임계값) 행위 (1.9.374)', run: () => { const m = require('../lib/pure-utils'); if (typeof _cadenceAssessment !== 'function' || m._cadenceAssessment !== _cadenceAssessment || typeof releaseCadenceCmd !== 'function') return false; return _cadenceAssessment(7, 1, 1).level === 'very-high' && _cadenceAssessment(3, 1, 1).level === 'high' && _cadenceAssessment(1, 1, 1).level === 'moderate' && _cadenceAssessment(0.2, 1, 1).level === 'healthy' && _cadenceAssessment(7, 1, 1).recommendation.length > 0; } },
|
|
2944
2944
|
{ name: 'UR-0084: _withLock 획득/재진입/해제 + maxWaitMs 하드닝(10s) 행위 (1.9.375)', run: () => { if (typeof _withLock !== 'function') return false; const src = read(__filename); const hardened = /maxWaitMs = opts\.maxWaitMs \|\| 10000/.test(src); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_lock_')); try { const target = path.join(tmp, 'f.md'); let reentrant = false; const lockSeen = _withLock(target, () => { const exists = fs.existsSync(target + '.lock'); reentrant = (_withLock(target, () => 42) === 42); return exists; }); const cleaned = !fs.existsSync(target + '.lock'); return hardened && lockSeen === true && reentrant === true && cleaned; } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } } },
|
|
2945
2945
|
{ name: 'UR-0073 Phase D: _teamDeployGate 이중 게이트 (dry-run 기본/env 게이트/실행) 행위 (1.9.376)', run: () => { const m = require('../lib/pure-utils'); if (typeof _teamDeployGate !== 'function' || m._teamDeployGate !== _teamDeployGate) return false; const team = { id: 'd', deployCommand: 'echo hi' }; const noCmd = _teamDeployGate({ id: 'x' }, { yes: true, envOn: true }).mode === 'no-command'; const dry = _teamDeployGate(team, { yes: false, envOn: true }).mode === 'dry-run'; const gated = _teamDeployGate(team, { yes: true, envOn: false }).mode === 'gated'; const exec = _teamDeployGate(team, { yes: true, envOn: true }).mode === 'execute'; return noCmd && dry && gated && exec; } },
|
|
@@ -4813,8 +4813,8 @@ function _writePlatformConstraints(root, catalog) {
|
|
|
4813
4813
|
}
|
|
4814
4814
|
// 사용자 요청 텍스트에서 플랫폼 alias 매칭 → 적용 제약 목록 반환
|
|
4815
4815
|
// 1.9.333 (UR-0025 심층): 매칭 로직은 순수 _matchConstraints(catalog, text) (lib/pure-utils) — fs(load)는 여기서 주입.
|
|
4816
|
-
function _checkRequestConstraints(root, text) {
|
|
4817
|
-
return _matchConstraints(_loadPlatformConstraints(root), text);
|
|
4816
|
+
function _checkRequestConstraints(root, text, lang) {
|
|
4817
|
+
return _matchConstraints(_loadPlatformConstraints(root), text, lang);
|
|
4818
4818
|
}
|
|
4819
4819
|
|
|
4820
4820
|
// 1.9.209: pre-wake sub-agent audit (사용자 명시)
|
|
@@ -5780,9 +5780,9 @@ function constraintsCmd(root, sub, ...rest) {
|
|
|
5780
5780
|
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
5781
5781
|
const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
|
|
5782
5782
|
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
5783
|
+
const _L = _uiLang(root); const _t = (ko, en) => _L === 'en' ? en : ko; // 1.31.2 (UR-0010): function-level so list/check/add share it
|
|
5783
5784
|
|
|
5784
5785
|
if (!sub || sub === 'help' || sub === '--help') {
|
|
5785
|
-
const _L = _uiLang(root); const _t = (ko, en) => _L === 'en' ? en : ko; // 1.23.2 (UR-0010 Phase 7)
|
|
5786
5786
|
log(_t(`# leerness constraints (1.9.208) — 플랫폼/API 제약 사전 체크`, `# leerness constraints — pre-check platform/API constraints`));
|
|
5787
5787
|
log('');
|
|
5788
5788
|
log(_t(` list → 등록된 모든 플랫폼 catalog 출력 (--json 가능)`, ` list → list all registered platform catalogs (--json)`));
|
|
@@ -5800,11 +5800,13 @@ function constraintsCmd(root, sub, ...rest) {
|
|
|
5800
5800
|
log(` total platforms: ${Object.keys(catalog.platforms).length}`);
|
|
5801
5801
|
log('');
|
|
5802
5802
|
for (const [pid, plat] of Object.entries(catalog.platforms)) {
|
|
5803
|
-
|
|
5803
|
+
// 1.31.2: aliases are matchers (kept for matching); hide Hangul-only aliases from EN display
|
|
5804
|
+
const aliasList = _L === 'en' ? (plat.aliases || []).filter(a => !/[가-힣]/.test(a)) : (plat.aliases || []);
|
|
5805
|
+
log(grn(` 📦 ${pid}`) + dim(` aliases: ${aliasList.join(', ')}`));
|
|
5804
5806
|
log(dim(` docs: ${plat.docs || '-'}`));
|
|
5805
5807
|
for (const c of plat.constraints || []) {
|
|
5806
5808
|
const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? '💰' : (c.kind === 'auth' ? '🔐' : '📋'));
|
|
5807
|
-
log(` ${icon} [${c.kind}] ${c.detail}`);
|
|
5809
|
+
log(` ${icon} [${c.kind}] ${_L === 'en' && c.detailEn ? c.detailEn : c.detail}`);
|
|
5808
5810
|
}
|
|
5809
5811
|
log('');
|
|
5810
5812
|
}
|
|
@@ -5814,32 +5816,32 @@ function constraintsCmd(root, sub, ...rest) {
|
|
|
5814
5816
|
if (sub === 'check') {
|
|
5815
5817
|
const text = rest.filter(x => !x.startsWith('-')).join(' ');
|
|
5816
5818
|
if (!text) { console.error('Usage: leerness constraints check "<request text>"'); process.exit(1); }
|
|
5817
|
-
const result = _checkRequestConstraints(root, text);
|
|
5819
|
+
const result = _checkRequestConstraints(root, text, _L);
|
|
5818
5820
|
if (has('--json')) { log(JSON.stringify({ query: text, ...result }, null, 2)); return; }
|
|
5819
5821
|
log(cyan(`# leerness constraints check (1.9.208)`));
|
|
5820
5822
|
log(` query: ${text.slice(0, 80)}${text.length > 80 ? '…' : ''}`);
|
|
5821
5823
|
log('');
|
|
5822
5824
|
if (result.matched.length === 0) {
|
|
5823
|
-
log(grn(` ✓ 매칭된 플랫폼 없음 (catalog ${result.totalPlatforms}종 검토 완료)`));
|
|
5825
|
+
log(grn(_t(` ✓ 매칭된 플랫폼 없음 (catalog ${result.totalPlatforms}종 검토 완료)`, ` ✓ no platform matched (reviewed ${result.totalPlatforms} catalog entries)`)));
|
|
5824
5826
|
if (result.suggestions.length) {
|
|
5825
5827
|
log('');
|
|
5826
|
-
log(yel(` 💡
|
|
5828
|
+
log(yel(_t(` 💡 제안:`, ` 💡 suggestions:`)));
|
|
5827
5829
|
result.suggestions.forEach(s => log(` • ${s}`));
|
|
5828
5830
|
}
|
|
5829
5831
|
return;
|
|
5830
5832
|
}
|
|
5831
|
-
log(red(` ⚠ ${result.matched.length}개 플랫폼 매칭 — 제약 사전 확인
|
|
5833
|
+
log(red(_t(` ⚠ ${result.matched.length}개 플랫폼 매칭 — 제약 사전 확인 필요:`, ` ⚠ ${result.matched.length} platform(s) matched — review constraints before building:`)));
|
|
5832
5834
|
log('');
|
|
5833
5835
|
for (const m of result.matched) {
|
|
5834
5836
|
log(grn(` 📦 ${m.platform}`) + dim(` (matched: "${m.matchedAlias}")`));
|
|
5835
5837
|
log(dim(` docs: ${m.docs || '-'}`));
|
|
5836
5838
|
for (const c of m.constraints || []) {
|
|
5837
5839
|
const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? '💰' : (c.kind === 'auth' ? '🔐' : '📋'));
|
|
5838
|
-
log(` ${icon} [${c.kind}] ${c.detail}`);
|
|
5840
|
+
log(` ${icon} [${c.kind}] ${_L === 'en' && c.detailEn ? c.detailEn : c.detail}`);
|
|
5839
5841
|
}
|
|
5840
5842
|
log('');
|
|
5841
5843
|
}
|
|
5842
|
-
log(dim(` → 구현 전 위 제약을 반영한 설계 권장 (rate limiter / idempotency key / 비용 추정)`));
|
|
5844
|
+
log(dim(_t(` → 구현 전 위 제약을 반영한 설계 권장 (rate limiter / idempotency key / 비용 추정)`, ` → recommend designing with the above constraints before building (rate limiter / idempotency key / cost estimate)`)));
|
|
5843
5845
|
return;
|
|
5844
5846
|
}
|
|
5845
5847
|
|
|
@@ -5857,7 +5859,7 @@ function constraintsCmd(root, sub, ...rest) {
|
|
|
5857
5859
|
if (kind && detail) catalog.platforms[id].constraints.push({ kind: kind.trim(), detail });
|
|
5858
5860
|
_writePlatformConstraints(root, catalog);
|
|
5859
5861
|
if (has('--json')) { log(JSON.stringify(catalog.platforms[id], null, 2)); return; }
|
|
5860
|
-
log(grn(`✓ platform "${id}" 갱신 — constraints: ${catalog.platforms[id].constraints.length}`));
|
|
5862
|
+
log(grn(_t(`✓ platform "${id}" 갱신 — constraints: ${catalog.platforms[id].constraints.length}`, `✓ platform "${id}" updated — constraints: ${catalog.platforms[id].constraints.length}`)));
|
|
5861
5863
|
return;
|
|
5862
5864
|
}
|
|
5863
5865
|
|
|
@@ -7998,6 +8000,9 @@ function installSafetyCmd(opts = {}) {
|
|
|
7998
8000
|
const deps = Object.keys(pkg.dependencies || {});
|
|
7999
8001
|
const scripts = pkg.scripts || {};
|
|
8000
8002
|
const installHooks = ['preinstall', 'install', 'postinstall'].filter(h => scripts[h]);
|
|
8003
|
+
// 1.31.1 (UR-0010): install-safety 출력 영어 opt-in (한국어 기본). safeInstall 의 npx --yes 토큰 + hardeningNote 의 PowerShell 토큰은 양 언어 공통(셸-무관 가드 보존).
|
|
8004
|
+
const uiLang = _uiLang(arg('--path', process.cwd()));
|
|
8005
|
+
const t = (ko, en) => (uiLang === 'en' ? en : ko);
|
|
8001
8006
|
const profile = {
|
|
8002
8007
|
version: VERSION,
|
|
8003
8008
|
runtimeDeps: deps.length,
|
|
@@ -8011,22 +8016,23 @@ function installSafetyCmd(opts = {}) {
|
|
|
8011
8016
|
// 타 패키지 대비 일반 하드닝은 셸별로 hardeningNote 참조.
|
|
8012
8017
|
safeInstall: [
|
|
8013
8018
|
'git checkout -b chore/leerness-update',
|
|
8014
|
-
'npx --yes leerness@latest migrate plan --path . # 임시폴더 비교(읽기 전용, 셸 무관)',
|
|
8019
|
+
t('npx --yes leerness@latest migrate plan --path . # 임시폴더 비교(읽기 전용, 셸 무관)', 'npx --yes leerness@latest migrate plan --path . # read-only temp-folder compare (shell-agnostic)'),
|
|
8015
8020
|
'npx --yes leerness@latest update --yes --path .',
|
|
8016
|
-
'git diff # 변경 검토 후 커밋 또는 롤백',
|
|
8021
|
+
t('git diff # 변경 검토 후 커밋 또는 롤백', 'git diff # review the diff, then commit or roll back'),
|
|
8017
8022
|
],
|
|
8018
|
-
hardeningNote: 'leerness 자체는 0 deps / 0 install-script 라 설치/업데이트 시 임의코드 실행 없음. 일반 npx 하드닝(타 패키지 대비)은 셸별 — bash: npm_config_ignore_scripts=true npx ... · PowerShell: $env:npm_config_ignore_scripts=1; npx ... · cmd: set npm_config_ignore_scripts=1 && npx ...',
|
|
8023
|
+
hardeningNote: t('leerness 자체는 0 deps / 0 install-script 라 설치/업데이트 시 임의코드 실행 없음. 일반 npx 하드닝(타 패키지 대비)은 셸별 — bash: npm_config_ignore_scripts=true npx ... · PowerShell: $env:npm_config_ignore_scripts=1; npx ... · cmd: set npm_config_ignore_scripts=1 && npx ...',
|
|
8024
|
+
'leerness itself has 0 deps / 0 install-script, so no arbitrary code runs on install/update. General npx hardening (for other packages) is shell-specific — bash: npm_config_ignore_scripts=true npx ... · PowerShell: $env:npm_config_ignore_scripts=1; npx ... · cmd: set npm_config_ignore_scripts=1 && npx ...'),
|
|
8019
8025
|
};
|
|
8020
8026
|
if (opts.json) { log(JSON.stringify(profile, null, 2)); return; }
|
|
8021
|
-
log(`# leerness install-safety (1.9.359) — 설치 안전
|
|
8022
|
-
log(` 버전: ${VERSION} · Node: ${profile.node || '(미지정)'}`);
|
|
8023
|
-
log(` 런타임 의존성: ${deps.length === 0 ? '0건 (외부 패키지 없음 — 공급망 노출 0)' : deps.length + '건 — ' + deps.join(', ')}`);
|
|
8024
|
-
log(` install-time 스크립트: ${installHooks.length === 0 ? '없음 (preinstall/install/postinstall 0 — 설치 시 임의코드 실행 없음)' : installHooks.join(', ')}`);
|
|
8025
|
-
log(` 동작 방식: offline-first (설치 시 네트워크/빌드 불필요, 단일 bin + lib)`);
|
|
8026
|
-
log(`\n 안전 설치 워크플로 (검토 후 적용):`);
|
|
8027
|
+
log(t(`# leerness install-safety (1.9.359) — 설치 안전 프로필`, `# leerness install-safety — install safety profile`));
|
|
8028
|
+
log(t(` 버전: ${VERSION} · Node: ${profile.node || '(미지정)'}`, ` version: ${VERSION} · Node: ${profile.node || '(unspecified)'}`));
|
|
8029
|
+
log(t(` 런타임 의존성: ${deps.length === 0 ? '0건 (외부 패키지 없음 — 공급망 노출 0)' : deps.length + '건 — ' + deps.join(', ')}`, ` runtime deps: ${deps.length === 0 ? '0 (no external packages — zero supply-chain exposure)' : deps.length + ' — ' + deps.join(', ')}`));
|
|
8030
|
+
log(t(` install-time 스크립트: ${installHooks.length === 0 ? '없음 (preinstall/install/postinstall 0 — 설치 시 임의코드 실행 없음)' : installHooks.join(', ')}`, ` install-time scripts: ${installHooks.length === 0 ? 'none (0 preinstall/install/postinstall — no arbitrary code on install)' : installHooks.join(', ')}`));
|
|
8031
|
+
log(t(` 동작 방식: offline-first (설치 시 네트워크/빌드 불필요, 단일 bin + lib)`, ` how it works: offline-first (no network/build on install, single bin + lib)`));
|
|
8032
|
+
log(t(`\n 안전 설치 워크플로 (검토 후 적용):`, `\n Safe install workflow (review then apply):`));
|
|
8027
8033
|
profile.safeInstall.forEach((s, i) => log(` ${i + 1}. ${s}`));
|
|
8028
8034
|
log(`\n ⓘ ${profile.hardeningNote}`); // 1.9.397 (UR-0098): 셸별 하드닝 노트
|
|
8029
|
-
if (installHooks.length > 0) warn('install-time 스크립트 존재 — 위 ignore-scripts safe-install 권장');
|
|
8035
|
+
if (installHooks.length > 0) warn(t('install-time 스크립트 존재 — 위 ignore-scripts safe-install 권장', 'install-time scripts present — use the ignore-scripts safe-install above'));
|
|
8030
8036
|
}
|
|
8031
8037
|
function debug(root) {
|
|
8032
8038
|
root = absRoot(root); let warnings = 0, failures = 0;
|
|
@@ -9859,7 +9865,7 @@ function handoff(root) {
|
|
|
9859
9865
|
// 1.9.373 (UR-0073 Phase C): 에이전트 팀 스케줄 알림 — 비-manual 팀이 정의돼 있으면 미리보기(dry-run) 안내. 실행 없음. opt-out.
|
|
9860
9866
|
if (!has('--no-team-reminders') && !has('--compact') && !has('--quiet') && process.env.LEERNESS_NO_TEAM_REMINDERS !== '1') {
|
|
9861
9867
|
try {
|
|
9862
|
-
const _teamRem = _teamHandoffReminders(_loadTeams(root));
|
|
9868
|
+
const _teamRem = _teamHandoffReminders(_loadTeams(root), _uiLang(root));
|
|
9863
9869
|
if (_teamRem.length) {
|
|
9864
9870
|
const t = (ko, en) => (_uiLang(root) === 'en' ? en : ko); // 1.30.5 (#156 F3): 로컬 t()
|
|
9865
9871
|
log('');
|
|
@@ -17413,33 +17419,36 @@ function rolesCmd(root, sub, ...args) {
|
|
|
17413
17419
|
// 무엇을 할 수 있고 어떻게 끄는지를 명시적으로 공개해 신뢰도를 높인다. SECURITY.md 와 동일 출처.
|
|
17414
17420
|
// CAPABILITY_SURFACE / POWERFUL_COMMANDS → lib/catalogs.js (1.9.295 UR-0025 4단계)
|
|
17415
17421
|
function capabilitiesCmd(root, opts = {}) {
|
|
17422
|
+
const _L = _uiLang(root); const _t = (ko, en) => _L === 'en' ? en : ko; // 1.31.3 (UR-0010)
|
|
17416
17423
|
if (opts.json) {
|
|
17417
17424
|
log(JSON.stringify({ version: VERSION, surface: CAPABILITY_SURFACE, powerfulCommands: POWERFUL_COMMANDS,
|
|
17418
|
-
principles:
|
|
17425
|
+
principles: _L === 'en'
|
|
17426
|
+
? ['0 runtime dependencies', 'no postinstall', 'no external LLM/API/CLI auto-call without user consent', 'auto-backup before changes']
|
|
17427
|
+
: ['0 런타임 의존성', 'postinstall 없음', '사용자 동의 없이 외부 LLM/API/CLI 자동 호출 안 함', '변경 전 자동 백업'] }, null, 2));
|
|
17419
17428
|
return;
|
|
17420
17429
|
}
|
|
17421
17430
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
17422
17431
|
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
17423
17432
|
const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
17424
17433
|
const riskMark = r => r === 'high' ? '🔴 high' : r === 'medium' ? '🟡 medium' : '🟢 low';
|
|
17425
|
-
log(cy(`# leerness capabilities (1.9.272) — 권한·보안 표면
|
|
17426
|
-
log(dm(` leerness 는 권한이 큰 CLI 하네스입니다. 아래가 할 수 있는 전부이며, 각 항목의 opt-out 을 함께
|
|
17434
|
+
log(cy(_t(`# leerness capabilities (1.9.272) — 권한·보안 표면 공개`, `# leerness capabilities — permission/security surface disclosure`)));
|
|
17435
|
+
log(dm(_t(` leerness 는 권한이 큰 CLI 하네스입니다. 아래가 할 수 있는 전부이며, 각 항목의 opt-out 을 함께 표기합니다.`, ` leerness is a high-privilege CLI harness. Below is everything it can do, with the opt-out for each item.`)));
|
|
17427
17436
|
log('');
|
|
17428
|
-
log(`##
|
|
17429
|
-
log(` ✓ 런타임 의존성 0 · postinstall 없음 · 변경 전 자동
|
|
17430
|
-
log(` ✓ 사용자 동의 없이 외부 LLM/API/CLI 를 자동 호출하지
|
|
17437
|
+
log(_t(`## 원칙`, `## Principles`));
|
|
17438
|
+
log(_t(` ✓ 런타임 의존성 0 · postinstall 없음 · 변경 전 자동 백업`, ` ✓ 0 runtime dependencies · no postinstall · auto-backup before changes`));
|
|
17439
|
+
log(_t(` ✓ 사용자 동의 없이 외부 LLM/API/CLI 를 자동 호출하지 않음`, ` ✓ never auto-calls an external LLM/API/CLI without user consent`));
|
|
17431
17440
|
log('');
|
|
17432
|
-
log(`## 권한
|
|
17441
|
+
log(_t(`## 권한 표면`, `## Permission surface`));
|
|
17433
17442
|
for (const [k, v] of Object.entries(CAPABILITY_SURFACE)) {
|
|
17434
17443
|
log(` ${riskMark(v.risk)} ${cy(k)}`);
|
|
17435
|
-
log(` ${v.desc}`);
|
|
17436
|
-
log(dm(` opt-out: ${v.optOut}`));
|
|
17444
|
+
log(` ${_L === 'en' && v.descEn ? v.descEn : v.desc}`);
|
|
17445
|
+
log(dm(` opt-out: ${_L === 'en' && v.optOutEn ? v.optOutEn : v.optOut}`));
|
|
17437
17446
|
}
|
|
17438
17447
|
log('');
|
|
17439
|
-
log(`## ⚠ 주의해서 쓸 명령 (회사/운영 코드)`);
|
|
17440
|
-
for (const c of POWERFUL_COMMANDS) log(` • ${c.cmd.padEnd(22)} ${dm(c.note)}`);
|
|
17448
|
+
log(_t(`## ⚠ 주의해서 쓸 명령 (회사/운영 코드)`, `## ⚠ Commands to use with care (company/production code)`));
|
|
17449
|
+
for (const c of POWERFUL_COMMANDS) log(` • ${c.cmd.padEnd(22)} ${dm(_L === 'en' && c.noteEn ? c.noteEn : c.note)}`);
|
|
17441
17450
|
log('');
|
|
17442
|
-
log(dm(` 전체 정책: SECURITY.md · 기계 판독: leerness capabilities --json · 권한 등급: leerness policy`));
|
|
17451
|
+
log(dm(_t(` 전체 정책: SECURITY.md · 기계 판독: leerness capabilities --json · 권한 등급: leerness policy`, ` full policy: SECURITY.md · machine-readable: leerness capabilities --json · permission tiers: leerness policy`)));
|
|
17443
17452
|
}
|
|
17444
17453
|
|
|
17445
17454
|
// ===== 1.9.281 (UR-0034, GPT-5.5 범용 하네스): 권한 등급(permission tiers) =====
|
package/lib/catalogs.js
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
// 런타임 변형 없음 — capabilitiesCmd/adapterCmd/_reuseDetect/reuseCheckCmd 소비처 모두 읽기 전용.
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
+
// 1.31.3 (UR-0010): desc/optOut/note 는 ko 기본 + *En 영어 변형 (capabilities --language en 렌더가 *En||원본 사용).
|
|
6
7
|
const CAPABILITY_SURFACE = {
|
|
7
|
-
filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
|
|
8
|
-
network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
|
|
9
|
-
childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
|
|
10
|
-
externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
|
|
11
|
-
automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
|
|
12
|
-
claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
|
|
8
|
+
filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', descEn: 'Create/update .harness/ metadata files, auto-backup to .harness/archive/ before changes. Does not modify source code directly.', optOut: '핵심 동작 (백업으로 보호)', optOutEn: 'core behavior (protected by backups)' },
|
|
9
|
+
network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', descEn: 'Only npm latest-version comparison (update --check). No other external URL is fetched automatically.', optOut: 'LEERNESS_OFFLINE=1', optOutEn: 'LEERNESS_OFFLINE=1' },
|
|
10
|
+
childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', descEn: 'git (status/commit/push on explicit commands), npm test (verify-code), external CLI --version detection. Shell spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in', optOutEn: 'limited to verify family · external CLIs are opt-in' },
|
|
11
|
+
externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', descEn: 'agents dispatch/multi — calls external AI CLIs (claude/codex/agy/grok/copilot). By default only generates command text; spawns for real on multi --execute.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)', optOutEn: 'disabled unless LEERNESS_ENABLE_* is set (off by default)' },
|
|
12
|
+
automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', descEn: 'web(playwright)/pc(robotjs)/lsp(typescript) bridges — opt-in dependencies. pc controls mouse/keyboard (full access).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)', optOutEn: 'disabled when the dependency is not installed (off by default, explicit install required)' },
|
|
13
|
+
claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', descEn: 'On init, installs a SessionStart hook (update --check) into .claude/settings.local.json.', optOut: 'leerness init . --no-auto-update', optOutEn: 'leerness init . --no-auto-update' }
|
|
13
14
|
};
|
|
14
15
|
const POWERFUL_COMMANDS = [
|
|
15
|
-
{ cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
|
|
16
|
-
{ cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
|
|
17
|
-
{ cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
|
|
18
|
-
{ cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
|
|
19
|
-
{ cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
|
|
20
|
-
{ cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
|
|
21
|
-
{ cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
|
|
16
|
+
{ cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)', noteEn: 'creates .harness/ 50+ files + .claude hook (backup before changes)' },
|
|
17
|
+
{ cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신', noteEn: 'auto migration — updates metadata files' },
|
|
18
|
+
{ cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)', noteEn: 'actually spawns external AI CLIs (parallel execution)' },
|
|
19
|
+
{ cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release', noteEn: 'git push + npm publish + GitHub release' },
|
|
20
|
+
{ cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)', noteEn: 'mouse/keyboard control (robotjs, full access)' },
|
|
21
|
+
{ cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)', noteEn: 'headless browser automation (playwright)' },
|
|
22
|
+
{ cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도', noteEn: 'enables external CLIs + attempts auto-install' }
|
|
22
23
|
];
|
|
23
24
|
const ADAPTERS = {
|
|
24
25
|
claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
|
|
@@ -66,8 +67,8 @@ const _DEFAULT_PLATFORM_CONSTRAINTS = {
|
|
|
66
67
|
docs: 'https://stripe.com/docs/rate-limits',
|
|
67
68
|
constraints: [
|
|
68
69
|
{ kind: 'rate-limit', detail: 'read: 100 req/s, write: 100 req/s (live mode), test mode: 25 req/s' },
|
|
69
|
-
{ kind: 'idempotency', detail: 'Idempotency-Key 헤더 24h 유지 — 중복 결제 방지 필수' },
|
|
70
|
-
{ kind: 'webhook', detail: 'webhook 서명 검증 필수 (Stripe-Signature header + endpoint secret)' }
|
|
70
|
+
{ kind: 'idempotency', detail: 'Idempotency-Key 헤더 24h 유지 — 중복 결제 방지 필수', detailEn: 'keep Idempotency-Key header for 24h — required to prevent duplicate charges' },
|
|
71
|
+
{ kind: 'webhook', detail: 'webhook 서명 검증 필수 (Stripe-Signature header + endpoint secret)', detailEn: 'verify the webhook signature (Stripe-Signature header + endpoint secret) — required' }
|
|
71
72
|
]
|
|
72
73
|
},
|
|
73
74
|
openai: {
|
|
@@ -75,8 +76,8 @@ const _DEFAULT_PLATFORM_CONSTRAINTS = {
|
|
|
75
76
|
docs: 'https://platform.openai.com/docs/guides/rate-limits',
|
|
76
77
|
constraints: [
|
|
77
78
|
{ kind: 'rate-limit', detail: 'tier-based: Free 3 RPM / Tier 1 500 RPM / Tier 5 10,000 RPM' },
|
|
78
|
-
{ kind: 'token-limit', detail: 'TPM (tokens/min) 별도 — 큰 입력 시 RPM 도달 전 차단 가능' },
|
|
79
|
-
{ kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens — 대량 호출 전 비용 추정 필수' }
|
|
79
|
+
{ kind: 'token-limit', detail: 'TPM (tokens/min) 별도 — 큰 입력 시 RPM 도달 전 차단 가능', detailEn: 'TPM (tokens/min) is separate — large inputs can hit it before RPM' },
|
|
80
|
+
{ kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens — 대량 호출 전 비용 추정 필수', detailEn: 'gpt-4: $30/$60 per 1M input/output tokens — estimate cost before high volume' }
|
|
80
81
|
]
|
|
81
82
|
},
|
|
82
83
|
anthropic: {
|
|
@@ -84,7 +85,7 @@ const _DEFAULT_PLATFORM_CONSTRAINTS = {
|
|
|
84
85
|
docs: 'https://docs.anthropic.com/claude/reference/rate-limits',
|
|
85
86
|
constraints: [
|
|
86
87
|
{ kind: 'rate-limit', detail: 'tier-based: Free 5 RPM / Tier 1 50 RPM / Tier 4 4,000 RPM' },
|
|
87
|
-
{ kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도' },
|
|
88
|
+
{ kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도', detailEn: 'claude-sonnet 200K context, claude-opus 200K, 1M tier separate' },
|
|
88
89
|
{ kind: 'cost', detail: 'sonnet: $3/$15 per 1M tokens (1M context tier 2x)' }
|
|
89
90
|
]
|
|
90
91
|
},
|
|
@@ -94,15 +95,15 @@ const _DEFAULT_PLATFORM_CONSTRAINTS = {
|
|
|
94
95
|
constraints: [
|
|
95
96
|
{ kind: 'rate-limit', detail: 'authenticated: 5,000 req/hr, unauthenticated: 60 req/hr' },
|
|
96
97
|
{ kind: 'rate-limit', detail: 'search API: 30 req/min (authenticated)' },
|
|
97
|
-
{ kind: 'secondary', detail: 'secondary rate limit — concurrent + content creation 별도 가드' }
|
|
98
|
+
{ kind: 'secondary', detail: 'secondary rate limit — concurrent + content creation 별도 가드', detailEn: 'secondary rate limit — guard concurrent + content-creation separately' }
|
|
98
99
|
]
|
|
99
100
|
},
|
|
100
101
|
discord: {
|
|
101
102
|
aliases: ['discord', 'discord api', 'discord bot'],
|
|
102
103
|
docs: 'https://discord.com/developers/docs/topics/rate-limits',
|
|
103
104
|
constraints: [
|
|
104
|
-
{ kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도' },
|
|
105
|
-
{ kind: 'invalid', detail: '10,000 invalid req/10min → 1h ban 위험' }
|
|
105
|
+
{ kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도', detailEn: 'global: 50 req/s, per-route separate' },
|
|
106
|
+
{ kind: 'invalid', detail: '10,000 invalid req/10min → 1h ban 위험', detailEn: '10,000 invalid req/10min → risk of a 1h ban' }
|
|
106
107
|
]
|
|
107
108
|
},
|
|
108
109
|
twitter: {
|
|
@@ -110,7 +111,7 @@ const _DEFAULT_PLATFORM_CONSTRAINTS = {
|
|
|
110
111
|
docs: 'https://developer.twitter.com/en/docs/twitter-api/rate-limits',
|
|
111
112
|
constraints: [
|
|
112
113
|
{ kind: 'rate-limit', detail: 'tier-based: Free 1,500 posts/month, Basic 50,000 posts/month' },
|
|
113
|
-
{ kind: 'auth', detail: 'OAuth 2.0 PKCE 필수 (user context), App-only는 별도 endpoint' }
|
|
114
|
+
{ kind: 'auth', detail: 'OAuth 2.0 PKCE 필수 (user context), App-only는 별도 endpoint', detailEn: 'OAuth 2.0 PKCE required (user context); App-only uses a separate endpoint' }
|
|
114
115
|
]
|
|
115
116
|
}
|
|
116
117
|
}
|
package/lib/pure-utils.js
CHANGED
|
@@ -462,7 +462,8 @@ function _parseSkillMd(text) {
|
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
// 1.9.333 (UR-0025 심층): 순수 플랫폼 제약 매칭 — catalog + 텍스트 → 매칭 플랫폼/제약/제안 (fs 의존 0, catalog 주입).
|
|
465
|
-
|
|
465
|
+
// 1.31.2 (UR-0010): optional lang ('en') → 영어 suggestion. 기본 'ko' (무회귀, selftest 2-arg 호출 보존).
|
|
466
|
+
function _matchConstraints(catalog, text, lang) {
|
|
466
467
|
if (!text || typeof text !== 'string' || !catalog || !catalog.platforms) return { matched: [], suggestions: [] };
|
|
467
468
|
const lower = text.toLowerCase();
|
|
468
469
|
const matched = [];
|
|
@@ -474,7 +475,9 @@ function _matchConstraints(catalog, text) {
|
|
|
474
475
|
const suggestions = [];
|
|
475
476
|
const generic = /\bapi\b|연동|integration|호출|rate|limit|quota|webhook/i.test(text);
|
|
476
477
|
if (generic && matched.length === 0) {
|
|
477
|
-
suggestions.push(
|
|
478
|
+
suggestions.push(lang === 'en'
|
|
479
|
+
? 'generic API-integration keywords detected — run leerness constraints list to review the pre-registered platform catalog'
|
|
480
|
+
: '일반적 API 연동 키워드 감지 — leerness constraints list 로 사전 등록된 플랫폼 catalog 확인 권장');
|
|
478
481
|
}
|
|
479
482
|
return { matched, suggestions, totalPlatforms: Object.keys(catalog.platforms).length };
|
|
480
483
|
}
|
|
@@ -807,10 +810,18 @@ function _composeTeamPlan(team, task) {
|
|
|
807
810
|
}
|
|
808
811
|
|
|
809
812
|
// 1.9.373 (UR-0073 Phase C): 비-manual·active 팀의 handoff 스케줄 알림 라인 (순수). 실행 트리거 아님 — 미리보기 안내만.
|
|
810
|
-
|
|
813
|
+
// 1.31.3 (UR-0010): optional lang ('en') → 영어 라벨. 기본 'ko' (무회귀, 1-arg 호출 보존).
|
|
814
|
+
function _teamHandoffReminders(teams, lang) {
|
|
815
|
+
const en = lang === 'en';
|
|
811
816
|
return (teams || [])
|
|
812
817
|
.filter(t => t && t.schedule && t.schedule !== 'manual' && (t.status || 'active') === 'active' && t.id)
|
|
813
|
-
.map(t =>
|
|
818
|
+
.map(t => {
|
|
819
|
+
const n = Array.isArray(t.members) ? t.members.length : 0;
|
|
820
|
+
const memberPart = n ? (en ? ` · ${n} member${n === 1 ? '' : 's'}` : ` · ${n}명`) : '';
|
|
821
|
+
const reviewPart = t.review !== false ? (en ? ' · review needed' : ' · 검수필요') : '';
|
|
822
|
+
const preview = en ? 'preview' : '미리보기';
|
|
823
|
+
return `🤝 ${t.id} (${t.schedule})${memberPart}${reviewPart} — ${preview}: leerness team preview ${t.id}`;
|
|
824
|
+
});
|
|
814
825
|
}
|
|
815
826
|
|
|
816
827
|
// 1.9.374 (UR-0074): 릴리스 케이던스 평가 (순수) — releases/day → 수준 + 권장. 외부리뷰 "릴리스 빈도 과다" 가시화.
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6188,6 +6188,83 @@ total++;
|
|
|
6188
6188
|
if (!ok) failed++;
|
|
6189
6189
|
}
|
|
6190
6190
|
|
|
6191
|
+
// 1.31.1 회귀 (UR-0010): install-safety 출력 영어 opt-in — en 한글 0 + ko 보존 + 셸-무관 가드(npx --yes/PowerShell/no npm_config prefix) 양 언어 보존.
|
|
6192
|
+
total++;
|
|
6193
|
+
{
|
|
6194
|
+
let ok = false;
|
|
6195
|
+
try {
|
|
6196
|
+
const H = /[가-힣]/;
|
|
6197
|
+
const en = (cp.spawnSync(process.execPath, [CLI, 'install-safety', '--language', 'en'], { encoding: 'utf8', timeout: 15000 }).stdout) || '';
|
|
6198
|
+
const ko = (cp.spawnSync(process.execPath, [CLI, 'install-safety'], { encoding: 'utf8', timeout: 15000 }).stdout) || '';
|
|
6199
|
+
const enOk = /install safety profile/.test(en) && !H.test(en);
|
|
6200
|
+
const koOk = /설치 안전 프로필/.test(ko);
|
|
6201
|
+
let guardOk = false;
|
|
6202
|
+
try { const j = JSON.parse(cp.spawnSync(process.execPath, [CLI, 'install-safety', '--json', '--language', 'en'], { encoding: 'utf8', timeout: 15000 }).stdout); guardOk = j.safeInstall.filter(x => x.includes('npx --yes')).length >= 2 && j.hardeningNote.includes('PowerShell') && !j.safeInstall.some(x => /^npm_config_\w+=/.test(String(x).trim())); } catch {}
|
|
6203
|
+
ok = enOk && koOk && guardOk;
|
|
6204
|
+
} catch {}
|
|
6205
|
+
console.log(ok ? '✓ B(1.31.1) UR-0010: install-safety en 영어(한글 0) + ko 보존 + 셸-무관 가드(npx --yes/PowerShell/no npm_config prefix) 양 언어 보존' : '✗ install-safety 영어화 가드 실패');
|
|
6206
|
+
if (!ok) failed++;
|
|
6207
|
+
}
|
|
6208
|
+
|
|
6209
|
+
// 1.31.2 회귀 (UR-0010): constraints 영어화 — list/check 라벨 + 카탈로그 detailEn + suggestion 영어 / ko 보존 / 매칭(한국어 alias) 무회귀.
|
|
6210
|
+
total++;
|
|
6211
|
+
{
|
|
6212
|
+
let ok = false;
|
|
6213
|
+
try {
|
|
6214
|
+
const H = /[가-힣]/;
|
|
6215
|
+
const cap = (args) => (cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 15000 }).stdout) || '';
|
|
6216
|
+
const listEn = cap(['constraints', 'list', '--language', 'en']);
|
|
6217
|
+
const listKo = cap(['constraints', 'list']);
|
|
6218
|
+
const chkEn = cap(['constraints', 'check', 'stripe payment api integration', '--language', 'en']);
|
|
6219
|
+
const noMatchEn = cap(['constraints', 'check', 'build a generic api integration widget xyz', '--language', 'en']);
|
|
6220
|
+
// EN: zero Hangul + English catalog detail + English suggestion/labels surfaced
|
|
6221
|
+
const enOk = !H.test(listEn) && /duplicate charges/.test(listEn) && /no platform matched/.test(noMatchEn)
|
|
6222
|
+
&& /review constraints before building/.test(chkEn) && !H.test(chkEn) && !H.test(noMatchEn)
|
|
6223
|
+
&& /review the pre-registered platform catalog/.test(noMatchEn);
|
|
6224
|
+
// KO preserved: Korean catalog detail still present in default output
|
|
6225
|
+
const koOk = H.test(listKo) && /필수|별도/.test(listKo) && /매칭된 플랫폼 없음|플랫폼 매칭/.test(cap(['constraints', 'check', '일반 api 연동 위젯 xyz']));
|
|
6226
|
+
// matching unaffected: Korean alias still matches stripe (--json)
|
|
6227
|
+
let matchOk = false;
|
|
6228
|
+
try { const j = JSON.parse(cap(['constraints', 'check', 'stripe 결제 api 연동', '--json'])); matchOk = (j.matched || []).some(m => m.platform === 'stripe'); } catch {}
|
|
6229
|
+
ok = enOk && koOk && matchOk;
|
|
6230
|
+
} catch {}
|
|
6231
|
+
console.log(ok ? '✓ B(1.31.2) UR-0010: constraints en 영어(list/check 라벨+카탈로그 detailEn+suggestion, 한글 0) + ko 보존 + 한국어 alias 매칭 무회귀' : '✗ constraints 영어화 가드 실패');
|
|
6232
|
+
if (!ok) failed++;
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6235
|
+
// 1.31.3 회귀 (UR-0010): capabilities(권한·보안 표면) 영어화(라벨+카탈로그 descEn/optOutEn/noteEn+principles) + team reminder 본문 영어화 / ko 보존.
|
|
6236
|
+
total++;
|
|
6237
|
+
{
|
|
6238
|
+
let ok = false;
|
|
6239
|
+
try {
|
|
6240
|
+
const H = /[가-힣]/;
|
|
6241
|
+
const cap = (args) => (cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 15000 }).stdout) || '';
|
|
6242
|
+
const capEn = cap(['capabilities', '--language', 'en']);
|
|
6243
|
+
const capKo = cap(['capabilities']);
|
|
6244
|
+
const capJsonEn = cap(['capabilities', '--json', '--language', 'en']);
|
|
6245
|
+
// EN: zero Hangul + English catalog desc/note + English labels; KO: preserved; JSON: English principles
|
|
6246
|
+
const capOk = !H.test(capEn) && /metadata files|external AI CLIs|mouse\/keyboard/.test(capEn)
|
|
6247
|
+
&& /Permission surface/.test(capEn) && /Principles/.test(capEn)
|
|
6248
|
+
&& H.test(capKo) && /권한 표면|백업/.test(capKo)
|
|
6249
|
+
&& /0 runtime dependencies/.test(capJsonEn);
|
|
6250
|
+
// team reminder body en/ko via handoff (full wiring: pure lang + caller _uiLang)
|
|
6251
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-team-i18n-'));
|
|
6252
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
6253
|
+
cp.spawnSync(process.execPath, [CLI, 'team', 'add', 'nightly', '--members', 'a,b', '--schedule', 'every-session', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6254
|
+
const hoEn = (cp.spawnSync(process.execPath, [CLI, 'handoff', d, '--language', 'en'], { encoding: 'utf8', timeout: 30000 }).stdout) || '';
|
|
6255
|
+
const hoKo = (cp.spawnSync(process.execPath, [CLI, 'handoff', d], { encoding: 'utf8', timeout: 30000 }).stdout) || '';
|
|
6256
|
+
const teamEn = hoEn.split('\n').filter(l => /🤝|team preview nightly/.test(l));
|
|
6257
|
+
const teamKo = hoKo.split('\n').filter(l => /🤝|team preview nightly/.test(l));
|
|
6258
|
+
const teamOk = teamEn.length >= 2 && !teamEn.some(l => H.test(l))
|
|
6259
|
+
&& teamEn.some(l => /2 members/.test(l)) && teamEn.some(l => /review needed/.test(l)) && teamEn.some(l => /preview:/.test(l))
|
|
6260
|
+
&& teamKo.some(l => /2명/.test(l)) && teamKo.some(l => /검수필요/.test(l)) && teamKo.some(l => /미리보기/.test(l));
|
|
6261
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch {}
|
|
6262
|
+
ok = capOk && teamOk;
|
|
6263
|
+
} catch {}
|
|
6264
|
+
console.log(ok ? '✓ B(1.31.3) UR-0010: capabilities en 영어(표면 desc/optOut/note/principles, 한글 0) + ko 보존 + team reminder 본문 en/ko(handoff 전체배선)' : '✗ capabilities/team reminder 영어화 가드 실패');
|
|
6265
|
+
if (!ok) failed++;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6191
6268
|
// 1.9.430 (10th 외부평가 UR-0130): health 보안 CRITICAL(커밋 시크릿)은 --strict 없이도 exit 1(CI 게이트). 클린은 exit 0.
|
|
6192
6269
|
total++;
|
|
6193
6270
|
{
|