@kabran-tecnologia/kabran-config 1.6.0 → 1.7.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.
@@ -34,8 +34,14 @@ import {
34
34
  calculateExecutionStats,
35
35
  countIssues,
36
36
  extractComponents,
37
+ getTraceId,
38
+ buildTelemetryExtension,
37
39
  } from './ci-result-utils.mjs'
38
40
 
41
+ // History and trends imports
42
+ import { loadHistory, addToHistory, saveHistory } from './ci-result-history.mjs'
43
+ import { calculateTrends } from './ci-result-trends.mjs'
44
+
39
45
  // Validator imports
40
46
  import { getLicenseCheckResult } from './license-check.mjs'
41
47
  import { getReadmeCheckResult } from './readme-validator.mjs'
@@ -132,6 +138,7 @@ async function runValidators(projectRoot, options = {}) {
132
138
  * @param {Object} input.project - Project information
133
139
  * @param {Object} input.metadata - Additional metadata
134
140
  * @param {Object} input.validators - Validator results (from runValidators)
141
+ * @param {Object} input.trends - Pre-calculated trends (optional)
135
142
  * @returns {Object} CI result object
136
143
  */
137
144
  export function generateCiResult(input) {
@@ -143,6 +150,7 @@ export function generateCiResult(input) {
143
150
  metadata = {},
144
151
  issues = [],
145
152
  validators = {},
153
+ trends = null,
146
154
  } = input
147
155
 
148
156
  const now = new Date().toISOString()
@@ -169,19 +177,36 @@ export function generateCiResult(input) {
169
177
  // Determine exit code
170
178
  const exitCode = executionStats.steps_failed > 0 ? 1 : 0
171
179
 
180
+ // Get trace ID if available
181
+ const traceId = getTraceId()
182
+
183
+ // Build meta object
184
+ const meta = {
185
+ generated_at: now,
186
+ generator: getGeneratorVersion(),
187
+ run_id: generateRunId(),
188
+ trigger: detectTrigger(),
189
+ branch: getGitBranch(),
190
+ commit: getGitCommit(),
191
+ }
192
+
193
+ // Add trace_id if available
194
+ if (traceId) {
195
+ meta.trace_id = traceId
196
+ }
197
+
198
+ // Build extensions with telemetry if trace_id exists
199
+ const extensions = { ...(metadata.extensions || {}) }
200
+ if (traceId) {
201
+ extensions.telemetry = buildTelemetryExtension(traceId)
202
+ }
203
+
172
204
  // Build result object
173
205
  const result = {
174
206
  $schema: 'https://kabran.dev/schemas/ci-result.v2.json',
175
207
  version: '1.0.0',
176
208
 
177
- meta: {
178
- generated_at: now,
179
- generator: getGeneratorVersion(),
180
- run_id: generateRunId(),
181
- trigger: detectTrigger(),
182
- branch: getGitBranch(),
183
- commit: getGitCommit(),
184
- },
209
+ meta,
185
210
 
186
211
  project: {
187
212
  name: project.name || 'unknown',
@@ -216,7 +241,12 @@ export function generateCiResult(input) {
216
241
  checks,
217
242
  issues,
218
243
  errors,
219
- extensions: metadata.extensions || {},
244
+ extensions,
245
+ }
246
+
247
+ // Add trends if provided
248
+ if (trends) {
249
+ result.trends = trends
220
250
  }
221
251
 
222
252
  return result
@@ -236,6 +266,10 @@ function parseArgs(args) {
236
266
  skipReadme: false,
237
267
  skipEnv: false,
238
268
  skipQualityStandard: false,
269
+ trackHistory: false,
270
+ historyFile: null,
271
+ maxHistoryEntries: 30,
272
+ calculateTrends: false,
239
273
  }
240
274
 
241
275
  for (let i = 0; i < args.length; i++) {
@@ -259,6 +293,16 @@ function parseArgs(args) {
259
293
  options.skipEnv = true
260
294
  } else if (arg === '--skip-quality-standard') {
261
295
  options.skipQualityStandard = true
296
+ } else if (arg === '--track-history') {
297
+ options.trackHistory = true
298
+ } else if (arg === '--history-file') {
299
+ options.historyFile = args[++i]
300
+ options.trackHistory = true
301
+ } else if (arg === '--max-history') {
302
+ options.maxHistoryEntries = parseInt(args[++i], 10) || 30
303
+ } else if (arg === '--calculate-trends') {
304
+ options.calculateTrends = true
305
+ options.trackHistory = true
262
306
  } else if (arg === '--help' || arg === '-h') {
263
307
  console.log(`
264
308
  Usage: generate-ci-result.mjs [options]
@@ -273,6 +317,10 @@ Options:
273
317
  --skip-readme Skip README check when running validators
274
318
  --skip-env Skip env check when running validators
275
319
  --skip-quality-standard Skip quality-standard check when running validators
320
+ --track-history Track result in history file (max 30 entries)
321
+ --history-file <file> Custom history file path (default: docs/quality/ci-result-history.json)
322
+ --max-history <n> Maximum history entries to keep (default: 30)
323
+ --calculate-trends Calculate and include trends from history
276
324
  -h, --help Show this help message
277
325
 
278
326
  Input format:
@@ -362,15 +410,35 @@ async function main() {
362
410
  })
363
411
  }
364
412
 
413
+ // Determine paths
414
+ const outputPath = options.output || resolve(options.projectRoot, 'docs/quality/ci-result.json')
415
+ const historyPath = options.historyFile || resolve(options.projectRoot, 'docs/quality/ci-result-history.json')
416
+
417
+ // Load history and calculate trends if requested
418
+ let history = null
419
+ if (options.trackHistory || options.calculateTrends) {
420
+ history = loadHistory(historyPath)
421
+
422
+ // Calculate trends if requested
423
+ if (options.calculateTrends && history.entries.length > 0) {
424
+ input.trends = calculateTrends(history.entries)
425
+ }
426
+ }
427
+
365
428
  // Generate result
366
429
  const result = generateCiResult(input)
367
430
 
431
+ // Update history with new result
432
+ if (options.trackHistory && history) {
433
+ addToHistory(result, history)
434
+ saveHistory(history, historyPath, options.maxHistoryEntries)
435
+ console.log(`History updated: ${historyPath} (${history.entries.length} entries)`)
436
+ }
437
+
368
438
  // Output
369
439
  if (options.stdout) {
370
440
  console.log(JSON.stringify(result, null, 2))
371
441
  } else {
372
- const outputPath = options.output || resolve(options.projectRoot, 'docs/quality/ci-result.json')
373
-
374
442
  // Ensure directory exists
375
443
  mkdirSync(dirname(outputPath), { recursive: true })
376
444
 
@@ -142,12 +142,48 @@ export function generateComment(comparison, current, baseline) {
142
142
  }
143
143
  }
144
144
 
145
+ // Trends section (if available)
146
+ if (current.trends && current.trends.data?.total_runs > 1) {
147
+ lines.push(`### 📈 Trends`)
148
+ lines.push('')
149
+ lines.push(`| Metric | Direction | Change (7d) | Avg (30d) |`)
150
+ lines.push(`|--------|-----------|-------------|-----------|`)
151
+
152
+ const scoreTrend = current.trends.score
153
+ if (scoreTrend) {
154
+ const dirEmoji = scoreTrend.direction === 'improving' ? '📈' : scoreTrend.direction === 'degrading' ? '📉' : '➡️'
155
+ lines.push(`| Score | ${dirEmoji} ${scoreTrend.direction} | ${scoreTrend.change_7d !== null ? formatDiff(scoreTrend.change_7d) : 'N/A'} | ${scoreTrend.avg_30d || 'N/A'} |`)
156
+ }
157
+
158
+ const perfTrend = current.trends.performance
159
+ if (perfTrend && perfTrend.avg_30d) {
160
+ const dirEmoji = perfTrend.direction === 'improving' ? '📈' : perfTrend.direction === 'degrading' ? '📉' : '➡️'
161
+ const change7d = perfTrend.change_7d !== null ? `${(perfTrend.change_7d / 1000).toFixed(1)}s` : 'N/A'
162
+ lines.push(`| Duration | ${dirEmoji} ${perfTrend.direction} | ${change7d} | ${(perfTrend.avg_30d / 1000).toFixed(1)}s |`)
163
+ }
164
+
165
+ lines.push('')
166
+ lines.push(`<sub>Based on ${current.trends.data.total_runs} runs</sub>`)
167
+ lines.push('')
168
+ }
169
+
145
170
  // Timing info
146
171
  lines.push(`---`)
147
172
  lines.push(`<sub>`)
148
173
  lines.push(`⏱️ Duration: ${current.timing?.total_human || 'N/A'} | `)
149
174
  lines.push(`🔀 Branch: ${current.meta?.branch || 'N/A'} | `)
150
175
  lines.push(`📝 Commit: ${current.meta?.commit || 'N/A'}`)
176
+
177
+ // Add trace link if available
178
+ if (current.meta?.trace_id) {
179
+ const traceUrl = current.extensions?.telemetry?.trace_url
180
+ if (traceUrl) {
181
+ lines.push(` | 🔍 [Trace](${traceUrl})`)
182
+ } else {
183
+ lines.push(` | 🔍 Trace: ${current.meta.trace_id}`)
184
+ }
185
+ }
186
+
151
187
  lines.push(`</sub>`)
152
188
 
153
189
  return lines.join('\n')
@@ -79,6 +79,7 @@ export function parseArgs(args) {
79
79
  skipQualityStandard: false,
80
80
  syncWorkflows: false,
81
81
  syncHusky: false,
82
+ telemetryEnv: false,
82
83
  force: false,
83
84
  dryRun: false,
84
85
  help: false,
@@ -97,6 +98,8 @@ export function parseArgs(args) {
97
98
  options.syncWorkflows = true;
98
99
  } else if (arg === '--sync-husky') {
99
100
  options.syncHusky = true;
101
+ } else if (arg === '--telemetry-env') {
102
+ options.telemetryEnv = true;
100
103
  } else if (arg === '--force') {
101
104
  options.force = true;
102
105
  } else if (arg === '--dry-run') {
@@ -132,6 +135,7 @@ ${colors.yellow}OPTIONS:${colors.reset}
132
135
  --skip-quality-standard Don't create quality-standard.md
133
136
  --sync-workflows Overwrite existing workflow files
134
137
  --sync-husky Overwrite existing husky hooks
138
+ --telemetry-env Generate .env.example with telemetry variables
135
139
  --force Overwrite all existing files
136
140
  --dry-run Preview changes without modifying files
137
141
  --help, -h Show this help message
@@ -146,6 +150,9 @@ ${colors.yellow}EXAMPLES:${colors.reset}
146
150
  # Update workflows only
147
151
  npx kabran-setup --sync-workflows
148
152
 
153
+ # Generate telemetry .env.example
154
+ npx kabran-setup --telemetry-env
155
+
149
156
  # Preview changes without modifying
150
157
  npx kabran-setup --dry-run
151
158
 
@@ -547,6 +554,79 @@ export function setupQualityStandard(projectDir, templatesDir, options) {
547
554
  return results;
548
555
  }
549
556
 
557
+ /**
558
+ * Setup telemetry .env.example
559
+ * @param {string} projectDir - Project directory
560
+ * @param {string} templatesDir - Templates directory
561
+ * @param {object} options - Setup options
562
+ * @returns {object} Results
563
+ */
564
+ export function setupTelemetryEnv(projectDir, templatesDir, options) {
565
+ const {force = false, dryRun = false} = options;
566
+
567
+ const results = {
568
+ created: 0,
569
+ overwritten: 0,
570
+ skipped: 0,
571
+ };
572
+
573
+ logInfo('Setting up telemetry .env.example...');
574
+
575
+ const src = join(templatesDir, 'telemetry', '.env.telemetry.example');
576
+ const dest = join(projectDir, '.env.example');
577
+
578
+ // Check if source template exists
579
+ if (!existsSync(src)) {
580
+ logWarn('Template not found: templates/telemetry/.env.telemetry.example');
581
+ return results;
582
+ }
583
+
584
+ // Check if destination exists
585
+ const destExists = existsSync(dest);
586
+
587
+ if (destExists && !force) {
588
+ // If .env.example exists, append telemetry section if not present
589
+ const existingContent = readFileSync(dest, 'utf-8');
590
+
591
+ if (existingContent.includes('Kabran Telemetry Configuration')) {
592
+ if (dryRun) {
593
+ logDry('Would skip .env.example (telemetry section already present)');
594
+ } else {
595
+ logSkip('.env.example (telemetry section already present)');
596
+ }
597
+ results.skipped = 1;
598
+ return results;
599
+ }
600
+
601
+ // Append telemetry section
602
+ const telemetryContent = readFileSync(src, 'utf-8');
603
+
604
+ if (dryRun) {
605
+ logDry('Would append telemetry section to .env.example');
606
+ results.overwritten = 1;
607
+ return results;
608
+ }
609
+
610
+ const newContent = existingContent.trimEnd() + '\n\n' + telemetryContent;
611
+ writeFileSync(dest, newContent, 'utf-8');
612
+ logSuccess('Appended telemetry section to: .env.example');
613
+ results.overwritten = 1;
614
+ return results;
615
+ }
616
+
617
+ const status = copyFile(src, dest, {overwrite: force, dryRun});
618
+
619
+ if (status === 'created' || status === 'would_create') {
620
+ results.created = 1;
621
+ } else if (status === 'overwritten' || status === 'would_overwrite') {
622
+ results.overwritten = 1;
623
+ } else {
624
+ results.skipped = 1;
625
+ }
626
+
627
+ return results;
628
+ }
629
+
550
630
  /**
551
631
  * Run setup
552
632
  * @param {string} projectDir - Project directory
@@ -561,6 +641,7 @@ export function runSetup(projectDir, options) {
561
641
  husky: {created: 0, overwritten: 0, skipped: 0},
562
642
  configs: {created: 0, overwritten: 0, skipped: 0},
563
643
  qualityStandard: {created: 0, overwritten: 0, skipped: 0},
644
+ telemetryEnv: {created: 0, overwritten: 0, skipped: 0},
564
645
  };
565
646
 
566
647
  console.log('');
@@ -592,11 +673,17 @@ export function runSetup(projectDir, options) {
592
673
  }
593
674
 
594
675
  // Setup quality-standard.md (unless skipped or in sync mode)
595
- if (!options.skipQualityStandard && !isSyncMode) {
676
+ if (!options.skipQualityStandard && !isSyncMode && !options.telemetryEnv) {
596
677
  summary.qualityStandard = setupQualityStandard(projectDir, templatesDir, options);
597
678
  console.log('');
598
679
  }
599
680
 
681
+ // Setup telemetry .env.example (only when explicitly requested)
682
+ if (options.telemetryEnv) {
683
+ summary.telemetryEnv = setupTelemetryEnv(projectDir, templatesDir, options);
684
+ console.log('');
685
+ }
686
+
600
687
  return summary;
601
688
  }
602
689
 
@@ -609,10 +696,10 @@ export function printSummary(summary) {
609
696
  console.log(`${colors.cyan}=== Setup Summary ===${colors.reset}`);
610
697
 
611
698
  const total = {
612
- created: summary.workflows.created + summary.husky.created + summary.configs.created + summary.qualityStandard.created,
699
+ created: summary.workflows.created + summary.husky.created + summary.configs.created + summary.qualityStandard.created + summary.telemetryEnv.created,
613
700
  overwritten:
614
- summary.workflows.overwritten + summary.husky.overwritten + summary.configs.overwritten + summary.qualityStandard.overwritten,
615
- skipped: summary.workflows.skipped + summary.husky.skipped + summary.configs.skipped + summary.qualityStandard.skipped,
701
+ summary.workflows.overwritten + summary.husky.overwritten + summary.configs.overwritten + summary.qualityStandard.overwritten + summary.telemetryEnv.overwritten,
702
+ skipped: summary.workflows.skipped + summary.husky.skipped + summary.configs.skipped + summary.qualityStandard.skipped + summary.telemetryEnv.skipped,
616
703
  };
617
704
 
618
705
  if (total.created > 0) {