argusqa-os 9.5.1 → 9.5.3

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,16 @@ 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';
43
46
 
44
47
  import { getExpensive } from '../registry.js';
45
48
  import { deduplicateFindings as deduplicateErrors } from './report-processor.js';
@@ -54,6 +57,7 @@ const logger = childLogger('orchestrator');
54
57
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
55
58
  const BASE_URL = process.env.TARGET_DEV_URL ?? 'http://localhost:3000';
56
59
  const OUTPUT_DIR = path.resolve(__dirname, '../../', config.outputDir);
60
+ const LIGHTHOUSE_TIMEOUT_MS = parseInt(process.env.ARGUS_LIGHTHOUSE_TIMEOUT ?? '120000', 10);
57
61
 
58
62
  // Thresholds for perf budgets and network analysis are centralized in targets.js.
59
63
 
@@ -362,7 +366,6 @@ function analyzeNetworkPerformance(perfEntries, pageUrl) {
362
366
 
363
367
  async function checkPerformanceBudgets(browser, url) {
364
368
  const violations = [];
365
- const LIGHTHOUSE_TIMEOUT_MS = parseInt(process.env.ARGUS_LIGHTHOUSE_TIMEOUT ?? '120000', 10);
366
369
 
367
370
  try {
368
371
  await browser.startTrace();
@@ -397,7 +400,6 @@ async function checkPerformanceBudgets(browser, url) {
397
400
  logger.warn(`[ARGUS] Performance trace skipped for ${url}: ${err.message}`);
398
401
  }
399
402
 
400
- void LIGHTHOUSE_TIMEOUT_MS; // referenced only here to prevent unused-var lint
401
403
  return violations;
402
404
  }
403
405
 
@@ -724,15 +726,7 @@ export async function crawlRouteCheap(route, baseUrl, mcp) {
724
726
  }
725
727
  } catch { /* URL parse failure */ }
726
728
 
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
729
+ // 10. Deduplicate within this cheap run
736
730
  result.errors = deduplicateErrors(result.errors);
737
731
 
738
732
  // 12. Screenshot
@@ -805,8 +799,14 @@ export async function crawlRouteExpensive(route, baseUrl, mcp) {
805
799
  // Performance budget check
806
800
  errors.push(...(await checkPerformanceBudgets(browser, url)));
807
801
 
808
- // Full Lighthouse audit
809
- errors.push(...(await checkLighthouse(browser, url)));
802
+ // Full Lighthouse audit (capped at LIGHTHOUSE_TIMEOUT_MS to prevent indefinite hang)
803
+ errors.push(...(await Promise.race([
804
+ checkLighthouse(browser, url),
805
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Lighthouse timed out after ${LIGHTHOUSE_TIMEOUT_MS}ms`)), LIGHTHOUSE_TIMEOUT_MS)),
806
+ ]).catch(err => {
807
+ logger.warn(`[ARGUS] Lighthouse skipped for ${url}: ${err.message}`);
808
+ return [];
809
+ })));
810
810
 
811
811
  // Broken internal link detection
812
812
  try {
@@ -880,24 +880,34 @@ async function crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile) {
880
880
  await refreshSession(browser, auth, targetBaseUrl);
881
881
  await restoreSession(browser, targetBaseUrl, sessionFile);
882
882
  } catch (err) {
883
- logger.warn(`[ARGUS] Auth: session restore skipped for ${route.name}: ${err.message}`);
883
+ logger.warn(`[ARGUS] Auth: session restore skipped for ${route.name} (${route.path}): ${err.message}`);
884
884
  }
885
885
  }
886
886
 
887
887
  // Cheap pass × 2 → merge for flakiness
888
- logger.info(`[ARGUS] ${route.name}: cheap run 1/2...`);
888
+ logger.info(`[ARGUS] ${route.name} (${route.path}): cheap run 1/2...`);
889
889
  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)...`);
890
+ logger.info(`[ARGUS] ${route.name} (${route.path}): cheap run 2/2 (flakiness check)...`);
891
891
  const cheapRun2 = await startSpan('argus.crawl_route', { url, critical: String(!!route.critical), pass: 'cheap_2' }, () => crawlRouteCheap(route, targetBaseUrl, mcp));
892
892
  const result = mergeRunResults(cheapRun1, cheapRun2);
893
893
 
894
894
  // Expensive pass × 1
895
- logger.info(`[ARGUS] ${route.name}: expensive analyzers (once)...`);
895
+ logger.info(`[ARGUS] ${route.name} (${route.path}): expensive analyzers (once)...`);
896
896
  const expensiveErrors = await startSpan('argus.crawl_route', { url, critical: String(!!route.critical), pass: 'expensive' }, () => crawlRouteExpensive(route, targetBaseUrl, mcp));
897
897
  result.errors.push(...expensiveErrors);
898
898
  result.errors = deduplicateErrors(result.errors);
899
899
 
900
- // Post-crawl expensive analyzers via registry (responsive, memory, hover, snapshot, keyboard)
900
+ // D9: Pre-fetch Figma design tokens when route specifies a figmaFrameUrl.
901
+ // Attaches figmaData to the route object so design-fidelity-analyzer can consume it.
902
+ if (route.figmaFrameUrl && !route.figmaData) {
903
+ try {
904
+ route.figmaData = await getFigmaFrame(route.figmaFrameUrl);
905
+ } catch (err) {
906
+ logger.warn(`[ARGUS] D9: Figma fetch failed for ${route.name} (${route.path}): ${err.message}`);
907
+ }
908
+ }
909
+
910
+ // Post-crawl expensive analyzers via registry (css, responsive, memory, hover, snapshot, keyboard, theme, design-fidelity)
901
911
  for (const { name, analyze } of getExpensive()) {
902
912
  if (name === 'lighthouse') continue; // runs inside crawlRouteExpensive
903
913
  try {
@@ -923,7 +933,7 @@ async function crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile) {
923
933
  if (Object.keys(screenshotPaths).length > 0) result.responsiveScreenshots = screenshotPaths;
924
934
  }
925
935
  } catch (err) {
926
- logger.warn(`[ARGUS] ${name} skipped for ${route.name}: ${err.message}`);
936
+ logger.warn(`[ARGUS] ${name} skipped for ${route.name} (${route.path}): ${err.message}`);
927
937
  }
928
938
  }
929
939
 
@@ -955,7 +965,7 @@ async function crawlShardWithClient(shard, targetBaseUrl, mcp, sessionFile) {
955
965
  const result = await crawlAndAnalyzeRoute(route, targetBaseUrl, mcp, sessionFile);
956
966
  const flakyCount = result.errors.filter(e => e.flaky).length;
957
967
  if (flakyCount > 0) {
958
- logger.info(`[ARGUS/parallel] ${route.name}: ${flakyCount} finding(s) downgraded to info (flaky)`);
968
+ logger.info(`[ARGUS/parallel] ${route.name} (${route.path}): ${flakyCount} finding(s) downgraded to info (flaky)`);
959
969
  }
960
970
  results.push(result);
961
971
  }
@@ -1060,7 +1070,7 @@ export async function runCrawl(mcp, routeOverrides = null, baseUrlOverride = nul
1060
1070
 
1061
1071
  const flakyCount = result.errors.filter(e => e.flaky).length;
1062
1072
  if (flakyCount > 0) {
1063
- logger.info(`[ARGUS] ${route.name}: ${flakyCount} finding(s) downgraded to info (flaky — appeared in only one cheap run)`);
1073
+ logger.info(`[ARGUS] ${route.name} (${route.path}): ${flakyCount} finding(s) downgraded to info (flaky — appeared in only one cheap run)`);
1064
1074
  }
1065
1075
 
1066
1076
  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
+ } });