@litmers/cursorflow-orchestrator 0.1.40 → 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 (207) hide show
  1. package/CHANGELOG.md +0 -2
  2. package/README.md +7 -3
  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/resume.js +21 -1
  12. package/dist/cli/resume.js.map +1 -1
  13. package/dist/cli/run.js +28 -9
  14. package/dist/cli/run.js.map +1 -1
  15. package/dist/cli/signal.d.ts +6 -1
  16. package/dist/cli/signal.js +94 -12
  17. package/dist/cli/signal.js.map +1 -1
  18. package/dist/cli/tasks.js +3 -46
  19. package/dist/cli/tasks.js.map +1 -1
  20. package/dist/core/agent-supervisor.d.ts +23 -0
  21. package/dist/core/agent-supervisor.js +42 -0
  22. package/dist/core/agent-supervisor.js.map +1 -0
  23. package/dist/core/auto-recovery.d.ts +2 -1
  24. package/dist/core/auto-recovery.js +6 -1
  25. package/dist/core/auto-recovery.js.map +1 -1
  26. package/dist/core/failure-policy.d.ts +0 -1
  27. package/dist/core/failure-policy.js +0 -1
  28. package/dist/core/failure-policy.js.map +1 -1
  29. package/dist/core/git-lifecycle-manager.d.ts +284 -0
  30. package/dist/core/git-lifecycle-manager.js +778 -0
  31. package/dist/core/git-lifecycle-manager.js.map +1 -0
  32. package/dist/core/git-pipeline-coordinator.d.ts +21 -0
  33. package/dist/core/git-pipeline-coordinator.js +205 -0
  34. package/dist/core/git-pipeline-coordinator.js.map +1 -0
  35. package/dist/core/intervention.d.ts +176 -0
  36. package/dist/core/intervention.js +424 -0
  37. package/dist/core/intervention.js.map +1 -0
  38. package/dist/core/lane-state-machine.d.ts +423 -0
  39. package/dist/core/lane-state-machine.js +890 -0
  40. package/dist/core/lane-state-machine.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +4 -1
  42. package/dist/core/orchestrator.js +29 -62
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/runner/agent.d.ts +7 -1
  45. package/dist/core/runner/agent.js +45 -30
  46. package/dist/core/runner/agent.js.map +1 -1
  47. package/dist/core/runner/pipeline.js +283 -123
  48. package/dist/core/runner/pipeline.js.map +1 -1
  49. package/dist/core/runner/task.d.ts +4 -5
  50. package/dist/core/runner/task.js +6 -80
  51. package/dist/core/runner/task.js.map +1 -1
  52. package/dist/core/runner.js +8 -2
  53. package/dist/core/runner.js.map +1 -1
  54. package/dist/core/stall-detection.d.ts +11 -4
  55. package/dist/core/stall-detection.js +62 -27
  56. package/dist/core/stall-detection.js.map +1 -1
  57. package/dist/hooks/contexts/index.d.ts +104 -0
  58. package/dist/hooks/contexts/index.js +134 -0
  59. package/dist/hooks/contexts/index.js.map +1 -0
  60. package/dist/hooks/data-accessor.d.ts +86 -0
  61. package/dist/hooks/data-accessor.js +410 -0
  62. package/dist/hooks/data-accessor.js.map +1 -0
  63. package/dist/hooks/flow-controller.d.ts +136 -0
  64. package/dist/hooks/flow-controller.js +351 -0
  65. package/dist/hooks/flow-controller.js.map +1 -0
  66. package/dist/hooks/index.d.ts +68 -0
  67. package/dist/hooks/index.js +105 -0
  68. package/dist/hooks/index.js.map +1 -0
  69. package/dist/hooks/manager.d.ts +129 -0
  70. package/dist/hooks/manager.js +389 -0
  71. package/dist/hooks/manager.js.map +1 -0
  72. package/dist/hooks/types.d.ts +463 -0
  73. package/dist/hooks/types.js +45 -0
  74. package/dist/hooks/types.js.map +1 -0
  75. package/dist/services/logging/buffer.d.ts +2 -2
  76. package/dist/services/logging/buffer.js +95 -42
  77. package/dist/services/logging/buffer.js.map +1 -1
  78. package/dist/services/logging/console.js +6 -1
  79. package/dist/services/logging/console.js.map +1 -1
  80. package/dist/services/logging/formatter.d.ts +9 -4
  81. package/dist/services/logging/formatter.js +64 -18
  82. package/dist/services/logging/formatter.js.map +1 -1
  83. package/dist/services/logging/index.d.ts +0 -1
  84. package/dist/services/logging/index.js +0 -1
  85. package/dist/services/logging/index.js.map +1 -1
  86. package/dist/services/logging/paths.d.ts +8 -0
  87. package/dist/services/logging/paths.js +48 -0
  88. package/dist/services/logging/paths.js.map +1 -0
  89. package/dist/services/logging/raw-log.d.ts +6 -0
  90. package/dist/services/logging/raw-log.js +37 -0
  91. package/dist/services/logging/raw-log.js.map +1 -0
  92. package/dist/services/process/index.js +1 -1
  93. package/dist/services/process/index.js.map +1 -1
  94. package/dist/types/agent.d.ts +15 -0
  95. package/dist/types/config.d.ts +22 -1
  96. package/dist/types/event-categories.d.ts +601 -0
  97. package/dist/types/event-categories.js +233 -0
  98. package/dist/types/event-categories.js.map +1 -0
  99. package/dist/types/events.d.ts +0 -20
  100. package/dist/types/flow.d.ts +10 -6
  101. package/dist/types/index.d.ts +1 -1
  102. package/dist/types/index.js +17 -3
  103. package/dist/types/index.js.map +1 -1
  104. package/dist/types/lane.d.ts +1 -1
  105. package/dist/types/logging.d.ts +1 -1
  106. package/dist/types/task.d.ts +12 -1
  107. package/dist/ui/log-viewer.d.ts +3 -0
  108. package/dist/ui/log-viewer.js +3 -0
  109. package/dist/ui/log-viewer.js.map +1 -1
  110. package/dist/utils/config.js +10 -1
  111. package/dist/utils/config.js.map +1 -1
  112. package/dist/utils/cursor-agent.d.ts +11 -1
  113. package/dist/utils/cursor-agent.js +63 -16
  114. package/dist/utils/cursor-agent.js.map +1 -1
  115. package/dist/utils/enhanced-logger.d.ts +5 -1
  116. package/dist/utils/enhanced-logger.js +98 -19
  117. package/dist/utils/enhanced-logger.js.map +1 -1
  118. package/dist/utils/event-registry.d.ts +222 -0
  119. package/dist/utils/event-registry.js +463 -0
  120. package/dist/utils/event-registry.js.map +1 -0
  121. package/dist/utils/events.d.ts +1 -13
  122. package/dist/utils/events.js.map +1 -1
  123. package/dist/utils/flow.d.ts +10 -0
  124. package/dist/utils/flow.js +75 -0
  125. package/dist/utils/flow.js.map +1 -1
  126. package/dist/utils/log-constants.d.ts +1 -0
  127. package/dist/utils/log-constants.js +2 -1
  128. package/dist/utils/log-constants.js.map +1 -1
  129. package/dist/utils/log-formatter.d.ts +2 -1
  130. package/dist/utils/log-formatter.js +10 -10
  131. package/dist/utils/log-formatter.js.map +1 -1
  132. package/dist/utils/logger.d.ts +11 -0
  133. package/dist/utils/logger.js +82 -3
  134. package/dist/utils/logger.js.map +1 -1
  135. package/dist/utils/repro-thinking-logs.js +0 -13
  136. package/dist/utils/repro-thinking-logs.js.map +1 -1
  137. package/dist/utils/run-service.js +1 -1
  138. package/dist/utils/run-service.js.map +1 -1
  139. package/examples/README.md +0 -2
  140. package/examples/demo-project/README.md +1 -2
  141. package/package.json +18 -28
  142. package/scripts/setup-security.sh +0 -1
  143. package/scripts/test-log-parser.ts +171 -0
  144. package/scripts/verify-change.sh +272 -0
  145. package/src/cli/logs.ts +121 -10
  146. package/src/cli/models.ts +20 -3
  147. package/src/cli/monitor.ts +1257 -1342
  148. package/src/cli/resume.ts +27 -1
  149. package/src/cli/run.ts +29 -11
  150. package/src/cli/signal.ts +115 -17
  151. package/src/cli/tasks.ts +2 -59
  152. package/src/core/agent-supervisor.ts +64 -0
  153. package/src/core/auto-recovery.ts +7 -1
  154. package/src/core/failure-policy.ts +0 -1
  155. package/src/core/git-lifecycle-manager.ts +1011 -0
  156. package/src/core/git-pipeline-coordinator.ts +221 -0
  157. package/src/core/intervention.ts +481 -0
  158. package/src/core/lane-state-machine.ts +1097 -0
  159. package/src/core/orchestrator.ts +35 -61
  160. package/src/core/runner/agent.ts +66 -33
  161. package/src/core/runner/pipeline.ts +318 -138
  162. package/src/core/runner/task.ts +12 -97
  163. package/src/core/runner.ts +8 -2
  164. package/src/core/stall-detection.ts +72 -27
  165. package/src/hooks/contexts/index.ts +256 -0
  166. package/src/hooks/data-accessor.ts +488 -0
  167. package/src/hooks/flow-controller.ts +425 -0
  168. package/src/hooks/index.ts +154 -0
  169. package/src/hooks/manager.ts +434 -0
  170. package/src/hooks/types.ts +544 -0
  171. package/src/services/logging/buffer.ts +104 -43
  172. package/src/services/logging/console.ts +7 -1
  173. package/src/services/logging/formatter.ts +74 -18
  174. package/src/services/logging/index.ts +0 -2
  175. package/src/services/logging/paths.ts +14 -0
  176. package/src/services/logging/raw-log.ts +43 -0
  177. package/src/services/process/index.ts +1 -1
  178. package/src/types/agent.ts +15 -0
  179. package/src/types/config.ts +23 -1
  180. package/src/types/event-categories.ts +663 -0
  181. package/src/types/events.ts +0 -25
  182. package/src/types/flow.ts +10 -6
  183. package/src/types/index.ts +50 -4
  184. package/src/types/lane.ts +1 -2
  185. package/src/types/logging.ts +2 -1
  186. package/src/types/task.ts +12 -1
  187. package/src/ui/log-viewer.ts +3 -0
  188. package/src/utils/config.ts +11 -1
  189. package/src/utils/cursor-agent.ts +68 -16
  190. package/src/utils/enhanced-logger.ts +105 -19
  191. package/src/utils/event-registry.ts +595 -0
  192. package/src/utils/events.ts +0 -16
  193. package/src/utils/flow.ts +83 -0
  194. package/src/utils/log-constants.ts +2 -1
  195. package/src/utils/log-formatter.ts +10 -11
  196. package/src/utils/logger.ts +49 -3
  197. package/src/utils/repro-thinking-logs.ts +0 -15
  198. package/src/utils/run-service.ts +1 -1
  199. package/dist/services/logging/file-writer.d.ts +0 -71
  200. package/dist/services/logging/file-writer.js +0 -516
  201. package/dist/services/logging/file-writer.js.map +0 -1
  202. package/dist/types/review.d.ts +0 -17
  203. package/dist/types/review.js +0 -6
  204. package/dist/types/review.js.map +0 -1
  205. package/scripts/ai-security-check.js +0 -233
  206. package/src/services/logging/file-writer.ts +0 -526
  207. package/src/types/review.ts +0 -20
@@ -0,0 +1,488 @@
1
+ /**
2
+ * CursorFlow Hook System - Data Accessor Implementation
3
+ *
4
+ * Hook에서 Git, 대화 기록, 로그 등의 데이터에 접근하기 위한 구현체입니다.
5
+ * 모든 메서드는 Lazy Loading으로 필요할 때만 데이터를 로드합니다.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import * as git from '../utils/git';
11
+ import { safeJoin } from '../utils/path';
12
+ import {
13
+ HookDataAccessor,
14
+ ChangedFile,
15
+ Commit,
16
+ Message,
17
+ TaskResult,
18
+ TaskDefinition,
19
+ ToolCall,
20
+ ErrorLog,
21
+ DependencyResult,
22
+ } from './types';
23
+
24
+ // ============================================================================
25
+ // Data Accessor Options
26
+ // ============================================================================
27
+
28
+ export interface DataAccessorOptions {
29
+ /** Worktree 디렉토리 */
30
+ worktreeDir: string;
31
+ /** Run 디렉토리 (로그 파일 위치) */
32
+ runDir: string;
33
+ /** 현재 태스크 브랜치 */
34
+ taskBranch: string;
35
+ /** 파이프라인 브랜치 */
36
+ pipelineBranch: string;
37
+ /** Lane 이름 */
38
+ laneName: string;
39
+ /** 현재 태스크 이름 */
40
+ taskName: string;
41
+ /** 완료된 태스크 목록 */
42
+ completedTasks: TaskResult[];
43
+ /** 남은 태스크 목록 */
44
+ pendingTasks: TaskDefinition[];
45
+ /** 의존성 결과 */
46
+ dependencyResults: DependencyResult[];
47
+ /** 태스크 시작 시간 */
48
+ taskStartTime: number;
49
+ /** Lane 시작 시간 */
50
+ laneStartTime: number;
51
+ /** Run Root (lanes 디렉토리의 부모) */
52
+ runRoot?: string;
53
+ }
54
+
55
+ // ============================================================================
56
+ // Data Accessor Implementation
57
+ // ============================================================================
58
+
59
+ /**
60
+ * HookDataAccessor 구현체
61
+ */
62
+ export class HookDataAccessorImpl implements HookDataAccessor {
63
+ private options: DataAccessorOptions;
64
+
65
+ // 캐시
66
+ private cachedChangedFiles: ChangedFile[] | null = null;
67
+ private cachedDiff: string | null = null;
68
+ private cachedMessages: Message[] | null = null;
69
+ private cachedToolCalls: ToolCall[] | null = null;
70
+ private cachedRawOutput: string | null = null;
71
+
72
+ constructor(options: DataAccessorOptions) {
73
+ this.options = options;
74
+ }
75
+
76
+ // ==========================================================================
77
+ // Git Data Access
78
+ // ==========================================================================
79
+
80
+ git = {
81
+ getChangedFiles: async (): Promise<ChangedFile[]> => {
82
+ if (this.cachedChangedFiles) {
83
+ return this.cachedChangedFiles;
84
+ }
85
+
86
+ try {
87
+ const { worktreeDir, pipelineBranch, taskBranch } = this.options;
88
+
89
+ // Get diff stat between pipeline and task branch
90
+ const diffStat = git.runGit(
91
+ ['diff', '--numstat', pipelineBranch, taskBranch],
92
+ { cwd: worktreeDir, silent: true }
93
+ );
94
+
95
+ const files: ChangedFile[] = [];
96
+
97
+ if (diffStat) {
98
+ const lines = diffStat.trim().split('\n').filter(l => l);
99
+
100
+ for (const line of lines) {
101
+ const [additions, deletions, filePath] = line.split('\t');
102
+ if (!filePath) continue;
103
+
104
+ // Get file status
105
+ let status: ChangedFile['status'] = 'modified';
106
+ try {
107
+ const statusOutput = git.runGit(
108
+ ['diff', '--name-status', pipelineBranch, taskBranch, '--', filePath],
109
+ { cwd: worktreeDir, silent: true }
110
+ );
111
+ if (statusOutput) {
112
+ const statusChar = statusOutput.charAt(0);
113
+ if (statusChar === 'A') status = 'added';
114
+ else if (statusChar === 'D') status = 'deleted';
115
+ else if (statusChar === 'R') status = 'renamed';
116
+ }
117
+ } catch {
118
+ // Ignore status detection errors
119
+ }
120
+
121
+ files.push({
122
+ path: filePath,
123
+ status,
124
+ additions: additions === '-' ? 0 : parseInt(additions, 10) || 0,
125
+ deletions: deletions === '-' ? 0 : parseInt(deletions, 10) || 0,
126
+ });
127
+ }
128
+ }
129
+
130
+ this.cachedChangedFiles = files;
131
+ return files;
132
+ } catch (error) {
133
+ console.error('Failed to get changed files:', error);
134
+ return [];
135
+ }
136
+ },
137
+
138
+ getDiff: async (): Promise<string> => {
139
+ if (this.cachedDiff !== null) {
140
+ return this.cachedDiff;
141
+ }
142
+
143
+ try {
144
+ const { worktreeDir, pipelineBranch, taskBranch } = this.options;
145
+
146
+ const diff = git.runGit(
147
+ ['diff', pipelineBranch, taskBranch],
148
+ { cwd: worktreeDir, silent: true }
149
+ );
150
+
151
+ this.cachedDiff = diff || '';
152
+ return this.cachedDiff;
153
+ } catch (error) {
154
+ console.error('Failed to get diff:', error);
155
+ return '';
156
+ }
157
+ },
158
+
159
+ getRecentCommits: async (count: number = 10): Promise<Commit[]> => {
160
+ try {
161
+ const { worktreeDir } = this.options;
162
+
163
+ // Get commit log with format
164
+ const logOutput = git.runGit(
165
+ ['log', `-${count}`, '--pretty=format:%H|%s|%an|%aI', '--name-only'],
166
+ { cwd: worktreeDir, silent: true }
167
+ );
168
+
169
+ if (!logOutput) return [];
170
+
171
+ const commits: Commit[] = [];
172
+ const entries = logOutput.split('\n\n').filter(e => e.trim());
173
+
174
+ for (const entry of entries) {
175
+ const lines = entry.trim().split('\n');
176
+ if (lines.length === 0) continue;
177
+
178
+ const [firstLine, ...fileLines] = lines;
179
+ const [hash, message, author, date] = firstLine.split('|');
180
+
181
+ if (hash && message && author && date) {
182
+ commits.push({
183
+ hash,
184
+ message,
185
+ author,
186
+ date,
187
+ files: fileLines.filter(f => f.trim()),
188
+ });
189
+ }
190
+ }
191
+
192
+ return commits;
193
+ } catch (error) {
194
+ console.error('Failed to get recent commits:', error);
195
+ return [];
196
+ }
197
+ },
198
+
199
+ getCurrentBranch: (): string => {
200
+ return this.options.taskBranch;
201
+ },
202
+
203
+ getConflictFiles: async (): Promise<string[]> => {
204
+ try {
205
+ const { worktreeDir } = this.options;
206
+
207
+ // Check for merge conflicts
208
+ const statusOutput = git.runGit(
209
+ ['status', '--porcelain'],
210
+ { cwd: worktreeDir, silent: true }
211
+ );
212
+
213
+ if (!statusOutput) return [];
214
+
215
+ const conflictFiles: string[] = [];
216
+ const lines = statusOutput.split('\n');
217
+
218
+ for (const line of lines) {
219
+ // UU = both modified (conflict)
220
+ // AA = both added (conflict)
221
+ if (line.startsWith('UU ') || line.startsWith('AA ')) {
222
+ conflictFiles.push(line.substring(3).trim());
223
+ }
224
+ }
225
+
226
+ return conflictFiles;
227
+ } catch (error) {
228
+ console.error('Failed to get conflict files:', error);
229
+ return [];
230
+ }
231
+ },
232
+ };
233
+
234
+ // ==========================================================================
235
+ // Conversation Data Access
236
+ // ==========================================================================
237
+
238
+ conversation = {
239
+ getCurrentTaskMessages: async (): Promise<Message[]> => {
240
+ const allMessages = await this.conversation.getAllMessages();
241
+ return allMessages.filter(m => m.taskName === this.options.taskName);
242
+ },
243
+
244
+ getAllMessages: async (): Promise<Message[]> => {
245
+ if (this.cachedMessages) {
246
+ return this.cachedMessages;
247
+ }
248
+
249
+ try {
250
+ const convoPath = safeJoin(this.options.runDir, 'conversation.jsonl');
251
+
252
+ if (!fs.existsSync(convoPath)) {
253
+ return [];
254
+ }
255
+
256
+ const content = fs.readFileSync(convoPath, 'utf8');
257
+ const lines = content.split('\n').filter(l => l.trim());
258
+
259
+ const messages: Message[] = [];
260
+
261
+ for (const line of lines) {
262
+ try {
263
+ const entry = JSON.parse(line);
264
+ messages.push({
265
+ role: entry.role || 'user',
266
+ content: entry.content || '',
267
+ timestamp: entry.timestamp || new Date().toISOString(),
268
+ taskName: entry.task || entry.taskName,
269
+ metadata: entry.metadata,
270
+ });
271
+ } catch {
272
+ // Skip invalid JSON lines
273
+ }
274
+ }
275
+
276
+ this.cachedMessages = messages;
277
+ return messages;
278
+ } catch (error) {
279
+ console.error('Failed to get messages:', error);
280
+ return [];
281
+ }
282
+ },
283
+
284
+ getRecentMessages: async (count: number = 10): Promise<Message[]> => {
285
+ const allMessages = await this.conversation.getAllMessages();
286
+ return allMessages.slice(-count);
287
+ },
288
+
289
+ getLastResponse: async (): Promise<string | null> => {
290
+ const allMessages = await this.conversation.getAllMessages();
291
+
292
+ // Find last assistant message
293
+ for (let i = allMessages.length - 1; i >= 0; i--) {
294
+ if (allMessages[i].role === 'assistant') {
295
+ return allMessages[i].content;
296
+ }
297
+ }
298
+
299
+ return null;
300
+ },
301
+ };
302
+
303
+ // ==========================================================================
304
+ // Tasks Data Access
305
+ // ==========================================================================
306
+
307
+ tasks = {
308
+ getCompletedTasks: (): TaskResult[] => {
309
+ return this.options.completedTasks;
310
+ },
311
+
312
+ getPendingTasks: (): TaskDefinition[] => {
313
+ return this.options.pendingTasks;
314
+ },
315
+
316
+ getTaskResult: (taskName: string): TaskResult | null => {
317
+ return this.options.completedTasks.find(t => t.name === taskName) || null;
318
+ },
319
+
320
+ getDependencyResults: (): DependencyResult[] => {
321
+ return this.options.dependencyResults;
322
+ },
323
+ };
324
+
325
+ // ==========================================================================
326
+ // Logs Data Access
327
+ // ==========================================================================
328
+
329
+ logs = {
330
+ getRawOutput: async (): Promise<string> => {
331
+ if (this.cachedRawOutput !== null) {
332
+ return this.cachedRawOutput;
333
+ }
334
+
335
+ try {
336
+ // Try multiple possible log file locations
337
+ const possiblePaths = [
338
+ safeJoin(this.options.runDir, 'terminal-raw.log'),
339
+ safeJoin(this.options.runDir, 'terminal.log'),
340
+ safeJoin(this.options.runDir, 'agent-output.log'),
341
+ ];
342
+
343
+ for (const logPath of possiblePaths) {
344
+ if (fs.existsSync(logPath)) {
345
+ const content = fs.readFileSync(logPath, 'utf8');
346
+ this.cachedRawOutput = content;
347
+ return content;
348
+ }
349
+ }
350
+
351
+ return '';
352
+ } catch (error) {
353
+ console.error('Failed to get raw output:', error);
354
+ return '';
355
+ }
356
+ },
357
+
358
+ getToolCalls: async (): Promise<ToolCall[]> => {
359
+ if (this.cachedToolCalls) {
360
+ return this.cachedToolCalls;
361
+ }
362
+
363
+ try {
364
+ const rawOutput = await this.logs.getRawOutput();
365
+ const toolCalls: ToolCall[] = [];
366
+
367
+ // Parse tool calls from JSONL output
368
+ const lines = rawOutput.split('\n');
369
+
370
+ for (const line of lines) {
371
+ if (!line.trim().startsWith('{')) continue;
372
+
373
+ try {
374
+ const parsed = JSON.parse(line);
375
+
376
+ // Check for tool_call type messages
377
+ if (parsed.type === 'tool_call' || parsed.tool_calls) {
378
+ const calls = parsed.tool_calls || [parsed];
379
+
380
+ for (const call of calls) {
381
+ if (call.name || call.function?.name) {
382
+ toolCalls.push({
383
+ name: call.name || call.function?.name,
384
+ parameters: call.parameters || call.function?.arguments || {},
385
+ result: call.result,
386
+ timestamp: parsed.timestamp || new Date().toISOString(),
387
+ });
388
+ }
389
+ }
390
+ }
391
+ } catch {
392
+ // Skip non-JSON lines
393
+ }
394
+ }
395
+
396
+ this.cachedToolCalls = toolCalls;
397
+ return toolCalls;
398
+ } catch (error) {
399
+ console.error('Failed to get tool calls:', error);
400
+ return [];
401
+ }
402
+ },
403
+
404
+ getErrors: async (): Promise<ErrorLog[]> => {
405
+ try {
406
+ const rawOutput = await this.logs.getRawOutput();
407
+ const errors: ErrorLog[] = [];
408
+
409
+ const lines = rawOutput.split('\n');
410
+
411
+ for (const line of lines) {
412
+ // Check for error patterns
413
+ const lowerLine = line.toLowerCase();
414
+
415
+ if (lowerLine.includes('error') || lowerLine.includes('exception') || lowerLine.includes('failed')) {
416
+ errors.push({
417
+ level: 'error',
418
+ message: line.trim(),
419
+ timestamp: new Date().toISOString(),
420
+ });
421
+ } else if (lowerLine.includes('warning') || lowerLine.includes('warn')) {
422
+ errors.push({
423
+ level: 'warn',
424
+ message: line.trim(),
425
+ timestamp: new Date().toISOString(),
426
+ });
427
+ }
428
+ }
429
+
430
+ return errors;
431
+ } catch (error) {
432
+ console.error('Failed to get errors:', error);
433
+ return [];
434
+ }
435
+ },
436
+ };
437
+
438
+ // ==========================================================================
439
+ // Timing Data Access
440
+ // ==========================================================================
441
+
442
+ get timing() {
443
+ const options = this.options;
444
+ return {
445
+ taskStartTime: options.taskStartTime,
446
+ laneStartTime: options.laneStartTime,
447
+
448
+ getElapsedTime: (): number => {
449
+ return Date.now() - options.taskStartTime;
450
+ },
451
+ };
452
+ }
453
+
454
+ // ==========================================================================
455
+ // Cache Management
456
+ // ==========================================================================
457
+
458
+ /**
459
+ * 캐시 초기화
460
+ */
461
+ clearCache(): void {
462
+ this.cachedChangedFiles = null;
463
+ this.cachedDiff = null;
464
+ this.cachedMessages = null;
465
+ this.cachedToolCalls = null;
466
+ this.cachedRawOutput = null;
467
+ }
468
+
469
+ /**
470
+ * 옵션 업데이트 (태스크 변경 시)
471
+ */
472
+ updateOptions(updates: Partial<DataAccessorOptions>): void {
473
+ this.options = { ...this.options, ...updates };
474
+ this.clearCache();
475
+ }
476
+ }
477
+
478
+ // ============================================================================
479
+ // Factory Function
480
+ // ============================================================================
481
+
482
+ /**
483
+ * HookDataAccessor 인스턴스 생성
484
+ */
485
+ export function createDataAccessor(options: DataAccessorOptions): HookDataAccessor {
486
+ return new HookDataAccessorImpl(options);
487
+ }
488
+