leerness 1.9.211 โ 1.9.213
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 +115 -0
- package/README.md +3 -3
- package/bin/harness.js +488 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,120 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.213 โ 2026-05-22
|
|
4
|
+
|
|
5
|
+
**๐ฏ intent inference + scope expansion ๊ฒ์ดํธ (์ฌ์ฉ์ ๋ช
์) โ ๋ง์ง๋ง pending task ์์ฑ.**
|
|
6
|
+
|
|
7
|
+
### ์ฌ์ฉ์ ๋ช
์
|
|
8
|
+
> *"์ฌ์ฉ์๊ฐ ๋งํ ๋ด์ฉ๋ง ์์ ํด์ผํ๋ ๊ฒฝ์ฐ๋ ์์ง๋ง, ๊ด๋ จ์๋๊ฒ์ด๋ ๋ณด๊ฐํ๋ฉด ์ข์ ๋ถ๋ถ์ ํ์
ํ์ฌ ์งํ / ๊ฒ์ ๊ฐ๋ฐ์์ ๋งต+์บ๋ฆญํฐ+๊ธฐ๋ณธ๊ธฐ๋ฅ ์์ฒญ ์ ์๋ ํ์
"*
|
|
9
|
+
|
|
10
|
+
### 1. 3์์น ์์ ์ค๊ณ
|
|
11
|
+
1. **Always-Off, Opt-In** โ ๊ธฐ๋ณธ ๋นํ์ฑ, ๋ช
์์ ํธ์ถ (`leerness intent expand`) ๋๋ review-request ํตํฉ ์๋ง ์๋
|
|
12
|
+
2. **Dry-run ๊ธฐ๋ณธ** โ ์ค์ task add **์ ๋ X**, ๋ชจ๋ ์ถ๋ ฅ์ ํ๋ณด ๋ณด๊ณ ๋ง
|
|
13
|
+
3. **๋ช
์ vs ์ถ๋ก ๋ถ๋ฆฌ ๋ผ๋ฒจ๋ง** โ `๐ค ์ฌ์ฉ์ ๋ช
์` vs `๐ค AI ์ถ๋ก ํ์ฅ` ํญ์ ๊ตฌ๋ถ
|
|
14
|
+
|
|
15
|
+
### 2. `_classifyIntent(text)` โ 3๋จ๊ณ ๋ถ๋ฅ
|
|
16
|
+
- **precise** ("์ ํํ", "๊ทธ๊ฒ๋ง", "๊ทธ๋๋ก", "๋งํ๋๋ก", "only") โ AI ์ถ๋ก ํ์ฅ **๋นํ์ฑ**
|
|
17
|
+
- **broad** ("๊ธฐ๋ณธ", "ํฌ๊ด์ ", "๋ฑ๋ฑ", "๋ค์ํ", "์ ์ฒด", "ํ์ํ") โ ํ์ฅ ํ๋ณด N๊ฑด dry-run ์ ์
|
|
18
|
+
- **default** (๋ช
์ ์์) โ ๋ช
์ ์ฐ์ , ํ์ฅ ํ๋ณด ๊ฒํ ์ฉ์ผ๋ก ํ์
|
|
19
|
+
|
|
20
|
+
### 3. `.harness/domain-catalog.json` โ 5 default + user ํธ์ง ๊ฐ๋ฅ
|
|
21
|
+
- **game** (10 ์ปดํฌ๋ํธ): map, character, gameLoop, collision, camera, hud, audio, save, menu, input
|
|
22
|
+
- **web** (8 ์ปดํฌ๋ํธ): routing, state, auth, api, db, ui, test, deploy
|
|
23
|
+
- **api** (7 ์ปดํฌ๋ํธ): endpoint, auth, rate-limit, validation, error, logging, docs
|
|
24
|
+
- **cli** (6 ์ปดํฌ๋ํธ): argParser, help, config, output, error, completion
|
|
25
|
+
- **data** (6 ์ปดํฌ๋ํธ): ingest, transform, storage, query, validation, lineage
|
|
26
|
+
|
|
27
|
+
### 4. `_inferScopeExpansion(text, root)` โ ํ์ฅ ํ๋ณด dry-run
|
|
28
|
+
- ๋๋ฉ์ธ ์๋ ํ์ง (alias ๋งค์นญ)
|
|
29
|
+
- ์ฌ์ฉ์ ๋ช
์ mention ์ถ์ถ
|
|
30
|
+
- ๋๋จธ์ง โ AI ์ถ๋ก ํ์ฅ ํ๋ณด
|
|
31
|
+
- **mode: 'dry-run' ๊ฐ์ ** (์คํ X)
|
|
32
|
+
|
|
33
|
+
### 5. `leerness intent <classify|expand|domains>` CLI
|
|
34
|
+
- `classify "<req>"` โ ์๋ ๋ถ๋ฅ + ์ ํธ
|
|
35
|
+
- `expand "<req>"` โ ๋๋ฉ์ธ ํ์ง + ํ์ฅ ํ๋ณด dry-run
|
|
36
|
+
- `domains` โ catalog ์ถ๋ ฅ
|
|
37
|
+
- ๋ชจ๋ `--json` ์ง์
|
|
38
|
+
|
|
39
|
+
### 6. ์ฌ์ฉ์ ๊ฒ์ ์์ ๊ฒ์ฆ
|
|
40
|
+
```
|
|
41
|
+
$ leerness intent expand "๋งต๊ณผ ์บ๋ฆญํฐ + ๊ธฐ๋ณธ ๊ฒ์ ๊ธฐ๋ฅ ํฌ๊ด์ ์ผ๋ก ๋ง๋ค์ด์ค"
|
|
42
|
+
๐ intent: broad ๐ฆ domain: game (matched: "๊ฒ์")
|
|
43
|
+
|
|
44
|
+
## ๐ค ์ฌ์ฉ์ ๋ช
์ (2)
|
|
45
|
+
โข character โ ์บ๋ฆญํฐ/์คํ๋ผ์ดํธ + ์ ๋๋ฉ์ด์
์ํ๋จธ์
|
|
46
|
+
โข gameLoop โ ๊ฒ์ ๋ฃจํ (tick/render/update)
|
|
47
|
+
|
|
48
|
+
## ๐ค AI ์ถ๋ก ํ์ฅ ํ๋ณด (8, dry-run)
|
|
49
|
+
[1] map / [2] collision / [3] camera / [4] hud / [5] audio / [6] save / [7] menu / [8] input
|
|
50
|
+
|
|
51
|
+
โ ์งํ ์ ๋ช
์ ์น์ธ ํ์: leerness task add "<์ ํํ ํ๋ณด ์์ฒญ>"
|
|
52
|
+
โ ์ ๋ถ ๋ฌด์ํ๋ ค๋ฉด ์ถ๊ฐ task add ์์ด ๋ช
์ ์์ฒญ๋ง ์งํ
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 7. precise ์๋ ๋ณดํธ ๊ฒ์ฆ
|
|
56
|
+
```
|
|
57
|
+
$ leerness intent expand "์ ํํ ๊ฒ์์ ๋งต๋ง ์์ ํด์ค ๊ทธ๊ฒ๋ง"
|
|
58
|
+
๐ก intent: precise โ AI ์ถ๋ก ํ์ฅ ๋นํ์ฑ (์ฌ์ฉ์ ์๋ ๋ณดํธ)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 8. ๋์ ํ๊ท (1.9.200~212) โ ๋ชจ๋ ์ ์ง
|
|
62
|
+
|
|
63
|
+
### 9. stress-v158 โ 19/19 PASS
|
|
64
|
+
- 1.9.213 (10) + ์ฑ๋ฅ (2) + ๋์ ํ๊ท (7)
|
|
65
|
+
- ๊ฒ์ ์์ broad โ 8 ํ์ฅ ํ๋ณด / precise โ 0 ํ์ฅ (์๋ ๋ณดํธ) / web ๋๋ฉ์ธ ํ์ง
|
|
66
|
+
- ์ฑ๋ฅ: --version cold start avg 388ms ยท MCP 54 ๋๊ตฌ 434ms
|
|
67
|
+
|
|
68
|
+
### 10. ์๋ release (75 ๋ผ์ด๋ main-push streak ยท 36 ๋ผ์ด๋ npm publish streak)
|
|
69
|
+
|
|
70
|
+
### 11. ๐ ์ฌ์ฉ์ ๋ช
์ task ๋ฐฑ๋ก๊ทธ **์์ ์์ง** (#307๊น์ง ๋ชจ๋ completed)
|
|
71
|
+
- 1.9.207~213 7 ๋ผ์ด๋ ์ฐ์ ์ฌ์ฉ์ ๋ช
์ ์์ฒญ ์ฒ๋ฆฌ:
|
|
72
|
+
- 207 user-requests audit / 208 platform constraints / 209 pre-wake audit
|
|
73
|
+
- 210 adaptive wakeup / 211 .harnessโ.leerness migration / 212 idempotency dedup
|
|
74
|
+
- 213 intent inference + scope expansion ๊ฒ์ดํธ
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 1.9.212 โ 2026-05-22
|
|
79
|
+
|
|
80
|
+
**๐ ๋ฉฑ๋ฑ์ฑ ๊ฐ์ฌ + ruleAdd/taskAdd dedup ๋ณด๊ฐ (์ฌ์ฉ์ ๋ช
์).**
|
|
81
|
+
|
|
82
|
+
### ์ฌ์ฉ์ ๋ช
์
|
|
83
|
+
> *"๋ค์ํ ๋ถ๋ถ์์ ๋ฉฑ๋ฑ์ฑ์ด ํ์ํ ๋ถ๋ถ์ด ๊ณ ๋ ค๋๊ณ ์๋์ง ํ์ธํด์ฃผ๊ณ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง ํ๋จํด์ค"*
|
|
84
|
+
|
|
85
|
+
### 1. ruleAdd dedup (line 10229+)
|
|
86
|
+
- ๊ฐ์ description + trigger + active ๋ฃฐ ์ด๋ฏธ ์กด์ฌ ์ skip
|
|
87
|
+
- `--force` ํ๋๊ทธ๋ก ์ฐํ ๊ฐ๋ฅ
|
|
88
|
+
- ์ถ๋ ฅ: `rule exists (skip): R-XXXX [trigger] description (--force ๋ก ๋ฎ์ด์ฐ๊ธฐ)`
|
|
89
|
+
|
|
90
|
+
### 2. taskAdd dedup (line 3709+)
|
|
91
|
+
- progress-tracker.md ์ markdown table ํ์ฑ (`| T-XXXX | status | request | โฆ |`)
|
|
92
|
+
- ๊ฐ์ request ํ
์คํธ + ํ์ฑ ์ํ(`requested`/`in-progress`) ์ด๋ฏธ ์กด์ฌ ์ skip
|
|
93
|
+
- `--force` ํ๋๊ทธ๋ก ์ฐํ ๊ฐ๋ฅ
|
|
94
|
+
|
|
95
|
+
### 3. `_runIdempotencyAudit(root)` โ 4์์ญ ์ ๊ฒ
|
|
96
|
+
- **rules**: ๊ฐ์ description + trigger + active ์ค๋ณต (severity: medium)
|
|
97
|
+
- **tasks**: ๊ฐ์ request ํ
์คํธ + ํ์ฑ ์ํ ์ค๋ณต (severity: medium)
|
|
98
|
+
- **user-requests**: 1.9.207 ์์ฒด dedup ๊ฒ์ฆ (severity: low)
|
|
99
|
+
- **wakeups**: 1.9.205 ๋์ผ expectedFireAt ๊ฒ์ฆ (severity: high)
|
|
100
|
+
|
|
101
|
+
### 4. `leerness idempotency audit` CLI
|
|
102
|
+
- ์๋ฐ ๋ถ๋ฅ (high/medium/low)
|
|
103
|
+
- ์์ญ๋ณ verified / violations ๋ณด๊ณ
|
|
104
|
+
- `--json` ์ต์
|
|
105
|
+
|
|
106
|
+
### 5. ๋์ ํ๊ท (1.9.200~211) โ ๋ชจ๋ ์ ์ง
|
|
107
|
+
|
|
108
|
+
### 6. stress-v157 โ 17/17 PASS
|
|
109
|
+
- 1.9.212 (8) + ์ฑ๋ฅ (2) + ๋์ ํ๊ท (7)
|
|
110
|
+
- ๊ฒฉ๋ฆฌ tmp dir์์ ruleAdd 2ํ โ 1๊ฑด๋ง ๋ฑ๋ก ๊ฒ์ฆ
|
|
111
|
+
- `--force` ์ฐํ ๊ฒ์ฆ
|
|
112
|
+
- ์๋ ์ค๋ณต rule ์ฝ์
โ audit์ด ์ ํํ 1๊ฑด violation ํ์ง
|
|
113
|
+
|
|
114
|
+
### 7. ์๋ release (74 ๋ผ์ด๋ main-push streak ยท 35 ๋ผ์ด๋ npm publish streak)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
3
118
|
## 1.9.211 โ 2026-05-22
|
|
4
119
|
|
|
5
120
|
**๐ `.harness` โ `.leerness` opt-in ๋ง์ด๊ทธ๋ ์ด์
+ AI ์ฐธ์กฐ ๊ฐ์ด๋ (์ฌ์ฉ์ ๋ช
์).**
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI ์ฝ๋ฉ ์์ด์ ํธ์ ๊ฑฐ์ง ์๋ฃยท์ค๋ณตยท๋ง๊ฐยท์ถฉ๋์ ๋ง์์ฃผ๋ ๊ฒ์ยท๊ธฐ์ตยทํ์
CLI ํ๋ค์ค.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() [-brightgreen)]() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
โ โโโ โโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโ โ
|
|
13
13
|
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ
|
|
14
14
|
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโโโโ โ
|
|
15
|
-
โ v1.9.
|
|
15
|
+
โ v1.9.213 AI Agent Reliability Harness + Sandbox โ
|
|
16
16
|
โ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ
|
|
17
|
-
โ
|
|
17
|
+
โ ๐ฏ intent inference + scope expansion 5๋๋ฉ์ธ (์ฌ์ฉ์ ๋ช
์) โ
|
|
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.
|
|
10
|
+
const VERSION = '1.9.213';
|
|
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 ์ ์ฆ์ ์คํ ๊ฐ๋ฅ
|
|
@@ -3129,6 +3298,287 @@ function migrateWorkspaceDirCmd(root) {
|
|
|
3129
3298
|
}
|
|
3130
3299
|
}
|
|
3131
3300
|
|
|
3301
|
+
// 1.9.212: leerness idempotency <audit> โ ๋ฉฑ๋ฑ์ฑ ์๋ฐ ํ์ง + ๊ถ์ฅ fix (์ฌ์ฉ์ ๋ช
์)
|
|
3302
|
+
// "๋ค์ํ ๋ถ๋ถ์์ ๋ฉฑ๋ฑ์ฑ์ด ํ์ํ ๋ถ๋ถ์ด ๊ณ ๋ ค๋๊ณ ์๋์ง ํ์ธํด์ฃผ๊ณ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง ํ๋จ"
|
|
3303
|
+
// ํ์ง ์์ญ:
|
|
3304
|
+
// 1. rules.md โ ๊ฐ์ description + trigger + active ์ค๋ณต
|
|
3305
|
+
// 2. progress-tracker.md โ ๊ฐ์ request ํ
์คํธ ์ค๋ณต (ํ์ฑ ์ํ)
|
|
3306
|
+
// 3. user-requests.json โ ๋์ผ ํ
์คํธ + open (1.9.207 ์์ฒด dedup ๊ฒ์ฆ)
|
|
3307
|
+
// 4. active-wakeups.json โ ๋์ผ expectedFireAt ์ค๋ณต (1.9.205 dedup ๊ฒ์ฆ)
|
|
3308
|
+
// 5. next-action-queue.json โ ๋์ผ title ์ค๋ณต (1.9.201 dedup ๊ฒ์ฆ)
|
|
3309
|
+
function _runIdempotencyAudit(root) {
|
|
3310
|
+
const audit = {
|
|
3311
|
+
auditedAt: new Date().toISOString(),
|
|
3312
|
+
auditVersion: VERSION,
|
|
3313
|
+
violations: [],
|
|
3314
|
+
verified: []
|
|
3315
|
+
};
|
|
3316
|
+
// 1) rules.md dup ๊ฒ์ฌ
|
|
3317
|
+
try {
|
|
3318
|
+
const rules = readRules(root);
|
|
3319
|
+
const active = rules.filter(r => r.status === 'active');
|
|
3320
|
+
const seen = new Map();
|
|
3321
|
+
for (const r of active) {
|
|
3322
|
+
const key = `${r.trigger}::${r.rule}`;
|
|
3323
|
+
if (seen.has(key)) {
|
|
3324
|
+
audit.violations.push({
|
|
3325
|
+
kind: 'rule-duplicate',
|
|
3326
|
+
location: '.harness/rules.md',
|
|
3327
|
+
detail: `์ค๋ณต ๋ฃฐ: ${r.id} == ${seen.get(key)} (${r.trigger}: ${r.rule.slice(0, 60)})`,
|
|
3328
|
+
severity: 'medium',
|
|
3329
|
+
fix: `leerness rule remove ${r.id}`
|
|
3330
|
+
});
|
|
3331
|
+
} else {
|
|
3332
|
+
seen.set(key, r.id);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
if (audit.violations.filter(v => v.kind === 'rule-duplicate').length === 0) {
|
|
3336
|
+
audit.verified.push({ kind: 'rules', detail: `${active.length} active rules, ์ค๋ณต 0๊ฑด` });
|
|
3337
|
+
}
|
|
3338
|
+
} catch (e) { audit.violations.push({ kind: 'audit-error', area: 'rules', detail: String(e.message || e), severity: 'low' }); }
|
|
3339
|
+
|
|
3340
|
+
// 2) progress-tracker.md ์ค๋ณต request ๊ฒ์ฌ
|
|
3341
|
+
// ํฌ๋งท: | T-XXXX | status | request | evidence | nextAction | date |
|
|
3342
|
+
try {
|
|
3343
|
+
const pt = path.join(root, '.harness', 'progress-tracker.md');
|
|
3344
|
+
if (exists(pt)) {
|
|
3345
|
+
const content = read(pt);
|
|
3346
|
+
const lines = content.split(/\r?\n/);
|
|
3347
|
+
const requests = new Map(); // text โ { id, status, line }
|
|
3348
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3349
|
+
if (!/^\|\s*T-\d{4,}\s*\|/.test(lines[i])) continue;
|
|
3350
|
+
const cells = lines[i].split('|').slice(1, -1).map(s => s.trim());
|
|
3351
|
+
if (cells.length < 3) continue;
|
|
3352
|
+
const id = cells[0];
|
|
3353
|
+
const status = (cells[1] || '').toLowerCase();
|
|
3354
|
+
const text = cells[2];
|
|
3355
|
+
if (text.length < 5) continue;
|
|
3356
|
+
const isActive = !/^(done|dropped|blocked|completed)$/i.test(status);
|
|
3357
|
+
if (!isActive) continue;
|
|
3358
|
+
if (requests.has(text)) {
|
|
3359
|
+
const prev = requests.get(text);
|
|
3360
|
+
audit.violations.push({
|
|
3361
|
+
kind: 'task-duplicate-request',
|
|
3362
|
+
location: '.harness/progress-tracker.md',
|
|
3363
|
+
detail: `์ค๋ณต request: "${text.slice(0, 50)}โฆ" (${prev.id} & ${id})`,
|
|
3364
|
+
severity: 'medium',
|
|
3365
|
+
fix: `์ค๋ณต task ์ค ํ๋๋ฅผ leerness task drop <id> ์ฒ๋ฆฌ`
|
|
3366
|
+
});
|
|
3367
|
+
} else {
|
|
3368
|
+
requests.set(text, { id, status, line: i + 1 });
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
if (audit.violations.filter(v => v.kind === 'task-duplicate-request').length === 0) {
|
|
3372
|
+
audit.verified.push({ kind: 'tasks', detail: `${requests.size} active tasks, request ์ค๋ณต 0๊ฑด` });
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
} catch (e) { audit.violations.push({ kind: 'audit-error', area: 'tasks', detail: String(e.message || e), severity: 'low' }); }
|
|
3376
|
+
|
|
3377
|
+
// 3) user-requests.json ์์ฒด dedup ๊ฒ์ฆ (1.9.207)
|
|
3378
|
+
try {
|
|
3379
|
+
const ur = _loadUserRequests(root);
|
|
3380
|
+
const seen = new Map();
|
|
3381
|
+
for (const r of ur.requests) {
|
|
3382
|
+
if (r.status !== 'open' && r.status !== 'in-progress') continue;
|
|
3383
|
+
const k = r.text.trim();
|
|
3384
|
+
if (seen.has(k)) {
|
|
3385
|
+
audit.violations.push({
|
|
3386
|
+
kind: 'user-request-duplicate',
|
|
3387
|
+
location: '.harness/user-requests.json',
|
|
3388
|
+
detail: `์ค๋ณต open ์์ฒญ: ${r.id} == ${seen.get(k)} ("${k.slice(0, 50)}โฆ")`,
|
|
3389
|
+
severity: 'low',
|
|
3390
|
+
fix: `leerness requests drop ${r.id}`
|
|
3391
|
+
});
|
|
3392
|
+
} else {
|
|
3393
|
+
seen.set(k, r.id);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
if (audit.violations.filter(v => v.kind === 'user-request-duplicate').length === 0) {
|
|
3397
|
+
audit.verified.push({ kind: 'user-requests', detail: `${seen.size} open requests, ์ค๋ณต 0๊ฑด (1.9.207 dedup OK)` });
|
|
3398
|
+
}
|
|
3399
|
+
} catch (e) { audit.violations.push({ kind: 'audit-error', area: 'user-requests', detail: String(e.message || e), severity: 'low' }); }
|
|
3400
|
+
|
|
3401
|
+
// 4) active-wakeups.json ๊ฒ์ฆ (1.9.205)
|
|
3402
|
+
try {
|
|
3403
|
+
const wk = _loadActiveWakeups(root);
|
|
3404
|
+
const pending = wk.wakeups.filter(w => w.status === 'pending');
|
|
3405
|
+
const seenT = new Map();
|
|
3406
|
+
for (const w of pending) {
|
|
3407
|
+
if (seenT.has(w.expectedFireAt)) {
|
|
3408
|
+
audit.violations.push({
|
|
3409
|
+
kind: 'wakeup-duplicate',
|
|
3410
|
+
location: '.harness/active-wakeups.json',
|
|
3411
|
+
detail: `๋์ผ expectedFireAt ์ค๋ณต: ${w.expectedFireAt}`,
|
|
3412
|
+
severity: 'high',
|
|
3413
|
+
fix: `leerness ์๋ _recordWakeup filter dedup ๊ฒํ ํ์`
|
|
3414
|
+
});
|
|
3415
|
+
} else {
|
|
3416
|
+
seenT.set(w.expectedFireAt, true);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
if (audit.violations.filter(v => v.kind === 'wakeup-duplicate').length === 0) {
|
|
3420
|
+
audit.verified.push({ kind: 'wakeups', detail: `${pending.length} pending wakeups, ์ค๋ณต 0๊ฑด (1.9.205 dedup OK)` });
|
|
3421
|
+
}
|
|
3422
|
+
} catch {}
|
|
3423
|
+
|
|
3424
|
+
audit.summary = {
|
|
3425
|
+
totalViolations: audit.violations.length,
|
|
3426
|
+
highSeverity: audit.violations.filter(v => v.severity === 'high').length,
|
|
3427
|
+
mediumSeverity: audit.violations.filter(v => v.severity === 'medium').length,
|
|
3428
|
+
lowSeverity: audit.violations.filter(v => v.severity === 'low').length,
|
|
3429
|
+
verifiedAreas: audit.verified.length,
|
|
3430
|
+
overall: audit.violations.length === 0 ? 'clean' : 'violations-found'
|
|
3431
|
+
};
|
|
3432
|
+
return audit;
|
|
3433
|
+
}
|
|
3434
|
+
function idempotencyCmd(root, sub) {
|
|
3435
|
+
root = absRoot(root);
|
|
3436
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
3437
|
+
const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
3438
|
+
const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
|
|
3439
|
+
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
3440
|
+
const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
|
|
3441
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
3442
|
+
|
|
3443
|
+
if (!sub || sub === 'help' || sub === '--help') {
|
|
3444
|
+
log(`# leerness idempotency (1.9.212) โ ๋ฉฑ๋ฑ์ฑ ์๋ฐ ํ์ง`);
|
|
3445
|
+
log('');
|
|
3446
|
+
log(` audit โ ์ํฌ์คํ์ด์ค ๋ฉฑ๋ฑ์ฑ ์ ๊ฒ (rules / tasks / user-requests / wakeups) (--json ๊ฐ๋ฅ)`);
|
|
3447
|
+
log('');
|
|
3448
|
+
log(dim(` dedup ์ ์ฉ ์์ญ: ruleAdd / taskAdd (1.9.212) + _recordUserRequest (1.9.207) + _recordWakeup (1.9.205)`));
|
|
3449
|
+
log(dim(` opt-out: --force ํ๋๊ทธ๋ก dedup ์ฐํ ๊ฐ๋ฅ`));
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
if (sub === 'audit') {
|
|
3454
|
+
const audit = _runIdempotencyAudit(root);
|
|
3455
|
+
if (has('--json')) { log(JSON.stringify(audit, null, 2)); return; }
|
|
3456
|
+
log(cyan(`# leerness idempotency audit (1.9.212)`));
|
|
3457
|
+
log(` audited at: ${audit.auditedAt}`);
|
|
3458
|
+
log('');
|
|
3459
|
+
const s = audit.summary;
|
|
3460
|
+
if (s.overall === 'clean') {
|
|
3461
|
+
log(grn(` โ ๋ฉฑ๋ฑ์ฑ ์๋ฐ ์์ โ verified ${s.verifiedAreas} ์์ญ`));
|
|
3462
|
+
} else {
|
|
3463
|
+
log(red(` โ ${s.totalViolations} ์๋ฐ ๋ฐ๊ฒฌ (high: ${s.highSeverity}, medium: ${s.mediumSeverity}, low: ${s.lowSeverity})`));
|
|
3464
|
+
}
|
|
3465
|
+
log('');
|
|
3466
|
+
if (audit.verified.length) {
|
|
3467
|
+
log(grn(`## โ
verified (${audit.verified.length})`));
|
|
3468
|
+
audit.verified.forEach(v => log(dim(` - [${v.kind}] ${v.detail}`)));
|
|
3469
|
+
log('');
|
|
3470
|
+
}
|
|
3471
|
+
if (audit.violations.length) {
|
|
3472
|
+
log(red(`## โ violations (${audit.violations.length})`));
|
|
3473
|
+
audit.violations.forEach(v => {
|
|
3474
|
+
const icon = v.severity === 'high' ? '๐จ' : (v.severity === 'medium' ? 'โ ' : 'โน');
|
|
3475
|
+
log(` ${icon} [${v.kind}] (${v.severity})`);
|
|
3476
|
+
log(dim(` ${v.location}`));
|
|
3477
|
+
log(` ${v.detail}`);
|
|
3478
|
+
if (v.fix) log(dim(` fix: ${v.fix}`));
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
console.error(`Unknown subcommand: ${sub}`);
|
|
3485
|
+
console.error(`Run: leerness idempotency help`);
|
|
3486
|
+
process.exit(1);
|
|
3487
|
+
}
|
|
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
|
+
|
|
3132
3582
|
// 1.9.199: wakeup miss ์๋ ํ๋ณต ๊ฐํ โ last-handoff timestamp ๊ธฐ๋ก + 25min ๊ธฐ๋ interval ์ ๋ฐ ์ธก์
|
|
3133
3583
|
// ์ฌ์ฉ์ ๋ช
์ (1.9.198 ํ์): "22:01์ ๋ผ์ด๋์ ์๋์ง์
๋์ง์์์ด"
|
|
3134
3584
|
// 1.9.196 task-log mtime ๊ธฐ๋ฐ detector ๋ task-log ๊ฐ ์์ฃผ ์ ์
๋ฐ์ดํธ๋๋ฉด false positive.
|
|
@@ -3707,6 +4157,30 @@ function taskList(root) {
|
|
|
3707
4157
|
for (const r of filtered) log(`| ${r.id} | ${r.status} | ${r.request} | ${r.evidence} | ${r.nextAction} | ${r.updated} |`);
|
|
3708
4158
|
}
|
|
3709
4159
|
function taskAdd(root, text) {
|
|
4160
|
+
// 1.9.212: ๋ฉฑ๋ฑ์ฑ โ ๊ฐ์ request ํ
์คํธ + ํ์ฑ ์ํ (requested/in-progress) ์ด๋ฏธ ์กด์ฌ ์ skip (์ฌ์ฉ์ ๋ช
์)
|
|
4161
|
+
// progress-tracker.md ํฌ๋งท: | T-XXXX | status | request | evidence | nextAction | date |
|
|
4162
|
+
if (!has('--force') && text && text.trim()) {
|
|
4163
|
+
try {
|
|
4164
|
+
const pt = path.join(absRoot(root), '.harness', 'progress-tracker.md');
|
|
4165
|
+
if (exists(pt)) {
|
|
4166
|
+
const content = read(pt);
|
|
4167
|
+
const norm = text.trim();
|
|
4168
|
+
const lines = content.split(/\r?\n/);
|
|
4169
|
+
for (const line of lines) {
|
|
4170
|
+
if (!/^\|\s*T-\d{4,}\s*\|/.test(line)) continue;
|
|
4171
|
+
const cells = line.split('|').slice(1, -1).map(s => s.trim());
|
|
4172
|
+
if (cells.length < 3) continue;
|
|
4173
|
+
const id = cells[0];
|
|
4174
|
+
const status = (cells[1] || '').toLowerCase();
|
|
4175
|
+
const request = cells[2];
|
|
4176
|
+
if (request === norm && !/^(done|dropped|blocked|completed)$/i.test(status)) {
|
|
4177
|
+
ok(`task exists (skip): ${id} request="${norm.slice(0, 60)}โฆ" (--force ๋ก ๋ฎ์ด์ฐ๊ธฐ)`);
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
} catch {}
|
|
4183
|
+
}
|
|
3710
4184
|
const id = nextId(root, 'T');
|
|
3711
4185
|
upsertProgress(root, { id, status: arg('--status','requested'), request: text, evidence: arg('--evidence','user-request'), nextAction: arg('--next','๋ค์ ์ก์
์์ฑ') });
|
|
3712
4186
|
ok(`task added: ${id}`);
|
|
@@ -10235,8 +10709,16 @@ function ruleAdd(root, description) {
|
|
|
10235
10709
|
if (!validTriggers.has(trigger)) {
|
|
10236
10710
|
warn(`unknown trigger "${trigger}" โ ์ฌ์ฉ ๊ฐ๋ฅ: ${[...validTriggers].join(', ')}. ๊ทธ๋๋ก ๋ฑ๋กํฉ๋๋ค.`);
|
|
10237
10711
|
}
|
|
10238
|
-
const id = nextRuleId(root);
|
|
10239
10712
|
const rules = readRules(root);
|
|
10713
|
+
// 1.9.212: ๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ โ ๊ฐ์ description + trigger + active ์ํ ์ด๋ฏธ ์กด์ฌ ์ skip (์ฌ์ฉ์ ๋ช
์)
|
|
10714
|
+
if (!has('--force')) {
|
|
10715
|
+
const dup = rules.find(r => r.rule === description && r.trigger === trigger && r.status === 'active');
|
|
10716
|
+
if (dup) {
|
|
10717
|
+
ok(`rule exists (skip): ${dup.id} [${dup.trigger}] ${description} (--force ๋ก ๋ฎ์ด์ฐ๊ธฐ)`);
|
|
10718
|
+
return;
|
|
10719
|
+
}
|
|
10720
|
+
}
|
|
10721
|
+
const id = nextRuleId(root);
|
|
10240
10722
|
rules.push({ id, trigger, rule: description, added: today(), status: 'active', lastVerified: '-' });
|
|
10241
10723
|
writeRules(root, rules);
|
|
10242
10724
|
ok(`rule added: ${id} [${trigger}] ${description}`);
|
|
@@ -16538,6 +17020,10 @@ async function main() {
|
|
|
16538
17020
|
if (cmd === 'workspace-dir') return workspaceDirCmd(arg('--path', process.cwd()), args[1]);
|
|
16539
17021
|
// 1.9.211: leerness migrate-workspace-dir โ .harness โ .leerness ๋ง์ด๊ทธ๋ ์ด์
(์ฌ์ฉ์ ๋ช
์)
|
|
16540
17022
|
if (cmd === 'migrate-workspace-dir') return migrateWorkspaceDirCmd(arg('--path', process.cwd()));
|
|
17023
|
+
// 1.9.212: leerness idempotency audit โ ๋ฉฑ๋ฑ์ฑ ์๋ฐ ํ์ง (์ฌ์ฉ์ ๋ช
์)
|
|
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));
|
|
16541
17027
|
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
16542
17028
|
if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
|
|
16543
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.
|
|
3
|
+
"version": "1.9.213",
|
|
4
4
|
"description": "Leerness: ๋นํ๊ดด ๋ง์ด๊ทธ๋ ์ด์
, ์๋ ๋ฒ์ ๊ฐ์งยท์
๋ฐ์ดํธ, ๊ณํ/์งํ/ํธ๋์คํ ์๋ํ, ๊ฒ์ผ๋ฆยท์ํฌ๋ฆฟยท์ธ์ฝ๋ฉ ์๋ ๊ฐ๋, Claude Code ์ฌ๋์ ํตํฉ์ ๊ฐ์ถ ํ๊ตญ์ด ์ฐ์ AI ๊ฐ๋ฐ ํ๋ค์ค.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"leerness",
|