argusqa-os 9.6.3 → 9.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argusqa-os",
3
- "version": "9.6.3",
3
+ "version": "9.6.5",
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,38 @@ 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;
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(/\/$/, '');
249
+
250
+ // Normalize route paths — crawlRouteCheap builds URLs via string concat (baseUrl + route.path)
251
+ // so paths without a leading slash produce malformed URLs like https://example.comlogin
252
+ const normalizedAffected = affected.map(r => {
253
+ if (!r.path.startsWith('/')) {
254
+ console.log(`::warning::Route path "${r.path}" has no leading slash — normalizing to "/${r.path}". Update argus.routes.json to use a leading slash.`);
255
+ return { ...r, path: `/${r.path}` };
256
+ }
257
+ return r;
258
+ });
226
259
 
227
- // Step 4: Audit each affected route via crawlRouteCheap
228
- for (const route of affected) {
229
- const url = new URL(route.path, targetUrl).href;
260
+ for (const route of normalizedAffected) {
261
+ const url = `${baseUrl}${route.path}`;
230
262
  console.log(`[argus] → Auditing ${url}`);
231
263
 
232
264
  try {
233
- const raw = await crawlRouteCheap(route, baseOrigin, mcp);
265
+ const raw = await crawlRouteCheap(route, baseUrl, mcp);
234
266
  const findings = Array.isArray(raw.errors) ? raw.errors : [];
235
267
  allFindings.push(...findings);
236
268
 
@@ -255,7 +287,19 @@ async function main() {
255
287
  }
256
288
  }
257
289
 
258
- // Step 5: Compute aggregate summary and merge-block decision
290
+ // Guard: if every route failed with an exception, the app was unreachable after
291
+ // the preflight check (e.g. race condition where app died between check and crawl).
292
+ // Throwing here causes the step to exit 1, which correctly blocks the merge.
293
+ const routeFailCount = perRoute.filter(r => r.error).length;
294
+ if (routeFailCount > 0 && routeFailCount === perRoute.length) {
295
+ throw new Error(
296
+ `All ${perRoute.length} route audit(s) failed — Chrome could not reach the app. ` +
297
+ `Ensure TARGET_DEV_URL is accessible throughout the job. ` +
298
+ `First error: ${perRoute[0].error}`,
299
+ );
300
+ }
301
+
302
+ // Step 6: Compute aggregate summary and merge-block decision
259
303
  const summary = {
260
304
  critical: allFindings.filter(f => f.severity === 'critical').length,
261
305
  warning: allFindings.filter(f => f.severity === 'warning').length,
@@ -267,14 +311,14 @@ async function main() {
267
311
  blockOn === 'warning' ? summary.critical + summary.warning > 0 :
268
312
  false;
269
313
 
270
- // Step 6: Write GitHub Actions outputs and step summary
271
- writeGithubOutputs({ blocked, summary, affectedRoutes: affected });
272
- writeStepSummary(buildStepSummary({ blocked, summary, affectedRoutes: affected, perRoute, findings: allFindings, changedFiles: files, blockOn }));
314
+ // Step 7: Write GitHub Actions outputs and step summary
315
+ writeGithubOutputs({ blocked, summary, affectedRoutes: normalizedAffected });
316
+ writeStepSummary(buildStepSummary({ blocked, summary, affectedRoutes: normalizedAffected, perRoute, findings: allFindings, changedFiles: files, blockOn }));
273
317
 
274
- // Step 7: Emit JSON result to stdout for downstream pipeline steps
318
+ // Step 8: Emit JSON result to stdout for downstream pipeline steps
275
319
  const result = {
276
320
  prUrl, targetUrl,
277
- affectedRoutes: affected.map(r => r.path),
321
+ affectedRoutes: normalizedAffected.map(r => r.path),
278
322
  changedFiles: files,
279
323
  findings: allFindings,
280
324
  perRoute,
package/src/mcp-server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Argus MCP Server (v9.6.3)
3
+ * Argus MCP Server (v9.6.5)
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.3' },
450
+ { name: 'argus', version: '9.6.5' },
451
451
  { capabilities: { tools: {} } },
452
452
  );
453
453
 
@@ -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.