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,90 @@
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 { writeFileSync } from 'node:fs';
14
+ import { ok, fail } from '../../../types/index.js';
15
+ import { renderSection } from '../../../core/renderers/section-renderer.js';
16
+ import { updateSection } from '../../../core/renderers/section-updater.js';
17
+ import { resolveCommandContext } from './context-resolver.js';
18
+ import { readCommandsFile } from './io-helpers.js';
19
+ import { writeActiveUnitType } from './state-helpers.js';
20
+ import { isInteractive, promptAllQuestions } from './prompt-helpers.js';
21
+ /**
22
+ * set-unit 커맨드를 실행한다
23
+ *
24
+ * 파이프라인:
25
+ * 1. 전체 컨텍스트 해석 (resolveCommandContext)
26
+ * 2. 미결정 페어링 질문 프롬프트 (인터랙티브 모드일 때)
27
+ * 3. 섹션 렌더링 (renderSection)
28
+ * 4. 커맨드 파일 업데이트 (updateSection)
29
+ * 5. 결과 반환
30
+ *
31
+ * @param args - 파싱된 CLI 인자 (command === 'set-unit' 보장)
32
+ * @param projectRoot - 프로젝트 루트 절대 경로
33
+ * @returns 실행 결과 또는 에러
34
+ */
35
+ export async function handleSetUnit(args, projectRoot) {
36
+ if (args.command !== 'set-unit') {
37
+ return fail('INTERNAL_ERROR', 'handleSetUnit에 잘못된 커맨드가 전달되었습니다');
38
+ }
39
+ const { unitId, noPrompt } = args;
40
+ const requestKeys = 'requestKeys' in args ? args.requestKeys : [];
41
+ // 1. 전체 컨텍스트 해석 (I/O + 파싱 + 의존성 수집)
42
+ const resolveResult = resolveCommandContext(unitId, projectRoot, requestKeys);
43
+ if (!resolveResult.success)
44
+ return resolveResult;
45
+ const { context, config, unitTypeKey, unitTypeConfig, planRelPath, warnings } = resolveResult.data;
46
+ // 2. 미결정 페어링 질문 프롬프트 (인터랙티브 모드)
47
+ let pairingAnswers = [];
48
+ const unresolvedCount = context.pairingQuestions.filter((q) => !q.resolved).length;
49
+ if (unresolvedCount > 0 && isInteractive(noPrompt)) {
50
+ pairingAnswers = await promptAllQuestions(context.pairingQuestions);
51
+ context.pairingAnswers = pairingAnswers;
52
+ }
53
+ // 3. 섹션 렌더링 (Core 순수 함수)
54
+ const sectionBody = renderSection(context, unitTypeConfig);
55
+ // 4. 커맨드 파일 업데이트 (Adapter I/O)
56
+ let commandsUpdated = false;
57
+ const cmdFile = readCommandsFile(config, projectRoot);
58
+ if (cmdFile.success) {
59
+ const useMarkers = config.commandSections.useMarkers;
60
+ const markerOptions = useMarkers ? { sectionKey: unitTypeConfig.key } : undefined;
61
+ const updateResult = updateSection(cmdFile.data.content, unitTypeConfig.commandSection, sectionBody, config.commandSections.separator, markerOptions);
62
+ if (updateResult.success && updateResult.data.updated) {
63
+ writeFileSync(cmdFile.data.absPath, updateResult.data.content, 'utf-8');
64
+ commandsUpdated = true;
65
+ }
66
+ else if (updateResult.success && !updateResult.data.updated) {
67
+ warnings.push(`커맨드 파일에서 '${unitTypeConfig.commandSection}' 섹션을 찾을 수 없습니다`);
68
+ }
69
+ }
70
+ else {
71
+ warnings.push(`커맨드 파일을 읽을 수 없습니다: ${config.paths.commands}`);
72
+ }
73
+ // 5. 활성 유닛 타입 상태 기록 (update-commit 자동 감지용)
74
+ writeActiveUnitType(projectRoot, unitTypeKey);
75
+ // 6. 결과 반환
76
+ return ok({
77
+ unitId,
78
+ unitType: unitTypeKey,
79
+ title: context.unit.title,
80
+ planPath: planRelPath,
81
+ depsFound: context.unit.depends.length,
82
+ docsFound: context.depDocs.reduce((sum, d) => sum + d.found.length, 0),
83
+ commitsFound: context.depCommits.length,
84
+ pairingQuestions: context.pairingQuestions,
85
+ pairingAnswers,
86
+ commandsUpdated,
87
+ warnings,
88
+ });
89
+ }
90
+ //# sourceMappingURL=set-unit.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * skill install 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * npm 패키지에 번들된 SKILL 파일을 프로젝트의 에이전트 인식 경로에 복사한다.
5
+ * Cursor(.cursor/skills/), Claude Code(.claude/skills/), Gemini(.gemini/skills/) 지원.
6
+ *
7
+ * 파이프라인:
8
+ * 1. 번들된 SKILL 소스 파일 경로 resolve (import.meta.url 기반)
9
+ * 2. 각 대상 플랫폼별로 존재 여부 확인
10
+ * 3. 기존 파일 존재 시 --force 없으면 건너뜀
11
+ * 4. 디렉토리 생성 + 파일 복사 + 결과 반환
12
+ *
13
+ * @module
14
+ */
15
+ import type { ToolResult } from '../../../types/index.js';
16
+ import type { ParsedArgs } from '../router.js';
17
+ export type Platform = 'cursor' | 'claude' | 'gemini';
18
+ export interface SkillInstallResult {
19
+ installed: string[];
20
+ skipped: string[];
21
+ overwritten: string[];
22
+ }
23
+ /**
24
+ * skill install 커맨드를 실행한다
25
+ *
26
+ * @param args - 파싱된 CLI 인자 (command === 'skill-install' 보장)
27
+ * @param projectRoot - 프로젝트 루트 절대 경로
28
+ * @returns 설치 결과 (installed, skipped, overwritten 목록)
29
+ */
30
+ export declare function handleSkillInstall(args: ParsedArgs, projectRoot: string): ToolResult<SkillInstallResult>;
31
+ //# sourceMappingURL=skill-install.d.ts.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * skill install 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * npm 패키지에 번들된 SKILL 파일을 프로젝트의 에이전트 인식 경로에 복사한다.
5
+ * Cursor(.cursor/skills/), Claude Code(.claude/skills/), Gemini(.gemini/skills/) 지원.
6
+ *
7
+ * 파이프라인:
8
+ * 1. 번들된 SKILL 소스 파일 경로 resolve (import.meta.url 기반)
9
+ * 2. 각 대상 플랫폼별로 존재 여부 확인
10
+ * 3. 기존 파일 존재 시 --force 없으면 건너뜀
11
+ * 4. 디렉토리 생성 + 파일 복사 + 결과 반환
12
+ *
13
+ * @module
14
+ */
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
16
+ import { dirname, join, relative } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { ok, fail } from '../../../types/index.js';
19
+ const PLATFORM_CONFIG = {
20
+ cursor: { sourceDir: 'cursor', targetDir: '.cursor/skills/vibe-commander' },
21
+ claude: { sourceDir: 'claude', targetDir: '.claude/skills/vibe-commander' },
22
+ gemini: { sourceDir: 'claude', targetDir: '.gemini/skills/vibe-commander' },
23
+ };
24
+ /**
25
+ * 번들된 SKILL 파일의 루트 디렉토리를 반환한다
26
+ *
27
+ * import.meta.url 기반으로 현재 모듈 위치에서 패키지 루트의 skills/ 디렉토리를 resolve.
28
+ * src/adapters/cli/commands/ 또는 dist/adapters/cli/commands/ 모두 동일한 깊이.
29
+ */
30
+ function getSkillsRoot() {
31
+ const thisDir = dirname(fileURLToPath(import.meta.url));
32
+ return join(thisDir, '..', '..', '..', '..', 'skills');
33
+ }
34
+ /**
35
+ * skill install 커맨드를 실행한다
36
+ *
37
+ * @param args - 파싱된 CLI 인자 (command === 'skill-install' 보장)
38
+ * @param projectRoot - 프로젝트 루트 절대 경로
39
+ * @returns 설치 결과 (installed, skipped, overwritten 목록)
40
+ */
41
+ export function handleSkillInstall(args, projectRoot) {
42
+ if (args.command !== 'skill-install') {
43
+ return fail('INTERNAL_ERROR', 'handleSkillInstall에 잘못된 커맨드가 전달되었습니다');
44
+ }
45
+ const skillsRoot = getSkillsRoot();
46
+ if (!existsSync(skillsRoot)) {
47
+ return fail('SKILL_SOURCE_NOT_FOUND', 'SKILL 소스 디렉토리를 찾을 수 없습니다', `경로: ${skillsRoot}. npm 패키지가 올바르게 설치되었는지 확인하세요.`);
48
+ }
49
+ const result = { installed: [], skipped: [], overwritten: [] };
50
+ for (const platform of args.targets) {
51
+ installForPlatform(platform, skillsRoot, projectRoot, args.force, result);
52
+ }
53
+ if (result.installed.length === 0 && result.overwritten.length === 0) {
54
+ if (result.skipped.length > 0) {
55
+ return ok(result);
56
+ }
57
+ return fail('SKILL_INSTALL_FAILED', '설치할 SKILL 파일이 없습니다', '소스 SKILL 파일이 존재하는지 확인하세요.');
58
+ }
59
+ return ok(result);
60
+ }
61
+ /**
62
+ * 단일 플랫폼에 SKILL 파일을 설치한다
63
+ */
64
+ function installForPlatform(platform, skillsRoot, projectRoot, force, result) {
65
+ const config = PLATFORM_CONFIG[platform];
66
+ const sourcePath = join(skillsRoot, config.sourceDir, 'SKILL.md');
67
+ const targetPath = join(projectRoot, config.targetDir, 'SKILL.md');
68
+ const displayPath = relative(projectRoot, targetPath).replace(/\\/g, '/');
69
+ if (!existsSync(sourcePath)) {
70
+ result.skipped.push(`${platform}: 소스 SKILL 파일 없음`);
71
+ return;
72
+ }
73
+ if (existsSync(targetPath)) {
74
+ if (!force) {
75
+ result.skipped.push(`${displayPath} (이미 존재 — --force로 덮어쓰기)`);
76
+ return;
77
+ }
78
+ result.overwritten.push(displayPath);
79
+ }
80
+ mkdirSync(dirname(targetPath), { recursive: true });
81
+ const content = readFileSync(sourcePath, 'utf-8');
82
+ writeFileSync(targetPath, content, 'utf-8');
83
+ result.installed.push(displayPath);
84
+ }
85
+ //# sourceMappingURL=skill-install.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 활성 유닛 상태 추적 헬퍼 — Layer 3 (Adapter)
3
+ *
4
+ * set-unit 실행 시 마지막 활성 유닛 타입을 상태 파일에 기록하고,
5
+ * update-commit 실행 시 이를 읽어 대상 섹션을 결정한다.
6
+ *
7
+ * 상태 파일: .vibe-commander.state.json (프로젝트 루트)
8
+ * 이 파일은 .gitignore에 추가되어 버전 관리에서 제외된다.
9
+ *
10
+ * @module
11
+ */
12
+ /** 상태 파일명 (프로젝트 루트 기준) */
13
+ export declare const STATE_FILENAME = ".vibe-commander.state.json";
14
+ /**
15
+ * 마지막 활성 유닛 타입을 상태 파일에 기록한다
16
+ *
17
+ * @param projectRoot - 프로젝트 루트 절대 경로
18
+ * @param unitTypeKey - 유닛 타입 키 (예: "implement", "refactor")
19
+ */
20
+ export declare function writeActiveUnitType(projectRoot: string, unitTypeKey: string): void;
21
+ /**
22
+ * 마지막 활성 유닛 타입을 상태 파일에서 읽어온다
23
+ *
24
+ * @param projectRoot - 프로젝트 루트 절대 경로
25
+ * @returns 유닛 타입 키 또는 null (파일 없음/파싱 실패 시)
26
+ */
27
+ export declare function readActiveUnitType(projectRoot: string): string | null;
28
+ //# sourceMappingURL=state-helpers.d.ts.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 활성 유닛 상태 추적 헬퍼 — Layer 3 (Adapter)
3
+ *
4
+ * set-unit 실행 시 마지막 활성 유닛 타입을 상태 파일에 기록하고,
5
+ * update-commit 실행 시 이를 읽어 대상 섹션을 결정한다.
6
+ *
7
+ * 상태 파일: .vibe-commander.state.json (프로젝트 루트)
8
+ * 이 파일은 .gitignore에 추가되어 버전 관리에서 제외된다.
9
+ *
10
+ * @module
11
+ */
12
+ import { readFileSync, writeFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ /** 상태 파일명 (프로젝트 루트 기준) */
15
+ export const STATE_FILENAME = '.vibe-commander.state.json';
16
+ /**
17
+ * 마지막 활성 유닛 타입을 상태 파일에 기록한다
18
+ *
19
+ * @param projectRoot - 프로젝트 루트 절대 경로
20
+ * @param unitTypeKey - 유닛 타입 키 (예: "implement", "refactor")
21
+ */
22
+ export function writeActiveUnitType(projectRoot, unitTypeKey) {
23
+ const statePath = join(projectRoot, STATE_FILENAME);
24
+ const state = { lastActiveUnitType: unitTypeKey };
25
+ try {
26
+ writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n', 'utf-8');
27
+ }
28
+ catch {
29
+ // 상태 기록 실패는 무시 (Graceful Degradation)
30
+ }
31
+ }
32
+ /**
33
+ * 마지막 활성 유닛 타입을 상태 파일에서 읽어온다
34
+ *
35
+ * @param projectRoot - 프로젝트 루트 절대 경로
36
+ * @returns 유닛 타입 키 또는 null (파일 없음/파싱 실패 시)
37
+ */
38
+ export function readActiveUnitType(projectRoot) {
39
+ const statePath = join(projectRoot, STATE_FILENAME);
40
+ try {
41
+ const raw = readFileSync(statePath, 'utf-8');
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed !== null &&
44
+ typeof parsed === 'object' &&
45
+ 'lastActiveUnitType' in parsed &&
46
+ typeof parsed.lastActiveUnitType === 'string') {
47
+ return parsed.lastActiveUnitType;
48
+ }
49
+ return null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ //# sourceMappingURL=state-helpers.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * update-commit 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * Commit SHA를 받아 커맨드 파일의 해당 섹션 Commit 라인을 업데이트한다.
5
+ * replace 모드(값 교체)와 append 모드(콤마 구분 추가)를 지원.
6
+ *
7
+ * Core 순수 함수(updateField)를 호출하며,
8
+ * I/O(파일 읽기/쓰기)는 이 Adapter에서만 수행.
9
+ *
10
+ * @module
11
+ */
12
+ import type { ToolResult } from '../../../types/index.js';
13
+ import type { ParsedArgs } from '../router.js';
14
+ /** update-commit 실행 결과 */
15
+ export interface UpdateCommitResult {
16
+ sha: string;
17
+ mode: 'replace' | 'append';
18
+ section: string;
19
+ sectionHeader: string;
20
+ updated: boolean;
21
+ commitMode: 'auto-head' | 'auto-recent' | 'manual';
22
+ count?: number;
23
+ requested?: number;
24
+ }
25
+ /**
26
+ * update-commit 커맨드를 실행한다
27
+ *
28
+ * 파이프라인:
29
+ * 1. commitMode에 따라 SHA 결정 (auto-head/auto-recent/manual)
30
+ * 2. 설정 로드 (loadConfig)
31
+ * 3. 커맨드 파일 읽기
32
+ * 4. 대상 섹션 결정 (자동 감지 또는 --section)
33
+ * 5. Commit 필드 업데이트 (updateField)
34
+ * 6. 파일 쓰기
35
+ * 7. 결과 반환
36
+ *
37
+ * @param args - 파싱된 CLI 인자 (command === 'update-commit' 보장)
38
+ * @param projectRoot - 프로젝트 루트 절대 경로
39
+ * @returns 실행 결과 또는 에러
40
+ */
41
+ export declare function handleUpdateCommit(args: ParsedArgs, projectRoot: string): ToolResult<UpdateCommitResult>;
42
+ //# sourceMappingURL=update-commit.d.ts.map
@@ -0,0 +1,180 @@
1
+ /**
2
+ * update-commit 커맨드 핸들러 — Layer 3 (Adapter)
3
+ *
4
+ * Commit SHA를 받아 커맨드 파일의 해당 섹션 Commit 라인을 업데이트한다.
5
+ * replace 모드(값 교체)와 append 모드(콤마 구분 추가)를 지원.
6
+ *
7
+ * Core 순수 함수(updateField)를 호출하며,
8
+ * I/O(파일 읽기/쓰기)는 이 Adapter에서만 수행.
9
+ *
10
+ * @module
11
+ */
12
+ import { writeFileSync } from 'node:fs';
13
+ import { ok, fail } from '../../../types/index.js';
14
+ import { loadConfig } from '../../../config/loader.js';
15
+ import { updateField } from '../../../core/renderers/section-updater.js';
16
+ import { normalizeLineEndings, getHeadingPrefix, isSameLevelHeading, } from '../../../core/parsers/index.js';
17
+ import { findHeaderIndex, getBodyStartIndex, findBodyEndIndex, } from '../../../core/renderers/updater-helpers.js';
18
+ import { readCommandsFile } from './io-helpers.js';
19
+ import { getHeadSha, getRecentShas } from './git-helpers.js';
20
+ import { readActiveUnitType } from './state-helpers.js';
21
+ /**
22
+ * update-commit 커맨드를 실행한다
23
+ *
24
+ * 파이프라인:
25
+ * 1. commitMode에 따라 SHA 결정 (auto-head/auto-recent/manual)
26
+ * 2. 설정 로드 (loadConfig)
27
+ * 3. 커맨드 파일 읽기
28
+ * 4. 대상 섹션 결정 (자동 감지 또는 --section)
29
+ * 5. Commit 필드 업데이트 (updateField)
30
+ * 6. 파일 쓰기
31
+ * 7. 결과 반환
32
+ *
33
+ * @param args - 파싱된 CLI 인자 (command === 'update-commit' 보장)
34
+ * @param projectRoot - 프로젝트 루트 절대 경로
35
+ * @returns 실행 결과 또는 에러
36
+ */
37
+ export function handleUpdateCommit(args, projectRoot) {
38
+ if (args.command !== 'update-commit') {
39
+ return fail('INTERNAL_ERROR', 'handleUpdateCommit에 잘못된 커맨드가 전달되었습니다');
40
+ }
41
+ const { commitMode, mode, section: sectionKey } = args;
42
+ // 1. commitMode에 따라 SHA 결정
43
+ let sha;
44
+ let actualCount;
45
+ let requested;
46
+ switch (commitMode) {
47
+ case 'auto-head': {
48
+ const headResult = getHeadSha(projectRoot);
49
+ if (!headResult.success)
50
+ return headResult;
51
+ sha = headResult.data;
52
+ break;
53
+ }
54
+ case 'auto-recent': {
55
+ const count = args.count ?? 1;
56
+ const recentResult = getRecentShas(projectRoot, count);
57
+ if (!recentResult.success)
58
+ return recentResult;
59
+ sha = recentResult.data.shas.join(', ');
60
+ actualCount = recentResult.data.actual;
61
+ requested = recentResult.data.requested;
62
+ break;
63
+ }
64
+ case 'manual': {
65
+ sha = args.sha ?? '';
66
+ if (!sha) {
67
+ return fail('MISSING_ARGUMENT', "'update-commit' manual 모드에 SHA가 필요합니다");
68
+ }
69
+ break;
70
+ }
71
+ }
72
+ // 2. 설정 로드
73
+ const configResult = loadConfig(projectRoot);
74
+ if (!configResult.success)
75
+ return configResult;
76
+ const config = configResult.data;
77
+ // 3. 커맨드 파일 읽기 (Adapter I/O)
78
+ const cmdFile = readCommandsFile(config, projectRoot);
79
+ if (!cmdFile.success)
80
+ return cmdFile;
81
+ const { content: commandsContent, absPath: commandsAbsPath, relPath: commandsRelPath, } = cmdFile.data;
82
+ // 4. 대상 섹션 결정 (상태 파일 우선 → 자동 감지 fallback)
83
+ const sectionResult = resolveTargetSection(sectionKey, config, commandsContent, projectRoot);
84
+ if (!sectionResult.success)
85
+ return sectionResult;
86
+ const { sectionHeader, unitTypeKey } = sectionResult.data;
87
+ // 5. Commit 필드 패턴 생성 (설정 기반)
88
+ const fieldName = config.commandSections.commitFieldName;
89
+ const fieldPattern = new RegExp(fieldName);
90
+ // 6. Commit 필드 업데이트 (Core 순수 함수)
91
+ const markerOptions = config.commandSections.useMarkers ? { sectionKey: unitTypeKey } : undefined;
92
+ const updateResult = updateField(commandsContent, sectionHeader, fieldPattern, sha, config.commandSections.separator, mode, markerOptions);
93
+ if (!updateResult.success) {
94
+ return updateResult;
95
+ }
96
+ if (!updateResult.data.updated) {
97
+ return fail('FIELD_NOT_FOUND', `'${sectionHeader}' 섹션에서 ${fieldName} 필드를 찾을 수 없습니다`, 'set-unit을 먼저 실행하여 섹션을 초기화하거나, --section 옵션으로 대상 섹션을 지정하세요');
98
+ }
99
+ // 7. 파일 쓰기 (Adapter I/O)
100
+ try {
101
+ writeFileSync(commandsAbsPath, updateResult.data.content, 'utf-8');
102
+ }
103
+ catch {
104
+ return fail('COMMANDS_WRITE_FAILED', `커맨드 파일을 쓸 수 없습니다: ${commandsRelPath}`, `절대 경로: ${commandsAbsPath}`);
105
+ }
106
+ // 8. 결과 반환
107
+ return ok({
108
+ sha,
109
+ mode,
110
+ section: unitTypeKey,
111
+ sectionHeader,
112
+ updated: true,
113
+ commitMode,
114
+ ...(actualCount !== undefined && { count: actualCount }),
115
+ ...(requested !== undefined && { requested }),
116
+ });
117
+ }
118
+ /**
119
+ * 대상 섹션을 결정한다.
120
+ *
121
+ * 우선순위:
122
+ * 1. sectionKey가 있으면 해당 유형 사용 (--section 옵션).
123
+ * 2. 상태 파일에 마지막 활성 유닛 타입이 있고, 해당 섹션이 활성 상태면 사용.
124
+ * 3. 없으면 commandsContent를 분석하여 활성 유닛이 있는 섹션 탐색 (fallback).
125
+ * 4. 활성 섹션이 없으면 에러 반환 (PRD §6.3 준수).
126
+ */
127
+ function resolveTargetSection(sectionKey, config, commandsContent, projectRoot) {
128
+ // 1. 특정 섹션이 지정된 경우
129
+ if (sectionKey !== undefined) {
130
+ const unitType = config.unitTypes.find((t) => t.key === sectionKey);
131
+ if (!unitType) {
132
+ return fail('SECTION_NOT_FOUND', `'${sectionKey}' 유닛 유형을 찾을 수 없습니다`, `사용 가능한 유형: ${config.unitTypes.map((t) => t.key).join(', ')}`);
133
+ }
134
+ return ok({ sectionHeader: unitType.commandSection, unitTypeKey: unitType.key });
135
+ }
136
+ // 2. 상태 파일에서 마지막 활성 유닛 타입 확인 (set-unit이 기록)
137
+ const lastActiveType = readActiveUnitType(projectRoot);
138
+ if (lastActiveType !== null) {
139
+ const stateUnitType = config.unitTypes.find((t) => t.key === lastActiveType);
140
+ if (stateUnitType &&
141
+ isActiveSection(commandsContent, stateUnitType, config.commandSections.separator)) {
142
+ return ok({
143
+ sectionHeader: stateUnitType.commandSection,
144
+ unitTypeKey: stateUnitType.key,
145
+ });
146
+ }
147
+ }
148
+ // 3. Fallback: 활성 유닛 정보가 있는 섹션 순회
149
+ for (const unitType of config.unitTypes) {
150
+ if (isActiveSection(commandsContent, unitType, config.commandSections.separator)) {
151
+ return ok({ sectionHeader: unitType.commandSection, unitTypeKey: unitType.key });
152
+ }
153
+ }
154
+ return fail('NO_ACTIVE_UNIT', '현재 활성화된 유닛을 찾을 수 없습니다', 'vc set-unit <unitId> 명령을 먼저 실행하거나, --section 옵션으로 대상 섹션을 지정하세요');
155
+ }
156
+ /**
157
+ * 섹션이 활성화되어 있는지(유닛 정보가 채워져 있는지) 확인한다.
158
+ *
159
+ * 본문 내에 유닛 ID 패턴에 매칭되는 문자열이 있고, 그 값이 placeholder('-')가 아니면 활성으로 간주.
160
+ */
161
+ function isActiveSection(content, unitType, separator) {
162
+ const normalized = normalizeLineEndings(content);
163
+ const lines = normalized.split('\n');
164
+ const headerIdx = findHeaderIndex(lines, unitType.commandSection);
165
+ if (headerIdx === -1)
166
+ return false;
167
+ const headingPrefix = getHeadingPrefix(unitType.commandSection);
168
+ const bodyStartIdx = getBodyStartIndex(lines, headerIdx, separator);
169
+ const bodyEndIdx = findBodyEndIndex(lines, bodyStartIdx, separator, headingPrefix, isSameLevelHeading);
170
+ const sectionLines = lines.slice(bodyStartIdx, bodyEndIdx);
171
+ const sectionContent = sectionLines.join('\n');
172
+ // 유닛 ID 패턴에서 앵커(^, $) 제거하여 본문 내 검색용 패턴 생성
173
+ // idPattern은 전체 ID 검증용(^...$)이므로, 섹션 본문 내 포함 검색 시 앵커 제거 필요
174
+ const searchPattern = unitType.idPattern.replace(/^\^/, '').replace(/\$$/, '');
175
+ const idRegex = new RegExp(searchPattern, 'm');
176
+ const match = sectionContent.match(idRegex);
177
+ // 매칭되는 ID가 있고, 그게 placeholder가 아니면 활성
178
+ return match !== null && match[0] !== '-';
179
+ }
180
+ //# sourceMappingURL=update-commit.js.map
@@ -0,0 +1,40 @@
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 type { ToolResult } from '../../../types/index.js';
16
+ import type { ParsedArgs } from '../router.js';
17
+ export interface ValidationItem {
18
+ category: 'schema' | 'paths' | 'regex' | 'patterns';
19
+ status: 'pass' | 'warn' | 'error';
20
+ message: string;
21
+ details?: string;
22
+ }
23
+ export interface ValidateResult {
24
+ valid: boolean;
25
+ errors: number;
26
+ warnings: number;
27
+ items: ValidationItem[];
28
+ }
29
+ /**
30
+ * validate 커맨드를 실행한다
31
+ *
32
+ * 4단계 검증 파이프라인을 순차 실행하고 종합 리포트를 반환한다.
33
+ * 스키마 검증 실패 시 후속 검증을 건너뛰고 즉시 에러 리포트를 반환.
34
+ *
35
+ * @param args - 파싱된 CLI 인자 (command === 'validate' 보장)
36
+ * @param projectRoot - 프로젝트 루트 절대 경로
37
+ * @returns ValidateResult 또는 에러
38
+ */
39
+ export declare function handleValidate(args: ParsedArgs, projectRoot: string): ToolResult<ValidateResult>;
40
+ //# sourceMappingURL=validate.d.ts.map