@svgrid/mcp 2.6.7 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -195
- package/dist/core-tools.d.ts +74 -0
- package/dist/core-tools.js +362 -0
- package/dist/data.js +680 -531
- package/dist/index.d.ts +11 -5
- package/dist/index.js +244 -354
- package/dist/installed.d.ts +33 -0
- package/dist/installed.js +87 -0
- package/dist/preview.d.ts +88 -0
- package/dist/preview.js +211 -0
- package/dist/prompts.d.ts +31 -0
- package/dist/prompts.js +65 -0
- package/dist/resources.d.ts +19 -0
- package/dist/resources.js +84 -0
- package/dist/search.d.ts +11 -0
- package/dist/search.js +28 -0
- package/dist/studio-tools.d.ts +13 -0
- package/dist/studio-tools.js +227 -0
- package/dist/validate.d.ts +40 -0
- package/dist/validate.js +70 -5
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ProjectTool } from './project-tools.js';
|
|
2
|
+
type ToolResult = {
|
|
3
|
+
content: Array<{
|
|
4
|
+
type: 'text';
|
|
5
|
+
text: string;
|
|
6
|
+
}>;
|
|
7
|
+
isError?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** Studio is opt-in: an explicit flag, or a licence key that implies intent. */
|
|
10
|
+
export declare function studioEnabled(): boolean;
|
|
11
|
+
export declare const STUDIO_TOOLS: ProjectTool[];
|
|
12
|
+
export declare function handleStudioTool(name: string, args: Record<string, unknown>): ToolResult | undefined;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Studio surface, as four tools instead of twenty-seven.
|
|
3
|
+
*
|
|
4
|
+
* The old surface was one tool per model mutation - `studio_add_block`,
|
|
5
|
+
* `studio_update_block`, `studio_remove_block`, `studio_move_block`, and ten
|
|
6
|
+
* separate `studio_set_*` tools. That is the API-wrapping antipattern: it made
|
|
7
|
+
* building a five-screen app twenty-odd round trips, and it cost ~3,741 tokens
|
|
8
|
+
* of `tools/list` on EVERY request, to every user, including the ones who only
|
|
9
|
+
* wanted to ask a question about the free grid. Studio was 79% of the payload
|
|
10
|
+
* and most sessions never called it once.
|
|
11
|
+
*
|
|
12
|
+
* These four delegate into the same `handleProjectTool` switch, so none of the
|
|
13
|
+
* behaviour is reimplemented here - only the shape a model sees. `studio_apply`
|
|
14
|
+
* takes a BATCH, which is the real win: a whole screen in one call rather than
|
|
15
|
+
* six.
|
|
16
|
+
*
|
|
17
|
+
* Off unless asked for. Set `SVGRID_MCP_STUDIO=1`, or a valid
|
|
18
|
+
* `SVGRID_LICENSE_KEY` - the tools that need a licence to be useful should not
|
|
19
|
+
* be charged to everyone else's context window.
|
|
20
|
+
*/
|
|
21
|
+
import { checkLicenseKey } from '@svgrid/enterprise/studio';
|
|
22
|
+
import { handleProjectTool } from './project-tools.js';
|
|
23
|
+
/** Studio is opt-in: an explicit flag, or a licence key that implies intent. */
|
|
24
|
+
export function studioEnabled() {
|
|
25
|
+
if (process.env.SVGRID_MCP_STUDIO === '1')
|
|
26
|
+
return true;
|
|
27
|
+
return checkLicenseKey(process.env.SVGRID_LICENSE_KEY ?? null).valid;
|
|
28
|
+
}
|
|
29
|
+
const PROJECT_ACTIONS = {
|
|
30
|
+
new: 'studio_new_project',
|
|
31
|
+
load: 'studio_load_project',
|
|
32
|
+
describe: 'studio_describe_project',
|
|
33
|
+
config: 'studio_get_config',
|
|
34
|
+
capabilities: 'studio_capabilities',
|
|
35
|
+
};
|
|
36
|
+
const APPLY_OPS = {
|
|
37
|
+
add_entity: 'studio_add_entity',
|
|
38
|
+
add_screen: 'studio_add_screen',
|
|
39
|
+
add_block: 'studio_add_block',
|
|
40
|
+
add_component: 'studio_add_component',
|
|
41
|
+
update_block: 'studio_update_block',
|
|
42
|
+
remove_block: 'studio_remove_block',
|
|
43
|
+
move_block: 'studio_move_block',
|
|
44
|
+
update_screen: 'studio_update_screen',
|
|
45
|
+
remove_screen: 'studio_remove_screen',
|
|
46
|
+
};
|
|
47
|
+
const SETTINGS = {
|
|
48
|
+
theme: 'studio_set_theme',
|
|
49
|
+
access: 'studio_set_access',
|
|
50
|
+
auth: 'studio_set_auth',
|
|
51
|
+
data_layer: 'studio_set_data_layer',
|
|
52
|
+
tenancy: 'studio_set_tenancy',
|
|
53
|
+
job: 'studio_set_job',
|
|
54
|
+
deploy_target: 'studio_set_deploy_target',
|
|
55
|
+
screen_layout: 'studio_set_screen_layout',
|
|
56
|
+
form_layout: 'studio_set_form_layout',
|
|
57
|
+
field_conditions: 'studio_set_field_conditions',
|
|
58
|
+
entity_source: 'studio_set_entity_source',
|
|
59
|
+
};
|
|
60
|
+
const BUILD_ACTIONS = {
|
|
61
|
+
validate: 'studio_validate',
|
|
62
|
+
generate: 'studio_generate_app',
|
|
63
|
+
};
|
|
64
|
+
const keys = (map) => Object.keys(map).join(' | ');
|
|
65
|
+
export const STUDIO_TOOLS = [
|
|
66
|
+
{
|
|
67
|
+
name: 'studio_project',
|
|
68
|
+
description: 'SvGrid Studio (commercial): open or inspect the project model. `new` starts an empty one, `load` parses a studio.config.json string, `describe` summarises the current project (entities, screens, blocks, ids), `config` returns it as studio.config.json, `capabilities` lists the block kinds, UI components, themes and data sources you can use. Call `capabilities` before `studio_apply` so you use real names.',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
action: { type: 'string', enum: Object.keys(PROJECT_ACTIONS), description: keys(PROJECT_ACTIONS) },
|
|
73
|
+
title: { type: 'string', description: 'For action "new": the project title.' },
|
|
74
|
+
config: { type: 'string', description: 'For action "load": a studio.config.json string.' },
|
|
75
|
+
},
|
|
76
|
+
required: ['action'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'studio_apply',
|
|
81
|
+
description: 'SvGrid Studio (commercial): change the project model. Takes a BATCH of operations applied in order, so a whole screen is one call rather than six. Each op is { op, ...args } where op is one of: ' +
|
|
82
|
+
keys(APPLY_OPS) +
|
|
83
|
+
'. The args are the same ones the individual operations took (entity, screen, kind, blockId, config, ...). Stops at the first failure and reports which op failed and what already applied. Get ids from studio_project action:"describe".',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
ops: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
description: 'Operations to apply in order.',
|
|
90
|
+
items: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
op: { type: 'string', enum: Object.keys(APPLY_OPS), description: 'Which operation.' },
|
|
94
|
+
},
|
|
95
|
+
required: ['op'],
|
|
96
|
+
additionalProperties: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ['ops'],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'studio_configure',
|
|
105
|
+
description: 'SvGrid Studio (commercial): set project-wide options. Pass any combination of: ' +
|
|
106
|
+
keys(SETTINGS) +
|
|
107
|
+
'. Each value is the argument object the individual setting took, e.g. { "theme": { "preset": "ember" }, "auth": { "enabled": true } }. Applied in one call so a full app configuration is one round trip.',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: Object.fromEntries(Object.keys(SETTINGS).map((k) => [
|
|
111
|
+
k,
|
|
112
|
+
{ type: 'object', description: `Arguments for ${SETTINGS[k]}.`, additionalProperties: true },
|
|
113
|
+
])),
|
|
114
|
+
required: [],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'studio_build',
|
|
119
|
+
description: 'SvGrid Studio (commercial): `validate` reports codegen errors and warnings for the current project; `generate` emits the full runnable SvelteKit app - every route, $lib module, package.json and config. Validate first: generate on an invalid project wastes a large response.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
action: { type: 'string', enum: Object.keys(BUILD_ACTIONS), description: keys(BUILD_ACTIONS) },
|
|
124
|
+
},
|
|
125
|
+
required: ['action'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
const fail = (message) => ({ isError: true, content: [{ type: 'text', text: message }] });
|
|
130
|
+
/**
|
|
131
|
+
* Catch the shape mistakes that would otherwise surface three calls later.
|
|
132
|
+
*
|
|
133
|
+
* An `EntitySchema` field is `{ field, type }`. Passing `{ name, type }` is the
|
|
134
|
+
* obvious guess and it is accepted silently: the entity stores, the screen
|
|
135
|
+
* builds, and nothing complains until `studio_build generate` fails with
|
|
136
|
+
* "no primary key ... or name a field \`id\`" - on a schema that HAS a field
|
|
137
|
+
* called id. That message is about the `field` property, but it reads as the
|
|
138
|
+
* `name` one, and following its advice reproduces the error exactly. It cost
|
|
139
|
+
* three round trips to diagnose by hand; a model would burn the same and then
|
|
140
|
+
* guess.
|
|
141
|
+
*
|
|
142
|
+
* So it is caught here, at the call that got it wrong, naming the fix.
|
|
143
|
+
*/
|
|
144
|
+
function checkOpShape(op, entry) {
|
|
145
|
+
if (op !== 'add_entity')
|
|
146
|
+
return null;
|
|
147
|
+
const schema = entry.schema;
|
|
148
|
+
if (!schema || !Array.isArray(schema.fields))
|
|
149
|
+
return null;
|
|
150
|
+
const misnamed = schema.fields.filter((f) => f && typeof f === 'object' && !('field' in f) && 'name' in f);
|
|
151
|
+
if (!misnamed.length)
|
|
152
|
+
return null;
|
|
153
|
+
const names = misnamed
|
|
154
|
+
.map((f) => f.name)
|
|
155
|
+
.filter((n) => typeof n === 'string')
|
|
156
|
+
.join(', ');
|
|
157
|
+
return (`EntitySchema fields use \`field\`, not \`name\` - rewrite { "name": "id" } as ` +
|
|
158
|
+
`{ "field": "id" } (affected: ${names}). Left as-is this stores fine and then ` +
|
|
159
|
+
`fails at generate time with a message about the primary key.`);
|
|
160
|
+
}
|
|
161
|
+
const isError = (r) => !r || r.isError;
|
|
162
|
+
const textOf = (r) => (r?.content ?? []).map((c) => c.text).join('\n');
|
|
163
|
+
export function handleStudioTool(name, args) {
|
|
164
|
+
if (name === 'studio_project') {
|
|
165
|
+
const action = String(args.action ?? '');
|
|
166
|
+
const target = PROJECT_ACTIONS[action];
|
|
167
|
+
if (!target)
|
|
168
|
+
return fail(`Unknown action "${action}". Use one of: ${keys(PROJECT_ACTIONS)}.`);
|
|
169
|
+
return handleProjectTool(target, args);
|
|
170
|
+
}
|
|
171
|
+
if (name === 'studio_apply') {
|
|
172
|
+
const ops = args.ops;
|
|
173
|
+
if (!Array.isArray(ops) || ops.length === 0) {
|
|
174
|
+
return fail(`ops must be a non-empty array of { op, ... }. Valid ops: ${keys(APPLY_OPS)}.`);
|
|
175
|
+
}
|
|
176
|
+
const applied = [];
|
|
177
|
+
for (const [i, raw] of ops.entries()) {
|
|
178
|
+
const entry = (raw ?? {});
|
|
179
|
+
const op = String(entry.op ?? '');
|
|
180
|
+
const target = APPLY_OPS[op];
|
|
181
|
+
if (!target) {
|
|
182
|
+
return fail(`ops[${i}]: unknown op "${op}". Valid ops: ${keys(APPLY_OPS)}.` +
|
|
183
|
+
(applied.length ? `\nAlready applied: ${applied.join(', ')}.` : ''));
|
|
184
|
+
}
|
|
185
|
+
const shapeError = checkOpShape(op, entry);
|
|
186
|
+
if (shapeError) {
|
|
187
|
+
return fail(`ops[${i}] (${op}): ${shapeError}` +
|
|
188
|
+
(applied.length ? `\nAlready applied: ${applied.join(', ')}.` : '\nNothing was applied.'));
|
|
189
|
+
}
|
|
190
|
+
const result = handleProjectTool(target, entry);
|
|
191
|
+
if (isError(result)) {
|
|
192
|
+
// Say what landed before the failure - the model has to know whether to
|
|
193
|
+
// retry the whole batch or only the tail.
|
|
194
|
+
return fail(`ops[${i}] (${op}) failed: ${textOf(result)}` +
|
|
195
|
+
(applied.length ? `\nAlready applied: ${applied.join(', ')}.` : '\nNothing was applied.'));
|
|
196
|
+
}
|
|
197
|
+
applied.push(`${i}:${op}`);
|
|
198
|
+
}
|
|
199
|
+
const describe = handleProjectTool('studio_describe_project', {});
|
|
200
|
+
return { content: [{ type: 'text', text: `Applied ${applied.length} op(s).\n\n${textOf(describe)}` }] };
|
|
201
|
+
}
|
|
202
|
+
if (name === 'studio_configure') {
|
|
203
|
+
const entries = Object.keys(SETTINGS).filter((k) => args[k] !== undefined);
|
|
204
|
+
if (!entries.length) {
|
|
205
|
+
return fail(`Pass at least one of: ${keys(SETTINGS)}.`);
|
|
206
|
+
}
|
|
207
|
+
const done = [];
|
|
208
|
+
for (const key of entries) {
|
|
209
|
+
const value = (args[key] ?? {});
|
|
210
|
+
const result = handleProjectTool(SETTINGS[key], value);
|
|
211
|
+
if (isError(result)) {
|
|
212
|
+
return fail(`${key} failed: ${textOf(result)}` +
|
|
213
|
+
(done.length ? `\nAlready set: ${done.join(', ')}.` : '\nNothing was set.'));
|
|
214
|
+
}
|
|
215
|
+
done.push(key);
|
|
216
|
+
}
|
|
217
|
+
return { content: [{ type: 'text', text: `Set ${done.join(', ')}.` }] };
|
|
218
|
+
}
|
|
219
|
+
if (name === 'studio_build') {
|
|
220
|
+
const action = String(args.action ?? '');
|
|
221
|
+
const target = BUILD_ACTIONS[action];
|
|
222
|
+
if (!target)
|
|
223
|
+
return fail(`Unknown action "${action}". Use one of: ${keys(BUILD_ACTIONS)}.`);
|
|
224
|
+
return handleProjectTool(target, args);
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
package/dist/validate.d.ts
CHANGED
|
@@ -27,6 +27,20 @@ export type Diagnostic = {
|
|
|
27
27
|
message: string;
|
|
28
28
|
/** The concrete edit that fixes it, when there is one. */
|
|
29
29
|
fix?: string;
|
|
30
|
+
/**
|
|
31
|
+
* A mechanically applicable identifier rename, when the correction is exact.
|
|
32
|
+
*
|
|
33
|
+
* Machine-readable ON PURPOSE. `fix` is prose written for a human or a model
|
|
34
|
+
* to read; parsing it back out to edit code would make the wording
|
|
35
|
+
* load-bearing, and someone would eventually reword it and silently break
|
|
36
|
+
* every rewrite. Set only where the replacement is certain - a known rename
|
|
37
|
+
* or a close-enough spelling guess - and never where the advice is "this has
|
|
38
|
+
* no equivalent, remove it".
|
|
39
|
+
*/
|
|
40
|
+
rename?: {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
};
|
|
30
44
|
/** Doc slug or demo id to read for the full story. */
|
|
31
45
|
see?: string;
|
|
32
46
|
};
|
|
@@ -89,6 +103,32 @@ export declare function blankOut(src: string): string;
|
|
|
89
103
|
*/
|
|
90
104
|
export declare function nearest(word: string, candidates: readonly string[]): string | null;
|
|
91
105
|
/** Run every static rule. Exported for tests and for hosts that skip compiling. */
|
|
106
|
+
/**
|
|
107
|
+
* Apply the mechanically-safe renames to the source, returning the corrected
|
|
108
|
+
* text and a description of every edit.
|
|
109
|
+
*
|
|
110
|
+
* Reporting a mistake and leaving the caller to re-derive the edit from prose
|
|
111
|
+
* is the loop this tool exists to shorten. But the tool's entire worth is that
|
|
112
|
+
* it never cries wolf, and rewriting raises those stakes: a false positive no
|
|
113
|
+
* longer wastes a turn, it corrupts working code. So the rules here are narrow
|
|
114
|
+
* on purpose.
|
|
115
|
+
*
|
|
116
|
+
* - Only diagnostics carrying a `rename`, which is set only where the
|
|
117
|
+
* replacement is exact.
|
|
118
|
+
* - Word-boundary matches only, so `data` never rewrites `rowData`.
|
|
119
|
+
* - Scoped to the reported LINE. `Diagnostic` has no column, and a whole-file
|
|
120
|
+
* replace would hit identifiers the checker never looked at - a local
|
|
121
|
+
* variable that happens to share a name with a wrong prop, say.
|
|
122
|
+
* - Skipped when the line does not contain the identifier, which means the
|
|
123
|
+
* source has moved on since the check.
|
|
124
|
+
*
|
|
125
|
+
* `applied` is the audit trail; a caller that wants to review before trusting
|
|
126
|
+
* has everything it needs.
|
|
127
|
+
*/
|
|
128
|
+
export declare function applyFixes(source: string, diagnostics: readonly Diagnostic[]): {
|
|
129
|
+
fixed: string;
|
|
130
|
+
applied: string[];
|
|
131
|
+
};
|
|
92
132
|
export declare function checkStatic(source: string, surface: ApiSurface, filename?: string): Diagnostic[];
|
|
93
133
|
/**
|
|
94
134
|
* Check a snippet and report what a model should do next. `compile` is the
|
package/dist/validate.js
CHANGED
|
@@ -348,7 +348,10 @@ function checkImports(ctx) {
|
|
|
348
348
|
severity: 'error',
|
|
349
349
|
line,
|
|
350
350
|
message: `${pkgName}@${isGrid ? surface.gridVersion : surface.enterpriseVersion} does not export \`${name}\`.`,
|
|
351
|
-
fix: guess
|
|
351
|
+
fix: guess
|
|
352
|
+
? `Did you mean \`${guess}\`?`
|
|
353
|
+
: 'Call svgrid_get with ref:"api" for the exported surface.',
|
|
354
|
+
rename: guess ? { from: name, to: guess } : undefined,
|
|
352
355
|
});
|
|
353
356
|
}
|
|
354
357
|
}
|
|
@@ -470,7 +473,7 @@ function checkGridProps(ctx) {
|
|
|
470
473
|
message: `\`on:${evt}\` never fires: SvGrid dispatches no component events, it takes callback props.`,
|
|
471
474
|
fix: real
|
|
472
475
|
? `Use \`${real}={...}\`.`
|
|
473
|
-
: `Look for the matching \`on...\` prop - call
|
|
476
|
+
: `Look for the matching \`on...\` prop - call svgrid_get with ref:"api" or read reference/SvGrid.`,
|
|
474
477
|
see: 'reference/SvGrid',
|
|
475
478
|
});
|
|
476
479
|
continue;
|
|
@@ -497,6 +500,9 @@ function checkGridProps(ctx) {
|
|
|
497
500
|
line: attr.line,
|
|
498
501
|
message: `\`${name}\` is not a SvGrid prop.`,
|
|
499
502
|
fix: renamed ? `Use \`${renamed}\`.` : PROP_RENAME_NOTES[name],
|
|
503
|
+
// An empty target means "there is no equivalent, take it out" - a
|
|
504
|
+
// deletion, not a rename, so it is never applied automatically.
|
|
505
|
+
rename: renamed ? { from: name, to: renamed } : undefined,
|
|
500
506
|
see: 'reference/SvGrid',
|
|
501
507
|
});
|
|
502
508
|
continue;
|
|
@@ -518,7 +524,10 @@ function checkGridProps(ctx) {
|
|
|
518
524
|
severity: 'error',
|
|
519
525
|
line: attr.line,
|
|
520
526
|
message: `\`${name}\` is not a prop of <SvGrid> in @svgrid/grid@${surface.gridVersion}.`,
|
|
521
|
-
fix: guess
|
|
527
|
+
fix: guess
|
|
528
|
+
? `Did you mean \`${guess}\`?`
|
|
529
|
+
: 'Call svgrid_search with the prop name, or read the reference/SvGrid doc for the prop list.',
|
|
530
|
+
rename: guess ? { from: name, to: guess } : undefined,
|
|
522
531
|
see: 'reference/SvGrid',
|
|
523
532
|
});
|
|
524
533
|
}
|
|
@@ -652,6 +661,7 @@ function checkColumns(ctx) {
|
|
|
652
661
|
line,
|
|
653
662
|
message: `\`${key}\` is not a SvGrid column key.`,
|
|
654
663
|
fix: renamed ? `Use \`${renamed}\`.` : COLUMN_RENAME_NOTES[key],
|
|
664
|
+
rename: renamed ? { from: key, to: renamed } : undefined,
|
|
655
665
|
see: 'help/columns/column-definitions',
|
|
656
666
|
});
|
|
657
667
|
continue;
|
|
@@ -874,13 +884,18 @@ function checkEnterpriseUsage(ctx) {
|
|
|
874
884
|
continue;
|
|
875
885
|
}
|
|
876
886
|
const hint = API_METHOD_HINTS[method];
|
|
877
|
-
const
|
|
887
|
+
const spelling = nearest(method, all);
|
|
888
|
+
const guess = hint ?? spelling;
|
|
878
889
|
push(ctx, {
|
|
879
890
|
rule: 'svgrid/unknown-api-method',
|
|
880
891
|
severity: 'error',
|
|
881
892
|
line,
|
|
882
893
|
message: `The grid API has no \`${method}()\` in @svgrid/grid@${ctx.surface.gridVersion}.`,
|
|
883
|
-
fix: guess ? `Use \`${guess}\`.` : 'Call
|
|
894
|
+
fix: guess ? `Use \`${guess}\`.` : 'Call svgrid_get with ref:"api" for the api surface.',
|
|
895
|
+
// Only the spelling guess is a bare identifier. An API_METHOD_HINTS
|
|
896
|
+
// value is a call expression - `exportData({ format: "xlsx" })` - and
|
|
897
|
+
// substituting that for an identifier would emit `api.exportData({...})(...)`.
|
|
898
|
+
rename: !hint && spelling ? { from: method, to: spelling } : undefined,
|
|
884
899
|
see: 'reference/SvGrid',
|
|
885
900
|
});
|
|
886
901
|
}
|
|
@@ -941,6 +956,56 @@ function checkFeatures(ctx) {
|
|
|
941
956
|
// Entry point
|
|
942
957
|
// ---------------------------------------------------------------------------
|
|
943
958
|
/** Run every static rule. Exported for tests and for hosts that skip compiling. */
|
|
959
|
+
/**
|
|
960
|
+
* Apply the mechanically-safe renames to the source, returning the corrected
|
|
961
|
+
* text and a description of every edit.
|
|
962
|
+
*
|
|
963
|
+
* Reporting a mistake and leaving the caller to re-derive the edit from prose
|
|
964
|
+
* is the loop this tool exists to shorten. But the tool's entire worth is that
|
|
965
|
+
* it never cries wolf, and rewriting raises those stakes: a false positive no
|
|
966
|
+
* longer wastes a turn, it corrupts working code. So the rules here are narrow
|
|
967
|
+
* on purpose.
|
|
968
|
+
*
|
|
969
|
+
* - Only diagnostics carrying a `rename`, which is set only where the
|
|
970
|
+
* replacement is exact.
|
|
971
|
+
* - Word-boundary matches only, so `data` never rewrites `rowData`.
|
|
972
|
+
* - Scoped to the reported LINE. `Diagnostic` has no column, and a whole-file
|
|
973
|
+
* replace would hit identifiers the checker never looked at - a local
|
|
974
|
+
* variable that happens to share a name with a wrong prop, say.
|
|
975
|
+
* - Skipped when the line does not contain the identifier, which means the
|
|
976
|
+
* source has moved on since the check.
|
|
977
|
+
*
|
|
978
|
+
* `applied` is the audit trail; a caller that wants to review before trusting
|
|
979
|
+
* has everything it needs.
|
|
980
|
+
*/
|
|
981
|
+
export function applyFixes(source, diagnostics) {
|
|
982
|
+
const lines = source.split('\n');
|
|
983
|
+
const applied = [];
|
|
984
|
+
for (const d of diagnostics) {
|
|
985
|
+
if (!d.rename)
|
|
986
|
+
continue;
|
|
987
|
+
const { from, to } = d.rename;
|
|
988
|
+
if (!from || !to || from === to)
|
|
989
|
+
continue;
|
|
990
|
+
const i = d.line - 1;
|
|
991
|
+
const line = lines[i];
|
|
992
|
+
if (line === undefined)
|
|
993
|
+
continue;
|
|
994
|
+
// Word boundaries via an explicit character class rather than \b, which
|
|
995
|
+
// treats `$` and `-` as boundaries and would match inside `$derived` or a
|
|
996
|
+
// hyphenated attribute.
|
|
997
|
+
const pattern = new RegExp(`(^|[^A-Za-z0-9_$])${escapeRegExp(from)}(?![A-Za-z0-9_$])`, 'g');
|
|
998
|
+
if (!pattern.test(line))
|
|
999
|
+
continue;
|
|
1000
|
+
pattern.lastIndex = 0;
|
|
1001
|
+
lines[i] = line.replace(pattern, (_m, before) => `${before}${to}`);
|
|
1002
|
+
applied.push(`${from} -> ${to} (line ${d.line})`);
|
|
1003
|
+
}
|
|
1004
|
+
return { fixed: lines.join('\n'), applied };
|
|
1005
|
+
}
|
|
1006
|
+
function escapeRegExp(value) {
|
|
1007
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1008
|
+
}
|
|
944
1009
|
export function checkStatic(source, surface, filename = 'Component.svelte') {
|
|
945
1010
|
const ctx = {
|
|
946
1011
|
raw: source,
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "commercial",
|
|
6
6
|
"url": "https://svgrid.com/pricing"
|
|
7
7
|
},
|
|
8
|
-
"version": "
|
|
8
|
+
"version": "3.0.0",
|
|
9
9
|
"description": "Model Context Protocol server for SvGrid, the Svelte 5 data grid: checks the code your AI writes against the real API surface, plus version-pinned docs, API reference and 373 demo sources.",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"author": "jQWidgets <sales@jqwidgets.com>",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
34
34
|
"svelte": "^5.55.5",
|
|
35
35
|
"zod": "^3.23.8",
|
|
36
|
-
"@svgrid/enterprise": "^2.
|
|
36
|
+
"@svgrid/enterprise": "^2.7.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.7",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.svgrid/svgrid",
|
|
4
4
|
"title": "SvGrid",
|
|
5
5
|
"description": "Checks AI-written SvGrid code against the real API, plus version-pinned Svelte 5 grid docs.",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "3.0.0",
|
|
7
7
|
"websiteUrl": "https://svgrid.com/docs/help/mcp-server/",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/sv-grid/sv-grid",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
17
17
|
"identifier": "@svgrid/mcp",
|
|
18
|
-
"version": "
|
|
18
|
+
"version": "3.0.0",
|
|
19
19
|
"runtimeHint": "npx",
|
|
20
20
|
"transport": {
|
|
21
21
|
"type": "stdio"
|