seo-gravity-mcp 1.3.2 → 1.3.4

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,4 +1,4 @@
1
- # 🚀 SEO Gravity (v1.3.2)
1
+ # 🚀 SEO Gravity (v1.3.4)
2
2
 
3
3
  <div align="center">
4
4
 
@@ -1,5 +1,6 @@
1
1
  import { FrameworkAdapter } from './types.js';
2
2
  export declare class AdapterRegistry {
3
+ private unknownAdapter;
3
4
  private adapters;
4
5
  getAdapterForProject(projectDir: string): FrameworkAdapter;
5
6
  getAdapterById(id: string): FrameworkAdapter | undefined;
@@ -15,7 +15,9 @@ import { PhpClassicAdapter } from './phpClassicAdapter.js';
15
15
  import { SsgAdapter } from './ssgAdapter.js';
16
16
  import { ViteReactAdapter } from './viteReactAdapter.js';
17
17
  import { StaticAdapter } from './staticAdapter.js';
18
+ import { UnknownAdapter } from './unknownAdapter.js';
18
19
  export class AdapterRegistry {
20
+ unknownAdapter = new UnknownAdapter();
19
21
  adapters = [
20
22
  // Specialized JS/TS Frameworks
21
23
  new NextAppAdapter(),
@@ -36,7 +38,7 @@ export class AdapterRegistry {
36
38
  // Static Site Generators & SPAs
37
39
  new SsgAdapter(),
38
40
  new ViteReactAdapter(),
39
- // Universal Fallback
41
+ // Static HTML Sites
40
42
  new StaticAdapter()
41
43
  ];
42
44
  getAdapterForProject(projectDir) {
@@ -45,9 +47,11 @@ export class AdapterRegistry {
45
47
  return adapter;
46
48
  }
47
49
  }
48
- return this.adapters[this.adapters.length - 1]; // fallback static
50
+ return this.unknownAdapter;
49
51
  }
50
52
  getAdapterById(id) {
53
+ if (id === 'unknown')
54
+ return this.unknownAdapter;
51
55
  return this.adapters.find(a => a.id === id);
52
56
  }
53
57
  getAllAdapters() {
@@ -3,7 +3,7 @@ import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../ty
3
3
  export declare class StaticAdapter implements FrameworkAdapter {
4
4
  id: string;
5
5
  name: string;
6
- detect(_projectDir: string): boolean;
6
+ detect(projectDir: string): boolean;
7
7
  getProjectInfo(projectDir: string): ProjectFrameworkInfo;
8
8
  discoverRoutes(projectDir: string): DiscoveredRoute[];
9
9
  mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
@@ -3,8 +3,9 @@ import * as path from 'path';
3
3
  export class StaticAdapter {
4
4
  id = 'static-html';
5
5
  name = 'Static HTML / Web Application';
6
- detect(_projectDir) {
7
- return true; // Fallback
6
+ detect(projectDir) {
7
+ return fs.existsSync(path.join(projectDir, 'index.html')) ||
8
+ fs.existsSync(path.join(projectDir, 'public/index.html'));
8
9
  }
9
10
  getProjectInfo(projectDir) {
10
11
  return {
@@ -0,0 +1,16 @@
1
+ import { FrameworkAdapter, MetadataLocationInfo, CanonicalLocationInfo, SchemaLocationInfo } from './types.js';
2
+ import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../types/findings.js';
3
+ export declare class UnknownAdapter implements FrameworkAdapter {
4
+ id: string;
5
+ name: string;
6
+ detect(_projectDir: string): boolean;
7
+ getProjectInfo(projectDir: string): ProjectFrameworkInfo;
8
+ discoverRoutes(_projectDir: string): DiscoveredRoute[];
9
+ mapRouteToSource(targetUrl: string, _routes: DiscoveredRoute[]): RouteSourceMapping;
10
+ findMetadataImplementation(_projectDir: string, _route: DiscoveredRoute): Promise<MetadataLocationInfo>;
11
+ findCanonicalDeclaration(_projectDir: string, _route: DiscoveredRoute): Promise<CanonicalLocationInfo>;
12
+ findSchemaDeclaration(_projectDir: string, _route: DiscoveredRoute): Promise<SchemaLocationInfo>;
13
+ findRobotsConfig(_projectDir: string): string | null;
14
+ findSitemapConfig(_projectDir: string): string | null;
15
+ findLlmsTxt(_projectDir: string): string | null;
16
+ }
@@ -0,0 +1,49 @@
1
+ import * as path from 'path';
2
+ export class UnknownAdapter {
3
+ id = 'unknown';
4
+ name = 'Unknown / Unrecognized Framework';
5
+ detect(_projectDir) {
6
+ return false;
7
+ }
8
+ getProjectInfo(projectDir) {
9
+ return {
10
+ framework: 'unknown',
11
+ name: path.basename(projectDir),
12
+ hasTypeScript: false,
13
+ hasSitemapConfig: false,
14
+ hasRobotsConfig: false,
15
+ hasLlmsTxt: false,
16
+ rootDir: path.resolve(projectDir),
17
+ routesDir: '.',
18
+ defaultDevPort: undefined
19
+ };
20
+ }
21
+ discoverRoutes(_projectDir) {
22
+ return [];
23
+ }
24
+ mapRouteToSource(targetUrl, _routes) {
25
+ return {
26
+ urlPath: targetUrl,
27
+ confidence: 0.0,
28
+ resolutionMethod: 'unmapped'
29
+ };
30
+ }
31
+ async findMetadataImplementation(_projectDir, _route) {
32
+ return { hasMetadata: false, type: 'none' };
33
+ }
34
+ async findCanonicalDeclaration(_projectDir, _route) {
35
+ return { hasCanonical: false, type: 'none' };
36
+ }
37
+ async findSchemaDeclaration(_projectDir, _route) {
38
+ return { hasSchema: false, typesFound: [] };
39
+ }
40
+ findRobotsConfig(_projectDir) {
41
+ return null;
42
+ }
43
+ findSitemapConfig(_projectDir) {
44
+ return null;
45
+ }
46
+ findLlmsTxt(_projectDir) {
47
+ return null;
48
+ }
49
+ }
@@ -179,7 +179,7 @@ export function generateBenchmarkMethodologyReport() {
179
179
  }
180
180
  return {
181
181
  title: 'SEO Gravity Multi-Framework Correlation & Invariant Benchmark Methodology',
182
- version: '1.3.2',
182
+ version: '1.3.4',
183
183
  evaluatedAt: new Date().toISOString(),
184
184
  definitionOfCorrelation: 'A correlation is defined as a verified mapping connecting an observed URL or Route Pattern to its exact physical source file, template, route configuration, and AST symbol coordinate range.',
185
185
  totalFrameworksTested: BENCHMARK_FIXTURES.length,
@@ -49,8 +49,8 @@ export const BUILTIN_INVARIANTS = [
49
49
  type: 'ast',
50
50
  description: has ? 'Canonical declaration found in AST' : 'Canonical missing from source AST',
51
51
  sourceFile: ctx.sourceFilePath,
52
- startLine: 1,
53
- endLine: 1,
52
+ startLine: ctx.sourceRange?.startLine,
53
+ endLine: ctx.sourceRange?.endLine,
54
54
  timestamp: new Date().toISOString()
55
55
  } : undefined
56
56
  };
@@ -69,7 +69,7 @@ export const BUILTIN_INVARIANTS = [
69
69
  remediationGuide: 'Export title in page metadata, define @section/block title, or add <title> in component head.',
70
70
  verificationMethod: 'Inspect page <title> in AST, template, or rendered DOM.',
71
71
  evaluate(ctx) {
72
- const has = Boolean(ctx.hasMetadata || ctx.extractedTitle);
72
+ const has = ctx.hasTitle !== undefined ? ctx.hasTitle : Boolean(ctx.extractedTitle || ctx.hasMetadata);
73
73
  return {
74
74
  satisfied: has,
75
75
  observedCondition: has ? `Title present ("${ctx.extractedTitle || 'Declared'}")` : 'Title missing',
@@ -78,8 +78,8 @@ export const BUILTIN_INVARIANTS = [
78
78
  type: 'ast',
79
79
  description: has ? `Title metadata: "${ctx.extractedTitle || 'Declared'}"` : 'Title metadata missing',
80
80
  sourceFile: ctx.sourceFilePath,
81
- startLine: 1,
82
- endLine: 1,
81
+ startLine: ctx.sourceRange?.startLine,
82
+ endLine: ctx.sourceRange?.endLine,
83
83
  timestamp: new Date().toISOString()
84
84
  } : undefined
85
85
  };
@@ -116,26 +116,36 @@ export const BUILTIN_INVARIANTS = [
116
116
  {
117
117
  id: 'INV-ROBOTS-ALLOWED',
118
118
  name: 'Robots Policy Determinable',
119
- description: 'Project must provide a determinable robots crawl policy via robots.txt, robots.ts, or standard allow-all defaults.',
119
+ description: 'The effective robots crawl policy must be determinable; absence of robots.txt defaults to allow-all unless another blocking mechanism is observed.',
120
120
  category: 'robots',
121
121
  requirementLevel: 'CONDITIONAL',
122
122
  severity: 'medium',
123
123
  scope: 'SITE',
124
124
  expectedCondition: 'Robots crawl policy is determinable and does not block critical assets',
125
- failureEvidence: 'No robots crawl configuration detected.',
126
- remediationGuide: 'Create public/robots.txt or app/robots.ts if specific bot directives are desired.',
127
- verificationMethod: 'Check GET /robots.txt response or adapter robots inspection.',
125
+ failureEvidence: 'Robots policy could not be determined or an observed policy blocks required resources.',
126
+ remediationGuide: 'Create or correct robots.txt/app/robots.ts when explicit crawler directives are needed.',
127
+ verificationMethod: 'Check GET /robots.txt and relevant meta/X-Robots directives.',
128
128
  evaluate(ctx) {
129
- const ok = Boolean(ctx.hasRobots);
129
+ const policy = ctx.robotsPolicyState ?? (ctx.hasRobots ? 'explicit' : 'default_allow');
130
+ const ok = policy === 'explicit' || policy === 'default_allow';
131
+ const observed = policy === 'explicit'
132
+ ? 'Explicit robots policy resolved.'
133
+ : policy === 'default_allow'
134
+ ? 'No explicit robots file; standard default allow policy assumed.'
135
+ : policy === 'blocking'
136
+ ? 'Blocking robots policy observed.'
137
+ : 'Robots policy could not be determined.';
130
138
  return {
131
139
  satisfied: ok,
132
- observedCondition: ok ? 'Robots policy determinable' : 'Robots policy not explicitly declared',
133
- evidence: ok ? 'robots.txt configuration present.' : 'No robots.txt found (default allow-all).',
140
+ observedCondition: observed,
141
+ evidence: ok
142
+ ? 'Effective robots policy is determinable.'
143
+ : 'Effective robots policy is not safely determinable or is blocking.',
134
144
  polymorphicEvidence: {
135
145
  type: 'route_config',
136
- description: ok ? 'Robots config resolved' : 'Robots config absent',
137
- sourceFile: 'robots.txt',
138
- configFormat: 'static_file',
146
+ description: observed,
147
+ sourceFile: ctx.hasRobots ? 'robots.txt' : undefined,
148
+ configFormat: ctx.hasRobots ? 'static_file' : 'implicit_default',
139
149
  declaredPattern: '/robots.txt',
140
150
  timestamp: new Date().toISOString()
141
151
  }
@@ -204,9 +214,8 @@ export const BUILTIN_INVARIANTS = [
204
214
  export class InvariantRegistry {
205
215
  invariants = new Map();
206
216
  constructor(initial = BUILTIN_INVARIANTS) {
207
- for (const inv of initial) {
217
+ for (const inv of initial)
208
218
  this.invariants.set(inv.id, inv);
209
- }
210
219
  }
211
220
  register(invariant) {
212
221
  this.invariants.set(invariant.id, invariant);
@@ -3,22 +3,30 @@ import { PolymorphicEvidence } from '../types/evidence.js';
3
3
  export type RequirementLevel = 'REQUIRED' | 'CONDITIONAL' | 'RECOMMENDED' | 'OPTIONAL';
4
4
  export type InvariantScope = 'SITE' | 'ROUTE' | 'PAGE' | 'COMPONENT' | 'RESOURCE' | 'site_wide' | 'crawl_graph';
5
5
  export type InvariantCategory = 'http' | 'indexability' | 'canonical' | 'metadata' | 'links' | 'schema' | 'robots' | 'ai_readiness';
6
+ export type RobotsPolicyState = 'explicit' | 'default_allow' | 'blocking' | 'unknown';
6
7
  export interface InvariantEvaluationContext {
7
8
  url: string;
8
9
  logicalPageId: string;
9
10
  sourceFilePath?: string;
11
+ sourceRange?: {
12
+ startLine: number;
13
+ endLine: number;
14
+ };
10
15
  statusCode?: number;
16
+ hasTitle?: boolean;
17
+ hasDescription?: boolean;
11
18
  hasMetadata?: boolean;
12
19
  hasCanonical?: boolean;
13
20
  isIndexable?: boolean;
14
21
  hasSchema?: boolean;
15
22
  hasRobots?: boolean;
23
+ robotsPolicyState?: RobotsPolicyState;
16
24
  hasSitemap?: boolean;
17
25
  hasLlmsTxt?: boolean;
18
26
  incomingLinksCount?: number;
19
27
  extractedTitle?: string;
20
28
  extractedCanonical?: string;
21
- rawPayload?: any;
29
+ rawPayload?: unknown;
22
30
  }
23
31
  export interface InvariantEvaluationResult {
24
32
  satisfied: boolean;
@@ -1,9 +1,132 @@
1
+ import { z } from 'zod';
1
2
  import { PolicyConfig } from './types.js';
2
3
  import { InvariantDiffItem } from '../types/canonical.js';
4
+ export declare const InvariantRuleSchema: z.ZodObject<{
5
+ enabled: z.ZodOptional<z.ZodBoolean>;
6
+ requirementLevel: z.ZodOptional<z.ZodEnum<["REQUIRED", "CONDITIONAL", "RECOMMENDED", "OPTIONAL"]>>;
7
+ severity: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low", "info"]>>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
10
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
11
+ enabled?: boolean | undefined;
12
+ }, {
13
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
14
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
15
+ enabled?: boolean | undefined;
16
+ }>;
17
+ export declare const RegressionPolicySchema: z.ZodObject<{
18
+ failOnLevels: z.ZodOptional<z.ZodArray<z.ZodEnum<["REQUIRED", "CONDITIONAL", "RECOMMENDED", "OPTIONAL"]>, "many">>;
19
+ failOnSeverities: z.ZodOptional<z.ZodArray<z.ZodEnum<["critical", "high", "medium", "low", "info"]>, "many">>;
20
+ allowExpectedChanges: z.ZodOptional<z.ZodBoolean>;
21
+ maxAllowedRegressions: z.ZodOptional<z.ZodNumber>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ allowExpectedChanges?: boolean | undefined;
24
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
25
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
26
+ maxAllowedRegressions?: number | undefined;
27
+ }, {
28
+ allowExpectedChanges?: boolean | undefined;
29
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
30
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
31
+ maxAllowedRegressions?: number | undefined;
32
+ }>;
33
+ export declare const PolicyConfigSchema: z.ZodObject<{
34
+ version: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodString]>>;
35
+ profile: z.ZodOptional<z.ZodEnum<["strict", "balanced", "startup", "ecommerce", "documentation", "custom"]>>;
36
+ regression: z.ZodOptional<z.ZodObject<{
37
+ failOnLevels: z.ZodOptional<z.ZodArray<z.ZodEnum<["REQUIRED", "CONDITIONAL", "RECOMMENDED", "OPTIONAL"]>, "many">>;
38
+ failOnSeverities: z.ZodOptional<z.ZodArray<z.ZodEnum<["critical", "high", "medium", "low", "info"]>, "many">>;
39
+ allowExpectedChanges: z.ZodOptional<z.ZodBoolean>;
40
+ maxAllowedRegressions: z.ZodOptional<z.ZodNumber>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ allowExpectedChanges?: boolean | undefined;
43
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
44
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
45
+ maxAllowedRegressions?: number | undefined;
46
+ }, {
47
+ allowExpectedChanges?: boolean | undefined;
48
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
49
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
50
+ maxAllowedRegressions?: number | undefined;
51
+ }>>;
52
+ invariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
53
+ enabled: z.ZodOptional<z.ZodBoolean>;
54
+ requirementLevel: z.ZodOptional<z.ZodEnum<["REQUIRED", "CONDITIONAL", "RECOMMENDED", "OPTIONAL"]>>;
55
+ severity: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low", "info"]>>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
58
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
59
+ enabled?: boolean | undefined;
60
+ }, {
61
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
62
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
63
+ enabled?: boolean | undefined;
64
+ }>>>;
65
+ policy: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
66
+ framework: z.ZodOptional<z.ZodObject<{
67
+ adapter: z.ZodOptional<z.ZodString>;
68
+ force: z.ZodOptional<z.ZodBoolean>;
69
+ entrypoint: z.ZodOptional<z.ZodString>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ adapter?: string | undefined;
72
+ force?: boolean | undefined;
73
+ entrypoint?: string | undefined;
74
+ }, {
75
+ adapter?: string | undefined;
76
+ force?: boolean | undefined;
77
+ entrypoint?: string | undefined;
78
+ }>>;
79
+ }, "strip", z.ZodTypeAny, {
80
+ framework?: {
81
+ adapter?: string | undefined;
82
+ force?: boolean | undefined;
83
+ entrypoint?: string | undefined;
84
+ } | undefined;
85
+ invariants?: Record<string, {
86
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
87
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
88
+ enabled?: boolean | undefined;
89
+ }> | undefined;
90
+ version?: string | number | undefined;
91
+ profile?: "strict" | "balanced" | "startup" | "ecommerce" | "documentation" | "custom" | undefined;
92
+ regression?: {
93
+ allowExpectedChanges?: boolean | undefined;
94
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
95
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
96
+ maxAllowedRegressions?: number | undefined;
97
+ } | undefined;
98
+ policy?: Record<string, any> | undefined;
99
+ }, {
100
+ framework?: {
101
+ adapter?: string | undefined;
102
+ force?: boolean | undefined;
103
+ entrypoint?: string | undefined;
104
+ } | undefined;
105
+ invariants?: Record<string, {
106
+ requirementLevel?: "REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL" | undefined;
107
+ severity?: "critical" | "high" | "medium" | "low" | "info" | undefined;
108
+ enabled?: boolean | undefined;
109
+ }> | undefined;
110
+ version?: string | number | undefined;
111
+ profile?: "strict" | "balanced" | "startup" | "ecommerce" | "documentation" | "custom" | undefined;
112
+ regression?: {
113
+ allowExpectedChanges?: boolean | undefined;
114
+ failOnLevels?: ("REQUIRED" | "CONDITIONAL" | "RECOMMENDED" | "OPTIONAL")[] | undefined;
115
+ failOnSeverities?: ("critical" | "high" | "medium" | "low" | "info")[] | undefined;
116
+ maxAllowedRegressions?: number | undefined;
117
+ } | undefined;
118
+ policy?: Record<string, any> | undefined;
119
+ }>;
3
120
  export declare class PolicyLoader {
4
121
  static resolvePolicy(projectDir?: string, explicitPath?: string): PolicyConfig;
5
- static parseConfigFile(filePath: string): any;
6
- static parseYaml(content: string): Record<string, any>;
7
- private static parseScalarValue;
122
+ static parseConfigFile(filePath: string): unknown;
123
+ static parseYaml(content: string): Record<string, unknown>;
8
124
  static isRegressionBreachingPolicy(diff: InvariantDiffItem, policy: PolicyConfig): boolean;
125
+ static evaluatePolicyGate(diffs: InvariantDiffItem[], policy: PolicyConfig): {
126
+ pass: boolean;
127
+ breachingDiffs: InvariantDiffItem[];
128
+ totalBreaches: number;
129
+ maxAllowed: number;
130
+ verdict: string;
131
+ };
9
132
  }