@byh3071/vhk 0.4.0 β 0.5.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/README.md +138 -28
- package/dist/index.js +589 -71
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,58 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: vhk-readme
|
|
3
|
+
date: 2026-05-23
|
|
4
|
+
tags: [vhk, cli, readme, v0.4.0]
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# π§ VHK β Vibe Harness Kit
|
|
2
8
|
|
|
3
|
-
> AI μ½λ© μμ΄μ νΈλ₯Ό λΆλ¦¬λ μ¬λμ μν νμ¬μ΄ν΄ CLI
|
|
9
|
+
> AI μ½λ© μμ΄μ νΈλ₯Ό λΆλ¦¬λ μ¬λμ μν **νκ΅μ΄ νμ¬μ΄ν΄ CLI** (v0.4.0)
|
|
10
|
+
|
|
11
|
+
λͺ
λ Ήμ΄λ₯Ό μΈμ°μ§ μμλ λ©λλ€. `vhk`λ§ μΉλ©΄ λ©λ΄κ° λμ€κ³ , νκ΅μ΄λ‘ λ§ν΄λ μμλ£μ΅λλ€.
|
|
4
12
|
|
|
5
13
|
## μ€μΉ
|
|
6
14
|
|
|
7
15
|
```bash
|
|
8
|
-
npm
|
|
16
|
+
npm install -g @byh3071/vhk
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
```bash
|
|
12
|
-
#
|
|
13
|
-
npx vhk
|
|
20
|
+
# ν λ²λ§ μΈ λ
|
|
21
|
+
npx @byh3071/vhk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
λ‘컬 κ°λ° μ€:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
cd vhk-cli
|
|
28
|
+
pnpm install
|
|
29
|
+
pnpm build
|
|
30
|
+
pnpm link --global
|
|
31
|
+
vhk --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## λΉ λ₯Έ μμ
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
vhk
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
μΈμ μμ΄ μ€ννλ©΄ **γλ λμλ릴κΉμ?γ** λ©λ΄κ° μ΄λ¦½λλ€.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# μμ°μ΄λ‘λ κ°λ₯
|
|
44
|
+
vhk νλ‘μ νΈ λ§λ€κ³ μΆμ΄
|
|
45
|
+
vhk κΈ°ν λλ¬κ³ λ°λ‘ μμ
|
|
46
|
+
vhk μ€λ ν μΌ μ 리
|
|
47
|
+
vhk λκ° μ λΌ
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## μν¬νλ‘μ° (κΆμ₯ μμ)
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
vhk κ²μ¦ (gate) β μμ΄λμ΄ GO/λ€λ¬κΈ°/λ€λ₯Έ μμ΄λμ΄
|
|
54
|
+
vhk μμ (init) β νλ€μ€ νμΌ μμ± (CLAUDE.md, PRD, ADR ν
νλ¦Ώ λ±)
|
|
55
|
+
κ°λ° ...
|
|
56
|
+
vhk μ 리 (recap) β μΈμ
λ‘κ·Έ + ADR/νΈλ¬λΈμν
μ μ
|
|
57
|
+
vhk μ κ² (check) β RULES.md κ·μΉ λ¦°νΈ
|
|
58
|
+
vhk 보μ scan β μν¬λ¦ΏΒ·ν€ μ μΆ κ²μ¬
|
|
59
|
+
vhk λ°°ν¬ (ship) β λ°°ν¬ μ²΄ν¬λ¦¬μ€νΈ + νκ³ β docs/build-log/
|
|
14
60
|
```
|
|
15
61
|
|
|
16
|
-
|
|
62
|
+
κΈ°νμ΄ μ΄λ―Έ λλ¬λ€λ©΄:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
vhk μμ --skip-gate
|
|
66
|
+
# λλ
|
|
67
|
+
vhk κΈ°ν λλ¬κ³ λ°λ‘ μμ
|
|
68
|
+
```
|
|
17
69
|
|
|
18
|
-
|
|
70
|
+
## μ 체 컀맨λ
|
|
71
|
+
|
|
72
|
+
| μμ΄ | νκ΅μ΄ λ³μΉ | μ€λͺ
|
|
|
73
|
+
|------|-------------|------|
|
|
74
|
+
| `vhk` | β | μμ λ©λ΄ (λͺ
λ Ή μμ) |
|
|
75
|
+
| `vhk gate` | `κ²μ¦`, `μμ΄λμ΄` | μμ΄λμ΄ κ²μ¦ (ν΅ 5λ¬Έν / ν 13λ¬Έν / μ€ν΅) |
|
|
76
|
+
| `vhk init` | `μμ`, `λ§λ€κΈ°` | νλ‘μ νΈ μ΄κΈ°ν + νλ€μ€ μμ± |
|
|
77
|
+
| `vhk recap` | `μ 리`, `μ€λ` | Git λ³κ²½ β `docs/log/` μΈμ
λ‘κ·Έ |
|
|
78
|
+
| `vhk sync` | `κ·μΉ`, `λ§μΆκΈ°` | RULES.md β `.cursorrules` + CLAUDE.md |
|
|
79
|
+
| `vhk check` | `μ κ²`, `λ¦°νΈ` | RULES.md κ·μΉ μλ° κ²μ¬ |
|
|
80
|
+
| `vhk secure scan` | `보μ`, `μ€μΊ` | μ½λ λ΄ μν¬λ¦ΏΒ·ν€ ν¨ν΄ μ€μΊ |
|
|
81
|
+
| `vhk ship` | `λ°°ν¬`, `릴리μ¦` | λ°°ν¬ μ²΄ν¬λ¦¬μ€νΈ + νκ³ + λΉλ λ‘κ·Έ |
|
|
82
|
+
| `vhk doctor` | `μ§λ¨`, `νκ²½` | Node / npm / pnpm / Git νκ²½ μ κ² |
|
|
83
|
+
|
|
84
|
+
### init μ΅μ
|
|
85
|
+
|
|
86
|
+
| μ΅μ
| μ€λͺ
|
|
|
87
|
+
|------|------|
|
|
88
|
+
| `--skip-gate` | μμ΄λμ΄ κ²μ¦(gate) μλ΅ |
|
|
89
|
+
| `--from-notion <url>` | Notion PRD νμ΄μ§μμ import |
|
|
90
|
+
| `--name`, `--description`, `--type` | λΉλνν μ
λ ₯ |
|
|
91
|
+
| `-y, --yes` | μ€ν νμΈ μ€ν΅ |
|
|
92
|
+
|
|
93
|
+
### recap μ΅μ
|
|
94
|
+
|
|
95
|
+
| μ΅μ
| μ€λͺ
|
|
|
96
|
+
|------|------|
|
|
97
|
+
| `--since YYYY-MM-DD` | λΆμ μμμΌ (κΈ°λ³Έ: μ€λ) |
|
|
98
|
+
|
|
99
|
+
## v0.4.0 νμ΄λΌμ΄νΈ
|
|
100
|
+
|
|
101
|
+
| κΈ°λ₯ | μ€λͺ
|
|
|
102
|
+
|------|------|
|
|
103
|
+
| **μμ λ©λ΄** | `vhk`λ§ μ
λ ₯ν΄λ λ€μ μμ
μ ν |
|
|
104
|
+
| **νκ΅μ΄ λ³μΉ** | `vhk κ²μ¦`, `vhk μμ`, `vhk μ 리` λ± |
|
|
105
|
+
| **μμ°μ΄ λΌμ°ν
** | `vhk "νλ‘μ νΈ λ§λ€κ³ μΆμ΄"` β init μ€ν |
|
|
106
|
+
| **doctor** | Node / npm / pnpm / Git + νλ‘μ νΈ νμΌ μ κ² |
|
|
107
|
+
| **ship** | λ°°ν¬ μ 체ν¬λ¦¬μ€νΈ, νκ³ , `docs/build-log/` μμ± |
|
|
108
|
+
| **λ€μμ μ΄κ²λ§ νμΈμ** | κ° λͺ
λ Ή λμ λ³΅λΆ λͺ
λ Ή + Cursor ννΈ |
|
|
109
|
+
| **check / secure** | RULES λ¦°νΈ, μν¬λ¦Ώ μ€μΊ (λν lockΒ·node_modules μ μΈ) |
|
|
110
|
+
|
|
111
|
+
## initμ΄ λ§λλ κ² (μμ½)
|
|
112
|
+
|
|
113
|
+
- `CLAUDE.md`, `.cursorrules`
|
|
114
|
+
- `docs/PRD.md`, `docs/ARCHITECTURE.md`
|
|
115
|
+
- `docs/adr/`, `docs/log/`, `docs/troubleshooting/`
|
|
116
|
+
- `COMMANDS.md`, `BACKLOG.md` (νλ‘μ νΈ μ νμ λ°λΌ)
|
|
117
|
+
|
|
118
|
+
## μμ°μ΄ μμ
|
|
119
|
+
|
|
120
|
+
| λ§νλ©΄ | μ€ν |
|
|
19
121
|
|--------|------|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
122
|
+
| νλ‘μ νΈ λ§λ€κ³ μΆμ΄ | `vhk μμ` |
|
|
123
|
+
| κΈ°ν λλ¬κ³ λ°λ‘ μμ | `vhk μμ --skip-gate` |
|
|
124
|
+
| μ€λ ν μΌ μ 리 | `vhk μ 리` |
|
|
125
|
+
| 보μ μ€μΊ λλ € | `vhk 보μ scan` |
|
|
126
|
+
| λ°°ν¬νκ³ μΆμ΄ | `vhk λ°°ν¬` |
|
|
127
|
+
| λκ° μ λΌ | `vhk doctor` |
|
|
24
128
|
|
|
25
129
|
## νΉμ§
|
|
26
130
|
|
|
27
|
-
- π°π· **νκ΅μ΄ νΌμ€νΈ** β
|
|
28
|
-
-
|
|
29
|
-
- π **λ‘컬 νΌμ€νΈ** β
|
|
30
|
-
-
|
|
131
|
+
- π°π· **νκ΅μ΄ νΌμ€νΈ** β μ§λ¬ΈΒ·νμ Β·λ€μ λ¨κ³ μλ΄κ° νκ΅μ΄
|
|
132
|
+
- π£οΈ **μμ°μ΄ μΉν** β λͺ
λ Ήμ΄ λͺ°λΌλ λ¬Έμ₯μΌλ‘ μμ
|
|
133
|
+
- π **λ‘컬 νΌμ€νΈ** β λ‘κ·ΈΒ·ADRΒ·λΉλ λ‘κ·Έλ νλ‘μ νΈ ν΄λμ μ μ₯
|
|
134
|
+
- π **보μ κΈ°λ³Έ** β `.gitignore`Β·μν¬λ¦Ώ μ€μΊΒ·λ―Όκ° νμΌ κ²½κ³
|
|
31
135
|
|
|
32
136
|
## μꡬ μ¬ν
|
|
33
137
|
|
|
34
138
|
- Node.js >= 20
|
|
139
|
+
- Git (recapΒ·ship κΆμ₯)
|
|
140
|
+
|
|
141
|
+
## κ°λ°
|
|
142
|
+
|
|
143
|
+
```powershell
|
|
144
|
+
pnpm install
|
|
145
|
+
pnpm build
|
|
146
|
+
pnpm test --run
|
|
147
|
+
pnpm dev
|
|
148
|
+
pnpm dev κ²μ¦
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> Windows PowerShell 5.xμμλ `&&` λμ `;` μ¬μ©: `pnpm build; pnpm test --run`
|
|
35
152
|
|
|
36
153
|
## λΌμ΄μ μ€
|
|
37
154
|
|
|
38
|
-
MIT
|
|
155
|
+
MIT β [LICENSE](LICENSE)
|
|
39
156
|
|
|
40
157
|
## λ°°ν¬ (maintainers)
|
|
41
158
|
|
|
42
159
|
```bash
|
|
43
|
-
# 1. npm λ‘κ·ΈμΈ (μ΅μ΄ 1ν)
|
|
44
160
|
npm login
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
pnpm test
|
|
49
|
-
|
|
50
|
-
# 3. λ°°ν¬
|
|
51
|
-
npm publish
|
|
52
|
-
|
|
53
|
-
# 4. νμΈ
|
|
54
|
-
npm info vhk
|
|
55
|
-
npx vhk --help
|
|
161
|
+
pnpm run prepublishOnly
|
|
162
|
+
npm publish --access public
|
|
163
|
+
npm info @byh3071/vhk
|
|
56
164
|
```
|
|
57
165
|
|
|
58
|
-
`
|
|
166
|
+
`prepublishOnly`κ° publish μ μ `pnpm build && pnpm test:run`μ μ€νν©λλ€.
|
|
167
|
+
|
|
168
|
+
Repository: https://github.com/byh3071-cpu/vhk
|
package/dist/index.js
CHANGED
|
@@ -323,7 +323,7 @@ var require_ignore = __commonJS({
|
|
|
323
323
|
// path matching.
|
|
324
324
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
325
325
|
// @returns {TestResult} true if a file is ignored
|
|
326
|
-
test(
|
|
326
|
+
test(path15, checkUnignored, mode) {
|
|
327
327
|
let ignored = false;
|
|
328
328
|
let unignored = false;
|
|
329
329
|
let matchedRule;
|
|
@@ -332,7 +332,7 @@ var require_ignore = __commonJS({
|
|
|
332
332
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
333
333
|
return;
|
|
334
334
|
}
|
|
335
|
-
const matched = rule[mode].test(
|
|
335
|
+
const matched = rule[mode].test(path15);
|
|
336
336
|
if (!matched) {
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
@@ -353,17 +353,17 @@ var require_ignore = __commonJS({
|
|
|
353
353
|
var throwError = (message, Ctor) => {
|
|
354
354
|
throw new Ctor(message);
|
|
355
355
|
};
|
|
356
|
-
var checkPath = (
|
|
357
|
-
if (!isString(
|
|
356
|
+
var checkPath = (path15, originalPath, doThrow) => {
|
|
357
|
+
if (!isString(path15)) {
|
|
358
358
|
return doThrow(
|
|
359
359
|
`path must be a string, but got \`${originalPath}\``,
|
|
360
360
|
TypeError
|
|
361
361
|
);
|
|
362
362
|
}
|
|
363
|
-
if (!
|
|
363
|
+
if (!path15) {
|
|
364
364
|
return doThrow(`path must not be empty`, TypeError);
|
|
365
365
|
}
|
|
366
|
-
if (checkPath.isNotRelative(
|
|
366
|
+
if (checkPath.isNotRelative(path15)) {
|
|
367
367
|
const r = "`path.relative()`d";
|
|
368
368
|
return doThrow(
|
|
369
369
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -372,7 +372,7 @@ var require_ignore = __commonJS({
|
|
|
372
372
|
}
|
|
373
373
|
return true;
|
|
374
374
|
};
|
|
375
|
-
var isNotRelative = (
|
|
375
|
+
var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
|
|
376
376
|
checkPath.isNotRelative = isNotRelative;
|
|
377
377
|
checkPath.convert = (p) => p;
|
|
378
378
|
var Ignore = class {
|
|
@@ -402,19 +402,19 @@ var require_ignore = __commonJS({
|
|
|
402
402
|
}
|
|
403
403
|
// @returns {TestResult}
|
|
404
404
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
405
|
-
const
|
|
405
|
+
const path15 = originalPath && checkPath.convert(originalPath);
|
|
406
406
|
checkPath(
|
|
407
|
-
|
|
407
|
+
path15,
|
|
408
408
|
originalPath,
|
|
409
409
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
410
410
|
);
|
|
411
|
-
return this._t(
|
|
411
|
+
return this._t(path15, cache, checkUnignored, slices);
|
|
412
412
|
}
|
|
413
|
-
checkIgnore(
|
|
414
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
415
|
-
return this.test(
|
|
413
|
+
checkIgnore(path15) {
|
|
414
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
|
|
415
|
+
return this.test(path15);
|
|
416
416
|
}
|
|
417
|
-
const slices =
|
|
417
|
+
const slices = path15.split(SLASH).filter(Boolean);
|
|
418
418
|
slices.pop();
|
|
419
419
|
if (slices.length) {
|
|
420
420
|
const parent = this._t(
|
|
@@ -427,18 +427,18 @@ var require_ignore = __commonJS({
|
|
|
427
427
|
return parent;
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
|
-
return this._rules.test(
|
|
430
|
+
return this._rules.test(path15, false, MODE_CHECK_IGNORE);
|
|
431
431
|
}
|
|
432
|
-
_t(
|
|
433
|
-
if (
|
|
434
|
-
return cache[
|
|
432
|
+
_t(path15, cache, checkUnignored, slices) {
|
|
433
|
+
if (path15 in cache) {
|
|
434
|
+
return cache[path15];
|
|
435
435
|
}
|
|
436
436
|
if (!slices) {
|
|
437
|
-
slices =
|
|
437
|
+
slices = path15.split(SLASH).filter(Boolean);
|
|
438
438
|
}
|
|
439
439
|
slices.pop();
|
|
440
440
|
if (!slices.length) {
|
|
441
|
-
return cache[
|
|
441
|
+
return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
442
442
|
}
|
|
443
443
|
const parent = this._t(
|
|
444
444
|
slices.join(SLASH) + SLASH,
|
|
@@ -446,29 +446,29 @@ var require_ignore = __commonJS({
|
|
|
446
446
|
checkUnignored,
|
|
447
447
|
slices
|
|
448
448
|
);
|
|
449
|
-
return cache[
|
|
449
|
+
return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
450
450
|
}
|
|
451
|
-
ignores(
|
|
452
|
-
return this._test(
|
|
451
|
+
ignores(path15) {
|
|
452
|
+
return this._test(path15, this._ignoreCache, false).ignored;
|
|
453
453
|
}
|
|
454
454
|
createFilter() {
|
|
455
|
-
return (
|
|
455
|
+
return (path15) => !this.ignores(path15);
|
|
456
456
|
}
|
|
457
457
|
filter(paths) {
|
|
458
458
|
return makeArray(paths).filter(this.createFilter());
|
|
459
459
|
}
|
|
460
460
|
// @returns {TestResult}
|
|
461
|
-
test(
|
|
462
|
-
return this._test(
|
|
461
|
+
test(path15) {
|
|
462
|
+
return this._test(path15, this._testCache, true);
|
|
463
463
|
}
|
|
464
464
|
};
|
|
465
465
|
var factory = (options) => new Ignore(options);
|
|
466
|
-
var isPathValid = (
|
|
466
|
+
var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
|
|
467
467
|
var setupWindows = () => {
|
|
468
468
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
469
469
|
checkPath.convert = makePosix;
|
|
470
470
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
471
|
-
checkPath.isNotRelative = (
|
|
471
|
+
checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
|
|
472
472
|
};
|
|
473
473
|
if (
|
|
474
474
|
// Detect `process` so that it can run in browsers.
|
|
@@ -485,75 +485,110 @@ var require_ignore = __commonJS({
|
|
|
485
485
|
|
|
486
486
|
// src/index.ts
|
|
487
487
|
import { Command, Help } from "commander";
|
|
488
|
-
import
|
|
489
|
-
import
|
|
488
|
+
import chalk15 from "chalk";
|
|
489
|
+
import inquirer7 from "inquirer";
|
|
490
490
|
|
|
491
491
|
// src/lib/nlp-router.ts
|
|
492
492
|
function normalize(input) {
|
|
493
493
|
return input.trim().toLowerCase().replace(/\s+/g, " ");
|
|
494
494
|
}
|
|
495
|
+
var NLP_KEYWORDS = {
|
|
496
|
+
save: ["\uC800\uC7A5", "\uC138\uC774\uBE0C", "\uCEE4\uBC0B", "\uC62C\uB824", "\uC62C\uB9AC\uAE30", "\uD478\uC2DC", "push", "commit"],
|
|
497
|
+
undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
|
|
498
|
+
status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08", "\uD655\uC778"],
|
|
499
|
+
diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
|
|
500
|
+
};
|
|
501
|
+
function matchesKeywords(text, command) {
|
|
502
|
+
const keywords = NLP_KEYWORDS[command];
|
|
503
|
+
if (!keywords) return false;
|
|
504
|
+
return keywords.some((kw) => text.includes(kw.toLowerCase()));
|
|
505
|
+
}
|
|
495
506
|
var RULES = [
|
|
496
507
|
{
|
|
497
508
|
command: "init",
|
|
498
509
|
explanation: "\uAC80\uC99D \uC2A4\uD0B5\uD558\uACE0 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --skip-gate)",
|
|
499
510
|
confidence: "high",
|
|
500
511
|
args: ["--skip-gate"],
|
|
501
|
-
test: (
|
|
512
|
+
test: (t2) => /κΈ°ν.*(λ|μλ£)|λ
Έμ
.*(κΈ°ν|μλ£)|κ²μ¦.*(μ€ν΅|건λ)|gate.*(μ€ν΅|건λ)|λ°λ‘.*μμ/.test(t2)
|
|
502
513
|
},
|
|
503
514
|
{
|
|
504
515
|
command: "init",
|
|
505
516
|
explanation: "Notion\uC5D0\uC11C \uAC00\uC838\uC640 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --from-notion)",
|
|
506
517
|
confidence: "low",
|
|
507
518
|
args: ["--from-notion"],
|
|
508
|
-
test: (
|
|
519
|
+
test: (t2) => /λ
Έμ
|notion/.test(t2) && /(μμ|λ§λ€|import|κ°μ Έ)/.test(t2)
|
|
509
520
|
},
|
|
510
521
|
{
|
|
511
522
|
command: "init",
|
|
512
523
|
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
|
|
513
524
|
confidence: "high",
|
|
514
|
-
test: (
|
|
525
|
+
test: (t2) => /νλ‘μ νΈ.*(λ§λ€|μμ)|ν΄λ.*λ§λ€|λ§λ€κ³ \s*μΆ|νλ€μ€|μ΄κΈ°ν/.test(t2) || /^μμ$/.test(t2)
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
command: "diff",
|
|
529
|
+
explanation: "\uBCC0\uACBD\uC0AC\uD56D \uC694\uC57D (vhk diff)",
|
|
530
|
+
confidence: "high",
|
|
531
|
+
test: (t2) => (matchesKeywords(t2, "diff") || /^diff$/.test(t2) || /λ³κ²½μ¬ν|μμ \s*λ΄μ|μ°¨μ΄\s*보/.test(t2)) && !/μ μ₯|컀λ°|push|νΈμ|μν|νν©|μΈμ΄λΈ|commit/.test(t2)
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
command: "undo",
|
|
535
|
+
explanation: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (vhk \uB418\uB3CC\uB9AC\uAE30)",
|
|
536
|
+
confidence: "high",
|
|
537
|
+
test: (t2) => matchesKeywords(t2, "undo") || /undo|컀λ°\s*μ·¨/.test(t2)
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
command: "status",
|
|
541
|
+
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778 (vhk \uC0C1\uD0DC)",
|
|
542
|
+
confidence: "high",
|
|
543
|
+
test: (t2) => matchesKeywords(t2, "status") || /^status$/.test(t2) || /λΈλμΉ.*(λ|μ΄λ)|git\s*μν|λκΈ°ν\s*μν/.test(t2)
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
command: "save",
|
|
547
|
+
explanation: "Git\uC5D0 \uC800\uC7A5 (vhk \uC800\uC7A5)",
|
|
548
|
+
confidence: "high",
|
|
549
|
+
test: (t2) => (matchesKeywords(t2, "save") || /κΉνλΈ|github/.test(t2)) && !/μ 리|recap|λλ|μ·¨μ|rollback|reset|리μ
|λ‘€λ°±|μλλλ‘/.test(t2)
|
|
515
550
|
},
|
|
516
551
|
{
|
|
517
552
|
command: "recap",
|
|
518
553
|
explanation: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC (vhk \uC815\uB9AC)",
|
|
519
554
|
confidence: "high",
|
|
520
|
-
test: (
|
|
555
|
+
test: (t2) => /μ€λ.*(μ 리|κΈ°λ‘)|ν\s*μΌ|μΈμ
|νκ³ |recap|μ 리ν΄/.test(t2)
|
|
521
556
|
},
|
|
522
557
|
{
|
|
523
558
|
command: "doctor",
|
|
524
559
|
explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
|
|
525
560
|
confidence: "high",
|
|
526
|
-
test: (
|
|
561
|
+
test: (t2) => /λκ°\s*μ|μ\s*λΌ|μλΌ|νκ²½\s*(μ κ²|μ§λ¨)|μ§λ¨|doctor|μ€μΉ.*νμΈ|μ\s*μ/.test(t2)
|
|
527
562
|
},
|
|
528
563
|
{
|
|
529
564
|
command: "gate",
|
|
530
565
|
explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
|
|
531
566
|
confidence: "high",
|
|
532
|
-
test: (
|
|
567
|
+
test: (t2) => /μμ΄λμ΄|κ²μ¦|gate|go\/refine|pain\s*point/.test(t2)
|
|
533
568
|
},
|
|
534
569
|
{
|
|
535
570
|
command: "secure",
|
|
536
571
|
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548 scan)",
|
|
537
572
|
confidence: "high",
|
|
538
|
-
test: (
|
|
573
|
+
test: (t2) => /보μ|μν¬λ¦Ώ|λΉλ°|ν€\s*μ μΆ|secure|scan/.test(t2)
|
|
539
574
|
},
|
|
540
575
|
{
|
|
541
576
|
command: "check",
|
|
542
577
|
explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
|
|
543
578
|
confidence: "high",
|
|
544
|
-
test: (
|
|
579
|
+
test: (t2) => /κ·μΉ.*(μ κ²|μλ°)|λ¦°νΈ|check|μλ°/.test(t2)
|
|
545
580
|
},
|
|
546
581
|
{
|
|
547
582
|
command: "sync",
|
|
548
583
|
explanation: "\uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654 (vhk \uADDC\uCE59)",
|
|
549
584
|
confidence: "high",
|
|
550
|
-
test: (
|
|
585
|
+
test: (t2) => /κ·μΉ.*(λ§|λκΈ°)|sync|cursorrules|claude\.md.*λ§/.test(t2)
|
|
551
586
|
},
|
|
552
587
|
{
|
|
553
588
|
command: "ship",
|
|
554
589
|
explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk ship)",
|
|
555
590
|
confidence: "high",
|
|
556
|
-
test: (
|
|
591
|
+
test: (t2) => /λ°°ν¬|μΆμ|릴리μ€|ship|λΉλ\s*μ /.test(t2)
|
|
557
592
|
}
|
|
558
593
|
];
|
|
559
594
|
function routeNaturalLanguage(input) {
|
|
@@ -578,6 +613,60 @@ function extractNotionUrl(input) {
|
|
|
578
613
|
|
|
579
614
|
// src/i18n/ko.ts
|
|
580
615
|
var ko = {
|
|
616
|
+
status: {
|
|
617
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC",
|
|
618
|
+
notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
619
|
+
branch: "\uBE0C\uB79C\uCE58:",
|
|
620
|
+
changes: "\uBCC0\uACBD:",
|
|
621
|
+
recentCommits: "\uCD5C\uADFC \uCEE4\uBC0B (3):",
|
|
622
|
+
noCommits: "\uCEE4\uBC0B \uC5C6\uC74C",
|
|
623
|
+
remote: "\uC6D0\uACA9:",
|
|
624
|
+
noUpstream: "upstream \uC5C6\uC74C",
|
|
625
|
+
inSync: "\uB3D9\uAE30\uD654\uB428",
|
|
626
|
+
ahead: (n) => `\u2191${n} ahead`,
|
|
627
|
+
behind: (n) => `\u2193${n} behind`,
|
|
628
|
+
package: "package.json:",
|
|
629
|
+
noPackage: "package.json \uC5C6\uC74C",
|
|
630
|
+
detached: "(detached HEAD)",
|
|
631
|
+
unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
|
|
632
|
+
},
|
|
633
|
+
save: {
|
|
634
|
+
title: "\uC800\uC7A5\uD558\uAE30",
|
|
635
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
636
|
+
noChanges: "\uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
637
|
+
filesHeader: (n) => `\uBCC0\uACBD\uB41C \uD30C\uC77C (${n}\uAC1C):`,
|
|
638
|
+
commitMessage: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (Enter\uB85C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9):",
|
|
639
|
+
saving: "\uC800\uC7A5 \uC911...",
|
|
640
|
+
pushing: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uC5D0 \uC62C\uB9AC\uB294 \uC911...",
|
|
641
|
+
successWithPush: "\uC800\uC7A5 + \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC!",
|
|
642
|
+
successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
|
|
643
|
+
noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
644
|
+
failed: "\uC800\uC7A5 \uC2E4\uD328",
|
|
645
|
+
done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`
|
|
646
|
+
},
|
|
647
|
+
undo: {
|
|
648
|
+
title: "\uB418\uB3CC\uB9AC\uAE30",
|
|
649
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
650
|
+
noCommits: "\uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
651
|
+
recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
|
|
652
|
+
howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
|
|
653
|
+
alreadyPushed: "\uC774 \uCEE4\uBC0B\uC740 \uC774\uBBF8 \uC6D0\uACA9\uC5D0 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4. \uB418\uB3CC\uB9AC\uBA74 \uCDA9\uB3CC\uC774 \uC0DD\uAE38 \uC218 \uC788\uC5B4\uC694.",
|
|
654
|
+
confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
655
|
+
cancelled: "\uCDE8\uC18C\uB428",
|
|
656
|
+
success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
|
|
657
|
+
stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
|
|
658
|
+
failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
|
|
659
|
+
},
|
|
660
|
+
diff: {
|
|
661
|
+
title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
|
|
662
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
663
|
+
noChanges: "\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
664
|
+
stagedHeader: "\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):",
|
|
665
|
+
unstagedHeader: "\u270F\uFE0F \uC218\uC815\uB428 (unstaged):",
|
|
666
|
+
untrackedHeader: (n) => `\u2795 \uC0C8 \uD30C\uC77C (${n}\uAC1C):`,
|
|
667
|
+
summaryHeader: "\u{1F4CA} \uCD1D \uBCC0\uACBD \uC694\uC57D (\uC791\uC5C5 \uD2B8\uB9AC vs HEAD)",
|
|
668
|
+
filesLine: (n) => `\uD30C\uC77C: ${n}\uAC1C`
|
|
669
|
+
},
|
|
581
670
|
start: {
|
|
582
671
|
title: "\u{1F527} VHK \u2014 \uBB34\uC5C7\uC744 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
583
672
|
subtitle: "\uBC88\uD638\uB9CC \uACE0\uB974\uBA74 \uB429\uB2C8\uB2E4. \uBA85\uB839\uC5B4\uB97C \uC678\uC6B8 \uD544\uC694 \uC5C6\uC5B4\uC694.",
|
|
@@ -749,6 +838,23 @@ var ko = {
|
|
|
749
838
|
hintCommit: "git status \uD655\uC778"
|
|
750
839
|
}
|
|
751
840
|
};
|
|
841
|
+
function lookup(path15) {
|
|
842
|
+
const parts = path15.split(".");
|
|
843
|
+
let cur = ko;
|
|
844
|
+
for (const part of parts) {
|
|
845
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
846
|
+
cur = cur[part];
|
|
847
|
+
}
|
|
848
|
+
return cur;
|
|
849
|
+
}
|
|
850
|
+
function t(key, ...args) {
|
|
851
|
+
const value = lookup(key);
|
|
852
|
+
if (typeof value === "function") {
|
|
853
|
+
return value(...args);
|
|
854
|
+
}
|
|
855
|
+
if (typeof value === "string") return value;
|
|
856
|
+
return key;
|
|
857
|
+
}
|
|
752
858
|
|
|
753
859
|
// src/commands/gate.ts
|
|
754
860
|
import inquirer from "inquirer";
|
|
@@ -861,7 +967,7 @@ ${ko.gate.checklistStart}
|
|
|
861
967
|
name: "answer",
|
|
862
968
|
message: `[${i + 1}/${total}] ${q.stage}: ${q.question}`
|
|
863
969
|
}]);
|
|
864
|
-
const { status } = await inquirer.prompt([{
|
|
970
|
+
const { status: status2 } = await inquirer.prompt([{
|
|
865
971
|
type: "list",
|
|
866
972
|
name: "status",
|
|
867
973
|
message: ko.gate.verdictPrompt(q.failIf),
|
|
@@ -871,10 +977,10 @@ ${ko.gate.checklistStart}
|
|
|
871
977
|
{ name: ko.gate.statusFailChoice, value: "fail" }
|
|
872
978
|
]
|
|
873
979
|
}]);
|
|
874
|
-
if (
|
|
875
|
-
if (
|
|
876
|
-
results.push({ id: q.id, stage: q.stage, status, answer });
|
|
877
|
-
const icon =
|
|
980
|
+
if (status2 === "fail") failCount++;
|
|
981
|
+
if (status2 === "hold") holdCount++;
|
|
982
|
+
results.push({ id: q.id, stage: q.stage, status: status2, answer });
|
|
983
|
+
const icon = status2 === "pass" ? chalk2.green(ko.gate.statusPassLine) : status2 === "hold" ? chalk2.yellow(ko.gate.statusHoldLine) : chalk2.red(ko.gate.statusFailLine);
|
|
878
984
|
console.log(icon);
|
|
879
985
|
}
|
|
880
986
|
console.log(chalk2.bold(`
|
|
@@ -1176,7 +1282,7 @@ function extractPageId(url) {
|
|
|
1176
1282
|
function getPageTitle(page) {
|
|
1177
1283
|
for (const prop of Object.values(page.properties)) {
|
|
1178
1284
|
if (prop.type === "title") {
|
|
1179
|
-
return prop.title.map((
|
|
1285
|
+
return prop.title.map((t2) => t2.plain_text).join("");
|
|
1180
1286
|
}
|
|
1181
1287
|
}
|
|
1182
1288
|
return "Untitled";
|
|
@@ -1206,7 +1312,7 @@ function extractText(block) {
|
|
|
1206
1312
|
const type = block.type;
|
|
1207
1313
|
const data = block[type];
|
|
1208
1314
|
if (!data?.rich_text) return "";
|
|
1209
|
-
return data.rich_text.map((
|
|
1315
|
+
return data.rich_text.map((t2) => t2.plain_text).join("");
|
|
1210
1316
|
}
|
|
1211
1317
|
function parseBlocks(blocks) {
|
|
1212
1318
|
const sections = {};
|
|
@@ -1298,7 +1404,7 @@ var PROJECT_TYPES = [
|
|
|
1298
1404
|
{ name: "\u{1F916} \uB178\uC158 \uD1B5\uD569/MCP \uC11C\uBC84", value: "notion" },
|
|
1299
1405
|
{ name: "\u{1F4F1} \uBAA8\uBC14\uC77C \uC571 (Flutter)", value: "mobile" }
|
|
1300
1406
|
];
|
|
1301
|
-
var VALID_TYPES = PROJECT_TYPES.map((
|
|
1407
|
+
var VALID_TYPES = PROJECT_TYPES.map((t2) => t2.value);
|
|
1302
1408
|
var STACK_PRESETS = {
|
|
1303
1409
|
webapp: ["Next.js", "TypeScript", "Tailwind CSS", "shadcn/ui", "Supabase", "Vercel"],
|
|
1304
1410
|
extension: ["Vite", "TypeScript", "@crxjs/vite-plugin", "Chrome Extension Manifest V3"],
|
|
@@ -1621,10 +1727,10 @@ var ADR_RULES = [
|
|
|
1621
1727
|
test: (f) => /\.env\.example$|auth\/|middleware\.(ts|js)$/.test(f)
|
|
1622
1728
|
}
|
|
1623
1729
|
];
|
|
1624
|
-
function detectAdrCandidates(
|
|
1730
|
+
function detectAdrCandidates(diff2) {
|
|
1625
1731
|
const candidates = [];
|
|
1626
1732
|
for (const rule of ADR_RULES) {
|
|
1627
|
-
const matched =
|
|
1733
|
+
const matched = diff2.files.map((f) => f.file).filter(rule.test);
|
|
1628
1734
|
if (matched.length > 0) {
|
|
1629
1735
|
candidates.push({
|
|
1630
1736
|
title: rule.title,
|
|
@@ -1691,23 +1797,23 @@ ${ko.recap.title}
|
|
|
1691
1797
|
console.log(chalk5.dim(`${ko.recap.analyzing}
|
|
1692
1798
|
`));
|
|
1693
1799
|
const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1694
|
-
const
|
|
1800
|
+
const diff2 = await getSessionDiff(since);
|
|
1695
1801
|
const commits = await getRecentCommits(10, since);
|
|
1696
|
-
if (
|
|
1802
|
+
if (diff2.filesChanged === 0 && commits.length === 0) {
|
|
1697
1803
|
console.log(chalk5.yellow(ko.recap.noChanges));
|
|
1698
1804
|
return;
|
|
1699
1805
|
}
|
|
1700
1806
|
console.log(chalk5.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
|
|
1701
|
-
console.log(` \uD30C\uC77C: ${chalk5.cyan(String(
|
|
1702
|
-
console.log(` \uCD94\uAC00: ${chalk5.green("+" +
|
|
1703
|
-
if (
|
|
1807
|
+
console.log(` \uD30C\uC77C: ${chalk5.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
|
|
1808
|
+
console.log(` \uCD94\uAC00: ${chalk5.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk5.red("-" + diff2.deletions)}`);
|
|
1809
|
+
if (diff2.files.length > 0) {
|
|
1704
1810
|
console.log(chalk5.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
|
|
1705
|
-
|
|
1811
|
+
diff2.files.slice(0, 15).forEach((f) => {
|
|
1706
1812
|
const icon = f.status === "new" ? chalk5.green("\u{1F195}") : f.status === "deleted" ? chalk5.red("\u{1F5D1}\uFE0F") : chalk5.yellow("\u270F\uFE0F");
|
|
1707
1813
|
console.log(` ${icon} ${f.file}`);
|
|
1708
1814
|
});
|
|
1709
|
-
if (
|
|
1710
|
-
console.log(chalk5.dim(` ... \uC678 ${
|
|
1815
|
+
if (diff2.files.length > 15) {
|
|
1816
|
+
console.log(chalk5.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
|
|
1711
1817
|
}
|
|
1712
1818
|
}
|
|
1713
1819
|
if (commits.length > 0) {
|
|
@@ -1748,7 +1854,7 @@ ${ko.recap.title}
|
|
|
1748
1854
|
const sessionNum = existing.length + 1;
|
|
1749
1855
|
const fileName = `${today}-session-${sessionNum}.md`;
|
|
1750
1856
|
const filePath = path6.join(logDir, fileName);
|
|
1751
|
-
const fileList =
|
|
1857
|
+
const fileList = diff2.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
|
|
1752
1858
|
const commitList = commits.slice(0, 10).map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
1753
1859
|
const content = [
|
|
1754
1860
|
`# \uC138\uC158 \uB85C\uADF8 \u2014 ${today} #${sessionNum}`,
|
|
@@ -1766,7 +1872,7 @@ ${ko.recap.title}
|
|
|
1766
1872
|
answers.blockers,
|
|
1767
1873
|
"",
|
|
1768
1874
|
"## \uBCC0\uACBD \uD30C\uC77C",
|
|
1769
|
-
`\uCD1D ${
|
|
1875
|
+
`\uCD1D ${diff2.filesChanged}\uAC1C \uD30C\uC77C (+${diff2.insertions} -${diff2.deletions})`,
|
|
1770
1876
|
"",
|
|
1771
1877
|
"| \uD30C\uC77C | \uC0C1\uD0DC |",
|
|
1772
1878
|
"|------|------|",
|
|
@@ -1779,7 +1885,7 @@ ${ko.recap.title}
|
|
|
1779
1885
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1780
1886
|
].join("\n");
|
|
1781
1887
|
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1782
|
-
const adrCandidates = detectAdrCandidates(
|
|
1888
|
+
const adrCandidates = detectAdrCandidates(diff2);
|
|
1783
1889
|
if (adrCandidates.length > 0) {
|
|
1784
1890
|
console.log(chalk5.cyan.bold(`
|
|
1785
1891
|
${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
@@ -2732,6 +2838,384 @@ ${ko.ship.title}
|
|
|
2732
2838
|
});
|
|
2733
2839
|
}
|
|
2734
2840
|
|
|
2841
|
+
// src/commands/save.ts
|
|
2842
|
+
import { execFileSync, execSync as execSync2 } from "child_process";
|
|
2843
|
+
import chalk11 from "chalk";
|
|
2844
|
+
import ora from "ora";
|
|
2845
|
+
import inquirer5 from "inquirer";
|
|
2846
|
+
function gitOut(args) {
|
|
2847
|
+
return execFileSync("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2848
|
+
}
|
|
2849
|
+
function gitRun(args) {
|
|
2850
|
+
execFileSync("git", args, { stdio: "pipe" });
|
|
2851
|
+
}
|
|
2852
|
+
function formatDefaultCommitMessage(date = /* @__PURE__ */ new Date()) {
|
|
2853
|
+
const y = date.getFullYear();
|
|
2854
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
2855
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
2856
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
2857
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
2858
|
+
return `\u2728 vhk save: ${y}-${m}-${d} ${h}:${min}`;
|
|
2859
|
+
}
|
|
2860
|
+
function statusIcon(code) {
|
|
2861
|
+
if (code.includes("M")) return "\u270F\uFE0F";
|
|
2862
|
+
if (code.includes("A") || code.includes("?")) return "\u2795";
|
|
2863
|
+
if (code.includes("D")) return "\u{1F5D1}\uFE0F";
|
|
2864
|
+
return "\u{1F4C4}";
|
|
2865
|
+
}
|
|
2866
|
+
async function save() {
|
|
2867
|
+
console.log(chalk11.bold(`
|
|
2868
|
+
\u{1F4BE} ${t("save.title")}`));
|
|
2869
|
+
console.log(chalk11.gray("\u2500".repeat(40)));
|
|
2870
|
+
try {
|
|
2871
|
+
execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
2872
|
+
} catch {
|
|
2873
|
+
console.log(chalk11.red(`\u274C ${t("save.notGitRepo")}`));
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const status2 = gitOut(["status", "--porcelain"]).trim();
|
|
2877
|
+
if (!status2) {
|
|
2878
|
+
console.log(chalk11.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const files = status2.split("\n").filter(Boolean);
|
|
2882
|
+
console.log(chalk11.cyan(`
|
|
2883
|
+
\u{1F4CB} ${t("save.filesHeader", files.length)}`));
|
|
2884
|
+
files.forEach((line) => {
|
|
2885
|
+
const code = line.substring(0, 2);
|
|
2886
|
+
const name = line.substring(3);
|
|
2887
|
+
console.log(` ${statusIcon(code)} ${name}`);
|
|
2888
|
+
});
|
|
2889
|
+
const { message } = await inquirer5.prompt([{
|
|
2890
|
+
type: "input",
|
|
2891
|
+
name: "message",
|
|
2892
|
+
message: t("save.commitMessage"),
|
|
2893
|
+
default: formatDefaultCommitMessage()
|
|
2894
|
+
}]);
|
|
2895
|
+
const spinner = ora(t("save.saving")).start();
|
|
2896
|
+
try {
|
|
2897
|
+
gitRun(["add", "."]);
|
|
2898
|
+
gitRun(["commit", "-m", message]);
|
|
2899
|
+
spinner.text = t("save.pushing");
|
|
2900
|
+
try {
|
|
2901
|
+
gitRun(["push"]);
|
|
2902
|
+
spinner.succeed(t("save.successWithPush"));
|
|
2903
|
+
} catch {
|
|
2904
|
+
spinner.succeed(t("save.successLocal"));
|
|
2905
|
+
console.log(chalk11.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
|
|
2906
|
+
}
|
|
2907
|
+
console.log(chalk11.green(`
|
|
2908
|
+
\u2705 ${t("save.done", files.length)}`));
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
spinner.fail(t("save.failed"));
|
|
2911
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2912
|
+
console.log(chalk11.red(msg));
|
|
2913
|
+
process.exitCode = 1;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// src/commands/undo.ts
|
|
2918
|
+
import { execFileSync as execFileSync2, execSync as execSync3 } from "child_process";
|
|
2919
|
+
import chalk12 from "chalk";
|
|
2920
|
+
import inquirer6 from "inquirer";
|
|
2921
|
+
function gitOut2(args) {
|
|
2922
|
+
return execFileSync2("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2923
|
+
}
|
|
2924
|
+
function gitRun2(args) {
|
|
2925
|
+
execFileSync2("git", args, { stdio: "pipe" });
|
|
2926
|
+
}
|
|
2927
|
+
function parseRecentCommits(logOutput) {
|
|
2928
|
+
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2929
|
+
}
|
|
2930
|
+
function countUnpushedCommits() {
|
|
2931
|
+
try {
|
|
2932
|
+
const out = gitOut2(["rev-list", "--count", "@{u}..HEAD"]).trim();
|
|
2933
|
+
return parseInt(out, 10) || 0;
|
|
2934
|
+
} catch {
|
|
2935
|
+
return -1;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function willUndoPushedCommits(undoCount, unpushedCount) {
|
|
2939
|
+
if (unpushedCount < 0) return false;
|
|
2940
|
+
return undoCount > unpushedCount;
|
|
2941
|
+
}
|
|
2942
|
+
async function undo() {
|
|
2943
|
+
console.log(chalk12.bold(`
|
|
2944
|
+
\u23EA ${t("undo.title")}`));
|
|
2945
|
+
console.log(chalk12.gray("\u2500".repeat(40)));
|
|
2946
|
+
try {
|
|
2947
|
+
execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
2948
|
+
} catch {
|
|
2949
|
+
console.log(chalk12.red(`\u274C ${t("undo.notGitRepo")}`));
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
let logOutput;
|
|
2953
|
+
try {
|
|
2954
|
+
logOutput = gitOut2(["log", "--oneline", "-5"]).trim();
|
|
2955
|
+
} catch {
|
|
2956
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const commits = parseRecentCommits(logOutput);
|
|
2960
|
+
if (commits.length === 0) {
|
|
2961
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
console.log(chalk12.cyan(`
|
|
2965
|
+
${t("undo.recentHeader")}`));
|
|
2966
|
+
commits.forEach((c, i) => {
|
|
2967
|
+
console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
|
|
2968
|
+
});
|
|
2969
|
+
const maxUndo = commits.length;
|
|
2970
|
+
const { count } = await inquirer6.prompt([{
|
|
2971
|
+
type: "number",
|
|
2972
|
+
name: "count",
|
|
2973
|
+
message: t("undo.howMany"),
|
|
2974
|
+
default: 1,
|
|
2975
|
+
min: 1,
|
|
2976
|
+
max: maxUndo
|
|
2977
|
+
}]);
|
|
2978
|
+
const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
|
|
2979
|
+
const unpushed = countUnpushedCommits();
|
|
2980
|
+
if (willUndoPushedCommits(undoCount, unpushed)) {
|
|
2981
|
+
console.log(chalk12.red(`
|
|
2982
|
+
\u26A0\uFE0F ${t("undo.alreadyPushed")}`));
|
|
2983
|
+
}
|
|
2984
|
+
const { confirm } = await inquirer6.prompt([{
|
|
2985
|
+
type: "confirm",
|
|
2986
|
+
name: "confirm",
|
|
2987
|
+
message: t("undo.confirmMessage", undoCount),
|
|
2988
|
+
default: false
|
|
2989
|
+
}]);
|
|
2990
|
+
if (!confirm) {
|
|
2991
|
+
console.log(chalk12.gray(t("undo.cancelled")));
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
try {
|
|
2995
|
+
gitRun2(["reset", "--soft", `HEAD~${undoCount}`]);
|
|
2996
|
+
console.log(chalk12.green(`
|
|
2997
|
+
\u2705 ${t("undo.success", undoCount)}`));
|
|
2998
|
+
console.log(chalk12.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
console.log(chalk12.red(`\u274C ${t("undo.failed")}`));
|
|
3001
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3002
|
+
console.log(chalk12.red(msg));
|
|
3003
|
+
process.exitCode = 1;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/commands/diff.ts
|
|
3008
|
+
import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
|
|
3009
|
+
import chalk13 from "chalk";
|
|
3010
|
+
function gitOut3(args) {
|
|
3011
|
+
try {
|
|
3012
|
+
return execFileSync3("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3013
|
+
} catch {
|
|
3014
|
+
return "";
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
function parseDiffStat(stat) {
|
|
3018
|
+
const files = [];
|
|
3019
|
+
const lines = stat.split("\n");
|
|
3020
|
+
for (const line of lines) {
|
|
3021
|
+
const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/);
|
|
3022
|
+
if (!match) continue;
|
|
3023
|
+
const name = match[1].trim();
|
|
3024
|
+
if (name.includes("changed") || name.includes("file")) continue;
|
|
3025
|
+
const plusMatch = line.match(/(\++)/);
|
|
3026
|
+
const minusMatch = line.match(/(\-+)/);
|
|
3027
|
+
files.push({
|
|
3028
|
+
name,
|
|
3029
|
+
additions: plusMatch ? plusMatch[1].length : 0,
|
|
3030
|
+
deletions: minusMatch ? minusMatch[1].length : 0
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
return files;
|
|
3034
|
+
}
|
|
3035
|
+
function summarizeNumstat(numstat) {
|
|
3036
|
+
let totalAdd = 0;
|
|
3037
|
+
let totalDel = 0;
|
|
3038
|
+
let fileCount = 0;
|
|
3039
|
+
for (const line of numstat.split("\n").filter(Boolean)) {
|
|
3040
|
+
const [add, del] = line.split(" ");
|
|
3041
|
+
if (add === void 0 || del === void 0) continue;
|
|
3042
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
3043
|
+
totalDel += parseInt(del, 10) || 0;
|
|
3044
|
+
fileCount++;
|
|
3045
|
+
}
|
|
3046
|
+
return { fileCount, totalAdd, totalDel };
|
|
3047
|
+
}
|
|
3048
|
+
function printFile(f) {
|
|
3049
|
+
const adds = f.additions > 0 ? chalk13.green(`+${f.additions}`) : "";
|
|
3050
|
+
const dels = f.deletions > 0 ? chalk13.red(`-${f.deletions}`) : "";
|
|
3051
|
+
const change = [adds, dels].filter(Boolean).join(" ");
|
|
3052
|
+
console.log(` ${f.name} ${change}`);
|
|
3053
|
+
}
|
|
3054
|
+
async function diff() {
|
|
3055
|
+
console.log(chalk13.bold(`
|
|
3056
|
+
\u{1F50D} ${t("diff.title")}`));
|
|
3057
|
+
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3058
|
+
try {
|
|
3059
|
+
execSync4("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3060
|
+
} catch {
|
|
3061
|
+
console.log(chalk13.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
const unstaged = gitOut3(["diff", "--stat"]);
|
|
3065
|
+
const staged = gitOut3(["diff", "--cached", "--stat"]);
|
|
3066
|
+
const untracked = gitOut3(["ls-files", "--others", "--exclude-standard"]);
|
|
3067
|
+
if (!unstaged && !staged && !untracked) {
|
|
3068
|
+
console.log(chalk13.green(`
|
|
3069
|
+
\u2705 ${t("diff.noChanges")}`));
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
if (staged) {
|
|
3073
|
+
console.log(chalk13.cyan(`
|
|
3074
|
+
${t("diff.stagedHeader")}`));
|
|
3075
|
+
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
3076
|
+
}
|
|
3077
|
+
if (unstaged) {
|
|
3078
|
+
console.log(chalk13.cyan(`
|
|
3079
|
+
${t("diff.unstagedHeader")}`));
|
|
3080
|
+
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
3081
|
+
}
|
|
3082
|
+
if (untracked) {
|
|
3083
|
+
const files = untracked.split("\n").filter(Boolean);
|
|
3084
|
+
console.log(chalk13.cyan(`
|
|
3085
|
+
${t("diff.untrackedHeader", files.length)}`));
|
|
3086
|
+
files.forEach((f) => console.log(` ${chalk13.green("+")} ${f}`));
|
|
3087
|
+
}
|
|
3088
|
+
const numstat = gitOut3(["diff", "--numstat", "HEAD"]);
|
|
3089
|
+
if (numstat) {
|
|
3090
|
+
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
3091
|
+
console.log(chalk13.cyan(`
|
|
3092
|
+
${t("diff.summaryHeader")}`));
|
|
3093
|
+
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
3094
|
+
console.log(` \uCD94\uAC00: ${chalk13.green(`+${totalAdd}`)}\uC904`);
|
|
3095
|
+
console.log(` \uC0AD\uC81C: ${chalk13.red(`-${totalDel}`)}\uC904`);
|
|
3096
|
+
}
|
|
3097
|
+
console.log("");
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/commands/status.ts
|
|
3101
|
+
import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
|
|
3102
|
+
import fs13 from "fs";
|
|
3103
|
+
import path14 from "path";
|
|
3104
|
+
import chalk14 from "chalk";
|
|
3105
|
+
function gitOut4(args) {
|
|
3106
|
+
return execFileSync4("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3107
|
+
}
|
|
3108
|
+
function countFileChanges(porcelain) {
|
|
3109
|
+
const lines = porcelain.split("\n").filter(Boolean);
|
|
3110
|
+
let staged = 0;
|
|
3111
|
+
let unstaged = 0;
|
|
3112
|
+
let untracked = 0;
|
|
3113
|
+
for (const line of lines) {
|
|
3114
|
+
const x = line[0];
|
|
3115
|
+
const y = line[1];
|
|
3116
|
+
if (x === "?" && y === "?") {
|
|
3117
|
+
untracked++;
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
if (x !== " ") staged++;
|
|
3121
|
+
if (y !== " ") unstaged++;
|
|
3122
|
+
}
|
|
3123
|
+
return { staged, unstaged, untracked };
|
|
3124
|
+
}
|
|
3125
|
+
function parseSyncCounts(revListOutput) {
|
|
3126
|
+
const parts = revListOutput.trim().split(/\s+/);
|
|
3127
|
+
return {
|
|
3128
|
+
ahead: parseInt(parts[0] ?? "0", 10) || 0,
|
|
3129
|
+
behind: parseInt(parts[1] ?? "0", 10) || 0,
|
|
3130
|
+
hasUpstream: true
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function formatSyncLabel(sync2) {
|
|
3134
|
+
if (!sync2.hasUpstream) return t("status.noUpstream");
|
|
3135
|
+
if (sync2.ahead === 0 && sync2.behind === 0) return t("status.inSync");
|
|
3136
|
+
const parts = [];
|
|
3137
|
+
if (sync2.ahead > 0) parts.push(t("status.ahead", sync2.ahead));
|
|
3138
|
+
if (sync2.behind > 0) parts.push(t("status.behind", sync2.behind));
|
|
3139
|
+
return parts.join(" \xB7 ");
|
|
3140
|
+
}
|
|
3141
|
+
function parseRecentCommitLines(logOutput) {
|
|
3142
|
+
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3143
|
+
}
|
|
3144
|
+
function readProjectPackage(cwd = process.cwd()) {
|
|
3145
|
+
const pkgPath = path14.join(cwd, "package.json");
|
|
3146
|
+
if (!fs13.existsSync(pkgPath)) return null;
|
|
3147
|
+
try {
|
|
3148
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3149
|
+
if (!pkg.name && !pkg.version) return null;
|
|
3150
|
+
return {
|
|
3151
|
+
name: pkg.name ?? "(no name)",
|
|
3152
|
+
version: pkg.version ?? "(no version)"
|
|
3153
|
+
};
|
|
3154
|
+
} catch {
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function getSyncCounts() {
|
|
3159
|
+
try {
|
|
3160
|
+
const out = gitOut4(["rev-list", "--left-right", "--count", "HEAD...@{u}"]);
|
|
3161
|
+
return parseSyncCounts(out);
|
|
3162
|
+
} catch {
|
|
3163
|
+
return { ahead: 0, behind: 0, hasUpstream: false };
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
async function status() {
|
|
3167
|
+
console.log(chalk14.bold(`
|
|
3168
|
+
\u{1F4CA} ${t("status.title")}`));
|
|
3169
|
+
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3170
|
+
try {
|
|
3171
|
+
execSync5("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3172
|
+
} catch {
|
|
3173
|
+
console.log(chalk14.red(`\u274C ${t("status.notGitRepo")}`));
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
let branch;
|
|
3177
|
+
try {
|
|
3178
|
+
branch = gitOut4(["branch", "--show-current"]).trim() || t("status.detached");
|
|
3179
|
+
} catch {
|
|
3180
|
+
branch = t("status.unknownBranch");
|
|
3181
|
+
}
|
|
3182
|
+
const porcelain = gitOut4(["status", "--porcelain"]).trim();
|
|
3183
|
+
const counts = countFileChanges(porcelain);
|
|
3184
|
+
const sync2 = getSyncCounts();
|
|
3185
|
+
let commits = [];
|
|
3186
|
+
try {
|
|
3187
|
+
commits = parseRecentCommitLines(gitOut4(["log", "--oneline", "-3"]).trim());
|
|
3188
|
+
} catch {
|
|
3189
|
+
commits = [];
|
|
3190
|
+
}
|
|
3191
|
+
const pkg = readProjectPackage();
|
|
3192
|
+
console.log(chalk14.cyan(`
|
|
3193
|
+
\u{1F33F} ${t("status.branch")}`) + chalk14.white(` ${branch}`));
|
|
3194
|
+
console.log(
|
|
3195
|
+
chalk14.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk14.white(
|
|
3196
|
+
` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
|
|
3197
|
+
)
|
|
3198
|
+
);
|
|
3199
|
+
console.log(chalk14.cyan(`
|
|
3200
|
+
\u{1F4CB} ${t("status.recentCommits")}`));
|
|
3201
|
+
if (commits.length === 0) {
|
|
3202
|
+
console.log(chalk14.dim(` ${t("status.noCommits")}`));
|
|
3203
|
+
} else {
|
|
3204
|
+
commits.forEach((c) => console.log(` ${chalk14.dim("\u2022")} ${c}`));
|
|
3205
|
+
}
|
|
3206
|
+
console.log(
|
|
3207
|
+
chalk14.cyan(`
|
|
3208
|
+
\u{1F504} ${t("status.remote")}`) + chalk14.white(` ${formatSyncLabel(sync2)}`)
|
|
3209
|
+
);
|
|
3210
|
+
console.log(chalk14.gray("\n" + "\u2500".repeat(40)));
|
|
3211
|
+
if (pkg) {
|
|
3212
|
+
console.log(chalk14.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk14.white(` ${pkg.name} v${pkg.version}`));
|
|
3213
|
+
} else {
|
|
3214
|
+
console.log(chalk14.dim(`\u{1F4E6} ${t("status.noPackage")}`));
|
|
3215
|
+
}
|
|
3216
|
+
console.log("");
|
|
3217
|
+
}
|
|
3218
|
+
|
|
2735
3219
|
// src/index.ts
|
|
2736
3220
|
var program = new Command();
|
|
2737
3221
|
var defaultHelp = new Help();
|
|
@@ -2743,9 +3227,13 @@ var KO_ALIASES = {
|
|
|
2743
3227
|
check: "\uC810\uAC80",
|
|
2744
3228
|
secure: "\uBCF4\uC548",
|
|
2745
3229
|
ship: "\uBC30\uD3EC",
|
|
2746
|
-
doctor: "\uD658\uACBD"
|
|
3230
|
+
doctor: "\uD658\uACBD",
|
|
3231
|
+
save: "\uC800\uC7A5",
|
|
3232
|
+
undo: "\uB418\uB3CC\uB9AC\uAE30",
|
|
3233
|
+
status: "\uC0C1\uD0DC",
|
|
3234
|
+
diff: "\uBCC0\uACBD"
|
|
2747
3235
|
};
|
|
2748
|
-
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.
|
|
3236
|
+
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.5.0");
|
|
2749
3237
|
program.configureHelp({
|
|
2750
3238
|
formatHelp(cmd, helper) {
|
|
2751
3239
|
if (cmd.parent) {
|
|
@@ -2753,7 +3241,7 @@ program.configureHelp({
|
|
|
2753
3241
|
}
|
|
2754
3242
|
const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
|
|
2755
3243
|
const terms = subs.map((c) => `${c.name()} (${KO_ALIASES[c.name()]})`);
|
|
2756
|
-
const termWidth = Math.max(...terms.map((
|
|
3244
|
+
const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
|
|
2757
3245
|
const lines = [
|
|
2758
3246
|
helper.commandDescription(cmd),
|
|
2759
3247
|
"",
|
|
@@ -2775,22 +3263,32 @@ var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uB
|
|
|
2775
3263
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
2776
3264
|
program.command("ship").alias("\uBC30\uD3EC").alias("\uB9B4\uB9AC\uC988").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
2777
3265
|
program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
|
|
3266
|
+
program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
|
|
3267
|
+
await save();
|
|
3268
|
+
});
|
|
3269
|
+
program.command("undo").alias("\uB418\uB3CC\uB9AC\uAE30").description("\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30").action(async () => {
|
|
3270
|
+
await undo();
|
|
3271
|
+
});
|
|
3272
|
+
program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC").action(async () => {
|
|
3273
|
+
await status();
|
|
3274
|
+
});
|
|
3275
|
+
program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
|
|
2778
3276
|
program.on("command:*", async (operands) => {
|
|
2779
3277
|
const input = operands.join(" ");
|
|
2780
3278
|
const route = routeNaturalLanguage(input);
|
|
2781
3279
|
if (route) {
|
|
2782
3280
|
console.log("");
|
|
2783
|
-
console.log(
|
|
2784
|
-
console.log(
|
|
3281
|
+
console.log(chalk15.cyan(` \u{1F4AC} "${input}"`));
|
|
3282
|
+
console.log(chalk15.cyan(` \u2192 ${route.explanation}`));
|
|
2785
3283
|
if (route.confidence === "low") {
|
|
2786
|
-
const { confirm } = await
|
|
3284
|
+
const { confirm } = await inquirer7.prompt([{
|
|
2787
3285
|
type: "confirm",
|
|
2788
3286
|
name: "confirm",
|
|
2789
3287
|
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
2790
3288
|
default: true
|
|
2791
3289
|
}]);
|
|
2792
3290
|
if (!confirm) {
|
|
2793
|
-
console.log(
|
|
3291
|
+
console.log(chalk15.dim(` ${ko.nlp.menuHint}`));
|
|
2794
3292
|
return;
|
|
2795
3293
|
}
|
|
2796
3294
|
}
|
|
@@ -2815,15 +3313,23 @@ program.on("command:*", async (operands) => {
|
|
|
2815
3313
|
return ship();
|
|
2816
3314
|
case "doctor":
|
|
2817
3315
|
return doctor();
|
|
3316
|
+
case "save":
|
|
3317
|
+
return save();
|
|
3318
|
+
case "undo":
|
|
3319
|
+
return undo();
|
|
3320
|
+
case "status":
|
|
3321
|
+
return status();
|
|
3322
|
+
case "diff":
|
|
3323
|
+
return diff();
|
|
2818
3324
|
}
|
|
2819
3325
|
}
|
|
2820
|
-
console.log(
|
|
3326
|
+
console.log(chalk15.yellow(`
|
|
2821
3327
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
2822
3328
|
`));
|
|
2823
3329
|
});
|
|
2824
3330
|
program.action(async () => {
|
|
2825
3331
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
2826
|
-
const { choice } = await
|
|
3332
|
+
const { choice } = await inquirer7.prompt([{
|
|
2827
3333
|
type: "list",
|
|
2828
3334
|
name: "choice",
|
|
2829
3335
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
@@ -2835,7 +3341,11 @@ program.action(async () => {
|
|
|
2835
3341
|
{ name: "\u{1F512} \uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB9AC\uAE30", value: "secure" },
|
|
2836
3342
|
{ name: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654", value: "sync" },
|
|
2837
3343
|
{ name: "\u{1F680} \uBC30\uD3EC\uD558\uAE30", value: "ship" },
|
|
2838
|
-
{ name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" }
|
|
3344
|
+
{ name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" },
|
|
3345
|
+
{ name: "\u{1F4BE} Git\uC5D0 \uC800\uC7A5\uD558\uAE30", value: "save" },
|
|
3346
|
+
{ name: "\u23EA \uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30", value: "undo" },
|
|
3347
|
+
{ name: "\u{1F50D} \uBCC0\uACBD\uC0AC\uD56D \uBCF4\uAE30", value: "diff" },
|
|
3348
|
+
{ name: "\u{1F4CA} \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uBCF4\uAE30", value: "status" }
|
|
2839
3349
|
]
|
|
2840
3350
|
}]);
|
|
2841
3351
|
switch (choice) {
|
|
@@ -2855,6 +3365,14 @@ program.action(async () => {
|
|
|
2855
3365
|
return doctor();
|
|
2856
3366
|
case "ship":
|
|
2857
3367
|
return ship();
|
|
3368
|
+
case "save":
|
|
3369
|
+
return save();
|
|
3370
|
+
case "undo":
|
|
3371
|
+
return undo();
|
|
3372
|
+
case "status":
|
|
3373
|
+
return status();
|
|
3374
|
+
case "diff":
|
|
3375
|
+
return diff();
|
|
2858
3376
|
}
|
|
2859
3377
|
});
|
|
2860
3378
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byh3071/vhk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Vibe Harness Kit β λ°μ΄λΈμ½λ© νμ¬μ΄ν΄ CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vhk": "./dist/index.js"
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"commander": "^14.0.3",
|
|
42
42
|
"handlebars": "^4.7.9",
|
|
43
43
|
"inquirer": "^9.3.8",
|
|
44
|
+
"ora": "^9.4.0",
|
|
44
45
|
"simple-git": "^3.36.0"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|