spec-up-t-healthcheck 1.0.0 → 1.1.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.
@@ -0,0 +1,505 @@
1
+ /**
2
+ * @fileoverview Shared result details formatter for health check results
3
+ *
4
+ * This module provides functions to format health check result details into HTML.
5
+ * It works in both Node.js and browser environments, enabling consistent display
6
+ * of health check results in CLI tools, HTML reports, and web applications.
7
+ *
8
+ * The module formats:
9
+ * - Errors array with red styling
10
+ * - Warnings array with yellow/orange styling
11
+ * - Success messages array with green styling
12
+ * - Additional metadata (missingFields, count, packageData)
13
+ *
14
+ * URLs in messages are automatically converted to clickable links.
15
+ *
16
+ * @author spec-up-t-healthcheck
17
+ */
18
+
19
+ /**
20
+ * Escapes HTML special characters to prevent XSS attacks.
21
+ * This is critical for security when displaying user-generated content.
22
+ *
23
+ * @param {string|any} text - Text to escape
24
+ * @returns {string} HTML-escaped text
25
+ *
26
+ * @example
27
+ * ```javascript
28
+ * escapeHtml('<script>alert("xss")</script>');
29
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
30
+ * ```
31
+ */
32
+ export function escapeHtml(text) {
33
+ if (typeof text !== 'string') {
34
+ return String(text);
35
+ }
36
+
37
+ const map = {
38
+ '&': '&amp;',
39
+ '<': '&lt;',
40
+ '>': '&gt;',
41
+ '"': '&quot;',
42
+ "'": '&#039;'
43
+ };
44
+
45
+ return text.replace(/[&<>"']/g, (m) => map[m]);
46
+ }
47
+
48
+ /**
49
+ * Converts URLs in text to clickable links that open in a new tab.
50
+ * The text is first escaped for HTML safety, then URLs are converted to links.
51
+ *
52
+ * This function enhances user experience by making URLs in error messages,
53
+ * warnings, and other feedback immediately actionable.
54
+ *
55
+ * @param {string} text - Text potentially containing URLs
56
+ * @returns {string} HTML string with clickable links
57
+ *
58
+ * @example
59
+ * ```javascript
60
+ * linkifyUrls('Check https://example.com for details');
61
+ * // Returns: 'Check <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for details'
62
+ * ```
63
+ */
64
+ export function linkifyUrls(text) {
65
+ if (typeof text !== 'string') {
66
+ return escapeHtml(String(text));
67
+ }
68
+
69
+ // First escape the text for HTML safety
70
+ const escaped = escapeHtml(text);
71
+
72
+ // URL regex pattern that matches http://, https://, and www. URLs
73
+ // Excludes common trailing punctuation like ), >, ", and whitespace
74
+ const urlPattern = /(https?:\/\/[^\s<>"()]+|www\.[^\s<>"()]+)/g;
75
+
76
+ // Replace URLs with clickable links
77
+ return escaped.replace(urlPattern, (url) => {
78
+ // Ensure the URL has a protocol for the href attribute
79
+ const href = url.startsWith('http') ? url : `https://${url}`;
80
+
81
+ // Create an anchor tag that opens in a new tab with security attributes
82
+ return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-decoration-underline">${url}</a>`;
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Formats result details into HTML.
88
+ *
89
+ * This is the main formatter function that converts a health check result's
90
+ * details object into a formatted HTML string. It handles:
91
+ * - Errors: Displayed in red with bullet points
92
+ * - Warnings: Displayed in yellow/orange with bullet points
93
+ * - Success: Displayed in green with bullet points (can be hidden for brevity)
94
+ * - Info: Displayed in muted gray with bullet points (contextual information)
95
+ * - Metadata: Missing fields, counts, package data
96
+ * - Console Messages: Special table format for console output (timestamp, type, message, operation, additionalData)
97
+ *
98
+ * All URLs in messages are automatically converted to clickable links.
99
+ *
100
+ * @param {Object} details - The details object from a health check result
101
+ * @param {string[]} [details.errors] - Array of error messages
102
+ * @param {string[]} [details.warnings] - Array of warning messages
103
+ * @param {string[]} [details.success] - Array of success messages
104
+ * @param {string[]} [details.info] - Array of informational messages
105
+ * @param {string[]} [details.missingFields] - Array of missing field names
106
+ * @param {number} [details.count] - Count of items found
107
+ * @param {Object} [details.packageData] - Package metadata
108
+ * @param {string} [details.packageData.name] - Package name
109
+ * @param {string} [details.packageData.version] - Package version
110
+ * @param {Object} [details.analysis] - Console message analysis data
111
+ * @param {Array} [details.allMessages] - All console messages for table display
112
+ * @returns {string} Formatted HTML string
113
+ *
114
+ * @example
115
+ * ```javascript
116
+ * const details = {
117
+ * errors: ['Field "title" is missing'],
118
+ * warnings: ['Field "favicon" is recommended'],
119
+ * success: ['Field "author" exists'],
120
+ * info: ['URL accessibility checks skipped (browser environment)']
121
+ * };
122
+ * const html = formatResultDetails(details);
123
+ * // Returns formatted HTML with error, warning, success, and info lists
124
+ * ```
125
+ */
126
+ export function formatResultDetails(details) {
127
+ let html = '';
128
+
129
+ // Special handling for console-messages check with errors/warnings
130
+ // Show compact error/warning list first, then full messages table
131
+ if (details.analysis && details.allMessages) {
132
+ // Show errors first (compact format for quick scanning)
133
+ if (details.errors && details.errors.length > 0) {
134
+ const isConsoleMessageFormat = typeof details.errors[0] === 'object' && details.errors[0].timestamp;
135
+ if (isConsoleMessageFormat) {
136
+ html += formatConsoleMessageList('Errors', details.errors, 'danger');
137
+ if (details.errorsNote) {
138
+ html += `<div class="mt-1"><small class="text-muted">${escapeHtml(details.errorsNote)}</small></div>`;
139
+ }
140
+ }
141
+ }
142
+
143
+ // Show warnings (compact format)
144
+ if (details.warnings && details.warnings.length > 0) {
145
+ const isConsoleMessageFormat = typeof details.warnings[0] === 'object' && details.warnings[0].timestamp;
146
+ if (isConsoleMessageFormat) {
147
+ html += formatConsoleMessageList('Warnings', details.warnings, 'warning');
148
+ if (details.warningsNote) {
149
+ html += `<div class="mt-1"><small class="text-muted">${escapeHtml(details.warningsNote)}</small></div>`;
150
+ }
151
+ }
152
+ }
153
+
154
+ // Add the full messages table (with all message types)
155
+ html += formatConsoleMessagesTable(details);
156
+ return html;
157
+ }
158
+
159
+ // Display errors array with clickable URLs
160
+ // Errors are shown with strong red styling to draw immediate attention
161
+ if (details.errors && details.errors.length > 0) {
162
+ // Check if errors are objects (from console-messages) or strings (from other checks)
163
+ const isConsoleMessageFormat = typeof details.errors[0] === 'object' && details.errors[0].timestamp;
164
+
165
+ if (isConsoleMessageFormat) {
166
+ html += formatConsoleMessageList('Errors', details.errors, 'danger');
167
+ if (details.errorsNote) {
168
+ html += `<small class="text-muted">${escapeHtml(details.errorsNote)}</small>`;
169
+ }
170
+ } else {
171
+ html += `<div class="mt-2 detail-errors"><strong class="text-danger">Errors:</strong><ul class="mb-0 mt-1">`;
172
+ details.errors.forEach(error => {
173
+ html += `<li class="text-danger">${linkifyUrls(error)}</li>`;
174
+ });
175
+ html += `</ul></div>`;
176
+ }
177
+ }
178
+
179
+ // Display warnings array with clickable URLs
180
+ // Warnings indicate potential issues that should be addressed but aren't critical
181
+ if (details.warnings && details.warnings.length > 0) {
182
+ // Check if warnings are objects (from console-messages) or strings (from other checks)
183
+ const isConsoleMessageFormat = typeof details.warnings[0] === 'object' && details.warnings[0].timestamp;
184
+
185
+ if (isConsoleMessageFormat) {
186
+ html += formatConsoleMessageList('Warnings', details.warnings, 'warning');
187
+ if (details.warningsNote) {
188
+ html += `<small class="text-muted">${escapeHtml(details.warningsNote)}</small>`;
189
+ }
190
+ } else {
191
+ html += `<div class="mt-2 detail-warnings"><strong class="text-warning">Warnings:</strong><ul class="mb-0 mt-1">`;
192
+ details.warnings.forEach(warning => {
193
+ html += `<li class="text-warning">${linkifyUrls(warning)}</li>`;
194
+ });
195
+ html += `</ul></div>`;
196
+ }
197
+ }
198
+
199
+ // Display success messages array with clickable URLs
200
+ // Add detail-success class so these can be hidden when "Show passing checks" is disabled
201
+ // This helps users focus on issues while still providing complete information when needed
202
+ if (details.success && details.success.length > 0) {
203
+ html += `<div class="mt-2 detail-success"><strong class="text-success">Success:</strong><ul class="mb-0 mt-1">`;
204
+ details.success.forEach(success => {
205
+ html += `<li class="text-success">${linkifyUrls(success)}</li>`;
206
+ });
207
+ html += `</ul></div>`;
208
+ }
209
+
210
+ // Display informational messages array with clickable URLs
211
+ // Info messages provide additional context without indicating success or failure
212
+ if (details.info && details.info.length > 0) {
213
+ html += `<div class="mt-2 detail-info"><strong class="text-info">Info:</strong><ul class="mb-0 mt-1">`;
214
+ details.info.forEach(info => {
215
+ html += `<li class="text-muted">${linkifyUrls(info)}</li>`;
216
+ });
217
+ html += `</ul></div>`;
218
+ }
219
+
220
+ // Display missing fields (existing functionality for backward compatibility)
221
+ // Provides a quick summary of what's missing in validation results
222
+ if (details.missingFields && details.missingFields.length > 0) {
223
+ html += `<br><small class="text-muted">Missing fields: ${details.missingFields.map(escapeHtml).join(', ')}</small>`;
224
+ }
225
+
226
+ // Display count (existing functionality)
227
+ // Shows the number of items found in checks that count resources
228
+ if (details.count !== undefined) {
229
+ html += `<br><small class="text-muted">Files found: ${details.count}</small>`;
230
+ }
231
+
232
+ // Display package data (existing functionality)
233
+ // Shows package name and version for package.json validation
234
+ if (details.packageData) {
235
+ html += `<br><small class="text-muted">Package: ${escapeHtml(details.packageData.name)}@${escapeHtml(details.packageData.version)}</small>`;
236
+ }
237
+
238
+ return html;
239
+ }
240
+
241
+ /**
242
+ * Formats console messages in a compact list format.
243
+ * This is used when showing errors/warnings from console-messages check.
244
+ *
245
+ * @param {string} title - Title for the list (e.g., "Errors", "Warnings")
246
+ * @param {Array<Object>} messages - Array of console message objects
247
+ * @param {string} colorClass - Bootstrap color class ('danger', 'warning', etc.)
248
+ * @returns {string} HTML string with formatted list
249
+ */
250
+ export function formatConsoleMessageList(title, messages, colorClass) {
251
+ let html = `<div class="mt-2"><strong class="text-${colorClass}">${escapeHtml(title)}:</strong>`;
252
+ html += `<div class="table-responsive mt-2"><table class="table-sm" style="width: 100%; background-color: transparent;">`;
253
+ html += `<thead>`;
254
+ html += `<tr>`;
255
+ // html += `<th style="width: 150px; background-color: transparent">Timestamp</th>`;
256
+ html += `<th class="d-none" style="background-color: transparent">Message</th>`;
257
+ html += `</tr>`;
258
+ html += `</thead>`;
259
+ html += `<tbody>`;
260
+
261
+ messages.forEach(msg => {
262
+ const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString() : 'N/A';
263
+ const message = msg.message || '';
264
+ const additionalData = msg.additionalData ? ` [${Array.isArray(msg.additionalData) ? msg.additionalData.join(', ') : msg.additionalData}]` : '';
265
+
266
+ html += `<tr>`;
267
+ // html += `<td><small>${escapeHtml(timestamp)}</small></td>`;
268
+ html += `<td><small>${linkifyUrls(message + additionalData)}</small></td>`;
269
+ html += `</tr>`;
270
+ });
271
+
272
+ html += `</tbody></table></div></div>`;
273
+ return html;
274
+ }
275
+
276
+ /**
277
+ * Formats all console messages in a detailed table format.
278
+ * This is used when the check includes allMessages for verbose display.
279
+ *
280
+ * @param {Object} details - Details object containing console message data
281
+ * @returns {string} HTML string with formatted table
282
+ */
283
+ export function formatConsoleMessagesTable(details) {
284
+ const { analysis, allMessages, metadata } = details;
285
+
286
+ let html = '<div class="mt-3">';
287
+
288
+ // // Add a clear heading for the full messages table
289
+ // html += `<div class="mb-3">`;
290
+ // html += `<strong class="text-primary">All Console Messages (${allMessages.length}):</strong><br>`;
291
+ // html += `<small class="text-muted">Complete log of all operations with timestamp, type, and details</small>`;
292
+ // html += `</div>`;
293
+
294
+ // // Add summary statistics
295
+ // if (analysis) {
296
+ // html += `<div class="mb-3">`;
297
+ // html += `<strong>Summary:</strong><br>`;
298
+ // html += `<small class="text-muted">`;
299
+ // html += `Total: ${analysis.totalMessages || 0} | `;
300
+ // html += `<span class="text-success">Success: ${analysis.successCount || 0}</span> | `;
301
+ // html += `<span class="text-danger">Errors: ${analysis.errorCount || 0}</span> | `;
302
+ // html += `<span class="text-warning">Warnings: ${analysis.warningCount || 0}</span>`;
303
+ // html += `</small>`;
304
+ // html += `</div>`;
305
+ // }
306
+
307
+ // // Add operations info if available
308
+ // if (metadata && metadata.operations && metadata.operations.length > 0) {
309
+ // html += `<div class="mb-2">`;
310
+ // html += `<small class="text-muted">Operations: ${metadata.operations.map(escapeHtml).join(', ')}</small>`;
311
+ // html += `</div>`;
312
+ // }
313
+
314
+ // Create the messages table (without Operation column)
315
+ html += `<div class="table-responsive">`;
316
+ html += `<table class="table-sm table-striped" style="width: 100%;">`;
317
+ html += `<thead class="table-light">`;
318
+ html += `<tr>`;
319
+ // html += `<th style="width: 150px;">Timestamp</th>`;
320
+ // html += `<th style="width: 80px;">Type</th>`;
321
+ html += `<th class="d-none">Message</th>`;
322
+ // html += `<th style="width: 150px;">Additional Data</th>`;
323
+ html += `</tr>`;
324
+ html += `</thead>`;
325
+ html += `<tbody>`;
326
+
327
+ // Format each message as a table row
328
+ allMessages.forEach(msg => {
329
+ const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString() : 'N/A';
330
+ const type = msg.type || 'info';
331
+ const message = msg.message || '';
332
+ const additionalData = msg.additionalData
333
+ ? (Array.isArray(msg.additionalData) ? msg.additionalData.join(', ') : String(msg.additionalData))
334
+ : '';
335
+
336
+ // Apply color based on message type
337
+ let typeClass = 'text-muted';
338
+ let typeBadge = 'secondary';
339
+ switch (type) {
340
+ case 'error':
341
+ typeClass = 'text-danger';
342
+ typeBadge = 'danger';
343
+ break;
344
+ case 'warn':
345
+ typeClass = 'text-warning';
346
+ typeBadge = 'warning';
347
+ break;
348
+ case 'success':
349
+ typeClass = 'text-success';
350
+ typeBadge = 'success';
351
+ break;
352
+ case 'info':
353
+ typeClass = 'text-info';
354
+ typeBadge = 'info';
355
+ break;
356
+ case 'highlight':
357
+ typeClass = 'text-primary';
358
+ typeBadge = 'primary';
359
+ break;
360
+ }
361
+
362
+ html += `<tr>`;
363
+ // html += `<td><small>${escapeHtml(timestamp)}</small></td>`;
364
+ // html += `<td><span class="badge bg-${typeBadge}">${escapeHtml(type)}</span></td>`;
365
+ html += `<td><small>${linkifyUrls(message)}</small></td>`;
366
+ // html += `<td><small class="text-muted">${escapeHtml(additionalData)}</small></td>`;
367
+ html += `</tr>`;
368
+ });
369
+
370
+ html += `</tbody></table></div></div>`;
371
+
372
+ return html;
373
+ }
374
+
375
+ /**
376
+ * Formats a single health check result into a table row HTML string.
377
+ *
378
+ * This function is useful for building tables of health check results.
379
+ * It includes status badges, check names, messages, and formatted details.
380
+ *
381
+ * @param {Object} result - A health check result object
382
+ * @param {string} result.status - Status of the check ('pass', 'fail', 'warn', 'skip')
383
+ * @param {string} result.check - Name of the health check
384
+ * @param {string} result.message - Primary message describing the result
385
+ * @param {Object} [result.details] - Additional details to format
386
+ * @returns {string} HTML table row string
387
+ *
388
+ * @example
389
+ * ```javascript
390
+ * const result = {
391
+ * status: 'fail',
392
+ * check: 'specs.json validation',
393
+ * message: 'specs.json has 2 error(s)',
394
+ * details: {
395
+ * errors: ['Field "title" is missing', 'Field "author" is empty']
396
+ * }
397
+ * };
398
+ * const rowHtml = formatResultAsTableRow(result);
399
+ * ```
400
+ */
401
+ export function formatResultAsTableRow(result) {
402
+ const { statusClass, statusIcon, statusText } = getStatusDisplay(result);
403
+ const rowClass = getRowClass(result);
404
+
405
+ let detailsHtml = '';
406
+ if (result.details && Object.keys(result.details).length > 0) {
407
+ detailsHtml = formatResultDetails(result.details);
408
+ }
409
+
410
+ return `<tr data-status="${result.status}" class="check-row ${rowClass}">
411
+ <td class="${statusClass} status-badge">
412
+ <i class="bi ${statusIcon} status-icon"></i>
413
+ <span>${statusText}</span>
414
+ </td>
415
+ <td>${escapeHtml(result.check)}</td>
416
+ <td>
417
+ ${escapeHtml(result.message)}
418
+ ${detailsHtml}
419
+ </td>
420
+ </tr>`;
421
+ }
422
+
423
+ /**
424
+ * Gets display properties (class, icon, text) for a result status.
425
+ *
426
+ * This helper function centralizes the mapping between status values
427
+ * and their visual representation, ensuring consistency across the UI.
428
+ *
429
+ * @param {Object} result - The health check result
430
+ * @param {string} result.status - Status value ('pass', 'fail', 'warn', 'skip')
431
+ * @returns {{statusClass: string, statusIcon: string, statusText: string}}
432
+ *
433
+ * @example
434
+ * ```javascript
435
+ * const display = getStatusDisplay({ status: 'fail' });
436
+ * // Returns: {
437
+ * // statusClass: 'text-danger',
438
+ * // statusIcon: 'bi-x-circle-fill',
439
+ * // statusText: 'Fail'
440
+ * // }
441
+ * ```
442
+ */
443
+ export function getStatusDisplay(result) {
444
+ switch (result.status) {
445
+ case 'pass':
446
+ return {
447
+ statusClass: 'text-success',
448
+ statusIcon: 'bi-check-circle-fill',
449
+ statusText: 'Pass'
450
+ };
451
+ case 'fail':
452
+ return {
453
+ statusClass: 'text-danger',
454
+ statusIcon: 'bi-x-circle-fill',
455
+ statusText: 'Fail'
456
+ };
457
+ case 'warn':
458
+ return {
459
+ statusClass: 'text-warning',
460
+ statusIcon: 'bi-exclamation-triangle-fill',
461
+ statusText: 'Warning'
462
+ };
463
+ case 'skip':
464
+ return {
465
+ statusClass: 'text-muted',
466
+ statusIcon: 'bi-dash-circle',
467
+ statusText: 'Skipped'
468
+ };
469
+ default:
470
+ return {
471
+ statusClass: 'text-secondary',
472
+ statusIcon: 'bi-question-circle',
473
+ statusText: 'Unknown'
474
+ };
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Gets the appropriate CSS class for a table row based on the result status.
480
+ *
481
+ * This provides Bootstrap table row styling to visually distinguish
482
+ * different result statuses in tabular displays.
483
+ *
484
+ * @param {Object} result - The health check result
485
+ * @param {string} result.status - Status value ('pass', 'fail', 'warn')
486
+ * @returns {string} CSS class name
487
+ *
488
+ * @example
489
+ * ```javascript
490
+ * const rowClass = getRowClass({ status: 'fail' });
491
+ * // Returns: 'table-danger'
492
+ * ```
493
+ */
494
+ export function getRowClass(result) {
495
+ switch (result.status) {
496
+ case 'fail':
497
+ return 'table-danger';
498
+ case 'warn':
499
+ return 'table-warning';
500
+ case 'pass':
501
+ return 'table-success';
502
+ default:
503
+ return '';
504
+ }
505
+ }
@@ -233,6 +233,8 @@ export class HealthCheckRegistry {
233
233
  *
234
234
  * This method dynamically imports health check modules and registers them
235
235
  * with the registry. It's designed to work with the standard module structure.
236
+ *
237
+ * In browser environments, Node.js-only checks (like link-checker) are skipped.
236
238
  */
237
239
  async autoDiscover() {
238
240
  if (this.autoDiscovered) {
@@ -240,18 +242,34 @@ export class HealthCheckRegistry {
240
242
  }
241
243
 
242
244
  try {
245
+ // Detect if we're in a browser environment
246
+ const isBrowser = typeof window !== 'undefined' && typeof process === 'undefined';
247
+
243
248
  // Import the built-in health checks
244
249
  const packageJsonModule = await import('./checks/package-json.js');
245
250
  const specFilesModule = await import('./checks/spec-files.js');
246
251
  const specsJsonModule = await import('./checks/specsjson.js');
247
252
  const externalSpecsUrlsModule = await import('./checks/external-specs-urls.js');
248
253
  const gitignoreModule = await import('./checks/gitignore.js');
254
+ const specDirectoryAndFilesModule = await import('./checks/spec-directory-and-files.js');
255
+ const consoleMessagesModule = await import('./checks/console-messages.js');
256
+
257
+ // Only import link-checker in Node.js environments (not browsers)
258
+ // Link checker requires linkinator which uses Node.js streams
259
+ let linkCheckerModule = null;
260
+ if (!isBrowser) {
261
+ try {
262
+ linkCheckerModule = await import('./checks/link-checker.js');
263
+ } catch (error) {
264
+ console.warn('Link checker module could not be loaded (Node.js only):', error.message);
265
+ }
266
+ }
249
267
 
250
268
  // Register package.json check
251
269
  if (packageJsonModule.checkPackageJson && packageJsonModule.CHECK_ID) {
252
270
  this.register({
253
271
  id: packageJsonModule.CHECK_ID,
254
- name: packageJsonModule.CHECK_NAME || 'Package.json Check',
272
+ name: packageJsonModule.CHECK_NAME || 'package.json Check',
255
273
  description: packageJsonModule.CHECK_DESCRIPTION || 'Validates package.json file',
256
274
  checkFunction: packageJsonModule.checkPackageJson,
257
275
  category: 'configuration',
@@ -275,7 +293,7 @@ export class HealthCheckRegistry {
275
293
  if (specsJsonModule.checkSpecsJson && specsJsonModule.CHECK_ID) {
276
294
  this.register({
277
295
  id: specsJsonModule.CHECK_ID,
278
- name: specsJsonModule.CHECK_NAME || 'Specs.json Check',
296
+ name: specsJsonModule.CHECK_NAME || 'specs.json Check',
279
297
  description: specsJsonModule.CHECK_DESCRIPTION || 'Validates specs.json file',
280
298
  checkFunction: specsJsonModule.checkSpecsJson,
281
299
  category: 'configuration',
@@ -299,7 +317,7 @@ export class HealthCheckRegistry {
299
317
  if (gitignoreModule.checkGitignore && gitignoreModule.CHECK_ID) {
300
318
  this.register({
301
319
  id: gitignoreModule.CHECK_ID,
302
- name: gitignoreModule.CHECK_NAME || '.gitignore Validation',
320
+ name: gitignoreModule.CHECK_NAME || '.gitignore validation',
303
321
  description: gitignoreModule.CHECK_DESCRIPTION || 'Validates .gitignore file',
304
322
  checkFunction: gitignoreModule.checkGitignore,
305
323
  category: 'configuration',
@@ -307,6 +325,42 @@ export class HealthCheckRegistry {
307
325
  });
308
326
  }
309
327
 
328
+ // Register spec directory and files check
329
+ if (specDirectoryAndFilesModule.checkSpecDirectoryAndFiles && specDirectoryAndFilesModule.CHECK_ID) {
330
+ this.register({
331
+ id: specDirectoryAndFilesModule.CHECK_ID,
332
+ name: specDirectoryAndFilesModule.CHECK_NAME || 'Spec Directory and Files Check',
333
+ description: specDirectoryAndFilesModule.CHECK_DESCRIPTION || 'Validates spec directories and required files',
334
+ checkFunction: specDirectoryAndFilesModule.checkSpecDirectoryAndFiles,
335
+ category: 'content',
336
+ priority: 16 // After specs.json validation, before general spec files discovery
337
+ });
338
+ }
339
+
340
+ // Register console messages check
341
+ if (consoleMessagesModule.checkConsoleMessages) {
342
+ this.register({
343
+ id: consoleMessagesModule.checkConsoleMessagesMetadata?.id || 'console-messages',
344
+ name: consoleMessagesModule.checkConsoleMessagesMetadata?.name || 'Console Messages',
345
+ description: consoleMessagesModule.checkConsoleMessagesMetadata?.description || 'Analyzes console output from operations',
346
+ checkFunction: consoleMessagesModule.checkConsoleMessages,
347
+ category: consoleMessagesModule.checkConsoleMessagesMetadata?.category || 'operations',
348
+ priority: 50 // Run after most other checks as it analyzes operation results
349
+ });
350
+ }
351
+
352
+ // Register link checker (Node.js only - not available in browsers)
353
+ if (linkCheckerModule && linkCheckerModule.checkLinks && linkCheckerModule.CHECK_ID) {
354
+ this.register({
355
+ id: linkCheckerModule.CHECK_ID,
356
+ name: linkCheckerModule.CHECK_NAME || 'Link Checker',
357
+ description: linkCheckerModule.CHECK_DESCRIPTION || 'Validates all links in the generated HTML output',
358
+ checkFunction: linkCheckerModule.checkLinks,
359
+ category: 'quality',
360
+ priority: 40 // Run after all file validations complete
361
+ });
362
+ }
363
+
310
364
  this.autoDiscovered = true;
311
365
  } catch (error) {
312
366
  console.warn('Failed to auto-discover some health checks:', error.message);
@@ -37,6 +37,10 @@ import { checkSpecFiles } from './checks/spec-files.js';
37
37
  import { checkSpecsJson } from './checks/specsjson.js';
38
38
  import { checkExternalSpecsUrls } from './checks/external-specs-urls.js';
39
39
  import { checkGitignore } from './checks/gitignore.js';
40
+ import { checkSpecDirectoryAndFiles } from './checks/spec-directory-and-files.js';
41
+ import { checkConsoleMessages } from './checks/console-messages.js';
42
+ // Note: checkLinks (link-checker) is NOT imported here to avoid Node.js dependencies in browsers
43
+ // It is dynamically imported by the health-check-registry during auto-discovery in Node.js environments only
40
44
 
41
45
  // Re-export types for backward compatibility
42
46
  /**
@@ -74,9 +78,6 @@ export function createHealthCheckResult(check, status, message, details = {}) {
74
78
  return utilsCreateHealthCheckResult(check, status, message, details);
75
79
  }
76
80
 
77
- // Re-export the actual check functions directly
78
- export { checkPackageJson, checkSpecFiles, checkSpecsJson, checkExternalSpecsUrls, checkGitignore };
79
-
80
81
  /**
81
82
  * Runs a comprehensive health check suite on a specification repository.
82
83
  *
@@ -123,6 +124,15 @@ export async function runHealthChecks(provider, options = {}) {
123
124
 
124
125
  // Re-export modular components for advanced usage
125
126
  export {
127
+ // Individual check functions
128
+ checkPackageJson,
129
+ checkSpecFiles,
130
+ checkSpecsJson,
131
+ checkExternalSpecsUrls,
132
+ checkGitignore,
133
+ checkSpecDirectoryAndFiles,
134
+ checkConsoleMessages,
135
+
126
136
  // Utils
127
137
  calculateSummary,
128
138
  isValidHealthCheckResult,