argusqa-os 9.2.0

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.
Files changed (57) hide show
  1. package/.mcp.json +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +879 -0
  4. package/package.json +69 -0
  5. package/src/adapters/browser.js +82 -0
  6. package/src/argus.js +8 -0
  7. package/src/batch-runner.js +8 -0
  8. package/src/cli/init.js +314 -0
  9. package/src/config/schema.js +108 -0
  10. package/src/config/targets.js +309 -0
  11. package/src/domain/finding.js +25 -0
  12. package/src/mcp-server.js +156 -0
  13. package/src/orchestration/crawl-and-report.js +16 -0
  14. package/src/orchestration/dispatcher.js +263 -0
  15. package/src/orchestration/env-comparison.js +498 -0
  16. package/src/orchestration/orchestrator.js +1128 -0
  17. package/src/orchestration/report-processor.js +134 -0
  18. package/src/orchestration/slack-notifier.js +337 -0
  19. package/src/orchestration/watch-mode.js +316 -0
  20. package/src/registry.js +18 -0
  21. package/src/server/index.js +94 -0
  22. package/src/server/interaction-handler.js +126 -0
  23. package/src/server/slash-command-handler.js +185 -0
  24. package/src/utils/api-frequency.js +128 -0
  25. package/src/utils/baseline-manager.js +255 -0
  26. package/src/utils/codebase-analyzer.js +299 -0
  27. package/src/utils/content-analyzer.js +155 -0
  28. package/src/utils/contract-validator.js +178 -0
  29. package/src/utils/css-analyzer.js +407 -0
  30. package/src/utils/diff.js +189 -0
  31. package/src/utils/flakiness-detector.js +82 -0
  32. package/src/utils/flow-runner.js +572 -0
  33. package/src/utils/github-reporter.js +310 -0
  34. package/src/utils/hover-analyzer.js +214 -0
  35. package/src/utils/html-reporter.js +301 -0
  36. package/src/utils/issues-analyzer.js +171 -0
  37. package/src/utils/keyboard-analyzer.js +141 -0
  38. package/src/utils/lighthouse-checker.js +120 -0
  39. package/src/utils/logger.js +39 -0
  40. package/src/utils/login-orchestrator.js +99 -0
  41. package/src/utils/mcp-client.js +264 -0
  42. package/src/utils/mcp-parsers.js +57 -0
  43. package/src/utils/memory-analyzer.js +270 -0
  44. package/src/utils/network-timing-analyzer.js +76 -0
  45. package/src/utils/parallel-crawler.js +28 -0
  46. package/src/utils/responsive-analyzer.js +253 -0
  47. package/src/utils/retry.js +36 -0
  48. package/src/utils/route-discoverer.js +306 -0
  49. package/src/utils/security-analyzer.js +302 -0
  50. package/src/utils/seo-analyzer.js +164 -0
  51. package/src/utils/session-manager.js +12 -0
  52. package/src/utils/session-persistence.js +214 -0
  53. package/src/utils/severity-overrides.js +91 -0
  54. package/src/utils/slack-guard.js +18 -0
  55. package/src/utils/slug.js +8 -0
  56. package/src/utils/snapshot-analyzer.js +330 -0
  57. package/src/utils/telemetry.js +190 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * ARGUS Target Configuration
3
+ *
4
+ * Define which URLs to test, what flows to check, and per-route settings.
5
+ * Claude Code reads this file when building test runs.
6
+ */
7
+
8
+ import { childLogger } from '../utils/logger.js';
9
+ const logger = childLogger('targets');
10
+
11
+ export const config = {
12
+ /** Milliseconds to wait after navigation before capturing state */
13
+ pageSettleMs: 2000,
14
+
15
+ /** Screenshot quality (1–100) */
16
+ screenshotQuality: 90,
17
+
18
+ /** Pixel diff % above which a visual change is flagged */
19
+ screenshotDiffThreshold: parseFloat(process.env.SCREENSHOT_DIFF_THRESHOLD ?? '0.5'),
20
+
21
+ /** Directory to write reports and screenshots */
22
+ outputDir: process.env.REPORT_OUTPUT_DIR ?? './reports',
23
+ };
24
+
25
+ /**
26
+ * Centralized detection thresholds (v9.1.5).
27
+ * All magic-number limits across analyzers live here — change once, apply everywhere.
28
+ * Override individual values in your fork; never edit analyzer source files for tuning.
29
+ */
30
+ export const thresholds = {
31
+ perf: {
32
+ LCP: 2500, // ms — Largest Contentful Paint
33
+ CLS: 0.1, // Cumulative Layout Shift
34
+ FID: 100, // ms — First Input Delay (approx via TBT)
35
+ TTFB: 800, // ms — Time to First Byte
36
+ },
37
+ network: {
38
+ slowWarning: 1000, // ms — API response time warning
39
+ slowCritical: 3000, // ms — API response time critical
40
+ sizeWarning: 500 * 1024, // bytes — payload size warning (500 KB)
41
+ sizeCritical: 2 * 1024 * 1024, // bytes — payload size critical (2 MB)
42
+ },
43
+ memory: {
44
+ detachedWarning: 10, // detached DOM nodes → warning
45
+ detachedCritical: 100, // detached DOM nodes → critical
46
+ heapGrowthWarning: 2 * 1024 * 1024, // bytes heap growth → warning (2 MB)
47
+ heapGrowthCritical: 10 * 1024 * 1024, // bytes heap growth → critical (10 MB)
48
+ },
49
+ hover: {
50
+ waitMs: 350, // ms to wait after hover before checking DOM state
51
+ maxDropdowns: 8, // max [aria-haspopup] elements to test per page
52
+ maxTooltips: 5, // max [data-tooltip] elements to test per page
53
+ },
54
+ security: {
55
+ headTimeoutMs: 3000, // ms — HEAD fetch timeout for CSP/XFrame header check
56
+ },
57
+ apiFrequency: {
58
+ warningCount: 3, // API called ≥ this many times → warning
59
+ criticalCount: 5, // API called ≥ this many times → critical
60
+ },
61
+ lighthouse: {
62
+ accessibility: { critical: 50, warning: 90 },
63
+ performance: { critical: 50, warning: 90 },
64
+ seo: { critical: 50, warning: 90 },
65
+ 'best-practices': { critical: 50, warning: 90 },
66
+ },
67
+ };
68
+
69
+ /**
70
+ * Routes to test in crawl-and-report.js (error detection).
71
+ * Add every key page your application serves.
72
+ *
73
+ * Fields:
74
+ * path — URL path appended to the base URL
75
+ * name — human-readable label for reports
76
+ * critical — if true, any error on this route is escalated to 'critical'
77
+ * waitFor — optional CSS selector to wait for before capturing (signals page ready)
78
+ */
79
+ export const routes = [
80
+ { path: '/', name: 'Home', critical: true, waitFor: 'main' },
81
+ { path: '/login', name: 'Login', critical: true, waitFor: 'form' },
82
+ { path: '/dashboard', name: 'Dashboard', critical: true, waitFor: '[data-testid="dashboard"]' },
83
+ { path: '/settings', name: 'Settings', critical: false, waitFor: null },
84
+ // Add more routes here as your app grows
85
+ ];
86
+
87
+ /**
88
+ * Comparison route pairs for env-comparison.js.
89
+ * Each entry maps a dev path to the equivalent staging path (usually the same).
90
+ */
91
+ export const comparisonRoutes = [
92
+ { path: '/', name: 'Home' },
93
+ { path: '/login', name: 'Login' },
94
+ { path: '/dashboard', name: 'Dashboard' },
95
+ ];
96
+
97
+ /**
98
+ * API endpoints to validate response shapes against (D7.4).
99
+ * Each entry is checked against captured network requests on every crawled route.
100
+ *
101
+ * Fields:
102
+ * url — path (e.g. '/api/user') or full URL for exact match
103
+ * method — HTTP method to match (optional — omit to match any method)
104
+ * schema — inline JSON Schema object (preferred)
105
+ * schemaFile — path to a JSON file containing the schema (alternative to schema)
106
+ *
107
+ * Supported schema keywords: type, required, properties, items.
108
+ *
109
+ * Violations are emitted as api_contract_violation warnings in the report.
110
+ *
111
+ * Examples:
112
+ * { url: '/api/user', method: 'GET', schema: { type: 'object', required: ['id', 'name'], properties: { id: { type: 'number' }, name: { type: 'string' } } } }
113
+ * { url: '/api/products', method: 'GET', schemaFile: './schemas/products.json' }
114
+ */
115
+ export const apiContracts = [
116
+ // Uncomment and configure to validate API response shapes:
117
+ // { url: '/api/user', method: 'GET', schema: { type: 'object', required: ['id', 'name'], properties: { id: { type: 'number' }, name: { type: 'string' } } } },
118
+ // { url: '/api/products', method: 'GET', schemaFile: './schemas/products.json' },
119
+ ];
120
+
121
+ /**
122
+ * Severity policy overrides (D7.5).
123
+ * Post-processes all findings before Slack routing, letting teams adjust or
124
+ * silence specific detection types without editing analyzer source code.
125
+ *
126
+ * Keys are finding type strings (e.g. 'seo_missing_description').
127
+ * Values are one of: 'critical' | 'warning' | 'info' | 'suppress'
128
+ * 'suppress' removes the finding entirely from the report and Slack alerts.
129
+ *
130
+ * Examples:
131
+ * seo_missing_description: 'info' — demote noisy SEO finding to info
132
+ * cache_headers_missing: 'suppress' — silence entirely on this project
133
+ * redirect_chain: 'warning' — keep at warning (already is; no-op)
134
+ */
135
+ export const severityOverrides = {
136
+ // seo_missing_description: 'info',
137
+ // cache_headers_missing: 'suppress',
138
+ };
139
+
140
+ /**
141
+ * Auth session persistence (v3 Phase B2 / D7.6).
142
+ *
143
+ * When set, runCrawl() runs the login flow once before crawling, saves the
144
+ * session state (cookies + localStorage), and restores it before each route.
145
+ * This unlocks crawling of authenticated routes without re-logging in per page.
146
+ *
147
+ * D7.6 mid-run refresh: before each route, if the saved session has less than
148
+ * `sessionRefreshWindowMs` of validity remaining, the login flow is re-run
149
+ * automatically so long crawls never fail due to an expired auth cookie.
150
+ *
151
+ * Credentials MUST come from environment variables — never hardcode them here.
152
+ * Add ARGUS_AUTH_EMAIL and ARGUS_AUTH_PASSWORD to your .env file.
153
+ *
154
+ * Fields:
155
+ * sessionFile — path for the saved session JSON (default: .argus-session.json)
156
+ * sessionMaxAgeMs — max session lifetime before forcing re-login (default: 1 h)
157
+ * sessionRefreshWindowMs — refresh when this many ms remain before expiry (default: 5 min)
158
+ * steps — login flow steps (navigate, fill, click, waitFor, sleep)
159
+ *
160
+ * Set to null to disable auth (public crawl only).
161
+ */
162
+ export const auth = null;
163
+
164
+ /**
165
+ * User flow definitions (v3 Phase B5).
166
+ *
167
+ * Each flow is a named sequence of steps executed end-to-end by flow-runner.js.
168
+ * Supported actions:
169
+ * navigate — { action: 'navigate', path: '/route' } or url: 'https://...' for absolute
170
+ * fill — { action: 'fill', selector: 'input#email', value: 'x@y.com' }
171
+ * Add typing: true to dispatch real keyboard events (needed for input-validation handlers)
172
+ * click — { action: 'click', selector: 'button[type=submit]' }
173
+ * press_key — { action: 'press_key', key: 'Tab' | 'Enter' | 'Escape' | 'ArrowDown' | ... }
174
+ * drag — { action: 'drag', sourceSelector: '.card', targetSelector: '.dropzone' }
175
+ * upload_file — { action: 'upload_file', selector: 'input[type=file]', filePath: '/abs/path' }
176
+ * select_option — { action: 'select_option', selector: 'select#country', value: 'US' }
177
+ * waitFor — { action: 'waitFor', selector: '#results', timeout: 10000 }
178
+ * sleep — { action: 'sleep', ms: 500 } (avoid; use waitFor instead)
179
+ * handle_dialog — { action: 'handle_dialog', accept: true } or accept: false
180
+ * assert — { action: 'assert', type: '...' } — see types below
181
+ *
182
+ * Assert types: no_console_errors, no_network_errors, element_visible, element_not_visible,
183
+ * url_contains, no_js_errors
184
+ *
185
+ * Set to [] to disable (default).
186
+ */
187
+ export const flows = [];
188
+
189
+ // Uncomment and configure to test user journeys:
190
+ // export const flows = [
191
+ // {
192
+ // name: 'Login flow',
193
+ // steps: [
194
+ // { action: 'navigate', path: '/login' },
195
+ // { action: 'fill', selector: '#email', value: process.env.ARGUS_AUTH_EMAIL ?? '' },
196
+ // { action: 'fill', selector: '#password', value: process.env.ARGUS_AUTH_PASSWORD ?? '' },
197
+ // { action: 'click', selector: 'button[type="submit"]' },
198
+ // { action: 'waitFor', selector: '[data-testid="dashboard"]', timeout: 15000 },
199
+ // { action: 'assert', type: 'no_console_errors' },
200
+ // { action: 'assert', type: 'url_contains', value: '/dashboard' },
201
+ // ],
202
+ // },
203
+ // {
204
+ // name: 'Checkout flow',
205
+ // steps: [
206
+ // { action: 'navigate', path: '/cart' },
207
+ // { action: 'click', selector: '[data-testid="checkout-btn"]' },
208
+ // { action: 'waitFor', selector: '[data-testid="payment-form"]' },
209
+ // { action: 'assert', type: 'no_network_errors' },
210
+ // { action: 'assert', type: 'element_visible', selector: '[data-testid="order-summary"]' },
211
+ // ],
212
+ // },
213
+ // {
214
+ // name: 'Advanced interactions',
215
+ // steps: [
216
+ // { action: 'navigate', path: '/upload' },
217
+ // // Upload a file — resolves the file input by selector
218
+ // { action: 'upload_file', selector: 'input[type="file"]', filePath: '/tmp/test-file.pdf' },
219
+ // // Select a dropdown option
220
+ // { action: 'navigate', path: '/settings' },
221
+ // { action: 'select_option', selector: 'select#country', value: 'US' },
222
+ // // Drag and drop
223
+ // { action: 'navigate', path: '/kanban' },
224
+ // { action: 'drag', sourceSelector: '[data-testid="card-1"]', targetSelector: '[data-testid="done-column"]' },
225
+ // // Keyboard navigation test
226
+ // { action: 'navigate', path: '/menu' },
227
+ // { action: 'press_key', key: 'Tab' },
228
+ // { action: 'press_key', key: 'Enter' },
229
+ // { action: 'assert', type: 'element_visible', selector: '[data-testid="submenu"]' },
230
+ // // Type with real keyboard events (for inputs with keydown validators)
231
+ // { action: 'fill', selector: 'input[name="search"]', value: 'hello', typing: true },
232
+ // ],
233
+ // },
234
+ // ];
235
+
236
+ /**
237
+ * Codebase cross-reference (Phase C1).
238
+ *
239
+ * Point sourceDir at your app's source code to enable:
240
+ * - env_var_missing — process.env.X used in code but absent from .env files
241
+ * - feature_flag_leakage — conditional env var that is falsy/unset in .env
242
+ * - error_source_linked — console error stack trace parsed to file:line (info)
243
+ * - dead_route — internal nav link that returns 404
244
+ *
245
+ * Set to null to disable (default).
246
+ */
247
+ export const codebase = {
248
+ sourceDir: process.env.ARGUS_SOURCE_DIR ?? null,
249
+ envFile: process.env.ARGUS_ENV_FILE ?? null,
250
+ };
251
+
252
+ // Warn at startup when codebase analysis is unconfigured so operators know
253
+ // env_var_missing / feature_flag_leakage detections will be silently skipped.
254
+ if (!codebase.sourceDir) {
255
+ logger.warn('[ARGUS] codebase.sourceDir not configured — codebase analysis will be skipped (set ARGUS_SOURCE_DIR to enable)');
256
+ }
257
+
258
+ /**
259
+ * Auto route discovery (Phase C3).
260
+ *
261
+ * Augments the manual routes[] above with paths discovered from:
262
+ * sitemap — fetches /sitemap.xml from the base URL; follows one level of sitemap index
263
+ * nextjs — scans pages/ (Next 12) and app/ (Next 13+) under codebase.sourceDir
264
+ * reactRouter — greps JS/TS source for <Route path="..."> and { path: "..." } patterns
265
+ *
266
+ * Discovered routes get: critical: false, waitFor: null, discovered: true.
267
+ * Manual route config (critical, waitFor, name) is always preserved.
268
+ * Set to null to disable auto-discovery entirely.
269
+ */
270
+ export const autoDiscover = {
271
+ sitemap: true, // fetch /sitemap.xml from BASE_URL
272
+ nextjs: true, // scan pages/ + app/ under codebase.sourceDir (if set)
273
+ reactRouter: false, // grep source for React Router path declarations (experimental)
274
+ };
275
+
276
+ /**
277
+ * GitHub PR integration (Phase C2).
278
+ *
279
+ * All configuration is via environment variables — no settings object needed here.
280
+ *
281
+ * Required env vars (set in .env or GitHub Actions secrets):
282
+ * GITHUB_TOKEN — personal access token or Actions GITHUB_TOKEN
283
+ * GITHUB_REPOSITORY — "owner/repo" (set automatically in GitHub Actions)
284
+ *
285
+ * Optional env vars:
286
+ * GITHUB_SHA — commit SHA for status checks (auto in GitHub Actions)
287
+ * GITHUB_PR_NUMBER — PR number; add to your workflow:
288
+ * env:
289
+ * GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
290
+ * ARGUS_REPORT_URL — URL to the full HTML report; linked from the commit status check
291
+ *
292
+ * When GITHUB_TOKEN + GITHUB_REPOSITORY are present, Argus will:
293
+ * - Post (or update) a findings summary comment on the open PR
294
+ * - Set a commit status check: 'failure' if new criticals exist, 'success' otherwise
295
+ */
296
+
297
+ // Uncomment and configure for authenticated crawls:
298
+ // export const auth = {
299
+ // sessionFile: '.argus-session.json',
300
+ // sessionMaxAgeMs: 60 * 60 * 1000, // 1 hour — re-login after this
301
+ // sessionRefreshWindowMs: 5 * 60 * 1000, // refresh when < 5 min remain (D7.6)
302
+ // steps: [
303
+ // { action: 'navigate', path: '/login' },
304
+ // { action: 'fill', selector: '#email', value: process.env.ARGUS_AUTH_EMAIL ?? '' },
305
+ // { action: 'fill', selector: '#password', value: process.env.ARGUS_AUTH_PASSWORD ?? '' },
306
+ // { action: 'click', selector: 'button[type="submit"]' },
307
+ // { action: 'waitFor', selector: '[data-testid="dashboard"]', timeout: 15000 },
308
+ // ],
309
+ // };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Finding factory — enforces required fields and valid severity at creation time.
3
+ *
4
+ * All analyzer modules should build findings with createFinding() instead of
5
+ * raw object literals so missing fields throw at dev time rather than silently
6
+ * producing malformed reports.
7
+ *
8
+ * Object.freeze() prevents accidental mutation as findings pass through the
9
+ * dedup, severity-override, and baseline-diff pipeline.
10
+ */
11
+
12
+ const VALID_SEVERITIES = new Set(['critical', 'warning', 'info']);
13
+
14
+ /**
15
+ * Create an immutable finding object.
16
+ *
17
+ * @param {{ type: string, severity: string, message: string, url?: string }} opts
18
+ * @returns {Readonly<object>}
19
+ */
20
+ export function createFinding({ type, severity, message, url = '', ...rest }) {
21
+ if (!type) throw new Error(`Finding missing: type`);
22
+ if (!VALID_SEVERITIES.has(severity)) throw new Error(`Invalid severity "${severity}" for type "${type}"`);
23
+ if (!message) throw new Error(`Finding missing: message`);
24
+ return Object.freeze({ type, severity, message, url, ...rest });
25
+ }
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Argus MCP Server (v9.2.0)
4
+ *
5
+ * Exposes Argus as an MCP server so Claude (or any MCP client) can call
6
+ * argus_audit, argus_audit_full, argus_compare, and argus_last_report
7
+ * directly from a conversation without using the CLI.
8
+ *
9
+ * Architecture: MCP-inside-MCP
10
+ * Claude (MCP client)
11
+ * → Argus MCP Server (this file)
12
+ * → chrome-devtools-mcp client (mcp-client.js)
13
+ * → Chrome (CDP)
14
+ *
15
+ * Registration: add to .mcp.json as "argus" server.
16
+ * Run standalone: node src/mcp-server.js
17
+ */
18
+
19
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
20
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
+ import {
22
+ ListToolsRequestSchema,
23
+ CallToolRequestSchema,
24
+ } from '@modelcontextprotocol/sdk/types.js';
25
+ import fs from 'fs';
26
+ import path from 'path';
27
+
28
+ import { createMcpClient } from './utils/mcp-client.js';
29
+ import { crawlRouteCheap, runCrawl } from './orchestration/crawl-and-report.js';
30
+ import { runComparison } from './orchestration/env-comparison.js';
31
+
32
+ const REPORTS_DIR = path.resolve(process.cwd(), 'reports');
33
+
34
+ // ── Tool definitions ─────────────────────────────────────────────────────────
35
+
36
+ const TOOLS = [
37
+ {
38
+ name: 'argus_audit',
39
+ description: 'Run a quick (cheap) QA pass on a URL. Returns findings as JSON.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ url: { type: 'string', description: 'Full URL to audit (e.g. http://localhost:3000/checkout)' },
44
+ critical: { type: 'boolean', description: 'Treat this route as critical — console errors become critical severity', default: false },
45
+ },
46
+ required: ['url'],
47
+ },
48
+ },
49
+ {
50
+ name: 'argus_audit_full',
51
+ description: 'Run a deep QA pass on a URL using all analyzers — Lighthouse performance/accessibility scoring, responsive layout checks across mobile/tablet/desktop viewports, memory leak detection via heap snapshot, hover-state bug detection, and accessibility tree snapshot. Returns a full JSON report with findings grouped by severity.',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {
55
+ url: { type: 'string', description: 'Full URL to audit (e.g. https://example.com/dashboard)' },
56
+ critical: { type: 'boolean', description: 'Mark this route as critical — console errors are escalated to critical severity', default: false },
57
+ },
58
+ required: ['url'],
59
+ },
60
+ },
61
+ {
62
+ name: 'argus_compare',
63
+ description: 'Snapshot and diff two environments (dev vs staging) side-by-side. Navigates both URLs, captures screenshots, runs the full analyzer suite on each, then diffs the findings to surface regressions — things that appear in staging but not dev, or changed severity. Configure the two target URLs via TARGET_DEV_URL and TARGET_STAGING_URL environment variables before starting the server.',
64
+ inputSchema: { type: 'object', properties: {} },
65
+ },
66
+ {
67
+ name: 'argus_last_report',
68
+ description: 'Return the most recent Argus JSON report from the reports/ directory.',
69
+ inputSchema: { type: 'object', properties: {} },
70
+ },
71
+ ];
72
+
73
+ // ── Helpers ───────────────────────────────────────────────────────────────────
74
+
75
+ async function withMcp(fn) {
76
+ const mcp = await createMcpClient();
77
+ try {
78
+ return await fn(mcp);
79
+ } finally {
80
+ try { mcp.close(); } catch { /* ignore — process already gone */ }
81
+ }
82
+ }
83
+
84
+ // ── Tool handlers ─────────────────────────────────────────────────────────────
85
+
86
+ async function handleAudit({ url, critical = false }) {
87
+ return withMcp(async (mcp) => {
88
+ const parsed = new URL(url);
89
+ const route = { path: parsed.pathname + parsed.search + parsed.hash, name: 'audit', critical };
90
+ const findings = await crawlRouteCheap(route, parsed.origin, mcp);
91
+ return { content: [{ type: 'text', text: JSON.stringify(findings, null, 2) }] };
92
+ });
93
+ }
94
+
95
+ async function handleAuditFull({ url, critical = false }) {
96
+ return withMcp(async (mcp) => {
97
+ const parsed = new URL(url);
98
+ const report = await runCrawl(
99
+ mcp,
100
+ [{ path: parsed.pathname + parsed.search + parsed.hash, name: 'audit', critical }],
101
+ parsed.origin,
102
+ );
103
+ return { content: [{ type: 'text', text: JSON.stringify(report, null, 2) }] };
104
+ });
105
+ }
106
+
107
+ async function handleCompare() {
108
+ return withMcp(async (mcp) => {
109
+ const report = await runComparison(mcp);
110
+ return { content: [{ type: 'text', text: JSON.stringify(report, null, 2) }] };
111
+ });
112
+ }
113
+
114
+ async function handleLastReport() {
115
+ if (!fs.existsSync(REPORTS_DIR)) {
116
+ return { content: [{ type: 'text', text: '{"error":"No reports found in reports/"}' }] };
117
+ }
118
+ const files = fs.readdirSync(REPORTS_DIR).filter(f => f.endsWith('.json'));
119
+ if (files.length === 0) {
120
+ return { content: [{ type: 'text', text: '{"error":"No reports found in reports/"}' }] };
121
+ }
122
+ const latest = files
123
+ .map(f => ({ f, mt: fs.statSync(path.join(REPORTS_DIR, f)).mtimeMs }))
124
+ .sort((a, b) => b.mt - a.mt)[0].f;
125
+ const json = fs.readFileSync(path.join(REPORTS_DIR, latest), 'utf8');
126
+ return { content: [{ type: 'text', text: json }] };
127
+ }
128
+
129
+ // ── Server bootstrap ──────────────────────────────────────────────────────────
130
+
131
+ const server = new Server(
132
+ { name: 'argus', version: '9.2.0' },
133
+ { capabilities: { tools: {} } },
134
+ );
135
+
136
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
137
+
138
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
139
+ try {
140
+ switch (req.params.name) {
141
+ case 'argus_audit': return await handleAudit(req.params.arguments ?? {});
142
+ case 'argus_audit_full': return await handleAuditFull(req.params.arguments ?? {});
143
+ case 'argus_compare': return await handleCompare();
144
+ case 'argus_last_report': return await handleLastReport();
145
+ default: throw new Error(`Unknown tool: ${req.params.name}`);
146
+ }
147
+ } catch (err) {
148
+ return {
149
+ content: [{ type: 'text', text: JSON.stringify({ error: err.message }) }],
150
+ isError: true,
151
+ };
152
+ }
153
+ });
154
+
155
+ const transport = new StdioServerTransport();
156
+ await server.connect(transport);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Argus Crawl Pipeline — backward-compat re-export shell (v9.2.0)
3
+ *
4
+ * The implementation has been split across three focused modules:
5
+ *
6
+ * orchestrator.js — crawl loop, route/flow crawl functions, runCrawl()
7
+ * report-processor.js — dedup, severity overrides, baseline, JSON write
8
+ * dispatcher.js — Slack / GitHub / HTML dispatch
9
+ *
10
+ * All callers (argus.js, batch-runner.js, server handlers, test-harness)
11
+ * continue to import from this file unchanged.
12
+ */
13
+
14
+ export { runCrawl, crawlRouteCheap, crawlRouteExpensive } from './orchestrator.js';
15
+ export { processReport, deduplicateFindings, rebuildSummary } from './report-processor.js';
16
+ export { dispatchAll } from './dispatcher.js';