vibe-commander 0.1.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/LICENSE +21 -0
- package/dist/adapters/cli/commands/context-resolver.d.ts +29 -0
- package/dist/adapters/cli/commands/context-resolver.js +93 -0
- package/dist/adapters/cli/commands/context.d.ts +26 -0
- package/dist/adapters/cli/commands/context.js +39 -0
- package/dist/adapters/cli/commands/git-helpers.d.ts +43 -0
- package/dist/adapters/cli/commands/git-helpers.js +99 -0
- package/dist/adapters/cli/commands/init-helpers.d.ts +30 -0
- package/dist/adapters/cli/commands/init-helpers.js +112 -0
- package/dist/adapters/cli/commands/init-interactive.d.ts +25 -0
- package/dist/adapters/cli/commands/init-interactive.js +143 -0
- package/dist/adapters/cli/commands/init.d.ts +25 -0
- package/dist/adapters/cli/commands/init.js +73 -0
- package/dist/adapters/cli/commands/io-helpers.d.ts +45 -0
- package/dist/adapters/cli/commands/io-helpers.js +144 -0
- package/dist/adapters/cli/commands/list-units.d.ts +39 -0
- package/dist/adapters/cli/commands/list-units.js +92 -0
- package/dist/adapters/cli/commands/prompt-helpers.d.ts +36 -0
- package/dist/adapters/cli/commands/prompt-helpers.js +113 -0
- package/dist/adapters/cli/commands/set-unit.d.ts +44 -0
- package/dist/adapters/cli/commands/set-unit.js +90 -0
- package/dist/adapters/cli/commands/skill-install.d.ts +31 -0
- package/dist/adapters/cli/commands/skill-install.js +85 -0
- package/dist/adapters/cli/commands/state-helpers.d.ts +28 -0
- package/dist/adapters/cli/commands/state-helpers.js +55 -0
- package/dist/adapters/cli/commands/update-commit.d.ts +42 -0
- package/dist/adapters/cli/commands/update-commit.js +180 -0
- package/dist/adapters/cli/commands/validate.d.ts +40 -0
- package/dist/adapters/cli/commands/validate.js +219 -0
- package/dist/adapters/cli/formatters-list.d.ts +36 -0
- package/dist/adapters/cli/formatters-list.js +106 -0
- package/dist/adapters/cli/formatters-unit.d.ts +44 -0
- package/dist/adapters/cli/formatters-unit.js +287 -0
- package/dist/adapters/cli/formatters.d.ts +2 -0
- package/dist/adapters/cli/formatters.js +3 -0
- package/dist/adapters/cli/index.d.ts +17 -0
- package/dist/adapters/cli/index.js +60 -0
- package/dist/adapters/cli/output.d.ts +58 -0
- package/dist/adapters/cli/output.js +159 -0
- package/dist/adapters/cli/router.d.ts +99 -0
- package/dist/adapters/cli/router.js +419 -0
- package/dist/adapters/cli/scanner.d.ts +19 -0
- package/dist/adapters/cli/scanner.js +227 -0
- package/dist/adapters/pipe/stdin-reader.d.ts +23 -0
- package/dist/adapters/pipe/stdin-reader.js +56 -0
- package/dist/config/loader.d.ts +27 -0
- package/dist/config/loader.js +84 -0
- package/dist/config/resolver.d.ts +71 -0
- package/dist/config/resolver.js +124 -0
- package/dist/config/schema.d.ts +205 -0
- package/dist/config/schema.js +186 -0
- package/dist/core/parsers/backlog-parser.d.ts +25 -0
- package/dist/core/parsers/backlog-parser.js +255 -0
- package/dist/core/parsers/dep-line-parser.d.ts +36 -0
- package/dist/core/parsers/dep-line-parser.js +176 -0
- package/dist/core/parsers/dependency-extractor.d.ts +64 -0
- package/dist/core/parsers/dependency-extractor.js +193 -0
- package/dist/core/parsers/index.d.ts +20 -0
- package/dist/core/parsers/index.js +20 -0
- package/dist/core/parsers/md-utils.d.ts +81 -0
- package/dist/core/parsers/md-utils.js +144 -0
- package/dist/core/parsers/metadata-parser.d.ts +49 -0
- package/dist/core/parsers/metadata-parser.js +133 -0
- package/dist/core/parsers/pairing-question-extractor.d.ts +42 -0
- package/dist/core/parsers/pairing-question-extractor.js +146 -0
- package/dist/core/parsers/plan-parser-helpers.d.ts +61 -0
- package/dist/core/parsers/plan-parser-helpers.js +90 -0
- package/dist/core/parsers/plan-parser.d.ts +55 -0
- package/dist/core/parsers/plan-parser.js +69 -0
- package/dist/core/parsers/title-extractor.d.ts +36 -0
- package/dist/core/parsers/title-extractor.js +101 -0
- package/dist/core/renderers/index.d.ts +15 -0
- package/dist/core/renderers/index.js +18 -0
- package/dist/core/renderers/interpolate.d.ts +92 -0
- package/dist/core/renderers/interpolate.js +117 -0
- package/dist/core/renderers/marker-utils.d.ts +60 -0
- package/dist/core/renderers/marker-utils.js +85 -0
- package/dist/core/renderers/section-renderer.d.ts +31 -0
- package/dist/core/renderers/section-renderer.js +171 -0
- package/dist/core/renderers/section-updater.d.ts +72 -0
- package/dist/core/renderers/section-updater.js +175 -0
- package/dist/core/renderers/template-engine.d.ts +69 -0
- package/dist/core/renderers/template-engine.js +92 -0
- package/dist/core/renderers/updater-helpers.d.ts +45 -0
- package/dist/core/renderers/updater-helpers.js +83 -0
- package/dist/core/resolvers/commit-filter.d.ts +84 -0
- package/dist/core/resolvers/commit-filter.js +95 -0
- package/dist/core/resolvers/config-generator.d.ts +32 -0
- package/dist/core/resolvers/config-generator.js +112 -0
- package/dist/core/resolvers/config-merger.d.ts +26 -0
- package/dist/core/resolvers/config-merger.js +89 -0
- package/dist/core/resolvers/config-validator.d.ts +42 -0
- package/dist/core/resolvers/config-validator.js +61 -0
- package/dist/core/resolvers/dep-commit-resolver.d.ts +63 -0
- package/dist/core/resolvers/dep-commit-resolver.js +158 -0
- package/dist/core/resolvers/dep-doc-resolver.d.ts +70 -0
- package/dist/core/resolvers/dep-doc-resolver.js +84 -0
- package/dist/core/resolvers/index.d.ts +15 -0
- package/dist/core/resolvers/index.js +12 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/config.js +10 -0
- package/dist/types/context.d.ts +55 -0
- package/dist/types/context.js +10 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.js +10 -0
- package/dist/types/init.d.ts +56 -0
- package/dist/types/init.js +10 -0
- package/dist/types/tool-result.d.ts +75 -0
- package/dist/types/tool-result.js +40 -0
- package/dist/types/unit.d.ts +118 -0
- package/dist/types/unit.js +10 -0
- package/package.json +71 -0
- package/skills/claude/SKILL.md +375 -0
- package/skills/common/cli-reference.md +251 -0
- package/skills/cursor/SKILL.md +353 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validate 커맨드 핸들러 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* 설정 파일의 스키마 유효성, 참조 경로 존재 여부, ID 패턴 정규식 유효성,
|
|
5
|
+
* 패턴 간 충돌 여부를 4단계로 종합 검증한다.
|
|
6
|
+
*
|
|
7
|
+
* 검증 파이프라인:
|
|
8
|
+
* 1. 스키마 유효성 — loadConfig()의 Zod 검증 결과
|
|
9
|
+
* 2. 경로 존재 — commands, roadmap, docRoots 실제 존재 확인
|
|
10
|
+
* 3. 정규식 유효성 — 각 unitType의 idPattern이 유효한 RegExp인지
|
|
11
|
+
* 4. 패턴 충돌 — docRoots 내 실제 파일명으로 다중 매칭 시뮬레이션
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { ok, fail } from '../../../types/index.js';
|
|
18
|
+
import { loadConfig } from '../../../config/loader.js';
|
|
19
|
+
import { validateRegexes, findPatternConflicts } from '../../../core/resolvers/config-validator.js';
|
|
20
|
+
// ── 공개 API ──
|
|
21
|
+
/**
|
|
22
|
+
* validate 커맨드를 실행한다
|
|
23
|
+
*
|
|
24
|
+
* 4단계 검증 파이프라인을 순차 실행하고 종합 리포트를 반환한다.
|
|
25
|
+
* 스키마 검증 실패 시 후속 검증을 건너뛰고 즉시 에러 리포트를 반환.
|
|
26
|
+
*
|
|
27
|
+
* @param args - 파싱된 CLI 인자 (command === 'validate' 보장)
|
|
28
|
+
* @param projectRoot - 프로젝트 루트 절대 경로
|
|
29
|
+
* @returns ValidateResult 또는 에러
|
|
30
|
+
*/
|
|
31
|
+
export function handleValidate(args, projectRoot) {
|
|
32
|
+
if (args.command !== 'validate') {
|
|
33
|
+
return fail('INTERNAL_ERROR', 'handleValidate에 잘못된 커맨드가 전달되었습니다');
|
|
34
|
+
}
|
|
35
|
+
const items = [];
|
|
36
|
+
// ── 1단계: 스키마 유효성 검증 ──
|
|
37
|
+
const configResult = loadConfig(projectRoot);
|
|
38
|
+
if (!configResult.success) {
|
|
39
|
+
items.push({
|
|
40
|
+
category: 'schema',
|
|
41
|
+
status: 'error',
|
|
42
|
+
message: '설정 파일 스키마 검증 실패',
|
|
43
|
+
details: configResult.error.details ?? configResult.error.message,
|
|
44
|
+
});
|
|
45
|
+
return ok(compileReport(items));
|
|
46
|
+
}
|
|
47
|
+
items.push({
|
|
48
|
+
category: 'schema',
|
|
49
|
+
status: 'pass',
|
|
50
|
+
message: '설정 파일 스키마 검증 통과',
|
|
51
|
+
});
|
|
52
|
+
const config = configResult.data;
|
|
53
|
+
// ── 2단계: 경로 존재 검증 ──
|
|
54
|
+
validatePaths(config, projectRoot, items);
|
|
55
|
+
// ── 3단계: 정규식 유효성 + 4단계: 패턴 충돌 검증 ──
|
|
56
|
+
validateIdPatterns(config, projectRoot, items);
|
|
57
|
+
return ok(compileReport(items));
|
|
58
|
+
}
|
|
59
|
+
// ── 내부 검증 함수 ──
|
|
60
|
+
/**
|
|
61
|
+
* 설정에 명시된 경로들의 실제 존재 여부를 검증한다
|
|
62
|
+
*/
|
|
63
|
+
function validatePaths(config, projectRoot, items) {
|
|
64
|
+
const commandsPath = join(projectRoot, config.paths.commands);
|
|
65
|
+
if (existsSync(commandsPath)) {
|
|
66
|
+
items.push({
|
|
67
|
+
category: 'paths',
|
|
68
|
+
status: 'pass',
|
|
69
|
+
message: `commands 파일 존재: ${config.paths.commands}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
items.push({
|
|
74
|
+
category: 'paths',
|
|
75
|
+
status: 'error',
|
|
76
|
+
message: `commands 파일 미존재: ${config.paths.commands}`,
|
|
77
|
+
details: `경로: ${commandsPath}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (config.paths.roadmap !== null) {
|
|
81
|
+
const roadmapPath = join(projectRoot, config.paths.roadmap);
|
|
82
|
+
if (existsSync(roadmapPath)) {
|
|
83
|
+
items.push({
|
|
84
|
+
category: 'paths',
|
|
85
|
+
status: 'pass',
|
|
86
|
+
message: `roadmap 파일 존재: ${config.paths.roadmap}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
items.push({
|
|
91
|
+
category: 'paths',
|
|
92
|
+
status: 'warn',
|
|
93
|
+
message: `roadmap 파일 미존재: ${config.paths.roadmap}`,
|
|
94
|
+
details: `경로: ${roadmapPath}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const [role, dir] of Object.entries(config.paths.docRoots)) {
|
|
99
|
+
const dirPath = join(projectRoot, dir);
|
|
100
|
+
if (existsSync(dirPath)) {
|
|
101
|
+
items.push({
|
|
102
|
+
category: 'paths',
|
|
103
|
+
status: 'pass',
|
|
104
|
+
message: `docRoots.${role} 디렉토리 존재: ${dir}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
items.push({
|
|
109
|
+
category: 'paths',
|
|
110
|
+
status: 'warn',
|
|
111
|
+
message: `docRoots.${role} 디렉토리 미존재: ${dir}`,
|
|
112
|
+
details: `경로: ${dirPath}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 각 unitType의 idPattern 정규식 유효성과 패턴 간 충돌을 검증한다
|
|
119
|
+
*/
|
|
120
|
+
function validateIdPatterns(config, projectRoot, items) {
|
|
121
|
+
// 3단계: 정규식 유효성 검증 (Core 레이어 호출)
|
|
122
|
+
const regexResult = validateRegexes(config.unitTypes);
|
|
123
|
+
if (!regexResult.success) {
|
|
124
|
+
items.push({
|
|
125
|
+
category: 'regex',
|
|
126
|
+
status: 'error',
|
|
127
|
+
message: regexResult.error.message,
|
|
128
|
+
details: regexResult.error.details,
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
items.push({
|
|
133
|
+
category: 'regex',
|
|
134
|
+
status: 'pass',
|
|
135
|
+
message: '모든 유닛 유형 정규식 유효',
|
|
136
|
+
});
|
|
137
|
+
const validRegexes = regexResult.data;
|
|
138
|
+
// 4단계: 패턴 충돌 검증 (Core 레이어 호출)
|
|
139
|
+
if (validRegexes.length < 2) {
|
|
140
|
+
items.push({
|
|
141
|
+
category: 'patterns',
|
|
142
|
+
status: 'pass',
|
|
143
|
+
message: '패턴 충돌 검사: 유효한 패턴이 2개 미만이므로 충돌 불가',
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const testIds = collectTestIds(config, projectRoot);
|
|
148
|
+
if (testIds.length === 0) {
|
|
149
|
+
items.push({
|
|
150
|
+
category: 'patterns',
|
|
151
|
+
status: 'pass',
|
|
152
|
+
message: '패턴 충돌 검사: 테스트할 파일 없음 (docRoots에 .md 파일 미발견)',
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const conflicts = findPatternConflicts(testIds, validRegexes);
|
|
157
|
+
if (conflicts.length > 0) {
|
|
158
|
+
for (const conflict of conflicts) {
|
|
159
|
+
items.push({
|
|
160
|
+
category: 'patterns',
|
|
161
|
+
status: 'warn',
|
|
162
|
+
message: `패턴 충돌: "${conflict.id}" → ${conflict.matchingKeys.join(', ')} 매칭`,
|
|
163
|
+
details: `unitTypes 배열 순서에 의해 "${conflict.matchingKeys[0] ?? '?'}" 우선 적용`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
items.push({
|
|
169
|
+
category: 'patterns',
|
|
170
|
+
status: 'pass',
|
|
171
|
+
message: `패턴 충돌 검사 통과: ${String(testIds.length)}개 ID 검증, 충돌 없음`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* docRoots 내 실제 .md 파일명에서 테스트용 ID를 수집한다
|
|
177
|
+
*
|
|
178
|
+
* 각 unitType의 planDir에 해당하는 디렉토리에서 .md 파일명을 추출하여
|
|
179
|
+
* 확장자를 제거한 문자열을 테스트 ID로 사용한다.
|
|
180
|
+
*/
|
|
181
|
+
function collectTestIds(config, projectRoot) {
|
|
182
|
+
const ids = new Set();
|
|
183
|
+
const scannedDirs = new Set();
|
|
184
|
+
for (const ut of config.unitTypes) {
|
|
185
|
+
const dir = config.paths.docRoots[ut.planDir];
|
|
186
|
+
if (!dir || scannedDirs.has(dir))
|
|
187
|
+
continue;
|
|
188
|
+
scannedDirs.add(dir);
|
|
189
|
+
const dirPath = join(projectRoot, dir);
|
|
190
|
+
if (!existsSync(dirPath))
|
|
191
|
+
continue;
|
|
192
|
+
try {
|
|
193
|
+
const files = readdirSync(dirPath);
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
if (file.endsWith('.md')) {
|
|
196
|
+
ids.add(file.replace(/\.md$/, ''));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// 디렉토리 읽기 실패는 무시 (paths 검증에서 이미 처리)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return [...ids];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 검증 항목 배열에서 에러/경고 수를 집계하여 최종 리포트를 생성한다
|
|
208
|
+
*/
|
|
209
|
+
function compileReport(items) {
|
|
210
|
+
const errors = items.filter((i) => i.status === 'error').length;
|
|
211
|
+
const warnings = items.filter((i) => i.status === 'warn').length;
|
|
212
|
+
return {
|
|
213
|
+
valid: errors === 0,
|
|
214
|
+
errors,
|
|
215
|
+
warnings,
|
|
216
|
+
items,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 목록/업데이트 관련 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* list-units, update-commit 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolResult, BacklogEntry } from '../../types/index.js';
|
|
9
|
+
/** update-commit 실행 결과 */
|
|
10
|
+
export interface UpdateCommitResult {
|
|
11
|
+
sha: string;
|
|
12
|
+
mode: 'replace' | 'append';
|
|
13
|
+
section: string;
|
|
14
|
+
sectionHeader: string;
|
|
15
|
+
updated: boolean;
|
|
16
|
+
commitMode: 'auto-head' | 'auto-recent' | 'manual';
|
|
17
|
+
count?: number;
|
|
18
|
+
requested?: number;
|
|
19
|
+
}
|
|
20
|
+
/** list-units 실행 결과 */
|
|
21
|
+
export interface ListUnitsResult {
|
|
22
|
+
total: number;
|
|
23
|
+
inProgress: BacklogEntry[];
|
|
24
|
+
ready: BacklogEntry[];
|
|
25
|
+
blocked: BacklogEntry[];
|
|
26
|
+
phase?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* update-commit 결과를 콘솔에 포맷팅하여 출력한다
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatUpdateCommit(result: ToolResult<unknown>, json: boolean): void;
|
|
32
|
+
/**
|
|
33
|
+
* list-units 결과를 콘솔에 포맷팅하여 출력한다
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatListUnits(result: ToolResult<unknown>, json: boolean): void;
|
|
36
|
+
//# sourceMappingURL=formatters-list.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 목록/업데이트 관련 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* list-units, update-commit 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { RESET, GREEN, YELLOW, CYAN, DIM, BOLD } from './output.js';
|
|
9
|
+
const COMMIT_MODE_LABELS = {
|
|
10
|
+
'auto-head': 'HEAD 자동 감지',
|
|
11
|
+
'auto-recent': '최근 N개 커밋',
|
|
12
|
+
manual: '수동 지정',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* update-commit 결과를 콘솔에 포맷팅하여 출력한다
|
|
16
|
+
*/
|
|
17
|
+
export function formatUpdateCommit(result, json) {
|
|
18
|
+
if (!result.success)
|
|
19
|
+
return;
|
|
20
|
+
if (json) {
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const data = result.data;
|
|
25
|
+
if (!isUpdateCommitResult(data))
|
|
26
|
+
return;
|
|
27
|
+
const sourceLabel = COMMIT_MODE_LABELS[data.commitMode] ?? data.commitMode;
|
|
28
|
+
console.log(`
|
|
29
|
+
${GREEN}✅ 커밋 SHA 업데이트 완료${RESET}
|
|
30
|
+
`);
|
|
31
|
+
console.log(`${BOLD}🔢 SHA${RESET}: ${CYAN}${data.sha}${RESET}`);
|
|
32
|
+
console.log(`${BOLD}📑 섹션${RESET}: ${data.sectionHeader} (${data.section})`);
|
|
33
|
+
console.log(`${BOLD}⚙️ 모드${RESET}: ${data.mode} (${sourceLabel})`);
|
|
34
|
+
if (data.commitMode === 'auto-recent' &&
|
|
35
|
+
data.requested !== undefined &&
|
|
36
|
+
data.count !== undefined &&
|
|
37
|
+
data.count < data.requested) {
|
|
38
|
+
console.log(`${YELLOW}⚠️ 요청: ${String(data.requested)}개, 실제: ${String(data.count)}개 (커밋 수 부족)${RESET}`);
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* list-units 결과를 콘솔에 포맷팅하여 출력한다
|
|
44
|
+
*/
|
|
45
|
+
export function formatListUnits(result, json) {
|
|
46
|
+
if (!result.success)
|
|
47
|
+
return;
|
|
48
|
+
if (json) {
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const data = result.data;
|
|
53
|
+
if (!isListUnitsResult(data))
|
|
54
|
+
return;
|
|
55
|
+
const phaseLabel = data.phase ? ` (phase: ${data.phase})` : '';
|
|
56
|
+
console.log(`
|
|
57
|
+
${BOLD}📋 미완료 유닛 목록${phaseLabel} — 총 ${String(data.total)}개${RESET}
|
|
58
|
+
`);
|
|
59
|
+
if (data.total === 0) {
|
|
60
|
+
console.log(`${DIM} 미완료 유닛이 없습니다.${RESET}
|
|
61
|
+
`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (data.inProgress.length > 0) {
|
|
65
|
+
console.log(`${YELLOW}🚧 진행중 (${String(data.inProgress.length)}개)${RESET}`);
|
|
66
|
+
for (const e of data.inProgress) {
|
|
67
|
+
console.log(` ${CYAN}${e.unitId}${RESET} ${e.title.trim()}`);
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
if (data.ready.length > 0) {
|
|
72
|
+
console.log(`${BOLD}🟢 착수 가능 (${String(data.ready.length)}개)${RESET}`);
|
|
73
|
+
for (const e of data.ready) {
|
|
74
|
+
const deps = e.depends.length > 0 ? ` ${DIM}[의존: ${e.depends.join(', ')}]${RESET}` : '';
|
|
75
|
+
console.log(` ${CYAN}${e.unitId}${RESET} ${e.title.trim()}${deps}`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
if (data.blocked.length > 0) {
|
|
80
|
+
console.log(`${BOLD}🔴 차단됨 (${String(data.blocked.length)}개)${RESET}`);
|
|
81
|
+
for (const e of data.blocked) {
|
|
82
|
+
const deps = e.depends.length > 0 ? ` ${DIM}[의존: ${e.depends.join(', ')}]${RESET}` : '';
|
|
83
|
+
console.log(` ${CYAN}${e.unitId}${RESET} ${e.title.trim()}${deps}`);
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** ListUnitsResult 런타임 타입 가드 */
|
|
89
|
+
function isListUnitsResult(data) {
|
|
90
|
+
return (data !== null &&
|
|
91
|
+
typeof data === 'object' &&
|
|
92
|
+
'total' in data &&
|
|
93
|
+
'inProgress' in data &&
|
|
94
|
+
'ready' in data &&
|
|
95
|
+
'blocked' in data);
|
|
96
|
+
}
|
|
97
|
+
/** UpdateCommitResult 런타임 타입 가드 */
|
|
98
|
+
function isUpdateCommitResult(data) {
|
|
99
|
+
return (data !== null &&
|
|
100
|
+
typeof data === 'object' &&
|
|
101
|
+
'sha' in data &&
|
|
102
|
+
'mode' in data &&
|
|
103
|
+
'section' in data &&
|
|
104
|
+
'updated' in data);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=formatters-list.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 유닛 작업 관련 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* set-unit, context, validate 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolResult, PairingQuestion } from '../../types/index.js';
|
|
9
|
+
/** set-unit 실행 result */
|
|
10
|
+
export interface SetUnitResult {
|
|
11
|
+
unitId: string;
|
|
12
|
+
unitType: string;
|
|
13
|
+
title: string;
|
|
14
|
+
planPath: string;
|
|
15
|
+
depsFound: number;
|
|
16
|
+
docsFound: number;
|
|
17
|
+
commitsFound: number;
|
|
18
|
+
pairingQuestions: PairingQuestion[];
|
|
19
|
+
commandsUpdated: boolean;
|
|
20
|
+
warnings: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* set-unit 결과를 콘솔에 포맷팅하여 출력한다
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatSetUnit(result: ToolResult<unknown>, json: boolean): void;
|
|
26
|
+
/**
|
|
27
|
+
* context 결과를 콘솔에 포맷팅하여 출력한다
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatContext(result: ToolResult<unknown>, json: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* init 결과를 콘솔에 포맷팅하여 출력한다
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatInit(result: ToolResult<unknown>, json: boolean): void;
|
|
34
|
+
/**
|
|
35
|
+
* validate 결과를 콘솔에 포맷팅하여 출력한다
|
|
36
|
+
*
|
|
37
|
+
* exit code 규칙: 에러 있으면 1, 경고만 있으면 0
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatValidate(result: ToolResult<unknown>, json: boolean): void;
|
|
40
|
+
/**
|
|
41
|
+
* skill install 결과를 콘솔에 포맷팅하여 출력한다
|
|
42
|
+
*/
|
|
43
|
+
export declare function formatSkillInstall(result: ToolResult<unknown>, json: boolean): void;
|
|
44
|
+
//# sourceMappingURL=formatters-unit.d.ts.map
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 유닛 작업 관련 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* set-unit, context, validate 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { RESET, GREEN, YELLOW, CYAN, DIM, BOLD, RED } from './output.js';
|
|
9
|
+
/**
|
|
10
|
+
* set-unit 결과를 콘솔에 포맷팅하여 출력한다
|
|
11
|
+
*/
|
|
12
|
+
export function formatSetUnit(result, json) {
|
|
13
|
+
if (!result.success)
|
|
14
|
+
return;
|
|
15
|
+
if (json) {
|
|
16
|
+
console.log(JSON.stringify(result, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const data = result.data;
|
|
20
|
+
if (!isSetUnitResult(data))
|
|
21
|
+
return;
|
|
22
|
+
console.log(`
|
|
23
|
+
${GREEN}✅ 작업 유닛 설정 완료: ${data.unitId}${RESET}
|
|
24
|
+
`);
|
|
25
|
+
console.log(`${BOLD}📝 제목${RESET}: ${data.title}`);
|
|
26
|
+
console.log(`${BOLD}📂 유형${RESET}: ${data.unitType}`);
|
|
27
|
+
console.log(`${BOLD}📄 계획서${RESET}: ${data.planPath}`);
|
|
28
|
+
console.log(`${BOLD}📝 커맨드 파일${RESET}: ${data.commandsUpdated ? GREEN + '업데이트됨' : RED + '업데이트 실패(섹션 누락)'}${RESET}`);
|
|
29
|
+
console.log(`
|
|
30
|
+
${BOLD}📊 수집된 컨텍스트${RESET}`);
|
|
31
|
+
console.log(` ${DIM}-${RESET} 의존성: ${CYAN}${String(data.depsFound)}${RESET}개`);
|
|
32
|
+
console.log(` ${DIM}-${RESET} 문서: ${CYAN}${String(data.docsFound)}${RESET}개`);
|
|
33
|
+
console.log(` ${DIM}-${RESET} 커밋: ${CYAN}${String(data.commitsFound)}${RESET}개`);
|
|
34
|
+
if (data.warnings.length > 0) {
|
|
35
|
+
console.log(`
|
|
36
|
+
${YELLOW}⚠️ 경고 (${String(data.warnings.length)}개)${RESET}`);
|
|
37
|
+
for (const w of data.warnings) {
|
|
38
|
+
console.log(` ${YELLOW}${DIM}-${RESET} ${w}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log(`
|
|
42
|
+
${DIM}이제 에이전트에게 '${data.unitId}' 작업을 지시하세요.${RESET}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* context 결과를 콘솔에 포맷팅하여 출력한다
|
|
47
|
+
*/
|
|
48
|
+
export function formatContext(result, json) {
|
|
49
|
+
if (!result.success)
|
|
50
|
+
return;
|
|
51
|
+
if (json) {
|
|
52
|
+
console.log(JSON.stringify(result, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const ctx = result.data;
|
|
56
|
+
if (!isCommandContext(ctx))
|
|
57
|
+
return;
|
|
58
|
+
console.log(`
|
|
59
|
+
${GREEN}✅ 유닛 컨텍스트: ${ctx.unit.unitId}${RESET}
|
|
60
|
+
`);
|
|
61
|
+
console.log(`${BOLD}📝 제목${RESET}: ${ctx.unit.title}`);
|
|
62
|
+
console.log(`${BOLD}📂 유닛 유형${RESET}: ${ctx.unit.unitType}`);
|
|
63
|
+
console.log(`${BOLD}📄 계획서${RESET}: ${ctx.unit.planPath}`);
|
|
64
|
+
if (ctx.unit.depends.length > 0) {
|
|
65
|
+
console.log(`
|
|
66
|
+
${BOLD}🔗 의존성 (${String(ctx.unit.depends.length)}개)${RESET}`);
|
|
67
|
+
for (const dep of ctx.unit.depends) {
|
|
68
|
+
console.log(` ${DIM}-${RESET} ${CYAN}${dep.unitId}${RESET} ${dep.description}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ctx.depDocs.length > 0) {
|
|
72
|
+
const totalDocs = ctx.depDocs.reduce((sum, d) => sum + d.found.length, 0);
|
|
73
|
+
console.log(`
|
|
74
|
+
${BOLD}📚 의존성 문서 (${String(totalDocs)}개)${RESET}`);
|
|
75
|
+
for (const dd of ctx.depDocs) {
|
|
76
|
+
for (const doc of dd.found) {
|
|
77
|
+
console.log(` ${DIM}-${RESET} [${doc.role}] ${CYAN}${doc.path}${RESET}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (ctx.depCommits.length > 0) {
|
|
82
|
+
console.log(`
|
|
83
|
+
${BOLD}💾 의존성 커밋 (${String(ctx.depCommits.length)}개)${RESET}`);
|
|
84
|
+
for (const sha of ctx.depCommits) {
|
|
85
|
+
console.log(` ${DIM}-${RESET} ${sha}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (ctx.pairingQuestions.length > 0) {
|
|
89
|
+
console.log(`
|
|
90
|
+
${BOLD}❓ 페어링 질문 (${String(ctx.pairingQuestions.length)}개)${RESET}`);
|
|
91
|
+
for (const q of ctx.pairingQuestions) {
|
|
92
|
+
const marker = q.resolved ? '✅' : '⬜';
|
|
93
|
+
const decision = q.resolved && q.selectedOption ? ` → ${q.selectedOption}` : '';
|
|
94
|
+
console.log(` ${marker} ${q.text}${YELLOW}${decision}${RESET}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (ctx.specialRequests.length > 0) {
|
|
98
|
+
console.log(`
|
|
99
|
+
${BOLD}⚡ 특별 요청사항 (${String(ctx.specialRequests.length)}개)${RESET}`);
|
|
100
|
+
for (const req of ctx.specialRequests) {
|
|
101
|
+
console.log(` ${DIM}-${RESET} ${req}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* init 결과를 콘솔에 포맷팅하여 출력한다
|
|
108
|
+
*/
|
|
109
|
+
export function formatInit(result, json) {
|
|
110
|
+
if (!result.success)
|
|
111
|
+
return;
|
|
112
|
+
if (json) {
|
|
113
|
+
console.log(JSON.stringify(result, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const data = result.data;
|
|
117
|
+
if (!isInitResult(data))
|
|
118
|
+
return;
|
|
119
|
+
const action = data.overwritten ? '덮어쓰기' : '생성';
|
|
120
|
+
const source = data.scanResult ? ' (프로젝트 스캔 기반)' : '';
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(`${GREEN}✅ vibe-commander.config.json ${action} 완료${source}${RESET}`);
|
|
123
|
+
console.log(`${BOLD}📁 경로${RESET}: ${CYAN}${data.configPath}${RESET}`);
|
|
124
|
+
if (data.scanResult) {
|
|
125
|
+
const totalPatternFiles = data.scanResult.idPatterns.reduce((sum, p) => sum + p.matchCount, 0);
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(`${BOLD}📊 스캔 요약${RESET}`);
|
|
128
|
+
console.log(` ${DIM}-${RESET} 감지된 패턴: ${CYAN}${String(data.scanResult.idPatterns.length)}${RESET}개 (총 ${String(totalPatternFiles)}개 파일)`);
|
|
129
|
+
console.log(` ${DIM}-${RESET} 문서 디렉토리: ${CYAN}${String(data.scanResult.docRoots.length)}${RESET}개`);
|
|
130
|
+
if (data.scanResult.commandFiles.length > 0) {
|
|
131
|
+
console.log(` ${DIM}-${RESET} 커맨드 파일: ${CYAN}${data.scanResult.commandFiles[0]?.filePath ?? ''}${RESET}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(`${DIM}다음 명령어로 유닛을 설정하세요:${RESET}`);
|
|
136
|
+
console.log(` ${CYAN}vc set-unit <unitId>${RESET}`);
|
|
137
|
+
console.log('');
|
|
138
|
+
}
|
|
139
|
+
/** InitResult 런타임 타입 가드 */
|
|
140
|
+
function isInitResult(data) {
|
|
141
|
+
return data !== null && typeof data === 'object' && 'configPath' in data && 'overwritten' in data;
|
|
142
|
+
}
|
|
143
|
+
/** CommandContext 런타임 타입 가드 */
|
|
144
|
+
function isCommandContext(data) {
|
|
145
|
+
return (data !== null &&
|
|
146
|
+
typeof data === 'object' &&
|
|
147
|
+
'unit' in data &&
|
|
148
|
+
'depDocs' in data &&
|
|
149
|
+
'depCommits' in data &&
|
|
150
|
+
'pairingQuestions' in data &&
|
|
151
|
+
'specialRequests' in data);
|
|
152
|
+
}
|
|
153
|
+
/** SetUnitResult 런타임 타입 가드 */
|
|
154
|
+
function isSetUnitResult(data) {
|
|
155
|
+
return (data !== null &&
|
|
156
|
+
typeof data === 'object' &&
|
|
157
|
+
'unitId' in data &&
|
|
158
|
+
'title' in data &&
|
|
159
|
+
'commandsUpdated' in data &&
|
|
160
|
+
'warnings' in data);
|
|
161
|
+
}
|
|
162
|
+
// ── validate 포맷터 ──
|
|
163
|
+
const CATEGORY_LABELS = {
|
|
164
|
+
schema: '📋 스키마 유효성',
|
|
165
|
+
paths: '📂 경로 존재',
|
|
166
|
+
regex: '🔤 정규식 유효성',
|
|
167
|
+
patterns: '🔀 패턴 충돌',
|
|
168
|
+
};
|
|
169
|
+
const CATEGORY_ORDER = ['schema', 'paths', 'regex', 'patterns'];
|
|
170
|
+
const STATUS_ICON = {
|
|
171
|
+
pass: '✅',
|
|
172
|
+
warn: '⚠️',
|
|
173
|
+
error: '❌',
|
|
174
|
+
};
|
|
175
|
+
const STATUS_COLOR = {
|
|
176
|
+
pass: GREEN,
|
|
177
|
+
warn: YELLOW,
|
|
178
|
+
error: RED,
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* validate 결과를 콘솔에 포맷팅하여 출력한다
|
|
182
|
+
*
|
|
183
|
+
* exit code 규칙: 에러 있으면 1, 경고만 있으면 0
|
|
184
|
+
*/
|
|
185
|
+
export function formatValidate(result, json) {
|
|
186
|
+
if (!result.success)
|
|
187
|
+
return;
|
|
188
|
+
const data = result.data;
|
|
189
|
+
if (!isValidateResult(data))
|
|
190
|
+
return;
|
|
191
|
+
if (!data.valid) {
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
}
|
|
194
|
+
if (json) {
|
|
195
|
+
console.log(JSON.stringify(result, null, 2));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
console.log('');
|
|
199
|
+
if (data.valid) {
|
|
200
|
+
console.log(`${GREEN}✅ 설정 검증 통과${RESET}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log(`${RED}❌ 설정 검증 실패${RESET}`);
|
|
204
|
+
}
|
|
205
|
+
console.log('');
|
|
206
|
+
for (const cat of CATEGORY_ORDER) {
|
|
207
|
+
const catItems = data.items.filter((i) => i.category === cat);
|
|
208
|
+
if (catItems.length === 0)
|
|
209
|
+
continue;
|
|
210
|
+
console.log(`${BOLD}${CATEGORY_LABELS[cat]}${RESET}`);
|
|
211
|
+
for (const item of catItems) {
|
|
212
|
+
const icon = STATUS_ICON[item.status];
|
|
213
|
+
const color = STATUS_COLOR[item.status];
|
|
214
|
+
console.log(` ${icon} ${color}${item.message}${RESET}`);
|
|
215
|
+
if (item.details) {
|
|
216
|
+
console.log(` ${DIM}${item.details}${RESET}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
console.log('');
|
|
220
|
+
}
|
|
221
|
+
const passCount = data.items.filter((i) => i.status === 'pass').length;
|
|
222
|
+
console.log(`${DIM}결과: ${String(passCount)}개 통과, ${String(data.warnings)}개 경고, ${String(data.errors)}개 에러${RESET}`);
|
|
223
|
+
console.log('');
|
|
224
|
+
}
|
|
225
|
+
/** ValidateResult 런타임 타입 가드 */
|
|
226
|
+
function isValidateResult(data) {
|
|
227
|
+
return (data !== null &&
|
|
228
|
+
typeof data === 'object' &&
|
|
229
|
+
'valid' in data &&
|
|
230
|
+
'errors' in data &&
|
|
231
|
+
'warnings' in data &&
|
|
232
|
+
'items' in data);
|
|
233
|
+
}
|
|
234
|
+
// ── skill install 포맷터 ──
|
|
235
|
+
/**
|
|
236
|
+
* skill install 결과를 콘솔에 포맷팅하여 출력한다
|
|
237
|
+
*/
|
|
238
|
+
export function formatSkillInstall(result, json) {
|
|
239
|
+
if (!result.success)
|
|
240
|
+
return;
|
|
241
|
+
if (json) {
|
|
242
|
+
console.log(JSON.stringify(result, null, 2));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const data = result.data;
|
|
246
|
+
if (!isSkillInstallResult(data))
|
|
247
|
+
return;
|
|
248
|
+
const totalDone = data.installed.length + data.overwritten.length;
|
|
249
|
+
console.log('');
|
|
250
|
+
if (totalDone > 0) {
|
|
251
|
+
console.log(`${GREEN}✅ SKILL 파일 설치 완료${RESET}`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
console.log(`${YELLOW}⚠️ 설치된 파일 없음${RESET}`);
|
|
255
|
+
}
|
|
256
|
+
if (data.installed.length > 0) {
|
|
257
|
+
console.log('');
|
|
258
|
+
console.log(`${BOLD}📦 설치됨 (${String(data.installed.length)}개)${RESET}`);
|
|
259
|
+
for (const p of data.installed) {
|
|
260
|
+
console.log(` ${GREEN}+${RESET} ${CYAN}${p}${RESET}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (data.overwritten.length > 0) {
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(`${BOLD}🔄 덮어씀 (${String(data.overwritten.length)}개)${RESET}`);
|
|
266
|
+
for (const p of data.overwritten) {
|
|
267
|
+
console.log(` ${YELLOW}~${RESET} ${CYAN}${p}${RESET}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (data.skipped.length > 0) {
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(`${BOLD}⏭️ 건너뜀 (${String(data.skipped.length)}개)${RESET}`);
|
|
273
|
+
for (const s of data.skipped) {
|
|
274
|
+
console.log(` ${DIM}-${RESET} ${s}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
/** SkillInstallResult 런타임 타입 가드 */
|
|
280
|
+
function isSkillInstallResult(data) {
|
|
281
|
+
return (data !== null &&
|
|
282
|
+
typeof data === 'object' &&
|
|
283
|
+
'installed' in data &&
|
|
284
|
+
'skipped' in data &&
|
|
285
|
+
'overwritten' in data);
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=formatters-unit.js.map
|