seo-gravity-mcp 1.3.1 → 1.3.3
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 +1 -1
- package/dist/adapters/adapterRegistry.d.ts +1 -0
- package/dist/adapters/adapterRegistry.js +6 -2
- package/dist/adapters/staticAdapter.d.ts +1 -1
- package/dist/adapters/staticAdapter.js +3 -2
- package/dist/adapters/unknownAdapter.d.ts +16 -0
- package/dist/adapters/unknownAdapter.js +49 -0
- package/dist/benchmark/methodology.js +1 -1
- package/dist/cli.js +4 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -2
- package/dist/invariants/registry.js +5 -5
- package/dist/invariants/types.d.ts +6 -0
- package/dist/policy/loader.d.ts +126 -1
- package/dist/policy/loader.js +85 -22
- package/dist/server/server.js +3 -2
- package/dist/test.js +165 -36
- package/dist/tools/orchestration.d.ts +3 -1
- package/dist/tools/orchestration.js +10 -8
- package/dist/tools/technical.js +6 -3
- package/dist/types/evidence.d.ts +2 -2
- package/dist/types/seo.d.ts +2 -0
- package/dist/utils/astLocator.d.ts +2 -0
- package/dist/utils/astLocator.js +15 -6
- package/dist/utils/cacheManager.d.ts +3 -0
- package/dist/utils/cacheManager.js +30 -0
- package/dist/utils/gitDiffEngine.js +7 -3
- package/dist/utils/jsdomRenderer.js +2 -0
- package/dist/utils/projectScanner.d.ts +1 -1
- package/dist/utils/projectScanner.js +4 -2
- package/dist/utils/scraper.js +20 -5
- package/dist/utils/searchEngines.js +28 -32
- package/dist/utils/snapshotEngine.d.ts +1 -1
- package/dist/utils/snapshotEngine.js +100 -38
- package/dist/utils/urlNormalizer.d.ts +2 -0
- package/dist/utils/urlNormalizer.js +60 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -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
|
-
//
|
|
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.
|
|
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(
|
|
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(
|
|
7
|
-
return
|
|
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: 3000
|
|
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.
|
|
182
|
+
version: '1.3.3',
|
|
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,
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { runDifferentialAudit } from './utils/gitDiffEngine.js';
|
|
|
6
6
|
import { exportFindingsToSarif } from './utils/sarifExporter.js';
|
|
7
7
|
import { PolicyLoader } from './policy/loader.js';
|
|
8
8
|
import { formatPrCommentMarkdown } from './utils/prCommentFormatter.js';
|
|
9
|
+
import { VERSION } from './version.js';
|
|
9
10
|
export const EXIT_CODES = {
|
|
10
11
|
PASS: 0,
|
|
11
12
|
REGRESSION: 1,
|
|
@@ -57,7 +58,7 @@ async function runCli() {
|
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
59
60
|
// Pretty format
|
|
60
|
-
console.log(`\n🚀 SEO Gravity CLI (
|
|
61
|
+
console.log(`\n🚀 SEO Gravity CLI (v${VERSION}) — SEO Engineering Infrastructure\n`);
|
|
61
62
|
console.log(`Auditing project at: ${path.resolve(projectDir)}...`);
|
|
62
63
|
console.log(`Policy Profile: ${policy.profile}`);
|
|
63
64
|
console.log(`\n========================================`);
|
|
@@ -93,7 +94,7 @@ async function runCli() {
|
|
|
93
94
|
console.log(JSON.stringify(snap.snapshot, null, 2));
|
|
94
95
|
}
|
|
95
96
|
else {
|
|
96
|
-
console.log(`\n🚀 SEO Gravity CLI (
|
|
97
|
+
console.log(`\n🚀 SEO Gravity CLI (v${VERSION})\n`);
|
|
97
98
|
console.log(`✅ Snapshot created and saved to: ${snap.savedToPath}`);
|
|
98
99
|
console.log(`Score: ${snap.snapshot.scores.overallHealth}/100 | Invariants: ${snap.snapshot.invariants?.length || 0}`);
|
|
99
100
|
}
|
|
@@ -105,7 +106,7 @@ async function runCli() {
|
|
|
105
106
|
console.error('❌ Configuration Error: --baseline <path_to_snapshot.json> is required for check command.');
|
|
106
107
|
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
107
108
|
}
|
|
108
|
-
const checkRes = await checkRegression(projectDir, baselinePath, baseUrl);
|
|
109
|
+
const checkRes = await checkRegression(projectDir, baselinePath, baseUrl, policy);
|
|
109
110
|
if (format === 'sarif') {
|
|
110
111
|
const sarif = exportFindingsToSarif(checkRes.regressionReport.newRegressions, projectDir);
|
|
111
112
|
const serialized = JSON.stringify(sarif, null, 2);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { startStdioServer } from './server/server.js';
|
|
3
|
+
import { VERSION, PACKAGE_NAME } from './version.js';
|
|
3
4
|
export * from './server/types.js';
|
|
4
5
|
export * from './server/registry.js';
|
|
5
6
|
export * from './server/server.js';
|
|
7
|
+
export * from './version.js';
|
|
6
8
|
// Start Stdio MCP Server
|
|
7
9
|
startStdioServer({
|
|
8
|
-
name:
|
|
9
|
-
version:
|
|
10
|
+
name: PACKAGE_NAME,
|
|
11
|
+
version: VERSION
|
|
10
12
|
}).catch((err) => {
|
|
11
13
|
console.error('Fatal error starting SEO Gravity MCP Server:', err);
|
|
12
14
|
process.exit(1);
|
|
@@ -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:
|
|
53
|
-
endLine:
|
|
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.
|
|
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:
|
|
82
|
-
endLine:
|
|
81
|
+
startLine: ctx.sourceRange?.startLine,
|
|
82
|
+
endLine: ctx.sourceRange?.endLine,
|
|
83
83
|
timestamp: new Date().toISOString()
|
|
84
84
|
} : undefined
|
|
85
85
|
};
|
|
@@ -7,7 +7,13 @@ export interface InvariantEvaluationContext {
|
|
|
7
7
|
url: string;
|
|
8
8
|
logicalPageId: string;
|
|
9
9
|
sourceFilePath?: string;
|
|
10
|
+
sourceRange?: {
|
|
11
|
+
startLine: number;
|
|
12
|
+
endLine: number;
|
|
13
|
+
};
|
|
10
14
|
statusCode?: number;
|
|
15
|
+
hasTitle?: boolean;
|
|
16
|
+
hasDescription?: boolean;
|
|
11
17
|
hasMetadata?: boolean;
|
|
12
18
|
hasCanonical?: boolean;
|
|
13
19
|
isIndexable?: boolean;
|
package/dist/policy/loader.d.ts
CHANGED
|
@@ -1,7 +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
|
-
|
|
122
|
+
static parseConfigFile(filePath: string): any;
|
|
123
|
+
static parseYaml(content: string): Record<string, any>;
|
|
6
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
|
+
};
|
|
7
132
|
}
|
package/dist/policy/loader.js
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import * as YAML from 'yaml';
|
|
4
|
+
import { z } from 'zod';
|
|
3
5
|
import { BUILTIN_PROFILES } from './profiles.js';
|
|
6
|
+
export const InvariantRuleSchema = z.object({
|
|
7
|
+
enabled: z.boolean().optional(),
|
|
8
|
+
requirementLevel: z.enum(['REQUIRED', 'CONDITIONAL', 'RECOMMENDED', 'OPTIONAL']).optional(),
|
|
9
|
+
severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).optional()
|
|
10
|
+
});
|
|
11
|
+
export const RegressionPolicySchema = z.object({
|
|
12
|
+
failOnLevels: z.array(z.enum(['REQUIRED', 'CONDITIONAL', 'RECOMMENDED', 'OPTIONAL'])).optional(),
|
|
13
|
+
failOnSeverities: z.array(z.enum(['critical', 'high', 'medium', 'low', 'info'])).optional(),
|
|
14
|
+
allowExpectedChanges: z.boolean().optional(),
|
|
15
|
+
maxAllowedRegressions: z.number().int().min(0).optional()
|
|
16
|
+
});
|
|
17
|
+
export const PolicyConfigSchema = z.object({
|
|
18
|
+
version: z.union([z.number(), z.string()]).optional(),
|
|
19
|
+
profile: z.enum(['strict', 'balanced', 'startup', 'ecommerce', 'documentation', 'custom']).optional(),
|
|
20
|
+
regression: RegressionPolicySchema.optional(),
|
|
21
|
+
invariants: z.record(InvariantRuleSchema).optional(),
|
|
22
|
+
policy: z.record(z.any()).optional(),
|
|
23
|
+
framework: z.object({
|
|
24
|
+
adapter: z.string().optional(),
|
|
25
|
+
force: z.boolean().optional(),
|
|
26
|
+
entrypoint: z.string().optional()
|
|
27
|
+
}).optional()
|
|
28
|
+
});
|
|
4
29
|
export class PolicyLoader {
|
|
5
30
|
static resolvePolicy(projectDir = '.', explicitPath) {
|
|
6
31
|
let rawConfig = null;
|
|
@@ -27,18 +52,39 @@ export class PolicyLoader {
|
|
|
27
52
|
if (!rawConfig) {
|
|
28
53
|
return baseProfile;
|
|
29
54
|
}
|
|
30
|
-
//
|
|
55
|
+
// Normalize snake_case regression options to camelCase
|
|
56
|
+
const rawReg = rawConfig.regression || {};
|
|
57
|
+
const normalizedRegression = {
|
|
58
|
+
...baseProfile.regression,
|
|
59
|
+
failOnLevels: rawReg.failOnLevels || rawReg.fail_on_levels || baseProfile.regression?.failOnLevels,
|
|
60
|
+
failOnSeverities: rawReg.failOnSeverities || rawReg.fail_on_severities || baseProfile.regression?.failOnSeverities,
|
|
61
|
+
allowExpectedChanges: rawReg.allowExpectedChanges ?? rawReg.allow_expected_changes ?? baseProfile.regression?.allowExpectedChanges,
|
|
62
|
+
maxAllowedRegressions: rawReg.maxAllowedRegressions ?? rawReg.max_allowed_regressions ?? baseProfile.regression?.maxAllowedRegressions
|
|
63
|
+
};
|
|
64
|
+
// Normalize invariant policy overrides
|
|
65
|
+
const mergedInvariants = {
|
|
66
|
+
...baseProfile.invariants,
|
|
67
|
+
...(rawConfig.invariants || {})
|
|
68
|
+
};
|
|
69
|
+
// Support convenient high-level policy shortcuts (e.g. policy: { canonical: required, sitemap: recommended })
|
|
70
|
+
const rawPolicy = rawConfig.policy || {};
|
|
71
|
+
if (rawPolicy.canonical) {
|
|
72
|
+
const lvl = String(rawPolicy.canonical).toUpperCase();
|
|
73
|
+
mergedInvariants['INV-CANONICAL-RESOLVES'] = { requirementLevel: lvl, severity: 'high', enabled: true };
|
|
74
|
+
}
|
|
75
|
+
if (rawPolicy.sitemap) {
|
|
76
|
+
const lvl = String(rawPolicy.sitemap).toUpperCase();
|
|
77
|
+
mergedInvariants['INV-SITEMAP-PRESENT'] = { requirementLevel: lvl, severity: 'medium', enabled: true };
|
|
78
|
+
}
|
|
79
|
+
if (rawPolicy.llms_txt || rawPolicy.llmsTxt) {
|
|
80
|
+
const lvl = String(rawPolicy.llms_txt || rawPolicy.llmsTxt).toUpperCase();
|
|
81
|
+
mergedInvariants['INV-LLMS-TXT'] = { requirementLevel: lvl, severity: 'low', enabled: true };
|
|
82
|
+
}
|
|
31
83
|
const merged = {
|
|
32
84
|
version: rawConfig.version || baseProfile.version,
|
|
33
85
|
profile: baseProfileName,
|
|
34
|
-
regression:
|
|
35
|
-
|
|
36
|
-
...rawConfig.regression
|
|
37
|
-
},
|
|
38
|
-
invariants: {
|
|
39
|
-
...baseProfile.invariants,
|
|
40
|
-
...rawConfig.invariants
|
|
41
|
-
},
|
|
86
|
+
regression: normalizedRegression,
|
|
87
|
+
invariants: mergedInvariants,
|
|
42
88
|
framework: rawConfig.framework
|
|
43
89
|
};
|
|
44
90
|
return merged;
|
|
@@ -49,29 +95,30 @@ export class PolicyLoader {
|
|
|
49
95
|
if (filePath.endsWith('.json')) {
|
|
50
96
|
return JSON.parse(content);
|
|
51
97
|
}
|
|
52
|
-
|
|
53
|
-
const parsed = { profile: 'balanced', invariants: {}, regression: {} };
|
|
54
|
-
const lines = content.split('\n');
|
|
55
|
-
for (const line of lines) {
|
|
56
|
-
const trimmed = line.trim();
|
|
57
|
-
if (!trimmed || trimmed.startsWith('#'))
|
|
58
|
-
continue;
|
|
59
|
-
if (trimmed.startsWith('profile:')) {
|
|
60
|
-
parsed.profile = trimmed.split(':')[1].trim().replace(/['"]/g, '');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return parsed;
|
|
98
|
+
return this.parseYaml(content);
|
|
64
99
|
}
|
|
65
100
|
catch {
|
|
66
101
|
return null;
|
|
67
102
|
}
|
|
68
103
|
}
|
|
104
|
+
static parseYaml(content) {
|
|
105
|
+
try {
|
|
106
|
+
const parsed = YAML.parse(content);
|
|
107
|
+
return (typeof parsed === 'object' && parsed !== null) ? parsed : {};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
69
113
|
static isRegressionBreachingPolicy(diff, policy) {
|
|
114
|
+
if (diff.status === 'EXPECTED_CHANGE') {
|
|
115
|
+
return policy.regression?.allowExpectedChanges === false;
|
|
116
|
+
}
|
|
70
117
|
if (diff.status !== 'NEW_REGRESSION')
|
|
71
118
|
return false;
|
|
72
119
|
const invOverride = policy.invariants?.[diff.invariantId];
|
|
73
120
|
if (invOverride && invOverride.enabled === false) {
|
|
74
|
-
return false; //
|
|
121
|
+
return false; // Explicitly disabled by project policy
|
|
75
122
|
}
|
|
76
123
|
const level = invOverride?.requirementLevel || diff.requirementLevel || 'REQUIRED';
|
|
77
124
|
const severity = invOverride?.severity || diff.severity || 'high';
|
|
@@ -81,4 +128,20 @@ export class PolicyLoader {
|
|
|
81
128
|
const severityBreached = failSeverities.includes(severity);
|
|
82
129
|
return levelBreached && severityBreached;
|
|
83
130
|
}
|
|
131
|
+
static evaluatePolicyGate(diffs, policy) {
|
|
132
|
+
const breachingDiffs = diffs.filter(d => this.isRegressionBreachingPolicy(d, policy));
|
|
133
|
+
const totalBreaches = breachingDiffs.length;
|
|
134
|
+
const maxAllowed = policy.regression?.maxAllowedRegressions ?? 0;
|
|
135
|
+
const pass = totalBreaches <= maxAllowed;
|
|
136
|
+
const verdict = pass
|
|
137
|
+
? `✅ PASSED (Policy: ${policy.profile}): ${totalBreaches} breach(es) within allowed threshold (max: ${maxAllowed}).`
|
|
138
|
+
: `🚨 FAILED (Policy: ${policy.profile}): ${totalBreaches} invariant regression(s) breached policy (max allowed: ${maxAllowed}).`;
|
|
139
|
+
return {
|
|
140
|
+
pass,
|
|
141
|
+
breachingDiffs,
|
|
142
|
+
totalBreaches,
|
|
143
|
+
maxAllowed,
|
|
144
|
+
verdict
|
|
145
|
+
};
|
|
146
|
+
}
|
|
84
147
|
}
|
package/dist/server/server.js
CHANGED
|
@@ -2,10 +2,11 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { TOOLS, executeTool } from './registry.js';
|
|
5
|
+
import { VERSION, PACKAGE_NAME } from '../version.js';
|
|
5
6
|
export function createMcpServer(options = {}) {
|
|
6
7
|
const server = new Server({
|
|
7
|
-
name: options.name ||
|
|
8
|
-
version: options.version ||
|
|
8
|
+
name: options.name || PACKAGE_NAME,
|
|
9
|
+
version: options.version || VERSION
|
|
9
10
|
}, {
|
|
10
11
|
capabilities: {
|
|
11
12
|
tools: {}
|