leerness 1.9.212 β†’ 1.9.214

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,112 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.214 β€” 2026-05-22
4
+
5
+ **πŸ›  운영 μ•ˆμ •ν™” λΌμš΄λ“œ β€” drift 차단 + AGENTS/CLAUDE 7 λΌμš΄λ“œ λˆ„μ  κ°±μ‹  + μ’…ν•© νšŒκ·€.**
6
+
7
+ > **백둜그 μ†Œμ§„ ν›„ 운영 κ°•ν™”** β€” 1.9.207~213 7 λΌμš΄λ“œμ˜ μ‹ κ·œ κΈ°λŠ₯을 AI/λ¬Έμ„œ 인지도에 톡합, drift warning ν•΄μ†Œ
8
+
9
+ ### 1. CLAUDE.md 7 λΌμš΄λ“œ λˆ„μ  톡합 (drift 차단)
10
+ - `## μ‚¬μš©μž λͺ…μ‹œ μ‹ κ·œ 7μ’… (1.9.207~213)` μ„Ήμ…˜ μ‹ μ„€
11
+ - 각 버전별 CLI + 핡심 κΈ°λŠ₯ μš”μ•½ (3~5 라인)
12
+ - AI μ—μ΄μ „νŠΈκ°€ 1.9.207~213 μ‹ κ·œ κΈ°λŠ₯을 μ¦‰μ‹œ 인지
13
+
14
+ ### 2. stress-v159 β€” **15/15 PASS** (1.9.207~213 7 λΌμš΄λ“œ 톡합 νšŒκ·€)
15
+ - 1.9.214 운영 (2)
16
+ - 1.9.207 requests 라이프사이클 (1) β€” add/audit/list/complete/drop
17
+ - 1.9.208 constraints (2) β€” stripe/openai λ§€μΉ­ + review-request 톡합
18
+ - 1.9.209 pre-wake-audit (1) β€” μ‹€ν–‰ + --last μ €μž₯ 검증
19
+ - 1.9.210 wakeup-interval (1) β€” adaptive ↔ override ↔ auto ν† κΈ€
20
+ - 1.9.211 workspace-dir (1) β€” dry-run + migrate + auto-detect
21
+ - 1.9.212 idempotency (1) β€” rule/task dedup + audit clean
22
+ - 1.9.213 intent (2) β€” game ν™•μž₯ + precise μ˜λ„ 보호
23
+ - μ„±λŠ₯ (2) + handoff (1) + release (1)
24
+
25
+ ### 3. λˆ„μ  νšŒκ·€ (1.9.200~213) β€” λͺ¨λ‘ μœ μ§€
26
+
27
+ ### 4. μžλ™ release (76 λΌμš΄λ“œ main-push streak Β· 37 λΌμš΄λ“œ npm publish streak)
28
+
29
+ ### 5. ν•Έλ“œμ˜€ν”„ (백둜그 status)
30
+ - μ‚¬μš©μž λͺ…μ‹œ task 백둜그 **0건 pending** (#298, #302~307 λͺ¨λ‘ completed)
31
+ - λ‹€μŒ λΌμš΄λ“œ: μƒˆ μ‚¬μš©μž μš”μ²­ λŒ€κΈ° λ˜λŠ” λ§ˆμΌμŠ€ν†€ 1.9.220 후보 μ€€λΉ„
32
+
33
+ ---
34
+
35
+ ## 1.9.213 β€” 2026-05-22
36
+
37
+ **🎯 intent inference + scope expansion 게이트 (μ‚¬μš©μž λͺ…μ‹œ) β€” λ§ˆμ§€λ§‰ pending task μ™„μ„±.**
38
+
39
+ ### μ‚¬μš©μž λͺ…μ‹œ
40
+ > *"μ‚¬μš©μžκ°€ λ§ν•œ λ‚΄μš©λ§Œ μˆ˜μ •ν•΄μ•Όν•˜λŠ” κ²½μš°λ„ μžˆμ§€λ§Œ, κ΄€λ ¨μžˆλŠ”κ²ƒμ΄λ‚˜ λ³΄κ°•ν•˜λ©΄ 쒋을 뢀뢄을 νŒŒμ•…ν•˜μ—¬ μ§„ν–‰ / κ²Œμž„ κ°œλ°œμ—μ„œ λ§΅+캐릭터+κΈ°λ³ΈκΈ°λŠ₯ μš”μ²­ μ‹œ μ˜λ„ νŒŒμ•…"*
41
+
42
+ ### 1. 3원칙 μ•ˆμ „ 섀계
43
+ 1. **Always-Off, Opt-In** β€” κΈ°λ³Έ λΉ„ν™œμ„±, λͺ…μ‹œμ  호좜 (`leerness intent expand`) λ˜λŠ” review-request 톡합 μ‹œλ§Œ μž‘λ™
44
+ 2. **Dry-run κΈ°λ³Έ** β€” μ‹€μ œ task add **μ ˆλŒ€ X**, λͺ¨λ“  좜λ ₯은 후보 보고만
45
+ 3. **λͺ…μ‹œ vs μΆ”λ‘  뢄리 라벨링** β€” `πŸ‘€ μ‚¬μš©μž λͺ…μ‹œ` vs `πŸ€– AI μΆ”λ‘  ν™•μž₯` 항상 ꡬ뢄
46
+
47
+ ### 2. `_classifyIntent(text)` β€” 3단계 λΆ„λ₯˜
48
+ - **precise** ("μ •ν™•νžˆ", "κ·Έκ²ƒλ§Œ", "κ·ΈλŒ€λ‘œ", "λ§ν•œλŒ€λ‘œ", "only") β†’ AI μΆ”λ‘  ν™•μž₯ **λΉ„ν™œμ„±**
49
+ - **broad** ("κΈ°λ³Έ", "포괄적", "λ“±λ“±", "λ‹€μ–‘ν•œ", "전체", "ν•„μš”ν•œ") β†’ ν™•μž₯ 후보 N건 dry-run μ œμ‹œ
50
+ - **default** (λͺ…μ‹œ μ—†μŒ) β†’ λͺ…μ‹œ μš°μ„ , ν™•μž₯ 후보 κ²€ν† μš©μœΌλ‘œ ν‘œμ‹œ
51
+
52
+ ### 3. `.harness/domain-catalog.json` β€” 5 default + user νŽΈμ§‘ κ°€λŠ₯
53
+ - **game** (10 μ»΄ν¬λ„ŒνŠΈ): map, character, gameLoop, collision, camera, hud, audio, save, menu, input
54
+ - **web** (8 μ»΄ν¬λ„ŒνŠΈ): routing, state, auth, api, db, ui, test, deploy
55
+ - **api** (7 μ»΄ν¬λ„ŒνŠΈ): endpoint, auth, rate-limit, validation, error, logging, docs
56
+ - **cli** (6 μ»΄ν¬λ„ŒνŠΈ): argParser, help, config, output, error, completion
57
+ - **data** (6 μ»΄ν¬λ„ŒνŠΈ): ingest, transform, storage, query, validation, lineage
58
+
59
+ ### 4. `_inferScopeExpansion(text, root)` β€” ν™•μž₯ 후보 dry-run
60
+ - 도메인 μžλ™ 탐지 (alias λ§€μΉ­)
61
+ - μ‚¬μš©μž λͺ…μ‹œ mention μΆ”μΆœ
62
+ - λ‚˜λ¨Έμ§€ β†’ AI μΆ”λ‘  ν™•μž₯ 후보
63
+ - **mode: 'dry-run' κ°•μ œ** (μ‹€ν–‰ X)
64
+
65
+ ### 5. `leerness intent <classify|expand|domains>` CLI
66
+ - `classify "<req>"` β€” μ˜λ„ λΆ„λ₯˜ + μ‹ ν˜Έ
67
+ - `expand "<req>"` β€” 도메인 탐지 + ν™•μž₯ 후보 dry-run
68
+ - `domains` β€” catalog 좜λ ₯
69
+ - λͺ¨λ‘ `--json` 지원
70
+
71
+ ### 6. μ‚¬μš©μž κ²Œμž„ μ˜ˆμ‹œ 검증
72
+ ```
73
+ $ leerness intent expand "λ§΅κ³Ό 캐릭터 + κΈ°λ³Έ κ²Œμž„ κΈ°λŠ₯ ν¬κ΄„μ μœΌλ‘œ λ§Œλ“€μ–΄μ€˜"
74
+ 🌐 intent: broad πŸ“¦ domain: game (matched: "κ²Œμž„")
75
+
76
+ ## πŸ‘€ μ‚¬μš©μž λͺ…μ‹œ (2)
77
+ β€’ character β€” 캐릭터/μŠ€ν”„λΌμ΄νŠΈ + μ• λ‹ˆλ©”μ΄μ…˜ μƒνƒœλ¨Έμ‹ 
78
+ β€’ gameLoop β€” κ²Œμž„ 루프 (tick/render/update)
79
+
80
+ ## πŸ€– AI μΆ”λ‘  ν™•μž₯ 후보 (8, dry-run)
81
+ [1] map / [2] collision / [3] camera / [4] hud / [5] audio / [6] save / [7] menu / [8] input
82
+
83
+ β†’ μ§„ν–‰ μ‹œ λͺ…μ‹œ 승인 ν•„μš”: leerness task add "<μ„ νƒν•œ 후보 μš”μ²­>"
84
+ β†’ μ „λΆ€ λ¬΄μ‹œν•˜λ €λ©΄ μΆ”κ°€ task add 없이 λͺ…μ‹œ μš”μ²­λ§Œ μ§„ν–‰
85
+ ```
86
+
87
+ ### 7. precise μ˜λ„ 보호 검증
88
+ ```
89
+ $ leerness intent expand "μ •ν™•νžˆ κ²Œμž„μ˜ 맡만 μˆ˜μ •ν•΄μ€˜ κ·Έκ²ƒλ§Œ"
90
+ πŸ›‘ intent: precise β€” AI μΆ”λ‘  ν™•μž₯ λΉ„ν™œμ„± (μ‚¬μš©μž μ˜λ„ 보호)
91
+ ```
92
+
93
+ ### 8. λˆ„μ  νšŒκ·€ (1.9.200~212) β€” λͺ¨λ‘ μœ μ§€
94
+
95
+ ### 9. stress-v158 β€” 19/19 PASS
96
+ - 1.9.213 (10) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
97
+ - κ²Œμž„ μ˜ˆμ‹œ broad β†’ 8 ν™•μž₯ 후보 / precise β†’ 0 ν™•μž₯ (μ˜λ„ 보호) / web 도메인 탐지
98
+ - μ„±λŠ₯: --version cold start avg 388ms Β· MCP 54 도ꡬ 434ms
99
+
100
+ ### 10. μžλ™ release (75 λΌμš΄λ“œ main-push streak Β· 36 λΌμš΄λ“œ npm publish streak)
101
+
102
+ ### 11. πŸŽ‰ μ‚¬μš©μž λͺ…μ‹œ task 백둜그 **μ™„μ „ μ†Œμ§„** (#307κΉŒμ§€ λͺ¨λ‘ completed)
103
+ - 1.9.207~213 7 λΌμš΄λ“œ 연속 μ‚¬μš©μž λͺ…μ‹œ μš”μ²­ 처리:
104
+ - 207 user-requests audit / 208 platform constraints / 209 pre-wake audit
105
+ - 210 adaptive wakeup / 211 .harness→.leerness migration / 212 idempotency dedup
106
+ - 213 intent inference + scope expansion 게이트
107
+
108
+ ---
109
+
3
110
  ## 1.9.212 β€” 2026-05-22
4
111
 
5
112
  **πŸ” λ©±λ“±μ„± 감사 + ruleAdd/taskAdd dedup 보강 (μ‚¬μš©μž λͺ…μ‹œ).**
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.212-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v157-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-142-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-74_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![idempotency](https://img.shields.io/badge/idempotency-audit_4μ˜μ—­-success)]() [![perf](https://img.shields.io/badge/cold_start-395ms-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.214-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v159-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-144-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-76_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![user-backlog](https://img.shields.io/badge/μ‚¬μš©μž_백둜그-0_pending-success)]() [![perf](https://img.shields.io/badge/cold_start-516ms-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.212 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.214 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ” λ©±λ“±μ„± 감사 + ruleAdd/taskAdd dedup (μ‚¬μš©μž λͺ…μ‹œ) β•‘
17
+ β•‘ πŸ›  운영 μ•ˆμ •ν™” + 7 λΌμš΄λ“œ λˆ„μ  톡합 (백둜그 0 pending) β•‘
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.212';
10
+ const VERSION = '1.9.214';
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 처리).
@@ -2526,6 +2526,175 @@ function _buildWorkspaceReferenceGuide(root, dirName) {
2526
2526
  return lines.join('\n');
2527
2527
  }
2528
2528
 
2529
+ // 1.9.213: intent inference + scope expansion 게이트 (μ‚¬μš©μž λͺ…μ‹œ)
2530
+ // "μ‚¬μš©μžκ°€ λ§ν•œ λ‚΄μš©λ§Œ μˆ˜μ •ν•΄μ•Όν•˜λŠ” κ²½μš°λ„ μžˆμ§€λ§Œ, κ΄€λ ¨μžˆλŠ”κ²ƒμ΄λ‚˜ λ³΄κ°•ν•˜λ©΄ 쒋을 뢀뢄을 νŒŒμ•…ν•˜μ—¬ μ§„ν–‰"
2531
+ // "κ²Œμž„ κ°œλ°œμ—μ„œ λ§΅+캐릭터+κΈ°λ³ΈκΈ°λŠ₯ μš”μ²­ μ‹œ μ˜λ„ νŒŒμ•…"
2532
+ // 3원칙: (1) Always-Off Opt-In, (2) Dry-run κΈ°λ³Έ (μ‹€ν–‰ X), (3) λͺ…μ‹œ vs μΆ”λ‘  뢄리 라벨링
2533
+ // .harness/domain-catalog.json μ‚¬μš©μž νŽΈμ§‘ κ°€λŠ₯ + default catalog 5μ’… (game/web/api/cli/data)
2534
+ function _domainCatalogPath(root) { return path.join(root, '.harness', 'domain-catalog.json'); }
2535
+ const _DEFAULT_DOMAIN_CATALOG = {
2536
+ version: '1.9.213',
2537
+ domains: {
2538
+ game: {
2539
+ aliases: ['κ²Œμž„', 'game', 'unity', 'unreal', 'godot', 'phaser', 'gamedev'],
2540
+ components: [
2541
+ { key: 'map', desc: 'λ§΅/타일 μ‹œμŠ€ν…œ + μ˜μ—­/슀폰' },
2542
+ { key: 'character', desc: '캐릭터/μŠ€ν”„λΌμ΄νŠΈ + μ• λ‹ˆλ©”μ΄μ…˜ μƒνƒœλ¨Έμ‹ ' },
2543
+ { key: 'gameLoop', desc: 'κ²Œμž„ 루프 (tick/render/update)' },
2544
+ { key: 'collision', desc: '좩돌 감지 (AABB/SAT/grid)' },
2545
+ { key: 'camera', desc: '카메라 follow + μ˜μ—­ μ œν•œ' },
2546
+ { key: 'hud', desc: 'HUD/UI (HP/score/inventory)' },
2547
+ { key: 'audio', desc: 'μ‚¬μš΄λ“œ λ§€λ‹ˆμ € (BGM/SFX)' },
2548
+ { key: 'save', desc: 'μ €μž₯/λ‘œλ“œ (slot μ‹œμŠ€ν…œ)' },
2549
+ { key: 'menu', desc: '메뉴 (메인/μΌμ‹œμ •μ§€/μ„€μ •)' },
2550
+ { key: 'input', desc: 'μž…λ ₯ ν•Έλ“€λŸ¬ (ν‚€λ³΄λ“œ/νŒ¨λ“œ/ν„°μΉ˜)' }
2551
+ ]
2552
+ },
2553
+ web: {
2554
+ aliases: ['μ›Ή', 'web', 'website', 'webapp', 'nextjs', 'react', 'vue', 'svelte', 'frontend'],
2555
+ components: [
2556
+ { key: 'routing', desc: 'λΌμš°νŒ… (path/dynamic/nested)' },
2557
+ { key: 'state', desc: 'μƒνƒœ 관리 (context/redux/zustand)' },
2558
+ { key: 'auth', desc: '인증 (OAuth/JWT/session)' },
2559
+ { key: 'api', desc: 'API ν΄λΌμ΄μ–ΈνŠΈ (fetch/axios/tanstack-query)' },
2560
+ { key: 'db', desc: 'DB 연동 (ORM/migration/seed)' },
2561
+ { key: 'ui', desc: 'UI μ»΄ν¬λ„ŒνŠΈ 라이브러리' },
2562
+ { key: 'test', desc: 'ν…ŒμŠ€νŠΈ (unit/e2e/visual)' },
2563
+ { key: 'deploy', desc: '배포 (Vercel/Netlify/Cloudflare)' }
2564
+ ]
2565
+ },
2566
+ api: {
2567
+ aliases: ['api', 'rest', 'graphql', 'endpoint', 'backend', 'server'],
2568
+ components: [
2569
+ { key: 'endpoint', desc: 'μ—”λ“œν¬μΈνŠΈ λΌμš°νŒ… + HTTP method' },
2570
+ { key: 'auth', desc: '인증/인가 (API key/OAuth/JWT)' },
2571
+ { key: 'rate-limit', desc: 'rate limit (RPS/RPM/token bucket)' },
2572
+ { key: 'validation', desc: 'μž…λ ₯ 검증 (zod/joi/yup)' },
2573
+ { key: 'error', desc: 'μ—λŸ¬ 핸듀링 + 응닡 ν˜•μ‹' },
2574
+ { key: 'logging', desc: 'λ‘œκΉ… + λͺ¨λ‹ˆν„°λ§ (structured logs)' },
2575
+ { key: 'docs', desc: 'API λ¬Έμ„œ (OpenAPI/Swagger)' }
2576
+ ]
2577
+ },
2578
+ cli: {
2579
+ aliases: ['cli', 'command-line', 'tool', 'utility', 'shell'],
2580
+ components: [
2581
+ { key: 'argParser', desc: '인자 νŒŒμ‹± (yargs/commander/clipanion)' },
2582
+ { key: 'help', desc: 'help / man / examples ν…μŠ€νŠΈ' },
2583
+ { key: 'config', desc: 'μ„€μ • 파일 + env λ³€μˆ˜' },
2584
+ { key: 'output', desc: '좜λ ₯ (TTY 색상/JSON/quiet)' },
2585
+ { key: 'error', desc: 'μ—λŸ¬ 처리 + exit code κ·œμ•½' },
2586
+ { key: 'completion', desc: 'shell completion (bash/zsh/fish)' }
2587
+ ]
2588
+ },
2589
+ data: {
2590
+ aliases: ['data', 'pipeline', 'etl', 'analytics', 'ingest'],
2591
+ components: [
2592
+ { key: 'ingest', desc: '데이터 μˆ˜μ§‘ (file/API/stream)' },
2593
+ { key: 'transform', desc: 'λ³€ν™˜ (cleaning/normalization/joining)' },
2594
+ { key: 'storage', desc: 'μ €μž₯μ†Œ (parquet/db/blob)' },
2595
+ { key: 'query', desc: '쿼리/뢄석 (SQL/aggregations)' },
2596
+ { key: 'validation', desc: '데이터 검증 (schema/contracts)' },
2597
+ { key: 'lineage', desc: '데이터 lineage 좔적' }
2598
+ ]
2599
+ }
2600
+ }
2601
+ };
2602
+ function _loadDomainCatalog(root) {
2603
+ try {
2604
+ const fp = _domainCatalogPath(root);
2605
+ if (!exists(fp)) return _DEFAULT_DOMAIN_CATALOG;
2606
+ const j = JSON.parse(read(fp));
2607
+ // user catalog merge β€” user override μš°μ„ 
2608
+ const merged = JSON.parse(JSON.stringify(_DEFAULT_DOMAIN_CATALOG));
2609
+ if (j.domains) {
2610
+ for (const k of Object.keys(j.domains)) merged.domains[k] = j.domains[k];
2611
+ }
2612
+ return merged;
2613
+ } catch { return _DEFAULT_DOMAIN_CATALOG; }
2614
+ }
2615
+ function _writeDomainCatalog(root, catalog) {
2616
+ try {
2617
+ mkdirp(path.join(root, '.harness'));
2618
+ writeUtf8(_domainCatalogPath(root), JSON.stringify({ ...catalog, updatedAt: new Date().toISOString() }, null, 2));
2619
+ return true;
2620
+ } catch { return false; }
2621
+ }
2622
+ // intent λΆ„λ₯˜: precise / broad / default
2623
+ function _classifyIntent(text) {
2624
+ if (!text || typeof text !== 'string') return { intent: 'default', signals: [] };
2625
+ const signals = [];
2626
+ // precise μ‹ ν˜Έ: "μ •ν™•νžˆ / κ·Έκ²ƒλ§Œ / κ·ΈλŒ€λ‘œ / only / just / 만"
2627
+ const preciseKws = ['μ •ν™•νžˆ', 'κ·Έκ²ƒλ§Œ', 'κ·ΈλŒ€λ‘œ', 'only', 'just only', 'λ§ν•œλŒ€λ‘œ', 'λ§ν•œ κ·ΈλŒ€λ‘œ'];
2628
+ for (const kw of preciseKws) {
2629
+ if (text.toLowerCase().includes(kw.toLowerCase())) signals.push({ kind: 'precise', match: kw });
2630
+ }
2631
+ // broad μ‹ ν˜Έ: "κΈ°λ³Έ / 포괄적 / λ“±λ“± / λ‹€μ–‘ν•œ / 전체 / 기본적인 / etc / overall"
2632
+ const broadKws = ['κΈ°λ³Έ', '포괄적', 'λ“±λ“±', 'λ‹€μ–‘ν•œ', '전체', '기본적인', 'etc', 'overall', 'ν•„μš”ν•œ', 'κ΄€λ ¨', 'comprehensive', 'including'];
2633
+ for (const kw of broadKws) {
2634
+ if (text.toLowerCase().includes(kw.toLowerCase())) signals.push({ kind: 'broad', match: kw });
2635
+ }
2636
+ // κ²°μ •
2637
+ const preciseCount = signals.filter(s => s.kind === 'precise').length;
2638
+ const broadCount = signals.filter(s => s.kind === 'broad').length;
2639
+ let intent;
2640
+ if (preciseCount > broadCount && preciseCount >= 1) intent = 'precise';
2641
+ else if (broadCount >= 1) intent = 'broad';
2642
+ else intent = 'default';
2643
+ return { intent, signals, preciseCount, broadCount };
2644
+ }
2645
+ function _detectDomain(text, root) {
2646
+ if (!text) return { domain: null, alias: null };
2647
+ const lower = text.toLowerCase();
2648
+ const catalog = _loadDomainCatalog(root);
2649
+ for (const [domain, info] of Object.entries(catalog.domains)) {
2650
+ for (const a of info.aliases || []) {
2651
+ if (lower.includes(a.toLowerCase())) {
2652
+ return { domain, alias: a, components: info.components };
2653
+ }
2654
+ }
2655
+ }
2656
+ return { domain: null, alias: null };
2657
+ }
2658
+ function _inferScopeExpansion(text, root) {
2659
+ const classify = _classifyIntent(text);
2660
+ const det = _detectDomain(text, root);
2661
+ const result = {
2662
+ text,
2663
+ intent: classify.intent,
2664
+ intentSignals: classify.signals,
2665
+ domain: det.domain,
2666
+ matchedAlias: det.alias,
2667
+ explicitMentions: [],
2668
+ expansionCandidates: [],
2669
+ mode: 'dry-run' // 항상 dry-run β€” μ‹€ν–‰ X
2670
+ };
2671
+ if (!det.domain || !det.components) {
2672
+ result.note = '도메인 미탐지 β€” λͺ…μ‹œ μš”μ²­λ§Œ μ§„ν–‰ (ν™•μž₯ 후보 μ—†μŒ)';
2673
+ return result;
2674
+ }
2675
+ // μ‚¬μš©μž ν…μŠ€νŠΈμ—μ„œ component key 직접 μ–ΈκΈ‰ μΆ”μΆœ
2676
+ const lowerText = text.toLowerCase();
2677
+ for (const c of det.components) {
2678
+ const mentionPatterns = [c.key.toLowerCase(), ...(c.desc.split(/[\s/()]/).filter(w => w.length >= 2).map(w => w.toLowerCase()))];
2679
+ const mentioned = mentionPatterns.some(p => lowerText.includes(p));
2680
+ if (mentioned) {
2681
+ result.explicitMentions.push({ key: c.key, desc: c.desc, source: 'user-explicit' });
2682
+ } else {
2683
+ result.expansionCandidates.push({ key: c.key, desc: c.desc, source: 'ai-inference' });
2684
+ }
2685
+ }
2686
+ // intent === precise λ©΄ expansion λΉ„μ–΄μžˆκ²Œ (μ˜λ„ 보호)
2687
+ if (classify.intent === 'precise') {
2688
+ result.expansionCandidates = [];
2689
+ result.note = `intent: precise β€” λͺ…μ‹œ μš”μ²­λ§Œ μ§„ν–‰, AI μΆ”λ‘  ν™•μž₯ λΉ„ν™œμ„± (μ‚¬μš©μž μ˜λ„ 보호)`;
2690
+ } else if (classify.intent === 'broad') {
2691
+ result.note = `intent: broad β€” ${result.expansionCandidates.length} ν™•μž₯ 후보 μ œμ‹œ (--expand-all λ˜λŠ” --select N 으둜 λͺ…μ‹œ 승인 ν•„μš”)`;
2692
+ } else {
2693
+ result.note = `intent: default β€” λͺ…μ‹œ ${result.explicitMentions.length}건만 μš°μ„ , ν™•μž₯ 후보 ${result.expansionCandidates.length}건 κ²€ν† μš©`;
2694
+ }
2695
+ return result;
2696
+ }
2697
+
2529
2698
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
2530
2699
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
2531
2700
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -3317,6 +3486,99 @@ function idempotencyCmd(root, sub) {
3317
3486
  process.exit(1);
3318
3487
  }
3319
3488
 
3489
+ // 1.9.213: leerness intent <classify|expand|domains> CLI β€” intent inference + scope expansion (μ‚¬μš©μž λͺ…μ‹œ)
3490
+ function intentCmd(root, sub, ...rest) {
3491
+ root = absRoot(root);
3492
+ const isTty = process.stdout && process.stdout.isTTY;
3493
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3494
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3495
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3496
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3497
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3498
+
3499
+ if (!sub || sub === 'help' || sub === '--help') {
3500
+ log(`# leerness intent (1.9.213) β€” μ‚¬μš©μž μ˜λ„ νŒŒμ•… + scope expansion 게이트`);
3501
+ log('');
3502
+ log(` classify "<request>" β†’ intent λΆ„λ₯˜ (precise/broad/default) + μ‹ ν˜Έ (--json κ°€λŠ₯)`);
3503
+ log(` expand "<request>" β†’ 도메인 탐지 + ν™•μž₯ 후보 dry-run (--json κ°€λŠ₯)`);
3504
+ log(` domains β†’ λ“±λ‘λœ 도메인 catalog 좜λ ₯ (game/web/api/cli/data)`);
3505
+ log('');
3506
+ log(dim(` 3원칙: (1) Always-Off Opt-In (2) Dry-run κΈ°λ³Έ (μ‹€ν–‰ X) (3) λͺ…μ‹œ vs μΆ”λ‘  뢄리 라벨링`));
3507
+ log(dim(` 예: leerness intent expand "λ§΅κ³Ό 캐릭터 + κΈ°λ³Έ κ²Œμž„ κΈ°λŠ₯ λ§Œλ“€μ–΄μ€˜"`));
3508
+ return;
3509
+ }
3510
+
3511
+ if (sub === 'classify') {
3512
+ const text = rest.filter(x => !x.startsWith('-')).join(' ');
3513
+ if (!text) { console.error('Usage: leerness intent classify "<request text>"'); process.exit(1); }
3514
+ const result = _classifyIntent(text);
3515
+ if (has('--json')) { log(JSON.stringify({ text, ...result }, null, 2)); return; }
3516
+ log(cyan(`# leerness intent classify (1.9.213)`));
3517
+ log(` text: ${text.slice(0, 100)}${text.length > 100 ? '…' : ''}`);
3518
+ log('');
3519
+ const icon = result.intent === 'precise' ? '🎯' : (result.intent === 'broad' ? '🌐' : 'πŸ”');
3520
+ log(` ${icon} intent: ${grn(result.intent)} (precise=${result.preciseCount} / broad=${result.broadCount})`);
3521
+ if (result.signals.length) {
3522
+ log('');
3523
+ log(` πŸ“‘ μ‹ ν˜Έ:`);
3524
+ result.signals.forEach(s => log(dim(` β€’ [${s.kind}] "${s.match}"`)));
3525
+ }
3526
+ return;
3527
+ }
3528
+
3529
+ if (sub === 'expand') {
3530
+ const text = rest.filter(x => !x.startsWith('-')).join(' ');
3531
+ if (!text) { console.error('Usage: leerness intent expand "<request text>"'); process.exit(1); }
3532
+ const result = _inferScopeExpansion(text, root);
3533
+ if (has('--json')) { log(JSON.stringify(result, null, 2)); return; }
3534
+ log(cyan(`# leerness intent expand (1.9.213) [DRY-RUN]`));
3535
+ log(` text: ${text.slice(0, 100)}${text.length > 100 ? '…' : ''}`);
3536
+ log('');
3537
+ const iIcon = result.intent === 'precise' ? '🎯' : (result.intent === 'broad' ? '🌐' : 'πŸ”');
3538
+ log(` ${iIcon} intent: ${grn(result.intent)}` + (result.domain ? ` πŸ“¦ domain: ${grn(result.domain)}` + (result.matchedAlias ? dim(` (matched: "${result.matchedAlias}")`) : '') : ` ${dim('domain 미탐지')}`));
3539
+ log('');
3540
+ if (result.explicitMentions.length) {
3541
+ log(grn(`## πŸ‘€ μ‚¬μš©μž λͺ…μ‹œ (${result.explicitMentions.length})`));
3542
+ result.explicitMentions.forEach(m => log(` β€’ ${m.key} β€” ${m.desc}`));
3543
+ log('');
3544
+ }
3545
+ if (result.expansionCandidates.length) {
3546
+ log(yel(`## πŸ€– AI μΆ”λ‘  ν™•μž₯ 후보 (${result.expansionCandidates.length}, dry-run)`));
3547
+ result.expansionCandidates.forEach((c, i) => log(` [${i + 1}] ${c.key} β€” ${c.desc}`));
3548
+ log('');
3549
+ log(dim(` β†’ μ§„ν–‰ μ‹œ λͺ…μ‹œ 승인 ν•„μš”: leerness task add "<μ„ νƒν•œ 후보 μš”μ²­>"`));
3550
+ log(dim(` β†’ μ „λΆ€ λ¬΄μ‹œν•˜λ €λ©΄ μΆ”κ°€ task add 없이 λͺ…μ‹œ μš”μ²­λ§Œ μ§„ν–‰`));
3551
+ } else if (result.domain && result.intent === 'precise') {
3552
+ log(red(` πŸ›‘ intent: precise β€” AI μΆ”λ‘  ν™•μž₯ λΉ„ν™œμ„± (μ‚¬μš©μž μ˜λ„ 보호)`));
3553
+ }
3554
+ if (result.note) {
3555
+ log('');
3556
+ log(dim(` β„Ή ${result.note}`));
3557
+ }
3558
+ return;
3559
+ }
3560
+
3561
+ if (sub === 'domains') {
3562
+ const catalog = _loadDomainCatalog(root);
3563
+ if (has('--json')) { log(JSON.stringify(catalog, null, 2)); return; }
3564
+ log(cyan(`# leerness intent domains (1.9.213)`));
3565
+ log(` total domains: ${Object.keys(catalog.domains).length}`);
3566
+ log('');
3567
+ for (const [name, info] of Object.entries(catalog.domains)) {
3568
+ log(grn(` πŸ“¦ ${name}`) + dim(` aliases: ${(info.aliases || []).join(', ')}`));
3569
+ for (const c of info.components || []) {
3570
+ log(` β€’ ${c.key.padEnd(12)} ${dim(c.desc)}`);
3571
+ }
3572
+ log('');
3573
+ }
3574
+ return;
3575
+ }
3576
+
3577
+ console.error(`Unknown subcommand: ${sub}`);
3578
+ console.error(`Run: leerness intent help`);
3579
+ process.exit(1);
3580
+ }
3581
+
3320
3582
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
3321
3583
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
3322
3584
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -16760,6 +17022,8 @@ async function main() {
16760
17022
  if (cmd === 'migrate-workspace-dir') return migrateWorkspaceDirCmd(arg('--path', process.cwd()));
16761
17023
  // 1.9.212: leerness idempotency audit β€” λ©±λ“±μ„± μœ„λ°˜ 탐지 (μ‚¬μš©μž λͺ…μ‹œ)
16762
17024
  if (cmd === 'idempotency') return idempotencyCmd(arg('--path', process.cwd()), args[1]);
17025
+ // 1.9.213: leerness intent <classify|expand|domains> β€” intent inference + scope expansion (μ‚¬μš©μž λͺ…μ‹œ)
17026
+ if (cmd === 'intent') return intentCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
16763
17027
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
16764
17028
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
16765
17029
  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.212",
3
+ "version": "1.9.214",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",