@cisco_open/linting-orchestrator 1.0.0-rc.4

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 (197) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +43 -0
  4. package/build/cli/api-client.d.ts +170 -0
  5. package/build/cli/api-client.d.ts.map +1 -0
  6. package/build/cli/api-client.js +284 -0
  7. package/build/cli/api-client.js.map +1 -0
  8. package/build/cli/commands/agents.d.ts +7 -0
  9. package/build/cli/commands/agents.d.ts.map +1 -0
  10. package/build/cli/commands/agents.js +694 -0
  11. package/build/cli/commands/agents.js.map +1 -0
  12. package/build/cli/commands/completion.d.ts +9 -0
  13. package/build/cli/commands/completion.d.ts.map +1 -0
  14. package/build/cli/commands/completion.js +177 -0
  15. package/build/cli/commands/completion.js.map +1 -0
  16. package/build/cli/commands/config.d.ts +10 -0
  17. package/build/cli/commands/config.d.ts.map +1 -0
  18. package/build/cli/commands/config.js +284 -0
  19. package/build/cli/commands/config.js.map +1 -0
  20. package/build/cli/commands/health.d.ts +11 -0
  21. package/build/cli/commands/health.d.ts.map +1 -0
  22. package/build/cli/commands/health.js +38 -0
  23. package/build/cli/commands/health.js.map +1 -0
  24. package/build/cli/commands/help.d.ts +6 -0
  25. package/build/cli/commands/help.d.ts.map +1 -0
  26. package/build/cli/commands/help.js +20 -0
  27. package/build/cli/commands/help.js.map +1 -0
  28. package/build/cli/commands/history.d.ts +11 -0
  29. package/build/cli/commands/history.d.ts.map +1 -0
  30. package/build/cli/commands/history.js +50 -0
  31. package/build/cli/commands/history.js.map +1 -0
  32. package/build/cli/commands/jobs.d.ts +12 -0
  33. package/build/cli/commands/jobs.d.ts.map +1 -0
  34. package/build/cli/commands/jobs.js +84 -0
  35. package/build/cli/commands/jobs.js.map +1 -0
  36. package/build/cli/commands/lint.d.ts +15 -0
  37. package/build/cli/commands/lint.d.ts.map +1 -0
  38. package/build/cli/commands/lint.js +384 -0
  39. package/build/cli/commands/lint.js.map +1 -0
  40. package/build/cli/commands/ps.d.ts +8 -0
  41. package/build/cli/commands/ps.d.ts.map +1 -0
  42. package/build/cli/commands/ps.js +74 -0
  43. package/build/cli/commands/ps.js.map +1 -0
  44. package/build/cli/commands/reproduce.d.ts +9 -0
  45. package/build/cli/commands/reproduce.d.ts.map +1 -0
  46. package/build/cli/commands/reproduce.js +31 -0
  47. package/build/cli/commands/reproduce.js.map +1 -0
  48. package/build/cli/commands/reset.d.ts +5 -0
  49. package/build/cli/commands/reset.d.ts.map +1 -0
  50. package/build/cli/commands/reset.js +13 -0
  51. package/build/cli/commands/reset.js.map +1 -0
  52. package/build/cli/commands/results.d.ts +13 -0
  53. package/build/cli/commands/results.d.ts.map +1 -0
  54. package/build/cli/commands/results.js +129 -0
  55. package/build/cli/commands/results.js.map +1 -0
  56. package/build/cli/commands/rulesets/check.d.ts +12 -0
  57. package/build/cli/commands/rulesets/check.d.ts.map +1 -0
  58. package/build/cli/commands/rulesets/check.js +226 -0
  59. package/build/cli/commands/rulesets/check.js.map +1 -0
  60. package/build/cli/commands/rulesets/index.d.ts +5 -0
  61. package/build/cli/commands/rulesets/index.d.ts.map +1 -0
  62. package/build/cli/commands/rulesets/index.js +6 -0
  63. package/build/cli/commands/rulesets/index.js.map +1 -0
  64. package/build/cli/commands/rulesets/view.d.ts +16 -0
  65. package/build/cli/commands/rulesets/view.d.ts.map +1 -0
  66. package/build/cli/commands/rulesets/view.js +100 -0
  67. package/build/cli/commands/rulesets/view.js.map +1 -0
  68. package/build/cli/commands/start.d.ts +16 -0
  69. package/build/cli/commands/start.d.ts.map +1 -0
  70. package/build/cli/commands/start.js +167 -0
  71. package/build/cli/commands/start.js.map +1 -0
  72. package/build/cli/commands/status.d.ts +9 -0
  73. package/build/cli/commands/status.d.ts.map +1 -0
  74. package/build/cli/commands/status.js +46 -0
  75. package/build/cli/commands/status.js.map +1 -0
  76. package/build/cli/commands/stop.d.ts +11 -0
  77. package/build/cli/commands/stop.d.ts.map +1 -0
  78. package/build/cli/commands/stop.js +78 -0
  79. package/build/cli/commands/stop.js.map +1 -0
  80. package/build/cli/config-manager.d.ts +134 -0
  81. package/build/cli/config-manager.d.ts.map +1 -0
  82. package/build/cli/config-manager.js +288 -0
  83. package/build/cli/config-manager.js.map +1 -0
  84. package/build/cli/formatters.d.ts +62 -0
  85. package/build/cli/formatters.d.ts.map +1 -0
  86. package/build/cli/formatters.js +715 -0
  87. package/build/cli/formatters.js.map +1 -0
  88. package/build/cli/history-manager.d.ts +97 -0
  89. package/build/cli/history-manager.d.ts.map +1 -0
  90. package/build/cli/history-manager.js +201 -0
  91. package/build/cli/history-manager.js.map +1 -0
  92. package/build/cli/index.d.ts +16 -0
  93. package/build/cli/index.d.ts.map +1 -0
  94. package/build/cli/index.js +335 -0
  95. package/build/cli/index.js.map +1 -0
  96. package/build/cli/list-rulesets.d.ts +15 -0
  97. package/build/cli/list-rulesets.d.ts.map +1 -0
  98. package/build/cli/list-rulesets.js +193 -0
  99. package/build/cli/list-rulesets.js.map +1 -0
  100. package/build/cli/utils/connection-error.d.ts +9 -0
  101. package/build/cli/utils/connection-error.d.ts.map +1 -0
  102. package/build/cli/utils/connection-error.js +30 -0
  103. package/build/cli/utils/connection-error.js.map +1 -0
  104. package/build/cli/utils/embedded-server.d.ts +21 -0
  105. package/build/cli/utils/embedded-server.d.ts.map +1 -0
  106. package/build/cli/utils/embedded-server.js +61 -0
  107. package/build/cli/utils/embedded-server.js.map +1 -0
  108. package/build/cli/utils/mode-validator.d.ts +13 -0
  109. package/build/cli/utils/mode-validator.d.ts.map +1 -0
  110. package/build/cli/utils/mode-validator.js +31 -0
  111. package/build/cli/utils/mode-validator.js.map +1 -0
  112. package/build/cli/utils/port-checker.d.ts +20 -0
  113. package/build/cli/utils/port-checker.d.ts.map +1 -0
  114. package/build/cli/utils/port-checker.js +49 -0
  115. package/build/cli/utils/port-checker.js.map +1 -0
  116. package/build/config.d.ts +57 -0
  117. package/build/config.d.ts.map +1 -0
  118. package/build/config.js +527 -0
  119. package/build/config.js.map +1 -0
  120. package/build/document-accessor.d.ts +79 -0
  121. package/build/document-accessor.d.ts.map +1 -0
  122. package/build/document-accessor.js +148 -0
  123. package/build/document-accessor.js.map +1 -0
  124. package/build/formatters/reproduce-markdown.d.ts +14 -0
  125. package/build/formatters/reproduce-markdown.d.ts.map +1 -0
  126. package/build/formatters/reproduce-markdown.js +182 -0
  127. package/build/formatters/reproduce-markdown.js.map +1 -0
  128. package/build/formatters/sarif-builder.d.ts +86 -0
  129. package/build/formatters/sarif-builder.d.ts.map +1 -0
  130. package/build/formatters/sarif-builder.js +276 -0
  131. package/build/formatters/sarif-builder.js.map +1 -0
  132. package/build/index.d.ts +3 -0
  133. package/build/index.d.ts.map +1 -0
  134. package/build/index.js +174 -0
  135. package/build/index.js.map +1 -0
  136. package/build/logger.d.ts +38 -0
  137. package/build/logger.d.ts.map +1 -0
  138. package/build/logger.js +105 -0
  139. package/build/logger.js.map +1 -0
  140. package/build/mock-server.d.ts +2 -0
  141. package/build/mock-server.d.ts.map +1 -0
  142. package/build/mock-server.js +290 -0
  143. package/build/mock-server.js.map +1 -0
  144. package/build/orchestrator.d.ts +149 -0
  145. package/build/orchestrator.d.ts.map +1 -0
  146. package/build/orchestrator.js +874 -0
  147. package/build/orchestrator.js.map +1 -0
  148. package/build/ruleset-loader.d.ts +79 -0
  149. package/build/ruleset-loader.d.ts.map +1 -0
  150. package/build/ruleset-loader.js +514 -0
  151. package/build/ruleset-loader.js.map +1 -0
  152. package/build/schemas.d.ts +2568 -0
  153. package/build/schemas.d.ts.map +1 -0
  154. package/build/schemas.js +674 -0
  155. package/build/schemas.js.map +1 -0
  156. package/build/server.d.ts +39 -0
  157. package/build/server.d.ts.map +1 -0
  158. package/build/server.js +834 -0
  159. package/build/server.js.map +1 -0
  160. package/build/storage/memory-storage.d.ts +190 -0
  161. package/build/storage/memory-storage.d.ts.map +1 -0
  162. package/build/storage/memory-storage.js +629 -0
  163. package/build/storage/memory-storage.js.map +1 -0
  164. package/build/storage/redis-storage.d.ts +134 -0
  165. package/build/storage/redis-storage.d.ts.map +1 -0
  166. package/build/storage/redis-storage.js +236 -0
  167. package/build/storage/redis-storage.js.map +1 -0
  168. package/build/storage/storage-adapter.d.ts +189 -0
  169. package/build/storage/storage-adapter.d.ts.map +1 -0
  170. package/build/storage/storage-adapter.js +36 -0
  171. package/build/storage/storage-adapter.js.map +1 -0
  172. package/build/types.d.ts +981 -0
  173. package/build/types.d.ts.map +1 -0
  174. package/build/types.js +5 -0
  175. package/build/types.js.map +1 -0
  176. package/build/utils/version.d.ts +40 -0
  177. package/build/utils/version.d.ts.map +1 -0
  178. package/build/utils/version.js +94 -0
  179. package/build/utils/version.js.map +1 -0
  180. package/build/validation.d.ts +95 -0
  181. package/build/validation.d.ts.map +1 -0
  182. package/build/validation.js +150 -0
  183. package/build/validation.js.map +1 -0
  184. package/build/worker-pool.d.ts +137 -0
  185. package/build/worker-pool.d.ts.map +1 -0
  186. package/build/worker-pool.js +549 -0
  187. package/build/worker-pool.js.map +1 -0
  188. package/build/worker.d.ts +2 -0
  189. package/build/worker.d.ts.map +1 -0
  190. package/build/worker.js +427 -0
  191. package/build/worker.js.map +1 -0
  192. package/package.json +110 -0
  193. package/rulesets/CHANGELOG.md +25 -0
  194. package/rulesets/config/rulesets.yaml +96 -0
  195. package/rulesets/sources/README.md +47 -0
  196. package/rulesets/sources/example/oas-recommended/v1.0.0/ruleset.yaml +6 -0
  197. package/rulesets/sources/example/oas-recommended/v2.0.0/ruleset.yaml +14 -0
@@ -0,0 +1,715 @@
1
+ /**
2
+ * Formatters for orchestrator CLI output
3
+ * Handles color coding, tables, and summary displays
4
+ */
5
+ import chalk from 'chalk';
6
+ import Table from 'cli-table3';
7
+ /**
8
+ * Ensure terminal colors are reset at end of output
9
+ * This prevents terminal corruption when piping to more/less and interrupting
10
+ */
11
+ function ensureReset(output) {
12
+ // Always end with a reset sequence to ensure terminal is left in clean state
13
+ return output + '\x1b[0m';
14
+ }
15
+ /**
16
+ * Get colored severity indicator
17
+ */
18
+ export function formatSeverity(severity) {
19
+ const severityNum = typeof severity === 'number' ? severity :
20
+ severity === 'error' ? 0 : severity === 'warn' ? 1 : severity === 'info' ? 2 : 3;
21
+ switch (severityNum) {
22
+ case 0: // error
23
+ return chalk.red('error');
24
+ case 1: // warning
25
+ return chalk.yellow('warning');
26
+ case 2: // info
27
+ return chalk.blue('info');
28
+ case 3: // hint
29
+ return chalk.gray('hint');
30
+ default:
31
+ return String(severity);
32
+ }
33
+ }
34
+ /**
35
+ * Get severity icon
36
+ */
37
+ export function getSeverityIcon(severity) {
38
+ const severityNum = typeof severity === 'number' ? severity :
39
+ severity === 'error' ? 0 : severity === 'warn' ? 1 : severity === 'info' ? 2 : 3;
40
+ switch (severityNum) {
41
+ case 0: // error
42
+ return chalk.red('✖');
43
+ case 1: // warning
44
+ return chalk.yellow('⚠');
45
+ case 2: // info
46
+ return chalk.blue('ℹ');
47
+ case 3: // hint
48
+ return chalk.gray('💡');
49
+ default:
50
+ return '•';
51
+ }
52
+ }
53
+ /**
54
+ * Format ruleset display name with override info.
55
+ * Returns long form: "pubhub v1.1.0 (2 rules excluded, 1 severity override)"
56
+ * Or short form: "pubhub v1.1.0" when no overrides present.
57
+ */
58
+ export function formatRulesetDisplay(rulesetName, rulesetVersion, ruleOverrides) {
59
+ let display = `${rulesetName} v${rulesetVersion}`;
60
+ if (ruleOverrides && Object.keys(ruleOverrides).length > 0) {
61
+ const excluded = Object.values(ruleOverrides).filter(v => v === 'off').length;
62
+ const severityChanges = Object.values(ruleOverrides).filter(v => v !== 'off').length;
63
+ const parts = [];
64
+ if (excluded > 0)
65
+ parts.push(`${excluded} rule${excluded !== 1 ? 's' : ''} excluded`);
66
+ if (severityChanges > 0)
67
+ parts.push(`${severityChanges} severity override${severityChanges !== 1 ? 's' : ''}`);
68
+ if (parts.length > 0) {
69
+ display += ` (${parts.join(', ')})`;
70
+ }
71
+ }
72
+ return display;
73
+ }
74
+ /**
75
+ * Format ruleset name in short form: "pubhub*" if overrides present, "pubhub" otherwise.
76
+ */
77
+ export function formatRulesetShort(rulesetName, ruleOverrides) {
78
+ if (ruleOverrides && Object.keys(ruleOverrides).length > 0) {
79
+ return `${rulesetName}*`;
80
+ }
81
+ return rulesetName;
82
+ }
83
+ /**
84
+ * Format lint result summary
85
+ */
86
+ export function formatSummary(result, dimmed = false) {
87
+ const { summary } = result;
88
+ const lines = [];
89
+ const applyStyle = (text) => dimmed ? chalk.dim(text) : text;
90
+ const applyBold = (text) => dimmed ? chalk.dim(text) : chalk.bold(text);
91
+ lines.push('');
92
+ lines.push(applyBold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
93
+ lines.push(applyBold('Summary'));
94
+ lines.push(applyBold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
95
+ if (summary.totalIssues === 0) {
96
+ lines.push(applyStyle(chalk.green('✓ No issues found!')));
97
+ }
98
+ else {
99
+ lines.push(applyStyle(`Total Issues: ${chalk.bold(summary.totalIssues)}`));
100
+ if (summary.errorCount > 0) {
101
+ lines.push(applyStyle(` ${chalk.red('✖')} Errors: ${chalk.red(summary.errorCount)}`));
102
+ }
103
+ if (summary.warningCount > 0) {
104
+ lines.push(applyStyle(` ${chalk.yellow('⚠')} Warnings: ${chalk.yellow(summary.warningCount)}`));
105
+ }
106
+ if (summary.infoCount > 0) {
107
+ lines.push(applyStyle(` ${chalk.blue('ℹ')} Info: ${chalk.blue(summary.infoCount)}`));
108
+ }
109
+ if (summary.hintCount > 0) {
110
+ lines.push(applyStyle(` ${chalk.gray('💡')} Hints: ${chalk.gray(summary.hintCount)}`));
111
+ }
112
+ }
113
+ lines.push('');
114
+ lines.push(applyStyle(`Ruleset: ${formatRulesetDisplay(result.rulesetName, result.rulesetVersion, result.ruleOverrides)}`));
115
+ lines.push(applyStyle(`Document: ${result.documentId}`));
116
+ lines.push(applyStyle(`Job ID: ${result.jobId}`));
117
+ if (result.summary.cacheHit) {
118
+ lines.push(chalk.dim('(cached result)'));
119
+ }
120
+ lines.push('');
121
+ return ensureReset(lines.join('\n'));
122
+ }
123
+ /**
124
+ * Format lint issues as a table
125
+ */
126
+ export function formatIssuesTable(issues, limit) {
127
+ if (issues.length === 0) {
128
+ return chalk.green('✓ No issues found!\n');
129
+ }
130
+ const displayIssues = limit ? issues.slice(0, limit) : issues;
131
+ const table = new Table({
132
+ head: [
133
+ chalk.bold('Severity'),
134
+ chalk.bold('Rule'),
135
+ chalk.bold('Location'),
136
+ chalk.bold('Message'),
137
+ ],
138
+ colWidths: [12, 35, 20, 50],
139
+ wordWrap: true,
140
+ style: {
141
+ head: [],
142
+ border: [],
143
+ },
144
+ });
145
+ for (const issue of displayIssues) {
146
+ const location = issue.path
147
+ ? `${issue.path.join('.')}${issue.range ? `:${issue.range.start.line}` : ''}`
148
+ : issue.range
149
+ ? `line ${issue.range.start.line}`
150
+ : '-';
151
+ table.push([
152
+ getSeverityIcon(issue.severity),
153
+ chalk.cyan(issue.code || issue.ruleId),
154
+ chalk.gray(location),
155
+ issue.message,
156
+ ]);
157
+ }
158
+ let output = table.toString() + '\n';
159
+ if (limit && issues.length > limit) {
160
+ output += chalk.dim(`\n... and ${issues.length - limit} more issues\n`);
161
+ output += chalk.dim(`Use 'spectify results <jobId>' to see all issues\n`);
162
+ }
163
+ return ensureReset(output);
164
+ }
165
+ /**
166
+ * Format issues grouped by rule (summary view for drill-down)
167
+ */
168
+ export function formatRuleSummaryTable(issues) {
169
+ if (issues.length === 0) {
170
+ return chalk.green('✓ No issues found!\n');
171
+ }
172
+ // Group issues by rule
173
+ const ruleGroups = new Map();
174
+ for (const issue of issues) {
175
+ const ruleCode = issue.code || issue.ruleId;
176
+ if (!ruleGroups.has(ruleCode)) {
177
+ ruleGroups.set(ruleCode, {
178
+ code: ruleCode,
179
+ errors: 0,
180
+ warnings: 0,
181
+ info: 0,
182
+ hints: 0,
183
+ total: 0,
184
+ message: issue.message, // Save first message as example
185
+ });
186
+ }
187
+ const group = ruleGroups.get(ruleCode);
188
+ group.total++;
189
+ switch (issue.severity) {
190
+ case 0:
191
+ group.errors++;
192
+ break;
193
+ case 1:
194
+ group.warnings++;
195
+ break;
196
+ case 2:
197
+ group.info++;
198
+ break;
199
+ case 3:
200
+ group.hints++;
201
+ break;
202
+ }
203
+ }
204
+ // Sort by total count (descending), then by severity
205
+ const sortedRules = Array.from(ruleGroups.values()).sort((a, b) => {
206
+ if (a.errors !== b.errors)
207
+ return b.errors - a.errors;
208
+ if (a.warnings !== b.warnings)
209
+ return b.warnings - a.warnings;
210
+ if (a.total !== b.total)
211
+ return b.total - a.total;
212
+ return a.code.localeCompare(b.code);
213
+ });
214
+ const table = new Table({
215
+ head: [
216
+ chalk.bold('Rule'),
217
+ chalk.bold('Total'),
218
+ chalk.bold('Errors'),
219
+ chalk.bold('Warnings'),
220
+ chalk.bold('Info'),
221
+ chalk.bold('Hints'),
222
+ chalk.bold('Example Message'),
223
+ ],
224
+ colWidths: [35, 8, 10, 12, 8, 8, 50],
225
+ wordWrap: true,
226
+ style: {
227
+ head: [],
228
+ border: [],
229
+ },
230
+ });
231
+ for (const rule of sortedRules) {
232
+ const errorText = rule.errors > 0 ? chalk.red(rule.errors) : chalk.dim('0');
233
+ const warningText = rule.warnings > 0 ? chalk.yellow(rule.warnings) : chalk.dim('0');
234
+ const infoText = rule.info > 0 ? chalk.blue(rule.info) : chalk.dim('0');
235
+ const hintText = rule.hints > 0 ? chalk.gray(rule.hints) : chalk.dim('0');
236
+ table.push([
237
+ chalk.cyan(rule.code),
238
+ rule.total.toString(),
239
+ errorText,
240
+ warningText,
241
+ infoText,
242
+ hintText,
243
+ chalk.gray(rule.message.substring(0, 80) + (rule.message.length > 80 ? '...' : '')),
244
+ ]);
245
+ }
246
+ return ensureReset(table.toString() + '\n');
247
+ }
248
+ /**
249
+ * Format issues for a specific rule (detail view without repetition)
250
+ */
251
+ export function formatRuleDetailView(issues) {
252
+ if (issues.length === 0) {
253
+ return chalk.green('✓ No issues found!\n');
254
+ }
255
+ // Get rule information from first issue (all should be the same)
256
+ const firstIssue = issues[0];
257
+ const severity = getSeverityIcon(firstIssue.severity);
258
+ const severityText = formatSeverity(firstIssue.severity);
259
+ const ruleCode = firstIssue.code || firstIssue.ruleId;
260
+ // Show rule header in clear white text
261
+ const lines = [];
262
+ lines.push(chalk.bold('Rule Details:'));
263
+ lines.push(` ${severity} ${severityText.toUpperCase()} - ${chalk.cyan(ruleCode)}`);
264
+ // Wrap message text at 80 characters
265
+ const message = firstIssue.message;
266
+ const wrappedMessage = wrapText(message, 80, ' ');
267
+ lines.push(wrappedMessage);
268
+ lines.push('');
269
+ console.log(lines.join('\n'));
270
+ // Create table with Line # first, then Path (wider, no Suggestion column)
271
+ const table = new Table({
272
+ head: [
273
+ chalk.bold('Line #'),
274
+ chalk.bold('Path'),
275
+ ],
276
+ colWidths: [10, 110],
277
+ wordWrap: true,
278
+ style: {
279
+ head: [],
280
+ border: [],
281
+ },
282
+ });
283
+ for (const issue of issues) {
284
+ const lineStr = issue.range
285
+ ? issue.range.start.line.toString()
286
+ : '-';
287
+ // Format path - join with dots for better wrapping
288
+ const pathStr = issue.path && issue.path.length > 0
289
+ ? issue.path.join('.')
290
+ : '-';
291
+ table.push([
292
+ chalk.yellow(lineStr),
293
+ pathStr,
294
+ ]);
295
+ }
296
+ return ensureReset(table.toString() + '\n');
297
+ }
298
+ /**
299
+ * Wrap text at specified width with prefix
300
+ */
301
+ function wrapText(text, width, prefix = '') {
302
+ const words = text.split(' ');
303
+ const lines = [];
304
+ let currentLine = prefix;
305
+ for (const word of words) {
306
+ const testLine = currentLine === prefix ? currentLine + word : currentLine + ' ' + word;
307
+ if (testLine.length > width && currentLine !== prefix) {
308
+ lines.push(currentLine);
309
+ currentLine = prefix + word;
310
+ }
311
+ else {
312
+ currentLine = testLine;
313
+ }
314
+ }
315
+ if (currentLine !== prefix) {
316
+ lines.push(currentLine);
317
+ }
318
+ return lines.join('\n');
319
+ }
320
+ /**
321
+ * Format history entries as a table
322
+ */
323
+ export function formatHistoryTable(entries) {
324
+ if (entries.length === 0) {
325
+ return chalk.dim('No history entries found.\n');
326
+ }
327
+ const table = new Table({
328
+ head: [
329
+ chalk.bold('Date'),
330
+ chalk.bold('JobId / OpenAPI document'),
331
+ chalk.bold('Ruleset'),
332
+ chalk.bold('Issues'),
333
+ ],
334
+ colWidths: [20, 45, 20, 20],
335
+ wordWrap: true,
336
+ });
337
+ for (const entry of entries) {
338
+ const date = new Date(entry.timestamp).toLocaleString();
339
+ const fileName = entry.filePath.split('/').pop() || entry.filePath;
340
+ const issuesSummary = entry.summary.totalIssues === 0
341
+ ? chalk.green('✓ No issues')
342
+ : `${chalk.red(`${entry.summary.errorCount}E`)} ${chalk.yellow(`${entry.summary.warningCount}W`)}`;
343
+ // Format JobId / OpenAPI document on two lines
344
+ const jobIdAndFile = `${chalk.dim(entry.jobId)}\n${fileName}`;
345
+ table.push([
346
+ chalk.dim(date),
347
+ jobIdAndFile,
348
+ `${entry.rulesetName} v${entry.rulesetVersion}`,
349
+ issuesSummary,
350
+ ]);
351
+ }
352
+ return ensureReset(table.toString() + '\n');
353
+ }
354
+ /**
355
+ * Format rulesets as a table
356
+ */
357
+ export function formatRulesetsTable(rulesets) {
358
+ if (rulesets.length === 0) {
359
+ return chalk.dim('No rulesets available.\n');
360
+ }
361
+ const table = new Table({
362
+ head: [
363
+ chalk.bold('Name'),
364
+ chalk.bold('Versions'),
365
+ chalk.bold('Rules'),
366
+ chalk.bold('Description'),
367
+ ],
368
+ colWidths: [18, 22, 8, 58],
369
+ wordWrap: true,
370
+ });
371
+ for (const ruleset of rulesets) {
372
+ // Format description with display name on first line
373
+ const description = ruleset.displayName
374
+ ? `${chalk.bold(ruleset.displayName)}\n${ruleset.description || ''}`
375
+ : ruleset.description || '-';
376
+ // List all versions, default first
377
+ const allVersions = ruleset.availableVersions && ruleset.availableVersions.length > 0
378
+ ? ruleset.availableVersions
379
+ : [ruleset.version];
380
+ const defaultVer = ruleset.defaultVersion || ruleset.version;
381
+ const sorted = [
382
+ ...allVersions.filter(v => v === defaultVer),
383
+ ...allVersions.filter(v => v !== defaultVer),
384
+ ];
385
+ const versionsStr = sorted
386
+ .map(v => v === defaultVer ? chalk.green(`${v} (default)`) : chalk.dim(v))
387
+ .join('\n');
388
+ table.push([
389
+ chalk.cyan(ruleset.name),
390
+ versionsStr,
391
+ String(ruleset.ruleCount),
392
+ description,
393
+ ]);
394
+ }
395
+ return ensureReset(table.toString() + '\n');
396
+ }
397
+ /**
398
+ * Format job status
399
+ */
400
+ export function formatJobStatus(status) {
401
+ const lines = [];
402
+ lines.push('');
403
+ lines.push(`Job ID: ${status.jobId}`);
404
+ switch (status.status) {
405
+ case 'queued':
406
+ lines.push(`Status: ${chalk.yellow('⏳ Queued')}`);
407
+ break;
408
+ case 'running':
409
+ lines.push(`Status: ${chalk.blue('▶ Running')}`);
410
+ if (status.progress) {
411
+ const percent = Math.round((status.progress.completed / status.progress.total) * 100);
412
+ lines.push(`Progress: ${status.progress.completed}/${status.progress.total} (${percent}%)`);
413
+ if (status.progress.currentRule) {
414
+ lines.push(`Current: ${status.progress.currentRule}`);
415
+ }
416
+ }
417
+ break;
418
+ case 'completed':
419
+ lines.push(`Status: ${chalk.green('✓ Completed')}`);
420
+ break;
421
+ case 'failed':
422
+ lines.push(`Status: ${chalk.red('✖ Failed')}`);
423
+ break;
424
+ }
425
+ if (status.startedAt) {
426
+ lines.push(`Started: ${new Date(status.startedAt).toLocaleString()}`);
427
+ }
428
+ if (status.completedAt) {
429
+ lines.push(`Completed: ${new Date(status.completedAt).toLocaleString()}`);
430
+ }
431
+ lines.push('');
432
+ return ensureReset(lines.join('\n'));
433
+ }
434
+ /**
435
+ * Format health status
436
+ */
437
+ export function formatHealth(health) {
438
+ const lines = [];
439
+ lines.push('');
440
+ lines.push(chalk.bold('Linting Orchestrator — Health Status'));
441
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
442
+ const statusIcon = health.status === 'healthy' || health.status === 'ok'
443
+ ? chalk.green('✓')
444
+ : health.status === 'degraded'
445
+ ? chalk.yellow('⚠')
446
+ : chalk.red('✖');
447
+ lines.push(`Status: ${statusIcon} ${health.status}`);
448
+ lines.push(`Version: ${health.version}`);
449
+ // Show server metadata
450
+ if (health.server) {
451
+ lines.push('');
452
+ lines.push(chalk.bold('Server Information'));
453
+ if (health.mode) {
454
+ lines.push(`Mode: ${health.mode}`);
455
+ }
456
+ lines.push(`Port: ${health.server.port}`);
457
+ lines.push(`Host: ${health.server.host}`);
458
+ if (health.server.startedAt) {
459
+ const started = new Date(health.server.startedAt);
460
+ lines.push(`Started: ${started.toLocaleString()}`);
461
+ }
462
+ }
463
+ // Show document store configuration
464
+ if (health.documentStore) {
465
+ lines.push('');
466
+ lines.push(chalk.bold('Document Store'));
467
+ lines.push(`Type: ${health.documentStore.type}`);
468
+ if (health.documentStore.fullPath) {
469
+ lines.push(`Location: ${health.documentStore.fullPath}`);
470
+ }
471
+ else if (health.documentStore.baseDir) {
472
+ lines.push(`Location: ${health.documentStore.baseDir}`);
473
+ }
474
+ }
475
+ // Show runtime information (Spectral versions, resolver)
476
+ if (health.runtime) {
477
+ const rt = health.runtime;
478
+ lines.push('');
479
+ lines.push(chalk.bold('Runtime'));
480
+ if (rt.nodeVersion)
481
+ lines.push(`Node: ${rt.nodeVersion}`);
482
+ if (rt.spectralCore)
483
+ lines.push(`Spectral: core ${rt.spectralCore}`);
484
+ if (rt.spectralRulesets)
485
+ lines.push(` rulesets ${rt.spectralRulesets}`);
486
+ if (rt.spectralCli)
487
+ lines.push(` cli ${rt.spectralCli}`);
488
+ lines.push(`Resolver: ${rt.resolver ? rt.resolver : chalk.dim('none (Spectral default)')}`);
489
+ }
490
+ // Show Report Service integration status (always show, even if not configured)
491
+ lines.push('');
492
+ lines.push(chalk.bold('Report Service Integration'));
493
+ if (health.reportService) {
494
+ const rs = health.reportService;
495
+ // Status indicator based on actual connection state
496
+ let statusIcon;
497
+ if (rs.status === 'connected') {
498
+ statusIcon = chalk.green('✓ Connected');
499
+ }
500
+ else if (rs.status === 'degraded') {
501
+ statusIcon = chalk.yellow('⚠ Degraded');
502
+ if (rs.pendingNotifications > 0) {
503
+ statusIcon += chalk.dim(` (service unreachable, ${rs.pendingNotifications} pending)`);
504
+ }
505
+ }
506
+ else if (rs.status === 'unreachable') {
507
+ statusIcon = chalk.red('✖ Unreachable');
508
+ }
509
+ else if (rs.status === 'error') {
510
+ statusIcon = chalk.red('✖ Error');
511
+ }
512
+ else if (rs.enabled) {
513
+ statusIcon = chalk.green('✓ Enabled');
514
+ }
515
+ else {
516
+ statusIcon = chalk.red('✖ Disabled');
517
+ }
518
+ lines.push(`Status: ${statusIcon}`);
519
+ if (rs.serviceUrl) {
520
+ lines.push(`URL: ${rs.serviceUrl}`);
521
+ }
522
+ // Show message for degraded/error states
523
+ if (rs.message) {
524
+ lines.push(` ${chalk.dim(rs.message)}`);
525
+ }
526
+ if (rs.pendingNotifications !== undefined && rs.pendingNotifications !== 'N/A') {
527
+ const pendingColor = rs.pendingNotifications > 0 ? chalk.yellow : chalk.dim;
528
+ lines.push(`Pending: ${pendingColor(rs.pendingNotifications.toString())}`);
529
+ }
530
+ if (rs.retryJobRunning !== undefined) {
531
+ const retryIcon = rs.retryJobRunning ? chalk.green('✓ Running') : chalk.dim('○ Stopped');
532
+ lines.push(`Retry Job: ${retryIcon}`);
533
+ }
534
+ if (rs.lastRetryRun) {
535
+ const lastRun = new Date(rs.lastRetryRun);
536
+ lines.push(`Last Run: ${lastRun.toLocaleString()}`);
537
+ }
538
+ // Show next retry time when there are pending notifications
539
+ if (rs.nextRetryAt && rs.pendingNotifications > 0) {
540
+ const nextRetry = new Date(rs.nextRetryAt);
541
+ const now = new Date();
542
+ const diffMs = nextRetry.getTime() - now.getTime();
543
+ if (diffMs > 0) {
544
+ const diffSecs = Math.ceil(diffMs / 1000);
545
+ const minutes = Math.floor(diffSecs / 60);
546
+ const seconds = diffSecs % 60;
547
+ const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
548
+ lines.push(`Next Retry: ${chalk.cyan(`in ${timeStr}`)}`);
549
+ }
550
+ else {
551
+ lines.push(`Next Retry: ${chalk.cyan('imminent')}`);
552
+ }
553
+ }
554
+ else if (rs.retryJobRunning && rs.retryJobInterval && rs.pendingNotifications > 0) {
555
+ // Fallback: show retry interval if no specific next time
556
+ const intervalMinutes = Math.round(rs.retryJobInterval / 60000);
557
+ lines.push(`Retry Interval: ${chalk.dim(`every ${intervalMinutes}m`)}`);
558
+ }
559
+ if (rs.error) {
560
+ lines.push(`Error: ${chalk.red(rs.error)}`);
561
+ }
562
+ }
563
+ else {
564
+ lines.push(`Status: ${chalk.dim('○ Not configured')}`);
565
+ lines.push(chalk.dim(`To enable: Set SPECTIFYD_REPORTS_ENABLED=true in .env`));
566
+ }
567
+ // Show capacity and worker stats
568
+ if (health.stats) {
569
+ const stats = health.stats;
570
+ if (stats.capacity) {
571
+ lines.push('');
572
+ lines.push(chalk.bold('Capacity'));
573
+ const pct = stats.capacity.utilizationPercent || 0;
574
+ const capacityColor = pct >= 90 ? chalk.red : pct >= 70 ? chalk.yellow : chalk.green;
575
+ lines.push(`Active: ${capacityColor(`${stats.capacity.activeJobs}/${stats.capacity.maxConcurrentJobs}`)} jobs (${capacityColor(`${pct}%`)})`);
576
+ }
577
+ if (stats.jobs) {
578
+ lines.push(`Queued: ${stats.jobs.queued} Running: ${stats.jobs.running} Completed: ${stats.jobs.completed} Failed: ${stats.jobs.failed}`);
579
+ }
580
+ if (stats.workers) {
581
+ lines.push('');
582
+ lines.push(chalk.bold('Workers'));
583
+ lines.push(`Total: ${stats.workers.total} Active: ${stats.workers.active} Idle: ${stats.workers.idle}`);
584
+ }
585
+ if (stats.cache) {
586
+ const hitRate = stats.cache.hitRate !== undefined
587
+ ? `${(stats.cache.hitRate * 100).toFixed(0)}%`
588
+ : 'N/A';
589
+ lines.push('');
590
+ lines.push(chalk.bold('Cache'));
591
+ lines.push(`Hit Rate: ${hitRate} (${stats.cache.hits} hits / ${stats.cache.misses} misses)`);
592
+ }
593
+ }
594
+ if (health.uptime !== undefined) {
595
+ const uptimeSeconds = health.uptime;
596
+ const hours = Math.floor(uptimeSeconds / 3600);
597
+ const minutes = Math.floor((uptimeSeconds % 3600) / 60);
598
+ const seconds = uptimeSeconds % 60;
599
+ let uptimeStr = '';
600
+ if (hours > 0)
601
+ uptimeStr += `${hours}h `;
602
+ if (minutes > 0)
603
+ uptimeStr += `${minutes}m `;
604
+ uptimeStr += `${seconds}s`;
605
+ lines.push('');
606
+ lines.push(`Uptime: ${uptimeStr}`);
607
+ }
608
+ lines.push('');
609
+ return ensureReset(lines.join('\n'));
610
+ }
611
+ /**
612
+ * Format jobs list as a table
613
+ */
614
+ export function formatJobsTable(jobs, detailed = false) {
615
+ if (jobs.length === 0) {
616
+ return chalk.dim('No jobs found.\n');
617
+ }
618
+ const table = new Table({
619
+ head: detailed
620
+ ? [
621
+ chalk.bold('Date'),
622
+ chalk.bold('JobId'),
623
+ chalk.bold('Document'),
624
+ chalk.bold('Ruleset'),
625
+ chalk.bold('Status'),
626
+ chalk.bold('Issues'),
627
+ ]
628
+ : [
629
+ chalk.bold('Date'),
630
+ chalk.bold('JobId'),
631
+ chalk.bold('DocumentId'),
632
+ chalk.bold('Ruleset'),
633
+ chalk.bold('Status'),
634
+ chalk.bold('Issues'),
635
+ ],
636
+ colWidths: detailed ? [18, 15, 30, 18, 12, 15] : [18, 15, 15, 18, 12, 15],
637
+ wordWrap: true,
638
+ });
639
+ for (const job of jobs) {
640
+ const date = new Date(job.timestamp).toLocaleString('en-US', {
641
+ month: 'short',
642
+ day: 'numeric',
643
+ hour: '2-digit',
644
+ minute: '2-digit'
645
+ });
646
+ // Format status with color
647
+ let statusDisplay = job.status;
648
+ if (job.status === 'completed') {
649
+ statusDisplay = chalk.green('✓ done');
650
+ }
651
+ else if (job.status === 'completed_with_errors') {
652
+ statusDisplay = chalk.yellow('⚠ done');
653
+ }
654
+ else if (job.status === 'failed') {
655
+ statusDisplay = chalk.red('✗ failed');
656
+ }
657
+ else if (job.status === 'timeout') {
658
+ statusDisplay = chalk.red('⏱ timeout');
659
+ }
660
+ else if (job.status === 'running') {
661
+ statusDisplay = chalk.blue('⟳ running');
662
+ }
663
+ // Format issues summary
664
+ let issuesSummary = '';
665
+ if (job.summary && job.summary.totalIssues === 0) {
666
+ issuesSummary = chalk.green('✓ 0');
667
+ }
668
+ else if (job.summary) {
669
+ const parts = [];
670
+ if (job.summary.errorCount > 0)
671
+ parts.push(chalk.red(`${job.summary.errorCount}E`));
672
+ if (job.summary.warningCount > 0)
673
+ parts.push(chalk.yellow(`${job.summary.warningCount}W`));
674
+ if (job.summary.infoCount > 0)
675
+ parts.push(chalk.cyan(`${job.summary.infoCount}I`));
676
+ issuesSummary = parts.join(' ');
677
+ }
678
+ else {
679
+ issuesSummary = chalk.dim('n/a');
680
+ }
681
+ // Short job ID (first 12 chars)
682
+ const shortJobId = chalk.dim(job.jobId.slice(0, 12));
683
+ // Ruleset version
684
+ const rulesetDisplay = `${job.rulesetName}\n${chalk.dim('v' + job.rulesetVersion)}`;
685
+ if (detailed && job.document) {
686
+ // Detailed mode - show document name
687
+ const docName = job.document.name || 'Unknown';
688
+ const docInfo = job.document.version
689
+ ? `${docName}\n${chalk.dim('v' + job.document.version)}`
690
+ : docName;
691
+ table.push([
692
+ chalk.dim(date),
693
+ shortJobId,
694
+ docInfo,
695
+ rulesetDisplay,
696
+ statusDisplay,
697
+ issuesSummary,
698
+ ]);
699
+ }
700
+ else {
701
+ // Lightweight mode - show document ID
702
+ const shortDocId = chalk.dim(job.documentId.slice(0, 12));
703
+ table.push([
704
+ chalk.dim(date),
705
+ shortJobId,
706
+ shortDocId,
707
+ rulesetDisplay,
708
+ statusDisplay,
709
+ issuesSummary,
710
+ ]);
711
+ }
712
+ }
713
+ return ensureReset(table.toString() + '\n');
714
+ }
715
+ //# sourceMappingURL=formatters.js.map