argusqa-os 9.6.0 → 9.6.2
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/README.md +5 -5
- package/package.json +3 -2
- package/src/cli/pr-validate.js +307 -0
- package/src/mcp-server.js +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/argusqa-os)
|
|
6
6
|
[](https://glama.ai/mcp/servers/ironclawdevs27/Argus)
|
|
7
|
-
[](test-harness/)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
10
|
**Argus catches the bugs your test suite misses — visual regressions, API loops, CSS drift, console noise, accessibility failures, and more — and delivers rich reports to Slack (or a local HTML dashboard).**
|
|
@@ -62,7 +62,7 @@ Argus scans your app and either posts findings to Slack or opens a local `report
|
|
|
62
62
|
|
|
63
63
|
## What Argus Catches
|
|
64
64
|
|
|
65
|
-
31 analysis engines,
|
|
65
|
+
31 analysis engines, 138 distinct issue types, zero test-file maintenance:
|
|
66
66
|
|
|
67
67
|
| Category | What it detects |
|
|
68
68
|
|---|---|
|
|
@@ -208,7 +208,7 @@ npm run report:html # Generate reports/report.html from last JSON audit
|
|
|
208
208
|
npm run server # Start Slack slash-command server (port 3001)
|
|
209
209
|
npm run init # Interactive setup wizard
|
|
210
210
|
npm run test:unit # 61 unit tests — no Chrome required
|
|
211
|
-
npm run test:harness #
|
|
211
|
+
npm run test:harness # 138-block correctness harness — requires Chrome
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
**Watch mode** — live monitoring as you develop:
|
|
@@ -331,7 +331,7 @@ Argus is a **complementary layer**, not a replacement for unit or E2E tests:
|
|
|
331
331
|
|
|
332
332
|
## Known Limitations
|
|
333
333
|
|
|
334
|
-
3 permanent test failures (`
|
|
334
|
+
3 permanent test failures (`641/644`) are MCP-layer restrictions — not fixable in Argus code:
|
|
335
335
|
|
|
336
336
|
| Tool | Constraint |
|
|
337
337
|
|---|---|
|
|
@@ -351,7 +351,7 @@ src/
|
|
|
351
351
|
adapters/browser.js — CdpBrowserAdapter — wraps all chrome-devtools-mcp calls
|
|
352
352
|
config/targets.js — routes, thresholds, auth steps
|
|
353
353
|
cli/init.js — argus init interactive setup wizard
|
|
354
|
-
test-harness/ —
|
|
354
|
+
test-harness/ — 138-block correctness harness, 62 fixture pages
|
|
355
355
|
test/unit/ — 61 Vitest unit tests (no Chrome required)
|
|
356
356
|
landing/ — Product landing page (React 19 + Vite + Tailwind)
|
|
357
357
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argusqa-os",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.2",
|
|
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": [
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"bin": {
|
|
34
34
|
"argus": "src/cli/init.js",
|
|
35
35
|
"argus-mcp": "src/mcp-server.js",
|
|
36
|
-
"argusqa-os": "src/mcp-server.js"
|
|
36
|
+
"argusqa-os": "src/mcp-server.js",
|
|
37
|
+
"argus-pr-validate": "src/cli/pr-validate.js"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"setup": "node -e \"import('fs').then(fs => fs.default.mkdirSync('./reports', { recursive: true }))\"",
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Argus PR Validator — headless CI entry point for GitHub Actions.
|
|
4
|
+
*
|
|
5
|
+
* Environment variables (set by action.yml):
|
|
6
|
+
* ARGUS_PR_URL Full GitHub PR URL, e.g. https://github.com/owner/repo/pull/42 (required)
|
|
7
|
+
* TARGET_DEV_URL Base URL of the running application, e.g. https://staging.example.com (required)
|
|
8
|
+
* ARGUS_BLOCK_ON critical | warning | none (default: critical)
|
|
9
|
+
* GITHUB_TOKEN GitHub PAT or workflow GITHUB_TOKEN — optional for public repos
|
|
10
|
+
* ARGUS_ROUTES_FILE Path to a JSON routes array [{path,name}] — optional, see loadRoutes()
|
|
11
|
+
* GITHUB_OUTPUT Set by GitHub runner — path for step output key=value pairs
|
|
12
|
+
* GITHUB_STEP_SUMMARY Set by GitHub runner — path for markdown step summary
|
|
13
|
+
*
|
|
14
|
+
* Exit codes:
|
|
15
|
+
* 0 — audit passed (blocked=false) or no routes to audit
|
|
16
|
+
* 1 — audit blocked (findings at or above ARGUS_BLOCK_ON threshold) OR startup error
|
|
17
|
+
*
|
|
18
|
+
* Exports (used by test harness — no Chrome required):
|
|
19
|
+
* buildStepSummary(opts) → markdown string
|
|
20
|
+
* writeGithubOutputs(opts) → void (writes to GITHUB_OUTPUT file)
|
|
21
|
+
* writeStepSummary(markdown) → void (writes to GITHUB_STEP_SUMMARY file)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import { createMcpClient } from '../utils/mcp-client.js';
|
|
28
|
+
import { crawlRouteCheap } from '../orchestration/crawl-and-report.js';
|
|
29
|
+
import { parsePrUrl, fetchPrFiles, mapFilesToRoutes } from '../utils/pr-diff-analyzer.js';
|
|
30
|
+
|
|
31
|
+
// ── Exported helpers (testable without Chrome) ────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build a GitHub-flavoured markdown step summary.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} opts
|
|
37
|
+
* @param {boolean} opts.blocked
|
|
38
|
+
* @param {{ critical: number, warning: number, info: number }} opts.summary
|
|
39
|
+
* @param {Array<{ path: string }>} opts.affectedRoutes
|
|
40
|
+
* @param {Array<{ route: string, critical: number, warning: number, info: number, error?: string }>} opts.perRoute
|
|
41
|
+
* @param {Array<object>} opts.findings
|
|
42
|
+
* @param {string[]} opts.changedFiles
|
|
43
|
+
* @param {string} opts.blockOn critical | warning | none
|
|
44
|
+
* @param {string} [opts.error] top-level error message (startup / fetch failure)
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
export function buildStepSummary({ blocked, summary, affectedRoutes, perRoute, findings, changedFiles, blockOn, error }) {
|
|
48
|
+
const icon = blocked ? '🔴' : summary.critical + summary.warning === 0 ? '✅' : '⚠️';
|
|
49
|
+
const status = blocked ? 'BLOCKED — merge prevented' : 'PASSED';
|
|
50
|
+
|
|
51
|
+
let md = `## ${icon} Argus PR Validator — ${status}\n\n`;
|
|
52
|
+
|
|
53
|
+
if (error) {
|
|
54
|
+
md += `> **Error:** ${String(error).replace(/`/g, "'")}\n\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
md += `| Metric | Value |\n|--------|-------|\n`;
|
|
58
|
+
md += `| Block threshold | \`${blockOn}\` |\n`;
|
|
59
|
+
md += `| Critical findings | **${summary.critical}** |\n`;
|
|
60
|
+
md += `| Warning findings | ${summary.warning} |\n`;
|
|
61
|
+
md += `| Info findings | ${summary.info} |\n`;
|
|
62
|
+
md += `| Routes audited | ${affectedRoutes.length} |\n`;
|
|
63
|
+
md += `| Files changed | ${changedFiles.length} |\n\n`;
|
|
64
|
+
|
|
65
|
+
if (perRoute.length > 0) {
|
|
66
|
+
md += `### Route Breakdown\n\n`;
|
|
67
|
+
md += `| Route | 🔴 Critical | ⚠️ Warning | ℹ️ Info |\n|-------|------------|-----------|--------|\n`;
|
|
68
|
+
for (const r of perRoute) {
|
|
69
|
+
const errNote = r.error ? ` _(error: ${String(r.error).slice(0, 60)})_` : '';
|
|
70
|
+
md += `| \`${r.route}\` | ${r.critical} | ${r.warning} | ${r.info}${errNote} |\n`;
|
|
71
|
+
}
|
|
72
|
+
md += '\n';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (findings.length > 0) {
|
|
76
|
+
md += `### Findings\n\n`;
|
|
77
|
+
md += `| Severity | Type | Message | URL |\n|----------|------|---------|-----|\n`;
|
|
78
|
+
const shown = findings.slice(0, 50);
|
|
79
|
+
for (const f of shown) {
|
|
80
|
+
const sev = f.severity === 'critical' ? '🔴 critical'
|
|
81
|
+
: f.severity === 'warning' ? '⚠️ warning'
|
|
82
|
+
: 'ℹ️ info';
|
|
83
|
+
const msg = String(f.message ?? '').replace(/\|/g, '\\|').slice(0, 100);
|
|
84
|
+
const url = String(f.url ?? '').replace(/\|/g, '\\|').slice(0, 80);
|
|
85
|
+
md += `| ${sev} | \`${f.type ?? ''}\` | ${msg} | ${url} |\n`;
|
|
86
|
+
}
|
|
87
|
+
if (findings.length > 50) {
|
|
88
|
+
md += `\n_…and ${findings.length - 50} more findings._\n`;
|
|
89
|
+
}
|
|
90
|
+
md += '\n';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
md += `---\n_Powered by [Argus QA](https://argus-qa.com)_\n`;
|
|
94
|
+
return md;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Write step outputs to $GITHUB_OUTPUT (key=value pairs).
|
|
99
|
+
* No-ops when GITHUB_OUTPUT is not set (local / non-Actions environments).
|
|
100
|
+
*/
|
|
101
|
+
export function writeGithubOutputs({ blocked, summary, affectedRoutes }) {
|
|
102
|
+
const outputPath = process.env.GITHUB_OUTPUT;
|
|
103
|
+
if (!outputPath) return;
|
|
104
|
+
const routes = Array.isArray(affectedRoutes)
|
|
105
|
+
? affectedRoutes.map(r => (typeof r === 'string' ? r : r.path)).join(',')
|
|
106
|
+
: '';
|
|
107
|
+
const lines = [
|
|
108
|
+
`blocked=${blocked}`,
|
|
109
|
+
`critical_count=${summary.critical}`,
|
|
110
|
+
`warning_count=${summary.warning}`,
|
|
111
|
+
`affected_routes=${routes}`,
|
|
112
|
+
].join('\n') + '\n';
|
|
113
|
+
fs.appendFileSync(outputPath, lines);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Append markdown to $GITHUB_STEP_SUMMARY.
|
|
118
|
+
* No-ops when GITHUB_STEP_SUMMARY is not set.
|
|
119
|
+
*/
|
|
120
|
+
export function writeStepSummary(markdown) {
|
|
121
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
122
|
+
if (!summaryPath) return;
|
|
123
|
+
fs.appendFileSync(summaryPath, markdown);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Route loader ──────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
async function loadRoutes() {
|
|
129
|
+
// 1. ARGUS_ROUTES_FILE env var
|
|
130
|
+
const routesFile = process.env.ARGUS_ROUTES_FILE;
|
|
131
|
+
if (routesFile) {
|
|
132
|
+
try {
|
|
133
|
+
const raw = JSON.parse(fs.readFileSync(routesFile, 'utf8'));
|
|
134
|
+
if (Array.isArray(raw) && raw.length > 0) {
|
|
135
|
+
console.log(`[argus] Loaded ${raw.length} route(s) from ${routesFile}`);
|
|
136
|
+
return raw;
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error(`::warning::Could not parse ARGUS_ROUTES_FILE (${routesFile}): ${err.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 2. argus.routes.json in working directory
|
|
144
|
+
const localFile = path.join(process.cwd(), 'argus.routes.json');
|
|
145
|
+
if (fs.existsSync(localFile)) {
|
|
146
|
+
try {
|
|
147
|
+
const raw = JSON.parse(fs.readFileSync(localFile, 'utf8'));
|
|
148
|
+
if (Array.isArray(raw) && raw.length > 0) {
|
|
149
|
+
console.log(`[argus] Loaded ${raw.length} route(s) from argus.routes.json`);
|
|
150
|
+
return raw;
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error(`::warning::Could not parse argus.routes.json: ${err.message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 3. Final fallback — audit root path only
|
|
158
|
+
// NOTE: We deliberately do NOT fall back to the package's targets.js here.
|
|
159
|
+
// targets.js contains the developer's own demo routes with app-specific
|
|
160
|
+
// waitFor selectors (e.g. [data-testid="dashboard"]) that do not exist on
|
|
161
|
+
// user apps — falling back to them causes false-positive load_failure
|
|
162
|
+
// findings and incorrectly blocks merges.
|
|
163
|
+
console.log('[argus] No routes configured — falling back to root path audit');
|
|
164
|
+
console.log('[argus] Tip: add argus.routes.json to your repo root to audit specific routes.');
|
|
165
|
+
return [{ path: '/', name: 'home' }];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
// Guard: run main() only when this script is executed directly, not when imported.
|
|
171
|
+
const _thisFile = fileURLToPath(import.meta.url);
|
|
172
|
+
if (process.argv[1] === _thisFile) {
|
|
173
|
+
await main();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function main() {
|
|
177
|
+
const prUrl = process.env.ARGUS_PR_URL;
|
|
178
|
+
const targetUrl = process.env.TARGET_DEV_URL ?? 'http://localhost:3000';
|
|
179
|
+
const blockOn = (process.env.ARGUS_BLOCK_ON ?? 'critical').toLowerCase().trim();
|
|
180
|
+
const token = process.env.GITHUB_TOKEN;
|
|
181
|
+
|
|
182
|
+
// Input validation
|
|
183
|
+
if (!prUrl) {
|
|
184
|
+
console.error('::error::ARGUS_PR_URL is not set. Set it to the full GitHub PR URL (e.g. https://github.com/owner/repo/pull/42).');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
if (!['none', 'warning', 'critical'].includes(blockOn)) {
|
|
188
|
+
console.error(`::error::ARGUS_BLOCK_ON must be none | warning | critical, got: "${blockOn}"`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let mcp;
|
|
193
|
+
const changedFiles = [];
|
|
194
|
+
const affectedRoutes = [];
|
|
195
|
+
const allFindings = [];
|
|
196
|
+
const perRoute = [];
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
// Step 1: Fetch the PR file list from GitHub
|
|
200
|
+
console.log(`[argus] Fetching PR diff: ${prUrl}`);
|
|
201
|
+
const files = await fetchPrFiles(prUrl, token);
|
|
202
|
+
changedFiles.push(...files);
|
|
203
|
+
console.log(`[argus] ${files.length} changed file(s)`);
|
|
204
|
+
|
|
205
|
+
// Step 2: Map changed files to affected routes
|
|
206
|
+
const routes = await loadRoutes();
|
|
207
|
+
const affected = mapFilesToRoutes(files, routes);
|
|
208
|
+
affectedRoutes.push(...affected);
|
|
209
|
+
|
|
210
|
+
if (affected.length === 0) {
|
|
211
|
+
console.log('[argus] No affected routes resolved — skipping audit');
|
|
212
|
+
const summary = { critical: 0, warning: 0, info: 0 };
|
|
213
|
+
writeGithubOutputs({ blocked: false, summary, affectedRoutes: [] });
|
|
214
|
+
writeStepSummary(buildStepSummary({ blocked: false, summary, affectedRoutes: [], perRoute: [], findings: [], changedFiles: files, blockOn }));
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(`[argus] Auditing ${affected.length} route(s): ${affected.map(r => r.path).join(', ')}`);
|
|
219
|
+
|
|
220
|
+
// Step 3: Connect to Chrome via the chrome-devtools MCP client
|
|
221
|
+
console.log('[argus] Connecting to Chrome on port 9222...');
|
|
222
|
+
mcp = await createMcpClient();
|
|
223
|
+
console.log('[argus] Chrome connected.');
|
|
224
|
+
|
|
225
|
+
const baseOrigin = new URL(targetUrl).origin;
|
|
226
|
+
|
|
227
|
+
// Step 4: Audit each affected route via crawlRouteCheap
|
|
228
|
+
for (const route of affected) {
|
|
229
|
+
const url = new URL(route.path, targetUrl).href;
|
|
230
|
+
console.log(`[argus] → Auditing ${url}`);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const raw = await crawlRouteCheap(route, baseOrigin, mcp);
|
|
234
|
+
const findings = Array.isArray(raw.errors) ? raw.errors : [];
|
|
235
|
+
allFindings.push(...findings);
|
|
236
|
+
|
|
237
|
+
const critical = findings.filter(f => f.severity === 'critical').length;
|
|
238
|
+
const warning = findings.filter(f => f.severity === 'warning').length;
|
|
239
|
+
const info = findings.filter(f => f.severity === 'info').length;
|
|
240
|
+
perRoute.push({ route: route.path, critical, warning, info });
|
|
241
|
+
|
|
242
|
+
console.log(`[argus] ${url}: ${critical} critical, ${warning} warning, ${info} info`);
|
|
243
|
+
|
|
244
|
+
// Emit inline GitHub Actions annotations for visible CI feedback
|
|
245
|
+
for (const f of findings.filter(g => g.severity === 'critical')) {
|
|
246
|
+
console.log(`::error::${String(f.message ?? '').replace(/\n/g, ' ')} [${f.type}] on ${url}`);
|
|
247
|
+
}
|
|
248
|
+
for (const f of findings.filter(g => g.severity === 'warning')) {
|
|
249
|
+
console.log(`::warning::${String(f.message ?? '').replace(/\n/g, ' ')} [${f.type}] on ${url}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
} catch (routeErr) {
|
|
253
|
+
console.error(`::warning::Audit failed for ${url}: ${routeErr.message}`);
|
|
254
|
+
perRoute.push({ route: route.path, critical: 0, warning: 0, info: 0, error: routeErr.message });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Step 5: Compute aggregate summary and merge-block decision
|
|
259
|
+
const summary = {
|
|
260
|
+
critical: allFindings.filter(f => f.severity === 'critical').length,
|
|
261
|
+
warning: allFindings.filter(f => f.severity === 'warning').length,
|
|
262
|
+
info: allFindings.filter(f => f.severity === 'info').length,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const blocked =
|
|
266
|
+
blockOn === 'critical' ? summary.critical > 0 :
|
|
267
|
+
blockOn === 'warning' ? summary.critical + summary.warning > 0 :
|
|
268
|
+
false;
|
|
269
|
+
|
|
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 }));
|
|
273
|
+
|
|
274
|
+
// Step 7: Emit JSON result to stdout for downstream pipeline steps
|
|
275
|
+
const result = {
|
|
276
|
+
prUrl, targetUrl,
|
|
277
|
+
affectedRoutes: affected.map(r => r.path),
|
|
278
|
+
changedFiles: files,
|
|
279
|
+
findings: allFindings,
|
|
280
|
+
perRoute,
|
|
281
|
+
summary,
|
|
282
|
+
blocked,
|
|
283
|
+
blockOn,
|
|
284
|
+
};
|
|
285
|
+
console.log(JSON.stringify(result, null, 2));
|
|
286
|
+
|
|
287
|
+
if (blocked) {
|
|
288
|
+
console.error(`::error::Argus PR Validator: ${summary.critical} critical finding(s) found. Merge blocked (block-on=${blockOn}).`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(`[argus] ✓ Audit passed — ${summary.critical} critical, ${summary.warning} warning, ${summary.info} info.`);
|
|
293
|
+
process.exit(0);
|
|
294
|
+
|
|
295
|
+
} catch (err) {
|
|
296
|
+
const summary = { critical: 0, warning: 0, info: 0 };
|
|
297
|
+
console.error(`::error::Argus PR validation failed: ${err.message}`);
|
|
298
|
+
writeGithubOutputs({ blocked: false, summary, affectedRoutes: [] });
|
|
299
|
+
writeStepSummary(buildStepSummary({ blocked: false, summary, affectedRoutes: [], perRoute: [], findings: [], changedFiles, blockOn, error: err.message }));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
|
|
302
|
+
} finally {
|
|
303
|
+
if (mcp) {
|
|
304
|
+
try { mcp.close(); } catch { /* ignore teardown errors */ }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
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.2)
|
|
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.2' },
|
|
451
451
|
{ capabilities: { tools: {} } },
|
|
452
452
|
);
|
|
453
453
|
|