vibe-commander 0.2.0 → 0.2.2
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 +18 -0
- package/README.ko.md +643 -0
- package/README.md +643 -0
- package/dist/adapters/cli/commands/context-resolver.js +30 -0
- package/dist/adapters/cli/commands/git-helpers.js +5 -3
- package/dist/adapters/cli/commands/init-helpers.js +1 -1
- package/dist/adapters/cli/commands/io-helpers.js +6 -2
- package/dist/adapters/cli/commands/list-units.d.ts +2 -3
- package/dist/adapters/cli/commands/list-units.js +8 -34
- package/dist/adapters/cli/commands/schema.js +7 -3
- package/dist/adapters/cli/commands/set-unit.d.ts +5 -0
- package/dist/adapters/cli/commands/set-unit.js +149 -10
- package/dist/adapters/cli/commands/skill-install.js +10 -4
- package/dist/adapters/cli/formatters-schema.d.ts +17 -0
- package/dist/adapters/cli/formatters-schema.js +155 -0
- package/dist/adapters/cli/formatters-unit.d.ts +0 -8
- package/dist/adapters/cli/formatters-unit.js +4 -146
- package/dist/adapters/cli/index.js +2 -1
- package/dist/adapters/cli/output.js +3 -0
- package/dist/adapters/cli/router.d.ts +1 -12
- package/dist/adapters/cli/router.js +12 -107
- package/dist/adapters/cli/stdin-parser.d.ts +23 -0
- package/dist/adapters/cli/stdin-parser.js +121 -0
- package/dist/config/schema.d.ts +2 -9
- package/dist/config/schema.js +143 -56
- package/dist/core/parsers/dep-line-parser.js +7 -3
- package/dist/core/parsers/dependency-extractor.js +80 -14
- package/dist/core/parsers/index.d.ts +2 -1
- package/dist/core/parsers/index.js +3 -1
- package/dist/core/parsers/plan-parser-helpers.js +5 -0
- package/dist/core/parsers/subsection-extractor.d.ts +20 -0
- package/dist/core/parsers/subsection-extractor.js +56 -0
- package/dist/core/parsers/title-extractor.d.ts +16 -0
- package/dist/core/parsers/title-extractor.js +26 -0
- package/dist/core/renderers/index.d.ts +2 -1
- package/dist/core/renderers/index.js +1 -1
- package/dist/core/renderers/marker-utils.js +5 -3
- package/dist/core/renderers/schema-renderer.d.ts +16 -0
- package/dist/core/renderers/schema-renderer.js +21 -0
- package/dist/core/renderers/section-renderer.d.ts +9 -1
- package/dist/core/renderers/section-renderer.js +14 -5
- package/dist/core/resolvers/backlog-resolver.d.ts +37 -0
- package/dist/core/resolvers/backlog-resolver.js +50 -0
- package/dist/core/resolvers/index.d.ts +0 -2
- package/examples/nextjs-web.config.json +73 -0
- package/examples/python-backend.config.json +91 -0
- package/examples/tauri-app.config.json +95 -0
- package/examples/vscode-tasks.json +101 -0
- package/package.json +14 -2
- package/schemas/config-schema.json +409 -0
- package/skills/claude/SKILL.md +22 -0
- package/skills/common/cli-reference.md +22 -0
- package/skills/cursor/SKILL.md +22 -0
- package/dist/adapters/cli/formatters.d.ts +0 -2
- package/dist/adapters/cli/formatters.js +0 -3
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { ok, fail } from '../../../types/index.js';
|
|
11
|
+
/** git CLI 실행 타임아웃 (밀리초) */
|
|
12
|
+
const GIT_EXEC_TIMEOUT_MS = 5_000;
|
|
11
13
|
/**
|
|
12
14
|
* 현재 HEAD 커밋의 short SHA(8자)를 반환한다
|
|
13
15
|
*
|
|
@@ -19,7 +21,7 @@ export function getHeadSha(projectRoot) {
|
|
|
19
21
|
const sha = execSync('git rev-parse --short=8 HEAD', {
|
|
20
22
|
cwd: projectRoot,
|
|
21
23
|
encoding: 'utf-8',
|
|
22
|
-
timeout:
|
|
24
|
+
timeout: GIT_EXEC_TIMEOUT_MS,
|
|
23
25
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
24
26
|
}).replace(/[\r\n]+$/, '');
|
|
25
27
|
if (!sha) {
|
|
@@ -47,7 +49,7 @@ export function getChangedFiles(projectRoot, sha) {
|
|
|
47
49
|
const output = execSync(`git diff-tree --no-commit-id --name-only -r ${sha}`, {
|
|
48
50
|
cwd: projectRoot,
|
|
49
51
|
encoding: 'utf-8',
|
|
50
|
-
timeout:
|
|
52
|
+
timeout: GIT_EXEC_TIMEOUT_MS,
|
|
51
53
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
54
|
});
|
|
53
55
|
const files = output
|
|
@@ -76,7 +78,7 @@ export function getRecentShas(projectRoot, count) {
|
|
|
76
78
|
const output = execSync(`git log --format=%h --abbrev=8 -n ${String(count)}`, {
|
|
77
79
|
cwd: projectRoot,
|
|
78
80
|
encoding: 'utf-8',
|
|
79
|
-
timeout:
|
|
81
|
+
timeout: GIT_EXEC_TIMEOUT_MS,
|
|
80
82
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
83
|
})
|
|
82
84
|
.replace(/\r\n/g, '\n')
|
|
@@ -15,6 +15,10 @@ import { loadConfig } from '../../../config/loader.js';
|
|
|
15
15
|
import { findDepCommits } from '../../../core/resolvers/dep-commit-resolver.js';
|
|
16
16
|
import { filterCommitsByChangedFiles } from '../../../core/resolvers/commit-filter.js';
|
|
17
17
|
import { getChangedFiles } from './git-helpers.js';
|
|
18
|
+
/** git log 조회 시 최대 커밋 수 */
|
|
19
|
+
const GIT_LOG_LIMIT = 200;
|
|
20
|
+
/** git log 실행 타임아웃 (밀리초) */
|
|
21
|
+
const GIT_LOG_TIMEOUT_MS = 10_000;
|
|
18
22
|
/**
|
|
19
23
|
* 커맨드 파일을 읽어 반환한다
|
|
20
24
|
*/
|
|
@@ -78,10 +82,10 @@ export function collectGitCommits(depIds, commitSearch, projectRoot) {
|
|
|
78
82
|
if (commitSearch.strategy === 'disabled')
|
|
79
83
|
return [];
|
|
80
84
|
try {
|
|
81
|
-
const output = execSync(
|
|
85
|
+
const output = execSync(`git log --pretty=oneline -${String(GIT_LOG_LIMIT)}`, {
|
|
82
86
|
cwd: projectRoot,
|
|
83
87
|
encoding: 'utf-8',
|
|
84
|
-
timeout:
|
|
88
|
+
timeout: GIT_LOG_TIMEOUT_MS,
|
|
85
89
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
86
90
|
});
|
|
87
91
|
const result = findDepCommits(depIds, output, commitSearch);
|
|
@@ -27,9 +27,8 @@ export interface ListUnitsResult {
|
|
|
27
27
|
* 2. 로드맵 경로 확인 — null이면 에러 반환
|
|
28
28
|
* 3. 로드맵 파일 읽기 (Adapter I/O)
|
|
29
29
|
* 4. 백로그 파싱 (parseBacklog)
|
|
30
|
-
* 5.
|
|
31
|
-
* 6.
|
|
32
|
-
* 7. 결과 반환
|
|
30
|
+
* 5. 백로그 분류 및 필터링 (classifyBacklog)
|
|
31
|
+
* 6. 결과 반환
|
|
33
32
|
*
|
|
34
33
|
* @param args - 파싱된 CLI 인자 (command === 'list-units' 보장)
|
|
35
34
|
* @param projectRoot - 프로젝트 루트 절대 경로
|
|
@@ -14,6 +14,7 @@ import { join } from 'node:path';
|
|
|
14
14
|
import { ok, fail } from '../../../types/index.js';
|
|
15
15
|
import { loadConfig } from '../../../config/loader.js';
|
|
16
16
|
import { parseBacklog } from '../../../core/parsers/backlog-parser.js';
|
|
17
|
+
import { classifyBacklog } from '../../../core/resolvers/backlog-resolver.js';
|
|
17
18
|
/**
|
|
18
19
|
* list-units 커맨드를 실행한다
|
|
19
20
|
*
|
|
@@ -22,9 +23,8 @@ import { parseBacklog } from '../../../core/parsers/backlog-parser.js';
|
|
|
22
23
|
* 2. 로드맵 경로 확인 — null이면 에러 반환
|
|
23
24
|
* 3. 로드맵 파일 읽기 (Adapter I/O)
|
|
24
25
|
* 4. 백로그 파싱 (parseBacklog)
|
|
25
|
-
* 5.
|
|
26
|
-
* 6.
|
|
27
|
-
* 7. 결과 반환
|
|
26
|
+
* 5. 백로그 분류 및 필터링 (classifyBacklog)
|
|
27
|
+
* 6. 결과 반환
|
|
28
28
|
*
|
|
29
29
|
* @param args - 파싱된 CLI 인자 (command === 'list-units' 보장)
|
|
30
30
|
* @param projectRoot - 프로젝트 루트 절대 경로
|
|
@@ -57,36 +57,10 @@ export function handleListUnits(args, projectRoot) {
|
|
|
57
57
|
const parseResult = parseBacklog(roadmapContent, config.backlogParsing);
|
|
58
58
|
if (!parseResult.success)
|
|
59
59
|
return parseResult;
|
|
60
|
-
// 5.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
entries = entries.filter((e) => {
|
|
66
|
-
const entryPhase = extractPhase(e.unitId);
|
|
67
|
-
return entryPhase !== null && entryPhase.toLowerCase() === normalizedPhase;
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
// 7. 상태별 분류
|
|
71
|
-
const inProgress = entries.filter((e) => e.status === 'inProgress');
|
|
72
|
-
const ready = entries.filter((e) => e.status === 'ready');
|
|
73
|
-
const blocked = entries.filter((e) => e.status === 'blocked');
|
|
74
|
-
return ok({
|
|
75
|
-
total: entries.length,
|
|
76
|
-
inProgress,
|
|
77
|
-
ready,
|
|
78
|
-
blocked,
|
|
79
|
-
...(phase !== undefined && { phase }),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* 유닛 ID에서 phase 태그를 추출한다
|
|
84
|
-
*
|
|
85
|
-
* "U-016[Mvp]" → "Mvp"
|
|
86
|
-
* "CP-MVP-03" → null
|
|
87
|
-
*/
|
|
88
|
-
function extractPhase(unitId) {
|
|
89
|
-
const match = unitId.match(/\[([^\]]+)\]$/);
|
|
90
|
-
return match?.[1] ?? null;
|
|
60
|
+
// 5. 백로그 분류 및 필터링 (Core 순수 함수)
|
|
61
|
+
const classificationResult = classifyBacklog(parseResult.data, { phase });
|
|
62
|
+
if (!classificationResult.success)
|
|
63
|
+
return classificationResult;
|
|
64
|
+
return ok(classificationResult.data);
|
|
91
65
|
}
|
|
92
66
|
//# sourceMappingURL=list-units.js.map
|
|
@@ -15,8 +15,8 @@ import { writeFileSync, mkdirSync } from 'node:fs';
|
|
|
15
15
|
import { resolve, dirname } from 'node:path';
|
|
16
16
|
import { ok, fail } from '../../../types/index.js';
|
|
17
17
|
import { loadConfig } from '../../../config/loader.js';
|
|
18
|
-
import {
|
|
19
|
-
import { renderCommandsSchema, renderTypesSchema, SCHEMA_SUBCOMMANDS, } from '../../../core/renderers/schema-renderer.js';
|
|
18
|
+
import { projectConfigSchema } from '../../../config/schema.js';
|
|
19
|
+
import { renderConfigSchema, renderCommandsSchema, renderTypesSchema, SCHEMA_SUBCOMMANDS, } from '../../../core/renderers/schema-renderer.js';
|
|
20
20
|
import { validatePath } from '../../../core/validators/input-validator.js';
|
|
21
21
|
/**
|
|
22
22
|
* schema 커맨드를 실행한다
|
|
@@ -43,7 +43,11 @@ export function handleSchema(args, projectRoot) {
|
|
|
43
43
|
let result;
|
|
44
44
|
switch (subcommand) {
|
|
45
45
|
case 'config': {
|
|
46
|
-
const schema =
|
|
46
|
+
const schema = renderConfigSchema(projectConfigSchema, {
|
|
47
|
+
id: 'https://raw.githubusercontent.com/viilab/vibe-commander/main/schemas/config-schema.json',
|
|
48
|
+
title: 'vibe-commander Configuration',
|
|
49
|
+
description: 'Unit-based vibe coding workflow automation CLI configuration. Defines project structure, unit types, parsing rules, and command sections.',
|
|
50
|
+
});
|
|
47
51
|
result = { subcommand: 'config', schema };
|
|
48
52
|
break;
|
|
49
53
|
}
|
|
@@ -10,11 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
|
-
import { writeFileSync } from 'node:fs';
|
|
13
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
14
15
|
import { ok, fail } from '../../../types/index.js';
|
|
15
16
|
import { validateUnitId } from '../../../core/validators/input-validator.js';
|
|
17
|
+
import { parseBacklog } from '../../../core/parsers/backlog-parser.js';
|
|
18
|
+
import { classifyBacklog } from '../../../core/resolvers/backlog-resolver.js';
|
|
16
19
|
import { renderSection } from '../../../core/renderers/section-renderer.js';
|
|
17
20
|
import { updateSection } from '../../../core/renderers/section-updater.js';
|
|
21
|
+
import { findMarkerRange } from '../../../core/renderers/marker-utils.js';
|
|
22
|
+
import { extractSubsectionContent } from '../../../core/parsers/subsection-extractor.js';
|
|
23
|
+
import { normalizeLineEndings } from '../../../core/parsers/md-utils.js';
|
|
24
|
+
import { loadConfig } from '../../../config/loader.js';
|
|
18
25
|
import { resolveCommandContext } from './context-resolver.js';
|
|
19
26
|
import { readCommandsFile, enrichWithSuggestions } from './io-helpers.js';
|
|
20
27
|
import { writeActiveUnitType } from './state-helpers.js';
|
|
@@ -37,9 +44,21 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
37
44
|
if (args.command !== 'set-unit') {
|
|
38
45
|
return fail('INTERNAL_ERROR', 'handleSetUnit에 잘못된 커맨드가 전달되었습니다');
|
|
39
46
|
}
|
|
40
|
-
const {
|
|
47
|
+
const { noPrompt, dryRun, next } = args;
|
|
48
|
+
let unitId = args.unitId;
|
|
41
49
|
const requestKeys = 'requestKeys' in args ? args.requestKeys : [];
|
|
42
|
-
|
|
50
|
+
let autoSelected = false;
|
|
51
|
+
let selectedFrom;
|
|
52
|
+
// 0-a. --next: 백로그에서 ready 유닛 자동 선택
|
|
53
|
+
if (next) {
|
|
54
|
+
const pickResult = pickNextUnit(projectRoot);
|
|
55
|
+
if (!pickResult.success)
|
|
56
|
+
return pickResult;
|
|
57
|
+
unitId = pickResult.data.unitId;
|
|
58
|
+
autoSelected = true;
|
|
59
|
+
selectedFrom = { ready: pickResult.data.readyCount, total: pickResult.data.totalCount };
|
|
60
|
+
}
|
|
61
|
+
// 0-b. 입력 안전성 검증 (제어 문자, 경로 순회, URL 인코딩 등)
|
|
43
62
|
const safetyCheck = validateUnitId(unitId);
|
|
44
63
|
if (!safetyCheck.valid) {
|
|
45
64
|
return fail('INVALID_INPUT', safetyCheck.error ?? '입력 검증 실패');
|
|
@@ -57,19 +76,38 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
57
76
|
pairingAnswers = await promptAllQuestions(context.pairingQuestions);
|
|
58
77
|
context.pairingAnswers = pairingAnswers;
|
|
59
78
|
}
|
|
60
|
-
// 3.
|
|
61
|
-
const
|
|
62
|
-
// 4.
|
|
79
|
+
// 3. 커맨드 파일 읽기 (보존 추출 + 업데이트에 재사용)
|
|
80
|
+
const cmdFile = readCommandsFile(config, projectRoot);
|
|
81
|
+
// 4. 기존 특별 요청사항 추출 (보존 로직)
|
|
82
|
+
const specialRequestsHeading = config.commandSections.specialRequestsHeading;
|
|
83
|
+
let preservedSpecialRequests;
|
|
84
|
+
if (cmdFile.success) {
|
|
85
|
+
const extracted = extractExistingSpecialRequests(cmdFile.data.content, unitTypeConfig.commandSection, specialRequestsHeading, config.commandSections.useMarkers ? unitTypeConfig.key : undefined, config.commandSections.separator);
|
|
86
|
+
if (extracted) {
|
|
87
|
+
preservedSpecialRequests = extracted;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// 5. 섹션 렌더링 (Core 순수 함수)
|
|
91
|
+
const sectionBody = renderSection(context, unitTypeConfig, undefined, {
|
|
92
|
+
preservedSpecialRequests,
|
|
93
|
+
specialRequestsHeading,
|
|
94
|
+
});
|
|
95
|
+
// 6. 커맨드 파일 업데이트 (Adapter I/O) — dry-run 시 건너뜀
|
|
63
96
|
let commandsUpdated = false;
|
|
64
97
|
if (!dryRun) {
|
|
65
|
-
const cmdFile = readCommandsFile(config, projectRoot);
|
|
66
98
|
if (cmdFile.success) {
|
|
67
99
|
const useMarkers = config.commandSections.useMarkers;
|
|
68
100
|
const markerOptions = useMarkers ? { sectionKey: unitTypeConfig.key } : undefined;
|
|
69
101
|
const updateResult = updateSection(cmdFile.data.content, unitTypeConfig.commandSection, sectionBody, config.commandSections.separator, markerOptions);
|
|
70
102
|
if (updateResult.success && updateResult.data.updated) {
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(cmdFile.data.absPath, updateResult.data.content, 'utf-8');
|
|
105
|
+
commandsUpdated = true;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
+
warnings.push(`커맨드 파일 쓰기 실패: ${msg}`);
|
|
110
|
+
}
|
|
73
111
|
}
|
|
74
112
|
else if (updateResult.success && !updateResult.data.updated) {
|
|
75
113
|
warnings.push(`커맨드 파일에서 '${unitTypeConfig.commandSection}' 섹션을 찾을 수 없습니다`);
|
|
@@ -78,7 +116,7 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
78
116
|
else {
|
|
79
117
|
warnings.push(`커맨드 파일을 읽을 수 없습니다: ${config.paths.commands}`);
|
|
80
118
|
}
|
|
81
|
-
//
|
|
119
|
+
// 7. 활성 유닛 타입 상태 기록 (update-commit 자동 감지용)
|
|
82
120
|
writeActiveUnitType(projectRoot, unitTypeKey);
|
|
83
121
|
}
|
|
84
122
|
// 6. 결과 반환
|
|
@@ -95,6 +133,107 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
95
133
|
commandsUpdated,
|
|
96
134
|
warnings,
|
|
97
135
|
...(dryRun && { dryRun: true, preview: sectionBody }),
|
|
136
|
+
...(autoSelected && { autoSelected, selectedFrom }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 백로그에서 첫 번째 ready 유닛을 선택한다
|
|
141
|
+
*
|
|
142
|
+
* loadConfig → 로드맵 확인 → parseBacklog → ready 필터 → 첫 번째 반환
|
|
143
|
+
*/
|
|
144
|
+
function pickNextUnit(projectRoot) {
|
|
145
|
+
const configResult = loadConfig(projectRoot);
|
|
146
|
+
if (!configResult.success)
|
|
147
|
+
return configResult;
|
|
148
|
+
const config = configResult.data;
|
|
149
|
+
if (!config.paths.roadmap) {
|
|
150
|
+
return fail('ROADMAP_NOT_CONFIGURED', '--next 옵션을 사용하려면 로드맵 파일이 설정되어야 합니다', 'vibe-commander.config.json의 paths.roadmap을 설정하거나, vc set-unit <unitId>로 직접 지정하세요');
|
|
151
|
+
}
|
|
152
|
+
const roadmapAbsPath = join(projectRoot, config.paths.roadmap);
|
|
153
|
+
let roadmapContent;
|
|
154
|
+
try {
|
|
155
|
+
roadmapContent = readFileSync(roadmapAbsPath, 'utf-8');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return fail('ROADMAP_NOT_FOUND', `로드맵 파일을 찾을 수 없습니다: ${config.paths.roadmap}`, `절대 경로: ${roadmapAbsPath}`);
|
|
159
|
+
}
|
|
160
|
+
const parseResult = parseBacklog(roadmapContent, config.backlogParsing);
|
|
161
|
+
if (!parseResult.success)
|
|
162
|
+
return parseResult;
|
|
163
|
+
// 백로그 분류 (Core 순수 함수)
|
|
164
|
+
const classificationResult = classifyBacklog(parseResult.data);
|
|
165
|
+
if (!classificationResult.success)
|
|
166
|
+
return classificationResult;
|
|
167
|
+
const { ready, inProgress, blocked, total } = classificationResult.data;
|
|
168
|
+
if (ready.length === 0) {
|
|
169
|
+
return fail('NO_READY_UNITS', '착수 가능한(ready) 유닛이 없습니다', `현재 상태: 진행중 ${String(inProgress.length)}개, 블록됨 ${String(blocked.length)}개. vc list-units로 확인하세요`);
|
|
170
|
+
}
|
|
171
|
+
const first = ready[0];
|
|
172
|
+
if (!first) {
|
|
173
|
+
return fail('NO_READY_UNITS', '착수 가능한(ready) 유닛이 없습니다');
|
|
174
|
+
}
|
|
175
|
+
return ok({
|
|
176
|
+
unitId: first.unitId,
|
|
177
|
+
readyCount: ready.length,
|
|
178
|
+
totalCount: total,
|
|
98
179
|
});
|
|
99
180
|
}
|
|
181
|
+
// ── 특별 요청사항 보존 헬퍼 ──
|
|
182
|
+
/**
|
|
183
|
+
* 커맨드 파일에서 대상 섹션 내 기존 특별 요청사항 콘텐츠를 추출한다
|
|
184
|
+
*
|
|
185
|
+
* Fallback 체인:
|
|
186
|
+
* 1. 마커 기반: findMarkerRange로 섹션 범위 확보 → 내부에서 서브섹션 추출
|
|
187
|
+
* 2. 헤더 기반: commandSection 헤더로 섹션 범위 확보 → 내부에서 서브섹션 추출
|
|
188
|
+
* 3. 둘 다 실패: null 반환 (기존 동작 Fallback)
|
|
189
|
+
*
|
|
190
|
+
* 추출된 콘텐츠에서 trailing separator는 제거하여 이중 separator를 방지한다.
|
|
191
|
+
*/
|
|
192
|
+
function extractExistingSpecialRequests(fileContent, sectionHeader, specialRequestsHeading, sectionKey, separator) {
|
|
193
|
+
const lines = normalizeLineEndings(fileContent).split('\n');
|
|
194
|
+
let raw = null;
|
|
195
|
+
// 1. 마커 기반 범위 탐색
|
|
196
|
+
if (sectionKey) {
|
|
197
|
+
const range = findMarkerRange(lines, sectionKey);
|
|
198
|
+
if (range) {
|
|
199
|
+
const sectionContent = lines.slice(range.start + 1, range.end).join('\n');
|
|
200
|
+
raw = extractSubsectionContent(sectionContent, specialRequestsHeading);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// 2. 헤더 기반 Fallback
|
|
204
|
+
if (raw === null) {
|
|
205
|
+
const needle = sectionHeader.trim();
|
|
206
|
+
const headerIdx = lines.findIndex((l) => l.trim() === needle);
|
|
207
|
+
if (headerIdx === -1)
|
|
208
|
+
return null;
|
|
209
|
+
const hashMatch = needle.match(/^(#+)/);
|
|
210
|
+
const level = hashMatch?.[1] ? hashMatch[1].length : 1;
|
|
211
|
+
let endIdx = lines.length;
|
|
212
|
+
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
213
|
+
const line = (lines[i] ?? '').trim();
|
|
214
|
+
const lineMatch = line.match(/^(#+)\s/);
|
|
215
|
+
if (lineMatch?.[1] && lineMatch[1].length <= level) {
|
|
216
|
+
endIdx = i;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const sectionContent = lines.slice(headerIdx, endIdx).join('\n');
|
|
221
|
+
raw = extractSubsectionContent(sectionContent, specialRequestsHeading);
|
|
222
|
+
}
|
|
223
|
+
if (!raw)
|
|
224
|
+
return null;
|
|
225
|
+
// trailing separator 제거 (updateSection이 별도 삽입하므로 이중 방지)
|
|
226
|
+
if (separator) {
|
|
227
|
+
const resultLines = raw.split('\n');
|
|
228
|
+
const sep = separator.trim();
|
|
229
|
+
while (resultLines.length > 0 && (resultLines[resultLines.length - 1] ?? '').trim() === sep) {
|
|
230
|
+
resultLines.pop();
|
|
231
|
+
}
|
|
232
|
+
while (resultLines.length > 0 && (resultLines[resultLines.length - 1] ?? '').trim() === '') {
|
|
233
|
+
resultLines.pop();
|
|
234
|
+
}
|
|
235
|
+
return resultLines.length > 0 ? resultLines.join('\n') : null;
|
|
236
|
+
}
|
|
237
|
+
return raw;
|
|
238
|
+
}
|
|
100
239
|
//# sourceMappingURL=set-unit.js.map
|
|
@@ -77,9 +77,15 @@ function installForPlatform(platform, skillsRoot, projectRoot, force, result) {
|
|
|
77
77
|
}
|
|
78
78
|
result.overwritten.push(displayPath);
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
try {
|
|
81
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
82
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
83
|
+
writeFileSync(targetPath, content, 'utf-8');
|
|
84
|
+
result.installed.push(displayPath);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
88
|
+
result.skipped.push(`${displayPath} (I/O 오류: ${msg})`);
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
//# sourceMappingURL=skill-install.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 스키마 및 SKILL 설치 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* schema, skill install 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolResult } from '../../types/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* skill install 결과를 콘솔에 포맷팅하여 출력한다
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatSkillInstall(result: ToolResult<unknown>, json: boolean): void;
|
|
13
|
+
/**
|
|
14
|
+
* schema 결과를 콘솔에 포맷팅하여 출력한다
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatSchema(result: ToolResult<unknown>, json: boolean): void;
|
|
17
|
+
//# sourceMappingURL=formatters-schema.d.ts.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 스키마 및 SKILL 설치 포맷터 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* schema, skill install 커맨드의 실행 결과를 포맷팅한다.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { RESET, GREEN, YELLOW, CYAN, DIM, BOLD, RED } from './output.js';
|
|
9
|
+
// ── skill install 포맷터 ──
|
|
10
|
+
/**
|
|
11
|
+
* skill install 결과를 콘솔에 포맷팅하여 출력한다
|
|
12
|
+
*/
|
|
13
|
+
export function formatSkillInstall(result, json) {
|
|
14
|
+
if (!result.success)
|
|
15
|
+
return;
|
|
16
|
+
if (json) {
|
|
17
|
+
console.log(JSON.stringify(result, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const data = result.data;
|
|
21
|
+
if (!isSkillInstallResult(data))
|
|
22
|
+
return;
|
|
23
|
+
const totalDone = data.installed.length + data.overwritten.length;
|
|
24
|
+
console.log('');
|
|
25
|
+
if (totalDone > 0) {
|
|
26
|
+
console.log(`${GREEN}✅ SKILL 파일 설치 완료${RESET}`);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(`${YELLOW}⚠️ 설치된 파일 없음${RESET}`);
|
|
30
|
+
}
|
|
31
|
+
if (data.installed.length > 0) {
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(`${BOLD}📦 설치됨 (${String(data.installed.length)}개)${RESET}`);
|
|
34
|
+
for (const p of data.installed) {
|
|
35
|
+
console.log(` ${GREEN}+${RESET} ${CYAN}${p}${RESET}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (data.overwritten.length > 0) {
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(`${BOLD}🔄 덮어씀 (${String(data.overwritten.length)}개)${RESET}`);
|
|
41
|
+
for (const p of data.overwritten) {
|
|
42
|
+
console.log(` ${YELLOW}~${RESET} ${CYAN}${p}${RESET}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (data.skipped.length > 0) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(`${BOLD}⏭️ 건너뜀 (${String(data.skipped.length)}개)${RESET}`);
|
|
48
|
+
for (const s of data.skipped) {
|
|
49
|
+
console.log(` ${DIM}-${RESET} ${s}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
}
|
|
54
|
+
/** SkillInstallResult 런타임 타입 가드 */
|
|
55
|
+
function isSkillInstallResult(data) {
|
|
56
|
+
return (data !== null &&
|
|
57
|
+
typeof data === 'object' &&
|
|
58
|
+
'installed' in data &&
|
|
59
|
+
'skipped' in data &&
|
|
60
|
+
'overwritten' in data);
|
|
61
|
+
}
|
|
62
|
+
// ── schema 포맷터 ──
|
|
63
|
+
/**
|
|
64
|
+
* schema 결과를 콘솔에 포맷팅하여 출력한다
|
|
65
|
+
*/
|
|
66
|
+
export function formatSchema(result, json) {
|
|
67
|
+
if (!result.success)
|
|
68
|
+
return;
|
|
69
|
+
if (json) {
|
|
70
|
+
console.log(JSON.stringify(result, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const data = result.data;
|
|
74
|
+
if (!isSchemaResult(data))
|
|
75
|
+
return;
|
|
76
|
+
if ('outputPath' in data && data.outputPath) {
|
|
77
|
+
console.log(`\n${GREEN}✅ 스키마가 파일에 저장되었습니다: ${CYAN}${data.outputPath}${RESET}\n`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
switch (data.subcommand) {
|
|
81
|
+
case 'help':
|
|
82
|
+
formatSchemaHelp(data);
|
|
83
|
+
break;
|
|
84
|
+
case 'config':
|
|
85
|
+
console.log(JSON.stringify(data.schema, null, 2));
|
|
86
|
+
break;
|
|
87
|
+
case 'commands':
|
|
88
|
+
formatSchemaCommands(data);
|
|
89
|
+
break;
|
|
90
|
+
case 'types':
|
|
91
|
+
formatSchemaTypes(data);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function formatSchemaHelp(data) {
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(`${BOLD}vc schema${RESET} — 런타임 스키마 조회`);
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(`${YELLOW}사용 가능한 서브커맨드:${RESET}`);
|
|
100
|
+
for (const sub of data.availableSubcommands) {
|
|
101
|
+
console.log(` ${CYAN}${sub.name}${RESET} ${sub.description}`);
|
|
102
|
+
}
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(`${YELLOW}사용법:${RESET} ${data.usage}`);
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
function formatSchemaCommands(data) {
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(`${BOLD}📋 vibe-commander CLI 스키마${RESET}`);
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(`${YELLOW}글로벌 옵션:${RESET}`);
|
|
112
|
+
for (const flag of data.globalFlags) {
|
|
113
|
+
const alias = flag.alias ? `, ${CYAN}${flag.alias}${RESET}` : '';
|
|
114
|
+
console.log(` ${CYAN}${flag.name}${RESET}${alias} ${DIM}${flag.description}${RESET}`);
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log(`${YELLOW}명령어 (${String(data.commands.length)}개):${RESET}`);
|
|
118
|
+
for (const cmd of data.commands) {
|
|
119
|
+
const argStr = cmd.args.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(' ');
|
|
120
|
+
const nameWithArgs = argStr ? `${cmd.name} ${argStr}` : cmd.name;
|
|
121
|
+
console.log(` ${CYAN}${nameWithArgs}${RESET}`);
|
|
122
|
+
console.log(` ${cmd.description}`);
|
|
123
|
+
if (cmd.args.length > 0) {
|
|
124
|
+
for (const arg of cmd.args) {
|
|
125
|
+
const req = arg.required ? `${RED}필수${RESET}` : `${DIM}선택${RESET}`;
|
|
126
|
+
console.log(` ${DIM}인자:${RESET} ${arg.name} (${req}) — ${arg.description}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (cmd.flags.length > 0) {
|
|
130
|
+
const flagNames = cmd.flags.map((f) => f.name).join(', ');
|
|
131
|
+
console.log(` ${DIM}플래그:${RESET} ${flagNames}`);
|
|
132
|
+
}
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function formatSchemaTypes(data) {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(`${BOLD}📋 프로젝트 유닛 타입 (${String(data.types.length)}개)${RESET}`);
|
|
139
|
+
console.log('');
|
|
140
|
+
for (const t of data.types) {
|
|
141
|
+
const displayName = t.displayName ? ` (${t.displayName})` : '';
|
|
142
|
+
console.log(` ${CYAN}${BOLD}${t.key}${RESET}${displayName}`);
|
|
143
|
+
console.log(` ${DIM}ID 패턴:${RESET} ${t.idPattern}`);
|
|
144
|
+
console.log(` ${DIM}계획서:${RESET} ${t.planDir}`);
|
|
145
|
+
console.log(` ${DIM}커맨드 섹션:${RESET} ${t.commandSection}`);
|
|
146
|
+
console.log(` ${DIM}의존성 수집:${RESET} ${t.collectDeps ? GREEN + '✅' : RED + '❌'}${RESET}`);
|
|
147
|
+
console.log(` ${DIM}헤더 템플릿:${RESET} ${t.headerTemplate}`);
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** SchemaResult 런타임 타입 가드 */
|
|
152
|
+
function isSchemaResult(data) {
|
|
153
|
+
return data !== null && typeof data === 'object' && 'subcommand' in data;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=formatters-schema.js.map
|
|
@@ -37,12 +37,4 @@ export declare function formatInit(result: ToolResult<unknown>, json: boolean):
|
|
|
37
37
|
* exit code 규칙: 에러 있으면 1, 경고만 있으면 0
|
|
38
38
|
*/
|
|
39
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
|
-
/**
|
|
45
|
-
* schema 결과를 콘솔에 포맷팅하여 출력한다
|
|
46
|
-
*/
|
|
47
|
-
export declare function formatSchema(result: ToolResult<unknown>, json: boolean): void;
|
|
48
40
|
//# sourceMappingURL=formatters-unit.d.ts.map
|