argusqa-os 9.5.1 → 9.5.5

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Argus Orchestrator (v9.3.0)
2
+ * Argus Orchestrator
3
3
  *
4
4
  * Per-route crawl loop: cheap×2 flakiness pass + expensive×1 pass.
5
5
  * Extracted from crawl-and-report.js god object.
@@ -15,7 +15,6 @@ import 'dotenv/config';
15
15
  import { routes, config, auth, flows, apiContracts, severityOverrides, codebase, autoDiscover, thresholds } from '../config/targets.js';
16
16
  import { discoverRoutes } from '../utils/route-discoverer.js';
17
17
  import { analyzeCodebase, detectDeadRoutes, INTERNAL_LINKS_SCRIPT } from '../utils/codebase-analyzer.js';
18
- import { CSS_ANALYSIS_SCRIPT, parseCssAnalysisResult } from '../utils/css-analyzer.js';
19
18
  import { SEO_ANALYSIS_SCRIPT, parseSeoAnalysisResult } from '../utils/seo-analyzer.js';
20
19
  import { SECURITY_ANALYSIS_SCRIPT, parseSecurityAnalysisResult, analyzeSecurityConsole, analyzeSecurityNetwork } from '../utils/security-analyzer.js';
21
20
  import { CONTENT_ANALYSIS_SCRIPT, parseContentAnalysisResult } from '../utils/content-analyzer.js';
@@ -26,6 +25,7 @@ import { analyzeApiFrequency } from '.
26
25
  import { slugify } from '../utils/slug.js';
27
26
  import { unwrapEval, createMcpClient } from '../utils/mcp-client.js';
28
27
  import { CdpBrowserAdapter } from '../adapters/browser.js';
28
+ import { getFigmaFrame } from '../adapters/figma.js';
29
29
  import { chunkArray } from '../utils/parallel-crawler.js';
30
30
  import { validateApiContracts } from '../utils/contract-validator.js';
31
31
  import { checkLighthouse } from '../utils/lighthouse-checker.js';
@@ -33,13 +33,18 @@ import { parseIssues } from '.
33
33
  import { parseNetworkTiming } from '../utils/network-timing-analyzer.js';
34
34
 
35
35
  // Side-effect imports: each module calls registerExpensive() at load time.
36
- // lighthouse-checker.js also self-registers via its direct named import above (line 31).
36
+ // lighthouse-checker.js also self-registers via its direct named import above.
37
37
  // Order below controls iteration order in crawlAndAnalyzeRoute — must match original call order.
38
+ import '../utils/css-analyzer.js';
38
39
  import '../utils/responsive-analyzer.js';
39
40
  import '../utils/memory-analyzer.js';
40
41
  import '../utils/hover-analyzer.js';
41
42
  import '../utils/snapshot-analyzer.js';
42
43
  import '../utils/keyboard-analyzer.js';
44
+ import '../utils/theme-analyzer.js';
45
+ import '../utils/design-fidelity-analyzer.js';
46
+ import '../utils/web-vitals-analyzer.js';
47
+ import '../utils/visual-diff-analyzer.js';
43
48
 
44
49
  import { getExpensive } from '../registry.js';
45
50
  import { deduplicateFindings as deduplicateErrors } from './report-processor.js';
@@ -54,6 +59,7 @@ const logger = childLogger('orchestrator');
54
59
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
55
60
  const BASE_URL = process.env.TARGET_DEV_URL ?? 'http://localhost:3000';
56
61
  const OUTPUT_DIR = path.resolve(__dirname, '../../', config.outputDir);
62
+ const LIGHTHOUSE_TIMEOUT_MS = parseInt(process.env.ARGUS_LIGHTHOUSE_TIMEOUT ?? '120000', 10);
57
63
 
58
64
  // Thresholds for perf budgets and network analysis are centralized in targets.js.
59
65
 
@@ -362,7 +368,6 @@ function analyzeNetworkPerformance(perfEntries, pageUrl) {
362
368
 
363
369
  async function checkPerformanceBudgets(browser, url) {
364
370
  const violations = [];
365
- const LIGHTHOUSE_TIMEOUT_MS = parseInt(process.env.ARGUS_LIGHTHOUSE_TIMEOUT ?? '120000', 10);
366
371
 
367
372
  try {
368
373
  await browser.startTrace();
@@ -397,7 +402,6 @@ async function checkPerformanceBudgets(browser, url) {
397
402
  logger.warn(`[ARGUS] Performance trace skipped for ${url}: ${err.message}`);
398
403
  }
399
404
 
400
- void LIGHTHOUSE_TIMEOUT_MS; // referenced only here to prevent unused-var lint
401
405
  return violations;
402
406
  }
403
407
 
@@ -724,15 +728,7 @@ export async function crawlRouteCheap(route, baseUrl, mcp) {
724
728
  }
725
729
  } catch { /* URL parse failure */ }
726
730
 
727
- // 10. CSS analysis
728
- try {
729
- const cssRaw = await browser.evaluate(CSS_ANALYSIS_SCRIPT);
730
- result.errors.push(...parseCssAnalysisResult(unwrapEval(cssRaw), url));
731
- } catch (err) {
732
- logger.warn(`[ARGUS] CSS analysis skipped for ${url}: ${err.message}`);
733
- }
734
-
735
- // 11. Deduplicate within this cheap run
731
+ // 10. Deduplicate within this cheap run
736
732
  result.errors = deduplicateErrors(result.errors);
737
733
 
738
734
  // 12. Screenshot
@@ -805,8 +801,14 @@ export async function crawlRouteExpensive(route, baseUrl, mcp) {
805
801
  // Performance budget check
806
802
  errors.push(...(await checkPerformanceBudgets(browser, url)));
807
803
 
808
- // Full Lighthouse audit
809
- errors.push(...(await checkLighthouse(browser, url)));
804
+ // Full Lighthouse audit (capped at LIGHTHOUSE_TIMEOUT_MS to prevent indefinite hang)
805
+ errors.push(...(await Promise.race([
806
+ checkLighthouse(browser, url),
807
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Lighthouse timed out after ${LIGHTHOUSE_TIMEOUT_MS}ms`)), LIGHTHOUSE_TIMEOUT_MS)),
808
+ ]).catch(err => {
809
+ logger.warn(`[ARGUS] Lighthouse skipped for ${url}: ${err.message}`);
810
+ return [];
811
+ })));
810
812
 
811
813
  // Broken internal link detection
812
814
  try {
@@ -880,24 +882,34 @@ async function crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile) {
880
882
  await refreshSession(browser, auth, targetBaseUrl);
881
883
  await restoreSession(browser, targetBaseUrl, sessionFile);
882
884
  } catch (err) {
883
- logger.warn(`[ARGUS] Auth: session restore skipped for ${route.name}: ${err.message}`);
885
+ logger.warn(`[ARGUS] Auth: session restore skipped for ${route.name} (${route.path}): ${err.message}`);
884
886
  }
885
887
  }
886
888
 
887
889
  // Cheap pass × 2 → merge for flakiness
888
- logger.info(`[ARGUS] ${route.name}: cheap run 1/2...`);
890
+ logger.info(`[ARGUS] ${route.name} (${route.path}): cheap run 1/2...`);
889
891
  const cheapRun1 = await startSpan('argus.crawl_route', { url, critical: String(!!route.critical), pass: 'cheap_1' }, () => crawlRouteCheap(route, targetBaseUrl, mcp));
890
- logger.info(`[ARGUS] ${route.name}: cheap run 2/2 (flakiness check)...`);
892
+ logger.info(`[ARGUS] ${route.name} (${route.path}): cheap run 2/2 (flakiness check)...`);
891
893
  const cheapRun2 = await startSpan('argus.crawl_route', { url, critical: String(!!route.critical), pass: 'cheap_2' }, () => crawlRouteCheap(route, targetBaseUrl, mcp));
892
894
  const result = mergeRunResults(cheapRun1, cheapRun2);
893
895
 
894
896
  // Expensive pass × 1
895
- logger.info(`[ARGUS] ${route.name}: expensive analyzers (once)...`);
897
+ logger.info(`[ARGUS] ${route.name} (${route.path}): expensive analyzers (once)...`);
896
898
  const expensiveErrors = await startSpan('argus.crawl_route', { url, critical: String(!!route.critical), pass: 'expensive' }, () => crawlRouteExpensive(route, targetBaseUrl, mcp));
897
899
  result.errors.push(...expensiveErrors);
898
900
  result.errors = deduplicateErrors(result.errors);
899
901
 
900
- // Post-crawl expensive analyzers via registry (responsive, memory, hover, snapshot, keyboard)
902
+ // D9: Pre-fetch Figma design tokens when route specifies a figmaFrameUrl.
903
+ // Attaches figmaData to the route object so design-fidelity-analyzer can consume it.
904
+ if (route.figmaFrameUrl && !route.figmaData) {
905
+ try {
906
+ route.figmaData = await getFigmaFrame(route.figmaFrameUrl);
907
+ } catch (err) {
908
+ logger.warn(`[ARGUS] D9: Figma fetch failed for ${route.name} (${route.path}): ${err.message}`);
909
+ }
910
+ }
911
+
912
+ // Post-crawl expensive analyzers via registry (css, responsive, memory, hover, snapshot, keyboard, theme, design-fidelity)
901
913
  for (const { name, analyze } of getExpensive()) {
902
914
  if (name === 'lighthouse') continue; // runs inside crawlRouteExpensive
903
915
  try {
@@ -923,7 +935,7 @@ async function crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile) {
923
935
  if (Object.keys(screenshotPaths).length > 0) result.responsiveScreenshots = screenshotPaths;
924
936
  }
925
937
  } catch (err) {
926
- logger.warn(`[ARGUS] ${name} skipped for ${route.name}: ${err.message}`);
938
+ logger.warn(`[ARGUS] ${name} skipped for ${route.name} (${route.path}): ${err.message}`);
927
939
  }
928
940
  }
929
941
 
@@ -955,7 +967,7 @@ async function crawlShardWithClient(shard, targetBaseUrl, mcp, sessionFile) {
955
967
  const result = await crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile);
956
968
  const flakyCount = result.errors.filter(e => e.flaky).length;
957
969
  if (flakyCount > 0) {
958
- logger.info(`[ARGUS/parallel] ${route.name}: ${flakyCount} finding(s) downgraded to info (flaky)`);
970
+ logger.info(`[ARGUS/parallel] ${route.name} (${route.path}): ${flakyCount} finding(s) downgraded to info (flaky)`);
959
971
  }
960
972
  results.push(result);
961
973
  }
@@ -1060,7 +1072,7 @@ export async function runCrawl(mcp, routeOverrides = null, baseUrlOverride = nul
1060
1072
 
1061
1073
  const flakyCount = result.errors.filter(e => e.flaky).length;
1062
1074
  if (flakyCount > 0) {
1063
- logger.info(`[ARGUS] ${route.name}: ${flakyCount} finding(s) downgraded to info (flaky — appeared in only one cheap run)`);
1075
+ logger.info(`[ARGUS] ${route.name} (${route.path}): ${flakyCount} finding(s) downgraded to info (flaky — appeared in only one cheap run)`);
1064
1076
  }
1065
1077
 
1066
1078
  report.routes.push(result);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Argus Report Processor (v9.1.3)
2
+ * Argus Report Processor
3
3
  *
4
4
  * Post-crawl pipeline: dedup → severity overrides → summary rebuild →
5
5
  * baseline load/apply/save → trend append → JSON write.
@@ -54,7 +54,8 @@ async function slackPostWithBackoff(args) {
54
54
  const isRateLimit = err.code === 'slack_webapi_rate_limited'
55
55
  || err.message?.toLowerCase().includes('ratelimited');
56
56
  if (!isRateLimit || attempt === SLACK_RATE_LIMIT_RETRIES - 1) throw err;
57
- const retryAfterMs = (err.retryAfter ?? 1) * 1000;
57
+ const jitter = Math.floor(Math.random() * 1000);
58
+ const retryAfterMs = (err.retryAfter ?? 1) * 1000 + jitter;
58
59
  logger.warn(`[ARGUS] Slack rate limited — retrying in ${retryAfterMs}ms (attempt ${attempt + 1})`);
59
60
  await new Promise(r => setTimeout(r, retryAfterMs));
60
61
  }
package/src/registry.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Argus Analyzer Plugin Registry (v9.1.2)
2
+ * Argus Analyzer Plugin Registry
3
3
  *
4
4
  * Analyzers self-register at module load time by calling registerCheap()
5
5
  * or registerExpensive(). The orchestrator iterates getCheap() / getExpensive()
@@ -26,6 +26,8 @@
26
26
  */
27
27
 
28
28
  import { childLogger } from './logger.js';
29
+ import { registerExpensive } from '../registry.js';
30
+ import { unwrapEval } from './mcp-client.js';
29
31
 
30
32
  const logger = childLogger('css-analyzer');
31
33
 
@@ -405,3 +407,8 @@ export function parseCssAnalysisResult(rawResult, url) {
405
407
 
406
408
  return bugs;
407
409
  }
410
+
411
+ registerExpensive({ name: 'css', analyze: async (browser, url) => {
412
+ const cssRaw = await browser.evaluate(CSS_ANALYSIS_SCRIPT);
413
+ return parseCssAnalysisResult(unwrapEval(cssRaw), url);
414
+ } });