vibe-commander 0.1.0 → 0.2.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/AGENTS.md +94 -0
- package/dist/adapters/cli/commands/context.js +10 -2
- package/dist/adapters/cli/commands/io-helpers.d.ts +19 -0
- package/dist/adapters/cli/commands/io-helpers.js +65 -0
- package/dist/adapters/cli/commands/schema.d.ts +28 -0
- package/dist/adapters/cli/commands/schema.js +106 -0
- package/dist/adapters/cli/commands/set-unit.d.ts +2 -0
- package/dist/adapters/cli/commands/set-unit.js +32 -22
- package/dist/adapters/cli/commands/update-commit.js +5 -0
- package/dist/adapters/cli/formatters-unit.d.ts +4 -0
- package/dist/adapters/cli/formatters-unit.js +93 -0
- package/dist/adapters/cli/index.js +4 -1
- package/dist/adapters/cli/output.js +18 -0
- package/dist/adapters/cli/router.d.ts +8 -1
- package/dist/adapters/cli/router.js +48 -5
- package/dist/config/schema.d.ts +10 -1
- package/dist/config/schema.js +17 -6
- package/dist/core/renderers/index.d.ts +2 -0
- package/dist/core/renderers/index.js +2 -0
- package/dist/core/renderers/schema-renderer.d.ts +92 -0
- package/dist/core/renderers/schema-renderer.js +162 -0
- package/dist/core/validators/input-validator.d.ts +50 -0
- package/dist/core/validators/input-validator.js +174 -0
- package/dist/types/tool-result.d.ts +3 -1
- package/dist/types/tool-result.js +10 -2
- package/package.json +12 -4
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 입력 검증 순수 함수 — Layer 1 (Core)
|
|
3
|
+
*
|
|
4
|
+
* 에이전트 환각(hallucination) 방어를 위한 입력 검증 유틸리티.
|
|
5
|
+
* 제어 문자, URL 인코딩, 경로 순회, 쿼리 파라미터 등 위험한 입력을 거부하고,
|
|
6
|
+
* 유사 유닛 ID를 레벤슈타인 거리 기반으로 제안한다.
|
|
7
|
+
*
|
|
8
|
+
* I/O 없는 순수 함수만 포함 (Core Layer 1 규칙 준수).
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
// ── 공개 API ──
|
|
13
|
+
/**
|
|
14
|
+
* 유닛 ID의 안전성과 형식을 검증한다
|
|
15
|
+
*
|
|
16
|
+
* 검증 순서:
|
|
17
|
+
* 1. 빈 값 체크
|
|
18
|
+
* 2. 제어 문자 (ASCII < 0x20) 거부
|
|
19
|
+
* 3. URL 인코딩 (% 포함) 거부
|
|
20
|
+
* 4. 경로 순회 (../, ..\) 거부
|
|
21
|
+
* 5. 쿼리 파라미터 (?, #) 거부
|
|
22
|
+
* 6. knownIds 제공 시 매칭 실패하면 유사 ID 제안
|
|
23
|
+
*
|
|
24
|
+
* @param id - 검증할 유닛 ID
|
|
25
|
+
* @param knownIds - 알려진 유닛 ID 목록 (fuzzy match에 사용, 선택)
|
|
26
|
+
* @returns 검증 결과
|
|
27
|
+
*/
|
|
28
|
+
export function validateUnitId(id, knownIds) {
|
|
29
|
+
if (!id || id.trim().length === 0) {
|
|
30
|
+
return { valid: false, error: '유닛 ID가 비어 있습니다' };
|
|
31
|
+
}
|
|
32
|
+
const dangerCheck = checkDangerousInput(id);
|
|
33
|
+
if (!dangerCheck.valid)
|
|
34
|
+
return dangerCheck;
|
|
35
|
+
if (knownIds && knownIds.length > 0 && !knownIds.includes(id)) {
|
|
36
|
+
const suggestions = findSimilarIds(id, knownIds);
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
error: `유닛 ID를 찾을 수 없습니다: ${id}`,
|
|
40
|
+
...(suggestions.length > 0 && { suggestions }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { valid: true };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Git SHA의 형식을 검증한다
|
|
47
|
+
*
|
|
48
|
+
* 유효한 SHA 형식: 7~40자리 16진수 문자열
|
|
49
|
+
*
|
|
50
|
+
* @param sha - 검증할 SHA 문자열
|
|
51
|
+
* @returns 검증 결과
|
|
52
|
+
*/
|
|
53
|
+
export function validateSha(sha) {
|
|
54
|
+
if (!sha || sha.trim().length === 0) {
|
|
55
|
+
return { valid: false, error: 'SHA가 비어 있습니다' };
|
|
56
|
+
}
|
|
57
|
+
const dangerCheck = checkDangerousInput(sha);
|
|
58
|
+
if (!dangerCheck.valid)
|
|
59
|
+
return dangerCheck;
|
|
60
|
+
if (!/^[0-9a-fA-F]+$/.test(sha)) {
|
|
61
|
+
return {
|
|
62
|
+
valid: false,
|
|
63
|
+
error: `유효하지 않은 SHA 형식입니다: ${sha}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (sha.length < 7 || sha.length > 40) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: `SHA 길이가 올바르지 않습니다 (7~40자 필요, 현재 ${String(sha.length)}자): ${sha}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { valid: true };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 경로 문자열의 안전성을 검증한다
|
|
76
|
+
*
|
|
77
|
+
* @param pathStr - 검증할 경로 문자열
|
|
78
|
+
* @returns 검증 결과
|
|
79
|
+
*/
|
|
80
|
+
export function validatePath(pathStr) {
|
|
81
|
+
if (!pathStr || pathStr.trim().length === 0) {
|
|
82
|
+
return { valid: false, error: '경로가 비어 있습니다' };
|
|
83
|
+
}
|
|
84
|
+
return checkDangerousInput(pathStr);
|
|
85
|
+
}
|
|
86
|
+
// ── 내부 함수 ──
|
|
87
|
+
/**
|
|
88
|
+
* 위험한 입력 패턴을 검사한다
|
|
89
|
+
*
|
|
90
|
+
* 에이전트가 생성할 수 있는 위험한 패턴:
|
|
91
|
+
* - 제어 문자 (ASCII < 0x20): 터미널 이스케이프 공격
|
|
92
|
+
* - URL 인코딩 (%XX): 필터 우회
|
|
93
|
+
* - 경로 순회 (../, ..\\): 디렉토리 탈출
|
|
94
|
+
* - 쿼리/프래그먼트 (?, #): URL 혼입
|
|
95
|
+
* - 널 바이트 (\x00): 문자열 절단 공격
|
|
96
|
+
*/
|
|
97
|
+
function checkDangerousInput(input) {
|
|
98
|
+
// 널 바이트
|
|
99
|
+
if (input.includes('\x00')) {
|
|
100
|
+
return { valid: false, error: '입력에 널 바이트가 포함되어 있습니다' };
|
|
101
|
+
}
|
|
102
|
+
// 제어 문자 (ASCII 0x01-0x1f, 널 바이트는 위에서 별도 체크)
|
|
103
|
+
// eslint-disable-next-line no-control-regex
|
|
104
|
+
if (/[\x01-\x1f]/.test(input)) {
|
|
105
|
+
return { valid: false, error: '입력에 제어 문자가 포함되어 있습니다' };
|
|
106
|
+
}
|
|
107
|
+
// URL 인코딩 (%XX 패턴)
|
|
108
|
+
if (/%[0-9a-fA-F]{2}/.test(input)) {
|
|
109
|
+
return { valid: false, error: '입력에 URL 인코딩 문자가 포함되어 있습니다 (예: %2e)' };
|
|
110
|
+
}
|
|
111
|
+
// 경로 순회
|
|
112
|
+
if (input.includes('../') || input.includes('..\\')) {
|
|
113
|
+
return { valid: false, error: '입력에 경로 순회 패턴이 포함되어 있습니다 (../)' };
|
|
114
|
+
}
|
|
115
|
+
// 쿼리 파라미터 / 프래그먼트
|
|
116
|
+
if (input.includes('?') || input.includes('#')) {
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
error: '입력에 쿼리 파라미터 또는 프래그먼트가 포함되어 있습니다 (?, #)',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { valid: true };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 레벤슈타인 거리를 계산한다
|
|
126
|
+
*
|
|
127
|
+
* 동적 프로그래밍 기반 O(n*m) 구현.
|
|
128
|
+
* 외부 의존성 없이 직접 구현 (최소 의존성 원칙).
|
|
129
|
+
*/
|
|
130
|
+
function levenshteinDistance(a, b) {
|
|
131
|
+
const m = a.length;
|
|
132
|
+
const n = b.length;
|
|
133
|
+
if (m === 0)
|
|
134
|
+
return n;
|
|
135
|
+
if (n === 0)
|
|
136
|
+
return m;
|
|
137
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
138
|
+
let curr = new Array(n + 1).fill(0);
|
|
139
|
+
for (let i = 1; i <= m; i++) {
|
|
140
|
+
curr[0] = i;
|
|
141
|
+
for (let j = 1; j <= n; j++) {
|
|
142
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
143
|
+
const del = (prev[j] ?? 0) + 1;
|
|
144
|
+
const ins = (curr[j - 1] ?? 0) + 1;
|
|
145
|
+
const sub = (prev[j - 1] ?? 0) + cost;
|
|
146
|
+
curr[j] = Math.min(del, ins, sub);
|
|
147
|
+
}
|
|
148
|
+
[prev, curr] = [curr, prev];
|
|
149
|
+
}
|
|
150
|
+
return prev[n] ?? m;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 입력 ID와 유사한 알려진 ID를 찾아 반환한다
|
|
154
|
+
*
|
|
155
|
+
* 레벤슈타인 거리 기반으로 가장 유사한 ID를 최대 3개까지 반환.
|
|
156
|
+
* 거리가 maxDistance 이하인 후보만 반환.
|
|
157
|
+
*
|
|
158
|
+
* @param id - 검색할 ID
|
|
159
|
+
* @param knownIds - 알려진 ID 목록
|
|
160
|
+
* @param maxDistance - 최대 허용 거리 (기본: 5)
|
|
161
|
+
* @param maxResults - 최대 반환 수 (기본: 3)
|
|
162
|
+
*/
|
|
163
|
+
function findSimilarIds(id, knownIds, maxDistance = 5, maxResults = 3) {
|
|
164
|
+
const candidates = [];
|
|
165
|
+
for (const knownId of knownIds) {
|
|
166
|
+
const distance = levenshteinDistance(id.toLowerCase(), knownId.toLowerCase());
|
|
167
|
+
if (distance <= maxDistance && distance > 0) {
|
|
168
|
+
candidates.push({ id: knownId, distance });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
172
|
+
return candidates.slice(0, maxResults).map((c) => c.id);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=input-validator.js.map
|
|
@@ -19,6 +19,8 @@ export interface ToolError {
|
|
|
19
19
|
message: string;
|
|
20
20
|
/** 추가 디버깅 정보 (선택) */
|
|
21
21
|
details?: string;
|
|
22
|
+
/** 에이전트/사용자에게 제안할 유사 값 목록 (선택) */
|
|
23
|
+
suggestions?: string[];
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* 도구 실행 결과 (Discriminated Union)
|
|
@@ -71,5 +73,5 @@ export declare function ok<T>(data: T): ToolResult<T>;
|
|
|
71
73
|
* return fail('PLAN_NOT_FOUND', '계획서를 찾을 수 없음', `경로: ${planPath}`);
|
|
72
74
|
* ```
|
|
73
75
|
*/
|
|
74
|
-
export declare function fail(code: string, message: string, details?: string): ToolResult<never>;
|
|
76
|
+
export declare function fail(code: string, message: string, details?: string, suggestions?: string[]): ToolResult<never>;
|
|
75
77
|
//# sourceMappingURL=tool-result.d.ts.map
|
|
@@ -34,7 +34,15 @@ export function ok(data) {
|
|
|
34
34
|
* return fail('PLAN_NOT_FOUND', '계획서를 찾을 수 없음', `경로: ${planPath}`);
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
export function fail(code, message, details) {
|
|
38
|
-
return {
|
|
37
|
+
export function fail(code, message, details, suggestions) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: {
|
|
41
|
+
code,
|
|
42
|
+
message,
|
|
43
|
+
...(details !== undefined && { details }),
|
|
44
|
+
...(suggestions !== undefined && suggestions.length > 0 && { suggestions }),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
39
47
|
}
|
|
40
48
|
//# sourceMappingURL=tool-result.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-commander",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Unit-based vibe coding workflow automation CLI — automate context collection from unit ID to command file in 10 seconds",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,14 +26,15 @@
|
|
|
26
26
|
"claude"
|
|
27
27
|
],
|
|
28
28
|
"bin": {
|
|
29
|
-
"vc": "
|
|
30
|
-
"vibe-commander": "
|
|
29
|
+
"vc": "dist/adapters/cli/index.js",
|
|
30
|
+
"vibe-commander": "dist/adapters/cli/index.js"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist/**/*.js",
|
|
34
34
|
"dist/**/*.d.ts",
|
|
35
35
|
"!dist/**/*.d.ts.map",
|
|
36
36
|
"skills/",
|
|
37
|
+
"AGENTS.md",
|
|
37
38
|
"LICENSE",
|
|
38
39
|
"README.md"
|
|
39
40
|
],
|
|
@@ -48,7 +49,14 @@
|
|
|
48
49
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
49
50
|
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
50
51
|
"typecheck": "tsc --noEmit",
|
|
51
|
-
"check": "npm run typecheck && npm run lint && npm run format:check && npm run test"
|
|
52
|
+
"check": "npm run typecheck && npm run lint && npm run format:check && npm run test",
|
|
53
|
+
"preversion": "git diff --exit-code && git diff --cached --exit-code && npm run check",
|
|
54
|
+
"prepublishOnly": "npm run build",
|
|
55
|
+
"postversion": "git push && git push --tags",
|
|
56
|
+
"release:patch": "npm version patch -m \"release: v%s\" && npm publish --access public",
|
|
57
|
+
"release:minor": "npm version minor -m \"release: v%s\" && npm publish --access public",
|
|
58
|
+
"release:major": "npm version major -m \"release: v%s\" && npm publish --access public",
|
|
59
|
+
"release:dry": "npm run check && npm run build && npm pack --dry-run"
|
|
52
60
|
},
|
|
53
61
|
"dependencies": {
|
|
54
62
|
"zod": "^4.3.6"
|