@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.
- package/CHANGELOG.md +0 -2
- package/README.md +8 -3
- package/commands/cursorflow-init.md +0 -4
- package/dist/cli/index.js +0 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +108 -9
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/models.js +20 -3
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -10
- package/dist/cli/monitor.js +1103 -1239
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/resume.js +21 -1
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +28 -9
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.d.ts +6 -1
- package/dist/cli/signal.js +99 -13
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/tasks.js +3 -46
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/agent-supervisor.d.ts +23 -0
- package/dist/core/agent-supervisor.js +42 -0
- package/dist/core/agent-supervisor.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +3 -117
- package/dist/core/auto-recovery.js +4 -482
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -53
- package/dist/core/failure-policy.js +7 -175
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/git-lifecycle-manager.d.ts +284 -0
- package/dist/core/git-lifecycle-manager.js +778 -0
- package/dist/core/git-lifecycle-manager.js.map +1 -0
- package/dist/core/git-pipeline-coordinator.d.ts +21 -0
- package/dist/core/git-pipeline-coordinator.js +205 -0
- package/dist/core/git-pipeline-coordinator.js.map +1 -0
- package/dist/core/intervention.d.ts +170 -0
- package/dist/core/intervention.js +408 -0
- package/dist/core/intervention.js.map +1 -0
- package/dist/core/lane-state-machine.d.ts +423 -0
- package/dist/core/lane-state-machine.js +890 -0
- package/dist/core/lane-state-machine.js.map +1 -0
- package/dist/core/orchestrator.d.ts +4 -1
- package/dist/core/orchestrator.js +39 -65
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +7 -1
- package/dist/core/runner/agent.js +54 -36
- package/dist/core/runner/agent.js.map +1 -1
- package/dist/core/runner/pipeline.js +283 -123
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/core/runner/task.d.ts +4 -5
- package/dist/core/runner/task.js +6 -80
- package/dist/core/runner/task.js.map +1 -1
- package/dist/core/runner.js +8 -2
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +11 -4
- package/dist/core/stall-detection.js +64 -27
- package/dist/core/stall-detection.js.map +1 -1
- package/dist/hooks/contexts/index.d.ts +104 -0
- package/dist/hooks/contexts/index.js +134 -0
- package/dist/hooks/contexts/index.js.map +1 -0
- package/dist/hooks/data-accessor.d.ts +86 -0
- package/dist/hooks/data-accessor.js +410 -0
- package/dist/hooks/data-accessor.js.map +1 -0
- package/dist/hooks/flow-controller.d.ts +136 -0
- package/dist/hooks/flow-controller.js +351 -0
- package/dist/hooks/flow-controller.js.map +1 -0
- package/dist/hooks/index.d.ts +68 -0
- package/dist/hooks/index.js +105 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/manager.d.ts +129 -0
- package/dist/hooks/manager.js +389 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/types.d.ts +463 -0
- package/dist/hooks/types.js +45 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/services/logging/buffer.d.ts +2 -2
- package/dist/services/logging/buffer.js +95 -42
- package/dist/services/logging/buffer.js.map +1 -1
- package/dist/services/logging/console.js +6 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +9 -4
- package/dist/services/logging/formatter.js +64 -18
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/services/logging/index.d.ts +0 -1
- package/dist/services/logging/index.js +0 -1
- package/dist/services/logging/index.js.map +1 -1
- package/dist/services/logging/paths.d.ts +8 -0
- package/dist/services/logging/paths.js +48 -0
- package/dist/services/logging/paths.js.map +1 -0
- package/dist/services/logging/raw-log.d.ts +6 -0
- package/dist/services/logging/raw-log.js +37 -0
- package/dist/services/logging/raw-log.js.map +1 -0
- package/dist/services/process/index.js +1 -1
- package/dist/services/process/index.js.map +1 -1
- package/dist/types/agent.d.ts +15 -0
- package/dist/types/config.d.ts +22 -1
- package/dist/types/event-categories.d.ts +601 -0
- package/dist/types/event-categories.js +233 -0
- package/dist/types/event-categories.js.map +1 -0
- package/dist/types/events.d.ts +0 -20
- package/dist/types/flow.d.ts +10 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +17 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +1 -1
- package/dist/types/logging.d.ts +1 -1
- package/dist/types/task.d.ts +12 -1
- package/dist/ui/log-viewer.d.ts +3 -0
- package/dist/ui/log-viewer.js +3 -0
- package/dist/ui/log-viewer.js.map +1 -1
- package/dist/utils/config.js +10 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +11 -1
- package/dist/utils/cursor-agent.js +63 -16
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +5 -1
- package/dist/utils/enhanced-logger.js +98 -19
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/event-registry.d.ts +222 -0
- package/dist/utils/event-registry.js +463 -0
- package/dist/utils/event-registry.js.map +1 -0
- package/dist/utils/events.d.ts +1 -13
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/flow.d.ts +10 -0
- package/dist/utils/flow.js +75 -0
- package/dist/utils/flow.js.map +1 -1
- package/dist/utils/log-constants.d.ts +1 -0
- package/dist/utils/log-constants.js +2 -1
- package/dist/utils/log-constants.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +2 -1
- package/dist/utils/log-formatter.js +10 -10
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +82 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/repro-thinking-logs.js +0 -13
- package/dist/utils/repro-thinking-logs.js.map +1 -1
- package/dist/utils/run-service.js +1 -1
- package/dist/utils/run-service.js.map +1 -1
- package/examples/README.md +0 -2
- package/examples/demo-project/README.md +1 -2
- package/package.json +13 -34
- package/scripts/setup-security.sh +0 -1
- package/scripts/test-log-parser.ts +171 -0
- package/scripts/verify-change.sh +272 -0
- package/src/cli/index.ts +0 -6
- package/src/cli/logs.ts +121 -10
- package/src/cli/models.ts +20 -3
- package/src/cli/monitor.ts +1273 -1342
- package/src/cli/resume.ts +27 -1
- package/src/cli/run.ts +29 -11
- package/src/cli/signal.ts +120 -18
- package/src/cli/tasks.ts +2 -59
- package/src/core/agent-supervisor.ts +64 -0
- package/src/core/auto-recovery.ts +14 -590
- package/src/core/failure-policy.ts +7 -229
- package/src/core/git-lifecycle-manager.ts +1011 -0
- package/src/core/git-pipeline-coordinator.ts +221 -0
- package/src/core/intervention.ts +463 -0
- package/src/core/lane-state-machine.ts +1097 -0
- package/src/core/orchestrator.ts +48 -64
- package/src/core/runner/agent.ts +77 -39
- package/src/core/runner/pipeline.ts +318 -138
- package/src/core/runner/task.ts +12 -97
- package/src/core/runner.ts +8 -2
- package/src/core/stall-detection.ts +74 -27
- package/src/hooks/contexts/index.ts +256 -0
- package/src/hooks/data-accessor.ts +488 -0
- package/src/hooks/flow-controller.ts +425 -0
- package/src/hooks/index.ts +154 -0
- package/src/hooks/manager.ts +434 -0
- package/src/hooks/types.ts +544 -0
- package/src/services/logging/buffer.ts +104 -43
- package/src/services/logging/console.ts +7 -1
- package/src/services/logging/formatter.ts +74 -18
- package/src/services/logging/index.ts +0 -2
- package/src/services/logging/paths.ts +14 -0
- package/src/services/logging/raw-log.ts +43 -0
- package/src/services/process/index.ts +1 -1
- package/src/types/agent.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/event-categories.ts +663 -0
- package/src/types/events.ts +0 -25
- package/src/types/flow.ts +10 -6
- package/src/types/index.ts +50 -4
- package/src/types/lane.ts +1 -2
- package/src/types/logging.ts +2 -1
- package/src/types/task.ts +12 -1
- package/src/ui/log-viewer.ts +3 -0
- package/src/utils/config.ts +11 -1
- package/src/utils/cursor-agent.ts +68 -16
- package/src/utils/enhanced-logger.ts +105 -19
- package/src/utils/event-registry.ts +595 -0
- package/src/utils/events.ts +0 -16
- package/src/utils/flow.ts +83 -0
- package/src/utils/log-constants.ts +2 -1
- package/src/utils/log-formatter.ts +10 -11
- package/src/utils/logger.ts +49 -3
- package/src/utils/repro-thinking-logs.ts +0 -15
- package/src/utils/run-service.ts +1 -1
- package/dist/cli/prepare.d.ts +0 -7
- package/dist/cli/prepare.js +0 -690
- package/dist/cli/prepare.js.map +0 -1
- package/dist/services/logging/file-writer.d.ts +0 -71
- package/dist/services/logging/file-writer.js +0 -516
- package/dist/services/logging/file-writer.js.map +0 -1
- package/dist/types/review.d.ts +0 -17
- package/dist/types/review.js +0 -6
- package/dist/types/review.js.map +0 -1
- package/scripts/ai-security-check.js +0 -233
- package/src/cli/prepare.ts +0 -777
- package/src/services/logging/file-writer.ts +0 -526
- package/src/types/review.ts +0 -20
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow Hook System - Hook Manager
|
|
3
|
+
*
|
|
4
|
+
* Hook 등록 및 실행을 관리하는 중앙 매니저입니다.
|
|
5
|
+
* 외부 개발자가 Hook을 등록하고 CursorFlow가 적절한 시점에 실행합니다.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { hooks, HookPoint } from '@litmers/cursorflow-orchestrator';
|
|
10
|
+
*
|
|
11
|
+
* hooks.register({
|
|
12
|
+
* point: HookPoint.AFTER_TASK,
|
|
13
|
+
* mode: 'sync',
|
|
14
|
+
* handler: async (ctx) => {
|
|
15
|
+
* // Your logic here
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as logger from '../utils/logger';
|
|
22
|
+
import {
|
|
23
|
+
HookPoint,
|
|
24
|
+
HookMode,
|
|
25
|
+
HookRegistration,
|
|
26
|
+
HookExecutionResult,
|
|
27
|
+
HookContext,
|
|
28
|
+
BeforeTaskContext,
|
|
29
|
+
AfterTaskContext,
|
|
30
|
+
OnErrorContext,
|
|
31
|
+
OnStallContext,
|
|
32
|
+
OnLaneEndContext,
|
|
33
|
+
HooksConfig,
|
|
34
|
+
} from './types';
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Hook Manager
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook Manager 클래스
|
|
42
|
+
*
|
|
43
|
+
* 싱글톤 패턴으로 구현되어 전역에서 접근 가능합니다.
|
|
44
|
+
*/
|
|
45
|
+
export class HookManager {
|
|
46
|
+
private static instance: HookManager | null = null;
|
|
47
|
+
|
|
48
|
+
/** 등록된 Hook 목록 (Hook Point별로 분류) */
|
|
49
|
+
private hooks: Map<HookPoint, HookRegistration[]> = new Map();
|
|
50
|
+
|
|
51
|
+
/** 설정 */
|
|
52
|
+
private config: HooksConfig = {
|
|
53
|
+
timeout: 30000,
|
|
54
|
+
continueOnError: false,
|
|
55
|
+
debug: false,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
private constructor() {
|
|
59
|
+
// 각 Hook Point에 대해 빈 배열 초기화
|
|
60
|
+
for (const point of Object.values(HookPoint)) {
|
|
61
|
+
this.hooks.set(point, []);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 싱글톤 인스턴스 획득
|
|
67
|
+
*/
|
|
68
|
+
static getInstance(): HookManager {
|
|
69
|
+
if (!HookManager.instance) {
|
|
70
|
+
HookManager.instance = new HookManager();
|
|
71
|
+
}
|
|
72
|
+
return HookManager.instance;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 인스턴스 리셋 (테스트용)
|
|
77
|
+
*/
|
|
78
|
+
static resetInstance(): void {
|
|
79
|
+
HookManager.instance = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ==========================================================================
|
|
83
|
+
// Configuration
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 설정 업데이트
|
|
88
|
+
*/
|
|
89
|
+
configure(config: Partial<HooksConfig>): void {
|
|
90
|
+
this.config = { ...this.config, ...config };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 현재 설정 조회
|
|
95
|
+
*/
|
|
96
|
+
getConfig(): HooksConfig {
|
|
97
|
+
return { ...this.config };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ==========================================================================
|
|
101
|
+
// Hook Registration
|
|
102
|
+
// ==========================================================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hook 등록
|
|
106
|
+
*
|
|
107
|
+
* @param registration Hook 등록 정보
|
|
108
|
+
* @returns 등록 해제 함수
|
|
109
|
+
*/
|
|
110
|
+
register<T extends HookPoint>(registration: HookRegistration<T>): () => void {
|
|
111
|
+
const { point, priority = 50, name, enabled = true } = registration;
|
|
112
|
+
|
|
113
|
+
if (!enabled) {
|
|
114
|
+
if (this.config.debug) {
|
|
115
|
+
logger.debug(`[HookManager] Skipping disabled hook: ${name || 'unnamed'}`);
|
|
116
|
+
}
|
|
117
|
+
return () => {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const hooks = this.hooks.get(point) || [];
|
|
121
|
+
|
|
122
|
+
// 우선순위에 따라 정렬된 위치에 삽입
|
|
123
|
+
const insertIndex = hooks.findIndex(h => (h.priority || 50) > priority);
|
|
124
|
+
if (insertIndex === -1) {
|
|
125
|
+
hooks.push(registration);
|
|
126
|
+
} else {
|
|
127
|
+
hooks.splice(insertIndex, 0, registration);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.hooks.set(point, hooks);
|
|
131
|
+
|
|
132
|
+
if (this.config.debug) {
|
|
133
|
+
logger.debug(`[HookManager] Registered hook "${name || 'unnamed'}" at ${point} (priority: ${priority})`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 등록 해제 함수 반환
|
|
137
|
+
return () => {
|
|
138
|
+
this.unregister(point, registration);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Hook 등록 해제
|
|
144
|
+
*/
|
|
145
|
+
private unregister<T extends HookPoint>(point: T, registration: HookRegistration<T>): void {
|
|
146
|
+
const hooks = this.hooks.get(point) || [];
|
|
147
|
+
const index = hooks.indexOf(registration as any);
|
|
148
|
+
|
|
149
|
+
if (index !== -1) {
|
|
150
|
+
hooks.splice(index, 1);
|
|
151
|
+
this.hooks.set(point, hooks);
|
|
152
|
+
|
|
153
|
+
if (this.config.debug) {
|
|
154
|
+
logger.debug(`[HookManager] Unregistered hook "${registration.name || 'unnamed'}" from ${point}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 특정 Hook Point의 모든 Hook 제거
|
|
161
|
+
*/
|
|
162
|
+
clearHooks(point?: HookPoint): void {
|
|
163
|
+
if (point) {
|
|
164
|
+
this.hooks.set(point, []);
|
|
165
|
+
} else {
|
|
166
|
+
for (const p of Object.values(HookPoint)) {
|
|
167
|
+
this.hooks.set(p, []);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 등록된 Hook 수 조회
|
|
174
|
+
*/
|
|
175
|
+
getHookCount(point?: HookPoint): number {
|
|
176
|
+
if (point) {
|
|
177
|
+
return this.hooks.get(point)?.length || 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let total = 0;
|
|
181
|
+
for (const hooks of this.hooks.values()) {
|
|
182
|
+
total += hooks.length;
|
|
183
|
+
}
|
|
184
|
+
return total;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Hook 등록 여부 확인
|
|
189
|
+
*/
|
|
190
|
+
hasHooks(point: HookPoint): boolean {
|
|
191
|
+
return (this.hooks.get(point)?.length || 0) > 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ==========================================================================
|
|
195
|
+
// Hook Execution
|
|
196
|
+
// ==========================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* beforeTask Hook 실행
|
|
200
|
+
*/
|
|
201
|
+
async executeBeforeTask(context: BeforeTaskContext): Promise<HookExecutionResult[]> {
|
|
202
|
+
return this.execute(HookPoint.BEFORE_TASK, context);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* afterTask Hook 실행
|
|
207
|
+
*/
|
|
208
|
+
async executeAfterTask(context: AfterTaskContext): Promise<HookExecutionResult[]> {
|
|
209
|
+
return this.execute(HookPoint.AFTER_TASK, context);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* onError Hook 실행
|
|
214
|
+
*/
|
|
215
|
+
async executeOnError(context: OnErrorContext): Promise<HookExecutionResult[]> {
|
|
216
|
+
return this.execute(HookPoint.ON_ERROR, context);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* onStall Hook 실행
|
|
221
|
+
*/
|
|
222
|
+
async executeOnStall(context: OnStallContext): Promise<HookExecutionResult[]> {
|
|
223
|
+
return this.execute(HookPoint.ON_STALL, context);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* onLaneEnd Hook 실행
|
|
228
|
+
*/
|
|
229
|
+
async executeOnLaneEnd(context: OnLaneEndContext): Promise<HookExecutionResult[]> {
|
|
230
|
+
return this.execute(HookPoint.ON_LANE_END, context);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 일반 Hook 실행
|
|
235
|
+
*/
|
|
236
|
+
async execute<T extends HookPoint>(
|
|
237
|
+
point: T,
|
|
238
|
+
context: HookContext
|
|
239
|
+
): Promise<HookExecutionResult[]> {
|
|
240
|
+
const hooks = this.hooks.get(point) || [];
|
|
241
|
+
|
|
242
|
+
if (hooks.length === 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (this.config.debug) {
|
|
247
|
+
logger.debug(`[HookManager] Executing ${hooks.length} hooks for ${point}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const results: HookExecutionResult[] = [];
|
|
251
|
+
const syncHooks = hooks.filter(h => h.mode === 'sync');
|
|
252
|
+
const asyncHooks = hooks.filter(h => h.mode === 'async');
|
|
253
|
+
|
|
254
|
+
// 동기 Hook 순차 실행 (블로킹)
|
|
255
|
+
for (const hook of syncHooks) {
|
|
256
|
+
const result = await this.executeHook(hook, context);
|
|
257
|
+
results.push(result);
|
|
258
|
+
|
|
259
|
+
// 에러 발생 시 중단 여부 결정
|
|
260
|
+
if (!result.success && !this.config.continueOnError) {
|
|
261
|
+
logger.error(`[HookManager] Hook "${hook.name || 'unnamed'}" failed, stopping execution`);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 비동기 Hook 병렬 실행 (논블로킹)
|
|
267
|
+
if (asyncHooks.length > 0) {
|
|
268
|
+
// 비동기 Hook은 백그라운드에서 실행 (결과를 기다리지 않음)
|
|
269
|
+
Promise.all(
|
|
270
|
+
asyncHooks.map(hook => this.executeHook(hook, context))
|
|
271
|
+
).then(asyncResults => {
|
|
272
|
+
for (const result of asyncResults) {
|
|
273
|
+
if (!result.success) {
|
|
274
|
+
logger.warn(`[HookManager] Async hook "${result.handlerName || 'unnamed'}" failed: ${result.error?.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}).catch(error => {
|
|
278
|
+
logger.error(`[HookManager] Async hooks error: ${error.message}`);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return results;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 단일 Hook 실행
|
|
287
|
+
*/
|
|
288
|
+
private async executeHook(
|
|
289
|
+
hook: HookRegistration,
|
|
290
|
+
context: HookContext
|
|
291
|
+
): Promise<HookExecutionResult> {
|
|
292
|
+
const startTime = Date.now();
|
|
293
|
+
const handlerName = hook.name || 'unnamed';
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
// 타임아웃 설정
|
|
297
|
+
const timeout = this.config.timeout || 30000;
|
|
298
|
+
|
|
299
|
+
const result = await Promise.race([
|
|
300
|
+
(hook.handler as any)(context),
|
|
301
|
+
new Promise((_, reject) =>
|
|
302
|
+
setTimeout(() => reject(new Error(`Hook timeout after ${timeout}ms`)), timeout)
|
|
303
|
+
),
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
const duration = Date.now() - startTime;
|
|
307
|
+
|
|
308
|
+
if (this.config.debug) {
|
|
309
|
+
logger.debug(`[HookManager] Hook "${handlerName}" completed in ${duration}ms`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
success: true,
|
|
314
|
+
duration,
|
|
315
|
+
handlerName,
|
|
316
|
+
};
|
|
317
|
+
} catch (error: any) {
|
|
318
|
+
const duration = Date.now() - startTime;
|
|
319
|
+
|
|
320
|
+
// FlowAbortError와 FlowRetryError는 정상적인 플로우 제어이므로 다시 throw
|
|
321
|
+
if (error.name === 'FlowAbortError' || error.name === 'FlowRetryError') {
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
logger.error(`[HookManager] Hook "${handlerName}" error: ${error.message}`);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
error,
|
|
330
|
+
duration,
|
|
331
|
+
handlerName,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ==========================================================================
|
|
337
|
+
// Hook Loading
|
|
338
|
+
// ==========================================================================
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 파일에서 Hook 로드
|
|
342
|
+
*/
|
|
343
|
+
async loadHooksFromFile(filePath: string): Promise<number> {
|
|
344
|
+
try {
|
|
345
|
+
// TypeScript/JavaScript 파일 동적 로드
|
|
346
|
+
const resolved = require.resolve(filePath);
|
|
347
|
+
|
|
348
|
+
// 캐시 삭제 (개발 중 핫 리로드를 위해)
|
|
349
|
+
delete require.cache[resolved];
|
|
350
|
+
|
|
351
|
+
const module = await import(resolved);
|
|
352
|
+
|
|
353
|
+
// 모듈이 HookManager를 반환하거나 register를 호출했으면 성공
|
|
354
|
+
if (this.config.debug) {
|
|
355
|
+
logger.debug(`[HookManager] Loaded hooks from ${filePath}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return this.getHookCount();
|
|
359
|
+
} catch (error: any) {
|
|
360
|
+
logger.error(`[HookManager] Failed to load hooks from ${filePath}: ${error.message}`);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ==========================================================================
|
|
366
|
+
// Debug & Inspection
|
|
367
|
+
// ==========================================================================
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 등록된 Hook 목록 조회
|
|
371
|
+
*/
|
|
372
|
+
listHooks(point?: HookPoint): Array<{ point: HookPoint; name: string; mode: HookMode; priority: number }> {
|
|
373
|
+
const result: Array<{ point: HookPoint; name: string; mode: HookMode; priority: number }> = [];
|
|
374
|
+
|
|
375
|
+
const points = point ? [point] : Object.values(HookPoint);
|
|
376
|
+
|
|
377
|
+
for (const p of points) {
|
|
378
|
+
const hooks = this.hooks.get(p) || [];
|
|
379
|
+
for (const hook of hooks) {
|
|
380
|
+
result.push({
|
|
381
|
+
point: p,
|
|
382
|
+
name: hook.name || 'unnamed',
|
|
383
|
+
mode: hook.mode,
|
|
384
|
+
priority: hook.priority || 50,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 디버그 정보 출력
|
|
394
|
+
*/
|
|
395
|
+
debug(): void {
|
|
396
|
+
logger.info('=== HookManager Debug ===');
|
|
397
|
+
logger.info(`Config: ${JSON.stringify(this.config)}`);
|
|
398
|
+
logger.info(`Total hooks: ${this.getHookCount()}`);
|
|
399
|
+
|
|
400
|
+
for (const point of Object.values(HookPoint)) {
|
|
401
|
+
const hooks = this.hooks.get(point) || [];
|
|
402
|
+
if (hooks.length > 0) {
|
|
403
|
+
logger.info(` ${point}: ${hooks.length} hooks`);
|
|
404
|
+
for (const hook of hooks) {
|
|
405
|
+
logger.info(` - ${hook.name || 'unnamed'} (${hook.mode}, priority: ${hook.priority || 50})`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Singleton Instance & Convenience API
|
|
414
|
+
// ============================================================================
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 전역 HookManager 인스턴스
|
|
418
|
+
*/
|
|
419
|
+
export const hooks = HookManager.getInstance();
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* HookManager 인스턴스 획득
|
|
423
|
+
*/
|
|
424
|
+
export function getHookManager(): HookManager {
|
|
425
|
+
return HookManager.getInstance();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* HookManager 인스턴스 리셋 (테스트용)
|
|
430
|
+
*/
|
|
431
|
+
export function resetHookManager(): void {
|
|
432
|
+
HookManager.resetInstance();
|
|
433
|
+
}
|
|
434
|
+
|