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 +1 -1
- package/src/cli/pr-validate.js +56 -12
- package/src/mcp-server.js +2 -2
- package/src/utils/mcp-client.js +2 -0
package/package.json
CHANGED
package/src/cli/pr-validate.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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,
|
|
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
|
-
//
|
|
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
|
|
271
|
-
writeGithubOutputs({ blocked, summary, affectedRoutes:
|
|
272
|
-
writeStepSummary(buildStepSummary({ blocked, summary, affectedRoutes:
|
|
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
|
|
318
|
+
// Step 8: Emit JSON result to stdout for downstream pipeline steps
|
|
275
319
|
const result = {
|
|
276
320
|
prUrl, targetUrl,
|
|
277
|
-
affectedRoutes:
|
|
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
|
+
* 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.
|
|
450
|
+
{ name: 'argus', version: '9.6.5' },
|
|
451
451
|
{ capabilities: { tools: {} } },
|
|
452
452
|
);
|
|
453
453
|
|
package/src/utils/mcp-client.js
CHANGED
|
@@ -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.
|