vibe-commander 0.1.0 → 0.1.1
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
package/AGENTS.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# AGENTS.md — vibe-commander for AI Agents
|
|
2
|
+
|
|
3
|
+
> This file is automatically included in the npm package.
|
|
4
|
+
> AI agents should read this file at the start of a conversation to understand how to use the `vc` CLI.
|
|
5
|
+
|
|
6
|
+
## Quick Reference
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
vc init # Initialize project (create config)
|
|
10
|
+
vc init --from-existing # Auto-detect from existing files
|
|
11
|
+
vc set-unit <unitId> # Set current working unit
|
|
12
|
+
vc set-unit <unitId> --dry-run # Preview without modifying files
|
|
13
|
+
vc update-commit [sha|N] # Update commit SHA (no arg=HEAD, number=recent N)
|
|
14
|
+
vc list-units [--phase Mvp] # List incomplete units
|
|
15
|
+
vc context <unitId> --json # Read-only context query
|
|
16
|
+
vc validate # Validate config
|
|
17
|
+
vc skill install # Install SKILL files for agents
|
|
18
|
+
vc schema [config|commands|types] # Introspect CLI schema
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Invariant Rules for Agents
|
|
22
|
+
|
|
23
|
+
1. **Always `--dry-run` first**: Before any mutating command (`set-unit`), run with `--dry-run` to preview changes. Only proceed if the preview looks correct.
|
|
24
|
+
2. **Always use `--json`**: Parse structured output instead of human-readable text. All commands support `--json` flag.
|
|
25
|
+
3. **Validate inputs**: Unit IDs follow project-specific patterns (e.g., `U-001[Mvp]`, `RU-010[Mmp]`). Do not fabricate IDs — use `vc list-units --json` to discover valid IDs.
|
|
26
|
+
4. **Check exit codes**: `0` = success, `1` = error, `2` = usage error. Non-zero exit always has a JSON error on stderr when `--json` is used.
|
|
27
|
+
5. **Use `--stdin` for pipe mode**: Pass JSON arguments via stdin for automation: `echo '{"unitId":"U-001[Mvp]"}' | vc set-unit --stdin --json`
|
|
28
|
+
|
|
29
|
+
## Safe Workflow Example
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1. Discover available units
|
|
33
|
+
vc list-units --json
|
|
34
|
+
|
|
35
|
+
# 2. Preview what set-unit will do (no file changes)
|
|
36
|
+
vc set-unit U-013[Mvp] --dry-run --json
|
|
37
|
+
|
|
38
|
+
# 3. Apply changes only after confirming preview
|
|
39
|
+
vc set-unit U-013[Mvp] --json
|
|
40
|
+
|
|
41
|
+
# 4. Update commit SHA after making changes
|
|
42
|
+
vc update-commit --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Config File
|
|
46
|
+
|
|
47
|
+
- Location: `vibe-commander.config.json` in project root
|
|
48
|
+
- Validate with: `vc validate --json`
|
|
49
|
+
- The config defines unit ID patterns, document paths, and command sections
|
|
50
|
+
|
|
51
|
+
## Error Handling
|
|
52
|
+
|
|
53
|
+
All errors follow the `ToolResult` structure:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"success": false,
|
|
58
|
+
"error": {
|
|
59
|
+
"code": "UNIT_TYPE_NOT_FOUND",
|
|
60
|
+
"message": "유닛 ID에 매칭되는 유형을 찾을 수 없습니다: U-116",
|
|
61
|
+
"details": "등록된 유형: implement, refactor, checkpoint",
|
|
62
|
+
"suggestions": ["U-116[Mmp]", "U-115[Mmp]"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When an invalid unit ID is provided, the `suggestions` field contains similar valid IDs based on fuzzy matching.
|
|
68
|
+
|
|
69
|
+
## Input Safety
|
|
70
|
+
|
|
71
|
+
The CLI rejects dangerous input patterns:
|
|
72
|
+
- Path traversal: `../`, `..\\`
|
|
73
|
+
- Control characters: ASCII < 0x20
|
|
74
|
+
- URL encoding: `%2e`, `%2F`, etc.
|
|
75
|
+
- Query parameters: `?`, `#`
|
|
76
|
+
|
|
77
|
+
## Schema Discovery
|
|
78
|
+
|
|
79
|
+
The `vc schema` command provides machine-readable information about the CLI and project configuration. Use this to dynamically discover capabilities:
|
|
80
|
+
|
|
81
|
+
- `vc schema config --json`: Returns the JSON Schema for `vibe-commander.config.json`.
|
|
82
|
+
- `vc schema commands --json`: Returns all available commands, their descriptions, arguments, and flags.
|
|
83
|
+
- `vc schema types --json`: Returns the list of `unitTypes` and their ID regex patterns for the current project.
|
|
84
|
+
- Use the `--output <path>` flag to save the schema to a file for repeated use.
|
|
85
|
+
|
|
86
|
+
## SKILL Files
|
|
87
|
+
|
|
88
|
+
After installing with `vc skill install`, SKILL files provide detailed CLI usage guides in:
|
|
89
|
+
- `.cursor/skills/vibe-commander/SKILL.md` (Cursor)
|
|
90
|
+
- `.claude/skills/vibe-commander/SKILL.md` (Claude Code)
|
|
91
|
+
|
|
92
|
+
## Architecture Note
|
|
93
|
+
|
|
94
|
+
vibe-commander uses **SKILL files + CLI**, not MCP servers. There is no MCP endpoint to connect to. Interact exclusively through the `vc` CLI binary.
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import { fail } from '../../../types/index.js';
|
|
14
|
+
import { validateUnitId } from '../../../core/validators/input-validator.js';
|
|
14
15
|
import { resolveCommandContext } from './context-resolver.js';
|
|
16
|
+
import { enrichWithSuggestions } from './io-helpers.js';
|
|
15
17
|
/**
|
|
16
18
|
* context 커맨드를 실행한다
|
|
17
19
|
*
|
|
@@ -27,10 +29,16 @@ export function handleContext(args, projectRoot) {
|
|
|
27
29
|
return fail('INTERNAL_ERROR', 'handleContext에 잘못된 커맨드가 전달되었습니다');
|
|
28
30
|
}
|
|
29
31
|
const { unitId } = args;
|
|
32
|
+
// 0. 입력 안전성 검증 (제어 문자, 경로 순회, URL 인코딩 등)
|
|
33
|
+
const safetyCheck = validateUnitId(unitId);
|
|
34
|
+
if (!safetyCheck.valid) {
|
|
35
|
+
return fail('INVALID_INPUT', safetyCheck.error ?? '입력 검증 실패');
|
|
36
|
+
}
|
|
30
37
|
// 1. 전체 컨텍스트 해석 (I/O + 파싱 + 의존성 수집)
|
|
31
38
|
const resolveResult = resolveCommandContext(unitId, projectRoot);
|
|
32
|
-
if (!resolveResult.success)
|
|
33
|
-
return resolveResult;
|
|
39
|
+
if (!resolveResult.success) {
|
|
40
|
+
return enrichWithSuggestions(resolveResult, unitId, projectRoot);
|
|
41
|
+
}
|
|
34
42
|
return {
|
|
35
43
|
success: true,
|
|
36
44
|
data: resolveResult.data.context,
|
|
@@ -42,4 +42,23 @@ export interface BuildSpecialRequestsResult {
|
|
|
42
42
|
* 존재하지 않는 키는 warnings에 추가하고 나머지는 정상 처리 (Graceful Degradation)
|
|
43
43
|
*/
|
|
44
44
|
export declare function buildSpecialRequests(config: ProjectConfig, unitTypeKey: string, requestKeys?: string[]): BuildSpecialRequestsResult;
|
|
45
|
+
/**
|
|
46
|
+
* 프로젝트의 계획서 디렉토리에서 알려진 유닛 ID 목록을 스캔한다
|
|
47
|
+
*
|
|
48
|
+
* docRoots의 plan 디렉토리에서 .md 파일명을 수집하여 유닛 ID로 반환.
|
|
49
|
+
* Graceful Degradation: 설정 로드 실패, 디렉토리 미존재 시 빈 배열 반환.
|
|
50
|
+
*/
|
|
51
|
+
export declare function scanKnownUnitIds(projectRoot: string): string[];
|
|
52
|
+
/**
|
|
53
|
+
* 유닛 ID 매칭/탐색 실패 시 fuzzy match 제안을 첨부한다
|
|
54
|
+
*
|
|
55
|
+
* UNIT_TYPE_NOT_FOUND 또는 PLAN_NOT_FOUND 에러에 대해
|
|
56
|
+
* 프로젝트의 알려진 유닛 ID를 스캔하여 유사 ID를 제안.
|
|
57
|
+
*
|
|
58
|
+
* @param result - 원본 실패 결과
|
|
59
|
+
* @param unitId - 잘못된 유닛 ID
|
|
60
|
+
* @param projectRoot - 프로젝트 루트 경로
|
|
61
|
+
* @returns 제안이 추가된 실패 결과
|
|
62
|
+
*/
|
|
63
|
+
export declare function enrichWithSuggestions(result: ToolResult<never>, unitId: string, projectRoot: string): ToolResult<never>;
|
|
45
64
|
//# sourceMappingURL=io-helpers.d.ts.map
|
|
@@ -10,6 +10,8 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
|
10
10
|
import { execSync } from 'node:child_process';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { ok, fail } from '../../../types/index.js';
|
|
13
|
+
import { validateUnitId } from '../../../core/validators/input-validator.js';
|
|
14
|
+
import { loadConfig } from '../../../config/loader.js';
|
|
13
15
|
import { findDepCommits } from '../../../core/resolvers/dep-commit-resolver.js';
|
|
14
16
|
import { filterCommitsByChangedFiles } from '../../../core/resolvers/commit-filter.js';
|
|
15
17
|
import { getChangedFiles } from './git-helpers.js';
|
|
@@ -134,6 +136,69 @@ export function buildSpecialRequests(config, unitTypeKey, requestKeys) {
|
|
|
134
136
|
}
|
|
135
137
|
return { requests, warnings };
|
|
136
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* 프로젝트의 계획서 디렉토리에서 알려진 유닛 ID 목록을 스캔한다
|
|
141
|
+
*
|
|
142
|
+
* docRoots의 plan 디렉토리에서 .md 파일명을 수집하여 유닛 ID로 반환.
|
|
143
|
+
* Graceful Degradation: 설정 로드 실패, 디렉토리 미존재 시 빈 배열 반환.
|
|
144
|
+
*/
|
|
145
|
+
export function scanKnownUnitIds(projectRoot) {
|
|
146
|
+
try {
|
|
147
|
+
const configResult = loadConfig(projectRoot);
|
|
148
|
+
if (!configResult.success)
|
|
149
|
+
return [];
|
|
150
|
+
const config = configResult.data;
|
|
151
|
+
const ids = [];
|
|
152
|
+
for (const unitType of config.unitTypes) {
|
|
153
|
+
const planDirKey = unitType.planDir;
|
|
154
|
+
const planDir = config.paths.docRoots[planDirKey];
|
|
155
|
+
if (!planDir)
|
|
156
|
+
continue;
|
|
157
|
+
const dirPath = join(projectRoot, planDir);
|
|
158
|
+
if (!existsSync(dirPath))
|
|
159
|
+
continue;
|
|
160
|
+
const files = readdirSync(dirPath);
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (file.endsWith('.md')) {
|
|
163
|
+
ids.push(file.slice(0, -3));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return [...new Set(ids)];
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 유닛 ID 매칭/탐색 실패 시 fuzzy match 제안을 첨부한다
|
|
175
|
+
*
|
|
176
|
+
* UNIT_TYPE_NOT_FOUND 또는 PLAN_NOT_FOUND 에러에 대해
|
|
177
|
+
* 프로젝트의 알려진 유닛 ID를 스캔하여 유사 ID를 제안.
|
|
178
|
+
*
|
|
179
|
+
* @param result - 원본 실패 결과
|
|
180
|
+
* @param unitId - 잘못된 유닛 ID
|
|
181
|
+
* @param projectRoot - 프로젝트 루트 경로
|
|
182
|
+
* @returns 제안이 추가된 실패 결과
|
|
183
|
+
*/
|
|
184
|
+
export function enrichWithSuggestions(result, unitId, projectRoot) {
|
|
185
|
+
if (result.success)
|
|
186
|
+
return result;
|
|
187
|
+
const enrichable = ['UNIT_TYPE_NOT_FOUND', 'PLAN_NOT_FOUND'];
|
|
188
|
+
if (!enrichable.includes(result.error.code))
|
|
189
|
+
return result;
|
|
190
|
+
const knownIds = scanKnownUnitIds(projectRoot);
|
|
191
|
+
if (knownIds.length === 0)
|
|
192
|
+
return result;
|
|
193
|
+
const fuzzyResult = validateUnitId(unitId, knownIds);
|
|
194
|
+
if (fuzzyResult.suggestions && fuzzyResult.suggestions.length > 0) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: { ...result.error, suggestions: fuzzyResult.suggestions },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
137
202
|
/**
|
|
138
203
|
* 간단한 glob 패턴(* 와일드카드)을 정규식으로 변환
|
|
139
204
|
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema 커맨드 핸들러 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* CLI 스키마를 런타임에 조회할 수 있게 하여, 에이전트가
|
|
5
|
+
* SKILL 파일이나 문서 없이도 CLI를 자율적으로 활용할 수 있도록 한다.
|
|
6
|
+
*
|
|
7
|
+
* 하위 커맨드:
|
|
8
|
+
* - config: 설정 파일(vibe-commander.config.json) JSON Schema 출력
|
|
9
|
+
* - commands: 사용 가능한 커맨드 목록 + 파라미터 + 플래그
|
|
10
|
+
* - types: 현재 프로젝트의 unitTypes 목록 + ID 패턴
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolResult } from '../../../types/index.js';
|
|
15
|
+
import type { ParsedArgs } from '../router.js';
|
|
16
|
+
import { type SchemaResult } from '../../../core/renderers/schema-renderer.js';
|
|
17
|
+
/**
|
|
18
|
+
* schema 커맨드를 실행한다
|
|
19
|
+
*
|
|
20
|
+
* 하위 커맨드에 따라 설정 스키마, 커맨드 목록, 유닛 타입 정보를 반환.
|
|
21
|
+
* --output 옵션이 지정되면 결과를 파일로 저장한다.
|
|
22
|
+
*
|
|
23
|
+
* @param args - 파싱된 CLI 인자 (command === 'schema' 보장)
|
|
24
|
+
* @param projectRoot - 프로젝트 루트 절대 경로
|
|
25
|
+
* @returns SchemaResult 또는 에러
|
|
26
|
+
*/
|
|
27
|
+
export declare function handleSchema(args: ParsedArgs, projectRoot: string): ToolResult<SchemaResult>;
|
|
28
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema 커맨드 핸들러 — Layer 3 (Adapter)
|
|
3
|
+
*
|
|
4
|
+
* CLI 스키마를 런타임에 조회할 수 있게 하여, 에이전트가
|
|
5
|
+
* SKILL 파일이나 문서 없이도 CLI를 자율적으로 활용할 수 있도록 한다.
|
|
6
|
+
*
|
|
7
|
+
* 하위 커맨드:
|
|
8
|
+
* - config: 설정 파일(vibe-commander.config.json) JSON Schema 출력
|
|
9
|
+
* - commands: 사용 가능한 커맨드 목록 + 파라미터 + 플래그
|
|
10
|
+
* - types: 현재 프로젝트의 unitTypes 목록 + ID 패턴
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
15
|
+
import { resolve, dirname } from 'node:path';
|
|
16
|
+
import { ok, fail } from '../../../types/index.js';
|
|
17
|
+
import { loadConfig } from '../../../config/loader.js';
|
|
18
|
+
import { getConfigJsonSchema } from '../../../config/schema.js';
|
|
19
|
+
import { renderCommandsSchema, renderTypesSchema, SCHEMA_SUBCOMMANDS, } from '../../../core/renderers/schema-renderer.js';
|
|
20
|
+
import { validatePath } from '../../../core/validators/input-validator.js';
|
|
21
|
+
/**
|
|
22
|
+
* schema 커맨드를 실행한다
|
|
23
|
+
*
|
|
24
|
+
* 하위 커맨드에 따라 설정 스키마, 커맨드 목록, 유닛 타입 정보를 반환.
|
|
25
|
+
* --output 옵션이 지정되면 결과를 파일로 저장한다.
|
|
26
|
+
*
|
|
27
|
+
* @param args - 파싱된 CLI 인자 (command === 'schema' 보장)
|
|
28
|
+
* @param projectRoot - 프로젝트 루트 절대 경로
|
|
29
|
+
* @returns SchemaResult 또는 에러
|
|
30
|
+
*/
|
|
31
|
+
export function handleSchema(args, projectRoot) {
|
|
32
|
+
if (args.command !== 'schema') {
|
|
33
|
+
return fail('INTERNAL_ERROR', 'handleSchema에 잘못된 커맨드가 전달되었습니다');
|
|
34
|
+
}
|
|
35
|
+
const { subcommand, output } = args;
|
|
36
|
+
if (!subcommand) {
|
|
37
|
+
return ok({
|
|
38
|
+
subcommand: 'help',
|
|
39
|
+
availableSubcommands: SCHEMA_SUBCOMMANDS,
|
|
40
|
+
usage: 'vc schema <config|commands|types> [--output <path>] [--json]',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
let result;
|
|
44
|
+
switch (subcommand) {
|
|
45
|
+
case 'config': {
|
|
46
|
+
const schema = getConfigJsonSchema();
|
|
47
|
+
result = { subcommand: 'config', schema };
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'commands': {
|
|
51
|
+
const commandsData = renderCommandsSchema();
|
|
52
|
+
result = {
|
|
53
|
+
subcommand: 'commands',
|
|
54
|
+
globalFlags: commandsData.globalFlags,
|
|
55
|
+
commands: commandsData.commands,
|
|
56
|
+
};
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case 'types': {
|
|
60
|
+
const configResult = loadConfig(projectRoot);
|
|
61
|
+
if (!configResult.success) {
|
|
62
|
+
return fail('CONFIG_NOT_FOUND', '설정 파일을 로드할 수 없습니다', `프로젝트 루트에 vibe-commander.config.json이 필요합니다. 'vc init'으로 생성하세요.\n${configResult.error.message}`);
|
|
63
|
+
}
|
|
64
|
+
const types = renderTypesSchema(configResult.data.unitTypes);
|
|
65
|
+
result = { subcommand: 'types', types };
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
return fail('UNKNOWN_SUBCOMMAND', `알 수 없는 schema 서브커맨드: ${String(subcommand)}`, '사용 가능한 서브커맨드: config, commands, types');
|
|
70
|
+
}
|
|
71
|
+
if (output) {
|
|
72
|
+
const pathCheck = validatePath(output);
|
|
73
|
+
if (!pathCheck.valid) {
|
|
74
|
+
return fail('INVALID_OUTPUT_PATH', pathCheck.error ?? '출력 경로가 유효하지 않습니다');
|
|
75
|
+
}
|
|
76
|
+
const outputPath = resolve(projectRoot, output);
|
|
77
|
+
const contentToWrite = extractContent(result);
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
80
|
+
writeFileSync(outputPath, JSON.stringify(contentToWrite, null, 2) + '\n', 'utf-8');
|
|
81
|
+
result = { ...result, outputPath };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return fail('OUTPUT_WRITE_ERROR', `결과를 파일에 저장할 수 없습니다: ${output}`, err instanceof Error ? err.message : String(err));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return ok(result);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* SchemaResult에서 파일 저장용 콘텐츠만 추출한다
|
|
91
|
+
*
|
|
92
|
+
* config → JSON Schema 객체, commands → 커맨드 메타데이터, types → 타입 배열
|
|
93
|
+
*/
|
|
94
|
+
function extractContent(result) {
|
|
95
|
+
switch (result.subcommand) {
|
|
96
|
+
case 'config':
|
|
97
|
+
return result.schema;
|
|
98
|
+
case 'commands':
|
|
99
|
+
return { globalFlags: result.globalFlags, commands: result.commands };
|
|
100
|
+
case 'types':
|
|
101
|
+
return result.types;
|
|
102
|
+
default:
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { writeFileSync } from 'node:fs';
|
|
14
14
|
import { ok, fail } from '../../../types/index.js';
|
|
15
|
+
import { validateUnitId } from '../../../core/validators/input-validator.js';
|
|
15
16
|
import { renderSection } from '../../../core/renderers/section-renderer.js';
|
|
16
17
|
import { updateSection } from '../../../core/renderers/section-updater.js';
|
|
17
18
|
import { resolveCommandContext } from './context-resolver.js';
|
|
18
|
-
import { readCommandsFile } from './io-helpers.js';
|
|
19
|
+
import { readCommandsFile, enrichWithSuggestions } from './io-helpers.js';
|
|
19
20
|
import { writeActiveUnitType } from './state-helpers.js';
|
|
20
21
|
import { isInteractive, promptAllQuestions } from './prompt-helpers.js';
|
|
21
22
|
/**
|
|
@@ -36,42 +37,50 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
36
37
|
if (args.command !== 'set-unit') {
|
|
37
38
|
return fail('INTERNAL_ERROR', 'handleSetUnit에 잘못된 커맨드가 전달되었습니다');
|
|
38
39
|
}
|
|
39
|
-
const { unitId, noPrompt } = args;
|
|
40
|
+
const { unitId, noPrompt, dryRun } = args;
|
|
40
41
|
const requestKeys = 'requestKeys' in args ? args.requestKeys : [];
|
|
42
|
+
// 0. 입력 안전성 검증 (제어 문자, 경로 순회, URL 인코딩 등)
|
|
43
|
+
const safetyCheck = validateUnitId(unitId);
|
|
44
|
+
if (!safetyCheck.valid) {
|
|
45
|
+
return fail('INVALID_INPUT', safetyCheck.error ?? '입력 검증 실패');
|
|
46
|
+
}
|
|
41
47
|
// 1. 전체 컨텍스트 해석 (I/O + 파싱 + 의존성 수집)
|
|
42
48
|
const resolveResult = resolveCommandContext(unitId, projectRoot, requestKeys);
|
|
43
|
-
if (!resolveResult.success)
|
|
44
|
-
return resolveResult;
|
|
49
|
+
if (!resolveResult.success) {
|
|
50
|
+
return enrichWithSuggestions(resolveResult, unitId, projectRoot);
|
|
51
|
+
}
|
|
45
52
|
const { context, config, unitTypeKey, unitTypeConfig, planRelPath, warnings } = resolveResult.data;
|
|
46
|
-
// 2. 미결정 페어링 질문 프롬프트 (인터랙티브
|
|
53
|
+
// 2. 미결정 페어링 질문 프롬프트 (인터랙티브 모드, dry-run 시 건너뜀)
|
|
47
54
|
let pairingAnswers = [];
|
|
48
55
|
const unresolvedCount = context.pairingQuestions.filter((q) => !q.resolved).length;
|
|
49
|
-
if (unresolvedCount > 0 && isInteractive(noPrompt)) {
|
|
56
|
+
if (unresolvedCount > 0 && !dryRun && isInteractive(noPrompt)) {
|
|
50
57
|
pairingAnswers = await promptAllQuestions(context.pairingQuestions);
|
|
51
58
|
context.pairingAnswers = pairingAnswers;
|
|
52
59
|
}
|
|
53
60
|
// 3. 섹션 렌더링 (Core 순수 함수)
|
|
54
61
|
const sectionBody = renderSection(context, unitTypeConfig);
|
|
55
|
-
// 4. 커맨드 파일 업데이트 (Adapter I/O)
|
|
62
|
+
// 4. 커맨드 파일 업데이트 (Adapter I/O) — dry-run 시 건너뜀
|
|
56
63
|
let commandsUpdated = false;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
if (!dryRun) {
|
|
65
|
+
const cmdFile = readCommandsFile(config, projectRoot);
|
|
66
|
+
if (cmdFile.success) {
|
|
67
|
+
const useMarkers = config.commandSections.useMarkers;
|
|
68
|
+
const markerOptions = useMarkers ? { sectionKey: unitTypeConfig.key } : undefined;
|
|
69
|
+
const updateResult = updateSection(cmdFile.data.content, unitTypeConfig.commandSection, sectionBody, config.commandSections.separator, markerOptions);
|
|
70
|
+
if (updateResult.success && updateResult.data.updated) {
|
|
71
|
+
writeFileSync(cmdFile.data.absPath, updateResult.data.content, 'utf-8');
|
|
72
|
+
commandsUpdated = true;
|
|
73
|
+
}
|
|
74
|
+
else if (updateResult.success && !updateResult.data.updated) {
|
|
75
|
+
warnings.push(`커맨드 파일에서 '${unitTypeConfig.commandSection}' 섹션을 찾을 수 없습니다`);
|
|
76
|
+
}
|
|
65
77
|
}
|
|
66
|
-
else
|
|
67
|
-
warnings.push(`커맨드
|
|
78
|
+
else {
|
|
79
|
+
warnings.push(`커맨드 파일을 읽을 수 없습니다: ${config.paths.commands}`);
|
|
68
80
|
}
|
|
81
|
+
// 5. 활성 유닛 타입 상태 기록 (update-commit 자동 감지용)
|
|
82
|
+
writeActiveUnitType(projectRoot, unitTypeKey);
|
|
69
83
|
}
|
|
70
|
-
else {
|
|
71
|
-
warnings.push(`커맨드 파일을 읽을 수 없습니다: ${config.paths.commands}`);
|
|
72
|
-
}
|
|
73
|
-
// 5. 활성 유닛 타입 상태 기록 (update-commit 자동 감지용)
|
|
74
|
-
writeActiveUnitType(projectRoot, unitTypeKey);
|
|
75
84
|
// 6. 결과 반환
|
|
76
85
|
return ok({
|
|
77
86
|
unitId,
|
|
@@ -85,6 +94,7 @@ export async function handleSetUnit(args, projectRoot) {
|
|
|
85
94
|
pairingAnswers,
|
|
86
95
|
commandsUpdated,
|
|
87
96
|
warnings,
|
|
97
|
+
...(dryRun && { dryRun: true, preview: sectionBody }),
|
|
88
98
|
});
|
|
89
99
|
}
|
|
90
100
|
//# sourceMappingURL=set-unit.js.map
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { writeFileSync } from 'node:fs';
|
|
13
13
|
import { ok, fail } from '../../../types/index.js';
|
|
14
|
+
import { validateSha } from '../../../core/validators/input-validator.js';
|
|
14
15
|
import { loadConfig } from '../../../config/loader.js';
|
|
15
16
|
import { updateField } from '../../../core/renderers/section-updater.js';
|
|
16
17
|
import { normalizeLineEndings, getHeadingPrefix, isSameLevelHeading, } from '../../../core/parsers/index.js';
|
|
@@ -66,6 +67,10 @@ export function handleUpdateCommit(args, projectRoot) {
|
|
|
66
67
|
if (!sha) {
|
|
67
68
|
return fail('MISSING_ARGUMENT', "'update-commit' manual 모드에 SHA가 필요합니다");
|
|
68
69
|
}
|
|
70
|
+
const shaCheck = validateSha(sha);
|
|
71
|
+
if (!shaCheck.valid) {
|
|
72
|
+
return fail('INVALID_INPUT', shaCheck.error ?? '입력 검증 실패');
|
|
73
|
+
}
|
|
69
74
|
break;
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -41,4 +41,8 @@ export declare function formatValidate(result: ToolResult<unknown>, json: boolea
|
|
|
41
41
|
* skill install 결과를 콘솔에 포맷팅하여 출력한다
|
|
42
42
|
*/
|
|
43
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;
|
|
44
48
|
//# sourceMappingURL=formatters-unit.d.ts.map
|
|
@@ -284,4 +284,97 @@ function isSkillInstallResult(data) {
|
|
|
284
284
|
'skipped' in data &&
|
|
285
285
|
'overwritten' in data);
|
|
286
286
|
}
|
|
287
|
+
// ── schema 포맷터 ──
|
|
288
|
+
/**
|
|
289
|
+
* schema 결과를 콘솔에 포맷팅하여 출력한다
|
|
290
|
+
*/
|
|
291
|
+
export function formatSchema(result, json) {
|
|
292
|
+
if (!result.success)
|
|
293
|
+
return;
|
|
294
|
+
if (json) {
|
|
295
|
+
console.log(JSON.stringify(result, null, 2));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const data = result.data;
|
|
299
|
+
if (!isSchemaResult(data))
|
|
300
|
+
return;
|
|
301
|
+
if ('outputPath' in data && data.outputPath) {
|
|
302
|
+
console.log(`\n${GREEN}✅ 스키마가 파일에 저장되었습니다: ${CYAN}${data.outputPath}${RESET}\n`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
switch (data.subcommand) {
|
|
306
|
+
case 'help':
|
|
307
|
+
formatSchemaHelp(data);
|
|
308
|
+
break;
|
|
309
|
+
case 'config':
|
|
310
|
+
console.log(JSON.stringify(data.schema, null, 2));
|
|
311
|
+
break;
|
|
312
|
+
case 'commands':
|
|
313
|
+
formatSchemaCommands(data);
|
|
314
|
+
break;
|
|
315
|
+
case 'types':
|
|
316
|
+
formatSchemaTypes(data);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function formatSchemaHelp(data) {
|
|
321
|
+
console.log('');
|
|
322
|
+
console.log(`${BOLD}vc schema${RESET} — 런타임 스키마 조회`);
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(`${YELLOW}사용 가능한 서브커맨드:${RESET}`);
|
|
325
|
+
for (const sub of data.availableSubcommands) {
|
|
326
|
+
console.log(` ${CYAN}${sub.name}${RESET} ${sub.description}`);
|
|
327
|
+
}
|
|
328
|
+
console.log('');
|
|
329
|
+
console.log(`${YELLOW}사용법:${RESET} ${data.usage}`);
|
|
330
|
+
console.log('');
|
|
331
|
+
}
|
|
332
|
+
function formatSchemaCommands(data) {
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(`${BOLD}📋 vibe-commander CLI 스키마${RESET}`);
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(`${YELLOW}글로벌 옵션:${RESET}`);
|
|
337
|
+
for (const flag of data.globalFlags) {
|
|
338
|
+
const alias = flag.alias ? `, ${CYAN}${flag.alias}${RESET}` : '';
|
|
339
|
+
console.log(` ${CYAN}${flag.name}${RESET}${alias} ${DIM}${flag.description}${RESET}`);
|
|
340
|
+
}
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log(`${YELLOW}명령어 (${String(data.commands.length)}개):${RESET}`);
|
|
343
|
+
for (const cmd of data.commands) {
|
|
344
|
+
const argStr = cmd.args.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(' ');
|
|
345
|
+
const nameWithArgs = argStr ? `${cmd.name} ${argStr}` : cmd.name;
|
|
346
|
+
console.log(` ${CYAN}${nameWithArgs}${RESET}`);
|
|
347
|
+
console.log(` ${cmd.description}`);
|
|
348
|
+
if (cmd.args.length > 0) {
|
|
349
|
+
for (const arg of cmd.args) {
|
|
350
|
+
const req = arg.required ? `${RED}필수${RESET}` : `${DIM}선택${RESET}`;
|
|
351
|
+
console.log(` ${DIM}인자:${RESET} ${arg.name} (${req}) — ${arg.description}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (cmd.flags.length > 0) {
|
|
355
|
+
const flagNames = cmd.flags.map((f) => f.name).join(', ');
|
|
356
|
+
console.log(` ${DIM}플래그:${RESET} ${flagNames}`);
|
|
357
|
+
}
|
|
358
|
+
console.log('');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function formatSchemaTypes(data) {
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log(`${BOLD}📋 프로젝트 유닛 타입 (${String(data.types.length)}개)${RESET}`);
|
|
364
|
+
console.log('');
|
|
365
|
+
for (const t of data.types) {
|
|
366
|
+
const displayName = t.displayName ? ` (${t.displayName})` : '';
|
|
367
|
+
console.log(` ${CYAN}${BOLD}${t.key}${RESET}${displayName}`);
|
|
368
|
+
console.log(` ${DIM}ID 패턴:${RESET} ${t.idPattern}`);
|
|
369
|
+
console.log(` ${DIM}계획서:${RESET} ${t.planDir}`);
|
|
370
|
+
console.log(` ${DIM}커맨드 섹션:${RESET} ${t.commandSection}`);
|
|
371
|
+
console.log(` ${DIM}의존성 수집:${RESET} ${t.collectDeps ? GREEN + '✅' : RED + '❌'}${RESET}`);
|
|
372
|
+
console.log(` ${DIM}헤더 템플릿:${RESET} ${t.headerTemplate}`);
|
|
373
|
+
console.log('');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** SchemaResult 런타임 타입 가드 */
|
|
377
|
+
function isSchemaResult(data) {
|
|
378
|
+
return data !== null && typeof data === 'object' && 'subcommand' in data;
|
|
379
|
+
}
|
|
287
380
|
//# sourceMappingURL=formatters-unit.js.map
|
|
@@ -22,7 +22,8 @@ import { handleContext } from './commands/context.js';
|
|
|
22
22
|
import { handleInit } from './commands/init.js';
|
|
23
23
|
import { handleValidate } from './commands/validate.js';
|
|
24
24
|
import { handleSkillInstall } from './commands/skill-install.js';
|
|
25
|
-
import {
|
|
25
|
+
import { handleSchema } from './commands/schema.js';
|
|
26
|
+
import { formatContext, formatSetUnit, formatInit, formatValidate, formatSkillInstall, formatSchema, } from './formatters-unit.js';
|
|
26
27
|
import { formatListUnits, formatUpdateCommit } from './formatters-list.js';
|
|
27
28
|
const handlers = {
|
|
28
29
|
init: handleInit,
|
|
@@ -32,6 +33,7 @@ const handlers = {
|
|
|
32
33
|
context: handleContext,
|
|
33
34
|
validate: handleValidate,
|
|
34
35
|
'skill-install': handleSkillInstall,
|
|
36
|
+
schema: handleSchema,
|
|
35
37
|
};
|
|
36
38
|
const formatters = {
|
|
37
39
|
init: formatInit,
|
|
@@ -41,6 +43,7 @@ const formatters = {
|
|
|
41
43
|
context: formatContext,
|
|
42
44
|
validate: formatValidate,
|
|
43
45
|
'skill-install': formatSkillInstall,
|
|
46
|
+
schema: formatSchema,
|
|
44
47
|
};
|
|
45
48
|
/**
|
|
46
49
|
* CLI 메인 실행 함수
|