@platformos/platformos-mcp-supervisor 0.0.2 → 0.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.
- package/README.md +105 -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.d.ts +15 -2
- package/dist/bin/args.js +31 -3
- package/dist/bin/args.spec.d.ts +1 -0
- package/dist/bin/args.spec.js +35 -0
- package/dist/bin/platformos-mcp-supervisor.js +6 -2
- 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 +34 -0
- package/dist/context.js +12 -0
- package/dist/cost-model.d.ts +188 -0
- package/dist/cost-model.js +197 -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 +105 -0
- package/dist/enrich/enrich.js +136 -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/dependants.d.ts +75 -0
- package/dist/impact/dependants.js +104 -0
- package/dist/impact/diff.d.ts +27 -0
- package/dist/impact/diff.js +33 -0
- package/dist/impact/impact.d.ts +21 -0
- package/dist/impact/impact.js +160 -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 +39 -0
- package/dist/impact/project-scan.js +62 -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 +11 -9
- package/dist/result/assemble.js +63 -16
- package/dist/result/assemble.spec.d.ts +1 -0
- package/dist/result/assemble.spec.js +319 -0
- package/dist/result/blocking.d.ts +92 -0
- package/dist/result/blocking.js +116 -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 +19 -0
- package/dist/result/impact-states.js +10 -0
- package/dist/result/response-budget.d.ts +9 -0
- package/dist/result/response-budget.js +133 -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 +212 -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 +150 -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 +6 -1
- package/dist/transport/server.js +21 -10
- package/dist/transport/validate-code.d.ts +50 -15
- package/dist/transport/validate-code.js +238 -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 +65 -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 +319 -0
- package/package.json +8 -7
package/dist/result/types.d.ts
CHANGED
|
@@ -1,58 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `validate_code` result contract — the agent-facing surface of the
|
|
3
|
-
* supervisor.
|
|
2
|
+
* The `validate_code` result contract — the agent-facing surface of the supervisor.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
* Deliberately separate from check-common's `Offense`, which is the stable, minimal
|
|
5
|
+
* detection type consumed by editors/CLI/browser. The `result/` layer assembles this
|
|
6
|
+
* shape from enriched diagnostics via order-independent pure transforms.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The outcome of a `validate_code` call.
|
|
10
|
+
*
|
|
11
|
+
* `ok` / `warning` / `error` all mean "this file was checked" and differ only in what was
|
|
12
|
+
* found. `not_applicable` means the file was NOT checked (see `fileApplicability`), so it
|
|
13
|
+
* is neither an approval nor a reason to block: `must_fix_before_write` is always `false`
|
|
14
|
+
* for it and `next_step` carries the reason.
|
|
15
|
+
*/
|
|
16
|
+
export type ValidateCodeStatus = 'ok' | 'warning' | 'error' | 'not_applicable';
|
|
17
|
+
/**
|
|
18
|
+
* WHY a file was not checked, as a machine-readable code an agent can branch on.
|
|
19
|
+
* Present exactly when `status` is `not_applicable`.
|
|
10
20
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
21
|
+
* - `outside_project` — resolved outside the project root this server serves.
|
|
22
|
+
* - `unsupported_type` — inside the project, but nothing here parses it: an asset, or a
|
|
23
|
+
* file in no deployed subtree that is not a source anyway. ROUTINE —
|
|
24
|
+
* never advise moving it under `app/`.
|
|
25
|
+
* - `misplaced_source` — a platformOS SOURCE sitting outside every subtree the platform
|
|
26
|
+
* deploys, so it will never be loaded. Almost always a mistake, and
|
|
27
|
+
* the opposite advice from `unsupported_type`.
|
|
28
|
+
* - `too_large` — buffer above the size bound; refused before parsing.
|
|
29
|
+
* - `timed_out` — validation exceeded its deadline; nothing conclusive.
|
|
30
|
+
* - `ignored` — excluded by the project's `.platformos-check.yml` `ignore` list,
|
|
31
|
+
* so no check ran.
|
|
32
|
+
* - `internal_error` — the validator failed to produce a result. A bug here, not a
|
|
33
|
+
* property of the file, and not something a retry can fix.
|
|
16
34
|
*/
|
|
17
|
-
export type
|
|
18
|
-
|
|
35
|
+
export type NotApplicableReason = 'outside_project' | 'unsupported_type' | 'misplaced_source' | 'too_large' | 'timed_out' | 'ignored' | 'internal_error';
|
|
36
|
+
/**
|
|
37
|
+
* A refusal to validate: agent-facing prose plus its machine-readable cause.
|
|
38
|
+
*
|
|
39
|
+
* `reason` becomes the result's `next_step`; `code` becomes `not_applicable_reason`.
|
|
40
|
+
*/
|
|
41
|
+
export interface Declined {
|
|
42
|
+
reason: string;
|
|
43
|
+
code: NotApplicableReason;
|
|
44
|
+
}
|
|
19
45
|
export type ValidateCodeSeverity = 'error' | 'warning' | 'info';
|
|
20
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Tool input. Exactly ONE of the two forms:
|
|
48
|
+
*
|
|
49
|
+
* - `file_path` + `content` — a single file.
|
|
50
|
+
* - `files` — several files validated TOGETHER, so they can reference each other.
|
|
51
|
+
*
|
|
52
|
+
* Both are optional here because the "exactly one" rule is enforced by the handler; a zod
|
|
53
|
+
* union would emit `anyOf`, which several MCP clients do not surface.
|
|
54
|
+
*/
|
|
21
55
|
export interface ValidateCodeParams {
|
|
22
|
-
/** Path of the file under edit (absolute, or relative to the project root). */
|
|
23
|
-
file_path
|
|
24
|
-
/** The file contents
|
|
25
|
-
content
|
|
26
|
-
/**
|
|
27
|
-
|
|
56
|
+
/** Path of the single file under edit (absolute, or relative to the project root). */
|
|
57
|
+
file_path?: string;
|
|
58
|
+
/** The single file's contents (the in-memory buffer). */
|
|
59
|
+
content?: string;
|
|
60
|
+
/** Several files under edit, validated together. */
|
|
61
|
+
files?: Array<{
|
|
62
|
+
file_path: string;
|
|
63
|
+
content: string;
|
|
64
|
+
}>;
|
|
28
65
|
}
|
|
29
66
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* regenerates edit text from scratch.
|
|
67
|
+
* One replacement in the buffer, translated verbatim from a check-common
|
|
68
|
+
* `FixDescription`. The supervisor never authors edit text.
|
|
33
69
|
*
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
* - `guidance` — prose only; no machine-applicable edit.
|
|
70
|
+
* Offsets are 0-based indices into the buffer AS SENT — not the 1-based line/column the
|
|
71
|
+
* diagnostic carries.
|
|
37
72
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
description?: string;
|
|
41
|
-
/** 0-based offset into the file. */
|
|
73
|
+
export interface AgentEdit {
|
|
74
|
+
/** 0-based offset into the buffer, INCLUSIVE. */
|
|
42
75
|
start_index: number;
|
|
43
|
-
/** 0-based offset into the
|
|
76
|
+
/** 0-based offset into the buffer, EXCLUSIVE. Equal to `start_index` for an insertion. */
|
|
44
77
|
end_index: number;
|
|
78
|
+
/** Replacement text for the range. Empty string to delete. */
|
|
45
79
|
new_text: string;
|
|
46
|
-
}
|
|
47
|
-
type: 'guidance';
|
|
48
|
-
description: string;
|
|
49
|
-
};
|
|
80
|
+
}
|
|
50
81
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
|
|
82
|
+
* An agent-facing fix: prose plus the edits that apply it.
|
|
83
|
+
*
|
|
84
|
+
* Every edit is relative to the buffer as sent, so applying more than one requires
|
|
85
|
+
* accounting for index drift — or simply applying them from the end backwards.
|
|
86
|
+
*/
|
|
87
|
+
export interface AgentFix {
|
|
88
|
+
/**
|
|
89
|
+
* What the fix does. Present for a suggestion (the engine's own `Suggestion.message`);
|
|
90
|
+
* absent for a plain autofix, which the engine describes only through the message.
|
|
91
|
+
*/
|
|
92
|
+
description?: string;
|
|
93
|
+
/** The edits, in the order the engine recorded them. Never empty. */
|
|
94
|
+
edits: AgentEdit[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A diagnostic as surfaced to the agent: the structured check-common fields (`check`,
|
|
98
|
+
* `severity`, range, `message`) plus the supervisor's ergonomic enrichment (`hint`,
|
|
99
|
+
* `suggestions`, `fix`, `see_also`).
|
|
100
|
+
*
|
|
101
|
+
* Line/column are 1-based (converted from check-common's 0-based offsets in `result/`).
|
|
56
102
|
*/
|
|
57
103
|
export interface ValidateCodeDiagnostic {
|
|
58
104
|
/** The check code, e.g. `MissingPartial`, or a `pos-supervisor:` advisory code. */
|
|
@@ -69,87 +115,149 @@ export interface ValidateCodeDiagnostic {
|
|
|
69
115
|
end_column?: number;
|
|
70
116
|
/** Markdown explanation for the agent. */
|
|
71
117
|
hint?: string;
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Fixes the engine offers as OPTIONS rather than as the answer — `Offense.suggest`.
|
|
120
|
+
*
|
|
121
|
+
* Kept separate from {@link fix} because an agent must act on them differently: `fix` is
|
|
122
|
+
* safe to apply unread, a suggestion is a choice. Absent when the engine offered none.
|
|
123
|
+
*/
|
|
124
|
+
suggestions?: AgentFix[];
|
|
125
|
+
/**
|
|
126
|
+
* The engine's safe auto-fix for this diagnostic, when it has one — `Offense.fix`.
|
|
127
|
+
* Distinct from {@link suggestions}: this one is the answer, not a menu.
|
|
128
|
+
*/
|
|
77
129
|
fix?: AgentFix;
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
args?: Record<string, unknown>;
|
|
84
|
-
reason?: string;
|
|
130
|
+
/**
|
|
131
|
+
* URL of this check's documentation page, from check-common `meta.docs.url`. Absent for
|
|
132
|
+
* checks whose meta publishes none, rather than a guessed URL that 404s.
|
|
133
|
+
*/
|
|
134
|
+
see_also?: string;
|
|
85
135
|
}
|
|
86
136
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
137
|
+
* Whether the cross-file comparison ran. `computed` means this file's dependants were linted
|
|
138
|
+
* with the change applied and without it; `unavailable` means that could not be done (a
|
|
139
|
+
* failure, or the deadline); `not_applicable` means the file has no dependants this stage
|
|
140
|
+
* can find — the graph holds no edges pointing at it, or it has no logical name.
|
|
141
|
+
*
|
|
142
|
+
* `disabled` means this SERVER was started with cross-file impact turned off (`--no-impact`),
|
|
143
|
+
* so nothing was attempted and retrying will not change that — which is the whole reason it
|
|
144
|
+
* is not folded into `unavailable`.
|
|
145
|
+
*
|
|
146
|
+
* NONE OF THE FOUR IS A CLEARANCE, including `computed`. See {@link ValidateCodeImpact}.
|
|
89
147
|
*/
|
|
90
|
-
export type
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
148
|
+
export type ValidateCodeImpactStatus = 'computed' | 'unavailable' | 'not_applicable' | 'disabled';
|
|
149
|
+
/**
|
|
150
|
+
* What this change BREAKS in files the request is not editing — the question lint cannot
|
|
151
|
+
* answer, being per-file and forward-looking. Derived per request by linting the edited
|
|
152
|
+
* file's dependants twice, with the changeset applied and without it, and reporting only the
|
|
153
|
+
* difference. The findings are the check engine's own, so they carry its message, severity
|
|
154
|
+
* and documentation link rather than a second opinion invented here.
|
|
155
|
+
*
|
|
156
|
+
* IT DOES NOT ANSWER "WHO DEPENDS ON THIS FILE", and never will: `{% render var %}` names its
|
|
157
|
+
* target at runtime, so no static analysis can enumerate a file's dependants, and a count
|
|
158
|
+
* that omits the ones it cannot see reads as a licence to delete. Only findings are
|
|
159
|
+
* published; their absence claims nothing.
|
|
160
|
+
*/
|
|
161
|
+
export interface ValidateCodeImpact {
|
|
162
|
+
status: ValidateCodeImpactStatus;
|
|
163
|
+
/**
|
|
164
|
+
* Files the change broke, each with the findings it GAINED. A finding already present
|
|
165
|
+
* before the change is not reported, however severe: it is not this edit's doing.
|
|
166
|
+
*
|
|
167
|
+
* PRESENT ONLY WHEN NON-EMPTY, and absence is not a clearance — it means no break was
|
|
168
|
+
* found among the dependants that are visible, which is not the same as none existing.
|
|
169
|
+
*/
|
|
170
|
+
breaks?: ValidateCodeImpactBreak[];
|
|
171
|
+
/**
|
|
172
|
+
* Present only when more dependants were found than the bound allows: `returned` were
|
|
173
|
+
* linted, `total` were found.
|
|
174
|
+
*
|
|
175
|
+
* NOT the same kind of truncation as the result-level `truncated`, which withholds
|
|
176
|
+
* findings that WERE computed. The difference here was never checked at all, so an
|
|
177
|
+
* otherwise-clean answer carrying this field is a PARTIAL one.
|
|
178
|
+
*/
|
|
179
|
+
unchecked_dependants?: ValidateCodeBucketTruncation;
|
|
109
180
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
181
|
+
/** One file the change broke, and what it broke in it. */
|
|
182
|
+
export interface ValidateCodeImpactBreak {
|
|
183
|
+
/** The broken file, project-relative. NOT a file the request asked about. */
|
|
184
|
+
file: string;
|
|
185
|
+
/** The findings this change introduced there, in reading order. */
|
|
186
|
+
diagnostics: ValidateCodeDiagnostic[];
|
|
115
187
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
188
|
+
/** How many entries of one bucket were returned, against how many were found. */
|
|
189
|
+
export interface ValidateCodeBucketTruncation {
|
|
190
|
+
/** Entries present in this result's list. */
|
|
191
|
+
returned: number;
|
|
192
|
+
/** Entries the checks actually produced. Always greater than `returned`. */
|
|
193
|
+
total: number;
|
|
120
194
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Present exactly when findings were withheld to keep the response bounded; ABSENT means
|
|
197
|
+
* the lists are complete. Only the affected buckets appear.
|
|
198
|
+
*/
|
|
199
|
+
export interface ValidateCodeTruncation {
|
|
200
|
+
errors?: ValidateCodeBucketTruncation;
|
|
201
|
+
warnings?: ValidateCodeBucketTruncation;
|
|
202
|
+
infos?: ValidateCodeBucketTruncation;
|
|
203
|
+
/** Plain-language statement of what was withheld and what is still true. */
|
|
204
|
+
note: string;
|
|
130
205
|
}
|
|
131
206
|
/**
|
|
132
|
-
* The full `validate_code` result. Serialized as a single JSON text block over
|
|
133
|
-
*
|
|
207
|
+
* The full `validate_code` result. Serialized as a single JSON text block over the MCP
|
|
208
|
+
* stdio transport.
|
|
134
209
|
*/
|
|
135
210
|
export interface ValidateCodeResult {
|
|
136
211
|
status: ValidateCodeStatus;
|
|
137
212
|
/**
|
|
138
|
-
* When true the agent MUST NOT write the file. Set by any error or any
|
|
139
|
-
*
|
|
213
|
+
* When true the agent MUST NOT write the file. Set by any error or any "blocking"
|
|
214
|
+
* warning (the blocking set is defined explicitly in `result/`).
|
|
140
215
|
*/
|
|
141
216
|
must_fix_before_write: boolean;
|
|
142
217
|
errors: ValidateCodeDiagnostic[];
|
|
143
218
|
warnings: ValidateCodeDiagnostic[];
|
|
144
219
|
infos: ValidateCodeDiagnostic[];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Cross-file consequence of the edit: what it breaks in files you are not editing.
|
|
222
|
+
* Always present, and never a clearance. See {@link ValidateCodeImpact}.
|
|
223
|
+
*/
|
|
224
|
+
impact: ValidateCodeImpact;
|
|
148
225
|
/** Deterministic prose telling the agent what to do next. */
|
|
149
226
|
next_step?: string;
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Machine-readable cause when `status` is `not_applicable`; absent otherwise.
|
|
229
|
+
* See {@link NotApplicableReason}.
|
|
230
|
+
*/
|
|
231
|
+
not_applicable_reason?: NotApplicableReason;
|
|
232
|
+
/**
|
|
233
|
+
* Present only when the response bound withheld findings. `status` and
|
|
234
|
+
* `must_fix_before_write` are computed from the COMPLETE set of findings, before
|
|
235
|
+
* anything is withheld. See {@link ValidateCodeTruncation}.
|
|
236
|
+
*/
|
|
237
|
+
truncated?: ValidateCodeTruncation;
|
|
238
|
+
}
|
|
239
|
+
/** One file's outcome inside a multi-file {@link ValidateFilesResult}. */
|
|
240
|
+
export interface ValidateFilesEntry {
|
|
241
|
+
/** The `file_path` exactly as the caller supplied it, so results are easy to match up. */
|
|
242
|
+
file_path: string;
|
|
243
|
+
result: ValidateCodeResult;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The multi-file result: one entry per requested file plus a request-level gate. Returned
|
|
247
|
+
* only for the `files` input form; the single form returns a bare
|
|
248
|
+
* {@link ValidateCodeResult}.
|
|
249
|
+
*
|
|
250
|
+
* A batch is NOT atomic. One declined or failing file never sinks the others, so `files`
|
|
251
|
+
* always has an entry for every requested path.
|
|
252
|
+
*/
|
|
253
|
+
export interface ValidateFilesResult {
|
|
254
|
+
/**
|
|
255
|
+
* True when ANY file blocks — an OR over the files' own gates, so a file that is merely
|
|
256
|
+
* `not_applicable` never blocks the set.
|
|
257
|
+
*/
|
|
258
|
+
must_fix_before_write: boolean;
|
|
259
|
+
/** Per-file results, in the order requested. */
|
|
260
|
+
files: ValidateFilesEntry[];
|
|
261
|
+
/** Deterministic prose telling the agent what to do next across the whole batch. */
|
|
262
|
+
next_step?: string;
|
|
155
263
|
}
|
package/dist/result/types.js
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `validate_code` result contract — the agent-facing surface of the
|
|
3
|
-
* supervisor.
|
|
2
|
+
* The `validate_code` result contract — the agent-facing surface of the supervisor.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* behaviour is tuned. The `result/` layer assembles this shape from enriched
|
|
9
|
-
* diagnostics + advisories via order-independent pure transforms.
|
|
10
|
-
*
|
|
11
|
-
* Field names mirror the v1 supervisor so the parity safety net (TASK-8.5) can
|
|
12
|
-
* compare unchanged-contract fields against the captured baselines. Fields
|
|
13
|
-
* marked "TASK-8" are part of the contract but are only populated once the
|
|
14
|
-
* supervisor's per-domain layer and rule library are restored; the minimal
|
|
15
|
-
* TASK-7 build leaves them empty / null.
|
|
4
|
+
* Deliberately separate from check-common's `Offense`, which is the stable, minimal
|
|
5
|
+
* detection type consumed by editors/CLI/browser. The `result/` layer assembles this
|
|
6
|
+
* shape from enriched diagnostics via order-independent pure transforms.
|
|
16
7
|
*/
|
|
17
8
|
export {};
|
|
18
9
|
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The safety net for what was DELETED from the server instructions.
|
|
3
|
+
*
|
|
4
|
+
* `SERVER_INSTRUCTIONS` used to enumerate platformOS semantics in English — that a JSON
|
|
5
|
+
* literal in `{% assign %}` needs double quotes, which subscript targets a write tag
|
|
6
|
+
* accepts, that a filter inside an `{% if %}` is rejected by the deploy converter. That
|
|
7
|
+
* prose was there for a reason: agents write those exact mistakes.
|
|
8
|
+
*
|
|
9
|
+
* Removing it is only safe if the mistakes are still CAUGHT, so each row below is a fixture
|
|
10
|
+
* built from a deleted claim, asserting the check that owns it fires. Otherwise the
|
|
11
|
+
* instructions get shorter and agents get worse.
|
|
12
|
+
*
|
|
13
|
+
* The two `[]` rows are DOCUMENTED SILENCES, which is the one claim no diagnostic can
|
|
14
|
+
* deliver — an agent cannot learn "we deliberately do not check this" from a finding that
|
|
15
|
+
* never appears. Those stayed in the prose; everything that fires did not.
|
|
16
|
+
*
|
|
17
|
+
* Run against the DEFAULT check set, which is what a project gets before it configures
|
|
18
|
+
* anything and therefore what these claims were promising.
|
|
19
|
+
*/
|
|
20
|
+
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
24
|
+
import { runBatchLint } from '../lint/lint-batch.js';
|
|
25
|
+
import { SERVER_INSTRUCTIONS } from './instructions.js';
|
|
26
|
+
let projectDir;
|
|
27
|
+
beforeAll(() => {
|
|
28
|
+
projectDir = mkdtempSync(join(tmpdir(), 'mcp-sup-claims-'));
|
|
29
|
+
mkdirSync(join(projectDir, '.git'));
|
|
30
|
+
});
|
|
31
|
+
afterAll(() => {
|
|
32
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* One deleted claim: the pattern it warned about, and the checks that must still report
|
|
36
|
+
* it. Measured, not predicted — several of these are owned by a different check than the
|
|
37
|
+
* prose implied (a filter inside a condition is a PARSE error, not a filter check).
|
|
38
|
+
*/
|
|
39
|
+
const DELETED_CLAIMS = [
|
|
40
|
+
{
|
|
41
|
+
claim: 'a JSON literal in {% assign %} must use double quotes',
|
|
42
|
+
file: 'app/views/pages/json_quotes.liquid',
|
|
43
|
+
source: "{% assign o = {'k': 1} %}{{ o }}",
|
|
44
|
+
checks: ['JsonLiteralQuoteStyle'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
claim: 'a subscript write needs a Hash or an Array, not a number',
|
|
48
|
+
file: 'app/views/pages/write_target.liquid',
|
|
49
|
+
source: '{% assign n = 5 %}{% assign n["k"] = 1 %}{{ n }}',
|
|
50
|
+
checks: ['InvalidWriteTarget'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
claim: '{% assign x << v %} needs an Array; a Hash is refused',
|
|
54
|
+
file: 'app/views/pages/append_hash.liquid',
|
|
55
|
+
source: '{% assign h = {"a": 1} %}{% assign h << 2 %}{{ h }}',
|
|
56
|
+
checks: ['InvalidWriteTarget'],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
claim: "hash_assign's target must end in a bracket",
|
|
60
|
+
file: 'app/views/pages/hash_assign_target.liquid',
|
|
61
|
+
source: '{% assign h = {"a": 1} %}{% hash_assign h.k = 2 %}{{ h }}',
|
|
62
|
+
// A PARSE error, not a write-target one: the grammar rejects the form outright.
|
|
63
|
+
checks: ['DeprecatedTag', 'LiquidHTMLSyntaxError'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
claim: 'a filter inside a condition is rejected by the converter',
|
|
67
|
+
file: 'app/views/pages/filter_in_if.liquid',
|
|
68
|
+
source: "{% assign a = 'x' %}{% if a | upcase == 'A' %}y{% endif %}",
|
|
69
|
+
checks: ['LiquidHTMLSyntaxError'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
claim: 'a filter on a {% for %} operand must be assigned first',
|
|
73
|
+
file: 'app/views/pages/filter_in_for.liquid',
|
|
74
|
+
source: '{% assign l = "a,b" | split: "," %}{% for x in l | reverse %}{{ x }}{% endfor %}',
|
|
75
|
+
checks: ['LiquidHTMLSyntaxError', 'UndefinedObject'],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
claim: 'a filter elsewhere in a platformOS tag is silently ignored by the platform',
|
|
79
|
+
file: 'app/views/pages/filter_on_cache.liquid',
|
|
80
|
+
source: "{% cache 'k' | upcase %}x{% endcache %}",
|
|
81
|
+
checks: ['FilterWithoutEffect'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
claim: "{% for x in y limit: 'ten' %} warns on the argument type",
|
|
85
|
+
file: 'app/views/pages/tag_arg_type.liquid',
|
|
86
|
+
source: "{% for x in (1..3) limit: 'ten' %}{{ x }}{% endfor %}",
|
|
87
|
+
checks: ['ValidTagArgumentTypes'],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
claim: 'a Hash filter given a number warns (a hard 500 at render time)',
|
|
91
|
+
file: 'app/views/pages/filter_arg_type.liquid',
|
|
92
|
+
source: "{{ 123 | hash_add_key: 'k', 1 }}",
|
|
93
|
+
checks: ['ValidFilterArgumentTypes'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
claim: 'a key defined twice in one YAML mapping is reported and does not block',
|
|
97
|
+
file: 'app/translations/dup.yml',
|
|
98
|
+
source: 'en:\n a: 1\n a: 2\n',
|
|
99
|
+
checks: ['DuplicateYAMLKey'],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
claim: 'YAML 1.1 look-alikes (yes:/true:) are ONE key',
|
|
103
|
+
file: 'app/translations/lookalike.yml',
|
|
104
|
+
source: 'fr:\n yes: 1\n true: 2\n',
|
|
105
|
+
checks: ['DuplicateYAMLKey'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
claim: 'YAML that does not parse is reported and blocks',
|
|
109
|
+
file: 'app/schema/broken.yml',
|
|
110
|
+
source: 'a: [1, 2\n',
|
|
111
|
+
checks: ['YAMLSyntaxError'],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
/** The claims that were KEPT, because nothing fires and so nothing else could say it. */
|
|
115
|
+
const KEPT_SILENCES = [
|
|
116
|
+
{
|
|
117
|
+
claim: 'a core Liquid filter given an odd type coerces, and is never reported',
|
|
118
|
+
file: 'app/views/pages/core_filter.liquid',
|
|
119
|
+
source: '{{ 5 | upcase }}',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
claim: 'the SHAPE of a model schema is not checked',
|
|
123
|
+
file: 'app/schema/shape.yml',
|
|
124
|
+
source: 'name: thing\nproperties:\n - name: a\n type: string\n bogus: 1\n',
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* The half of duplicate-key detection that does NOT fire, and why the prose says the
|
|
128
|
+
* comparison is not exhaustive.
|
|
129
|
+
*
|
|
130
|
+
* `1:30` is a YAML 1.1 sexagesimal: Ruby's Psych resolves it to 5400, so these two keys
|
|
131
|
+
* are ONE key on the platform and the second value is discarded, while npm's `yaml` reads
|
|
132
|
+
* it as 90. The pair is one of the four named in check-common's
|
|
133
|
+
* `yaml/duplicate-keys.spec.ts`, which sweeps 5,476 token pairs against a live-Ruby oracle.
|
|
134
|
+
*
|
|
135
|
+
* Its control is the `yes:`/`true:` row above, which MUST fire.
|
|
136
|
+
*/
|
|
137
|
+
{
|
|
138
|
+
claim: 'a YAML 1.1 sexagesimal colliding with its own value is NOT reported',
|
|
139
|
+
file: 'app/translations/sexagesimal.yml',
|
|
140
|
+
source: 'en:\n 1:30: a\n 5400: b\n',
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
const checksFor = async (file, source) => {
|
|
144
|
+
const { diagnostics } = await runBatchLint({
|
|
145
|
+
projectDir,
|
|
146
|
+
buffers: [{ filePath: file, content: source }],
|
|
147
|
+
});
|
|
148
|
+
return [...new Set((diagnostics.get(file) ?? []).map((d) => d.check))].sort();
|
|
149
|
+
};
|
|
150
|
+
describe('Every claim removed from the instructions is still caught by a check', () => {
|
|
151
|
+
it.each(DELETED_CLAIMS)('$claim', async ({ file, source, checks }) => {
|
|
152
|
+
expect(await checksFor(file, source)).toEqual([...checks].sort());
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('The claims that were KEPT are the ones no diagnostic can deliver', () => {
|
|
156
|
+
it.each(KEPT_SILENCES)('$claim', async ({ file, source }) => {
|
|
157
|
+
// Nothing fires — which is exactly why the prose has to say it. This is the control
|
|
158
|
+
// for the group above: if these started reporting, the instructions would be claiming
|
|
159
|
+
// a silence that no longer exists.
|
|
160
|
+
expect(await checksFor(file, source)).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
it('and the instructions still state each of them', () => {
|
|
163
|
+
expect({
|
|
164
|
+
untypedArguments: SERVER_INSTRUCTIONS.includes('An argument the documentation leaves untyped'),
|
|
165
|
+
schemaShape: SERVER_INSTRUCTIONS.includes('The SHAPE of a model schema is not checked'),
|
|
166
|
+
duplicateKeyGap: SERVER_INSTRUCTIONS.includes('silence there does not prove two keys are distinct'),
|
|
167
|
+
perProjectConfig: SERVER_INSTRUCTIONS.includes('Coverage is per project'),
|
|
168
|
+
}).toEqual({
|
|
169
|
+
untypedArguments: true,
|
|
170
|
+
schemaShape: true,
|
|
171
|
+
duplicateKeyGap: true,
|
|
172
|
+
perProjectConfig: true,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
//# sourceMappingURL=instructions-coverage.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SERVER_INSTRUCTIONS: string;
|