@litmers/cursorflow-orchestrator 0.1.40 → 0.2.3

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 +8 -3
  3. package/commands/cursorflow-init.md +0 -4
  4. package/dist/cli/index.js +0 -6
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/logs.js +108 -9
  7. package/dist/cli/logs.js.map +1 -1
  8. package/dist/cli/models.js +20 -3
  9. package/dist/cli/models.js.map +1 -1
  10. package/dist/cli/monitor.d.ts +7 -10
  11. package/dist/cli/monitor.js +1103 -1239
  12. package/dist/cli/monitor.js.map +1 -1
  13. package/dist/cli/resume.js +21 -1
  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 +99 -13
  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 +3 -117
  26. package/dist/core/auto-recovery.js +4 -482
  27. package/dist/core/auto-recovery.js.map +1 -1
  28. package/dist/core/failure-policy.d.ts +0 -53
  29. package/dist/core/failure-policy.js +7 -175
  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 +170 -0
  38. package/dist/core/intervention.js +408 -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 +39 -65
  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 +54 -36
  48. package/dist/core/runner/agent.js.map +1 -1
  49. package/dist/core/runner/pipeline.js +283 -123
  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 -80
  53. package/dist/core/runner/task.js.map +1 -1
  54. package/dist/core/runner.js +8 -2
  55. package/dist/core/runner.js.map +1 -1
  56. package/dist/core/stall-detection.d.ts +11 -4
  57. package/dist/core/stall-detection.js +64 -27
  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 +6 -1
  81. package/dist/services/logging/console.js.map +1 -1
  82. package/dist/services/logging/formatter.d.ts +9 -4
  83. package/dist/services/logging/formatter.js +64 -18
  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 +22 -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 +12 -1
  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 +10 -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 +98 -19
  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/log-constants.d.ts +1 -0
  129. package/dist/utils/log-constants.js +2 -1
  130. package/dist/utils/log-constants.js.map +1 -1
  131. package/dist/utils/log-formatter.d.ts +2 -1
  132. package/dist/utils/log-formatter.js +10 -10
  133. package/dist/utils/log-formatter.js.map +1 -1
  134. package/dist/utils/logger.d.ts +11 -0
  135. package/dist/utils/logger.js +82 -3
  136. package/dist/utils/logger.js.map +1 -1
  137. package/dist/utils/repro-thinking-logs.js +0 -13
  138. package/dist/utils/repro-thinking-logs.js.map +1 -1
  139. package/dist/utils/run-service.js +1 -1
  140. package/dist/utils/run-service.js.map +1 -1
  141. package/examples/README.md +0 -2
  142. package/examples/demo-project/README.md +1 -2
  143. package/package.json +13 -34
  144. package/scripts/setup-security.sh +0 -1
  145. package/scripts/test-log-parser.ts +171 -0
  146. package/scripts/verify-change.sh +272 -0
  147. package/src/cli/index.ts +0 -6
  148. package/src/cli/logs.ts +121 -10
  149. package/src/cli/models.ts +20 -3
  150. package/src/cli/monitor.ts +1273 -1342
  151. package/src/cli/resume.ts +27 -1
  152. package/src/cli/run.ts +29 -11
  153. package/src/cli/signal.ts +120 -18
  154. package/src/cli/tasks.ts +2 -59
  155. package/src/core/agent-supervisor.ts +64 -0
  156. package/src/core/auto-recovery.ts +14 -590
  157. package/src/core/failure-policy.ts +7 -229
  158. package/src/core/git-lifecycle-manager.ts +1011 -0
  159. package/src/core/git-pipeline-coordinator.ts +221 -0
  160. package/src/core/intervention.ts +463 -0
  161. package/src/core/lane-state-machine.ts +1097 -0
  162. package/src/core/orchestrator.ts +48 -64
  163. package/src/core/runner/agent.ts +77 -39
  164. package/src/core/runner/pipeline.ts +318 -138
  165. package/src/core/runner/task.ts +12 -97
  166. package/src/core/runner.ts +8 -2
  167. package/src/core/stall-detection.ts +74 -27
  168. package/src/hooks/contexts/index.ts +256 -0
  169. package/src/hooks/data-accessor.ts +488 -0
  170. package/src/hooks/flow-controller.ts +425 -0
  171. package/src/hooks/index.ts +154 -0
  172. package/src/hooks/manager.ts +434 -0
  173. package/src/hooks/types.ts +544 -0
  174. package/src/services/logging/buffer.ts +104 -43
  175. package/src/services/logging/console.ts +7 -1
  176. package/src/services/logging/formatter.ts +74 -18
  177. package/src/services/logging/index.ts +0 -2
  178. package/src/services/logging/paths.ts +14 -0
  179. package/src/services/logging/raw-log.ts +43 -0
  180. package/src/services/process/index.ts +1 -1
  181. package/src/types/agent.ts +15 -0
  182. package/src/types/config.ts +23 -1
  183. package/src/types/event-categories.ts +663 -0
  184. package/src/types/events.ts +0 -25
  185. package/src/types/flow.ts +10 -6
  186. package/src/types/index.ts +50 -4
  187. package/src/types/lane.ts +1 -2
  188. package/src/types/logging.ts +2 -1
  189. package/src/types/task.ts +12 -1
  190. package/src/ui/log-viewer.ts +3 -0
  191. package/src/utils/config.ts +11 -1
  192. package/src/utils/cursor-agent.ts +68 -16
  193. package/src/utils/enhanced-logger.ts +105 -19
  194. package/src/utils/event-registry.ts +595 -0
  195. package/src/utils/events.ts +0 -16
  196. package/src/utils/flow.ts +83 -0
  197. package/src/utils/log-constants.ts +2 -1
  198. package/src/utils/log-formatter.ts +10 -11
  199. package/src/utils/logger.ts +49 -3
  200. package/src/utils/repro-thinking-logs.ts +0 -15
  201. package/src/utils/run-service.ts +1 -1
  202. package/dist/cli/prepare.d.ts +0 -7
  203. package/dist/cli/prepare.js +0 -690
  204. package/dist/cli/prepare.js.map +0 -1
  205. package/dist/services/logging/file-writer.d.ts +0 -71
  206. package/dist/services/logging/file-writer.js +0 -516
  207. package/dist/services/logging/file-writer.js.map +0 -1
  208. package/dist/types/review.d.ts +0 -17
  209. package/dist/types/review.js +0 -6
  210. package/dist/types/review.js.map +0 -1
  211. package/scripts/ai-security-check.js +0 -233
  212. package/src/cli/prepare.ts +0 -777
  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