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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/cli/commands/context-resolver.d.ts +29 -0
  3. package/dist/adapters/cli/commands/context-resolver.js +93 -0
  4. package/dist/adapters/cli/commands/context.d.ts +26 -0
  5. package/dist/adapters/cli/commands/context.js +39 -0
  6. package/dist/adapters/cli/commands/git-helpers.d.ts +43 -0
  7. package/dist/adapters/cli/commands/git-helpers.js +99 -0
  8. package/dist/adapters/cli/commands/init-helpers.d.ts +30 -0
  9. package/dist/adapters/cli/commands/init-helpers.js +112 -0
  10. package/dist/adapters/cli/commands/init-interactive.d.ts +25 -0
  11. package/dist/adapters/cli/commands/init-interactive.js +143 -0
  12. package/dist/adapters/cli/commands/init.d.ts +25 -0
  13. package/dist/adapters/cli/commands/init.js +73 -0
  14. package/dist/adapters/cli/commands/io-helpers.d.ts +45 -0
  15. package/dist/adapters/cli/commands/io-helpers.js +144 -0
  16. package/dist/adapters/cli/commands/list-units.d.ts +39 -0
  17. package/dist/adapters/cli/commands/list-units.js +92 -0
  18. package/dist/adapters/cli/commands/prompt-helpers.d.ts +36 -0
  19. package/dist/adapters/cli/commands/prompt-helpers.js +113 -0
  20. package/dist/adapters/cli/commands/set-unit.d.ts +44 -0
  21. package/dist/adapters/cli/commands/set-unit.js +90 -0
  22. package/dist/adapters/cli/commands/skill-install.d.ts +31 -0
  23. package/dist/adapters/cli/commands/skill-install.js +85 -0
  24. package/dist/adapters/cli/commands/state-helpers.d.ts +28 -0
  25. package/dist/adapters/cli/commands/state-helpers.js +55 -0
  26. package/dist/adapters/cli/commands/update-commit.d.ts +42 -0
  27. package/dist/adapters/cli/commands/update-commit.js +180 -0
  28. package/dist/adapters/cli/commands/validate.d.ts +40 -0
  29. package/dist/adapters/cli/commands/validate.js +219 -0
  30. package/dist/adapters/cli/formatters-list.d.ts +36 -0
  31. package/dist/adapters/cli/formatters-list.js +106 -0
  32. package/dist/adapters/cli/formatters-unit.d.ts +44 -0
  33. package/dist/adapters/cli/formatters-unit.js +287 -0
  34. package/dist/adapters/cli/formatters.d.ts +2 -0
  35. package/dist/adapters/cli/formatters.js +3 -0
  36. package/dist/adapters/cli/index.d.ts +17 -0
  37. package/dist/adapters/cli/index.js +60 -0
  38. package/dist/adapters/cli/output.d.ts +58 -0
  39. package/dist/adapters/cli/output.js +159 -0
  40. package/dist/adapters/cli/router.d.ts +99 -0
  41. package/dist/adapters/cli/router.js +419 -0
  42. package/dist/adapters/cli/scanner.d.ts +19 -0
  43. package/dist/adapters/cli/scanner.js +227 -0
  44. package/dist/adapters/pipe/stdin-reader.d.ts +23 -0
  45. package/dist/adapters/pipe/stdin-reader.js +56 -0
  46. package/dist/config/loader.d.ts +27 -0
  47. package/dist/config/loader.js +84 -0
  48. package/dist/config/resolver.d.ts +71 -0
  49. package/dist/config/resolver.js +124 -0
  50. package/dist/config/schema.d.ts +205 -0
  51. package/dist/config/schema.js +186 -0
  52. package/dist/core/parsers/backlog-parser.d.ts +25 -0
  53. package/dist/core/parsers/backlog-parser.js +255 -0
  54. package/dist/core/parsers/dep-line-parser.d.ts +36 -0
  55. package/dist/core/parsers/dep-line-parser.js +176 -0
  56. package/dist/core/parsers/dependency-extractor.d.ts +64 -0
  57. package/dist/core/parsers/dependency-extractor.js +193 -0
  58. package/dist/core/parsers/index.d.ts +20 -0
  59. package/dist/core/parsers/index.js +20 -0
  60. package/dist/core/parsers/md-utils.d.ts +81 -0
  61. package/dist/core/parsers/md-utils.js +144 -0
  62. package/dist/core/parsers/metadata-parser.d.ts +49 -0
  63. package/dist/core/parsers/metadata-parser.js +133 -0
  64. package/dist/core/parsers/pairing-question-extractor.d.ts +42 -0
  65. package/dist/core/parsers/pairing-question-extractor.js +146 -0
  66. package/dist/core/parsers/plan-parser-helpers.d.ts +61 -0
  67. package/dist/core/parsers/plan-parser-helpers.js +90 -0
  68. package/dist/core/parsers/plan-parser.d.ts +55 -0
  69. package/dist/core/parsers/plan-parser.js +69 -0
  70. package/dist/core/parsers/title-extractor.d.ts +36 -0
  71. package/dist/core/parsers/title-extractor.js +101 -0
  72. package/dist/core/renderers/index.d.ts +15 -0
  73. package/dist/core/renderers/index.js +18 -0
  74. package/dist/core/renderers/interpolate.d.ts +92 -0
  75. package/dist/core/renderers/interpolate.js +117 -0
  76. package/dist/core/renderers/marker-utils.d.ts +60 -0
  77. package/dist/core/renderers/marker-utils.js +85 -0
  78. package/dist/core/renderers/section-renderer.d.ts +31 -0
  79. package/dist/core/renderers/section-renderer.js +171 -0
  80. package/dist/core/renderers/section-updater.d.ts +72 -0
  81. package/dist/core/renderers/section-updater.js +175 -0
  82. package/dist/core/renderers/template-engine.d.ts +69 -0
  83. package/dist/core/renderers/template-engine.js +92 -0
  84. package/dist/core/renderers/updater-helpers.d.ts +45 -0
  85. package/dist/core/renderers/updater-helpers.js +83 -0
  86. package/dist/core/resolvers/commit-filter.d.ts +84 -0
  87. package/dist/core/resolvers/commit-filter.js +95 -0
  88. package/dist/core/resolvers/config-generator.d.ts +32 -0
  89. package/dist/core/resolvers/config-generator.js +112 -0
  90. package/dist/core/resolvers/config-merger.d.ts +26 -0
  91. package/dist/core/resolvers/config-merger.js +89 -0
  92. package/dist/core/resolvers/config-validator.d.ts +42 -0
  93. package/dist/core/resolvers/config-validator.js +61 -0
  94. package/dist/core/resolvers/dep-commit-resolver.d.ts +63 -0
  95. package/dist/core/resolvers/dep-commit-resolver.js +158 -0
  96. package/dist/core/resolvers/dep-doc-resolver.d.ts +70 -0
  97. package/dist/core/resolvers/dep-doc-resolver.js +84 -0
  98. package/dist/core/resolvers/index.d.ts +15 -0
  99. package/dist/core/resolvers/index.js +12 -0
  100. package/dist/index.d.ts +8 -0
  101. package/dist/index.js +8 -0
  102. package/dist/types/config.d.ts +10 -0
  103. package/dist/types/config.js +10 -0
  104. package/dist/types/context.d.ts +55 -0
  105. package/dist/types/context.js +10 -0
  106. package/dist/types/index.d.ts +15 -0
  107. package/dist/types/index.js +10 -0
  108. package/dist/types/init.d.ts +56 -0
  109. package/dist/types/init.js +10 -0
  110. package/dist/types/tool-result.d.ts +75 -0
  111. package/dist/types/tool-result.js +40 -0
  112. package/dist/types/unit.d.ts +118 -0
  113. package/dist/types/unit.js +10 -0
  114. package/package.json +71 -0
  115. package/skills/claude/SKILL.md +375 -0
  116. package/skills/common/cli-reference.md +251 -0
  117. package/skills/cursor/SKILL.md +353 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * init 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * 대화형 질문을 통해 프로젝트 설정 파일(vibe-commander.config.json)을 생성한다.
5
+ * 모드 분기(대화형/stdin/from-existing)를 담당하는 진입점 파일.
6
+ *
7
+ * 파이프라인:
8
+ * 1. 기존 설정 파일 존재 확인 → 덮어쓰기 확인
9
+ * 2. 대화형 질문으로 최소 설정 정보 수집
10
+ * 3. 수집된 정보 → ProjectConfig 구조 구성
11
+ * 4. Zod 스키마 검증 → JSON 직렬화 → 파일 쓰기
12
+ *
13
+ * @module
14
+ */
15
+ import type { ToolResult, InitResult } from '../../../types/index.js';
16
+ import type { ParsedArgs } from '../router.js';
17
+ /**
18
+ * init 커맨드를 실행한다
19
+ *
20
+ * @param args - 파싱된 CLI 인자 (command === 'init' 보장)
21
+ * @param projectRoot - 프로젝트 루트 절대 경로
22
+ * @returns 실행 결과 또는 에러
23
+ */
24
+ export declare function handleInit(args: ParsedArgs, projectRoot: string): Promise<ToolResult<InitResult>>;
25
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * init 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * 대화형 질문을 통해 프로젝트 설정 파일(vibe-commander.config.json)을 생성한다.
5
+ * 모드 분기(대화형/stdin/from-existing)를 담당하는 진입점 파일.
6
+ *
7
+ * 파이프라인:
8
+ * 1. 기존 설정 파일 존재 확인 → 덮어쓰기 확인
9
+ * 2. 대화형 질문으로 최소 설정 정보 수집
10
+ * 3. 수집된 정보 → ProjectConfig 구조 구성
11
+ * 4. Zod 스키마 검증 → JSON 직렬화 → 파일 쓰기
12
+ *
13
+ * @module
14
+ */
15
+ import { existsSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { fail } from '../../../types/index.js';
18
+ import { CONFIG_FILENAME } from '../../../config/schema.js';
19
+ import { scanProject } from '../scanner.js';
20
+ import { handleInteractive, handleFromExistingInteractive } from './init-interactive.js';
21
+ import { extractStdinAnswers, buildAnswersFromScan, buildAndWriteConfig, readExistingConfig, } from './init-helpers.js';
22
+ /**
23
+ * init 커맨드를 실행한다
24
+ *
25
+ * @param args - 파싱된 CLI 인자 (command === 'init' 보장)
26
+ * @param projectRoot - 프로젝트 루트 절대 경로
27
+ * @returns 실행 결과 또는 에러
28
+ */
29
+ export async function handleInit(args, projectRoot) {
30
+ if (args.command !== 'init') {
31
+ return fail('INTERNAL_ERROR', 'handleInit에 잘못된 커맨드가 전달되었습니다');
32
+ }
33
+ const configPath = join(projectRoot, CONFIG_FILENAME);
34
+ const alreadyExists = existsSync(configPath);
35
+ if (args.fromExisting) {
36
+ return handleFromExisting(args, projectRoot, configPath, alreadyExists);
37
+ }
38
+ if (args.stdinData) {
39
+ const answers = extractStdinAnswers(args.stdinData);
40
+ return buildAndWriteConfig(answers, configPath, alreadyExists);
41
+ }
42
+ if (!process.stdin.isTTY) {
43
+ return fail('NOT_INTERACTIVE', '대화형 모드는 TTY 환경에서만 사용 가능합니다', '--stdin 옵션으로 JSON 입력을 사용하세요: echo \'{"commandsPath":"..."}\' | vc init --stdin');
44
+ }
45
+ return handleInteractive(configPath, alreadyExists);
46
+ }
47
+ /**
48
+ * --from-existing 모드 — 프로젝트를 스캔하여 패턴을 감지하고 설정을 생성한다
49
+ *
50
+ * 기존 config가 존재하면:
51
+ * - stdin/비TTY: --force 없으면 병합 모드 기본 적용
52
+ * - TTY: 병합/덮어쓰기/취소 3지선다 프롬프트
53
+ */
54
+ async function handleFromExisting(args, projectRoot, configPath, alreadyExists) {
55
+ const scanResult = scanProject(projectRoot);
56
+ if (scanResult.idPatterns.length === 0) {
57
+ return fail('SCAN_NO_PATTERNS', 'ID 패턴을 감지하지 못했습니다', `${String(scanResult.totalMdFiles)}개 .md 파일을 스캔했지만 ID 패턴이 발견되지 않았습니다. 'vc init'으로 수동 설정을 시도하세요.`);
58
+ }
59
+ const existingConfig = alreadyExists ? readExistingConfig(configPath) : null;
60
+ const force = args.force;
61
+ if (args.stdinData) {
62
+ const answers = buildAnswersFromScan(scanResult, args.stdinData);
63
+ const mergeWith = !force && existingConfig ? existingConfig : undefined;
64
+ return buildAndWriteConfig(answers, configPath, alreadyExists, scanResult, mergeWith);
65
+ }
66
+ if (!process.stdin.isTTY) {
67
+ const answers = buildAnswersFromScan(scanResult);
68
+ const mergeWith = !force && existingConfig ? existingConfig : undefined;
69
+ return buildAndWriteConfig(answers, configPath, alreadyExists, scanResult, mergeWith);
70
+ }
71
+ return handleFromExistingInteractive(scanResult, configPath, alreadyExists, existingConfig);
72
+ }
73
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CLI 공통 I/O 헬퍼 — Layer 3 (Adapter)
3
+ *
4
+ * 커맨드 파일 읽기, 의존 문서 파일 확인, Git 커밋 수집 등
5
+ * CLI 커맨드 핸들러에서 공통으로 사용하는 I/O 래퍼를 제공한다.
6
+ *
7
+ * @module
8
+ */
9
+ import type { ToolResult, DepDocs, ProjectConfig, CommitSearchConfig } from '../../../types/index.js';
10
+ import type { DepDocPattern } from '../../../core/resolvers/dep-doc-resolver.js';
11
+ /**
12
+ * 커맨드 파일을 읽어 반환한다
13
+ */
14
+ export declare function readCommandsFile(config: ProjectConfig, projectRoot: string): ToolResult<{
15
+ content: string;
16
+ absPath: string;
17
+ relPath: string;
18
+ }>;
19
+ /**
20
+ * DepDocPattern[]에서 실제 존재하는 파일만 필터링하여 DepDocs[]로 변환
21
+ */
22
+ export declare function resolveActualDocs(patterns: DepDocPattern[], projectRoot: string): DepDocs[];
23
+ /**
24
+ * Git 로그에서 의존 유닛 관련 커밋 SHA를 수집하고 교차 검증 필터를 적용한다
25
+ *
26
+ * 1. `git log --pretty=oneline`으로 커밋 메시지 검색 (findDepCommits)
27
+ * 2. excludePaths/requirePaths 설정이 있으면 각 SHA의 변경 파일을 조회 (getChangedFiles)
28
+ * 3. filterCommitsByChangedFiles로 필터링 (Option C: 독립 순차 적용)
29
+ *
30
+ * excludePaths/requirePaths가 모두 빈 배열이면 git diff-tree 호출을 건너뜀 (기존 동작 유지).
31
+ */
32
+ export declare function collectGitCommits(depIds: string[], commitSearch: CommitSearchConfig, projectRoot: string): string[];
33
+ /** buildSpecialRequests 반환 타입 */
34
+ export interface BuildSpecialRequestsResult {
35
+ requests: string[];
36
+ warnings: string[];
37
+ }
38
+ /**
39
+ * 기본 + 유닛 유형별 + 커스텀 키 선택 특별 요청사항을 합산
40
+ *
41
+ * 병합 순서: defaultSpecialRequests → specialRequestsByType[key] → customRequests[선택된 키들]
42
+ * 존재하지 않는 키는 warnings에 추가하고 나머지는 정상 처리 (Graceful Degradation)
43
+ */
44
+ export declare function buildSpecialRequests(config: ProjectConfig, unitTypeKey: string, requestKeys?: string[]): BuildSpecialRequestsResult;
45
+ //# sourceMappingURL=io-helpers.d.ts.map
@@ -0,0 +1,144 @@
1
+ /**
2
+ * CLI 공통 I/O 헬퍼 — Layer 3 (Adapter)
3
+ *
4
+ * 커맨드 파일 읽기, 의존 문서 파일 확인, Git 커밋 수집 등
5
+ * CLI 커맨드 핸들러에서 공통으로 사용하는 I/O 래퍼를 제공한다.
6
+ *
7
+ * @module
8
+ */
9
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
10
+ import { execSync } from 'node:child_process';
11
+ import { join } from 'node:path';
12
+ import { ok, fail } from '../../../types/index.js';
13
+ import { findDepCommits } from '../../../core/resolvers/dep-commit-resolver.js';
14
+ import { filterCommitsByChangedFiles } from '../../../core/resolvers/commit-filter.js';
15
+ import { getChangedFiles } from './git-helpers.js';
16
+ /**
17
+ * 커맨드 파일을 읽어 반환한다
18
+ */
19
+ export function readCommandsFile(config, projectRoot) {
20
+ const relPath = config.paths.commands;
21
+ const absPath = join(projectRoot, relPath);
22
+ try {
23
+ const content = readFileSync(absPath, 'utf-8');
24
+ return ok({ content, absPath, relPath });
25
+ }
26
+ catch {
27
+ return fail('COMMANDS_NOT_FOUND', `커맨드 파일을 찾을 수 없습니다: ${relPath}`, `절대 경로: ${absPath}`);
28
+ }
29
+ }
30
+ /**
31
+ * DepDocPattern[]에서 실제 존재하는 파일만 필터링하여 DepDocs[]로 변환
32
+ */
33
+ export function resolveActualDocs(patterns, projectRoot) {
34
+ const result = [];
35
+ for (const pattern of patterns) {
36
+ const found = [];
37
+ for (const loc of pattern.locations) {
38
+ if (loc.glob) {
39
+ const dirPath = join(projectRoot, loc.dir);
40
+ try {
41
+ if (existsSync(dirPath)) {
42
+ const files = readdirSync(dirPath);
43
+ const regex = globToRegex(loc.filePattern);
44
+ for (const file of files) {
45
+ if (regex.test(file)) {
46
+ found.push({ role: loc.role, path: `${loc.dir}/${file}` });
47
+ }
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // 디렉토리 읽기 실패 시 무시
53
+ }
54
+ }
55
+ else {
56
+ const fullPath = join(projectRoot, loc.fullPattern);
57
+ if (existsSync(fullPath)) {
58
+ found.push({ role: loc.role, path: loc.fullPattern });
59
+ }
60
+ }
61
+ }
62
+ result.push({ unitId: pattern.unitId, found });
63
+ }
64
+ return result;
65
+ }
66
+ /**
67
+ * Git 로그에서 의존 유닛 관련 커밋 SHA를 수집하고 교차 검증 필터를 적용한다
68
+ *
69
+ * 1. `git log --pretty=oneline`으로 커밋 메시지 검색 (findDepCommits)
70
+ * 2. excludePaths/requirePaths 설정이 있으면 각 SHA의 변경 파일을 조회 (getChangedFiles)
71
+ * 3. filterCommitsByChangedFiles로 필터링 (Option C: 독립 순차 적용)
72
+ *
73
+ * excludePaths/requirePaths가 모두 빈 배열이면 git diff-tree 호출을 건너뜀 (기존 동작 유지).
74
+ */
75
+ export function collectGitCommits(depIds, commitSearch, projectRoot) {
76
+ if (commitSearch.strategy === 'disabled')
77
+ return [];
78
+ try {
79
+ const output = execSync('git log --pretty=oneline -200', {
80
+ cwd: projectRoot,
81
+ encoding: 'utf-8',
82
+ timeout: 10000,
83
+ stdio: ['pipe', 'pipe', 'pipe'],
84
+ });
85
+ const result = findDepCommits(depIds, output, commitSearch);
86
+ if (!result.success)
87
+ return [];
88
+ const shas = result.data.map((c) => c.sha);
89
+ // excludePaths/requirePaths 모두 빈 배열이면 필터 건너뜀 (기존 동작 유지)
90
+ const needsFilter = commitSearch.excludePaths.length > 0 || commitSearch.requirePaths.length > 0;
91
+ if (!needsFilter)
92
+ return shas;
93
+ // 교차 검증: 각 SHA에 대해 변경 파일 수집
94
+ const commitsWithFiles = shas.map((sha) => {
95
+ const filesResult = getChangedFiles(projectRoot, sha);
96
+ return {
97
+ sha,
98
+ changedFiles: filesResult.success ? filesResult.data : [],
99
+ };
100
+ });
101
+ // 필터링 적용 (Option C — 독립 순차)
102
+ const filtered = filterCommitsByChangedFiles(commitsWithFiles, {
103
+ excludePaths: commitSearch.excludePaths,
104
+ requirePaths: commitSearch.requirePaths,
105
+ });
106
+ return filtered.map((c) => c.sha);
107
+ }
108
+ catch {
109
+ return [];
110
+ }
111
+ }
112
+ /**
113
+ * 기본 + 유닛 유형별 + 커스텀 키 선택 특별 요청사항을 합산
114
+ *
115
+ * 병합 순서: defaultSpecialRequests → specialRequestsByType[key] → customRequests[선택된 키들]
116
+ * 존재하지 않는 키는 warnings에 추가하고 나머지는 정상 처리 (Graceful Degradation)
117
+ */
118
+ export function buildSpecialRequests(config, unitTypeKey, requestKeys) {
119
+ const defaults = config.defaultSpecialRequests;
120
+ const typeSpecific = config.specialRequestsByType[unitTypeKey] ?? [];
121
+ const requests = [...defaults, ...typeSpecific];
122
+ const warnings = [];
123
+ if (requestKeys && requestKeys.length > 0) {
124
+ const customRequests = config.customRequests;
125
+ for (const key of requestKeys) {
126
+ const value = customRequests[key];
127
+ if (value !== undefined) {
128
+ requests.push(value);
129
+ }
130
+ else {
131
+ warnings.push(`커스텀 요청사항 키 '${key}'를 찾을 수 없습니다 (무시됨)`);
132
+ }
133
+ }
134
+ }
135
+ return { requests, warnings };
136
+ }
137
+ /**
138
+ * 간단한 glob 패턴(* 와일드카드)을 정규식으로 변환
139
+ */
140
+ function globToRegex(pattern) {
141
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
142
+ return new RegExp(`^${escaped}$`);
143
+ }
144
+ //# sourceMappingURL=io-helpers.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * list-units 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * 로드맵 백로그를 파싱하여 미완료 유닛 목록을
5
+ * ready/inProgress/blocked로 분류하여 출력한다.
6
+ *
7
+ * Core 순수 함수(parseBacklog)를 호출하며,
8
+ * I/O(파일 읽기)는 이 Adapter에서만 수행.
9
+ *
10
+ * @module
11
+ */
12
+ import type { ToolResult, BacklogEntry } from '../../../types/index.js';
13
+ import type { ParsedArgs } from '../router.js';
14
+ /** list-units 실행 결과 */
15
+ export interface ListUnitsResult {
16
+ total: number;
17
+ inProgress: BacklogEntry[];
18
+ ready: BacklogEntry[];
19
+ blocked: BacklogEntry[];
20
+ phase?: string;
21
+ }
22
+ /**
23
+ * list-units 커맨드를 실행한다
24
+ *
25
+ * 파이프라인:
26
+ * 1. 설정 로드 (loadConfig)
27
+ * 2. 로드맵 경로 확인 — null이면 에러 반환
28
+ * 3. 로드맵 파일 읽기 (Adapter I/O)
29
+ * 4. 백로그 파싱 (parseBacklog)
30
+ * 5. completed 제외, phase 필터링
31
+ * 6. 상태별 분류 (inProgress/ready/blocked)
32
+ * 7. 결과 반환
33
+ *
34
+ * @param args - 파싱된 CLI 인자 (command === 'list-units' 보장)
35
+ * @param projectRoot - 프로젝트 루트 절대 경로
36
+ * @returns 실행 결과 또는 에러
37
+ */
38
+ export declare function handleListUnits(args: ParsedArgs, projectRoot: string): ToolResult<ListUnitsResult>;
39
+ //# sourceMappingURL=list-units.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * list-units 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * 로드맵 백로그를 파싱하여 미완료 유닛 목록을
5
+ * ready/inProgress/blocked로 분류하여 출력한다.
6
+ *
7
+ * Core 순수 함수(parseBacklog)를 호출하며,
8
+ * I/O(파일 읽기)는 이 Adapter에서만 수행.
9
+ *
10
+ * @module
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { ok, fail } from '../../../types/index.js';
15
+ import { loadConfig } from '../../../config/loader.js';
16
+ import { parseBacklog } from '../../../core/parsers/backlog-parser.js';
17
+ /**
18
+ * list-units 커맨드를 실행한다
19
+ *
20
+ * 파이프라인:
21
+ * 1. 설정 로드 (loadConfig)
22
+ * 2. 로드맵 경로 확인 — null이면 에러 반환
23
+ * 3. 로드맵 파일 읽기 (Adapter I/O)
24
+ * 4. 백로그 파싱 (parseBacklog)
25
+ * 5. completed 제외, phase 필터링
26
+ * 6. 상태별 분류 (inProgress/ready/blocked)
27
+ * 7. 결과 반환
28
+ *
29
+ * @param args - 파싱된 CLI 인자 (command === 'list-units' 보장)
30
+ * @param projectRoot - 프로젝트 루트 절대 경로
31
+ * @returns 실행 결과 또는 에러
32
+ */
33
+ export function handleListUnits(args, projectRoot) {
34
+ if (args.command !== 'list-units') {
35
+ return fail('INTERNAL_ERROR', 'handleListUnits에 잘못된 커맨드가 전달되었습니다');
36
+ }
37
+ const { phase } = args;
38
+ // 1. 설정 로드
39
+ const configResult = loadConfig(projectRoot);
40
+ if (!configResult.success)
41
+ return configResult;
42
+ const config = configResult.data;
43
+ // 2. 로드맵 경로 확인
44
+ if (!config.paths.roadmap) {
45
+ return fail('ROADMAP_NOT_CONFIGURED', '로드맵 파일이 설정되지 않았습니다', 'vibe-commander.config.json의 paths.roadmap을 설정하세요');
46
+ }
47
+ // 3. 로드맵 파일 읽기 (Adapter I/O)
48
+ const roadmapAbsPath = join(projectRoot, config.paths.roadmap);
49
+ let roadmapContent;
50
+ try {
51
+ roadmapContent = readFileSync(roadmapAbsPath, 'utf-8');
52
+ }
53
+ catch {
54
+ return fail('ROADMAP_NOT_FOUND', `로드맵 파일을 찾을 수 없습니다: ${config.paths.roadmap}`, `절대 경로: ${roadmapAbsPath}`);
55
+ }
56
+ // 4. 백로그 파싱 (Core 순수 함수)
57
+ const parseResult = parseBacklog(roadmapContent, config.backlogParsing);
58
+ if (!parseResult.success)
59
+ return parseResult;
60
+ // 5. 활성 항목만 필터 (completed 제외)
61
+ let entries = parseResult.data.filter((e) => e.status !== 'completed');
62
+ // 6. phase 필터링 (대소문자 무시)
63
+ if (phase) {
64
+ const normalizedPhase = phase.toLowerCase();
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;
91
+ }
92
+ //# sourceMappingURL=list-units.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * CLI 인터랙티브 프롬프트 유틸리티 — Layer 3 (Adapter)
3
+ *
4
+ * 페어링 질문의 미결정 항목을 CLI에서 즉석으로 답변받는 기능을 제공한다.
5
+ * Node.js 내장 readline/promises 모듈을 사용하여 외부 의존성 추가 없음
6
+ * (RULE-003 준수).
7
+ *
8
+ * readline 인터페이스는 사용 후 반드시 close()하여 이벤트 루프를 정리한다.
9
+ *
10
+ * @module
11
+ */
12
+ import type { PairingQuestion, PairingAnswer } from '../../../types/index.js';
13
+ /**
14
+ * 현재 환경이 인터랙티브 프롬프트를 지원하는지 판별한다
15
+ *
16
+ * 다음 조건 중 하나라도 해당되면 비대화형으로 판정:
17
+ * - `--no-prompt` 플래그가 활성화됨
18
+ * - stdin이 TTY가 아님 (파이프, CI 환경 등)
19
+ *
20
+ * @param noPrompt - CLI `--no-prompt` 옵션 값
21
+ * @returns 대화형 프롬프트 가능 여부
22
+ */
23
+ export declare function isInteractive(noPrompt: boolean): boolean;
24
+ /**
25
+ * 미결정 페어링 질문들에 대해 CLI 프롬프트를 실행한다
26
+ *
27
+ * 미결정(resolved=false) 질문만 추출하여 순서대로 프롬프트를 표시한다.
28
+ * - 옵션이 있는 질문: 번호 선택 프롬프트
29
+ * - 옵션이 없는 질문: 자유 텍스트 입력
30
+ * - 각 질문마다 건너뛰기(Skip) 가능
31
+ *
32
+ * @param questions - 전체 페어링 질문 목록 (resolved/unresolved 모두)
33
+ * @returns 사용자가 답변한 PairingAnswer 배열 (건너뛴 질문은 제외)
34
+ */
35
+ export declare function promptAllQuestions(questions: PairingQuestion[]): Promise<PairingAnswer[]>;
36
+ //# sourceMappingURL=prompt-helpers.d.ts.map
@@ -0,0 +1,113 @@
1
+ /**
2
+ * CLI 인터랙티브 프롬프트 유틸리티 — Layer 3 (Adapter)
3
+ *
4
+ * 페어링 질문의 미결정 항목을 CLI에서 즉석으로 답변받는 기능을 제공한다.
5
+ * Node.js 내장 readline/promises 모듈을 사용하여 외부 의존성 추가 없음
6
+ * (RULE-003 준수).
7
+ *
8
+ * readline 인터페이스는 사용 후 반드시 close()하여 이벤트 루프를 정리한다.
9
+ *
10
+ * @module
11
+ */
12
+ import { createInterface } from 'node:readline/promises';
13
+ import { stdin, stdout } from 'node:process';
14
+ import { CYAN, YELLOW, GREEN, DIM, RESET, BOLD } from '../output.js';
15
+ /**
16
+ * 현재 환경이 인터랙티브 프롬프트를 지원하는지 판별한다
17
+ *
18
+ * 다음 조건 중 하나라도 해당되면 비대화형으로 판정:
19
+ * - `--no-prompt` 플래그가 활성화됨
20
+ * - stdin이 TTY가 아님 (파이프, CI 환경 등)
21
+ *
22
+ * @param noPrompt - CLI `--no-prompt` 옵션 값
23
+ * @returns 대화형 프롬프트 가능 여부
24
+ */
25
+ export function isInteractive(noPrompt) {
26
+ if (noPrompt)
27
+ return false;
28
+ return process.stdin.isTTY;
29
+ }
30
+ /**
31
+ * 미결정 페어링 질문들에 대해 CLI 프롬프트를 실행한다
32
+ *
33
+ * 미결정(resolved=false) 질문만 추출하여 순서대로 프롬프트를 표시한다.
34
+ * - 옵션이 있는 질문: 번호 선택 프롬프트
35
+ * - 옵션이 없는 질문: 자유 텍스트 입력
36
+ * - 각 질문마다 건너뛰기(Skip) 가능
37
+ *
38
+ * @param questions - 전체 페어링 질문 목록 (resolved/unresolved 모두)
39
+ * @returns 사용자가 답변한 PairingAnswer 배열 (건너뛴 질문은 제외)
40
+ */
41
+ export async function promptAllQuestions(questions) {
42
+ const unresolved = questions.filter((q) => !q.resolved);
43
+ if (unresolved.length === 0)
44
+ return [];
45
+ console.log('');
46
+ console.log(`${BOLD}🔍 미결정 페어링 질문이 ${String(unresolved.length)}개 발견되었습니다.${RESET}`);
47
+ const rl = createInterface({ input: stdin, output: stdout });
48
+ const answers = [];
49
+ try {
50
+ for (const question of unresolved) {
51
+ const answer = await promptSingleQuestion(rl, question);
52
+ if (answer)
53
+ answers.push(answer);
54
+ }
55
+ }
56
+ catch {
57
+ // Ctrl+C 또는 스트림 종료 시 수집된 답변까지만 반환
58
+ }
59
+ finally {
60
+ rl.close();
61
+ }
62
+ if (answers.length > 0) {
63
+ console.log('');
64
+ console.log(`${GREEN}✅ ${String(answers.length)}개 질문에 답변 완료${RESET}`);
65
+ }
66
+ return answers;
67
+ }
68
+ /**
69
+ * 단일 페어링 질문에 대한 프롬프트를 실행한다
70
+ *
71
+ * @param rl - readline 인터페이스 (외부에서 생성, 수명 관리)
72
+ * @param question - 프롬프트할 페어링 질문
73
+ * @returns 답변 또는 null (건너뛰기)
74
+ */
75
+ async function promptSingleQuestion(rl, question) {
76
+ const options = question.options ?? [];
77
+ console.log('');
78
+ console.log(`${BOLD}❓ ${question.id}${RESET}: ${question.text}`);
79
+ if (options.length > 0) {
80
+ return promptWithOptions(rl, question.id, options);
81
+ }
82
+ return promptFreeText(rl, question.id);
83
+ }
84
+ /**
85
+ * 옵션 선택 프롬프트 (번호 입력)
86
+ */
87
+ async function promptWithOptions(rl, questionId, options) {
88
+ for (let i = 0; i < options.length; i++) {
89
+ console.log(` ${CYAN}[${String(i + 1)}]${RESET} ${options[i] ?? ''}`);
90
+ }
91
+ console.log(` ${DIM}[S] 건너뛰기${RESET}`);
92
+ const answer = await rl.question(`${YELLOW}선택: ${RESET}`);
93
+ const trimmed = answer.trim().toLowerCase();
94
+ if (trimmed === 's' || trimmed === '')
95
+ return null;
96
+ const idx = parseInt(trimmed, 10) - 1;
97
+ if (idx >= 0 && idx < options.length) {
98
+ return { questionId, selectedOption: options[idx] };
99
+ }
100
+ console.log(`${DIM}유효하지 않은 선택입니다. 건너뜁니다.${RESET}`);
101
+ return null;
102
+ }
103
+ /**
104
+ * 자유 텍스트 입력 프롬프트
105
+ */
106
+ async function promptFreeText(rl, questionId) {
107
+ const answer = await rl.question(`${YELLOW}답변 입력 (건너뛰려면 Enter): ${RESET}`);
108
+ const trimmed = answer.trim();
109
+ if (trimmed === '')
110
+ return null;
111
+ return { questionId, freeText: trimmed };
112
+ }
113
+ //# sourceMappingURL=prompt-helpers.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * set-unit 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * 유닛 ID를 받아 계획서 파싱 → 의존성 수집 → 섹션 렌더링 → 커맨드 파일 업데이트
5
+ * 전체 파이프라인을 실행한다.
6
+ *
7
+ * Core 순수 함수들(parseUnitPlan, resolveDepDocs, findDepCommits,
8
+ * renderSection, updateSection)을 조합하며,
9
+ * I/O(파일 읽기/쓰기, git 실행)는 이 Adapter에서만 수행.
10
+ *
11
+ * @module
12
+ */
13
+ import type { ToolResult, PairingQuestion, PairingAnswer } from '../../../types/index.js';
14
+ import type { ParsedArgs } from '../router.js';
15
+ /** set-unit 실행 결과 */
16
+ export interface SetUnitResult {
17
+ unitId: string;
18
+ unitType: string;
19
+ title: string;
20
+ planPath: string;
21
+ depsFound: number;
22
+ docsFound: number;
23
+ commitsFound: number;
24
+ pairingQuestions: PairingQuestion[];
25
+ pairingAnswers: PairingAnswer[];
26
+ commandsUpdated: boolean;
27
+ warnings: string[];
28
+ }
29
+ /**
30
+ * set-unit 커맨드를 실행한다
31
+ *
32
+ * 파이프라인:
33
+ * 1. 전체 컨텍스트 해석 (resolveCommandContext)
34
+ * 2. 미결정 페어링 질문 프롬프트 (인터랙티브 모드일 때)
35
+ * 3. 섹션 렌더링 (renderSection)
36
+ * 4. 커맨드 파일 업데이트 (updateSection)
37
+ * 5. 결과 반환
38
+ *
39
+ * @param args - 파싱된 CLI 인자 (command === 'set-unit' 보장)
40
+ * @param projectRoot - 프로젝트 루트 절대 경로
41
+ * @returns 실행 결과 또는 에러
42
+ */
43
+ export declare function handleSetUnit(args: ParsedArgs, projectRoot: string): Promise<ToolResult<SetUnitResult>>;
44
+ //# sourceMappingURL=set-unit.d.ts.map