leerness 1.9.207 β†’ 1.9.208

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,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.208 β€” 2026-05-22
4
+
5
+ **🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ
8
+ > *"μ‚¬μš©μžμ˜ λͺ…령을 λ°›μœΌλ©΄, κ³ λ €ν•΄μ•Όν•˜λŠ” 뢀뢄을 λ¨Όμ € ν™•μΈν•˜κ³  μ§„ν–‰ν•˜κ²Œ ν•˜λ©΄ μ–΄λ–¨μ§€ / νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"*
9
+
10
+ ### 1. `.harness/platform-constraints.json` + κΈ°λ³Έ catalog 6μ’…
11
+ - **stripe** (rate: 100 req/s, idempotency key, webhook signing)
12
+ - **openai** (tier-based RPM, TPM, cost per 1M tokens)
13
+ - **anthropic** (claude tier, context-window, cost)
14
+ - **github** (5K req/hr auth, search 30 req/min, secondary)
15
+ - **discord** (50 req/s global, invalid ban)
16
+ - **twitter** (tier-based, OAuth 2.0 PKCE)
17
+ - 각 alias array λ§€μΉ­ + docs URL 동봉
18
+
19
+ ### 2. 헬퍼 ν•¨μˆ˜
20
+ - `_loadPlatformConstraints(root)` β€” default merge user override
21
+ - `_writePlatformConstraints(root, catalog)` β€” μ‚¬μš©μž catalog μ €μž₯
22
+ - `_checkRequestConstraints(root, text)` β€” ν…μŠ€νŠΈ β†’ λ§€μΉ­ ν”Œλž«νΌ + μ œμ•½ + suggestions
23
+
24
+ ### 3. `leerness constraints <list|check|add>` CLI
25
+ - `list` β€” 등둝 catalog 좜λ ₯ (--json)
26
+ - `check "<req>"` β€” μ‚¬μš©μž μš”μ²­ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json)
27
+ - `add <id> --alias name --constraint "kind:detail"` β€” μ‚¬μš©μž μ •μ˜
28
+
29
+ ### 4. review-request μžλ™ 톡합
30
+ - `_checkRequestConstraints()` review-request λ‚΄λΆ€μ—μ„œ μžλ™ 호좜
31
+ - JSON 좜λ ₯에 `platformConstraints` + `constraintSuggestions` ν•„λ“œ μΆ”κ°€
32
+ - human 좜λ ₯에 `## 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크` μ„Ήμ…˜
33
+ - efficiencyHints 에 λ§€μΉ­ N건 μ•Œλ¦Ό
34
+
35
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.200~207) β€” λͺ¨λ‘ μœ μ§€
36
+
37
+ ### 6. stress-v153 β€” 18/18 PASS
38
+ - 1.9.208 (9) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
39
+ - κΈ°λ³Έ 6 catalog + μ‚¬μš©μž μ •μ˜ add + review-request 톡합 검증
40
+ - μ„±λŠ₯: --version cold start avg 483ms Β· MCP 54 도ꡬ 431ms
41
+
42
+ ### 7. μžλ™ release (70 λΌμš΄λ“œ main-push streak Β· 31 λΌμš΄λ“œ npm publish streak)
43
+ - `release sync-main` μžλ™ β†’ main merge + npm publish leerness@1.9.208
44
+
45
+ ---
46
+
3
47
  ## 1.9.207 β€” 2026-05-22
4
48
 
5
49
  **πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 MVP (μ‚¬μš©μž λͺ…μ‹œ).**
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.207-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v152-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-137-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-69_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![requests](https://img.shields.io/badge/requests-audit_λˆ„λ½ν™•μΈ-success)]() [![perf](https://img.shields.io/badge/cold_start-2206ms-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-μ¦‰μ‹œ_μ‚¬μš©_κ°€λŠ₯-success)]() [![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.208-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v153-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-138-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-70_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![constraints](https://img.shields.io/badge/constraints-API_사전체크_6plat-success)]() [![perf](https://img.shields.io/badge/cold_start-483ms-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-μ¦‰μ‹œ_μ‚¬μš©_κ°€λŠ₯-success)]() [![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.207 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.208 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 (requests audit) (μ‚¬μš©μž λͺ…μ‹œ) β•‘
17
+ β•‘ 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 6μ’… (μ‚¬μš©μž λͺ…μ‹œ) β•‘
18
18
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -7,7 +7,7 @@ const cp = require('child_process');
7
7
  const os = require('os'); // 1.9.178: _publishToNpm μ—μ„œ os.tmpdir() μ‚¬μš© (μ „μ—­ import)
8
8
  const readline = require('readline');
9
9
 
10
- const VERSION = '1.9.207';
10
+ const VERSION = '1.9.208';
11
11
 
12
12
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning μ–΅μ œ (μ‚¬μš©μž λͺ…μ‹œ).
13
13
  // leerness λŠ” cross-platform PATH resolution 을 μœ„ν•΄ shell: true λ₯Ό μ˜λ„μ μœΌλ‘œ μ‚¬μš© (claude.cmd / ollama.cmd λ“± Windows .cmd 처리).
@@ -2006,6 +2006,111 @@ function _auditUserRequests(root) {
2006
2006
  };
2007
2007
  }
2008
2008
 
2009
+ // 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
2010
+ // "μ‚¬μš©μžμ˜ λͺ…령을 λ°›μœΌλ©΄, κ³ λ €ν•΄μ•Όν•˜λŠ” 뢀뢄을 λ¨Όμ € ν™•μΈν•˜κ³  μ§„ν–‰ν•˜κ²Œ ν•˜λ©΄ μ–΄λ–¨μ§€"
2011
+ // "νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"
2012
+ // .harness/platform-constraints.json 에 ν”Œλž«νΌλ³„ μ œμ•½ catalog μ €μž₯ β†’ μš”μ²­ ν…μŠ€νŠΈ ν‚€μ›Œλ“œ λ§€μΉ­ β†’ 적용 κ°€λŠ₯ν•œ μ œμ•½ 보고
2013
+ // κΈ°λ³Έ catalog 6μ’…: Stripe / OpenAI / Anthropic / GitHub / Discord / Twitter
2014
+ function _platformConstraintsPath(root) { return path.join(root, '.harness', 'platform-constraints.json'); }
2015
+ const _DEFAULT_PLATFORM_CONSTRAINTS = {
2016
+ version: '1.9.208',
2017
+ platforms: {
2018
+ stripe: {
2019
+ aliases: ['stripe', 'stripe api', 'payment', '결제'],
2020
+ docs: 'https://stripe.com/docs/rate-limits',
2021
+ constraints: [
2022
+ { kind: 'rate-limit', detail: 'read: 100 req/s, write: 100 req/s (live mode), test mode: 25 req/s' },
2023
+ { kind: 'idempotency', detail: 'Idempotency-Key 헀더 24h μœ μ§€ β€” 쀑볡 결제 λ°©μ§€ ν•„μˆ˜' },
2024
+ { kind: 'webhook', detail: 'webhook μ„œλͺ… 검증 ν•„μˆ˜ (Stripe-Signature header + endpoint secret)' }
2025
+ ]
2026
+ },
2027
+ openai: {
2028
+ aliases: ['openai', 'gpt', 'chatgpt', 'gpt-4', 'gpt-3'],
2029
+ docs: 'https://platform.openai.com/docs/guides/rate-limits',
2030
+ constraints: [
2031
+ { kind: 'rate-limit', detail: 'tier-based: Free 3 RPM / Tier 1 500 RPM / Tier 5 10,000 RPM' },
2032
+ { kind: 'token-limit', detail: 'TPM (tokens/min) 별도 β€” 큰 μž…λ ₯ μ‹œ RPM 도달 μ „ 차단 κ°€λŠ₯' },
2033
+ { kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens β€” λŒ€λŸ‰ 호좜 μ „ λΉ„μš© μΆ”μ • ν•„μˆ˜' }
2034
+ ]
2035
+ },
2036
+ anthropic: {
2037
+ aliases: ['anthropic', 'claude', 'claude api', 'sonnet', 'opus', 'haiku'],
2038
+ docs: 'https://docs.anthropic.com/claude/reference/rate-limits',
2039
+ constraints: [
2040
+ { kind: 'rate-limit', detail: 'tier-based: Free 5 RPM / Tier 1 50 RPM / Tier 4 4,000 RPM' },
2041
+ { kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도' },
2042
+ { kind: 'cost', detail: 'sonnet: $3/$15 per 1M tokens (1M context tier 2x)' }
2043
+ ]
2044
+ },
2045
+ github: {
2046
+ aliases: ['github', 'github api', 'gh api', 'octokit'],
2047
+ docs: 'https://docs.github.com/en/rest/rate-limit',
2048
+ constraints: [
2049
+ { kind: 'rate-limit', detail: 'authenticated: 5,000 req/hr, unauthenticated: 60 req/hr' },
2050
+ { kind: 'rate-limit', detail: 'search API: 30 req/min (authenticated)' },
2051
+ { kind: 'secondary', detail: 'secondary rate limit β€” concurrent + content creation 별도 κ°€λ“œ' }
2052
+ ]
2053
+ },
2054
+ discord: {
2055
+ aliases: ['discord', 'discord api', 'discord bot'],
2056
+ docs: 'https://discord.com/developers/docs/topics/rate-limits',
2057
+ constraints: [
2058
+ { kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도' },
2059
+ { kind: 'invalid', detail: '10,000 invalid req/10min β†’ 1h ban μœ„ν—˜' }
2060
+ ]
2061
+ },
2062
+ twitter: {
2063
+ aliases: ['twitter', 'twitter api', 'x api', 'x.com api'],
2064
+ docs: 'https://developer.twitter.com/en/docs/twitter-api/rate-limits',
2065
+ constraints: [
2066
+ { kind: 'rate-limit', detail: 'tier-based: Free 1,500 posts/month, Basic 50,000 posts/month' },
2067
+ { kind: 'auth', detail: 'OAuth 2.0 PKCE ν•„μˆ˜ (user context), App-onlyλŠ” 별도 endpoint' }
2068
+ ]
2069
+ }
2070
+ }
2071
+ };
2072
+ function _loadPlatformConstraints(root) {
2073
+ try {
2074
+ const fp = _platformConstraintsPath(root);
2075
+ if (!exists(fp)) return _DEFAULT_PLATFORM_CONSTRAINTS;
2076
+ const j = JSON.parse(read(fp));
2077
+ // user catalog 와 default merge β€” user override μš°μ„ 
2078
+ const merged = { ...JSON.parse(JSON.stringify(_DEFAULT_PLATFORM_CONSTRAINTS)) };
2079
+ if (j.platforms) {
2080
+ for (const k of Object.keys(j.platforms)) {
2081
+ merged.platforms[k] = j.platforms[k];
2082
+ }
2083
+ }
2084
+ return merged;
2085
+ } catch { return _DEFAULT_PLATFORM_CONSTRAINTS; }
2086
+ }
2087
+ function _writePlatformConstraints(root, catalog) {
2088
+ try {
2089
+ mkdirp(path.join(root, '.harness'));
2090
+ writeUtf8(_platformConstraintsPath(root), JSON.stringify({ ...catalog, updatedAt: new Date().toISOString() }, null, 2));
2091
+ return true;
2092
+ } catch { return false; }
2093
+ }
2094
+ // μ‚¬μš©μž μš”μ²­ ν…μŠ€νŠΈμ—μ„œ ν”Œλž«νΌ alias λ§€μΉ­ β†’ 적용 μ œμ•½ λͺ©λ‘ λ°˜ν™˜
2095
+ function _checkRequestConstraints(root, text) {
2096
+ if (!text || typeof text !== 'string') return { matched: [], suggestions: [] };
2097
+ const catalog = _loadPlatformConstraints(root);
2098
+ const lower = text.toLowerCase();
2099
+ const matched = [];
2100
+ for (const [pid, plat] of Object.entries(catalog.platforms)) {
2101
+ const aliases = plat.aliases || [];
2102
+ const hit = aliases.find(a => lower.includes(a.toLowerCase()));
2103
+ if (hit) matched.push({ platform: pid, matchedAlias: hit, docs: plat.docs, constraints: plat.constraints });
2104
+ }
2105
+ // suggestions β€” 보편 ν‚€μ›Œλ“œ (api/rate/limit/integration) μ‹œ catalog κ²€ν†  ꢌμž₯
2106
+ const suggestions = [];
2107
+ const generic = /\bapi\b|연동|integration|호좜|rate|limit|quota|webhook/i.test(text);
2108
+ if (generic && matched.length === 0) {
2109
+ suggestions.push('일반적 API 연동 ν‚€μ›Œλ“œ 감지 β€” leerness constraints list 둜 사전 λ“±λ‘λœ ν”Œλž«νΌ catalog 확인 ꢌμž₯');
2110
+ }
2111
+ return { matched, suggestions, totalPlatforms: Object.keys(catalog.platforms).length };
2112
+ }
2113
+
2009
2114
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
2010
2115
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
2011
2116
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -2231,6 +2336,104 @@ function requestsCmd(root, sub, ...rest) {
2231
2336
  process.exit(1);
2232
2337
  }
2233
2338
 
2339
+ // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 CLI (μ‚¬μš©μž λͺ…μ‹œ)
2340
+ // "νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"
2341
+ // list β†’ λ“±λ‘λœ λͺ¨λ“  ν”Œλž«νΌ catalog 좜λ ₯
2342
+ // check "<req>" β†’ μ‚¬μš©μž μš”μ²­ ν…μŠ€νŠΈμ—μ„œ ν”Œλž«νΌ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json κ°€λŠ₯)
2343
+ // add <id> --aliases ... --constraint "kind:detail" β†’ μ‚¬μš©μž μ •μ˜ ν”Œλž«νΌ μΆ”κ°€
2344
+ function constraintsCmd(root, sub, ...rest) {
2345
+ root = absRoot(root);
2346
+ const isTty = process.stdout && process.stdout.isTTY;
2347
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2348
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2349
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2350
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
2351
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2352
+
2353
+ if (!sub || sub === 'help' || sub === '--help') {
2354
+ log(`# leerness constraints (1.9.208) β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크`);
2355
+ log('');
2356
+ log(` list β†’ λ“±λ‘λœ λͺ¨λ“  ν”Œλž«νΌ catalog 좜λ ₯ (--json κ°€λŠ₯)`);
2357
+ log(` check "<request>" β†’ μ‚¬μš©μž μš”μ²­μ—μ„œ ν”Œλž«νΌ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json κ°€λŠ₯)`);
2358
+ log(` add <id> --constraint "kind:detail" --alias name`);
2359
+ log('');
2360
+ log(dim(` 예: leerness constraints check "Stripe API 연동 결제 λͺ¨λ“ˆ"`));
2361
+ return;
2362
+ }
2363
+
2364
+ if (sub === 'list') {
2365
+ const catalog = _loadPlatformConstraints(root);
2366
+ if (has('--json')) { log(JSON.stringify(catalog, null, 2)); return; }
2367
+ log(cyan(`# leerness constraints list (1.9.208)`));
2368
+ log(` total platforms: ${Object.keys(catalog.platforms).length}`);
2369
+ log('');
2370
+ for (const [pid, plat] of Object.entries(catalog.platforms)) {
2371
+ log(grn(` πŸ“¦ ${pid}`) + dim(` aliases: ${(plat.aliases || []).join(', ')}`));
2372
+ log(dim(` docs: ${plat.docs || '-'}`));
2373
+ for (const c of plat.constraints || []) {
2374
+ const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? 'πŸ’°' : (c.kind === 'auth' ? 'πŸ”' : 'πŸ“‹'));
2375
+ log(` ${icon} [${c.kind}] ${c.detail}`);
2376
+ }
2377
+ log('');
2378
+ }
2379
+ return;
2380
+ }
2381
+
2382
+ if (sub === 'check') {
2383
+ const text = rest.filter(x => !x.startsWith('-')).join(' ');
2384
+ if (!text) { console.error('Usage: leerness constraints check "<request text>"'); process.exit(1); }
2385
+ const result = _checkRequestConstraints(root, text);
2386
+ if (has('--json')) { log(JSON.stringify({ query: text, ...result }, null, 2)); return; }
2387
+ log(cyan(`# leerness constraints check (1.9.208)`));
2388
+ log(` query: ${text.slice(0, 80)}${text.length > 80 ? '…' : ''}`);
2389
+ log('');
2390
+ if (result.matched.length === 0) {
2391
+ log(grn(` βœ“ 맀칭된 ν”Œλž«νΌ μ—†μŒ (catalog ${result.totalPlatforms}μ’… κ²€ν†  μ™„λ£Œ)`));
2392
+ if (result.suggestions.length) {
2393
+ log('');
2394
+ log(yel(` πŸ’‘ μ œμ•ˆ:`));
2395
+ result.suggestions.forEach(s => log(` β€’ ${s}`));
2396
+ }
2397
+ return;
2398
+ }
2399
+ log(red(` ⚠ ${result.matched.length}개 ν”Œλž«νΌ λ§€μΉ­ β€” μ œμ•½ 사전 확인 ν•„μš”:`));
2400
+ log('');
2401
+ for (const m of result.matched) {
2402
+ log(grn(` πŸ“¦ ${m.platform}`) + dim(` (matched: "${m.matchedAlias}")`));
2403
+ log(dim(` docs: ${m.docs || '-'}`));
2404
+ for (const c of m.constraints || []) {
2405
+ const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? 'πŸ’°' : (c.kind === 'auth' ? 'πŸ”' : 'πŸ“‹'));
2406
+ log(` ${icon} [${c.kind}] ${c.detail}`);
2407
+ }
2408
+ log('');
2409
+ }
2410
+ log(dim(` β†’ κ΅¬ν˜„ μ „ μœ„ μ œμ•½μ„ λ°˜μ˜ν•œ 섀계 ꢌμž₯ (rate limiter / idempotency key / λΉ„μš© μΆ”μ •)`));
2411
+ return;
2412
+ }
2413
+
2414
+ if (sub === 'add') {
2415
+ const id = rest[0];
2416
+ if (!id) { console.error('Usage: leerness constraints add <platform-id> --constraint "kind:detail"'); process.exit(1); }
2417
+ const catalog = _loadPlatformConstraints(root);
2418
+ const aliases = (arg('--alias', '') || '').split(',').map(s => s.trim()).filter(Boolean);
2419
+ const detailRaw = arg('--constraint', '');
2420
+ if (!detailRaw) { console.error('--constraint "kind:detail" required'); process.exit(1); }
2421
+ const [kind, ...rest2] = detailRaw.split(':');
2422
+ const detail = rest2.join(':').trim();
2423
+ if (!catalog.platforms[id]) catalog.platforms[id] = { aliases: [], constraints: [] };
2424
+ if (aliases.length) catalog.platforms[id].aliases = [...new Set([...(catalog.platforms[id].aliases || []), ...aliases])];
2425
+ if (kind && detail) catalog.platforms[id].constraints.push({ kind: kind.trim(), detail });
2426
+ _writePlatformConstraints(root, catalog);
2427
+ if (has('--json')) { log(JSON.stringify(catalog.platforms[id], null, 2)); return; }
2428
+ log(grn(`βœ“ platform "${id}" κ°±μ‹  β€” constraints: ${catalog.platforms[id].constraints.length}`));
2429
+ return;
2430
+ }
2431
+
2432
+ console.error(`Unknown subcommand: ${sub}`);
2433
+ console.error(`Run: leerness constraints help`);
2434
+ process.exit(1);
2435
+ }
2436
+
2234
2437
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
2235
2438
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
2236
2439
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -15238,6 +15441,15 @@ function reviewRequestCmd(root, request) {
15238
15441
  efficiencyHints.push('✨ 좩돌 μ‹ ν˜Έ μ—†μŒ β€” μ¦‰μ‹œ μ§„ν–‰ μ•ˆμ „');
15239
15442
  }
15240
15443
 
15444
+ // 6.5) 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 체크 β€” μ‚¬μš©μž λͺ…μ‹œ ("ν˜ΈμΆœμ†λ„ μ΄ˆλ‹Ή 5회" 같은 κ·œμ • 사전 확인)
15445
+ let constraintsCheck = { matched: [], suggestions: [] };
15446
+ try {
15447
+ constraintsCheck = _checkRequestConstraints(root, text);
15448
+ if (constraintsCheck.matched.length > 0) {
15449
+ efficiencyHints.push(`⚠ ν”Œλž«νΌ μ œμ•½ ${constraintsCheck.matched.length}건 β€” leerness constraints check 둜 상세 확인`);
15450
+ }
15451
+ } catch {}
15452
+
15241
15453
  // 7) proceed ꢌμž₯ (좩돌 critical μ‹œ false)
15242
15454
  const proceed = conflictHints.length < 3 && planConflicts.length === 0;
15243
15455
 
@@ -15252,6 +15464,8 @@ function reviewRequestCmd(root, request) {
15252
15464
  featureConflicts,
15253
15465
  recommendedSteps,
15254
15466
  efficiencyHints,
15467
+ platformConstraints: constraintsCheck.matched,
15468
+ constraintSuggestions: constraintsCheck.suggestions,
15255
15469
  proceed,
15256
15470
  proceedReason: proceed ? 'μ•ˆμ „ β€” 좩돌 μ‹ ν˜Έ < 3 + plan 좩돌 0' : '⚠ 좩돌 critical β€” μ‚¬μš©μž 확인 ν›„ μ§„ν–‰',
15257
15471
  durationMs: dt
@@ -15295,6 +15509,18 @@ function reviewRequestCmd(root, request) {
15295
15509
  log(`## πŸ’‘ 효율 μ œμ•ˆ`);
15296
15510
  efficiencyHints.forEach(h => log(` ${h}`));
15297
15511
  log('');
15512
+ // 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 λ…ΈμΆœ (μ‚¬μš©μž λͺ…μ‹œ)
15513
+ if (constraintsCheck.matched.length > 0) {
15514
+ log(`## 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (${constraintsCheck.matched.length})`);
15515
+ for (const m of constraintsCheck.matched) {
15516
+ log(` - πŸ“¦ ${m.platform} (docs: ${m.docs || '-'})`);
15517
+ for (const c of (m.constraints || []).slice(0, 3)) {
15518
+ log(` β€’ [${c.kind}] ${c.detail}`);
15519
+ }
15520
+ }
15521
+ log(` β†’ leerness constraints check "${text.slice(0, 40)}…" 둜 상세 확인`);
15522
+ log('');
15523
+ }
15298
15524
  if (recommendedSteps.length) {
15299
15525
  log(`## πŸ“ ꢌμž₯ 단계 (${estimatedType})`);
15300
15526
  recommendedSteps.forEach(s => log(` ${s}`));
@@ -15568,6 +15794,8 @@ async function main() {
15568
15794
  if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
15569
15795
  // 1.9.207: leerness requests <audit|add|list|complete|drop> β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 (μ‚¬μš©μž λͺ…μ‹œ)
15570
15796
  if (cmd === 'requests') return requestsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15797
+ // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
15798
+ if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15571
15799
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
15572
15800
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
15573
15801
  if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.207",
3
+ "version": "1.9.208",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",