argusqa-os 9.6.2 → 9.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argusqa-os",
3
- "version": "9.6.2",
3
+ "version": "9.6.4",
4
4
  "mcpName": "io.github.ironclawdevs27/argus",
5
5
  "description": "Argus — AI-powered automated dev-testing platform using Chrome DevTools MCP and Claude Code",
6
6
  "keywords": [
@@ -123,6 +123,20 @@ export function writeStepSummary(markdown) {
123
123
  fs.appendFileSync(summaryPath, markdown);
124
124
  }
125
125
 
126
+ // ── Preflight reachability check ──────────────────────────────────────────────
127
+
128
+ async function checkTargetReachable(url) {
129
+ try {
130
+ // fetch throws only on network errors (ECONNREFUSED, ETIMEDOUT, DNS failure).
131
+ // HTTP error status codes (4xx/5xx) still mean the server is up — Argus should
132
+ // audit those pages, so we only gate on network-level failures.
133
+ await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(10000) });
134
+ return { ok: true };
135
+ } catch (err) {
136
+ return { ok: false, error: err.message };
137
+ }
138
+ }
139
+
126
140
  // ── Route loader ──────────────────────────────────────────────────────────────
127
141
 
128
142
  async function loadRoutes() {
@@ -217,20 +231,27 @@ async function main() {
217
231
 
218
232
  console.log(`[argus] Auditing ${affected.length} route(s): ${affected.map(r => r.path).join(', ')}`);
219
233
 
220
- // Step 3: Connect to Chrome via the chrome-devtools MCP client
234
+ // Step 3: Verify target is reachable before spending time on Chrome startup
235
+ console.log(`[argus] Verifying target is reachable: ${targetUrl}`);
236
+ const reachable = await checkTargetReachable(targetUrl);
237
+ if (!reachable.ok) {
238
+ throw new Error(`Target URL not reachable (${targetUrl}): ${reachable.error}. Make sure your app is running and accessible from the runner before this action fires.`);
239
+ }
240
+
241
+ // Step 4: Connect to Chrome via the chrome-devtools MCP client
221
242
  console.log('[argus] Connecting to Chrome on port 9222...');
222
243
  mcp = await createMcpClient();
223
244
  console.log('[argus] Chrome connected.');
224
245
 
225
- const baseOrigin = new URL(targetUrl).origin;
226
-
227
- // Step 4: Audit each affected route via crawlRouteCheap
246
+ // Step 5: Audit each affected route via crawlRouteCheap
247
+ // Preserve path prefix (e.g. /project/ in GitHub Pages) — .origin would strip it
248
+ const baseUrl = targetUrl.replace(/\/$/, '');
228
249
  for (const route of affected) {
229
250
  const url = new URL(route.path, targetUrl).href;
230
251
  console.log(`[argus] → Auditing ${url}`);
231
252
 
232
253
  try {
233
- const raw = await crawlRouteCheap(route, baseOrigin, mcp);
254
+ const raw = await crawlRouteCheap(route, baseUrl, mcp);
234
255
  const findings = Array.isArray(raw.errors) ? raw.errors : [];
235
256
  allFindings.push(...findings);
236
257
 
@@ -255,7 +276,7 @@ async function main() {
255
276
  }
256
277
  }
257
278
 
258
- // Step 5: Compute aggregate summary and merge-block decision
279
+ // Step 6: Compute aggregate summary and merge-block decision
259
280
  const summary = {
260
281
  critical: allFindings.filter(f => f.severity === 'critical').length,
261
282
  warning: allFindings.filter(f => f.severity === 'warning').length,
@@ -267,11 +288,11 @@ async function main() {
267
288
  blockOn === 'warning' ? summary.critical + summary.warning > 0 :
268
289
  false;
269
290
 
270
- // Step 6: Write GitHub Actions outputs and step summary
291
+ // Step 7: Write GitHub Actions outputs and step summary
271
292
  writeGithubOutputs({ blocked, summary, affectedRoutes: affected });
272
293
  writeStepSummary(buildStepSummary({ blocked, summary, affectedRoutes: affected, perRoute, findings: allFindings, changedFiles: files, blockOn }));
273
294
 
274
- // Step 7: Emit JSON result to stdout for downstream pipeline steps
295
+ // Step 8: Emit JSON result to stdout for downstream pipeline steps
275
296
  const result = {
276
297
  prUrl, targetUrl,
277
298
  affectedRoutes: affected.map(r => r.path),
package/src/mcp-server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Argus MCP Server (v9.6.2)
3
+ * Argus MCP Server (v9.6.4)
4
4
  *
5
5
  * Exposes Argus as an MCP server so Claude (or any MCP client) can call
6
6
  * argus_audit, argus_audit_full, argus_compare, argus_last_report, and
@@ -447,7 +447,7 @@ async function handleLastReport() {
447
447
  // ── Server bootstrap ──────────────────────────────────────────────────────────
448
448
 
449
449
  const server = new Server(
450
- { name: 'argus', version: '9.6.2' },
450
+ { name: 'argus', version: '9.6.4' },
451
451
  { capabilities: { tools: {} } },
452
452
  );
453
453
 
@@ -57,7 +57,7 @@ const TOOL_TIMEOUT_MS = parseInt(process.env.MCP_TOOL_TIMEOUT_MS ?? '30000', 10)
57
57
  export async function createMcpClient() {
58
58
  // On Windows, npx is npx.cmd — shell:true resolves this cross-platform.
59
59
  const proc = spawn('npx', [
60
- '-y', 'chrome-devtools-mcp@latest',
60
+ '-y', 'chrome-devtools-mcp@1.1.1',
61
61
  `--browser-url=${BROWSER_URL}`,
62
62
  '--headless=true',
63
63
  '--viewport=1920x1080',
@@ -133,6 +133,8 @@ export async function createMcpClient() {
133
133
  capabilities: {},
134
134
  clientInfo: { name: 'argus', version: '1.0.0' },
135
135
  });
136
+ // MCP 2024-11-05 spec: client MUST send this notification before any tool calls
137
+ proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }) + '\n');
136
138
 
137
139
  /**
138
140
  * Call an MCP tool by name with params.
@@ -36,8 +36,6 @@ export function parsePrUrl(prUrl) {
36
36
  */
37
37
  export async function fetchPrFiles(prUrl, githubToken) {
38
38
  const { owner, repo, prNumber } = parsePrUrl(prUrl);
39
- const apiUrl =
40
- `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`;
41
39
  const headers = {
42
40
  Accept: 'application/vnd.github+json',
43
41
  'X-GitHub-Api-Version': '2022-11-28',
@@ -45,15 +43,41 @@ export async function fetchPrFiles(prUrl, githubToken) {
45
43
  ...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
46
44
  };
47
45
 
48
- const res = await fetch(apiUrl, { headers });
49
- if (!res.ok) {
50
- const body = await res.text().catch(() => '');
51
- throw new Error(`GitHub API ${res.status}: ${body || res.statusText}`);
46
+ const allFiles = [];
47
+ const MAX_PAGES = 3; // caps at 300 files; avoids runaway requests on mega-PRs
48
+
49
+ for (let page = 1; page <= MAX_PAGES; page++) {
50
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100&page=${page}`;
51
+ const res = await fetch(apiUrl, { headers });
52
+ if (!res.ok) {
53
+ const body = await res.text().catch(() => '');
54
+ throw new Error(`GitHub API ${res.status}: ${body || res.statusText}`);
55
+ }
56
+ const files = await res.json();
57
+ allFiles.push(...files.map(f => f.filename));
58
+ if (files.length < 100) break; // last page reached
59
+ }
60
+
61
+ if (allFiles.length >= 300) {
62
+ console.log('::warning::PR has 300+ changed files — Argus analyzed the first 300. Routes affected by later files may be missed.');
52
63
  }
53
- const files = await res.json();
54
- return files.map(f => f.filename);
64
+
65
+ return allFiles;
55
66
  }
56
67
 
68
+ /**
69
+ * Files that are never relevant to app routes — CI configs, docs, repo metadata.
70
+ * Changes to ONLY these files cause mapFilesToRoutes to return [] (skip audit).
71
+ */
72
+ const EXCLUDED_PATTERNS = [
73
+ /^\.github\//i,
74
+ /^docs?\//i,
75
+ /\.md$/i,
76
+ /^(LICENSE|CHANGELOG|CONTRIBUTING|CODE_OF_CONDUCT|SECURITY)(\..*)?$/i,
77
+ /^\.gitignore$/i,
78
+ /^\.gitattributes$/i,
79
+ ];
80
+
57
81
  /**
58
82
  * Patterns that indicate an infrastructure-level file whose change can affect
59
83
  * every route — framework configs, root layouts, global stylesheets, package.json.
@@ -88,15 +112,22 @@ export function mapFilesToRoutes(changedFiles, routes) {
88
112
  if (!routes || routes.length === 0) return [];
89
113
  if (!changedFiles || changedFiles.length === 0) return routes;
90
114
 
115
+ // Strip files that are never app-route-relevant (CI configs, docs, repo metadata)
116
+ const appFiles = changedFiles.filter(
117
+ f => !EXCLUDED_PATTERNS.some(re => re.test(f)),
118
+ );
119
+
120
+ // README-only / CI-only PR — skip the audit entirely
121
+ if (appFiles.length === 0) return [];
122
+
91
123
  // Infrastructure change → full audit
92
- if (changedFiles.some(f => INFRA_PATTERNS.some(re => re.test(f)))) {
124
+ if (appFiles.some(f => INFRA_PATTERNS.some(re => re.test(f)))) {
93
125
  return routes;
94
126
  }
95
127
 
96
- // Build a flat set of lowercase slugs from every changed file path
128
+ // Build a flat set of lowercase slugs from app-relevant changed files
97
129
  const fileSlugs = new Set(
98
- changedFiles.flatMap(f =>
99
- // Strip extension, split on separators, keep non-trivial tokens
130
+ appFiles.flatMap(f =>
100
131
  f.toLowerCase()
101
132
  .replace(/\.[^./\\]+$/, '')
102
133
  .split(/[/\\._-]+/)