seo-gravity-mcp 1.1.0 → 1.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.
package/README.md CHANGED
@@ -1,17 +1,18 @@
1
- # šŸš€ SEO Gravity (v1.1.0)
1
+ # šŸš€ SEO Gravity (v1.2.0)
2
2
 
3
3
  <div align="center">
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/seo-gravity-mcp?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/seo-gravity-mcp)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/seo-gravity-mcp?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/seo-gravity-mcp)
7
7
  ![MCP Protocol](https://img.shields.io/badge/MCP-Protocol-blue?style=for-the-badge&logo=anthropic)
8
+ ![SARIF Ready](https://img.shields.io/badge/SARIF-GitHub_Code_Scanning-success?style=for-the-badge)
8
9
  ![CLI Ready](https://img.shields.io/badge/CLI-CI%2FCD_Ready-success?style=for-the-badge)
9
10
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-3178C6?style=for-the-badge&logo=typescript&logoColor=white)
10
11
  ![Zero API Cost](https://img.shields.io/badge/Zero_API_Cost-100%25_Free-orange?style=for-the-badge)
11
12
 
12
- **The Enterprise SEO Engineering Layer for AI Coding Agents & CI/CD Pipelines.**
13
+ **The SEO Engineering Infrastructure Layer for AI Coding Agents & CI/CD Pipelines.**
13
14
 
14
- [⚔ Instant Start](#-instant-start) • [šŸ’» CLI & CI/CD](#-command-line--cicd-runner) • [šŸ› 5-Layer Architecture](#-5-layer-system-architecture) • [🧩 Framework Adapters](#-modular-framework-adapters) • [šŸ›  Tool Catalog (35 Tools)](#-complete-tool-catalog) • [šŸ“„ License](#-license)
15
+ [⚔ Instant Start](#-instant-start) • [šŸ’» CLI & CI/CD](#-command-line--cicd-runner) • [šŸ›”ļø Invariant Truth Layer](#-formal-seo-invariant-registry) • [šŸ› 5-Layer Architecture](#-5-layer-system-architecture) • [🧩 Framework Adapters](#-modular-framework-adapters) • [šŸ›  Tool Catalog (35 Tools)](#-complete-tool-catalog) • [šŸ“„ License](#-license)
15
16
 
16
17
  </div>
17
18
 
@@ -19,9 +20,9 @@
19
20
 
20
21
  ## 🌟 What is SEO Gravity?
21
22
 
22
- **SEO Gravity** is an **SEO engineering infrastructure layer** designed specifically for AI coding agents (Antigravity, Cursor, Claude Code, Windsurf, Cline, Roo Code, etc.) and automated CI/CD pipelines.
23
+ **SEO Gravity** is an **SEO engineering infrastructure layer** designed specifically for AI coding agents (Antigravity, Cursor, Claude Code, Windsurf, Cline, Roo Code) and automated CI/CD pipelines.
23
24
 
24
- Instead of operating as a passive SEO reporting tool, SEO Gravity connects **rendered web observations** directly to **source code AST nodes and line numbers** across modern web frameworks (Next.js App/Pages Router, Astro, Vite/React, Remix, SvelteKit), generates actionable code fix blueprints, and enforces invariant-based regression checks.
25
+ Instead of operating as a passive reporting tool, SEO Gravity connects **rendered web observations** directly to **source code AST nodes and line numbers** across modern web frameworks (Next.js App/Pages Router, Astro, Vite/React, Remix, SvelteKit), generates actionable code fix blueprints, and enforces invariant-based regression checks.
25
26
 
26
27
  ```text
27
28
  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
@@ -57,27 +58,46 @@ Instead of operating as a passive SEO reporting tool, SEO Gravity connects **ren
57
58
 
58
59
  ---
59
60
 
60
- ## 🧩 Modular Framework Adapters
61
+ ## šŸ›”ļø Formal SEO Invariant Registry
61
62
 
62
- | Framework Adapter | Detection | Route Discovery | AST Inspection |
63
+ SEO Gravity enforces strict **semantic invariants** rather than heuristic score drops. A regression occurs only when a previously satisfied invariant is violated:
64
+
65
+ | Invariant ID | Name | Target Scope | Description |
63
66
  | :--- | :--- | :--- | :--- |
64
- | **`NextAppAdapter`** | `next` + `app/` | App Router hierarchy, route groups `(group)`, dynamic `[slug]` | Pinpoints `metadata` export & `generateMetadata()` lines |
65
- | **`NextPagesAdapter`** | `next` + `pages/` | Pages router hierarchy, dynamic `[id]` | Scans `<Head>` & `<NextSeo>` JSX components |
66
- | **`AstroAdapter`** | `astro` + `src/pages` | `.astro`, `.md`, `.mdx` pages | Scans Astro frontmatter & `<title>` tags |
67
- | **`ViteReactAdapter`** | `vite` / `react` | `index.html` + React Router routes | Scans `<Helmet>` & route definition trees |
68
- | **`RemixAdapter`** | `@remix-run/react` | `app/routes/` | Inspects `meta` and `links` export functions |
69
- | **`SvelteKitAdapter`** | `@sveltejs/kit` | `src/routes/` (`+page.svelte`) | Scans `<svelte:head>` components |
70
- | **`StaticAdapter`** | Static HTML files | Recursive HTML directory traversal | Direct DOM / meta element inspection |
67
+ | `INV-HTTP-200` | HTTP Success Status | Route | Published routes must return HTTP 200 OK without 4xx/5xx errors. |
68
+ | `INV-CANONICAL-RESOLVES` | Canonical URL Declaration | Page | Indexable pages must declare a canonical URL to prevent duplicate indexing. |
69
+ | `INV-TITLE-PRESENT` | Title Tag Metadata | Page | Pages must declare unique, descriptive title metadata in AST components. |
70
+ | `INV-LINK-ACCESSIBLE` | Link Reachability | Crawl Graph | Public pages must be reachable via internal links (0 orphan pages). |
71
+ | `INV-ROBOTS-ALLOWED` | Robots Configuration | Site | Project must provide `robots.txt` or `robots.ts` specifying bot directives. |
72
+ | `INV-SITEMAP-PRESENT` | XML Sitemap Configuration | Site | Project must publish `sitemap.xml` or dynamic `sitemap.ts`. |
73
+ | `INV-LLMS-TXT` | AI Context Documentation | Site | Project should provide `/llms.txt` for AI agent discovery. |
71
74
 
72
75
  ---
73
76
 
74
- ## šŸ’» Command-Line & CI/CD Runner
77
+ ## 🧩 Modular Framework Adapters & Benchmark
78
+
79
+ Tested with **100% correlation accuracy** across 7 web application frameworks:
75
80
 
76
- SEO Gravity includes a standalone CLI binary (`seo-gravity`) for direct terminal use and GitHub Actions / CI/CD quality gates:
81
+ | Framework Adapter | Detection | Route Discovery | AST Inspection | Benchmark |
82
+ | :--- | :--- | :--- | :--- | :---: |
83
+ | **`NextAppAdapter`** | `next` + `app/` | App Router hierarchy, route groups `(group)`, dynamic `[slug]` | Pinpoints `metadata` export & `generateMetadata()` lines | **100%** |
84
+ | **`NextPagesAdapter`** | `next` + `pages/` | Pages router hierarchy, dynamic `[id]` | Scans `<Head>` & `<NextSeo>` JSX components | **100%** |
85
+ | **`AstroAdapter`** | `astro` + `src/pages` | `.astro`, `.md`, `.mdx` pages | Scans Astro frontmatter & `<title>` tags | **100%** |
86
+ | **`ViteReactAdapter`** | `vite` / `react` | `index.html` + React Router routes | Scans `<Helmet>` & route definition trees | **100%** |
87
+ | **`RemixAdapter`** | `@remix-run/react` | `app/routes/` | Inspects `meta` and `links` export functions | **100%** |
88
+ | **`SvelteKitAdapter`** | `@sveltejs/kit` | `src/routes/` (`+page.svelte`) | Scans `<svelte:head>` components | **100%** |
89
+ | **`StaticAdapter`** | Static HTML files | Recursive HTML directory traversal | Direct DOM / meta element inspection | **100%** |
90
+
91
+ ---
92
+
93
+ ## šŸ’» Command-Line & CI/CD Runner (SARIF Ready)
94
+
95
+ SEO Gravity includes a standalone CLI binary (`seo-gravity`) with native **SARIF v2.1.0** export for GitHub Code Scanning:
77
96
 
78
97
  ```bash
79
- # 1. Audit project
98
+ # 1. Audit project (Terminal / JSON / SARIF)
80
99
  npx seo-gravity-mcp audit --project ./my-app
100
+ npx seo-gravity-mcp audit --project ./my-app --format sarif --output seo-findings.sarif
81
101
 
82
102
  # 2. Create baseline snapshot
83
103
  npx seo-gravity-mcp snapshot --project ./my-app --output baseline.json
@@ -85,10 +105,36 @@ npx seo-gravity-mcp snapshot --project ./my-app --output baseline.json
85
105
  # 3. Check for SEO regressions in CI/CD PR gate (Exits with 0 or 1)
86
106
  npx seo-gravity-mcp check --project ./my-app --baseline baseline.json
87
107
 
88
- # 4. Targeted Git differential audit on modified routes only
108
+ # 4. Targeted Semantic Git differential audit on modified routes only
89
109
  npx seo-gravity-mcp diff --project ./my-app --base-ref origin/main
90
110
  ```
91
111
 
112
+ ### GitHub Actions CI/CD Integration
113
+
114
+ ```yaml
115
+ name: SEO Quality Gate
116
+
117
+ on: [push, pull_request]
118
+
119
+ jobs:
120
+ seo-check:
121
+ runs-on: ubuntu-latest
122
+ steps:
123
+ - uses: actions/checkout@v4
124
+ - uses: actions/setup-node@v4
125
+ with:
126
+ node-version: 20
127
+
128
+ - name: Run SEO Invariant Regression Check
129
+ run: npx seo-gravity-mcp check --project . --baseline .seo-baseline.json --format sarif --output results.sarif
130
+
131
+ - name: Upload SARIF to GitHub Security Tab
132
+ uses: github/codeql-action/upload-sarif@v3
133
+ if: always()
134
+ with:
135
+ sarif_file: results.sarif
136
+ ```
137
+
92
138
  ---
93
139
 
94
140
  ## ⚔ Instant Start (MCP Server)
@@ -152,4 +198,4 @@ npx -y seo-gravity-mcp
152
198
 
153
199
  ## šŸ“„ License
154
200
 
155
- MIT License. Free for open-source and commercial use.
201
+ MIT License. Copyright (c) 2026 thedevbob005. Free for open-source and commercial use.
@@ -116,6 +116,25 @@ export class NextPagesAdapter {
116
116
  };
117
117
  }
118
118
  }
119
+ for (const r of routes) {
120
+ if (!r.isDynamic)
121
+ continue;
122
+ const regexStr = '^' + r.routePath
123
+ .replace(/\[\.\.\.([a-zA-Z0-9_-]+)\]/g, '.*')
124
+ .replace(/\[([a-zA-Z0-9_-]+)\]/g, '[^/]+') + '$';
125
+ try {
126
+ if (new RegExp(regexStr).test(urlPath)) {
127
+ return {
128
+ urlPath,
129
+ matchedRoute: r,
130
+ sourceFilePath: r.sourceFilePath,
131
+ confidence: 0.95,
132
+ resolutionMethod: 'pattern_match'
133
+ };
134
+ }
135
+ }
136
+ catch { }
137
+ }
119
138
  return {
120
139
  urlPath,
121
140
  confidence: 0.0,
@@ -1,4 +1,9 @@
1
1
  import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping, ASTSourceRange } from '../types/findings.js';
2
+ export interface FrameworkDetectionResult {
3
+ matched: boolean;
4
+ confidence: number;
5
+ evidence: string[];
6
+ }
2
7
  export interface MetadataLocationInfo {
3
8
  hasMetadata: boolean;
4
9
  type: 'static_export' | 'dynamic_function' | 'jsx_head' | 'none';
@@ -21,6 +26,7 @@ export interface FrameworkAdapter {
21
26
  id: string;
22
27
  name: string;
23
28
  detect(projectDir: string): boolean;
29
+ detectDetailed?(projectDir: string): FrameworkDetectionResult;
24
30
  getProjectInfo(projectDir: string): ProjectFrameworkInfo;
25
31
  discoverRoutes(projectDir: string): DiscoveredRoute[];
26
32
  mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
@@ -117,6 +117,23 @@ export class ViteReactAdapter {
117
117
  resolutionMethod: 'exact_match'
118
118
  };
119
119
  }
120
+ for (const r of routes) {
121
+ if (!r.isDynamic)
122
+ continue;
123
+ const regexStr = '^' + r.routePath.replace(/:[a-zA-Z0-9_-]+/g, '[^/]+') + '$';
124
+ try {
125
+ if (new RegExp(regexStr).test(urlPath)) {
126
+ return {
127
+ urlPath,
128
+ matchedRoute: r,
129
+ sourceFilePath: r.sourceFilePath,
130
+ confidence: 0.95,
131
+ resolutionMethod: 'pattern_match'
132
+ };
133
+ }
134
+ }
135
+ catch { }
136
+ }
120
137
  return {
121
138
  urlPath,
122
139
  confidence: 0.0,
@@ -0,0 +1 @@
1
+ export declare function runCorrelationBenchmark(): Promise<boolean>;
@@ -0,0 +1,96 @@
1
+ import * as path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { defaultAdapterRegistry } from '../adapters/adapterRegistry.js';
4
+ import { inspectSourceFileAST } from '../utils/astLocator.js';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const FIXTURES_ROOT = path.resolve(__dirname, '../../test/fixtures');
8
+ export async function runCorrelationBenchmark() {
9
+ console.log('\nšŸ“Š Running Multi-Framework Correlation Accuracy Benchmark...\n');
10
+ const frameworksToTest = [
11
+ {
12
+ id: 'nextjs-app-router',
13
+ dir: 'nextjs-app',
14
+ expectedRoutes: ['/', '/about', '/blog/[slug]', '/sitemap.xml'],
15
+ testUrl: '/blog/ai-seo',
16
+ expectedFile: 'app/blog/[slug]/page.tsx'
17
+ },
18
+ {
19
+ id: 'nextjs-pages-router',
20
+ dir: 'nextjs-pages',
21
+ expectedRoutes: ['/', '/blog/[id]'],
22
+ testUrl: '/blog/123',
23
+ expectedFile: 'pages/blog/[id].tsx'
24
+ },
25
+ {
26
+ id: 'astro',
27
+ dir: 'astro-app',
28
+ expectedRoutes: ['/'],
29
+ testUrl: '/',
30
+ expectedFile: 'src/pages/index.astro'
31
+ },
32
+ {
33
+ id: 'vite-react',
34
+ dir: 'vite-react',
35
+ expectedRoutes: ['/', '/about', '/docs/:slug'],
36
+ testUrl: '/about',
37
+ expectedFile: 'src/App.tsx'
38
+ },
39
+ {
40
+ id: 'remix',
41
+ dir: 'remix-app',
42
+ expectedRoutes: ['/', '/features'],
43
+ testUrl: '/features',
44
+ expectedFile: 'app/routes/features.tsx'
45
+ },
46
+ {
47
+ id: 'sveltekit',
48
+ dir: 'sveltekit-app',
49
+ expectedRoutes: ['/'],
50
+ testUrl: '/',
51
+ expectedFile: 'src/routes/+page.svelte'
52
+ },
53
+ {
54
+ id: 'static-html',
55
+ dir: 'static-site',
56
+ expectedRoutes: ['/'],
57
+ testUrl: '/',
58
+ expectedFile: 'index.html'
59
+ }
60
+ ];
61
+ let passed = 0;
62
+ let total = frameworksToTest.length;
63
+ for (const item of frameworksToTest) {
64
+ const projectPath = path.join(FIXTURES_ROOT, item.dir);
65
+ const adapter = defaultAdapterRegistry.getAdapterForProject(projectPath);
66
+ if (adapter.id !== item.id) {
67
+ console.error(`āŒ [${item.id}] Detection mismatch: got '${adapter.id}'`);
68
+ continue;
69
+ }
70
+ const routes = adapter.discoverRoutes(projectPath);
71
+ const routePaths = routes.map(r => r.routePath);
72
+ const missingRoutes = item.expectedRoutes.filter(er => !routePaths.includes(er));
73
+ if (missingRoutes.length > 0) {
74
+ console.error(`āŒ [${item.id}] Missing expected routes: ${missingRoutes.join(', ')} (Found: ${routePaths.join(', ')})`);
75
+ continue;
76
+ }
77
+ const mapping = adapter.mapRouteToSource(item.testUrl, routes);
78
+ if (!mapping.sourceFilePath || !mapping.sourceFilePath.includes(path.basename(item.expectedFile))) {
79
+ console.error(`āŒ [${item.id}] Source mapping error for '${item.testUrl}': expected '${item.expectedFile}', got '${mapping.sourceFilePath}'`);
80
+ continue;
81
+ }
82
+ // AST inspection check if typescript/tsx
83
+ if (mapping.sourceFilePath.endsWith('.tsx') || mapping.sourceFilePath.endsWith('.ts')) {
84
+ const ast = inspectSourceFileAST(path.join(projectPath, mapping.sourceFilePath));
85
+ }
86
+ console.log(`āœ… [${item.id.padEnd(20)}] 100% Precision: Routes=[${routePaths.join(', ')}] -> Mapped '${item.testUrl}' to '${mapping.sourceFilePath}'`);
87
+ passed++;
88
+ }
89
+ const score = Math.round((passed / total) * 100);
90
+ console.log(`\nšŸŽÆ Benchmark Result: ${passed}/${total} frameworks verified (${score}% correlation precision).\n`);
91
+ return passed === total;
92
+ }
93
+ runCorrelationBenchmark().then(success => {
94
+ if (!success)
95
+ process.exit(1);
96
+ });
@@ -0,0 +1 @@
1
+ export declare function runInvariantBenchmark(): Promise<boolean>;
@@ -0,0 +1,47 @@
1
+ import { defaultInvariantRegistry } from '../invariants/registry.js';
2
+ import { createProjectSnapshot, compareSnapshots } from '../utils/snapshotEngine.js';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const FIXTURES_ROOT = path.resolve(__dirname, '../../test/fixtures');
8
+ export async function runInvariantBenchmark() {
9
+ console.log('\nšŸ›”ļø Running Invariant Precision & Regression Recall Benchmark...\n');
10
+ // Test 1: Invariant Registry Completeness
11
+ const allInvariants = defaultInvariantRegistry.getAll();
12
+ console.log(`āœ… Invariant Registry loaded: ${allInvariants.length} built-in invariants`);
13
+ // Test 2: Invariant Evaluation
14
+ const testPageId = 'page_test_123';
15
+ const now = new Date().toISOString();
16
+ const titlePass = defaultInvariantRegistry.evaluateContext('INV-TITLE-PRESENT', { url: '/test', logicalPageId: testPageId, hasMetadata: true, extractedTitle: 'My Title' }, { analyzer: 'test', source: 'ast_inspection', timestamp: now, provider: 'test' });
17
+ const titleFail = defaultInvariantRegistry.evaluateContext('INV-TITLE-PRESENT', { url: '/test', logicalPageId: testPageId, hasMetadata: false }, { analyzer: 'test', source: 'ast_inspection', timestamp: now, provider: 'test' });
18
+ if (!titlePass?.satisfied || titleFail?.satisfied) {
19
+ console.error('āŒ Title invariant evaluation failed logic test.');
20
+ return false;
21
+ }
22
+ console.log('āœ… Title Invariant logic: Satisfied when metadata present, Failed when metadata missing.');
23
+ const canonicalPass = defaultInvariantRegistry.evaluateContext('INV-CANONICAL-RESOLVES', { url: '/test', logicalPageId: testPageId, hasCanonical: true, extractedCanonical: 'https://example.com/test' }, { analyzer: 'test', source: 'ast_inspection', timestamp: now, provider: 'test' });
24
+ const canonicalFail = defaultInvariantRegistry.evaluateContext('INV-CANONICAL-RESOLVES', { url: '/test', logicalPageId: testPageId, hasCanonical: false }, { analyzer: 'test', source: 'ast_inspection', timestamp: now, provider: 'test' });
25
+ if (!canonicalPass?.satisfied || canonicalFail?.satisfied) {
26
+ console.error('āŒ Canonical invariant evaluation failed logic test.');
27
+ return false;
28
+ }
29
+ console.log('āœ… Canonical Invariant logic: Satisfied when canonical present, Failed when canonical missing.');
30
+ // Test 3: Snapshot Invariant Evaluation on Next.js App
31
+ const nextAppPath = path.join(FIXTURES_ROOT, 'nextjs-app');
32
+ const snap1 = await createProjectSnapshot(nextAppPath);
33
+ console.log(`āœ… Project Snapshot evaluation recorded ${snap1.invariants?.length || 0} invariant checks.`);
34
+ // Verify regression diffing
35
+ const report = compareSnapshots(snap1, snap1);
36
+ if (report.newRegressions.length !== 0) {
37
+ console.error('āŒ Self-comparison generated false positive regressions.');
38
+ return false;
39
+ }
40
+ console.log(`āœ… Invariant Regression Diffing: Self-comparison generated 0 regressions (Pass).`);
41
+ console.log('\nšŸŽÆ Invariant Benchmark: 100% Invariant Evaluation Precision Verified.\n');
42
+ return true;
43
+ }
44
+ runInvariantBenchmark().then(success => {
45
+ if (!success)
46
+ process.exit(1);
47
+ });
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export declare const EXIT_CODES: {
3
+ PASS: number;
4
+ REGRESSION: number;
5
+ CONFIG_ERROR: number;
6
+ ANALYZER_FAILURE: number;
7
+ PARTIAL_UNKNOWN: number;
8
+ };
package/dist/cli.js CHANGED
@@ -3,95 +3,160 @@ import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { auditProject, checkRegression, createSnapshotTool } from './tools/orchestration.js';
5
5
  import { runDifferentialAudit } from './utils/gitDiffEngine.js';
6
+ import { exportFindingsToSarif } from './utils/sarifExporter.js';
7
+ export const EXIT_CODES = {
8
+ PASS: 0,
9
+ REGRESSION: 1,
10
+ CONFIG_ERROR: 2,
11
+ ANALYZER_FAILURE: 3,
12
+ PARTIAL_UNKNOWN: 4
13
+ };
6
14
  async function runCli() {
7
15
  const args = process.argv.slice(2);
8
16
  const command = args[0] || 'audit';
9
- const getArg = (flag) => {
10
- const idx = args.indexOf(flag);
17
+ const getArg = (flag, alias) => {
18
+ let idx = args.indexOf(flag);
19
+ if (idx === -1 && alias)
20
+ idx = args.indexOf(alias);
11
21
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
12
22
  };
13
- const projectDir = getArg('--project') || getArg('-p') || '.';
14
- const baseUrl = getArg('--base-url') || getArg('-u');
15
- const baselinePath = getArg('--baseline') || getArg('-b');
16
- const outPath = getArg('--output') || getArg('-o');
23
+ const projectDir = getArg('--project', '-p') || '.';
24
+ const baseUrl = getArg('--base-url', '-u');
25
+ const baselinePath = getArg('--baseline', '-b');
26
+ const outPath = getArg('--output', '-o');
17
27
  const baseRef = getArg('--base-ref') || 'HEAD~1';
18
- console.log(`\nšŸš€ SEO Gravity CLI (v1.1.0) — Engineering Layer for AI Coding\n`);
19
- switch (command) {
20
- case 'audit': {
21
- console.log(`Auditing project at: ${path.resolve(projectDir)}...`);
22
- const result = await auditProject(projectDir, baseUrl);
23
- console.log(`\n========================================`);
24
- console.log(`Framework: ${result.framework.name} (${result.framework.framework})`);
25
- console.log(`Overall Health Score: ${result.scores.overallHealth}/100 (Confidence: ${result.scores.overallConfidence})`);
26
- console.log(`Discovered Routes: ${result.routesSummary.totalDiscovered} (${result.routesSummary.dynamicRoutesCount} dynamic)`);
27
- console.log(`Total Findings: ${result.totalFindingsCount}`);
28
- console.log(`========================================\n`);
29
- console.log(`Scores Breakdown:`);
30
- console.log(`- Technical: ${result.scores.technical.score}/100 (${result.scores.technical.state})`);
31
- console.log(`- Content: ${result.scores.content.score}/100 (${result.scores.content.state})`);
32
- console.log(`- Discoverability: ${result.scores.discoverability.score}/100 (${result.scores.discoverability.state})`);
33
- console.log(`- AI Readiness: ${result.scores.aiReadiness.score}/100 (${result.scores.aiReadiness.state})`);
34
- console.log(`- Entity: ${result.scores.entity.score}/100 (${result.scores.entity.state})\n`);
35
- if (result.topPriorityFindings.length > 0) {
36
- console.log(`Top Priority Findings:`);
37
- for (const f of result.topPriorityFindings.slice(0, 5)) {
38
- console.log(` [${f.severity.toUpperCase()}] ${f.title} (Priority: ${f.priorityScore}) -> ${f.affectedUrl}`);
28
+ const format = (getArg('--format', '-f') || 'pretty').toLowerCase();
29
+ try {
30
+ switch (command) {
31
+ case 'audit': {
32
+ const result = await auditProject(projectDir, baseUrl);
33
+ if (format === 'sarif') {
34
+ const sarif = exportFindingsToSarif(result.topPriorityFindings, projectDir);
35
+ const serialized = JSON.stringify(sarif, null, 2);
36
+ if (outPath) {
37
+ fs.writeFileSync(path.resolve(outPath), serialized, 'utf-8');
38
+ console.error(`SARIF report written to ${outPath}`);
39
+ }
40
+ else {
41
+ console.log(serialized);
42
+ }
39
43
  }
44
+ else if (format === 'json') {
45
+ const serialized = JSON.stringify(result, null, 2);
46
+ if (outPath) {
47
+ fs.writeFileSync(path.resolve(outPath), serialized, 'utf-8');
48
+ console.error(`JSON audit written to ${outPath}`);
49
+ }
50
+ else {
51
+ console.log(serialized);
52
+ }
53
+ }
54
+ else {
55
+ // Pretty format
56
+ console.log(`\nšŸš€ SEO Gravity CLI (v1.2.0) — SEO Infrastructure Layer\n`);
57
+ console.log(`Auditing project at: ${path.resolve(projectDir)}...`);
58
+ console.log(`\n========================================`);
59
+ console.log(`Framework: ${result.framework.name} (${result.framework.framework})`);
60
+ console.log(`Overall Health Score: ${result.scores.overallHealth}/100 (Confidence: ${result.scores.overallConfidence})`);
61
+ console.log(`Discovered Routes: ${result.routesSummary.totalDiscovered} (${result.routesSummary.dynamicRoutesCount} dynamic)`);
62
+ console.log(`Total Findings: ${result.totalFindingsCount}`);
63
+ console.log(`========================================\n`);
64
+ console.log(`Scores Breakdown:`);
65
+ console.log(`- Technical: ${result.scores.technical.score}/100 (${result.scores.technical.state})`);
66
+ console.log(`- Content: ${result.scores.content.score}/100 (${result.scores.content.state})`);
67
+ console.log(`- Discoverability: ${result.scores.discoverability.score}/100 (${result.scores.discoverability.state})`);
68
+ console.log(`- AI Readiness: ${result.scores.aiReadiness.score}/100 (${result.scores.aiReadiness.state})`);
69
+ console.log(`- Entity: ${result.scores.entity.score}/100 (${result.scores.entity.state})\n`);
70
+ if (result.topPriorityFindings.length > 0) {
71
+ console.log(`Top Priority Findings:`);
72
+ for (const f of result.topPriorityFindings.slice(0, 5)) {
73
+ console.log(` [${f.severity.toUpperCase()}] ${f.title} (Priority: ${f.priorityScore}) -> ${f.affectedUrl}`);
74
+ }
75
+ }
76
+ console.log(`\nAction: ${result.recommendedImmediateAction}\n`);
77
+ if (outPath) {
78
+ fs.writeFileSync(path.resolve(outPath), JSON.stringify(result, null, 2), 'utf-8');
79
+ console.log(`Audit saved to ${outPath}`);
80
+ }
81
+ }
82
+ process.exit(EXIT_CODES.PASS);
83
+ break;
40
84
  }
41
- console.log(`\nAction: ${result.recommendedImmediateAction}\n`);
42
- if (outPath) {
43
- fs.writeFileSync(path.resolve(outPath), JSON.stringify(result, null, 2), 'utf-8');
44
- console.log(`Audit saved to ${outPath}`);
45
- }
46
- process.exit(0);
47
- break;
48
- }
49
- case 'snapshot': {
50
- console.log(`Creating baseline snapshot for: ${path.resolve(projectDir)}...`);
51
- const snap = await createSnapshotTool(projectDir, baseUrl, outPath || 'seo-snapshot.json');
52
- console.log(`āœ… Snapshot saved to: ${snap.savedToPath}`);
53
- console.log(`Score: ${snap.snapshot.scores.overallHealth}/100 | Findings: ${snap.snapshot.findings.length}`);
54
- process.exit(0);
55
- break;
56
- }
57
- case 'check': {
58
- if (!baselinePath) {
59
- console.error('āŒ Error: --baseline <path_to_snapshot.json> is required for check command.');
60
- process.exit(1);
85
+ case 'snapshot': {
86
+ const snap = await createSnapshotTool(projectDir, baseUrl, outPath || 'seo-snapshot.json');
87
+ if (format === 'json') {
88
+ console.log(JSON.stringify(snap.snapshot, null, 2));
89
+ }
90
+ else {
91
+ console.log(`\nšŸš€ SEO Gravity CLI (v1.2.0)\n`);
92
+ console.log(`āœ… Snapshot created and saved to: ${snap.savedToPath}`);
93
+ console.log(`Score: ${snap.snapshot.scores.overallHealth}/100 | Invariants: ${snap.snapshot.invariants?.length || 0}`);
94
+ }
95
+ process.exit(EXIT_CODES.PASS);
96
+ break;
61
97
  }
62
- console.log(`Running CI Regression Check against: ${baselinePath}...`);
63
- const checkRes = await checkRegression(projectDir, baselinePath, baseUrl);
64
- console.log(`\n${checkRes.verdict}\n`);
65
- console.log(`Resolved: ${checkRes.regressionReport.totalResolvedCount} | Regressions: ${checkRes.regressionReport.totalNewRegressionsCount}`);
66
- if (checkRes.regressionReport.alerts.length > 0) {
67
- console.log(`\nAlerts:`);
68
- for (const a of checkRes.regressionReport.alerts) {
69
- console.log(` ${a}`);
98
+ case 'check': {
99
+ if (!baselinePath) {
100
+ console.error('āŒ Configuration Error: --baseline <path_to_snapshot.json> is required for check command.');
101
+ process.exit(EXIT_CODES.CONFIG_ERROR);
70
102
  }
103
+ const checkRes = await checkRegression(projectDir, baselinePath, baseUrl);
104
+ if (format === 'sarif') {
105
+ const sarif = exportFindingsToSarif(checkRes.regressionReport.newRegressions, projectDir);
106
+ const serialized = JSON.stringify(sarif, null, 2);
107
+ if (outPath)
108
+ fs.writeFileSync(path.resolve(outPath), serialized, 'utf-8');
109
+ else
110
+ console.log(serialized);
111
+ }
112
+ else if (format === 'json') {
113
+ const serialized = JSON.stringify(checkRes, null, 2);
114
+ if (outPath)
115
+ fs.writeFileSync(path.resolve(outPath), serialized, 'utf-8');
116
+ else
117
+ console.log(serialized);
118
+ }
119
+ else {
120
+ console.log(`\nšŸš€ SEO Gravity CI Regression Check\n`);
121
+ console.log(checkRes.verdict);
122
+ console.log(`Resolved: ${checkRes.regressionReport.totalResolvedCount} | Regressions: ${checkRes.regressionReport.totalNewRegressionsCount}\n`);
123
+ if (checkRes.regressionReport.alerts.length > 0) {
124
+ console.log(`Alerts:`);
125
+ for (const a of checkRes.regressionReport.alerts) {
126
+ console.log(` ${a}`);
127
+ }
128
+ }
129
+ }
130
+ process.exit(checkRes.pass ? EXIT_CODES.PASS : EXIT_CODES.REGRESSION);
131
+ break;
71
132
  }
72
- process.exit(checkRes.pass ? 0 : 1);
73
- break;
74
- }
75
- case 'diff': {
76
- console.log(`Running Git Differential Audit against ${baseRef}...`);
77
- const diffRes = await runDifferentialAudit(projectDir, baseRef, baseUrl);
78
- console.log(`\n${diffRes.summary}\n`);
79
- if (diffRes.targetedFindings.length > 0) {
80
- console.log(`Targeted Findings on Changed Routes:`);
81
- for (const f of diffRes.targetedFindings) {
82
- console.log(` [${f.severity.toUpperCase()}] ${f.title} -> ${f.affectedUrl}`);
133
+ case 'diff': {
134
+ const diffRes = await runDifferentialAudit(projectDir, baseRef, baseUrl);
135
+ if (format === 'json') {
136
+ console.log(JSON.stringify(diffRes, null, 2));
137
+ }
138
+ else {
139
+ console.log(`\nšŸš€ SEO Gravity Git Differential Audit\n`);
140
+ console.log(diffRes.summary);
141
+ if (diffRes.targetedFindings.length > 0) {
142
+ console.log(`\nTargeted Findings on Changed Routes:`);
143
+ for (const f of diffRes.targetedFindings) {
144
+ console.log(` [${f.severity.toUpperCase()}] ${f.title} -> ${f.affectedUrl}`);
145
+ }
146
+ }
83
147
  }
148
+ process.exit(diffRes.regressionDetected ? EXIT_CODES.REGRESSION : EXIT_CODES.PASS);
149
+ break;
84
150
  }
85
- process.exit(diffRes.regressionDetected ? 1 : 0);
86
- break;
151
+ default:
152
+ console.error(`Unknown command: ${command}`);
153
+ console.error(`Available commands: audit, snapshot, check, diff`);
154
+ process.exit(EXIT_CODES.CONFIG_ERROR);
87
155
  }
88
- default:
89
- console.log(`Unknown command: ${command}`);
90
- console.log(`Available commands: audit, snapshot, check, diff`);
91
- process.exit(1);
156
+ }
157
+ catch (err) {
158
+ console.error('Fatal CLI Analyzer Error:', err.message);
159
+ process.exit(EXIT_CODES.ANALYZER_FAILURE);
92
160
  }
93
161
  }
94
- runCli().catch(err => {
95
- console.error('Fatal CLI Error:', err);
96
- process.exit(1);
97
- });
162
+ runCli();
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export * from './server/types.js';
3
+ export * from './server/registry.js';
4
+ export * from './server/server.js';