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
@@ -1,353 +1,21 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
3
- export function detectFramework(projectDir) {
2
+ import { defaultAdapterRegistry } from '../adapters/adapterRegistry.js';
3
+ export function getProjectAdapter(projectDir) {
4
4
  const resolvedDir = path.resolve(projectDir);
5
- const pkgPath = path.join(resolvedDir, 'package.json');
6
- let pkg = {};
7
- if (fs.existsSync(pkgPath)) {
8
- try {
9
- pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
10
- }
11
- catch {
12
- // Ignored
13
- }
14
- }
15
- const allDeps = {
16
- ...(pkg.dependencies || {}),
17
- ...(pkg.devDependencies || {})
18
- };
19
- const hasFile = (relPath) => fs.existsSync(path.join(resolvedDir, relPath));
20
- const hasDir = (relPath) => {
21
- const p = path.join(resolvedDir, relPath);
22
- return fs.existsSync(p) && fs.statSync(p).isDirectory();
23
- };
24
- let framework = 'unknown';
25
- let routesDir;
26
- let devCommand = pkg.scripts?.dev ? 'npm run dev' : undefined;
27
- let defaultDevPort = 3000;
28
- // Next.js App Router vs Pages Router
29
- if (allDeps['next']) {
30
- if (hasDir('app') || hasDir('src/app')) {
31
- framework = 'nextjs-app-router';
32
- routesDir = hasDir('app') ? 'app' : 'src/app';
33
- }
34
- else {
35
- framework = 'nextjs-pages-router';
36
- routesDir = hasDir('pages') ? 'pages' : 'src/pages';
37
- }
38
- defaultDevPort = 3000;
39
- }
40
- // Astro
41
- else if (allDeps['astro'] || hasFile('astro.config.mjs') || hasFile('astro.config.ts')) {
42
- framework = 'astro';
43
- routesDir = hasDir('src/pages') ? 'src/pages' : 'pages';
44
- defaultDevPort = 4321;
45
- }
46
- // Remix
47
- else if (allDeps['@remix-run/react'] || allDeps['@remix-run/node']) {
48
- framework = 'remix';
49
- routesDir = hasDir('app/routes') ? 'app/routes' : 'routes';
50
- defaultDevPort = 3000;
51
- }
52
- // SvelteKit
53
- else if (allDeps['@sveltejs/kit']) {
54
- framework = 'sveltekit';
55
- routesDir = hasDir('src/routes') ? 'src/routes' : 'routes';
56
- defaultDevPort = 5173;
57
- }
58
- // Nuxt
59
- else if (allDeps['nuxt'] || allDeps['nuxt3']) {
60
- framework = 'nuxt';
61
- routesDir = hasDir('pages') ? 'pages' : 'src/pages';
62
- defaultDevPort = 3000;
63
- }
64
- // Vite / React SPA
65
- else if (allDeps['vite'] || allDeps['react']) {
66
- framework = 'vite-react';
67
- routesDir = hasDir('src') ? 'src' : '.';
68
- defaultDevPort = 5173;
69
- }
70
- // Static HTML
71
- else if (hasFile('index.html')) {
72
- framework = 'static-html';
73
- routesDir = '.';
74
- defaultDevPort = 8080;
75
- }
76
- // Detect sitemap, robots, llms.txt
77
- const hasSitemapConfig = hasFile('public/sitemap.xml') ||
78
- hasFile('sitemap.xml') ||
79
- hasFile('app/sitemap.ts') ||
80
- hasFile('app/sitemap.js') ||
81
- hasFile('src/app/sitemap.ts') ||
82
- hasFile('src/app/sitemap.js') ||
83
- hasFile('src/pages/sitemap.xml.ts');
84
- const hasRobotsConfig = hasFile('public/robots.txt') ||
85
- hasFile('robots.txt') ||
86
- hasFile('app/robots.ts') ||
87
- hasFile('app/robots.js') ||
88
- hasFile('src/app/robots.ts') ||
89
- hasFile('src/app/robots.js');
90
- const hasLlmsTxt = hasFile('public/llms.txt') ||
91
- hasFile('llms.txt') ||
92
- hasFile('public/llms-full.txt') ||
93
- hasFile('llms-full.txt');
94
- const hasTypeScript = hasFile('tsconfig.json') ||
95
- Boolean(allDeps['typescript']);
96
- // Package manager detection
97
- let packageManager = 'npm';
98
- if (hasFile('pnpm-lock.yaml'))
99
- packageManager = 'pnpm';
100
- else if (hasFile('yarn.lock'))
101
- packageManager = 'yarn';
102
- else if (hasFile('bun.lockb'))
103
- packageManager = 'bun';
104
- return {
105
- framework,
106
- name: pkg.name || path.basename(resolvedDir),
107
- version: pkg.version,
108
- packageManager,
109
- hasTypeScript,
110
- hasSitemapConfig,
111
- hasRobotsConfig,
112
- hasLlmsTxt,
113
- rootDir: resolvedDir,
114
- routesDir,
115
- devCommand,
116
- defaultDevPort
117
- };
5
+ return defaultAdapterRegistry.getAdapterForProject(resolvedDir);
118
6
  }
119
- export function discoverRoutes(projectDir, frameworkInfo) {
7
+ export function detectFramework(projectDir) {
120
8
  const resolvedDir = path.resolve(projectDir);
121
- const info = frameworkInfo || detectFramework(resolvedDir);
122
- const discovered = [];
123
- if (!info.routesDir)
124
- return discovered;
125
- const fullRoutesDir = path.join(resolvedDir, info.routesDir);
126
- if (!fs.existsSync(fullRoutesDir))
127
- return discovered;
128
- const readRouteFileContent = (relFile) => {
129
- try {
130
- const fullPath = path.join(resolvedDir, relFile);
131
- const content = fs.readFileSync(fullPath, 'utf-8');
132
- return {
133
- hasMetadataExport: /export\s+const\s+metadata\b/.test(content),
134
- hasGenerateMetadata: /export\s+(async\s+)?function\s+generateMetadata\b/.test(content),
135
- hasSchemaMarkup: /application\/ld\+json/.test(content) || /schema\.org/.test(content),
136
- hasHeadComponent: /<Head\b|<Helmet\b|astro:head|<meta\b|<title\b/.test(content),
137
- isClientComponent: /['"]use client['"]/.test(content)
138
- };
139
- }
140
- catch {
141
- return {
142
- hasMetadataExport: false,
143
- hasGenerateMetadata: false,
144
- hasSchemaMarkup: false,
145
- hasHeadComponent: false,
146
- isClientComponent: false
147
- };
148
- }
149
- };
150
- // Next.js App Router Scanner
151
- if (info.framework === 'nextjs-app-router') {
152
- const scanAppDir = (currentDir, routePrefix) => {
153
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
154
- for (const entry of entries) {
155
- const entryPath = path.join(currentDir, entry.name);
156
- if (entry.isDirectory()) {
157
- // Ignore route groups like (marketing) in path
158
- const segment = entry.name.startsWith('(') && entry.name.endsWith(')') ? '' : entry.name;
159
- const nextPrefix = segment ? (routePrefix === '/' ? `/${segment}` : `${routePrefix}/${segment}`) : routePrefix;
160
- scanAppDir(entryPath, nextPrefix);
161
- }
162
- else if (entry.isFile()) {
163
- if (/^page\.(tsx|jsx|js|ts)$/.test(entry.name)) {
164
- const relFile = path.relative(resolvedDir, entryPath).replace(/\\/g, '/');
165
- const routePath = routePrefix === '' ? '/' : routePrefix;
166
- const dynamicParams = extractDynamicParams(routePath);
167
- const meta = readRouteFileContent(relFile);
168
- discovered.push({
169
- routePath,
170
- sourceFilePath: relFile,
171
- isDynamic: dynamicParams.length > 0,
172
- dynamicParams,
173
- routeType: 'page',
174
- ...meta
175
- });
176
- }
177
- else if (/^sitemap\.(ts|js|xml)$/.test(entry.name)) {
178
- const relFile = path.relative(resolvedDir, entryPath).replace(/\\/g, '/');
179
- discovered.push({
180
- routePath: '/sitemap.xml',
181
- sourceFilePath: relFile,
182
- isDynamic: false,
183
- dynamicParams: [],
184
- routeType: 'sitemap',
185
- ...readRouteFileContent(relFile)
186
- });
187
- }
188
- else if (/^robots\.(ts|js|txt)$/.test(entry.name)) {
189
- const relFile = path.relative(resolvedDir, entryPath).replace(/\\/g, '/');
190
- discovered.push({
191
- routePath: '/robots.txt',
192
- sourceFilePath: relFile,
193
- isDynamic: false,
194
- dynamicParams: [],
195
- routeType: 'robots',
196
- ...readRouteFileContent(relFile)
197
- });
198
- }
199
- }
200
- }
201
- };
202
- scanAppDir(fullRoutesDir, '/');
203
- }
204
- // Astro Scanner
205
- else if (info.framework === 'astro') {
206
- const scanAstroDir = (currentDir, routePrefix) => {
207
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
208
- for (const entry of entries) {
209
- const entryPath = path.join(currentDir, entry.name);
210
- if (entry.isDirectory()) {
211
- const nextPrefix = routePrefix === '/' ? `/${entry.name}` : `${routePrefix}/${entry.name}`;
212
- scanAstroDir(entryPath, nextPrefix);
213
- }
214
- else if (entry.isFile() && (entry.name.endsWith('.astro') || entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
215
- const baseName = entry.name.replace(/\.(astro|md|mdx)$/, '');
216
- let routePath = routePrefix;
217
- if (baseName !== 'index') {
218
- routePath = routePrefix === '/' ? `/${baseName}` : `${routePrefix}/${baseName}`;
219
- }
220
- if (routePath === '')
221
- routePath = '/';
222
- const relFile = path.relative(resolvedDir, entryPath).replace(/\\/g, '/');
223
- const dynamicParams = extractDynamicParams(routePath);
224
- const meta = readRouteFileContent(relFile);
225
- discovered.push({
226
- routePath,
227
- sourceFilePath: relFile,
228
- isDynamic: dynamicParams.length > 0,
229
- dynamicParams,
230
- routeType: 'page',
231
- ...meta
232
- });
233
- }
234
- }
235
- };
236
- scanAstroDir(fullRoutesDir, '/');
237
- }
238
- // Pages Router / Nuxt / SvelteKit / Static
239
- else {
240
- const scanGenericDir = (currentDir, routePrefix) => {
241
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
242
- for (const entry of entries) {
243
- const entryPath = path.join(currentDir, entry.name);
244
- if (entry.isDirectory()) {
245
- if (['node_modules', '.git', '.next', 'dist', 'build'].includes(entry.name))
246
- continue;
247
- const nextPrefix = routePrefix === '/' ? `/${entry.name}` : `${routePrefix}/${entry.name}`;
248
- scanGenericDir(entryPath, nextPrefix);
249
- }
250
- else if (entry.isFile() && /\.(tsx|jsx|js|ts|vue|svelte|html)$/.test(entry.name)) {
251
- const baseName = entry.name.replace(/\.(tsx|jsx|js|ts|vue|svelte|html)$/, '');
252
- let routePath = routePrefix;
253
- if (baseName !== 'index' && baseName !== 'page') {
254
- routePath = routePrefix === '/' ? `/${baseName}` : `${routePrefix}/${baseName}`;
255
- }
256
- if (routePath === '')
257
- routePath = '/';
258
- const relFile = path.relative(resolvedDir, entryPath).replace(/\\/g, '/');
259
- const dynamicParams = extractDynamicParams(routePath);
260
- const meta = readRouteFileContent(relFile);
261
- discovered.push({
262
- routePath,
263
- sourceFilePath: relFile,
264
- isDynamic: dynamicParams.length > 0,
265
- dynamicParams,
266
- routeType: 'page',
267
- ...meta
268
- });
269
- }
270
- }
271
- };
272
- scanGenericDir(fullRoutesDir, '/');
273
- }
274
- return discovered;
9
+ const adapter = defaultAdapterRegistry.getAdapterForProject(resolvedDir);
10
+ return adapter.getProjectInfo(resolvedDir);
275
11
  }
276
- function extractDynamicParams(routePath) {
277
- const params = [];
278
- const matches = routePath.match(/\[\.{0,3}([a-zA-Z0-9_-]+)\]/g);
279
- if (matches) {
280
- for (const m of matches) {
281
- params.push(m.replace(/[\[\].]/g, ''));
282
- }
283
- }
284
- return params;
12
+ export function discoverRoutes(projectDir, _frameworkInfo) {
13
+ const resolvedDir = path.resolve(projectDir);
14
+ const adapter = defaultAdapterRegistry.getAdapterForProject(resolvedDir);
15
+ return adapter.discoverRoutes(resolvedDir);
285
16
  }
286
- export function mapUrlToRouteSource(targetUrl, discoveredRoutes) {
287
- let urlPath = targetUrl;
288
- try {
289
- if (targetUrl.startsWith('http')) {
290
- urlPath = new URL(targetUrl).pathname;
291
- }
292
- }
293
- catch {
294
- urlPath = targetUrl;
295
- }
296
- urlPath = urlPath.replace(/\/$/, '') || '/';
297
- // 1. Exact Match
298
- for (const r of discoveredRoutes) {
299
- const cleanRoute = r.routePath.replace(/\/$/, '') || '/';
300
- if (cleanRoute === urlPath) {
301
- return {
302
- urlPath,
303
- matchedRoute: r,
304
- sourceFilePath: r.sourceFilePath,
305
- confidence: 1.0,
306
- resolutionMethod: 'exact_match'
307
- };
308
- }
309
- }
310
- // 2. Dynamic Pattern Match (e.g. /blog/my-post -> /blog/[slug])
311
- for (const r of discoveredRoutes) {
312
- if (!r.isDynamic)
313
- continue;
314
- const regexStr = '^' + r.routePath
315
- .replace(/\[\.\.\.([a-zA-Z0-9_-]+)\]/g, '.*')
316
- .replace(/\[([a-zA-Z0-9_-]+)\]/g, '[^/]+') + '$';
317
- try {
318
- const reg = new RegExp(regexStr);
319
- if (reg.test(urlPath)) {
320
- return {
321
- urlPath,
322
- matchedRoute: r,
323
- sourceFilePath: r.sourceFilePath,
324
- confidence: 0.9,
325
- resolutionMethod: 'pattern_match'
326
- };
327
- }
328
- }
329
- catch {
330
- // Ignored
331
- }
332
- }
333
- // 3. Fallback Heuristic
334
- const segments = urlPath.split('/').filter(Boolean);
335
- if (segments.length > 0) {
336
- const parentSegment = `/${segments[0]}`;
337
- const parentRoute = discoveredRoutes.find(r => r.routePath.startsWith(parentSegment));
338
- if (parentRoute) {
339
- return {
340
- urlPath,
341
- matchedRoute: parentRoute,
342
- sourceFilePath: parentRoute.sourceFilePath,
343
- confidence: 0.6,
344
- resolutionMethod: 'fallback_heuristic'
345
- };
346
- }
347
- }
348
- return {
349
- urlPath,
350
- confidence: 0.0,
351
- resolutionMethod: 'unmapped'
352
- };
17
+ export function mapUrlToRouteSource(targetUrl, discoveredRoutes, projectDir = '.') {
18
+ const resolvedDir = path.resolve(projectDir);
19
+ const adapter = defaultAdapterRegistry.getAdapterForProject(resolvedDir);
20
+ return adapter.mapRouteToSource(targetUrl, discoveredRoutes);
353
21
  }
@@ -0,0 +1,64 @@
1
+ import { Finding } from '../types/findings.js';
2
+ export interface SarifPhysicalLocation {
3
+ artifactLocation: {
4
+ uri: string;
5
+ uriBaseId?: string;
6
+ };
7
+ region?: {
8
+ startLine: number;
9
+ startColumn?: number;
10
+ endLine?: number;
11
+ endColumn?: number;
12
+ snippet?: {
13
+ text: string;
14
+ };
15
+ };
16
+ }
17
+ export interface SarifResult {
18
+ ruleId: string;
19
+ ruleIndex?: number;
20
+ level: 'error' | 'warning' | 'note' | 'none';
21
+ message: {
22
+ text: string;
23
+ };
24
+ locations?: Array<{
25
+ physicalLocation: SarifPhysicalLocation;
26
+ }>;
27
+ fixes?: Array<{
28
+ description: {
29
+ text: string;
30
+ };
31
+ }>;
32
+ }
33
+ export interface SarifRule {
34
+ id: string;
35
+ name: string;
36
+ shortDescription: {
37
+ text: string;
38
+ };
39
+ fullDescription?: {
40
+ text: string;
41
+ };
42
+ help?: {
43
+ text: string;
44
+ };
45
+ defaultConfiguration: {
46
+ level: 'error' | 'warning' | 'note';
47
+ };
48
+ }
49
+ export interface SarifReport {
50
+ $schema: string;
51
+ version: string;
52
+ runs: Array<{
53
+ tool: {
54
+ driver: {
55
+ name: string;
56
+ version: string;
57
+ informationUri: string;
58
+ rules: SarifRule[];
59
+ };
60
+ };
61
+ results: SarifResult[];
62
+ }>;
63
+ }
64
+ export declare function exportFindingsToSarif(findings: Finding[], projectRootDir?: string, toolVersion?: string): SarifReport;
@@ -0,0 +1,81 @@
1
+ export function exportFindingsToSarif(findings, projectRootDir = '.', toolVersion = '1.2.0') {
2
+ const ruleMap = new Map();
3
+ const results = [];
4
+ const mapSeverityToSarifLevel = (sev) => {
5
+ switch (sev) {
6
+ case 'critical':
7
+ case 'high':
8
+ return 'error';
9
+ case 'medium':
10
+ return 'warning';
11
+ case 'low':
12
+ case 'info':
13
+ default:
14
+ return 'note';
15
+ }
16
+ };
17
+ for (const f of findings) {
18
+ if (!ruleMap.has(f.id)) {
19
+ ruleMap.set(f.id, {
20
+ id: f.id,
21
+ name: f.title.replace(/[^a-zA-Z0-9]/g, '_'),
22
+ shortDescription: { text: f.title },
23
+ fullDescription: { text: f.evidence },
24
+ help: { text: `${f.recommendation} (Expected Impact: ${f.expectedImpact})` },
25
+ defaultConfiguration: {
26
+ level: mapSeverityToSarifLevel(f.severity)
27
+ }
28
+ });
29
+ }
30
+ const relPath = f.sourceLocation?.filePath || (f.affectedUrl.startsWith('/') ? f.affectedUrl.slice(1) : f.affectedUrl);
31
+ const startLine = f.sourceRange?.startLine || f.sourceLocation?.startLine || f.sourceLocation?.line || 1;
32
+ const endLine = f.sourceRange?.endLine || f.sourceLocation?.endLine || startLine;
33
+ results.push({
34
+ ruleId: f.id,
35
+ level: mapSeverityToSarifLevel(f.severity),
36
+ message: {
37
+ text: `${f.title}: ${f.evidence}. ${f.recommendation}`
38
+ },
39
+ locations: [
40
+ {
41
+ physicalLocation: {
42
+ artifactLocation: {
43
+ uri: relPath.replace(/\\/g, '/')
44
+ },
45
+ region: {
46
+ startLine,
47
+ endLine,
48
+ snippet: f.sourceRange?.snippet ? { text: f.sourceRange.snippet } : undefined
49
+ }
50
+ }
51
+ }
52
+ ],
53
+ fixes: f.suggestedFix?.snippet
54
+ ? [
55
+ {
56
+ description: {
57
+ text: `${f.suggestedFix.explanation}\n\`\`\`\n${f.suggestedFix.snippet}\n\`\`\``
58
+ }
59
+ }
60
+ ]
61
+ : undefined
62
+ });
63
+ }
64
+ return {
65
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
66
+ version: '2.1.0',
67
+ runs: [
68
+ {
69
+ tool: {
70
+ driver: {
71
+ name: 'SEO Gravity',
72
+ version: toolVersion,
73
+ informationUri: 'https://github.com/thedevbob005/seo-gravity-mcp',
74
+ rules: Array.from(ruleMap.values())
75
+ }
76
+ },
77
+ results
78
+ }
79
+ ]
80
+ };
81
+ }
@@ -1,9 +1,11 @@
1
- import { ProjectSnapshot, RegressionReport } from '../types/findings.js';
1
+ import { ProjectSnapshot, RegressionReport, GitMetadata } from '../types/findings.js';
2
2
  export interface CreateSnapshotOptions {
3
3
  baseUrl?: string;
4
4
  maxCrawlDepth?: number;
5
5
  maxPagesToAudit?: number;
6
6
  includeCrawlGraph?: boolean;
7
7
  }
8
+ export declare function computeLogicalPageId(urlOrPath: string): string;
9
+ export declare function extractGitMetadata(projectDir: string): GitMetadata;
8
10
  export declare function createProjectSnapshot(projectPath: string, options?: CreateSnapshotOptions): Promise<ProjectSnapshot>;
9
11
  export declare function compareSnapshots(baseline: ProjectSnapshot, current: ProjectSnapshot): RegressionReport;