seo-gravity-mcp 1.0.2 → 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.
Files changed (67) hide show
  1. package/README.md +118 -105
  2. package/dist/adapters/adapterRegistry.d.ts +7 -0
  3. package/dist/adapters/adapterRegistry.js +30 -0
  4. package/dist/adapters/astroAdapter.d.ts +17 -0
  5. package/dist/adapters/astroAdapter.js +191 -0
  6. package/dist/adapters/nextAppAdapter.d.ts +17 -0
  7. package/dist/adapters/nextAppAdapter.js +265 -0
  8. package/dist/adapters/nextPagesAdapter.d.ts +17 -0
  9. package/dist/adapters/nextPagesAdapter.js +209 -0
  10. package/dist/adapters/remixAdapter.d.ts +16 -0
  11. package/dist/adapters/remixAdapter.js +118 -0
  12. package/dist/adapters/staticAdapter.d.ts +16 -0
  13. package/dist/adapters/staticAdapter.js +117 -0
  14. package/dist/adapters/svelteKitAdapter.d.ts +16 -0
  15. package/dist/adapters/svelteKitAdapter.js +122 -0
  16. package/dist/adapters/types.d.ts +39 -0
  17. package/dist/adapters/types.js +1 -0
  18. package/dist/adapters/viteReactAdapter.d.ts +16 -0
  19. package/dist/adapters/viteReactAdapter.js +189 -0
  20. package/dist/benchmark/correlationBenchmark.d.ts +1 -0
  21. package/dist/benchmark/correlationBenchmark.js +96 -0
  22. package/dist/benchmark/invariantBenchmark.d.ts +1 -0
  23. package/dist/benchmark/invariantBenchmark.js +47 -0
  24. package/dist/cli.d.ts +8 -0
  25. package/dist/cli.js +162 -0
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.js +8 -646
  28. package/dist/invariants/registry.d.ts +12 -0
  29. package/dist/invariants/registry.js +178 -0
  30. package/dist/invariants/types.d.ts +38 -0
  31. package/dist/invariants/types.js +1 -0
  32. package/dist/providers/providerRegistry.d.ts +65 -0
  33. package/dist/providers/providerRegistry.js +64 -0
  34. package/dist/providers/types.d.ts +22 -0
  35. package/dist/providers/types.js +1 -0
  36. package/dist/server/registry.d.ts +3 -0
  37. package/dist/server/registry.js +559 -0
  38. package/dist/server/server.d.ts +70 -0
  39. package/dist/server/server.js +53 -0
  40. package/dist/server/types.d.ts +7 -0
  41. package/dist/server/types.js +1 -0
  42. package/dist/test.js +108 -138
  43. package/dist/tools/orchestration.js +23 -21
  44. package/dist/types/canonical.d.ts +64 -0
  45. package/dist/types/canonical.js +1 -0
  46. package/dist/types/findings.d.ts +22 -0
  47. package/dist/types/findings.js +1 -1
  48. package/dist/types/seo.d.ts +24 -0
  49. package/dist/utils/astLocator.d.ts +14 -0
  50. package/dist/utils/astLocator.js +137 -0
  51. package/dist/utils/cacheManager.d.ts +29 -0
  52. package/dist/utils/cacheManager.js +60 -0
  53. package/dist/utils/experimentEngine.d.ts +26 -0
  54. package/dist/utils/experimentEngine.js +41 -0
  55. package/dist/utils/findingEngine.d.ts +2 -1
  56. package/dist/utils/findingEngine.js +1 -0
  57. package/dist/utils/gitDiffEngine.d.ts +28 -0
  58. package/dist/utils/gitDiffEngine.js +109 -0
  59. package/dist/utils/opportunityEngine.d.ts +19 -0
  60. package/dist/utils/opportunityEngine.js +61 -0
  61. package/dist/utils/projectScanner.d.ts +4 -2
  62. package/dist/utils/projectScanner.js +14 -346
  63. package/dist/utils/sarifExporter.d.ts +64 -0
  64. package/dist/utils/sarifExporter.js +81 -0
  65. package/dist/utils/snapshotEngine.d.ts +3 -1
  66. package/dist/utils/snapshotEngine.js +232 -94
  67. package/package.json +8 -5
@@ -0,0 +1,7 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export interface ServerOptions {
3
+ name?: string;
4
+ version?: string;
5
+ transport?: 'stdio';
6
+ }
7
+ export type ToolRegistrationFn = (server: McpServer) => void;
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js CHANGED
@@ -3,163 +3,133 @@ import { fileURLToPath } from 'url';
3
3
  // Core Tool Imports
4
4
  import { auditOnPage } from './tools/onpage.js';
5
5
  import { auditGeoAiReadiness } from './tools/geo.js';
6
- import { clusterKeywords, classifySearchIntent } from './tools/keywords.js';
7
- import { generateSchemaMarkup, validateSchema } from './tools/schema.js';
8
- // New v1.0.2 Agent Orchestration Imports
9
- import { auditProject, diagnoseSeo, prioritizeFindings, generateFixPlan, createSnapshotTool, checkRegression } from './tools/orchestration.js';
10
- import { detectFramework, discoverRoutes, mapUrlToRouteSource } from './utils/projectScanner.js';
11
- import { CrawlGraphBuilder } from './utils/crawlGraph.js';
12
- import { calculatePriorityScore } from './utils/findingEngine.js';
6
+ // Modular Server & Agent Orchestration
7
+ import { createMcpServer } from './server/server.js';
8
+ import { TOOLS } from './server/registry.js';
9
+ import { auditProject, prioritizeFindings, createSnapshotTool, checkRegression } from './tools/orchestration.js';
10
+ import { defaultAdapterRegistry } from './adapters/adapterRegistry.js';
11
+ import { inspectSourceFileAST } from './utils/astLocator.js';
12
+ import { defaultCacheManager } from './utils/cacheManager.js';
13
+ import { defaultProviderRegistry } from './providers/providerRegistry.js';
14
+ import { detectOpportunities } from './utils/opportunityEngine.js';
15
+ import { exportFindingsToSarif } from './utils/sarifExporter.js';
16
+ import { defaultInvariantRegistry } from './invariants/registry.js';
13
17
  const __filename = fileURLToPath(import.meta.url);
14
18
  const __dirname = path.dirname(__filename);
15
19
  const FIXTURES_DIR = path.resolve(__dirname, '../test/fixtures');
16
20
  async function runTests() {
17
- console.log('🧪 Starting SEO Gravity MCP v1.0.2 Comprehensive Test Suite...\n');
21
+ console.log('🧪 Starting SEO Gravity MCP v1.2.0 (Engineering Infrastructure) Test Suite...\n');
18
22
  // -------------------------------------------------------------
19
- // Test 1: On-Page Audit
23
+ // Test 1: Modular Server & 35 Tool Catalog
20
24
  // -------------------------------------------------------------
21
- console.log('1️⃣ Testing On-Page Audit...');
25
+ console.log('1️⃣ Testing Modular Server Architecture...');
26
+ const server = createMcpServer({ name: 'seo-gravity-mcp', version: '1.2.0' });
27
+ console.log(` ✅ Server Initialized: ${TOOLS.length} tools registered across all 8 architectural layers.`);
28
+ // -------------------------------------------------------------
29
+ // Test 2: On-Page & GEO Evidence Analysis
30
+ // -------------------------------------------------------------
31
+ console.log('\n2️⃣ Testing On-Page & GEO Evidence Analysis...');
22
32
  const sampleHtml = `
23
33
  <!DOCTYPE html>
24
34
  <html>
25
35
  <head>
26
- <title>Best Project Management Software in 2026 - Top 10 Picks</title>
27
- <meta name="description" content="Discover the best project management software tools for agile teams in 2026. Compare pricing, features, and user ratings. Start free today.">
28
- <link rel="canonical" href="https://example.com/best-project-management-software">
36
+ <title>Best Project Management Software in 2026</title>
37
+ <meta name="description" content="Compare the best agile tools for engineering teams.">
38
+ <link rel="canonical" href="https://example.com/best-tools">
29
39
  </head>
30
40
  <body>
31
- <h1>Best Project Management Software for Teams</h1>
32
- <h2>1. Why Project Management Tools Matter</h2>
33
- <p>Project management software refers to digital platforms designed to plan, organize, and allocate resources efficiently across engineering and marketing teams.</p>
34
- <h2>2. Top Features Comparison</h2>
35
- <p>Key features include Gantt charts, real-time collaboration, and API integrations.</p>
36
- <img src="hero.png" alt="Project management dashboard preview">
37
- <a href="/pricing">View Pricing</a>
38
- <a href="https://wikipedia.org/wiki/Project_management">Learn more on Wikipedia</a>
41
+ <h1>Best Project Management Software</h1>
42
+ <p>Project management software refers to digital platforms designed to plan and organize workflows.</p>
43
+ <a href="/pricing">Pricing</a>
39
44
  </body>
40
45
  </html>
41
46
  `;
42
47
  const onpage = await auditOnPage(sampleHtml, 'project management software');
43
- console.log(` ✅ On-Page Score: ${onpage.overallScore}/100 | Title: ${onpage.titleAudit.status} | H1s: ${onpage.headingsAudit.h1Count}`);
48
+ const geo = await auditGeoAiReadiness(sampleHtml, 'best project management software');
49
+ console.log(` ✅ On-Page Score: ${onpage.overallScore}/100 | Title: ${onpage.titleAudit.status}`);
50
+ console.log(` ✅ GEO AI Retrieval Signals: Direct Answer=${geo.aiRetrievalSignals?.directEntityDefinition}`);
44
51
  // -------------------------------------------------------------
45
- // Test 2: GEO / AEO Readiness with Evidence Tiers
52
+ // Test 3: Framework Adapters & Multi-Framework Route Discovery
46
53
  // -------------------------------------------------------------
47
- console.log('\n2️⃣ Testing GEO & AI Search Readiness Audit (with Evidence Tiers)...');
48
- const geo = await auditGeoAiReadiness(sampleHtml, 'best project management software');
49
- console.log(` ✅ GEO Score: ${geo.overallGeoScore}/100 | Citation: ${geo.citationLikelihood} | Confidence: ${geo.confidence}`);
50
- console.log(` ✅ AI Retrieval Signals: Direct Answer=${geo.aiRetrievalSignals?.directEntityDefinition}, Stats=${geo.aiRetrievalSignals?.factualDataAndStats}`);
51
- // -------------------------------------------------------------
52
- // Test 3: Schema Generation & Validation
53
- // -------------------------------------------------------------
54
- console.log('\n3️⃣ Testing Schema.org JSON-LD Generation & Validation...');
55
- const schema = generateSchemaMarkup('FAQPage', {
56
- items: [
57
- { question: 'What is project management software?', answer: 'It is a platform to coordinate team tasks and roadmaps.' },
58
- { question: 'Is it free to start?', answer: 'Yes, free tiers are available for small teams.' }
59
- ]
60
- });
61
- const val = await validateSchema(schema.jsonLdScript.replace(/<script[^>]*>/, '').replace(/<\/script>/, ''));
62
- console.log(` ✅ Schema Validated (${val.schemasDetectedCount} detected, Valid: ${val.schemas[0]?.isValid})`);
63
- // -------------------------------------------------------------
64
- // Test 4: Keyword Clustering & Intent Classification
65
- // -------------------------------------------------------------
66
- console.log('\n4️⃣ Testing Keyword Clustering & Search Intent Classification...');
67
- const keywords = [
68
- 'best running shoes',
69
- 'buy running shoes online',
70
- 'how to choose running shoes',
71
- 'running shoes for flat feet',
72
- 'running shoes review',
73
- 'running shoes sale discount'
74
- ];
75
- const clusters = clusterKeywords(keywords);
76
- const intents = classifySearchIntent(keywords);
77
- console.log(` ✅ Clustered ${keywords.length} keywords into ${clusters.clusterCount} cluster(s)`);
78
- console.log(` ✅ Intent: "${keywords[1]}" -> ${intents[1].intent} (${(intents[1].confidenceScore * 100).toFixed(0)}%)`);
79
- // -------------------------------------------------------------
80
- // Test 5: Priority Scoring Algorithm
81
- // -------------------------------------------------------------
82
- console.log('\n5️⃣ Testing Finding Priority Scoring Algorithm...');
83
- const p1 = calculatePriorityScore('critical', 1.0, 'trivial', 'site_wide');
84
- const p2 = calculatePriorityScore('low', 0.8, 'high', 'isolated');
85
- console.log(` ✅ Critical Site-wide Issue Priority: ${p1.priorityScore} (Tier: ${p1.priorityTier})`);
86
- console.log(` ✅ Isolated Low-Severity Issue Priority: ${p2.priorityScore} (Tier: ${p2.priorityTier})`);
87
- // -------------------------------------------------------------
88
- // Test 6: Project Framework Detection & Route Discovery
89
- // -------------------------------------------------------------
90
- console.log('\n6️⃣ Testing Framework Scanner & Route Discovery on Fixtures...');
54
+ console.log('\n3️⃣ Testing Framework Adapters (Next.js, Astro, Remix, Vite, SvelteKit)...');
91
55
  const nextAppPath = path.join(FIXTURES_DIR, 'nextjs-app');
92
- const nextFramework = detectFramework(nextAppPath);
93
- const nextRoutes = discoverRoutes(nextAppPath, nextFramework);
94
- console.log(` ✅ Next.js Fixture: Framework=${nextFramework.framework}, TypeScript=${nextFramework.hasTypeScript}, Sitemap=${nextFramework.hasSitemapConfig}`);
95
- console.log(` ✅ Discovered ${nextRoutes.length} route(s): ${nextRoutes.map(r => r.routePath).join(', ')}`);
96
- const astroAppPath = path.join(FIXTURES_DIR, 'astro-app');
97
- const astroFramework = detectFramework(astroAppPath);
98
- const astroRoutes = discoverRoutes(astroAppPath, astroFramework);
99
- console.log(` ✅ Astro Fixture: Framework=${astroFramework.framework}, Routes=${astroRoutes.map(r => r.routePath).join(', ')}`);
100
- // -------------------------------------------------------------
101
- // Test 7: Source-to-URL Mapping
102
- // -------------------------------------------------------------
103
- console.log('\n7️⃣ Testing Source-to-URL Correlation...');
104
- const map1 = mapUrlToRouteSource('/blog/ai-productivity-2026', nextRoutes);
105
- const map2 = mapUrlToRouteSource('/about', nextRoutes);
106
- console.log(` ✅ URL '/blog/ai-productivity-2026' mapped to -> ${map1.sourceFilePath} (Method: ${map1.resolutionMethod}, Conf: ${map1.confidence})`);
107
- console.log(` ✅ URL '/about' mapped to -> ${map2.sourceFilePath} (Method: ${map2.resolutionMethod}, Conf: ${map2.confidence})`);
108
- // -------------------------------------------------------------
109
- // Test 8: Crawl Graph Analysis & PageRank
110
- // -------------------------------------------------------------
111
- console.log('\n8️⃣ Testing Crawl Graph Analysis...');
112
- const brokenPath = path.join(FIXTURES_DIR, 'broken-site/index.html');
113
- const crawlBuilder = new CrawlGraphBuilder(brokenPath);
114
- const crawlGraph = await crawlBuilder.buildGraph();
115
- console.log(` ✅ Crawl Graph Built: ${crawlGraph.totalNodes} node(s), ${crawlGraph.totalEdges} edge(s), Max Depth: ${crawlGraph.maxClickDepth}`);
116
- // -------------------------------------------------------------
117
- // Test 9: Flagship Project Audit (seo_project_audit)
118
- // -------------------------------------------------------------
119
- console.log('\n9️⃣ Testing Flagship seo_project_audit Tool...');
120
- const auditResult = await auditProject(nextAppPath);
121
- console.log(` ✅ Project Audit Completed:`);
122
- console.log(` - Overall Health Score: ${auditResult.scores.overallHealth}/100 (Confidence: ${auditResult.scores.overallConfidence})`);
123
- console.log(` - Technical: ${auditResult.scores.technical.score}/100 (${auditResult.scores.technical.state})`);
124
- console.log(` - Content: ${auditResult.scores.content.score}/100 (${auditResult.scores.content.state})`);
125
- console.log(` - AI Readiness: ${auditResult.scores.aiReadiness.score}/100 (${auditResult.scores.aiReadiness.state})`);
126
- console.log(` - Total Findings Detected: ${auditResult.totalFindingsCount}`);
127
- console.log(` - Immediate Action: ${auditResult.recommendedImmediateAction}`);
128
- // -------------------------------------------------------------
129
- // Test 10: Deep Root-Cause Diagnosis (seo_diagnose)
130
- // -------------------------------------------------------------
131
- console.log('\n🔟 Testing Deep Diagnostic (seo_diagnose)...');
132
- const diagResult = await diagnoseSeo(nextAppPath, '/about');
133
- console.log(` ✅ Diagnosis for '/about':`);
134
- console.log(` - Matched File: ${diagResult.sourceLocation?.filePath}`);
135
- console.log(` - Issues Detected: ${diagResult.detectedIssues.length}`);
136
- if (diagResult.suggestedFixBlueprints.length > 0) {
137
- console.log(` - Suggested Fix Blueprint: ${diagResult.suggestedFixBlueprints[0].title}`);
138
- }
139
- // -------------------------------------------------------------
140
- // Test 11: Sprint Prioritization (seo_prioritize)
141
- // -------------------------------------------------------------
142
- console.log('\n1️⃣1️⃣ Testing Finding Prioritization (seo_prioritize)...');
143
- const sprintResult = await prioritizeFindings(nextAppPath);
144
- console.log(` ✅ Prioritization Sprints:`);
145
- console.log(` - Quick Wins: ${sprintResult.sprints.quickWins.length} items`);
146
- console.log(` - Critical Blockers: ${sprintResult.sprints.criticalBlockers.length} items`);
147
- console.log(` - Projected Score After Quick Wins: ${sprintResult.projectedScoreImprovement.projectedScoreAfterQuickWins}/100`);
148
- // -------------------------------------------------------------
149
- // Test 12: Fix Plan Generation (seo_fix_plan)
150
- // -------------------------------------------------------------
151
- console.log('\n1️⃣2️⃣ Testing Fix Plan Generation (seo_fix_plan)...');
152
- const fixPlan = await generateFixPlan(nextAppPath);
153
- console.log(` ✅ Fix Plan Generated: ${fixPlan.totalSteps} remediation step(s) ready for AI coding execution.`);
154
- // -------------------------------------------------------------
155
- // Test 13: Snapshot Creation, Diffing & Regression Checks
156
- // -------------------------------------------------------------
157
- console.log('\n1️⃣3️⃣ Testing Snapshot Creation & Regression Verification (seo_snapshot_create & seo_regression_check)...');
158
- const baselineSnap = await createSnapshotTool(nextAppPath);
159
- console.log(` ✅ Baseline Snapshot Created: ID=${baselineSnap.snapshot.snapshotId}, Score=${baselineSnap.snapshot.scores.overallHealth}/100`);
160
- const regCheck = await checkRegression(nextAppPath, baselineSnap.snapshot);
161
- console.log(` ✅ Regression Check: ${regCheck.verdict} (Status: ${regCheck.regressionReport.status})`);
162
- console.log('\n🎉 ALL 13 TEST SUITES (CORE + v1.0.2 ORCHESTRATION) PASSED CLEANLY!\n');
56
+ const nextAdapter = defaultAdapterRegistry.getAdapterForProject(nextAppPath);
57
+ const nextRoutes = nextAdapter.discoverRoutes(nextAppPath);
58
+ console.log(` ✅ Next.js Adapter: ${nextAdapter.name} -> Discovered ${nextRoutes.length} route(s)`);
59
+ const pagesAppPath = path.join(FIXTURES_DIR, 'nextjs-pages');
60
+ const pagesAdapter = defaultAdapterRegistry.getAdapterForProject(pagesAppPath);
61
+ const pagesRoutes = pagesAdapter.discoverRoutes(pagesAppPath);
62
+ console.log(` ✅ Next.js Pages Adapter: ${pagesAdapter.name} -> Discovered ${pagesRoutes.length} route(s)`);
63
+ const viteAppPath = path.join(FIXTURES_DIR, 'vite-react');
64
+ const viteAdapter = defaultAdapterRegistry.getAdapterForProject(viteAppPath);
65
+ const viteRoutes = viteAdapter.discoverRoutes(viteAppPath);
66
+ console.log(` ✅ Vite React Adapter: ${viteAdapter.name} -> Discovered ${viteRoutes.length} route(s)`);
67
+ const remixAppPath = path.join(FIXTURES_DIR, 'remix-app');
68
+ const remixAdapter = defaultAdapterRegistry.getAdapterForProject(remixAppPath);
69
+ const remixRoutes = remixAdapter.discoverRoutes(remixAppPath);
70
+ console.log(` ✅ Remix Adapter: ${remixAdapter.name} -> Discovered ${remixRoutes.length} route(s)`);
71
+ const svelteAppPath = path.join(FIXTURES_DIR, 'sveltekit-app');
72
+ const svelteAdapter = defaultAdapterRegistry.getAdapterForProject(svelteAppPath);
73
+ const svelteRoutes = svelteAdapter.discoverRoutes(svelteAppPath);
74
+ console.log(` ✅ SvelteKit Adapter: ${svelteAdapter.name} -> Discovered ${svelteRoutes.length} route(s)`);
75
+ // -------------------------------------------------------------
76
+ // Test 4: Deep AST & Line-Range Correlation Engine
77
+ // -------------------------------------------------------------
78
+ console.log('\n4️⃣ Testing AST Inspection & Line-Range Locator...');
79
+ const homePageFile = path.join(nextAppPath, 'app/page.tsx');
80
+ const astResult = inspectSourceFileAST(homePageFile);
81
+ console.log(` ✅ AST Analysis for app/page.tsx:`);
82
+ console.log(` - Has Metadata Export: ${astResult.hasMetadataExport} (Lines ${astResult.metadataRange?.startLine}-${astResult.metadataRange?.endLine})`);
83
+ console.log(` - Title Extracted: "${astResult.extractedTitle}"`);
84
+ console.log(` - Canonical Declared: ${astResult.hasCanonicalDeclaration} ("${astResult.extractedCanonical}")`);
85
+ // -------------------------------------------------------------
86
+ // Test 5: Formal Invariant Registry & Evaluation
87
+ // -------------------------------------------------------------
88
+ console.log('\n5️⃣ Testing Formal Invariant Registry...');
89
+ const allInvariants = defaultInvariantRegistry.getAll();
90
+ console.log(` ✅ Loaded Invariants: ${allInvariants.length} formal rules (${allInvariants.map(i => i.id).join(', ')})`);
91
+ const snapResult = await createSnapshotTool(nextAppPath);
92
+ console.log(` ✅ Snapshot ID: ${snapResult.snapshot.snapshotId}`);
93
+ console.log(` ✅ Formal Invariants Evaluated: ${snapResult.snapshot.invariants?.length || 0} invariant check(s) recorded`);
94
+ const regCheck = await checkRegression(nextAppPath, snapResult.snapshot);
95
+ console.log(` ✅ Invariant Regression Check: ${regCheck.verdict}`);
96
+ // -------------------------------------------------------------
97
+ // Test 6: SARIF v2.1.0 Exporter
98
+ // -------------------------------------------------------------
99
+ console.log('\n6️⃣ Testing SARIF v2.1.0 Exporter for CI/CD...');
100
+ const sarif = exportFindingsToSarif(snapResult.snapshot.findings, nextAppPath);
101
+ console.log(` ✅ SARIF Report Generated: Version ${sarif.version}, Tool: ${sarif.runs[0].tool.driver.name}, Rules: ${sarif.runs[0].tool.driver.rules.length}, Results: ${sarif.runs[0].results.length}`);
102
+ // -------------------------------------------------------------
103
+ // Test 7: Cache Manager with Provenance
104
+ // -------------------------------------------------------------
105
+ console.log('\n7️⃣ Testing Content-Hash Cache Manager with Provenance...');
106
+ const cacheKey = defaultCacheManager.computeKey('test_ast', { file: 'page.tsx', size: 500 });
107
+ defaultCacheManager.set(cacheKey, { parsed: true }, 10000, 'typescript_ast');
108
+ const cachedVal = defaultCacheManager.getWithMetadata(cacheKey);
109
+ console.log(` ✅ Cache Provenance: Provider=${cachedVal?.metadata.provider}, Age=${cachedVal?.metadata.ageMs}ms, Key=${cachedVal?.metadata.key}`);
110
+ // -------------------------------------------------------------
111
+ // Test 8: Pluggable Provider Abstraction
112
+ // -------------------------------------------------------------
113
+ console.log('\n8️⃣ Testing Pluggable Provider Abstraction...');
114
+ const serpProv = defaultProviderRegistry.getSerpProvider();
115
+ const speedProv = defaultProviderRegistry.getPageSpeedProvider();
116
+ console.log(` ✅ Default SERP Provider: ${serpProv.name} (Free: ${serpProv.isFree})`);
117
+ console.log(` ✅ Default PageSpeed Provider: ${speedProv.name}`);
118
+ // -------------------------------------------------------------
119
+ // Test 9: Opportunity Engine
120
+ // -------------------------------------------------------------
121
+ console.log('\n9️⃣ Testing Strategic Opportunity Engine...');
122
+ const opps = await detectOpportunities(nextAppPath, ['saas pricing calculator', 'agile workflows']);
123
+ console.log(` ✅ Opportunities Discovered: ${opps.totalOpportunitiesFound} strategic opportunity item(s)`);
124
+ // -------------------------------------------------------------
125
+ // Test 10: Flagship Project Audit & Sprint Prioritization
126
+ // -------------------------------------------------------------
127
+ console.log('\n🔟 Testing Flagship Project Audit & Sprint Prioritization...');
128
+ const audit = await auditProject(nextAppPath);
129
+ const sprints = await prioritizeFindings(nextAppPath);
130
+ console.log(` ✅ Audit Complete: Overall Score=${audit.scores.overallHealth}/100 | Dynamic Routes=${audit.routesSummary.dynamicRoutesCount}`);
131
+ console.log(` ✅ Priority Sprints: Quick Wins=${sprints.sprints.quickWins.length}, Critical=${sprints.sprints.criticalBlockers.length}`);
132
+ console.log('\n🎉 ALL v1.2.0 ARCHITECTURAL & CI TEST SUITES PASSED CLEANLY!\n');
163
133
  }
164
134
  runTests().catch(err => {
165
135
  console.error('❌ Test failed:', err);
@@ -1,8 +1,9 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { detectFramework, discoverRoutes, mapUrlToRouteSource } from '../utils/projectScanner.js';
3
+ import { getProjectAdapter } from '../utils/projectScanner.js';
4
4
  import { createProjectSnapshot, compareSnapshots } from '../utils/snapshotEngine.js';
5
5
  import { calculateMultiDimensionalScores } from '../utils/findingEngine.js';
6
+ import { inspectSourceFileAST } from '../utils/astLocator.js';
6
7
  export async function auditProject(projectPath, baseUrl, crawlDepth = 2) {
7
8
  const snapshot = await createProjectSnapshot(projectPath, {
8
9
  baseUrl,
@@ -21,7 +22,6 @@ export async function auditProject(projectPath, baseUrl, crawlDepth = 2) {
21
22
  for (const f of snapshot.findings) {
22
23
  findingsByCategory[f.category] = (findingsByCategory[f.category] || 0) + 1;
23
24
  }
24
- // Sort by priorityScore descending
25
25
  const sortedFindings = [...snapshot.findings].sort((a, b) => b.priorityScore - a.priorityScore);
26
26
  const missingMetaCount = snapshot.discoveredRoutes.filter(r => r.routeType === 'page' && !r.hasMetadataExport && !r.hasGenerateMetadata).length;
27
27
  const missingSchemaCount = snapshot.discoveredRoutes.filter(r => r.routeType === 'page' && !r.hasSchemaMarkup).length;
@@ -34,6 +34,7 @@ export async function auditProject(projectPath, baseUrl, crawlDepth = 2) {
34
34
  schemaVersion: 'seo.gravity/v1',
35
35
  projectPath: snapshot.projectPath,
36
36
  scannedAt: snapshot.createdAt,
37
+ gitMetadata: snapshot.gitMetadata,
37
38
  framework: snapshot.frameworkInfo,
38
39
  routesSummary: {
39
40
  totalDiscovered: snapshot.discoveredRoutes.length,
@@ -60,9 +61,10 @@ export async function auditProject(projectPath, baseUrl, crawlDepth = 2) {
60
61
  }
61
62
  export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
62
63
  const resolvedPath = path.resolve(projectPath);
63
- const frameworkInfo = detectFramework(resolvedPath);
64
- const discoveredRoutes = discoverRoutes(resolvedPath, frameworkInfo);
65
- const mapping = mapUrlToRouteSource(targetUrlOrFile, discoveredRoutes);
64
+ const adapter = getProjectAdapter(resolvedPath);
65
+ const frameworkInfo = adapter.getProjectInfo(resolvedPath);
66
+ const discoveredRoutes = adapter.discoverRoutes(resolvedPath);
67
+ const mapping = adapter.mapRouteToSource(targetUrlOrFile, discoveredRoutes);
66
68
  let sourceFile = mapping.sourceFilePath;
67
69
  if (!sourceFile && fs.existsSync(path.join(resolvedPath, targetUrlOrFile))) {
68
70
  sourceFile = targetUrlOrFile;
@@ -70,22 +72,18 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
70
72
  const detectedIssues = [];
71
73
  const rootCauses = [];
72
74
  const fixBlueprints = [];
75
+ let ast = null;
73
76
  let fileContent = '';
74
77
  if (sourceFile) {
75
- try {
76
- fileContent = fs.readFileSync(path.join(resolvedPath, sourceFile), 'utf-8');
77
- }
78
- catch {
79
- // File read failed
78
+ const absPath = path.join(resolvedPath, sourceFile);
79
+ if (fs.existsSync(absPath)) {
80
+ fileContent = fs.readFileSync(absPath, 'utf-8');
81
+ ast = inspectSourceFileAST(absPath);
80
82
  }
81
83
  }
82
84
  // Next.js App Router diagnostics
83
- if (frameworkInfo.framework === 'nextjs-app-router' && sourceFile) {
84
- const hasMeta = /export\s+const\s+metadata\b/.test(fileContent);
85
- const hasGenMeta = /export\s+(async\s+)?function\s+generateMetadata\b/.test(fileContent);
86
- const hasCanonical = /canonical/.test(fileContent);
87
- const hasSchema = /application\/ld\+json/.test(fileContent);
88
- if (!hasMeta && !hasGenMeta) {
85
+ if (frameworkInfo.framework === 'nextjs-app-router' && sourceFile && ast) {
86
+ if (!ast.hasMetadataExport && !ast.hasGenerateMetadata) {
89
87
  const issueId = 'SEO-METATAG-001';
90
88
  detectedIssues.push({
91
89
  id: issueId,
@@ -96,7 +94,8 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
96
94
  evidenceType: 'observed',
97
95
  evidence: `File '${sourceFile}' contains no 'metadata' or 'generateMetadata' export.`,
98
96
  affectedUrl: targetUrlOrFile,
99
- sourceLocation: { filePath: sourceFile, line: 1 },
97
+ sourceLocation: { filePath: sourceFile, line: 1, startLine: 1, endLine: 1 },
98
+ sourceRange: { filePath: sourceFile, startLine: 1, endLine: 1, astNodeType: 'SourceFile' },
100
99
  likelyRootCause: 'Next.js App Router relies on explicit metadata exports to generate <title> and <meta> tags.',
101
100
  recommendation: 'Export a Metadata object or dynamic generateMetadata function.',
102
101
  expectedImpact: 'Generates proper search snippet title and description.',
@@ -113,18 +112,20 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
113
112
  issueId,
114
113
  description: 'Missing Next.js metadata export in page component.',
115
114
  sourceCodeSnippet: fileContent.slice(0, 150),
115
+ sourceRange: { filePath: sourceFile, startLine: 1, endLine: 1 },
116
116
  whyItOccurs: 'Without metadata export, Next.js falls back to empty root title, causing generic SERP snippets.'
117
117
  });
118
118
  fixBlueprints.push({
119
119
  issueId,
120
120
  title: 'Add static metadata export',
121
121
  targetFile: sourceFile,
122
+ sourceRange: { filePath: sourceFile, startLine: 1, endLine: 1 },
122
123
  actionType: 'modify_file',
123
124
  codeToInsertOrReplace: `import type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n title: 'Descriptive Page Title | Brand',\n description: 'High-converting search meta description under 155 characters.',\n};\n`,
124
125
  verificationInstructions: 'Run `npm run build` to ensure type-check passes, then curl page and inspect <head>.'
125
126
  });
126
127
  }
127
- if (!hasCanonical) {
128
+ if (!ast.hasCanonicalDeclaration) {
128
129
  const issueId = 'SEO-CANONICAL-001';
129
130
  detectedIssues.push({
130
131
  id: issueId,
@@ -135,7 +136,8 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
135
136
  evidenceType: 'observed',
136
137
  evidence: `File '${sourceFile}' does not declare alternates.canonical.`,
137
138
  affectedUrl: targetUrlOrFile,
138
- sourceLocation: { filePath: sourceFile },
139
+ sourceLocation: { filePath: sourceFile, line: 1 },
140
+ sourceRange: ast.canonicalRange || { filePath: sourceFile, startLine: 1, endLine: 1 },
139
141
  likelyRootCause: 'Page does not declare its canonical master URL, risking duplicate content indexation.',
140
142
  recommendation: 'Add alternates.canonical to page metadata.',
141
143
  expectedImpact: 'Consolidates ranking signals onto the primary URL.',
@@ -152,6 +154,7 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
152
154
  issueId,
153
155
  title: 'Add canonical tag to metadata',
154
156
  targetFile: sourceFile,
157
+ sourceRange: ast.metadataRange,
155
158
  actionType: 'modify_file',
156
159
  codeToInsertOrReplace: `alternates: {\n canonical: 'https://yourdomain.com${mapping.matchedRoute?.routePath || ''}',\n},`,
157
160
  verificationInstructions: 'Verify <link rel="canonical"> in server response.'
@@ -163,6 +166,7 @@ export async function diagnoseSeo(projectPath, targetUrlOrFile, focusIssueId) {
163
166
  targetUrlOrFile,
164
167
  matchedRoute: mapping.matchedRoute,
165
168
  sourceLocation: sourceFile ? { filePath: sourceFile, line: 1 } : undefined,
169
+ sourceRange: ast?.metadataRange || (sourceFile ? { filePath: sourceFile, startLine: 1, endLine: 1 } : undefined),
166
170
  detectedIssues,
167
171
  likelyRootCauses: rootCauses,
168
172
  suggestedFixBlueprints: fixBlueprints
@@ -195,14 +199,12 @@ export async function prioritizeFindings(findingsOrProjectPath, maxCount = 20) {
195
199
  minorPolish.push(f);
196
200
  }
197
201
  }
198
- // Sort each sprint by priorityScore descending
199
202
  quickWins.sort((a, b) => b.priorityScore - a.priorityScore);
200
203
  criticalBlockers.sort((a, b) => b.priorityScore - a.priorityScore);
201
204
  architecturalImprovements.sort((a, b) => b.priorityScore - a.priorityScore);
202
205
  minorPolish.sort((a, b) => b.priorityScore - a.priorityScore);
203
206
  const initialScores = calculateMultiDimensionalScores(findingsList);
204
207
  const currentScore = initialScores.overallHealth;
205
- // Estimate post quick-wins score
206
208
  const remainingAfterQuickWins = findingsList.filter(f => !quickWins.includes(f));
207
209
  const scoreAfterQuickWins = calculateMultiDimensionalScores(remainingAfterQuickWins).overallHealth;
208
210
  const totalEffortHours = quickWins.length * 0.25 +
@@ -0,0 +1,64 @@
1
+ export type ProvenanceSource = 'rendered_dom' | 'raw_html' | 'ast_inspection' | 'http_headers' | 'robots_txt' | 'sitemap_xml' | 'static_analysis' | 'provider_api';
2
+ export interface Provenance {
3
+ analyzer: string;
4
+ source: ProvenanceSource;
5
+ timestamp: string;
6
+ provider: string;
7
+ }
8
+ export interface PageIdentity {
9
+ logicalPageId: string;
10
+ canonicalUrl?: string;
11
+ observedUrl: string;
12
+ routePattern?: string;
13
+ normalizedPath: string;
14
+ }
15
+ export interface Observation {
16
+ id: string;
17
+ logicalPageId: string;
18
+ observedUrl: string;
19
+ key: string;
20
+ rawValue: any;
21
+ normalizedValue?: string | number | boolean;
22
+ provenance: Provenance;
23
+ }
24
+ export interface ASTSourceRange {
25
+ filePath: string;
26
+ startLine: number;
27
+ endLine: number;
28
+ startColumn?: number;
29
+ endColumn?: number;
30
+ astNodeType?: string;
31
+ exportName?: string;
32
+ snippet?: string;
33
+ }
34
+ export type InvariantType = 'INVARIANT_HTTP_200' | 'INVARIANT_CANONICAL_PRESENT' | 'INVARIANT_INDEXABLE' | 'INVARIANT_SCHEMA_VALID' | 'INVARIANT_TITLE_PRESENT' | 'INVARIANT_DESCRIPTION_PRESENT' | 'INVARIANT_LINK_ACCESSIBLE' | 'INVARIANT_ROBOTS_ALLOWED';
35
+ export interface SEOInvariant {
36
+ id: InvariantType;
37
+ logicalPageId: string;
38
+ url: string;
39
+ description: string;
40
+ expectedCondition: string;
41
+ observedCondition: string;
42
+ satisfied: boolean;
43
+ provenance: Provenance;
44
+ }
45
+ export type InvariantDiffStatus = 'NEW_REGRESSION' | 'RESOLVED' | 'UNCHANGED' | 'EXPECTED_CHANGE' | 'UNKNOWN';
46
+ export interface InvariantDiffItem {
47
+ invariantId: InvariantType;
48
+ logicalPageId: string;
49
+ url: string;
50
+ status: InvariantDiffStatus;
51
+ baselineSatisfied: boolean;
52
+ currentSatisfied: boolean;
53
+ message: string;
54
+ sourceRange?: ASTSourceRange;
55
+ }
56
+ export interface GitMetadata {
57
+ commitHash?: string;
58
+ shortHash?: string;
59
+ branch?: string;
60
+ isDirty?: boolean;
61
+ author?: string;
62
+ commitDate?: string;
63
+ changedFilesSinceBase?: string[];
64
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,5 @@
1
+ import { Provenance, Observation, ASTSourceRange, SEOInvariant, InvariantDiffItem, GitMetadata } from './canonical.js';
2
+ export * from './canonical.js';
1
3
  export type FindingCategory = 'technical' | 'content' | 'discoverability' | 'authority' | 'entity' | 'performance' | 'ai_readiness';
2
4
  export type SeverityLevel = 'critical' | 'high' | 'medium' | 'low' | 'info';
3
5
  export type EvidenceType = 'observed' | 'derived' | 'heuristic' | 'predictive';
@@ -8,6 +10,8 @@ export interface SourceLocation {
8
10
  filePath: string;
9
11
  line?: number;
10
12
  column?: number;
13
+ startLine?: number;
14
+ endLine?: number;
11
15
  exportName?: string;
12
16
  componentName?: string;
13
17
  snippet?: string;
@@ -18,17 +22,24 @@ export interface SuggestedFix {
18
22
  explanation: string;
19
23
  snippet?: string;
20
24
  diffPreview?: string;
25
+ startLine?: number;
26
+ endLine?: number;
21
27
  }
22
28
  export interface Finding {
23
29
  id: string;
30
+ ruleId?: string;
24
31
  category: FindingCategory;
25
32
  title: string;
26
33
  severity: SeverityLevel;
27
34
  confidence: number;
28
35
  evidenceType: EvidenceType;
29
36
  evidence: string;
37
+ observations?: Observation[];
38
+ provenance?: Provenance;
30
39
  affectedUrl: string;
40
+ logicalPageId?: string;
31
41
  sourceLocation?: SourceLocation;
42
+ sourceRange?: ASTSourceRange;
32
43
  likelyRootCause?: string;
33
44
  recommendation: string;
34
45
  suggestedFix?: SuggestedFix;
@@ -88,16 +99,19 @@ export interface DiscoveredRoute {
88
99
  hasSchemaMarkup: boolean;
89
100
  hasHeadComponent: boolean;
90
101
  isClientComponent: boolean;
102
+ astSourceRange?: ASTSourceRange;
91
103
  }
92
104
  export interface RouteSourceMapping {
93
105
  urlPath: string;
94
106
  matchedRoute?: DiscoveredRoute;
95
107
  sourceFilePath?: string;
108
+ sourceRange?: ASTSourceRange;
96
109
  confidence: number;
97
110
  resolutionMethod: 'exact_match' | 'pattern_match' | 'fallback_heuristic' | 'unmapped';
98
111
  }
99
112
  export interface CrawlGraphNode {
100
113
  url: string;
114
+ logicalPageId?: string;
101
115
  routePath?: string;
102
116
  title: string;
103
117
  statusCode: number;
@@ -134,10 +148,13 @@ export interface ProjectSnapshot {
134
148
  snapshotId: string;
135
149
  createdAt: string;
136
150
  projectPath: string;
151
+ gitMetadata?: GitMetadata;
137
152
  frameworkInfo: ProjectFrameworkInfo;
138
153
  discoveredRoutes: DiscoveredRoute[];
139
154
  scores: MultiDimensionalScores;
140
155
  findings: Finding[];
156
+ observations?: Observation[];
157
+ invariants?: SEOInvariant[];
141
158
  crawlGraph?: CrawlGraphSummary;
142
159
  routeMappings: RouteSourceMapping[];
143
160
  }
@@ -157,6 +174,7 @@ export interface RegressionReport {
157
174
  resolvedFindings: Finding[];
158
175
  newRegressions: Finding[];
159
176
  unresolvedFindings: Finding[];
177
+ invariantDiffs?: InvariantDiffItem[];
160
178
  totalResolvedCount: number;
161
179
  totalNewRegressionsCount: number;
162
180
  alerts: string[];
@@ -165,6 +183,7 @@ export interface ProjectAuditResult {
165
183
  schemaVersion: 'seo.gravity/v1';
166
184
  projectPath: string;
167
185
  scannedAt: string;
186
+ gitMetadata?: GitMetadata;
168
187
  framework: ProjectFrameworkInfo;
169
188
  routesSummary: {
170
189
  totalDiscovered: number;
@@ -189,17 +208,20 @@ export interface DiagnosticResult {
189
208
  targetUrlOrFile: string;
190
209
  matchedRoute?: DiscoveredRoute;
191
210
  sourceLocation?: SourceLocation;
211
+ sourceRange?: ASTSourceRange;
192
212
  detectedIssues: Finding[];
193
213
  likelyRootCauses: Array<{
194
214
  issueId: string;
195
215
  description: string;
196
216
  sourceCodeSnippet?: string;
217
+ sourceRange?: ASTSourceRange;
197
218
  whyItOccurs: string;
198
219
  }>;
199
220
  suggestedFixBlueprints: Array<{
200
221
  issueId: string;
201
222
  title: string;
202
223
  targetFile: string;
224
+ sourceRange?: ASTSourceRange;
203
225
  actionType: 'modify_file' | 'create_file' | 'config_update';
204
226
  codeToInsertOrReplace: string;
205
227
  verificationInstructions: string;
@@ -1 +1 @@
1
- export {};
1
+ export * from './canonical.js';