@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +0 -53
  2. package/dist/api/generated/api.d.ts +2 -34
  3. package/dist/assets/docker/docker-compose-dev.yml +0 -1
  4. package/dist/commands/workflow/generate.js +3 -10
  5. package/dist/commands/workflow/generate.spec.js +6 -4
  6. package/dist/services/coding_agents.js +30 -0
  7. package/dist/services/coding_agents.spec.js +36 -61
  8. package/dist/services/messages.d.ts +1 -0
  9. package/dist/services/messages.js +65 -1
  10. package/dist/services/workflow_generator.spec.js +77 -0
  11. package/dist/templates/agent_instructions/AGENTS.md.template +209 -19
  12. package/dist/templates/agent_instructions/agents/context_fetcher.md.template +82 -0
  13. package/dist/templates/agent_instructions/agents/prompt_writer.md.template +595 -0
  14. package/dist/templates/agent_instructions/agents/workflow_planner.md.template +13 -4
  15. package/dist/templates/agent_instructions/agents/workflow_quality.md.template +244 -0
  16. package/dist/templates/agent_instructions/commands/build_workflow.md.template +52 -9
  17. package/dist/templates/agent_instructions/commands/plan_workflow.md.template +4 -4
  18. package/dist/templates/project/package.json.template +2 -2
  19. package/dist/templates/project/src/simple/scenarios/question_ada_lovelace.json.template +3 -0
  20. package/dist/templates/workflow/scenarios/test_input.json.template +7 -0
  21. package/dist/utils/template.spec.js +6 -0
  22. package/package.json +1 -2
  23. package/dist/commands/workflow/debug.d.ts +0 -15
  24. package/dist/commands/workflow/debug.js +0 -55
  25. package/dist/commands/workflow/debug.test.js +0 -75
  26. package/dist/services/trace_reader.d.ts +0 -8
  27. package/dist/services/trace_reader.js +0 -51
  28. package/dist/services/trace_reader.test.d.ts +0 -1
  29. package/dist/services/trace_reader.test.js +0 -163
  30. package/dist/utils/trace_formatter.d.ts +0 -27
  31. package/dist/utils/trace_formatter.js +0 -465
  32. /package/dist/{commands/workflow/debug.test.d.ts → services/workflow_generator.spec.d.ts} +0 -0
  33. /package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template} +0 -0
@@ -1,163 +0,0 @@
1
- import { describe, it, expect, vi, afterEach } from 'vitest';
2
- import { findTraceFile, readTraceFile } from './trace_reader.js';
3
- // Mock file system operations
4
- vi.mock('node:fs/promises', () => ({
5
- readFile: vi.fn(),
6
- stat: vi.fn()
7
- }));
8
- // Mock API
9
- vi.mock('../api/generated/api.js', () => ({
10
- getWorkflowIdOutput: vi.fn()
11
- }));
12
- describe('TraceReader', () => {
13
- const getMocks = async () => {
14
- const fsModule = await import('node:fs/promises');
15
- const apiModule = await import('../api/generated/api.js');
16
- return {
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- mockReadFile: fsModule.readFile,
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- mockStat: fsModule.stat,
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- mockGetWorkflowIdOutput: apiModule.getWorkflowIdOutput
23
- };
24
- };
25
- afterEach(() => {
26
- vi.clearAllMocks();
27
- });
28
- describe('findTraceFile', () => {
29
- it('should find trace file from workflow output metadata', async () => {
30
- const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
31
- const workflowId = 'test-workflow-123';
32
- const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-123.json';
33
- mockGetWorkflowIdOutput.mockResolvedValue({
34
- status: 200,
35
- data: {
36
- workflowId,
37
- output: { result: 'test result' },
38
- trace: {
39
- destinations: {
40
- local: expectedPath,
41
- remote: null
42
- }
43
- }
44
- }
45
- });
46
- mockStat.mockResolvedValue({ isFile: () => true });
47
- const result = await findTraceFile(workflowId);
48
- expect(result).toBe(expectedPath);
49
- expect(mockGetWorkflowIdOutput).toHaveBeenCalledWith(workflowId);
50
- expect(mockStat).toHaveBeenCalledWith(expectedPath);
51
- });
52
- it('should throw error when no trace path in metadata', async () => {
53
- const { mockGetWorkflowIdOutput } = await getMocks();
54
- const workflowId = 'test-workflow-456';
55
- mockGetWorkflowIdOutput.mockResolvedValue({
56
- status: 200,
57
- data: {
58
- workflowId,
59
- output: { result: 'test result' },
60
- trace: {
61
- destinations: {
62
- local: null,
63
- remote: null
64
- }
65
- }
66
- }
67
- });
68
- await expect(findTraceFile(workflowId))
69
- .rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
70
- });
71
- it('should throw error when trace file not on disk', async () => {
72
- const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
73
- const workflowId = 'test-workflow-789';
74
- const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-789.json';
75
- mockGetWorkflowIdOutput.mockResolvedValue({
76
- status: 200,
77
- data: {
78
- workflowId,
79
- output: { result: 'test result' },
80
- trace: {
81
- destinations: {
82
- local: expectedPath,
83
- remote: null
84
- }
85
- }
86
- }
87
- });
88
- mockStat.mockRejectedValue(new Error('ENOENT'));
89
- await expect(findTraceFile(workflowId))
90
- .rejects.toThrow(`Trace file not found at path: ${expectedPath}`);
91
- });
92
- it('should throw error when API call fails', async () => {
93
- const { mockGetWorkflowIdOutput } = await getMocks();
94
- const workflowId = 'non-existent';
95
- mockGetWorkflowIdOutput.mockRejectedValue(new Error('Workflow not found'));
96
- await expect(findTraceFile(workflowId))
97
- .rejects.toThrow('Workflow not found');
98
- });
99
- it('should handle missing trace property gracefully', async () => {
100
- const { mockGetWorkflowIdOutput } = await getMocks();
101
- const workflowId = 'test-workflow-no-trace';
102
- mockGetWorkflowIdOutput.mockResolvedValue({
103
- status: 200,
104
- data: {
105
- workflowId,
106
- output: { result: 'test result' }
107
- // No trace property at all
108
- }
109
- });
110
- await expect(findTraceFile(workflowId))
111
- .rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
112
- });
113
- it('should throw error when workflow not found (404)', async () => {
114
- const { mockGetWorkflowIdOutput } = await getMocks();
115
- const workflowId = 'non-existent-workflow';
116
- mockGetWorkflowIdOutput.mockResolvedValue({
117
- status: 404,
118
- data: void 0
119
- });
120
- await expect(findTraceFile(workflowId))
121
- .rejects.toThrow(`Failed to get workflow output for ${workflowId}`);
122
- });
123
- });
124
- describe('readTraceFile', () => {
125
- it('should read and parse JSON file successfully', async () => {
126
- const { mockReadFile } = await getMocks();
127
- const path = '/logs/test.json';
128
- const traceData = {
129
- root: { workflowName: 'test' },
130
- events: []
131
- };
132
- mockReadFile.mockResolvedValue(JSON.stringify(traceData));
133
- const result = await readTraceFile(path);
134
- expect(result).toEqual(traceData);
135
- expect(mockReadFile).toHaveBeenCalledWith(path, 'utf-8');
136
- });
137
- it('should throw error for non-existent file', async () => {
138
- const { mockReadFile } = await getMocks();
139
- const path = '/logs/missing.json';
140
- const error = new Error('ENOENT');
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
- error.code = 'ENOENT';
143
- mockReadFile.mockRejectedValue(error);
144
- await expect(readTraceFile(path))
145
- .rejects.toThrow(`Trace file not found at path: ${path}`);
146
- });
147
- it('should throw error for invalid JSON', async () => {
148
- const { mockReadFile } = await getMocks();
149
- const path = '/logs/invalid.json';
150
- mockReadFile.mockResolvedValue('invalid json {');
151
- await expect(readTraceFile(path))
152
- .rejects.toThrow(`Invalid JSON in trace file: ${path}`);
153
- });
154
- it('should rethrow other errors', async () => {
155
- const { mockReadFile } = await getMocks();
156
- const path = '/logs/test.json';
157
- const error = new Error('Permission denied');
158
- mockReadFile.mockRejectedValue(error);
159
- await expect(readTraceFile(path))
160
- .rejects.toThrow('Permission denied');
161
- });
162
- });
163
- });
@@ -1,27 +0,0 @@
1
- /**
2
- * Format duration in human-readable format
3
- */
4
- export declare function formatDuration(ms: number): string;
5
- /**
6
- * Format trace data based on the requested format
7
- */
8
- export declare function format(traceData: string | object, outputFormat?: 'json' | 'text'): string;
9
- /**
10
- * Get summary statistics from trace
11
- */
12
- export declare function getSummary(traceData: string | object): {
13
- totalDuration: number;
14
- totalEvents: number;
15
- totalSteps: number;
16
- totalActivities: number;
17
- hasErrors: boolean;
18
- };
19
- /**
20
- * Display trace tree with debug command formatting style
21
- */
22
- export declare function displayDebugTree(node: any): string;
23
- export declare const traceFormatter: {
24
- format: typeof format;
25
- getSummary: typeof getSummary;
26
- displayDebugTree: typeof displayDebugTree;
27
- };
@@ -1,465 +0,0 @@
1
- import Table from 'cli-table3';
2
- import { formatOutput } from '#utils/output_formatter.js';
3
- /**
4
- * Format duration in human-readable format
5
- */
6
- export function formatDuration(ms) {
7
- if (ms < 1000) {
8
- return `${ms}ms`;
9
- }
10
- if (ms < 60000) {
11
- return `${(ms / 1000).toFixed(2)}s`;
12
- }
13
- const minutes = Math.floor(ms / 60000);
14
- const seconds = ((ms % 60000) / 1000).toFixed(0);
15
- return `${minutes}m ${seconds}s`;
16
- }
17
- /**
18
- * Format error for display
19
- */
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- function formatError(error) {
22
- if (typeof error === 'string') {
23
- return error;
24
- }
25
- if (error.message) {
26
- return error.message;
27
- }
28
- return JSON.stringify(error);
29
- }
30
- /**
31
- * Truncate long values for display
32
- */
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- function truncateValue(value, maxLength = 50) {
35
- const str = typeof value === 'string' ? value : JSON.stringify(value);
36
- if (str.length <= maxLength) {
37
- return str;
38
- }
39
- return str.substring(0, maxLength) + '...';
40
- }
41
- /**
42
- * Get a readable name for an event
43
- */
44
- function getEventName(event) {
45
- if (event.kind === 'workflow') {
46
- return `Workflow: ${event.workflowName}`;
47
- }
48
- if (event.kind === 'activity') {
49
- return `Activity: ${event.details?.activityName || 'unknown'}`;
50
- }
51
- if (event.kind === 'step') {
52
- return `Step: ${event.details?.stepName || event.details?.name || 'unknown'}`;
53
- }
54
- return event.kind || 'Unknown Event';
55
- }
56
- /**
57
- * Format the phase
58
- */
59
- function formatPhase(phase) {
60
- switch (phase) {
61
- case 'start':
62
- return '[START]';
63
- case 'end':
64
- return '[END]';
65
- case 'error':
66
- return '[ERROR]';
67
- default:
68
- return phase;
69
- }
70
- }
71
- /**
72
- * Format details for table display
73
- */
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- function formatDetails(details) {
76
- if (!details) {
77
- return '-';
78
- }
79
- if (typeof details === 'string') {
80
- return details;
81
- }
82
- // Extract key information
83
- const info = [];
84
- if (details.input) {
85
- info.push(`Input: ${truncateValue(details.input)}`);
86
- }
87
- if (details.output) {
88
- info.push(`Output: ${truncateValue(details.output)}`);
89
- }
90
- if (details.activityName) {
91
- info.push(`Activity: ${details.activityName}`);
92
- }
93
- if (details.stepName || details.name) {
94
- info.push(`Step: ${details.stepName || details.name}`);
95
- }
96
- return info.length > 0 ? info.join(', ') : JSON.stringify(details).substring(0, 50) + '...';
97
- }
98
- /**
99
- * Format details for tree display
100
- */
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- function formatTreeDetails(details, depth) {
103
- const lines = [];
104
- const indent = ' '.repeat(depth);
105
- // Only show key details
106
- if (details.input && details.input !== null) {
107
- lines.push(`${indent}Input: ${truncateValue(details.input)}`);
108
- }
109
- if (details.output && details.output !== null) {
110
- lines.push(`${indent}Output: ${truncateValue(details.output)}`);
111
- }
112
- return lines.join('\n');
113
- }
114
- /**
115
- * Format the header with workflow information
116
- */
117
- function formatHeader(root) {
118
- const lines = [];
119
- lines.push('═'.repeat(60));
120
- lines.push(`Workflow: ${root.workflowName}`);
121
- lines.push(`Workflow ID: ${root.workflowId}`);
122
- lines.push(`Start Time: ${new Date(root.timestamp).toISOString()}`);
123
- if (root.duration) {
124
- lines.push(`Duration: ${formatDuration(root.duration)}`);
125
- }
126
- if (root.phase === 'error' && root.error) {
127
- lines.push('Status: Failed');
128
- lines.push(`Error: ${formatError(root.error)}`);
129
- }
130
- else if (root.phase === 'end') {
131
- lines.push('Status: Completed');
132
- }
133
- else {
134
- lines.push('Status: In Progress');
135
- }
136
- lines.push('═'.repeat(60));
137
- return lines.join('\n');
138
- }
139
- /**
140
- * Format events as a timeline table
141
- */
142
- function formatEventsTable(events) {
143
- const table = new Table({
144
- head: ['Time', 'Event', 'Phase', 'Duration', 'Details'],
145
- style: {
146
- head: ['cyan']
147
- },
148
- colWidths: [20, 25, 10, 12, null],
149
- wordWrap: true
150
- });
151
- for (const event of events) {
152
- const time = new Date(event.timestamp).toISOString().substring(11, 23);
153
- const eventName = getEventName(event);
154
- const phase = formatPhase(event.phase);
155
- const duration = event.duration ? formatDuration(event.duration) : '-';
156
- const details = formatDetails(event.details);
157
- table.push([time, eventName, phase, duration, details]);
158
- }
159
- return table.toString();
160
- }
161
- /**
162
- * Format trace as a tree structure
163
- */
164
- function formatTree(node, depth) {
165
- const lines = [];
166
- const indent = ' '.repeat(depth);
167
- const marker = depth === 0 ? '' : '├─';
168
- // Format current node
169
- const nodeName = getEventName(node);
170
- const phase = formatPhase(node.phase);
171
- const duration = node.duration ? ` (${formatDuration(node.duration)})` : '';
172
- lines.push(`${indent}${marker} ${nodeName} ${phase}${duration}`);
173
- // Add error details if present
174
- if (node.error) {
175
- lines.push(`${indent} └─ ERROR: ${formatError(node.error)}`);
176
- }
177
- // Add important details
178
- if (node.details && typeof node.details === 'object') {
179
- const detailLines = formatTreeDetails(node.details, depth + 1);
180
- if (detailLines) {
181
- lines.push(detailLines);
182
- }
183
- }
184
- // Process children
185
- if (node.children && node.children.length > 0) {
186
- for (const child of node.children) {
187
- lines.push(formatTree(child, depth + 1));
188
- }
189
- }
190
- return lines.join('\n');
191
- }
192
- /**
193
- * Format trace as human-readable text with tree structure
194
- */
195
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
- function formatAsText(trace) {
197
- const output = [];
198
- // Add header with workflow info
199
- if (trace.root) {
200
- output.push(formatHeader(trace.root));
201
- output.push('');
202
- }
203
- // Create execution timeline table
204
- if (trace.events && trace.events.length > 0) {
205
- output.push('Execution Timeline:');
206
- output.push(formatEventsTable(trace.events));
207
- output.push('');
208
- }
209
- // Show tree structure
210
- if (trace.root) {
211
- output.push('Execution Tree:');
212
- output.push(formatTree(trace.root, 0));
213
- }
214
- return output.join('\n');
215
- }
216
- /**
217
- * Format trace data based on the requested format
218
- */
219
- export function format(traceData, outputFormat = 'text') {
220
- // Parse if string
221
- const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
222
- if (outputFormat === 'json') {
223
- return formatOutput(trace, 'json');
224
- }
225
- // Format as human-readable text
226
- return formatAsText(trace);
227
- }
228
- /**
229
- * Get summary statistics from trace
230
- */
231
- export function getSummary(traceData) {
232
- const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
233
- const stats = {
234
- totalDuration: trace.root?.duration || 0,
235
- totalEvents: trace.events?.length || 0,
236
- totalSteps: 0,
237
- totalActivities: 0,
238
- hasErrors: false
239
- };
240
- if (trace.events) {
241
- for (const event of trace.events) {
242
- if (event.kind === 'step') {
243
- stats.totalSteps++;
244
- }
245
- if (event.kind === 'activity') {
246
- stats.totalActivities++;
247
- }
248
- if (event.phase === 'error') {
249
- stats.hasErrors = true;
250
- }
251
- }
252
- }
253
- return stats;
254
- }
255
- /**
256
- * Get tree connector character
257
- */
258
- function getConnector(isLast) {
259
- return isLast ? '└─ ' : '├─ ';
260
- }
261
- /**
262
- * Format value for debug detail display
263
- */
264
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
265
- function formatDebugDetailValue(value) {
266
- if (value === null) {
267
- return 'null';
268
- }
269
- if (value === undefined) {
270
- return 'undefined';
271
- }
272
- if (typeof value === 'string') {
273
- // For short strings, show inline
274
- if (value.length <= 60) {
275
- return value;
276
- }
277
- // For longer strings, truncate and suggest JSON format
278
- return `${value.substring(0, 60)}... (truncated)`;
279
- }
280
- if (typeof value === 'number' || typeof value === 'boolean') {
281
- return String(value);
282
- }
283
- if (Array.isArray(value)) {
284
- if (value.length === 0) {
285
- return '[]';
286
- }
287
- if (value.length <= 3 && value.every(v => typeof v === 'string' || typeof v === 'number')) {
288
- return `[${value.join(', ')}]`;
289
- }
290
- return `[Array with ${value.length} items]`;
291
- }
292
- if (typeof value === 'object') {
293
- const keys = Object.keys(value);
294
- if (keys.length === 0) {
295
- return '{}';
296
- }
297
- // For simple objects with few keys, show inline
298
- if (keys.length <= 2) {
299
- const pairs = keys.map(k => {
300
- const v = value[k];
301
- if (typeof v === 'string' && v.length > 30) {
302
- return `${k}: "${v.substring(0, 30)}..."`;
303
- }
304
- if (typeof v === 'object') {
305
- return `${k}: {...}`;
306
- }
307
- return `${k}: ${JSON.stringify(v)}`;
308
- });
309
- return `{ ${pairs.join(', ')} }`;
310
- }
311
- // For complex objects, just show key count
312
- return `{ ${keys.length} properties }`;
313
- }
314
- return String(value);
315
- }
316
- /**
317
- * Get debug node display information
318
- */
319
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
- function getDebugNodeInfo(node) {
321
- if (typeof node === 'string') {
322
- return node;
323
- }
324
- if (typeof node !== 'object') {
325
- return String(node);
326
- }
327
- const parts = [];
328
- // Look for common identifying properties
329
- if (node.kind) {
330
- parts.push(`[${node.kind}]`);
331
- }
332
- else if (node.type) {
333
- parts.push(`[${node.type}]`);
334
- }
335
- if (node.name) {
336
- parts.push(node.name);
337
- }
338
- else if (node.workflowName) {
339
- parts.push(node.workflowName);
340
- }
341
- else if (node.stepName) {
342
- parts.push(node.stepName);
343
- }
344
- else if (node.activityName) {
345
- parts.push(node.activityName);
346
- }
347
- // Add phase/status indicators
348
- if (node.phase === 'error' || node.status === 'failed') {
349
- parts.push('[FAILED]');
350
- }
351
- else if (node.phase === 'end' || node.status === 'completed') {
352
- parts.push('[COMPLETED]');
353
- }
354
- else if (node.status === 'running') {
355
- parts.push('[RUNNING]');
356
- }
357
- // If we have no meaningful parts, show a summary of the object
358
- if (parts.length === 0) {
359
- const keys = Object.keys(node).filter(k => k !== 'children' && k !== 'parent');
360
- if (keys.length > 0) {
361
- return `Node {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', ...' : ''}}`;
362
- }
363
- return 'Node';
364
- }
365
- return parts.join(' ');
366
- }
367
- /**
368
- * Add debug node details to lines array
369
- */
370
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
371
- function addDebugNodeDetails(node, prefix, lines) {
372
- if (typeof node !== 'object' || node === null) {
373
- return;
374
- }
375
- const details = [];
376
- // Add timing information
377
- if (node.startedAt || node.timestamp) {
378
- const startTime = node.startedAt || node.timestamp;
379
- const startDate = new Date(startTime);
380
- if (!isNaN(startDate.getTime())) {
381
- details.push(`${prefix}Started: ${startDate.toISOString()}`);
382
- }
383
- }
384
- if (node.endedAt) {
385
- const endDate = new Date(node.endedAt);
386
- if (!isNaN(endDate.getTime())) {
387
- details.push(`${prefix}Ended: ${endDate.toISOString()}`);
388
- }
389
- }
390
- // Calculate and show duration
391
- if (node.startedAt && node.endedAt) {
392
- const duration = node.endedAt - node.startedAt;
393
- details.push(`${prefix}Duration: ${formatDuration(duration)}`);
394
- }
395
- else if (node.duration) {
396
- details.push(`${prefix}Duration: ${formatDuration(node.duration)}`);
397
- }
398
- // Show input
399
- if (node.input !== undefined && node.input !== null) {
400
- const inputStr = formatDebugDetailValue(node.input);
401
- details.push(`${prefix}Input: ${inputStr}`);
402
- }
403
- // Show output
404
- if (node.output !== undefined && node.output !== null) {
405
- const outputStr = formatDebugDetailValue(node.output);
406
- details.push(`${prefix}Output: ${outputStr}`);
407
- }
408
- // Show error if present
409
- if (node.error) {
410
- const errorMsg = typeof node.error === 'string' ? node.error : (node.error.message || JSON.stringify(node.error));
411
- details.push(`${prefix}Error: ${errorMsg}`);
412
- }
413
- // Add all details to lines
414
- for (const detail of details) {
415
- lines.push(detail);
416
- }
417
- // Add spacing if we had details
418
- if (details.length > 0) {
419
- lines.push('');
420
- }
421
- }
422
- /**
423
- * Build debug tree lines recursively
424
- */
425
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
426
- function buildDebugTreeLines(node, depth, isLast, prefix, lines) {
427
- if (node === null || node === undefined) {
428
- return;
429
- }
430
- // Create the tree structure characters
431
- const isRoot = depth === 0;
432
- const connector = isRoot ? '' : getConnector(isLast);
433
- const indent = isRoot ? '' : prefix + connector;
434
- // Build the node display string
435
- const nodeInfo = getDebugNodeInfo(node);
436
- lines.push(indent + nodeInfo);
437
- // Display additional details with proper indentation
438
- const detailPrefix = isRoot ? ' ' : prefix + (isLast ? ' ' : '│ ');
439
- addDebugNodeDetails(node, detailPrefix, lines);
440
- // Update prefix for children
441
- const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
442
- // Process children if they exist
443
- if (node.children && Array.isArray(node.children)) {
444
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
445
- node.children.forEach((child, i) => {
446
- const isLastChild = i === node.children.length - 1;
447
- buildDebugTreeLines(child, depth + 1, isLastChild, childPrefix, lines);
448
- });
449
- }
450
- }
451
- /**
452
- * Display trace tree with debug command formatting style
453
- */
454
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
455
- export function displayDebugTree(node) {
456
- const lines = [];
457
- buildDebugTreeLines(node, 0, false, '', lines);
458
- return lines.join('\n');
459
- }
460
- // For backward compatibility, export an object with the main functions
461
- export const traceFormatter = {
462
- format,
463
- getSummary,
464
- displayDebugTree
465
- };