create-backlist 10.0.2 → 10.0.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.
@@ -0,0 +1,49 @@
1
+ // CI/CD JSON reporter — structured output for integrations
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
4
+ import { VERSION } from '../qa-engine.js';
5
+
6
+ export class JSONReporter {
7
+ #session;
8
+
9
+ constructor(session) { this.#session = session; }
10
+
11
+ async generate(reportDir) {
12
+ const summary = this.#session.getSummary();
13
+ const filename = `${this.#session.id.toLowerCase()}.json`;
14
+ const filepath = path.join(reportDir, filename);
15
+
16
+ const output = {
17
+ meta: {
18
+ version : VERSION,
19
+ runId : this.#session.id,
20
+ generatedAt: new Date().toISOString(),
21
+ dataSource : 'real-runtime-testing',
22
+ },
23
+ urls : this.#session.urls,
24
+ summary,
25
+ results : this.#session.results,
26
+ bugs : this.#session.bugs,
27
+ routeMap : this.#session.routeMap,
28
+ apiLog : this.#session.apiLog,
29
+ secFindings: this.#session.secFindings,
30
+ perfMetrics: this.#session.perfMetrics,
31
+ a11yResults: this.#session.a11yResults,
32
+ seoResults : this.#session.seoResults,
33
+ consoleErrors: this.#session.consoleErrors,
34
+ networkLog : this.#session.networkLog.filter(e => e.type === 'failed'),
35
+ screenshots: this.#session.screenshots,
36
+ // CI-friendly exit signals
37
+ ci: {
38
+ exitCode : summary.failed > 0 || this.#session.bugs.some(b => b.severity === 'P0') ? 1 : 0,
39
+ p0BugCount : this.#session.bugs.filter(b => b.severity === 'P0').length,
40
+ p1BugCount : this.#session.bugs.filter(b => b.severity === 'P1').length,
41
+ passRate : summary.passRate,
42
+ passed : summary.passed === summary.total,
43
+ },
44
+ };
45
+
46
+ await fs.writeJson(filepath, output, { spaces: 2 });
47
+ return filepath;
48
+ }
49
+ }
@@ -0,0 +1,184 @@
1
+ // Real-time terminal dashboard — shows ACTUAL test execution
2
+ import chalk from 'chalk';
3
+ import { formatDuration } from '../qa-engine.js';
4
+
5
+ const ESC = '\x1b[';
6
+ const CLEAR_LINE = ESC + '2K\r';
7
+ const CURSOR_UP = (n) => ESC + `${n}A`;
8
+ const CURSOR_HIDE = ESC + '?25l';
9
+ const CURSOR_SHOW = ESC + '?25h';
10
+
11
+ export class TerminalDashboard {
12
+ #session;
13
+ #lines = 0;
14
+ #active = false;
15
+ #startTime = Date.now();
16
+ #phase = 'Initializing...';
17
+ #currentTest = '';
18
+ #log = [];
19
+ #timer = null;
20
+
21
+ constructor(session) { this.#session = session; }
22
+
23
+ start() {
24
+ this.#active = true;
25
+ this.#startTime = Date.now();
26
+ process.stdout.write(CURSOR_HIDE);
27
+ this.#render();
28
+ this.#timer = setInterval(() => this.#render(), 500);
29
+ }
30
+
31
+ stop() {
32
+ this.#active = false;
33
+ if (this.#timer) { clearInterval(this.#timer); this.#timer = null; }
34
+ process.stdout.write(CURSOR_SHOW);
35
+ this.#clear();
36
+ this.#printFinalSummary();
37
+ }
38
+
39
+ setPhase(phase) { this.#phase = phase; this.log(chalk.cyan(phase)); }
40
+ setCurrentTest(name) { this.#currentTest = name; }
41
+ addResult(result) { this.#currentTest = ''; }
42
+
43
+ log(msg) {
44
+ const entry = `${chalk.gray(new Date().toLocaleTimeString())} ${msg}`;
45
+ this.#log.push(entry);
46
+ if (this.#log.length > 12) this.#log.shift();
47
+ }
48
+
49
+ #render() {
50
+ if (!this.#active) return;
51
+ this.#clear();
52
+
53
+ const lines = this.#buildLines();
54
+ this.#lines = lines.length;
55
+ process.stdout.write(lines.join('\n') + '\n');
56
+ }
57
+
58
+ #clear() {
59
+ if (this.#lines > 0) {
60
+ process.stdout.write(CURSOR_UP(this.#lines) + CLEAR_LINE);
61
+ for (let i = 1; i < this.#lines; i++) {
62
+ process.stdout.write('\n' + CLEAR_LINE);
63
+ }
64
+ process.stdout.write(CURSOR_UP(this.#lines - 1));
65
+ }
66
+ this.#lines = 0;
67
+ }
68
+
69
+ #buildLines() {
70
+ const elapsed = ((Date.now() - this.#startTime) / 1000).toFixed(1);
71
+ const results = this.#session.results;
72
+ const bugs = this.#session.bugs;
73
+ const passed = results.filter(r => r.status === 'PASS' || r.status === 'FLAKY').length;
74
+ const failed = results.filter(r => r.status === 'FAIL').length;
75
+ const total = results.length;
76
+ const passRate = total > 0 ? Math.round((passed / total) * 100) : 0;
77
+ const mem = process.memoryUsage();
78
+ const heapMB = (mem.heapUsed / 1024 / 1024).toFixed(0);
79
+
80
+ const w = Math.min(process.stdout.columns || 80, 90);
81
+ const bar = '─'.repeat(w - 2);
82
+ const lines = [];
83
+
84
+ const c1 = chalk.hex('#00F5FF');
85
+ const c2 = chalk.hex('#BF40FF');
86
+ const pad = (s) => String(s).padEnd(w - 2);
87
+
88
+ lines.push(c1(`┌${bar}┐`));
89
+ lines.push(c1('│') + c2.bold(pad(` ⚡ BACKLIST ENTERPRISE QA v12.0 — REAL RUNTIME TESTING`)) + c1('│'));
90
+ lines.push(c1(`├${bar}┤`));
91
+ lines.push(c1('│') + pad(` ${chalk.cyan('Phase:')} ${chalk.white(this.#phase.slice(0, w - 14))}`) + c1('│'));
92
+ lines.push(c1(`├${bar}┤`));
93
+
94
+ // Real metrics
95
+ const pBar = this.#progressBar(passRate, 28);
96
+ lines.push(c1('│') + pad(
97
+ ` ${chalk.green('✓')} ${chalk.bold(passed)} pass ` +
98
+ `${chalk.red('✗')} ${chalk.bold(failed)} fail ` +
99
+ `${chalk.cyan('🐛')} ${chalk.bold(bugs.length)} bugs ` +
100
+ `${chalk.gray('⏱')} ${chalk.white(elapsed + 's')} ` +
101
+ `${chalk.gray('Heap')} ${chalk.white(heapMB + 'MB')}`
102
+ ) + c1('│'));
103
+ lines.push(c1('│') + pad(
104
+ ` [${pBar}] ${chalk.bold(passRate + '%')} (${total} real tests)`
105
+ ) + c1('│'));
106
+ lines.push(c1(`├${bar}┤`));
107
+
108
+ // Current test
109
+ const ct = this.#currentTest
110
+ ? ` ${chalk.yellow('⟳')} ${chalk.yellow('Testing:')} ${chalk.white(this.#currentTest.slice(0, w - 18))}`
111
+ : ` ${chalk.gray('⊙ Awaiting next test...')}`;
112
+ lines.push(c1('│') + pad(ct) + c1('│'));
113
+ lines.push(c1(`├${bar}┤`));
114
+
115
+ // Discovered routes
116
+ const routeCount = this.#session.routeMap.length;
117
+ const apiCount = this.#session.apiLog.length;
118
+ const screensCount = this.#session.screenshots.length;
119
+ lines.push(c1('│') + pad(
120
+ ` ${chalk.cyan('Routes:')} ${chalk.white(routeCount)} ` +
121
+ `${chalk.cyan('APIs:')} ${chalk.white(apiCount)} ` +
122
+ `${chalk.cyan('Screenshots:')} ${chalk.white(screensCount)} ` +
123
+ `${chalk.cyan('Net errors:')} ${chalk.white(this.#session.networkLog.filter(e => e.type === 'failed').length)}`
124
+ ) + c1('│'));
125
+ lines.push(c1(`├${bar}┤`));
126
+
127
+ // Recent real results
128
+ lines.push(c1('│') + chalk.gray(pad(' Real Test Results:')) + c1('│'));
129
+ const recent = results.slice(-6);
130
+ for (const r of recent) {
131
+ const icon = r.status === 'PASS' ? chalk.green('✓')
132
+ : r.status === 'FAIL' ? chalk.red('✗')
133
+ : r.status === 'FLAKY' ? chalk.yellow('⚠')
134
+ : chalk.gray('⊘');
135
+ const dur = chalk.gray(formatDuration(r.duration || 0));
136
+ const name = r.name?.slice(0, w - 36) || '';
137
+ lines.push(c1('│') + pad(` ${icon} ${chalk.gray('[' + (r.type || '').padEnd(12) + ']')} ${chalk.white(name)} ${dur}`) + c1('│'));
138
+ }
139
+ for (let i = recent.length; i < 6; i++) {
140
+ lines.push(c1('│') + pad('') + c1('│'));
141
+ }
142
+
143
+ // Log
144
+ lines.push(c1(`├${bar}┤`));
145
+ lines.push(c1('│') + chalk.gray(pad(' Live log:')) + c1('│'));
146
+ for (const entry of this.#log.slice(-5)) {
147
+ lines.push(c1('│') + (' ' + entry).slice(0, w - 2).padEnd(w - 2) + c1('│'));
148
+ }
149
+ for (let i = this.#log.length; i < 5; i++) {
150
+ lines.push(c1('│') + pad('') + c1('│'));
151
+ }
152
+
153
+ lines.push(c1(`└${bar}┘`));
154
+ lines.push(chalk.dim(` Real runtime data only · ${total} tests · ${bugs.length} bugs · Ctrl+C to stop`));
155
+
156
+ return lines;
157
+ }
158
+
159
+ #progressBar(pct, width = 20) {
160
+ const filled = Math.min(Math.round((pct / 100) * width), width);
161
+ const empty = width - filled;
162
+ const color = pct >= 90 ? chalk.green : pct >= 70 ? chalk.yellow : chalk.red;
163
+ return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
164
+ }
165
+
166
+ #printFinalSummary() {
167
+ const summary = this.#session.getSummary();
168
+ const color = Number(summary.passRate) >= 90 ? chalk.green
169
+ : Number(summary.passRate) >= 70 ? chalk.yellow : chalk.red;
170
+
171
+ console.log('');
172
+ console.log(chalk.hex('#00F5FF').bold(' ── Real Runtime QA Results ─────────────────────────────'));
173
+ console.log(` Tests run: ${chalk.white.bold(summary.total)}`);
174
+ console.log(` Passed: ${chalk.green.bold(summary.passed)}`);
175
+ console.log(` Failed: ${chalk.red.bold(summary.failed)}`);
176
+ console.log(` Flaky: ${chalk.yellow.bold(summary.flaky)}`);
177
+ console.log(` Pass rate: ${color.bold(summary.passRate + '%')}`);
178
+ console.log(` Bugs found: ${chalk.cyan.bold(this.#session.bugs.length)}`);
179
+ console.log(` Duration: ${chalk.white(formatDuration(summary.duration))}`);
180
+ console.log(` Routes: ${chalk.white(this.#session.routeMap.length)} discovered`);
181
+ console.log(` Screenshots:${chalk.white(this.#session.screenshots.length)} captured`);
182
+ console.log('');
183
+ }
184
+ }
@@ -0,0 +1,98 @@
1
+ // AI-assisted bug classification — pattern matching + heuristics
2
+ // (Works without external API — pure local intelligence)
3
+
4
+ const SEVERITY_PATTERNS = {
5
+ P0: [
6
+ /security|auth.*bypass|sql.inject|xss|rce|remote.code|exposed.*secret|password.*leak/i,
7
+ /crash|fatal|500.*error|server.*down|database.*connect.*fail/i,
8
+ /payment|transaction|data.*loss|corrupt/i,
9
+ ],
10
+ P1: [
11
+ /login.*fail|auth.*error|jwt|token.*invalid/i,
12
+ /api.*timeout|endpoint.*down|cors.*error/i,
13
+ /form.*broken|submit.*fail|validation.*miss/i,
14
+ /lcp|largest.*contentful|performance.*poor/i,
15
+ /wcag|accessibility.*critical|a11y.*serious/i,
16
+ ],
17
+ P2: [
18
+ /console.*error|js.*error|runtime.*error/i,
19
+ /network.*fail|fetch.*error|404/i,
20
+ /missing.*meta|seo.*issue|canonical/i,
21
+ /slow.*resource|image.*size|bundle.*large/i,
22
+ ],
23
+ P3: [
24
+ /warning|minor|style|layout|cosmetic/i,
25
+ /todo|typo|label|placeholder/i,
26
+ /p3|low.*priority/i,
27
+ ],
28
+ };
29
+
30
+ const CATEGORY_PATTERNS = {
31
+ 'security' : /security|csp|hsts|cors|xss|injection|auth|token|csrf/i,
32
+ 'performance' : /lcp|fcp|cls|fid|ttfb|tbt|slow|timeout|load.*time|render/i,
33
+ 'accessibility': /wcag|a11y|aria|alt.*text|contrast|keyboard|screen.*reader/i,
34
+ 'seo' : /title|meta|description|canonical|sitemap|robots|og:|h1/i,
35
+ 'api' : /api|endpoint|status.*code|response|json|rest|graphql/i,
36
+ 'javascript' : /js.*error|console.*error|uncaught|undefined|null.*reference/i,
37
+ 'network' : /network|fetch|xhr|request.*fail|connection/i,
38
+ 'form' : /form|input|submit|validation|field|required/i,
39
+ 'auth' : /login|auth|token|session|password|credential|logout/i,
40
+ 'ui' : /layout|render|visual|style|component|button|link/i,
41
+ };
42
+
43
+ const RECOMMENDATIONS = {
44
+ 'security' : 'Review security configuration and conduct a penetration test',
45
+ 'performance' : 'Run Lighthouse audit and optimize assets/server response times',
46
+ 'accessibility': 'Fix WCAG 2.1 AA violations — use aXe DevTools for details',
47
+ 'seo' : 'Fix meta tags and submit updated sitemap to Search Console',
48
+ 'api' : 'Check API contract, add proper error handling and status codes',
49
+ 'javascript' : 'Debug JavaScript errors in browser DevTools — add error boundaries',
50
+ 'network' : 'Check CDN, server logs, and network configuration',
51
+ 'form' : 'Add client-side and server-side validation to all forms',
52
+ 'auth' : 'Audit authentication flow — check JWT expiry and refresh logic',
53
+ 'ui' : 'Test across browsers and screen sizes — check CSS specificity',
54
+ };
55
+
56
+ export class AIClassifier {
57
+ async classify(bug, session) {
58
+ const text = `${bug.title} ${bug.description || ''}`.toLowerCase();
59
+
60
+ // Determine severity from patterns
61
+ let severity = bug.severity || 'P3';
62
+ let confidence = 0.7;
63
+
64
+ for (const [sev, patterns] of Object.entries(SEVERITY_PATTERNS)) {
65
+ if (patterns.some(p => p.test(text))) {
66
+ severity = sev;
67
+ confidence = 0.85;
68
+ break;
69
+ }
70
+ }
71
+
72
+ // Determine category
73
+ let category = bug.type || 'general';
74
+ for (const [cat, pattern] of Object.entries(CATEGORY_PATTERNS)) {
75
+ if (pattern.test(text)) {
76
+ category = cat;
77
+ break;
78
+ }
79
+ }
80
+
81
+ // Context from session
82
+ const relatedBugs = session.bugs.filter(b =>
83
+ b.id !== bug.id && b.type === bug.type
84
+ ).length;
85
+
86
+ if (relatedBugs > 3) {
87
+ // Systemic issue — upgrade severity
88
+ const order = ['P3','P2','P1','P0'];
89
+ const idx = order.indexOf(severity);
90
+ if (idx > 0) { severity = order[idx - 1]; confidence = 0.9; }
91
+ }
92
+
93
+ const recommendation = RECOMMENDATIONS[category]
94
+ || 'Investigate and fix based on error details';
95
+
96
+ return { severity, category, recommendation, confidence };
97
+ }
98
+ }