bktide 1.0.1755568192 → 1.0.1755655078

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/README.md CHANGED
@@ -50,10 +50,25 @@ See [Shell Completions Guide](docs/shell-completions.md) for detailed installati
50
50
  - [Development Guide](docs/development.md) - Information about running and developing the CLI
51
51
  - [Authentication](docs/authentication.md) - How to authenticate with Buildkite
52
52
  - [Caching](docs/caching.md) - Information about the CLI's caching system
53
+ - [Testing Guide](docs/testing-guide.md) - Running tests and testing strategy
54
+ - [API Reference](docs/api-reference.md) - Complete command reference
55
+ - [Troubleshooting](docs/troubleshooting.md) - Common issues and solutions
53
56
  - [Alfred Integration (Overview)](docs/alfred.md) - What the Alfred integration does and quick usage
54
57
  - [Alfred Installation](docs/alfred-installation.md) - End-user install, configuration, and troubleshooting
55
58
  - [Alfred Development](docs/alfred-development.md) - Packaging, wrapper behavior, metadata, and workflow wiring
56
59
 
60
+ ## Testing
61
+
62
+ Run the test suite with:
63
+ ```bash
64
+ npm test # Run all tests
65
+ npm run test:watch # Watch mode for development
66
+ npm run test:coverage # Generate coverage report
67
+ npm run test:extract-patterns # Extract patterns from real data (requires token)
68
+ ```
69
+
70
+ See [Testing Guide](docs/testing/README.md) for details on the hybrid testing strategy.
71
+
57
72
  ## Usage
58
73
 
59
74
  ### Show Your Login Information
@@ -141,6 +156,33 @@ bktide annotations https://buildkite.com/gusto/zenpayroll/builds/1287418 --forma
141
156
  bktide annotations gusto/zenpayroll/1287418 --context build-resources --format json
142
157
  ```
143
158
 
159
+ ### Show Build Details
160
+
161
+ View detailed information about a specific build including jobs and annotations.
162
+
163
+ ```bash
164
+ # View build by slug format
165
+ bktide build org/pipeline/123
166
+
167
+ # View build by URL format
168
+ bktide build @https://buildkite.com/org/pipeline/builds/123
169
+ ```
170
+
171
+ Additional options:
172
+ ```bash
173
+ # Fetch all jobs (handles pagination for large builds)
174
+ bktide build org/pipeline/123 --jobs
175
+
176
+ # Show failure details and error summaries
177
+ bktide build org/pipeline/123 --failed
178
+
179
+ # Include full annotation content
180
+ bktide build org/pipeline/123 --annotations
181
+
182
+ # Combine options for comprehensive view
183
+ bktide build org/pipeline/123 --jobs --failed --annotations
184
+ ```
185
+
144
186
  ### Generate Shell Completions
145
187
 
146
188
  ```bash
@@ -70,21 +70,43 @@ export class ShowBuild extends BaseCommand {
70
70
  }
71
71
  async fetchBuildData(buildSlug, options) {
72
72
  // Determine what data we need to fetch based on options
73
- const needsJobs = options.jobs || options.failed || options.full;
73
+ const needsAllJobs = options.jobs || options.failed || options.full;
74
74
  const needsAnnotations = options.annotations || options.annotationsFull || options.full;
75
75
  if (options.debug) {
76
76
  logger.debug('Fetching build data', {
77
77
  buildSlug,
78
- needsJobs,
78
+ needsAllJobs,
79
79
  needsAnnotations,
80
80
  full: options.full
81
81
  });
82
82
  }
83
- // Fetch the appropriate level of detail
84
- if (options.full || needsJobs) {
85
- return await this.client.getBuildFull(buildSlug);
83
+ // Use the new pagination-aware method when we need all jobs
84
+ if (needsAllJobs) {
85
+ // Show progress when fetching many jobs (only in plain format)
86
+ const progressCallback = options.format === 'plain' || !options.format
87
+ ? (fetched, total) => {
88
+ const totalStr = total ? `/${total}` : '';
89
+ process.stderr.write(`\rFetching jobs: ${fetched}${totalStr}...`);
90
+ }
91
+ : undefined;
92
+ const buildData = await this.client.getBuildSummaryWithAllJobs(buildSlug, {
93
+ fetchAllJobs: true,
94
+ onProgress: progressCallback
95
+ });
96
+ // Clear the progress line
97
+ if (progressCallback) {
98
+ process.stderr.write('\r\x1b[K'); // Clear the line
99
+ }
100
+ // If we need full details (like command text), fetch that separately
101
+ if (options.full) {
102
+ // For now, getBuildFull still provides more detailed fields
103
+ // In the future, we could enhance the pagination query to include these
104
+ return await this.client.getBuildFull(buildSlug);
105
+ }
106
+ return buildData;
86
107
  }
87
108
  else {
109
+ // Just get the summary with first 100 jobs
88
110
  return await this.client.getBuildSummary(buildSlug);
89
111
  }
90
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ShowBuild.js","sourceRoot":"/","sources":["commands/ShowBuild.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseCommand, BaseCommandOptions } from './BaseCommand.js';\nimport { logger } from '../services/logger.js';\nimport { parseBuildRef } from '../utils/parseBuildRef.js';\nimport { FormatterFactory, FormatterType } from '../formatters/index.js';\nimport { Progress } from '../ui/progress.js';\n\nexport interface ShowBuildOptions extends BaseCommandOptions {\n jobs?: boolean;\n failed?: boolean;\n annotations?: boolean;\n annotationsFull?: boolean;\n full?: boolean;\n summary?: boolean;\n allJobs?: boolean;\n buildArg?: string;\n}\n\nexport class ShowBuild extends BaseCommand {\n static requiresToken = true;\n\n async execute(options: ShowBuildOptions): Promise<number> {\n if (options.debug) {\n logger.debug('Starting ShowBuild command execution', options);\n }\n \n if (!options.buildArg) {\n logger.error('Build reference is required');\n return 1;\n }\n \n // Adjust options based on implications\n const adjustedOptions = { ...options };\n if (options.failed) {\n adjustedOptions.jobs = true; // --failed implies --jobs\n }\n if (options.annotationsFull) {\n adjustedOptions.annotations = true; // --annotations-full implies --annotations\n }\n if (options.allJobs) {\n adjustedOptions.jobs = true; // --all-jobs implies --jobs\n }\n if (options.full) {\n // --full shows everything\n adjustedOptions.jobs = true;\n adjustedOptions.annotations = true;\n adjustedOptions.allJobs = true; // --full shows all jobs\n }\n \n // Initialize spinner early\n const format = options.format || 'plain';\n const spinner = Progress.spinner('Fetching build details…', { format });\n \n try {\n // Ensure the command is initialized\n await this.ensureInitialized();\n \n // Parse build reference\n const buildRef = parseBuildRef(options.buildArg);\n if (options.debug) {\n logger.debug('Parsed build reference:', buildRef);\n }\n \n // Construct build slug for GraphQL\n const buildSlug = `${buildRef.org}/${buildRef.pipeline}/${buildRef.number}`;\n \n // Fetch build data based on what's needed\n const buildData = await this.fetchBuildData(buildSlug, adjustedOptions);\n spinner.stop();\n \n // Get the appropriate formatter\n const formatter = FormatterFactory.getFormatter(\n FormatterType.BUILD_DETAIL,\n options.format || 'plain'\n ) as any;\n \n // Format and output the results\n const output = formatter.formatBuildDetail(buildData, adjustedOptions);\n \n logger.console(output);\n \n return 0;\n } catch (error) {\n spinner.stop();\n logger.error('Failed to fetch build:', error);\n \n // Handle the error with the formatter\n const formatter = FormatterFactory.getFormatter(\n FormatterType.BUILD_DETAIL,\n options.format || 'plain'\n ) as any;\n \n const errorOutput = formatter.formatBuildDetail(null, {\n ...adjustedOptions,\n hasError: true,\n errorMessage: error instanceof Error ? error.message : 'Unknown error occurred',\n errorType: 'api',\n });\n \n logger.console(errorOutput);\n return 1;\n }\n }\n \n private async fetchBuildData(buildSlug: string, options: ShowBuildOptions): Promise<any> {\n // Determine what data we need to fetch based on options\n const needsJobs = options.jobs || options.failed || options.full;\n const needsAnnotations = options.annotations || options.annotationsFull || options.full;\n \n if (options.debug) {\n logger.debug('Fetching build data', {\n buildSlug,\n needsJobs,\n needsAnnotations,\n full: options.full\n });\n }\n \n // Fetch the appropriate level of detail\n if (options.full || needsJobs) {\n return await this.client.getBuildFull(buildSlug);\n } else {\n return await this.client.getBuildSummary(buildSlug);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ShowBuild.js","sourceRoot":"/","sources":["commands/ShowBuild.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseCommand, BaseCommandOptions } from './BaseCommand.js';\nimport { logger } from '../services/logger.js';\nimport { parseBuildRef } from '../utils/parseBuildRef.js';\nimport { FormatterFactory, FormatterType } from '../formatters/index.js';\nimport { Progress } from '../ui/progress.js';\n\nexport interface ShowBuildOptions extends BaseCommandOptions {\n jobs?: boolean;\n failed?: boolean;\n annotations?: boolean;\n annotationsFull?: boolean;\n full?: boolean;\n summary?: boolean;\n allJobs?: boolean;\n buildArg?: string;\n}\n\nexport class ShowBuild extends BaseCommand {\n static requiresToken = true;\n\n async execute(options: ShowBuildOptions): Promise<number> {\n if (options.debug) {\n logger.debug('Starting ShowBuild command execution', options);\n }\n \n if (!options.buildArg) {\n logger.error('Build reference is required');\n return 1;\n }\n \n // Adjust options based on implications\n const adjustedOptions = { ...options };\n if (options.failed) {\n adjustedOptions.jobs = true; // --failed implies --jobs\n }\n if (options.annotationsFull) {\n adjustedOptions.annotations = true; // --annotations-full implies --annotations\n }\n if (options.allJobs) {\n adjustedOptions.jobs = true; // --all-jobs implies --jobs\n }\n if (options.full) {\n // --full shows everything\n adjustedOptions.jobs = true;\n adjustedOptions.annotations = true;\n adjustedOptions.allJobs = true; // --full shows all jobs\n }\n \n // Initialize spinner early\n const format = options.format || 'plain';\n const spinner = Progress.spinner('Fetching build details…', { format });\n \n try {\n // Ensure the command is initialized\n await this.ensureInitialized();\n \n // Parse build reference\n const buildRef = parseBuildRef(options.buildArg);\n if (options.debug) {\n logger.debug('Parsed build reference:', buildRef);\n }\n \n // Construct build slug for GraphQL\n const buildSlug = `${buildRef.org}/${buildRef.pipeline}/${buildRef.number}`;\n \n // Fetch build data based on what's needed\n const buildData = await this.fetchBuildData(buildSlug, adjustedOptions);\n spinner.stop();\n \n // Get the appropriate formatter\n const formatter = FormatterFactory.getFormatter(\n FormatterType.BUILD_DETAIL,\n options.format || 'plain'\n ) as any;\n \n // Format and output the results\n const output = formatter.formatBuildDetail(buildData, adjustedOptions);\n \n logger.console(output);\n \n return 0;\n } catch (error) {\n spinner.stop();\n logger.error('Failed to fetch build:', error);\n \n // Handle the error with the formatter\n const formatter = FormatterFactory.getFormatter(\n FormatterType.BUILD_DETAIL,\n options.format || 'plain'\n ) as any;\n \n const errorOutput = formatter.formatBuildDetail(null, {\n ...adjustedOptions,\n hasError: true,\n errorMessage: error instanceof Error ? error.message : 'Unknown error occurred',\n errorType: 'api',\n });\n \n logger.console(errorOutput);\n return 1;\n }\n }\n \n private async fetchBuildData(buildSlug: string, options: ShowBuildOptions): Promise<any> {\n // Determine what data we need to fetch based on options\n const needsAllJobs = options.jobs || options.failed || options.full;\n const needsAnnotations = options.annotations || options.annotationsFull || options.full;\n \n if (options.debug) {\n logger.debug('Fetching build data', {\n buildSlug,\n needsAllJobs,\n needsAnnotations,\n full: options.full\n });\n }\n \n // Use the new pagination-aware method when we need all jobs\n if (needsAllJobs) {\n // Show progress when fetching many jobs (only in plain format)\n const progressCallback = options.format === 'plain' || !options.format\n ? (fetched: number, total?: number) => {\n const totalStr = total ? `/${total}` : '';\n process.stderr.write(`\\rFetching jobs: ${fetched}${totalStr}...`);\n }\n : undefined;\n \n const buildData = await this.client.getBuildSummaryWithAllJobs(buildSlug, {\n fetchAllJobs: true,\n onProgress: progressCallback\n });\n \n // Clear the progress line\n if (progressCallback) {\n process.stderr.write('\\r\\x1b[K'); // Clear the line\n }\n \n // If we need full details (like command text), fetch that separately\n if (options.full) {\n // For now, getBuildFull still provides more detailed fields\n // In the future, we could enhance the pagination query to include these\n return await this.client.getBuildFull(buildSlug);\n }\n \n return buildData;\n } else {\n // Just get the summary with first 100 jobs\n return await this.client.getBuildSummary(buildSlug);\n }\n }\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  import { BaseFormatter } from './Formatter.js';
2
2
  import { formatAnnotationBody } from '../../utils/textFormatter.js';
3
3
  import { SEMANTIC_COLORS, formatEmptyState, formatError } from '../../ui/theme.js';
4
+ import { useAscii } from '../../ui/symbols.js';
5
+ import { termWidth } from '../../ui/width.js';
4
6
  export class PlainTextFormatter extends BaseFormatter {
5
7
  name = 'PlainText';
6
8
  formatAnnotations(annotations, options) {
@@ -20,40 +22,97 @@ export class PlainTextFormatter extends BaseFormatter {
20
22
  ]);
21
23
  }
22
24
  const lines = [];
23
- // Style symbols for different annotation types
25
+ const isAscii = useAscii();
26
+ const terminalWidth = termWidth();
27
+ // Box drawing characters
28
+ const boxChars = isAscii ? {
29
+ horizontal: '-',
30
+ vertical: '|',
31
+ topLeft: '+',
32
+ topRight: '+',
33
+ bottomLeft: '+',
34
+ bottomRight: '+',
35
+ cross: '+',
36
+ verticalRight: '+',
37
+ verticalLeft: '+',
38
+ horizontalDown: '+',
39
+ horizontalUp: '+'
40
+ } : {
41
+ horizontal: '─',
42
+ vertical: '│',
43
+ topLeft: '┌',
44
+ topRight: '┐',
45
+ bottomLeft: '└',
46
+ bottomRight: '┘',
47
+ cross: '┼',
48
+ verticalRight: '├',
49
+ verticalLeft: '┤',
50
+ horizontalDown: '┬',
51
+ horizontalUp: '┴'
52
+ };
53
+ // Style symbols for different annotation types (handle both cases)
24
54
  const styleSymbols = {
25
- error: '✖',
26
- warning: '',
27
- info: '',
28
- success: ''
55
+ error: isAscii ? '[X]' : '✖',
56
+ ERROR: isAscii ? '[X]' : '✖',
57
+ warning: isAscii ? '[!]' : '⚠',
58
+ WARNING: isAscii ? '[!]' : '⚠',
59
+ info: isAscii ? '[i]' : 'ℹ',
60
+ INFO: isAscii ? '[i]' : 'ℹ',
61
+ success: isAscii ? '[✓]' : '✓',
62
+ SUCCESS: isAscii ? '[✓]' : '✓'
29
63
  };
30
- // Style colors for different annotation types
64
+ // Style colors for different annotation types (handle both cases)
31
65
  const styleColors = {
32
66
  error: SEMANTIC_COLORS.error,
67
+ ERROR: SEMANTIC_COLORS.error,
33
68
  warning: SEMANTIC_COLORS.warning,
69
+ WARNING: SEMANTIC_COLORS.warning,
34
70
  info: SEMANTIC_COLORS.info,
35
- success: SEMANTIC_COLORS.success
71
+ INFO: SEMANTIC_COLORS.info,
72
+ success: SEMANTIC_COLORS.success,
73
+ SUCCESS: SEMANTIC_COLORS.success
74
+ };
75
+ // Create a horizontal divider with padding and centering
76
+ const createDivider = (width = 80) => {
77
+ const padding = 2; // 1 space on each side
78
+ const maxWidth = Math.min(width, terminalWidth - padding);
79
+ const dividerLength = Math.max(20, maxWidth - padding); // Minimum 20 chars
80
+ const divider = boxChars.horizontal.repeat(dividerLength);
81
+ // Center the divider within the terminal width
82
+ const totalPadding = terminalWidth - dividerLength;
83
+ const leftPadding = Math.floor(totalPadding / 2);
84
+ const spaces = ' '.repeat(Math.max(0, leftPadding));
85
+ return SEMANTIC_COLORS.dim(spaces + divider);
36
86
  };
37
87
  annotations.forEach((annotation, index) => {
88
+ const symbol = styleSymbols[annotation.style] || (isAscii ? '*' : '•');
89
+ const colorFn = styleColors[annotation.style] || ((s) => s);
90
+ // Add divider between annotations (but not before the first one)
38
91
  if (index > 0) {
39
- lines.push(''); // Add blank line between annotations
92
+ lines.push('');
93
+ lines.push(createDivider());
94
+ lines.push('');
40
95
  }
41
- const symbol = styleSymbols[annotation.style] || '•';
42
- const colorFn = styleColors[annotation.style] || ((s) => s);
43
- // Style with symbol and color
44
- lines.push(colorFn(`${symbol} ${annotation.context} (${annotation.style})`));
45
- lines.push('');
96
+ // Single line header with pipe: "│ ℹ info: test-mapping-build"
97
+ const pipe = colorFn(boxChars.vertical);
98
+ const header = `${pipe} ${symbol} ${annotation.style.toLowerCase()}: ${annotation.context}`;
99
+ lines.push(header);
100
+ // Add blank line with pipe for visual continuity
101
+ lines.push(pipe);
46
102
  // Format the body HTML with proper HTML/markdown handling
47
103
  const formattedBody = formatAnnotationBody(annotation.body.html);
48
- // Indent the formatted body properly
49
- const indentedBody = formattedBody
50
- .split('\n')
51
- .map(line => ` ${line}`)
52
- .join('\n');
53
- lines.push(indentedBody);
104
+ // Add vertical pipes to the left of the body content for visual continuity
105
+ // Use the same color as the header for the pipes
106
+ const bodyLines = formattedBody.split('\n');
107
+ bodyLines.forEach((line) => {
108
+ const paddedLine = line ? ` ${line}` : '';
109
+ lines.push(`${pipe}${paddedLine}`);
110
+ });
54
111
  });
55
- // Add summary if multiple annotations
112
+ // Add summary footer
56
113
  if (annotations.length > 1) {
114
+ lines.push('');
115
+ lines.push(createDivider());
57
116
  lines.push('');
58
117
  lines.push(SEMANTIC_COLORS.dim(`${SEMANTIC_COLORS.count(annotations.length.toString())} annotations found`));
59
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlainTextFormatter.js","sourceRoot":"/","sources":["formatters/annotations/PlainTextFormatter.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseFormatter, AnnotationFormatterOptions } from './Formatter.js';\nimport { Annotation } from '../../types/index.js';\nimport { formatAnnotationBody } from '../../utils/textFormatter.js';\nimport { SEMANTIC_COLORS, formatEmptyState, formatError } from '../../ui/theme.js';\n\nexport class PlainTextFormatter extends BaseFormatter {\n name = 'PlainText';\n\n formatAnnotations(annotations: Annotation[], options?: AnnotationFormatterOptions): string {\n if (options?.hasError) {\n return formatError(options.errorMessage || 'Failed to fetch annotations', {\n showHelp: true,\n helpCommand: 'bktide annotations --help'\n });\n }\n\n if (!annotations || annotations.length === 0) {\n const message = options?.contextFilter\n ? `No annotations found for this build with context '${options.contextFilter}'`\n : 'No annotations found for this build';\n \n return formatEmptyState(message, [\n 'Annotations are created by build steps',\n 'Check the build has completed and has annotation steps'\n ]);\n }\n\n const lines: string[] = [];\n \n // Style symbols for different annotation types\n const styleSymbols: Record<string, string> = {\n error: '✖',\n warning: '⚠',\n info: 'ℹ',\n success: '✓'\n };\n \n // Style colors for different annotation types\n const styleColors: Record<string, (s: string) => string> = {\n error: SEMANTIC_COLORS.error,\n warning: SEMANTIC_COLORS.warning,\n info: SEMANTIC_COLORS.info,\n success: SEMANTIC_COLORS.success\n };\n \n annotations.forEach((annotation, index) => {\n if (index > 0) {\n lines.push(''); // Add blank line between annotations\n }\n \n const symbol = styleSymbols[annotation.style] || '•';\n const colorFn = styleColors[annotation.style] || ((s: string) => s);\n \n // Style with symbol and color\n lines.push(colorFn(`${symbol} ${annotation.context} (${annotation.style})`));\n lines.push('');\n \n // Format the body HTML with proper HTML/markdown handling\n const formattedBody = formatAnnotationBody(annotation.body.html);\n \n // Indent the formatted body properly\n const indentedBody = formattedBody\n .split('\\n')\n .map(line => ` ${line}`)\n .join('\\n');\n \n lines.push(indentedBody);\n });\n \n // Add summary if multiple annotations\n if (annotations.length > 1) {\n lines.push('');\n lines.push(SEMANTIC_COLORS.dim(`${SEMANTIC_COLORS.count(annotations.length.toString())} annotations found`));\n }\n\n return lines.join('\\n');\n }\n}\n"]}
1
+ {"version":3,"file":"PlainTextFormatter.js","sourceRoot":"/","sources":["formatters/annotations/PlainTextFormatter.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseFormatter, AnnotationFormatterOptions } from './Formatter.js';\nimport { Annotation } from '../../types/index.js';\nimport { formatAnnotationBody } from '../../utils/textFormatter.js';\nimport { SEMANTIC_COLORS, formatEmptyState, formatError } from '../../ui/theme.js';\nimport { useAscii } from '../../ui/symbols.js';\nimport { termWidth } from '../../ui/width.js';\n\nexport class PlainTextFormatter extends BaseFormatter {\n name = 'PlainText';\n\n formatAnnotations(annotations: Annotation[], options?: AnnotationFormatterOptions): string {\n if (options?.hasError) {\n return formatError(options.errorMessage || 'Failed to fetch annotations', {\n showHelp: true,\n helpCommand: 'bktide annotations --help'\n });\n }\n\n if (!annotations || annotations.length === 0) {\n const message = options?.contextFilter\n ? `No annotations found for this build with context '${options.contextFilter}'`\n : 'No annotations found for this build';\n \n return formatEmptyState(message, [\n 'Annotations are created by build steps',\n 'Check the build has completed and has annotation steps'\n ]);\n }\n\n const lines: string[] = [];\n const isAscii = useAscii();\n const terminalWidth = termWidth();\n \n // Box drawing characters\n const boxChars = isAscii ? {\n horizontal: '-',\n vertical: '|',\n topLeft: '+',\n topRight: '+',\n bottomLeft: '+',\n bottomRight: '+',\n cross: '+',\n verticalRight: '+',\n verticalLeft: '+',\n horizontalDown: '+',\n horizontalUp: '+'\n } : {\n horizontal: '─',\n vertical: '│',\n topLeft: '┌',\n topRight: '┐',\n bottomLeft: '└',\n bottomRight: '┘',\n cross: '┼',\n verticalRight: '├',\n verticalLeft: '┤',\n horizontalDown: '┬',\n horizontalUp: '┴'\n };\n \n // Style symbols for different annotation types (handle both cases)\n const styleSymbols: Record<string, string> = {\n error: isAscii ? '[X]' : '✖',\n ERROR: isAscii ? '[X]' : '✖',\n warning: isAscii ? '[!]' : '⚠',\n WARNING: isAscii ? '[!]' : '⚠',\n info: isAscii ? '[i]' : 'ℹ',\n INFO: isAscii ? '[i]' : 'ℹ',\n success: isAscii ? '[]' : '✓',\n SUCCESS: isAscii ? '[✓]' : '✓'\n };\n \n // Style colors for different annotation types (handle both cases)\n const styleColors: Record<string, (s: string) => string> = {\n error: SEMANTIC_COLORS.error,\n ERROR: SEMANTIC_COLORS.error,\n warning: SEMANTIC_COLORS.warning,\n WARNING: SEMANTIC_COLORS.warning,\n info: SEMANTIC_COLORS.info,\n INFO: SEMANTIC_COLORS.info,\n success: SEMANTIC_COLORS.success,\n SUCCESS: SEMANTIC_COLORS.success\n };\n \n // Create a horizontal divider with padding and centering\n const createDivider = (width: number = 80) => {\n const padding = 2; // 1 space on each side\n const maxWidth = Math.min(width, terminalWidth - padding);\n const dividerLength = Math.max(20, maxWidth - padding); // Minimum 20 chars\n const divider = boxChars.horizontal.repeat(dividerLength);\n \n // Center the divider within the terminal width\n const totalPadding = terminalWidth - dividerLength;\n const leftPadding = Math.floor(totalPadding / 2);\n const spaces = ' '.repeat(Math.max(0, leftPadding));\n \n return SEMANTIC_COLORS.dim(spaces + divider);\n };\n \n annotations.forEach((annotation, index) => {\n const symbol = styleSymbols[annotation.style] || (isAscii ? '*' : '•');\n const colorFn = styleColors[annotation.style] || ((s: string) => s);\n \n // Add divider between annotations (but not before the first one)\n if (index > 0) {\n lines.push('');\n lines.push(createDivider());\n lines.push('');\n }\n \n // Single line header with pipe: \"│ ℹ info: test-mapping-build\"\n const pipe = colorFn(boxChars.vertical);\n const header = `${pipe} ${symbol} ${annotation.style.toLowerCase()}: ${annotation.context}`;\n lines.push(header);\n \n // Add blank line with pipe for visual continuity\n lines.push(pipe);\n \n // Format the body HTML with proper HTML/markdown handling\n const formattedBody = formatAnnotationBody(annotation.body.html);\n \n // Add vertical pipes to the left of the body content for visual continuity\n // Use the same color as the header for the pipes\n const bodyLines = formattedBody.split('\\n');\n bodyLines.forEach((line) => {\n const paddedLine = line ? ` ${line}` : '';\n lines.push(`${pipe}${paddedLine}`);\n });\n });\n \n // Add summary footer\n if (annotations.length > 1) {\n lines.push('');\n lines.push(createDivider());\n lines.push('');\n lines.push(SEMANTIC_COLORS.dim(`${SEMANTIC_COLORS.count(annotations.length.toString())} annotations found`));\n }\n\n return lines.join('\\n');\n }\n}\n"]}