@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
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`.
|
|
20
|
+
*
|
|
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.
|
|
34
|
+
*/
|
|
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.
|
|
10
38
|
*
|
|
11
|
-
*
|
|
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.
|
|
39
|
+
* `reason` becomes the result's `next_step`; `code` becomes `not_applicable_reason`.
|
|
16
40
|
*/
|
|
17
|
-
export
|
|
18
|
-
|
|
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,147 @@ 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
|
+
* Applicability of the blast-radius answer. Distinguishes a real "nothing depends on this"
|
|
138
|
+
* (`computed`, total 0 — safe to change) from "we could not find out" (`unavailable`) and
|
|
139
|
+
* from "this file type has no dependency graph" (`not_applicable`), so a failed lookup can
|
|
140
|
+
* never be misread as a green light.
|
|
141
|
+
*
|
|
142
|
+
* `not_applicable` covers files that are not graph-trackable edge targets (schema /
|
|
143
|
+
* custom-model-type / translation YAML): they are wired by model/table NAME, not by file
|
|
144
|
+
* reference (see ADR 004), so `total: 0` would falsely read as "safe to change". It also
|
|
145
|
+
* covers a source in no platformOS directory, which has no logical name to reference.
|
|
89
146
|
*/
|
|
90
|
-
export type
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
export interface
|
|
101
|
-
|
|
102
|
-
status:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
147
|
+
export type ValidateCodeImpactStatus = 'computed' | 'unavailable' | 'not_applicable';
|
|
148
|
+
/**
|
|
149
|
+
* Cross-file "blast radius" of editing the file: who DEPENDS ON it, which lint cannot see
|
|
150
|
+
* (lint is per-file, forward-looking). Derived per request from the project's own text
|
|
151
|
+
* with the changeset's buffers overlaid.
|
|
152
|
+
*
|
|
153
|
+
* `scope` is always `direct` (immediate callers only — transitive closure is noise).
|
|
154
|
+
* `dependents` is meaningful ONLY when `status` is `computed`; otherwise it is zeroed and
|
|
155
|
+
* the status says why.
|
|
156
|
+
*/
|
|
157
|
+
export interface ValidateCodeImpact {
|
|
158
|
+
scope: 'direct';
|
|
159
|
+
status: ValidateCodeImpactStatus;
|
|
160
|
+
dependents: {
|
|
161
|
+
/** Number of distinct files that reference the edited file. */
|
|
162
|
+
total: number;
|
|
163
|
+
/** Distinct referencing files per edge kind (render/include/function/…); a file using two kinds counts in both. */
|
|
164
|
+
by_kind: Record<string, number>;
|
|
165
|
+
/** Up to 10 distinct referencing files, project-relative, sorted. */
|
|
166
|
+
sample: string[];
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Dependent callers whose arguments do NOT match the edited file's `{% doc %}`
|
|
170
|
+
* signature — the cross-file counterpart to the `PartialCallArguments` lint check.
|
|
171
|
+
* Present ONLY when the edited buffer declares a `{% doc %}` block: an empty array means
|
|
172
|
+
* "checked, every caller matches", absent means "no contract to check against".
|
|
173
|
+
*/
|
|
174
|
+
signature_risk?: ValidateCodeSignatureRisk[];
|
|
109
175
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
176
|
+
/** One dependent caller at risk from the edited file's current `{% doc %}` signature. */
|
|
177
|
+
export interface ValidateCodeSignatureRisk {
|
|
178
|
+
/** The referencing file, project-relative. */
|
|
179
|
+
caller: string;
|
|
180
|
+
/** Required `@param`s the caller does not pass. */
|
|
181
|
+
missing_required: string[];
|
|
182
|
+
/** Arguments the caller passes that the `{% doc %}` block does not declare. */
|
|
183
|
+
unexpected_args: string[];
|
|
115
184
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
185
|
+
/** How many entries of one bucket were returned, against how many were found. */
|
|
186
|
+
export interface ValidateCodeBucketTruncation {
|
|
187
|
+
/** Entries present in this result's list. */
|
|
188
|
+
returned: number;
|
|
189
|
+
/** Entries the checks actually produced. Always greater than `returned`. */
|
|
190
|
+
total: number;
|
|
120
191
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Present exactly when findings were withheld to keep the response bounded; ABSENT means
|
|
194
|
+
* the lists are complete. Only the affected buckets appear.
|
|
195
|
+
*/
|
|
196
|
+
export interface ValidateCodeTruncation {
|
|
197
|
+
errors?: ValidateCodeBucketTruncation;
|
|
198
|
+
warnings?: ValidateCodeBucketTruncation;
|
|
199
|
+
infos?: ValidateCodeBucketTruncation;
|
|
200
|
+
/** Plain-language statement of what was withheld and what is still true. */
|
|
201
|
+
note: string;
|
|
130
202
|
}
|
|
131
203
|
/**
|
|
132
|
-
* The full `validate_code` result. Serialized as a single JSON text block over
|
|
133
|
-
*
|
|
204
|
+
* The full `validate_code` result. Serialized as a single JSON text block over the MCP
|
|
205
|
+
* stdio transport.
|
|
134
206
|
*/
|
|
135
207
|
export interface ValidateCodeResult {
|
|
136
208
|
status: ValidateCodeStatus;
|
|
137
209
|
/**
|
|
138
|
-
* When true the agent MUST NOT write the file. Set by any error or any
|
|
139
|
-
*
|
|
210
|
+
* When true the agent MUST NOT write the file. Set by any error or any "blocking"
|
|
211
|
+
* warning (the blocking set is defined explicitly in `result/`).
|
|
140
212
|
*/
|
|
141
213
|
must_fix_before_write: boolean;
|
|
142
214
|
errors: ValidateCodeDiagnostic[];
|
|
143
215
|
warnings: ValidateCodeDiagnostic[];
|
|
144
216
|
infos: ValidateCodeDiagnostic[];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Cross-file blast radius: who depends on the edited file. Always present; `status`
|
|
219
|
+
* distinguishes a real "nothing depends on this" from "not computed".
|
|
220
|
+
* See {@link ValidateCodeImpact}.
|
|
221
|
+
*/
|
|
222
|
+
impact: ValidateCodeImpact;
|
|
148
223
|
/** Deterministic prose telling the agent what to do next. */
|
|
149
224
|
next_step?: string;
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Machine-readable cause when `status` is `not_applicable`; absent otherwise.
|
|
227
|
+
* See {@link NotApplicableReason}.
|
|
228
|
+
*/
|
|
229
|
+
not_applicable_reason?: NotApplicableReason;
|
|
230
|
+
/**
|
|
231
|
+
* Present only when the response bound withheld findings. `status` and
|
|
232
|
+
* `must_fix_before_write` are computed from the COMPLETE set of findings, before
|
|
233
|
+
* anything is withheld. See {@link ValidateCodeTruncation}.
|
|
234
|
+
*/
|
|
235
|
+
truncated?: ValidateCodeTruncation;
|
|
236
|
+
}
|
|
237
|
+
/** One file's outcome inside a multi-file {@link ValidateFilesResult}. */
|
|
238
|
+
export interface ValidateFilesEntry {
|
|
239
|
+
/** The `file_path` exactly as the caller supplied it, so results are easy to match up. */
|
|
240
|
+
file_path: string;
|
|
241
|
+
result: ValidateCodeResult;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The multi-file result: one entry per requested file plus a request-level gate. Returned
|
|
245
|
+
* only for the `files` input form; the single form returns a bare
|
|
246
|
+
* {@link ValidateCodeResult}.
|
|
247
|
+
*
|
|
248
|
+
* A batch is NOT atomic. One declined or failing file never sinks the others, so `files`
|
|
249
|
+
* always has an entry for every requested path.
|
|
250
|
+
*/
|
|
251
|
+
export interface ValidateFilesResult {
|
|
252
|
+
/**
|
|
253
|
+
* True when ANY file blocks — an OR over the files' own gates, so a file that is merely
|
|
254
|
+
* `not_applicable` never blocks the set.
|
|
255
|
+
*/
|
|
256
|
+
must_fix_before_write: boolean;
|
|
257
|
+
/** Per-file results, in the order requested. */
|
|
258
|
+
files: ValidateFilesEntry[];
|
|
259
|
+
/** Deterministic prose telling the agent what to do next across the whole batch. */
|
|
260
|
+
next_step?: string;
|
|
155
261
|
}
|
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;
|