@litmers/cursorflow-orchestrator 0.1.39 → 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.
Files changed (214) hide show
  1. package/CHANGELOG.md +0 -2
  2. package/README.md +20 -16
  3. package/commands/cursorflow-init.md +0 -4
  4. package/dist/cli/logs.js +108 -9
  5. package/dist/cli/logs.js.map +1 -1
  6. package/dist/cli/models.js +20 -3
  7. package/dist/cli/models.js.map +1 -1
  8. package/dist/cli/monitor.d.ts +7 -10
  9. package/dist/cli/monitor.js +1088 -1240
  10. package/dist/cli/monitor.js.map +1 -1
  11. package/dist/cli/prepare.js +0 -1
  12. package/dist/cli/prepare.js.map +1 -1
  13. package/dist/cli/resume.js +23 -5
  14. package/dist/cli/resume.js.map +1 -1
  15. package/dist/cli/run.js +28 -9
  16. package/dist/cli/run.js.map +1 -1
  17. package/dist/cli/signal.d.ts +6 -1
  18. package/dist/cli/signal.js +94 -12
  19. package/dist/cli/signal.js.map +1 -1
  20. package/dist/cli/tasks.js +3 -46
  21. package/dist/cli/tasks.js.map +1 -1
  22. package/dist/core/agent-supervisor.d.ts +23 -0
  23. package/dist/core/agent-supervisor.js +42 -0
  24. package/dist/core/agent-supervisor.js.map +1 -0
  25. package/dist/core/auto-recovery.d.ts +2 -1
  26. package/dist/core/auto-recovery.js +6 -1
  27. package/dist/core/auto-recovery.js.map +1 -1
  28. package/dist/core/failure-policy.d.ts +0 -1
  29. package/dist/core/failure-policy.js +0 -1
  30. package/dist/core/failure-policy.js.map +1 -1
  31. package/dist/core/git-lifecycle-manager.d.ts +284 -0
  32. package/dist/core/git-lifecycle-manager.js +778 -0
  33. package/dist/core/git-lifecycle-manager.js.map +1 -0
  34. package/dist/core/git-pipeline-coordinator.d.ts +21 -0
  35. package/dist/core/git-pipeline-coordinator.js +205 -0
  36. package/dist/core/git-pipeline-coordinator.js.map +1 -0
  37. package/dist/core/intervention.d.ts +176 -0
  38. package/dist/core/intervention.js +424 -0
  39. package/dist/core/intervention.js.map +1 -0
  40. package/dist/core/lane-state-machine.d.ts +423 -0
  41. package/dist/core/lane-state-machine.js +890 -0
  42. package/dist/core/lane-state-machine.js.map +1 -0
  43. package/dist/core/orchestrator.d.ts +4 -1
  44. package/dist/core/orchestrator.js +38 -63
  45. package/dist/core/orchestrator.js.map +1 -1
  46. package/dist/core/runner/agent.d.ts +7 -1
  47. package/dist/core/runner/agent.js +45 -30
  48. package/dist/core/runner/agent.js.map +1 -1
  49. package/dist/core/runner/pipeline.js +283 -109
  50. package/dist/core/runner/pipeline.js.map +1 -1
  51. package/dist/core/runner/task.d.ts +4 -5
  52. package/dist/core/runner/task.js +6 -77
  53. package/dist/core/runner/task.js.map +1 -1
  54. package/dist/core/runner.js +11 -2
  55. package/dist/core/runner.js.map +1 -1
  56. package/dist/core/stall-detection.d.ts +27 -4
  57. package/dist/core/stall-detection.js +116 -28
  58. package/dist/core/stall-detection.js.map +1 -1
  59. package/dist/hooks/contexts/index.d.ts +104 -0
  60. package/dist/hooks/contexts/index.js +134 -0
  61. package/dist/hooks/contexts/index.js.map +1 -0
  62. package/dist/hooks/data-accessor.d.ts +86 -0
  63. package/dist/hooks/data-accessor.js +410 -0
  64. package/dist/hooks/data-accessor.js.map +1 -0
  65. package/dist/hooks/flow-controller.d.ts +136 -0
  66. package/dist/hooks/flow-controller.js +351 -0
  67. package/dist/hooks/flow-controller.js.map +1 -0
  68. package/dist/hooks/index.d.ts +68 -0
  69. package/dist/hooks/index.js +105 -0
  70. package/dist/hooks/index.js.map +1 -0
  71. package/dist/hooks/manager.d.ts +129 -0
  72. package/dist/hooks/manager.js +389 -0
  73. package/dist/hooks/manager.js.map +1 -0
  74. package/dist/hooks/types.d.ts +463 -0
  75. package/dist/hooks/types.js +45 -0
  76. package/dist/hooks/types.js.map +1 -0
  77. package/dist/services/logging/buffer.d.ts +2 -2
  78. package/dist/services/logging/buffer.js +95 -42
  79. package/dist/services/logging/buffer.js.map +1 -1
  80. package/dist/services/logging/console.js +8 -5
  81. package/dist/services/logging/console.js.map +1 -1
  82. package/dist/services/logging/formatter.d.ts +9 -3
  83. package/dist/services/logging/formatter.js +64 -17
  84. package/dist/services/logging/formatter.js.map +1 -1
  85. package/dist/services/logging/index.d.ts +0 -1
  86. package/dist/services/logging/index.js +0 -1
  87. package/dist/services/logging/index.js.map +1 -1
  88. package/dist/services/logging/paths.d.ts +8 -0
  89. package/dist/services/logging/paths.js +48 -0
  90. package/dist/services/logging/paths.js.map +1 -0
  91. package/dist/services/logging/raw-log.d.ts +6 -0
  92. package/dist/services/logging/raw-log.js +37 -0
  93. package/dist/services/logging/raw-log.js.map +1 -0
  94. package/dist/services/process/index.js +1 -1
  95. package/dist/services/process/index.js.map +1 -1
  96. package/dist/types/agent.d.ts +15 -0
  97. package/dist/types/config.d.ts +24 -1
  98. package/dist/types/event-categories.d.ts +601 -0
  99. package/dist/types/event-categories.js +233 -0
  100. package/dist/types/event-categories.js.map +1 -0
  101. package/dist/types/events.d.ts +0 -20
  102. package/dist/types/flow.d.ts +10 -6
  103. package/dist/types/index.d.ts +1 -1
  104. package/dist/types/index.js +17 -3
  105. package/dist/types/index.js.map +1 -1
  106. package/dist/types/lane.d.ts +1 -1
  107. package/dist/types/logging.d.ts +1 -1
  108. package/dist/types/task.d.ts +13 -2
  109. package/dist/ui/log-viewer.d.ts +3 -0
  110. package/dist/ui/log-viewer.js +3 -0
  111. package/dist/ui/log-viewer.js.map +1 -1
  112. package/dist/utils/config.js +15 -1
  113. package/dist/utils/config.js.map +1 -1
  114. package/dist/utils/cursor-agent.d.ts +11 -1
  115. package/dist/utils/cursor-agent.js +63 -16
  116. package/dist/utils/cursor-agent.js.map +1 -1
  117. package/dist/utils/enhanced-logger.d.ts +5 -1
  118. package/dist/utils/enhanced-logger.js +99 -20
  119. package/dist/utils/enhanced-logger.js.map +1 -1
  120. package/dist/utils/event-registry.d.ts +222 -0
  121. package/dist/utils/event-registry.js +463 -0
  122. package/dist/utils/event-registry.js.map +1 -0
  123. package/dist/utils/events.d.ts +1 -13
  124. package/dist/utils/events.js.map +1 -1
  125. package/dist/utils/flow.d.ts +10 -0
  126. package/dist/utils/flow.js +75 -0
  127. package/dist/utils/flow.js.map +1 -1
  128. package/dist/utils/git.d.ts +12 -1
  129. package/dist/utils/git.js +54 -1
  130. package/dist/utils/git.js.map +1 -1
  131. package/dist/utils/log-constants.d.ts +1 -0
  132. package/dist/utils/log-constants.js +2 -1
  133. package/dist/utils/log-constants.js.map +1 -1
  134. package/dist/utils/log-formatter.d.ts +3 -2
  135. package/dist/utils/log-formatter.js +11 -11
  136. package/dist/utils/log-formatter.js.map +1 -1
  137. package/dist/utils/logger.d.ts +11 -0
  138. package/dist/utils/logger.js +82 -3
  139. package/dist/utils/logger.js.map +1 -1
  140. package/dist/utils/repro-thinking-logs.js +0 -13
  141. package/dist/utils/repro-thinking-logs.js.map +1 -1
  142. package/dist/utils/run-service.js +1 -1
  143. package/dist/utils/run-service.js.map +1 -1
  144. package/examples/README.md +0 -2
  145. package/examples/demo-project/README.md +1 -2
  146. package/package.json +18 -28
  147. package/scripts/setup-security.sh +0 -1
  148. package/scripts/test-log-parser.ts +171 -0
  149. package/scripts/verify-change.sh +272 -0
  150. package/src/cli/logs.ts +121 -10
  151. package/src/cli/models.ts +20 -3
  152. package/src/cli/monitor.ts +1257 -1342
  153. package/src/cli/prepare.ts +0 -1
  154. package/src/cli/resume.ts +29 -5
  155. package/src/cli/run.ts +29 -11
  156. package/src/cli/signal.ts +115 -17
  157. package/src/cli/tasks.ts +2 -59
  158. package/src/core/agent-supervisor.ts +64 -0
  159. package/src/core/auto-recovery.ts +7 -1
  160. package/src/core/failure-policy.ts +0 -1
  161. package/src/core/git-lifecycle-manager.ts +1011 -0
  162. package/src/core/git-pipeline-coordinator.ts +221 -0
  163. package/src/core/intervention.ts +481 -0
  164. package/src/core/lane-state-machine.ts +1097 -0
  165. package/src/core/orchestrator.ts +45 -62
  166. package/src/core/runner/agent.ts +66 -33
  167. package/src/core/runner/pipeline.ts +318 -122
  168. package/src/core/runner/task.ts +12 -93
  169. package/src/core/runner.ts +12 -2
  170. package/src/core/stall-detection.ts +145 -28
  171. package/src/hooks/contexts/index.ts +256 -0
  172. package/src/hooks/data-accessor.ts +488 -0
  173. package/src/hooks/flow-controller.ts +425 -0
  174. package/src/hooks/index.ts +154 -0
  175. package/src/hooks/manager.ts +434 -0
  176. package/src/hooks/types.ts +544 -0
  177. package/src/services/logging/buffer.ts +104 -43
  178. package/src/services/logging/console.ts +9 -5
  179. package/src/services/logging/formatter.ts +74 -17
  180. package/src/services/logging/index.ts +0 -2
  181. package/src/services/logging/paths.ts +14 -0
  182. package/src/services/logging/raw-log.ts +43 -0
  183. package/src/services/process/index.ts +1 -1
  184. package/src/types/agent.ts +15 -0
  185. package/src/types/config.ts +25 -1
  186. package/src/types/event-categories.ts +663 -0
  187. package/src/types/events.ts +0 -25
  188. package/src/types/flow.ts +10 -6
  189. package/src/types/index.ts +50 -4
  190. package/src/types/lane.ts +1 -2
  191. package/src/types/logging.ts +2 -1
  192. package/src/types/task.ts +13 -2
  193. package/src/ui/log-viewer.ts +3 -0
  194. package/src/utils/config.ts +17 -1
  195. package/src/utils/cursor-agent.ts +68 -16
  196. package/src/utils/enhanced-logger.ts +106 -20
  197. package/src/utils/event-registry.ts +595 -0
  198. package/src/utils/events.ts +0 -16
  199. package/src/utils/flow.ts +84 -0
  200. package/src/utils/git.ts +59 -1
  201. package/src/utils/log-constants.ts +2 -1
  202. package/src/utils/log-formatter.ts +11 -12
  203. package/src/utils/logger.ts +49 -3
  204. package/src/utils/repro-thinking-logs.ts +0 -15
  205. package/src/utils/run-service.ts +1 -1
  206. package/dist/services/logging/file-writer.d.ts +0 -71
  207. package/dist/services/logging/file-writer.js +0 -516
  208. package/dist/services/logging/file-writer.js.map +0 -1
  209. package/dist/types/review.d.ts +0 -17
  210. package/dist/types/review.js +0 -6
  211. package/dist/types/review.js.map +0 -1
  212. package/scripts/ai-security-check.js +0 -233
  213. package/src/services/logging/file-writer.ts +0 -526
  214. package/src/types/review.ts +0 -20
@@ -0,0 +1,778 @@
1
+ "use strict";
2
+ /**
3
+ * Git Lifecycle Manager
4
+ *
5
+ * Git 파이프라인의 생명주기를 명확하게 관리합니다.
6
+ *
7
+ * 작업 흐름:
8
+ * 1. 작업 시작 (startWork): 브랜치 생성 및 체크아웃
9
+ * 2. 작업 중 (saveProgress): 주기적 커밋 (선택적)
10
+ * 3. 작업 종료 (finalizeWork): 남은 변경사항 커밋 및 푸시
11
+ * 4. 머지 (mergeToTarget): 다음 단계로 머지
12
+ *
13
+ * 생명주기 상태:
14
+ * - IDLE: 초기 상태 / 작업 종료 후
15
+ * - PREPARING: 브랜치 준비 중
16
+ * - WORKING: 작업 진행 중
17
+ * - COMMITTING: 커밋 중
18
+ * - PUSHING: 푸시 중
19
+ * - MERGING: 머지 중
20
+ * - ERROR: 오류 발생
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.GitLifecycleManager = exports.BranchType = exports.GitLifecyclePhase = void 0;
57
+ exports.getGitLifecycleManager = getGitLifecycleManager;
58
+ exports.resetGitLifecycleManager = resetGitLifecycleManager;
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const git = __importStar(require("../utils/git"));
62
+ const logger = __importStar(require("../utils/logger"));
63
+ const events_1 = require("../utils/events");
64
+ const event_categories_1 = require("../types/event-categories");
65
+ // ============================================================================
66
+ // Types & Enums
67
+ // ============================================================================
68
+ /**
69
+ * Git 작업 생명주기 상태
70
+ */
71
+ var GitLifecyclePhase;
72
+ (function (GitLifecyclePhase) {
73
+ /** 대기 상태 */
74
+ GitLifecyclePhase["IDLE"] = "IDLE";
75
+ /** 브랜치/워크트리 준비 중 */
76
+ GitLifecyclePhase["PREPARING"] = "PREPARING";
77
+ /** 작업 진행 중 */
78
+ GitLifecyclePhase["WORKING"] = "WORKING";
79
+ /** 커밋 중 */
80
+ GitLifecyclePhase["COMMITTING"] = "COMMITTING";
81
+ /** 푸시 중 */
82
+ GitLifecyclePhase["PUSHING"] = "PUSHING";
83
+ /** 머지 중 */
84
+ GitLifecyclePhase["MERGING"] = "MERGING";
85
+ /** 오류 발생 */
86
+ GitLifecyclePhase["ERROR"] = "ERROR";
87
+ })(GitLifecyclePhase || (exports.GitLifecyclePhase = GitLifecyclePhase = {}));
88
+ /**
89
+ * 브랜치 유형
90
+ */
91
+ var BranchType;
92
+ (function (BranchType) {
93
+ /** Pipeline 브랜치 (Lane당 하나) */
94
+ BranchType["PIPELINE"] = "pipeline";
95
+ /** Task 브랜치 (Task당 하나) */
96
+ BranchType["TASK"] = "task";
97
+ /** Flow 브랜치 (최종 결과) */
98
+ BranchType["FLOW"] = "flow";
99
+ })(BranchType || (exports.BranchType = BranchType = {}));
100
+ // ============================================================================
101
+ // Git Lifecycle Manager
102
+ // ============================================================================
103
+ /**
104
+ * Git 파이프라인 생명주기 관리자
105
+ *
106
+ * 사용 예:
107
+ * ```typescript
108
+ * const gitManager = GitLifecycleManager.getInstance();
109
+ *
110
+ * // 1. 작업 시작
111
+ * await gitManager.startWork({
112
+ * worktreeDir: '/path/to/worktree',
113
+ * branchName: 'cursorflow/task-1',
114
+ * baseBranch: 'main',
115
+ * repoRoot: '/path/to/repo',
116
+ * laneName: 'lane-1'
117
+ * });
118
+ *
119
+ * // 2. 작업 진행...
120
+ *
121
+ * // 3. 작업 종료 (모든 변경사항 커밋 및 푸시)
122
+ * await gitManager.finalizeWork({
123
+ * worktreeDir: '/path/to/worktree',
124
+ * branchName: 'cursorflow/task-1',
125
+ * commitMessage: 'feat: implement feature',
126
+ * laneName: 'lane-1'
127
+ * });
128
+ *
129
+ * // 4. 파이프라인 브랜치로 머지
130
+ * await gitManager.mergeToTarget({
131
+ * worktreeDir: '/path/to/worktree',
132
+ * sourceBranch: 'cursorflow/task-1',
133
+ * targetBranch: 'cursorflow/pipeline',
134
+ * laneName: 'lane-1',
135
+ * mergeType: 'task_to_pipeline'
136
+ * });
137
+ * ```
138
+ */
139
+ class GitLifecycleManager {
140
+ static instance = null;
141
+ /** Lane별 상태 추적 */
142
+ laneStates = new Map();
143
+ /** 디버그 모드 */
144
+ verbose = false;
145
+ constructor() {
146
+ this.verbose = process.env['DEBUG_GIT'] === 'true';
147
+ }
148
+ /**
149
+ * 싱글톤 인스턴스 획득
150
+ */
151
+ static getInstance() {
152
+ if (!GitLifecycleManager.instance) {
153
+ GitLifecycleManager.instance = new GitLifecycleManager();
154
+ }
155
+ return GitLifecycleManager.instance;
156
+ }
157
+ /**
158
+ * 인스턴스 리셋 (테스트용)
159
+ */
160
+ static resetInstance() {
161
+ GitLifecycleManager.instance = null;
162
+ }
163
+ // --------------------------------------------------------------------------
164
+ // Lane State Management
165
+ // --------------------------------------------------------------------------
166
+ /**
167
+ * Lane의 Git 상태 초기화
168
+ */
169
+ initializeLane(laneName, pipelineBranch, worktreeDir) {
170
+ this.laneStates.set(laneName, {
171
+ laneName,
172
+ phase: GitLifecyclePhase.IDLE,
173
+ currentBranch: null,
174
+ pipelineBranch,
175
+ worktreeDir: worktreeDir || null,
176
+ lastCommitHash: null,
177
+ uncommittedChanges: false,
178
+ lastOperation: null,
179
+ lastOperationTime: null,
180
+ error: null,
181
+ });
182
+ this.log(`[${laneName}] Git state initialized (pipeline: ${pipelineBranch})`);
183
+ }
184
+ /**
185
+ * Lane의 Git 상태 조회
186
+ */
187
+ getLaneState(laneName) {
188
+ return this.laneStates.get(laneName);
189
+ }
190
+ /**
191
+ * Lane 상태 업데이트
192
+ */
193
+ updateLaneState(laneName, updates) {
194
+ const state = this.laneStates.get(laneName);
195
+ if (state) {
196
+ Object.assign(state, updates, { lastOperationTime: Date.now() });
197
+ }
198
+ }
199
+ // --------------------------------------------------------------------------
200
+ // 1. Work Start (작업 시작)
201
+ // --------------------------------------------------------------------------
202
+ /**
203
+ * 작업 시작 - 브랜치 생성 및 체크아웃
204
+ *
205
+ * 1. 워크트리가 없으면 생성
206
+ * 2. 브랜치가 없으면 baseBranch에서 생성
207
+ * 3. 의존성 브랜치들 머지 (있는 경우)
208
+ * 4. 브랜치 체크아웃
209
+ */
210
+ async startWork(options) {
211
+ const { worktreeDir, branchName, baseBranch, repoRoot, laneName = 'unknown', taskName, branchType = BranchType.TASK, dependencyBranches = [], } = options;
212
+ this.updateLaneState(laneName, {
213
+ phase: GitLifecyclePhase.PREPARING,
214
+ lastOperation: 'startWork',
215
+ error: null,
216
+ });
217
+ try {
218
+ // 1. 워크트리 확인/생성
219
+ const worktreeResult = await this.ensureWorktree(worktreeDir, branchName, baseBranch, repoRoot, laneName);
220
+ if (!worktreeResult.success) {
221
+ return worktreeResult;
222
+ }
223
+ // 2. 브랜치 체크아웃
224
+ this.log(`[${laneName}] Checking out branch: ${branchName}`);
225
+ git.runGit(['checkout', branchName], { cwd: worktreeDir });
226
+ events_1.events.emit(event_categories_1.GitEventType.BRANCH_CHECKED_OUT, {
227
+ laneName,
228
+ branchName,
229
+ });
230
+ // 3. 의존성 브랜치 머지 (있는 경우)
231
+ if (dependencyBranches.length > 0) {
232
+ for (const depBranch of dependencyBranches) {
233
+ const mergeResult = await this.mergeDependencyBranch(worktreeDir, depBranch, branchName, laneName);
234
+ if (!mergeResult.success) {
235
+ this.log(`[${laneName}] Warning: Failed to merge dependency ${depBranch}: ${mergeResult.error}`);
236
+ // 의존성 머지 실패는 경고만 하고 계속 진행
237
+ }
238
+ }
239
+ }
240
+ // 4. 상태 업데이트
241
+ this.updateLaneState(laneName, {
242
+ phase: GitLifecyclePhase.WORKING,
243
+ currentBranch: branchName,
244
+ worktreeDir,
245
+ uncommittedChanges: false,
246
+ });
247
+ this.log(`[${laneName}] Work started on branch: ${branchName}`);
248
+ return { success: true, details: { branchName, branchType } };
249
+ }
250
+ catch (error) {
251
+ const errorMsg = `Failed to start work: ${error.message}`;
252
+ this.updateLaneState(laneName, {
253
+ phase: GitLifecyclePhase.ERROR,
254
+ error: errorMsg,
255
+ });
256
+ events_1.events.emit(event_categories_1.GitEventType.ERROR, {
257
+ laneName,
258
+ operation: 'startWork',
259
+ error: errorMsg,
260
+ recoverable: true,
261
+ });
262
+ return { success: false, error: errorMsg };
263
+ }
264
+ }
265
+ /**
266
+ * 워크트리 확인 및 생성
267
+ */
268
+ async ensureWorktree(worktreeDir, branchName, baseBranch, repoRoot, laneName) {
269
+ const worktreeExists = fs.existsSync(worktreeDir);
270
+ const worktreeIsValid = worktreeExists && git.isValidWorktree(worktreeDir);
271
+ if (worktreeExists && !worktreeIsValid) {
272
+ this.log(`[${laneName}] Invalid worktree detected, cleaning up: ${worktreeDir}`);
273
+ try {
274
+ git.cleanupInvalidWorktreeDir(worktreeDir);
275
+ }
276
+ catch (e) {
277
+ return { success: false, error: `Failed to cleanup invalid worktree: ${e.message}` };
278
+ }
279
+ }
280
+ if (!worktreeExists || !worktreeIsValid) {
281
+ this.log(`[${laneName}] Creating worktree: ${worktreeDir} (branch: ${branchName})`);
282
+ // 부모 디렉토리 생성
283
+ const worktreeParent = path.dirname(worktreeDir);
284
+ if (!fs.existsSync(worktreeParent)) {
285
+ fs.mkdirSync(worktreeParent, { recursive: true });
286
+ }
287
+ // 재시도 로직
288
+ let retries = 3;
289
+ let lastError = null;
290
+ while (retries > 0) {
291
+ try {
292
+ await git.createWorktreeAsync(worktreeDir, branchName, {
293
+ baseBranch,
294
+ cwd: repoRoot,
295
+ });
296
+ events_1.events.emit(event_categories_1.GitEventType.WORKTREE_CREATED, {
297
+ laneName,
298
+ worktreeDir,
299
+ branchName,
300
+ });
301
+ events_1.events.emit(event_categories_1.GitEventType.BRANCH_CREATED, {
302
+ laneName,
303
+ branchName,
304
+ baseBranch,
305
+ worktreeDir,
306
+ });
307
+ return { success: true };
308
+ }
309
+ catch (e) {
310
+ lastError = e;
311
+ retries--;
312
+ if (retries > 0) {
313
+ const delay = Math.floor(Math.random() * 1000) + 500;
314
+ this.log(`[${laneName}] Worktree creation failed, retrying in ${delay}ms... (${retries} retries left)`);
315
+ await new Promise(resolve => setTimeout(resolve, delay));
316
+ }
317
+ }
318
+ }
319
+ return { success: false, error: `Failed to create worktree after retries: ${lastError?.message}` };
320
+ }
321
+ // 기존 워크트리 재사용
322
+ this.log(`[${laneName}] Reusing existing worktree: ${worktreeDir}`);
323
+ return { success: true };
324
+ }
325
+ /**
326
+ * 의존성 브랜치 머지
327
+ */
328
+ async mergeDependencyBranch(worktreeDir, depBranch, currentBranch, laneName) {
329
+ try {
330
+ // 원격에서 fetch
331
+ git.runGit(['fetch', 'origin', depBranch], { cwd: worktreeDir, silent: true });
332
+ const remoteBranch = `origin/${depBranch}`;
333
+ // 충돌 사전 체크
334
+ const conflictCheck = git.checkMergeConflict(remoteBranch, { cwd: worktreeDir });
335
+ if (conflictCheck.willConflict) {
336
+ events_1.events.emit(event_categories_1.GitEventType.MERGE_CONFLICT, {
337
+ laneName,
338
+ sourceBranch: depBranch,
339
+ targetBranch: currentBranch,
340
+ conflictingFiles: conflictCheck.conflictingFiles,
341
+ preCheck: true,
342
+ });
343
+ return {
344
+ success: false,
345
+ error: `Merge conflict detected: ${conflictCheck.conflictingFiles.join(', ')}`
346
+ };
347
+ }
348
+ // 머지 실행
349
+ const mergeResult = git.safeMerge(remoteBranch, {
350
+ cwd: worktreeDir,
351
+ noFf: true,
352
+ message: `chore: merge dependency from ${depBranch}`,
353
+ abortOnConflict: true,
354
+ });
355
+ if (!mergeResult.success) {
356
+ return { success: false, error: mergeResult.error || 'Merge failed' };
357
+ }
358
+ events_1.events.emit(event_categories_1.GitEventType.DEPENDENCY_SYNCED, {
359
+ laneName,
360
+ sourceBranch: depBranch,
361
+ targetBranch: currentBranch,
362
+ });
363
+ return { success: true };
364
+ }
365
+ catch (error) {
366
+ return { success: false, error: error.message };
367
+ }
368
+ }
369
+ // --------------------------------------------------------------------------
370
+ // 2. Save Progress (진행 상황 저장 - 선택적)
371
+ // --------------------------------------------------------------------------
372
+ /**
373
+ * 진행 상황 저장 (중간 커밋)
374
+ *
375
+ * 장기 작업 중 주기적으로 호출하여 진행 상황을 저장할 수 있습니다.
376
+ */
377
+ async saveProgress(worktreeDir, message, laneName = 'unknown') {
378
+ try {
379
+ // 변경사항 확인
380
+ const status = git.runGit(['status', '--porcelain'], { cwd: worktreeDir });
381
+ if (!status.trim()) {
382
+ this.log(`[${laneName}] No changes to save`);
383
+ return { success: true, filesChanged: 0 };
384
+ }
385
+ this.updateLaneState(laneName, {
386
+ phase: GitLifecyclePhase.COMMITTING,
387
+ lastOperation: 'saveProgress',
388
+ });
389
+ // Stage all changes
390
+ git.runGit(['add', '-A'], { cwd: worktreeDir });
391
+ // Commit
392
+ git.runGit(['commit', '-m', message], { cwd: worktreeDir });
393
+ const commitHash = git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir }).trim();
394
+ // 변경된 파일 수 계산
395
+ const filesChangedOutput = git.runGit(['diff', '--stat', 'HEAD~1', 'HEAD'], { cwd: worktreeDir, silent: true });
396
+ const filesChanged = (filesChangedOutput.match(/\d+ file/g) || []).length || 1;
397
+ this.updateLaneState(laneName, {
398
+ phase: GitLifecyclePhase.WORKING,
399
+ lastCommitHash: commitHash,
400
+ uncommittedChanges: false,
401
+ });
402
+ events_1.events.emit(event_categories_1.GitEventType.COMMITTED, {
403
+ laneName,
404
+ branchName: this.getLaneState(laneName)?.currentBranch || 'unknown',
405
+ commitHash,
406
+ message,
407
+ filesChanged,
408
+ });
409
+ this.log(`[${laneName}] Progress saved: ${commitHash.substring(0, 7)}`);
410
+ return { success: true, commitHash, filesChanged };
411
+ }
412
+ catch (error) {
413
+ const errorMsg = `Failed to save progress: ${error.message}`;
414
+ this.updateLaneState(laneName, {
415
+ phase: GitLifecyclePhase.WORKING, // 복구 가능한 오류
416
+ error: errorMsg,
417
+ });
418
+ return { success: false, error: errorMsg };
419
+ }
420
+ }
421
+ // --------------------------------------------------------------------------
422
+ // 3. Finalize Work (작업 종료)
423
+ // --------------------------------------------------------------------------
424
+ /**
425
+ * 작업 종료 - 모든 변경사항 커밋 및 푸시
426
+ *
427
+ * 1. 남은 변경사항 모두 스테이징
428
+ * 2. 커밋 생성
429
+ * 3. 원격으로 푸시
430
+ */
431
+ async finalizeWork(options) {
432
+ const { worktreeDir, branchName, commitMessage = 'chore: finalize work', laneName = 'unknown', taskName, push = true, remote = 'origin', skipIfClean = false, } = options;
433
+ try {
434
+ // 1. 변경사항 확인
435
+ const status = git.runGit(['status', '--porcelain'], { cwd: worktreeDir });
436
+ const hasChanges = !!status.trim();
437
+ if (!hasChanges && skipIfClean) {
438
+ this.log(`[${laneName}] No changes to finalize, skipping`);
439
+ return { success: true, filesChanged: 0 };
440
+ }
441
+ let commitHash;
442
+ let filesChanged = 0;
443
+ // 2. 변경사항이 있으면 커밋
444
+ if (hasChanges) {
445
+ this.updateLaneState(laneName, {
446
+ phase: GitLifecyclePhase.COMMITTING,
447
+ lastOperation: 'finalizeWork',
448
+ });
449
+ // Stage all changes
450
+ git.runGit(['add', '-A'], { cwd: worktreeDir });
451
+ // Commit
452
+ const fullMessage = taskName
453
+ ? `${commitMessage}\n\nTask: ${taskName}`
454
+ : commitMessage;
455
+ git.runGit(['commit', '-m', fullMessage], { cwd: worktreeDir });
456
+ commitHash = git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir }).trim();
457
+ // 변경된 파일 수 계산
458
+ const filesChangedOutput = git.runGit(['diff', '--stat', 'HEAD~1', 'HEAD'], { cwd: worktreeDir, silent: true });
459
+ filesChanged = (filesChangedOutput.match(/\d+ file/g) || []).length || 1;
460
+ events_1.events.emit(event_categories_1.GitEventType.COMMITTED, {
461
+ laneName,
462
+ branchName,
463
+ commitHash,
464
+ message: commitMessage,
465
+ filesChanged,
466
+ });
467
+ this.log(`[${laneName}] Changes committed: ${commitHash.substring(0, 7)} (${filesChanged} files)`);
468
+ }
469
+ // 3. 푸시
470
+ if (push) {
471
+ this.updateLaneState(laneName, {
472
+ phase: GitLifecyclePhase.PUSHING,
473
+ });
474
+ try {
475
+ git.push(branchName, { cwd: worktreeDir, setUpstream: true });
476
+ events_1.events.emit(event_categories_1.GitEventType.PUSHED, {
477
+ laneName,
478
+ branchName,
479
+ remote,
480
+ commitHash: commitHash || git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir }).trim(),
481
+ });
482
+ this.log(`[${laneName}] Pushed to ${remote}/${branchName}`);
483
+ }
484
+ catch (pushError) {
485
+ // 푸시 실패 처리
486
+ events_1.events.emit(event_categories_1.GitEventType.PUSH_REJECTED, {
487
+ laneName,
488
+ branchName,
489
+ reason: pushError.message,
490
+ hint: 'Try pulling remote changes first',
491
+ });
492
+ // 푸시 실패는 별도 처리 가능하도록 세부 정보 반환
493
+ return {
494
+ success: false,
495
+ error: `Push failed: ${pushError.message}`,
496
+ commitHash,
497
+ filesChanged,
498
+ details: { pushFailed: true },
499
+ };
500
+ }
501
+ }
502
+ // 4. 상태 업데이트
503
+ this.updateLaneState(laneName, {
504
+ phase: GitLifecyclePhase.IDLE,
505
+ lastCommitHash: commitHash || this.getLaneState(laneName)?.lastCommitHash,
506
+ uncommittedChanges: false,
507
+ error: null,
508
+ });
509
+ return { success: true, commitHash, filesChanged };
510
+ }
511
+ catch (error) {
512
+ const errorMsg = `Failed to finalize work: ${error.message}`;
513
+ this.updateLaneState(laneName, {
514
+ phase: GitLifecyclePhase.ERROR,
515
+ error: errorMsg,
516
+ });
517
+ events_1.events.emit(event_categories_1.GitEventType.ERROR, {
518
+ laneName,
519
+ operation: 'finalizeWork',
520
+ error: errorMsg,
521
+ recoverable: true,
522
+ });
523
+ return { success: false, error: errorMsg };
524
+ }
525
+ }
526
+ // --------------------------------------------------------------------------
527
+ // 4. Merge to Target (머지)
528
+ // --------------------------------------------------------------------------
529
+ /**
530
+ * 타겟 브랜치로 머지
531
+ *
532
+ * 사용 사례:
533
+ * - Task → Pipeline 머지
534
+ * - 의존성 브랜치 머지
535
+ * - 최종 Flow 브랜치 생성
536
+ */
537
+ async mergeToTarget(options) {
538
+ const { worktreeDir, sourceBranch, targetBranch, commitMessage, laneName = 'unknown', mergeType = 'task_to_pipeline', abortOnConflict = true, push = true, } = options;
539
+ this.updateLaneState(laneName, {
540
+ phase: GitLifecyclePhase.MERGING,
541
+ lastOperation: 'mergeToTarget',
542
+ });
543
+ try {
544
+ // 1. 타겟 브랜치로 체크아웃
545
+ this.log(`[${laneName}] Checking out ${targetBranch} for merge`);
546
+ git.runGit(['checkout', targetBranch], { cwd: worktreeDir });
547
+ events_1.events.emit(event_categories_1.GitEventType.MERGE_STARTED, {
548
+ laneName,
549
+ sourceBranch,
550
+ targetBranch,
551
+ mergeType,
552
+ });
553
+ // 2. 충돌 사전 체크
554
+ const conflictCheck = git.checkMergeConflict(sourceBranch, { cwd: worktreeDir });
555
+ if (conflictCheck.willConflict) {
556
+ events_1.events.emit(event_categories_1.GitEventType.MERGE_CONFLICT, {
557
+ laneName,
558
+ sourceBranch,
559
+ targetBranch,
560
+ conflictingFiles: conflictCheck.conflictingFiles,
561
+ preCheck: true,
562
+ });
563
+ if (abortOnConflict) {
564
+ this.updateLaneState(laneName, {
565
+ phase: GitLifecyclePhase.ERROR,
566
+ error: `Merge conflict: ${conflictCheck.conflictingFiles.join(', ')}`,
567
+ });
568
+ return {
569
+ success: false,
570
+ error: `Merge conflict detected: ${conflictCheck.conflictingFiles.join(', ')}`,
571
+ details: { conflictingFiles: conflictCheck.conflictingFiles },
572
+ };
573
+ }
574
+ }
575
+ // 3. 머지 실행
576
+ const mergeMsg = commitMessage || `chore: merge ${sourceBranch} into ${targetBranch}`;
577
+ const mergeResult = git.safeMerge(sourceBranch, {
578
+ cwd: worktreeDir,
579
+ noFf: true,
580
+ message: mergeMsg,
581
+ abortOnConflict,
582
+ });
583
+ if (!mergeResult.success) {
584
+ if (mergeResult.conflict) {
585
+ events_1.events.emit(event_categories_1.GitEventType.MERGE_CONFLICT, {
586
+ laneName,
587
+ sourceBranch,
588
+ targetBranch,
589
+ conflictingFiles: mergeResult.conflictingFiles,
590
+ preCheck: false,
591
+ });
592
+ }
593
+ this.updateLaneState(laneName, {
594
+ phase: GitLifecyclePhase.ERROR,
595
+ error: mergeResult.error || 'Merge failed',
596
+ });
597
+ return {
598
+ success: false,
599
+ error: mergeResult.error || 'Merge failed',
600
+ details: { conflictingFiles: mergeResult.conflictingFiles },
601
+ };
602
+ }
603
+ const mergeCommit = git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir }).trim();
604
+ // 변경된 파일 수 계산
605
+ const stats = git.getLastOperationStats(worktreeDir);
606
+ const filesChangedMatch = stats?.match(/(\d+) file/);
607
+ const filesChanged = filesChangedMatch ? parseInt(filesChangedMatch[1], 10) : 0;
608
+ events_1.events.emit(event_categories_1.GitEventType.MERGE_COMPLETED, {
609
+ laneName,
610
+ sourceBranch,
611
+ targetBranch,
612
+ mergeCommit,
613
+ filesChanged,
614
+ });
615
+ this.log(`[${laneName}] Merged ${sourceBranch} → ${targetBranch} (${mergeCommit.substring(0, 7)})`);
616
+ // 4. 푸시
617
+ if (push) {
618
+ this.updateLaneState(laneName, {
619
+ phase: GitLifecyclePhase.PUSHING,
620
+ });
621
+ try {
622
+ git.push(targetBranch, { cwd: worktreeDir, setUpstream: true });
623
+ events_1.events.emit(event_categories_1.GitEventType.PUSHED, {
624
+ laneName,
625
+ branchName: targetBranch,
626
+ remote: 'origin',
627
+ commitHash: mergeCommit,
628
+ });
629
+ }
630
+ catch (pushError) {
631
+ events_1.events.emit(event_categories_1.GitEventType.PUSH_REJECTED, {
632
+ laneName,
633
+ branchName: targetBranch,
634
+ reason: pushError.message,
635
+ });
636
+ return {
637
+ success: false,
638
+ error: `Merge succeeded but push failed: ${pushError.message}`,
639
+ commitHash: mergeCommit,
640
+ filesChanged,
641
+ details: { pushFailed: true },
642
+ };
643
+ }
644
+ }
645
+ // 5. 상태 업데이트
646
+ this.updateLaneState(laneName, {
647
+ phase: GitLifecyclePhase.IDLE,
648
+ currentBranch: targetBranch,
649
+ lastCommitHash: mergeCommit,
650
+ error: null,
651
+ });
652
+ return { success: true, commitHash: mergeCommit, filesChanged };
653
+ }
654
+ catch (error) {
655
+ const errorMsg = `Failed to merge: ${error.message}`;
656
+ this.updateLaneState(laneName, {
657
+ phase: GitLifecyclePhase.ERROR,
658
+ error: errorMsg,
659
+ });
660
+ events_1.events.emit(event_categories_1.GitEventType.ERROR, {
661
+ laneName,
662
+ operation: 'mergeToTarget',
663
+ error: errorMsg,
664
+ recoverable: true,
665
+ });
666
+ return { success: false, error: errorMsg };
667
+ }
668
+ }
669
+ // --------------------------------------------------------------------------
670
+ // 5. Cleanup (정리)
671
+ // --------------------------------------------------------------------------
672
+ /**
673
+ * 워크트리 정리
674
+ */
675
+ async cleanupWorktree(worktreeDir, laneName = 'unknown') {
676
+ try {
677
+ if (!fs.existsSync(worktreeDir)) {
678
+ return { success: true };
679
+ }
680
+ git.cleanupInvalidWorktreeDir(worktreeDir);
681
+ events_1.events.emit(event_categories_1.GitEventType.WORKTREE_CLEANED, {
682
+ laneName,
683
+ worktreeDir,
684
+ });
685
+ this.log(`[${laneName}] Worktree cleaned: ${worktreeDir}`);
686
+ return { success: true };
687
+ }
688
+ catch (error) {
689
+ return { success: false, error: `Failed to cleanup worktree: ${error.message}` };
690
+ }
691
+ }
692
+ /**
693
+ * Lane 상태 정리
694
+ */
695
+ cleanupLane(laneName) {
696
+ this.laneStates.delete(laneName);
697
+ this.log(`[${laneName}] Git state cleaned up`);
698
+ }
699
+ // --------------------------------------------------------------------------
700
+ // Utility Methods
701
+ // --------------------------------------------------------------------------
702
+ /**
703
+ * 현재 브랜치에 미커밋 변경사항이 있는지 확인
704
+ */
705
+ hasUncommittedChanges(worktreeDir) {
706
+ try {
707
+ const status = git.runGit(['status', '--porcelain'], { cwd: worktreeDir });
708
+ return !!status.trim();
709
+ }
710
+ catch {
711
+ return false;
712
+ }
713
+ }
714
+ /**
715
+ * 안전하게 모든 변경사항 커밋 (오류 무시)
716
+ */
717
+ async safeCommitAll(worktreeDir, message, laneName = 'unknown') {
718
+ try {
719
+ if (!this.hasUncommittedChanges(worktreeDir)) {
720
+ return { success: true, filesChanged: 0 };
721
+ }
722
+ git.runGit(['add', '-A'], { cwd: worktreeDir });
723
+ git.runGit(['commit', '-m', message, '--no-verify'], { cwd: worktreeDir });
724
+ const commitHash = git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir }).trim();
725
+ return { success: true, commitHash };
726
+ }
727
+ catch (error) {
728
+ // 오류 무시하고 반환
729
+ return { success: false, error: error.message };
730
+ }
731
+ }
732
+ /**
733
+ * 브랜치가 원격에 존재하는지 확인
734
+ */
735
+ branchExistsRemote(branchName, worktreeDir) {
736
+ try {
737
+ git.runGit(['ls-remote', '--heads', 'origin', branchName], { cwd: worktreeDir });
738
+ return true;
739
+ }
740
+ catch {
741
+ return false;
742
+ }
743
+ }
744
+ /**
745
+ * 로깅 헬퍼
746
+ */
747
+ log(message) {
748
+ if (this.verbose) {
749
+ logger.debug(`[GitLifecycle] ${message}`);
750
+ }
751
+ else {
752
+ logger.info(`[GitLifecycle] ${message}`);
753
+ }
754
+ }
755
+ /**
756
+ * 디버그 모드 설정
757
+ */
758
+ setVerbose(verbose) {
759
+ this.verbose = verbose;
760
+ }
761
+ }
762
+ exports.GitLifecycleManager = GitLifecycleManager;
763
+ // ============================================================================
764
+ // Convenience Functions
765
+ // ============================================================================
766
+ /**
767
+ * 싱글톤 인스턴스 획득
768
+ */
769
+ function getGitLifecycleManager() {
770
+ return GitLifecycleManager.getInstance();
771
+ }
772
+ /**
773
+ * 인스턴스 리셋 (테스트용)
774
+ */
775
+ function resetGitLifecycleManager() {
776
+ GitLifecycleManager.resetInstance();
777
+ }
778
+ //# sourceMappingURL=git-lifecycle-manager.js.map