@platformos/platformos-mcp-supervisor 0.0.2 → 0.1.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 +90 -0
- package/dist/adapter-input.d.ts +113 -0
- package/dist/adapter-input.js +210 -0
- package/dist/adapter-input.spec.d.ts +1 -0
- package/dist/adapter-input.spec.js +275 -0
- package/dist/bin/args.spec.d.ts +1 -0
- package/dist/bin/args.spec.js +35 -0
- package/dist/check-docs.d.ts +21 -0
- package/dist/check-docs.js +38 -0
- package/dist/check-docs.spec.d.ts +1 -0
- package/dist/check-docs.spec.js +87 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.js +11 -0
- package/dist/cost-model.d.ts +128 -0
- package/dist/cost-model.js +137 -0
- package/dist/cost-model.spec.d.ts +1 -0
- package/dist/cost-model.spec.js +91 -0
- package/dist/deadline.d.ts +27 -0
- package/dist/deadline.js +40 -0
- package/dist/deadline.spec.d.ts +1 -0
- package/dist/deadline.spec.js +83 -0
- package/dist/enrich/enrich.d.ts +87 -0
- package/dist/enrich/enrich.js +97 -0
- package/dist/enrich/enrich.spec.d.ts +1 -0
- package/dist/enrich/enrich.spec.js +184 -0
- package/dist/enrich/symbol.d.ts +16 -0
- package/dist/enrich/symbol.js +58 -0
- package/dist/enrich/symbol.spec.d.ts +1 -0
- package/dist/enrich/symbol.spec.js +89 -0
- package/dist/impact/impact.d.ts +9 -0
- package/dist/impact/impact.js +189 -0
- package/dist/impact/impact.spec.d.ts +1 -0
- package/dist/impact/impact.spec.js +357 -0
- package/dist/impact/project-scan.d.ts +37 -0
- package/dist/impact/project-scan.js +60 -0
- package/dist/impact/project-scan.spec.d.ts +1 -0
- package/dist/impact/project-scan.spec.js +121 -0
- package/dist/index.d.ts +1 -1
- package/dist/lint/fixes.d.ts +28 -0
- package/dist/lint/fixes.js +30 -0
- package/dist/lint/lint-batch.d.ts +100 -0
- package/dist/lint/lint-batch.js +98 -0
- package/dist/lint/lint-batch.spec.d.ts +1 -0
- package/dist/lint/lint-batch.spec.js +503 -0
- package/dist/lint/lint.d.ts +11 -1
- package/dist/lint/lint.js +9 -3
- package/dist/result/assemble.d.ts +12 -9
- package/dist/result/assemble.js +64 -16
- package/dist/result/assemble.spec.d.ts +1 -0
- package/dist/result/assemble.spec.js +319 -0
- package/dist/result/blocking.d.ts +86 -0
- package/dist/result/blocking.js +96 -0
- package/dist/result/blocking.spec.d.ts +1 -0
- package/dist/result/blocking.spec.js +201 -0
- package/dist/result/impact-states.d.ts +16 -0
- package/dist/result/impact-states.js +18 -0
- package/dist/result/response-budget.d.ts +9 -0
- package/dist/result/response-budget.js +131 -0
- package/dist/result/response-budget.spec.d.ts +1 -0
- package/dist/result/response-budget.spec.js +222 -0
- package/dist/result/types.d.ts +210 -104
- package/dist/result/types.js +4 -13
- package/dist/transport/instructions-coverage.spec.d.ts +1 -0
- package/dist/transport/instructions-coverage.spec.js +176 -0
- package/dist/transport/instructions.d.ts +1 -0
- package/dist/transport/instructions.js +132 -0
- package/dist/transport/process-guards.d.ts +43 -0
- package/dist/transport/process-guards.js +63 -0
- package/dist/transport/process-guards.spec.d.ts +1 -0
- package/dist/transport/process-guards.spec.js +200 -0
- package/dist/transport/server.d.ts +1 -1
- package/dist/transport/server.js +16 -9
- package/dist/transport/validate-code.d.ts +36 -18
- package/dist/transport/validate-code.js +200 -33
- package/dist/transport/validate-code.spec.d.ts +1 -0
- package/dist/transport/validate-code.spec.js +2303 -0
- package/dist/validate/batch-bounds.d.ts +34 -0
- package/dist/validate/batch-bounds.js +60 -0
- package/dist/validate/batch-coherence.d.ts +21 -0
- package/dist/validate/batch-coherence.js +54 -0
- package/dist/validate/batch-coherence.spec.d.ts +1 -0
- package/dist/validate/batch-coherence.spec.js +111 -0
- package/dist/validate/validate-buffers.d.ts +27 -0
- package/dist/validate/validate-buffers.js +265 -0
- package/package.json +8 -7
|
@@ -1,49 +1,216 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Registration of the `validate_code` MCP tool.
|
|
2
|
+
* Registration of the `validate_code` MCP tool — the supervisor's entire surface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* ONE TOOL, TWO INPUT SHAPES. Validating one file and validating a coordinated multi-file
|
|
5
|
+
* change are the same operation on a different number of buffers, so they are one tool:
|
|
6
|
+
*
|
|
7
|
+
* validate_code({ file_path, content }) -> a single result
|
|
8
|
+
* validate_code({ files: [...] }) -> one result per file + a batch gate
|
|
9
|
+
*
|
|
10
|
+
* There is no `many` flag — which form was sent is evident from the arguments.
|
|
11
|
+
*
|
|
12
|
+
* PASSING SEVERAL FILES IS MORE ACCURATE, not just faster: with every buffer overlaid at
|
|
13
|
+
* once a partial created alongside its caller resolves, where one at a time it is reported
|
|
14
|
+
* missing. The description says so, because that is why an agent should reach for it.
|
|
15
|
+
*
|
|
16
|
+
* This module owns only the MCP surface: schema, description, registration, and mapping
|
|
17
|
+
* input shape to output shape. All logic lives in `validate/`.
|
|
8
18
|
*/
|
|
9
19
|
import { z } from 'zod';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
20
|
+
import { getAppPaths, PlatformOSFileType } from '@platformos/platformos-common';
|
|
21
|
+
import { validateBuffers, } from '../validate/validate-buffers.js';
|
|
22
|
+
import { MAX_BATCH_BYTES, MAX_BATCH_FILES, batchTooLarge } from '../validate/batch-bounds.js';
|
|
23
|
+
import { collidingBufferPaths } from '../validate/batch-coherence.js';
|
|
24
|
+
import { assembleNotApplicableResult } from '../result/assemble.js';
|
|
25
|
+
/** A non-blank path. Blank is malformed input, refused at the protocol boundary. */
|
|
26
|
+
const filePath = z
|
|
27
|
+
.string()
|
|
28
|
+
.refine((value) => value.trim().length > 0, { message: 'file_path must not be empty' });
|
|
29
|
+
const content = z.string();
|
|
12
30
|
/**
|
|
13
31
|
* zod raw shape for the tool input (validated by the MCP SDK before dispatch).
|
|
14
32
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
33
|
+
* Both forms are optional at the schema level and the handler requires exactly one,
|
|
34
|
+
* because a zod union of two object shapes defeats the SDK's JSON-Schema conversion
|
|
35
|
+
* — it emits `anyOf`, which several MCP clients do not surface to the model at all.
|
|
36
|
+
* Two optional fields with a documented rule converts cleanly and reads clearly.
|
|
37
|
+
*
|
|
38
|
+
* Typed as `ZodRawShape` (not the inferred literal shape) so `registerTool` does not
|
|
39
|
+
* instantiate excessively deeply over the schema (TS2589 under zod 3.25).
|
|
18
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* An example path of each kind, for the agent-facing prose below.
|
|
43
|
+
*
|
|
44
|
+
* DERIVED from `platformos-common`'s tables, not spelled out: an agent imitates these
|
|
45
|
+
* strings when it invents a path, so an example naming a directory the platform stopped
|
|
46
|
+
* accepting teaches the mistake to every caller.
|
|
47
|
+
*
|
|
48
|
+
* The first entry of `getAppPaths` is the canonical directory for the type; the rest are
|
|
49
|
+
* legacy aliases, which are exactly what an example should not suggest.
|
|
50
|
+
*/
|
|
51
|
+
const exampleOf = (type, name) => `${getAppPaths(type)[0]}/${name}.liquid`;
|
|
52
|
+
const EXAMPLE_PARTIAL = exampleOf(PlatformOSFileType.Partial, 'card');
|
|
53
|
+
const EXAMPLE_PAGE = exampleOf(PlatformOSFileType.Page, 'home');
|
|
54
|
+
const EXAMPLE_PROMO = exampleOf(PlatformOSFileType.Partial, 'promo');
|
|
19
55
|
export const VALIDATE_CODE_INPUT = {
|
|
20
|
-
file_path:
|
|
21
|
-
.
|
|
22
|
-
.describe('Path of the file
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.enum(['full', 'quick'])
|
|
56
|
+
file_path: filePath
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Path of the file to validate, absolute or relative to the project root ' +
|
|
59
|
+
`(e.g. "${EXAMPLE_PARTIAL}"). Pair with \`content\`.`),
|
|
60
|
+
content: content
|
|
26
61
|
.optional()
|
|
27
|
-
.describe('
|
|
62
|
+
.describe('The exact file contents you are about to write. Pair with `file_path`.'),
|
|
63
|
+
files: z
|
|
64
|
+
.array(z.object({ file_path: filePath, content }))
|
|
65
|
+
.min(1)
|
|
66
|
+
.max(MAX_BATCH_FILES)
|
|
67
|
+
.optional()
|
|
68
|
+
.describe('Two or more files to validate together, each with its own `file_path` and ' +
|
|
69
|
+
'`content`. Files in one call can reference each other.'),
|
|
28
70
|
};
|
|
29
|
-
const DESCRIPTION =
|
|
30
|
-
|
|
71
|
+
const DESCRIPTION = `Validate platformOS Liquid, GraphQL and YAML code BEFORE writing it to disk.
|
|
72
|
+
|
|
73
|
+
Pass EITHER one file OR several — never both.
|
|
74
|
+
|
|
75
|
+
One file:
|
|
76
|
+
{ "file_path": "${EXAMPLE_PARTIAL}",
|
|
77
|
+
"content": "<div>{{ title }}</div>" }
|
|
78
|
+
|
|
79
|
+
Several files (use this whenever one change touches more than one file):
|
|
80
|
+
{ "files": [
|
|
81
|
+
{ "file_path": "${EXAMPLE_PAGE}",
|
|
82
|
+
"content": "{% render 'promo' %}" },
|
|
83
|
+
{ "file_path": "${EXAMPLE_PROMO}",
|
|
84
|
+
"content": "<div>Promo</div>" }
|
|
85
|
+
] }
|
|
86
|
+
|
|
87
|
+
Sending several together is faster AND more accurate: they can reference each
|
|
88
|
+
other, so the new partial above resolves. Validated one at a time, the page would
|
|
89
|
+
be reported as rendering a missing partial. List each file at most once — the same
|
|
90
|
+
file twice is refused, because only one of the two could be checked.
|
|
91
|
+
|
|
92
|
+
Returns, for one file:
|
|
93
|
+
{ "status": "ok" | "warning" | "error" | "not_applicable",
|
|
94
|
+
"must_fix_before_write": boolean,
|
|
95
|
+
"errors": [...], "warnings": [...], "infos": [...],
|
|
96
|
+
"impact": { ... } }
|
|
97
|
+
|
|
98
|
+
For several files:
|
|
99
|
+
{ "must_fix_before_write": boolean,
|
|
100
|
+
"files": [ { "file_path": "...", "result": { ...as above... } } ] }
|
|
101
|
+
|
|
102
|
+
must_fix_before_write true means the file will not work — do not write it. false
|
|
103
|
+
means nothing fatal was found, which is not a guarantee of correctness.
|
|
104
|
+
status "not_applicable" means the file was NOT checked (see not_applicable_reason);
|
|
105
|
+
that is neither approval nor refusal.
|
|
106
|
+
A "truncated" field appears only when a file produced too many findings to return
|
|
107
|
+
in full; it carries the true total per list. The lists keep the top of the file,
|
|
108
|
+
and the verdict above is always computed from ALL findings, not the shortened list.`;
|
|
109
|
+
/**
|
|
110
|
+
* The agent-facing prose, exported so tests can pin claims that must not rot. The
|
|
111
|
+
* description is the agent's whole understanding of this tool; a stale sentence
|
|
112
|
+
* here misleads silently.
|
|
113
|
+
*/
|
|
114
|
+
export const TOOL_TEXT = { VALIDATE_CODE_DESCRIPTION: DESCRIPTION };
|
|
31
115
|
export function registerValidateCode(server, ctx) {
|
|
32
|
-
server.registerTool('validate_code', { description: DESCRIPTION, inputSchema: VALIDATE_CODE_INPUT }, async (args) =>
|
|
116
|
+
server.registerTool('validate_code', { description: DESCRIPTION, inputSchema: VALIDATE_CODE_INPUT }, async (args) => ({
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: JSON.stringify(await runValidateCode(ctx, args)),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
}));
|
|
33
124
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
125
|
+
/**
|
|
126
|
+
* The tool handler: normalize the input shape, validate, and shape the response to
|
|
127
|
+
* match what was asked.
|
|
128
|
+
*
|
|
129
|
+
* Returns the flat single-file result for the single form and the per-file envelope
|
|
130
|
+
* for the batch form — the response mirrors the request, so a caller never parses a
|
|
131
|
+
* shape it did not ask for.
|
|
132
|
+
*/
|
|
133
|
+
export async function runValidateCode(ctx, params, overrides = {}) {
|
|
134
|
+
const requested = requestedBuffers(params);
|
|
135
|
+
if ('error' in requested) {
|
|
136
|
+
ctx.log(`validate_code: rejected — ${requested.error.reason}`);
|
|
137
|
+
return assembleNotApplicableResult(requested.error);
|
|
138
|
+
}
|
|
139
|
+
const { buffers, isBatch } = requested;
|
|
140
|
+
ctx.log(isBatch ? `validate_code: ${buffers.length} file(s)` : `validate_code: ${buffers[0].filePath}`);
|
|
141
|
+
// Both refusals are request-level and impossible with a single buffer, so they are
|
|
142
|
+
// decided here, before any work starts. First refusal wins; the cheaper check runs first.
|
|
143
|
+
const refusal = isBatch
|
|
144
|
+
? (batchTooLarge(buffers) ?? collidingBufferPaths(ctx.projectDir, buffers))
|
|
145
|
+
: undefined;
|
|
146
|
+
if (refusal) {
|
|
147
|
+
ctx.log(`validate_code: refused (${refusal.code}) — ${refusal.reason}`);
|
|
148
|
+
return {
|
|
149
|
+
must_fix_before_write: false,
|
|
150
|
+
files: buffers.map((buffer) => ({
|
|
151
|
+
file_path: buffer.filePath,
|
|
152
|
+
result: assembleNotApplicableResult(refusal),
|
|
153
|
+
})),
|
|
154
|
+
next_step: refusal.reason,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const byFile = await validateBuffers(ctx, buffers, overrides);
|
|
158
|
+
if (!isBatch) {
|
|
159
|
+
// `validateBuffers` seeds an entry per requested buffer, so this always hits.
|
|
160
|
+
return byFile.get(buffers[0].filePath);
|
|
161
|
+
}
|
|
162
|
+
const files = buffers.map((buffer) => ({
|
|
163
|
+
file_path: buffer.filePath,
|
|
164
|
+
result: byFile.get(buffer.filePath),
|
|
165
|
+
}));
|
|
166
|
+
return {
|
|
167
|
+
// A file that was merely NOT CHECKED must never block the changeset: this is the
|
|
168
|
+
// OR of the files' own gates, and a `not_applicable` file's gate is false.
|
|
169
|
+
must_fix_before_write: files.some((entry) => entry.result.must_fix_before_write),
|
|
170
|
+
files,
|
|
171
|
+
};
|
|
44
172
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Read the two accepted input forms into the one internal shape.
|
|
175
|
+
*
|
|
176
|
+
* The SDK validates types; what it cannot express is "exactly one form". Both failures are
|
|
177
|
+
* the caller's malformed request rather than a property of any file, and are reported as
|
|
178
|
+
* such — silently preferring one form would validate something the caller did not ask about.
|
|
179
|
+
*/
|
|
180
|
+
function requestedBuffers(params) {
|
|
181
|
+
const hasSingle = params.file_path !== undefined || params.content !== undefined;
|
|
182
|
+
const hasBatch = params.files !== undefined && params.files.length > 0;
|
|
183
|
+
if (hasSingle && hasBatch) {
|
|
184
|
+
return {
|
|
185
|
+
error: {
|
|
186
|
+
code: 'internal_error',
|
|
187
|
+
reason: 'Both `files` and `file_path`/`content` were provided. Send ONE file as ' +
|
|
188
|
+
'`file_path` + `content`, or several as `files` — not both. Nothing was checked.',
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (hasBatch) {
|
|
193
|
+
return {
|
|
194
|
+
buffers: params.files.map((file) => ({
|
|
195
|
+
filePath: file.file_path,
|
|
196
|
+
content: file.content,
|
|
197
|
+
})),
|
|
198
|
+
isBatch: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (params.file_path === undefined || params.content === undefined) {
|
|
202
|
+
return {
|
|
203
|
+
error: {
|
|
204
|
+
code: 'internal_error',
|
|
205
|
+
reason: 'No file to validate. Send one file as `file_path` + `content`, or several as ' +
|
|
206
|
+
'`files`. Nothing was checked.',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
buffers: [{ filePath: params.file_path, content: params.content }],
|
|
212
|
+
isBatch: false,
|
|
213
|
+
};
|
|
48
214
|
}
|
|
215
|
+
export { MAX_BATCH_BYTES, MAX_BATCH_FILES };
|
|
49
216
|
//# sourceMappingURL=validate-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|