@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.
Files changed (92) hide show
  1. package/README.md +105 -0
  2. package/dist/adapter-input.d.ts +113 -0
  3. package/dist/adapter-input.js +210 -0
  4. package/dist/adapter-input.spec.d.ts +1 -0
  5. package/dist/adapter-input.spec.js +275 -0
  6. package/dist/bin/args.d.ts +15 -2
  7. package/dist/bin/args.js +31 -3
  8. package/dist/bin/args.spec.d.ts +1 -0
  9. package/dist/bin/args.spec.js +35 -0
  10. package/dist/bin/platformos-mcp-supervisor.js +6 -2
  11. package/dist/check-docs.d.ts +21 -0
  12. package/dist/check-docs.js +38 -0
  13. package/dist/check-docs.spec.d.ts +1 -0
  14. package/dist/check-docs.spec.js +87 -0
  15. package/dist/context.d.ts +34 -0
  16. package/dist/context.js +12 -0
  17. package/dist/cost-model.d.ts +188 -0
  18. package/dist/cost-model.js +197 -0
  19. package/dist/cost-model.spec.d.ts +1 -0
  20. package/dist/cost-model.spec.js +91 -0
  21. package/dist/deadline.d.ts +27 -0
  22. package/dist/deadline.js +40 -0
  23. package/dist/deadline.spec.d.ts +1 -0
  24. package/dist/deadline.spec.js +83 -0
  25. package/dist/enrich/enrich.d.ts +105 -0
  26. package/dist/enrich/enrich.js +136 -0
  27. package/dist/enrich/enrich.spec.d.ts +1 -0
  28. package/dist/enrich/enrich.spec.js +184 -0
  29. package/dist/enrich/symbol.d.ts +16 -0
  30. package/dist/enrich/symbol.js +58 -0
  31. package/dist/enrich/symbol.spec.d.ts +1 -0
  32. package/dist/enrich/symbol.spec.js +89 -0
  33. package/dist/impact/dependants.d.ts +75 -0
  34. package/dist/impact/dependants.js +104 -0
  35. package/dist/impact/diff.d.ts +27 -0
  36. package/dist/impact/diff.js +33 -0
  37. package/dist/impact/impact.d.ts +21 -0
  38. package/dist/impact/impact.js +160 -0
  39. package/dist/impact/impact.spec.d.ts +1 -0
  40. package/dist/impact/impact.spec.js +357 -0
  41. package/dist/impact/project-scan.d.ts +39 -0
  42. package/dist/impact/project-scan.js +62 -0
  43. package/dist/impact/project-scan.spec.d.ts +1 -0
  44. package/dist/impact/project-scan.spec.js +121 -0
  45. package/dist/index.d.ts +1 -1
  46. package/dist/lint/fixes.d.ts +28 -0
  47. package/dist/lint/fixes.js +30 -0
  48. package/dist/lint/lint-batch.d.ts +100 -0
  49. package/dist/lint/lint-batch.js +98 -0
  50. package/dist/lint/lint-batch.spec.d.ts +1 -0
  51. package/dist/lint/lint-batch.spec.js +503 -0
  52. package/dist/lint/lint.d.ts +11 -1
  53. package/dist/lint/lint.js +9 -3
  54. package/dist/result/assemble.d.ts +11 -9
  55. package/dist/result/assemble.js +63 -16
  56. package/dist/result/assemble.spec.d.ts +1 -0
  57. package/dist/result/assemble.spec.js +319 -0
  58. package/dist/result/blocking.d.ts +92 -0
  59. package/dist/result/blocking.js +116 -0
  60. package/dist/result/blocking.spec.d.ts +1 -0
  61. package/dist/result/blocking.spec.js +201 -0
  62. package/dist/result/impact-states.d.ts +19 -0
  63. package/dist/result/impact-states.js +10 -0
  64. package/dist/result/response-budget.d.ts +9 -0
  65. package/dist/result/response-budget.js +133 -0
  66. package/dist/result/response-budget.spec.d.ts +1 -0
  67. package/dist/result/response-budget.spec.js +222 -0
  68. package/dist/result/types.d.ts +212 -104
  69. package/dist/result/types.js +4 -13
  70. package/dist/transport/instructions-coverage.spec.d.ts +1 -0
  71. package/dist/transport/instructions-coverage.spec.js +176 -0
  72. package/dist/transport/instructions.d.ts +1 -0
  73. package/dist/transport/instructions.js +150 -0
  74. package/dist/transport/process-guards.d.ts +43 -0
  75. package/dist/transport/process-guards.js +63 -0
  76. package/dist/transport/process-guards.spec.d.ts +1 -0
  77. package/dist/transport/process-guards.spec.js +200 -0
  78. package/dist/transport/server.d.ts +6 -1
  79. package/dist/transport/server.js +21 -10
  80. package/dist/transport/validate-code.d.ts +50 -15
  81. package/dist/transport/validate-code.js +238 -33
  82. package/dist/transport/validate-code.spec.d.ts +1 -0
  83. package/dist/transport/validate-code.spec.js +2303 -0
  84. package/dist/validate/batch-bounds.d.ts +34 -0
  85. package/dist/validate/batch-bounds.js +65 -0
  86. package/dist/validate/batch-coherence.d.ts +21 -0
  87. package/dist/validate/batch-coherence.js +54 -0
  88. package/dist/validate/batch-coherence.spec.d.ts +1 -0
  89. package/dist/validate/batch-coherence.spec.js +111 -0
  90. package/dist/validate/validate-buffers.d.ts +27 -0
  91. package/dist/validate/validate-buffers.js +319 -0
  92. package/package.json +8 -7
@@ -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
- * This is intentionally separate from check-common's `Offense`: `Offense` is a
6
- * stable, minimal detection type consumed by editors/CLI/browser, whereas
7
- * `ValidateCodeResult` is an LLM-ergonomics surface that may churn as agent
8
- * behaviour is tuned. The `result/` layer assembles this shape from enriched
9
- * diagnostics + advisories via order-independent pure transforms.
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
- * 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.
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 ValidateCodeMode = 'full' | 'quick';
18
- export type ValidateCodeStatus = 'ok' | 'warning' | 'error';
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
- /** Tool input. */
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: string;
24
- /** The file contents to validate (the in-memory buffer). */
25
- content: string;
26
- /** Depth of analysis. Defaults to `full`. */
27
- mode?: ValidateCodeMode;
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
- * An agent-facing fix. Translated from a check-common `FixDescription` (the
31
- * structured edits the engine already computed); the supervisor never
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
- * - `text_edit` — replace `[start_index, end_index)` with `new_text`.
35
- * - `insert` — insert `new_text` at `start_index` (`end_index === start_index`).
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 type AgentFix = {
39
- type: 'text_edit' | 'insert';
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 file. */
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
- * A diagnostic as surfaced to the agent. Carries the structured check-common
52
- * fields (`check`, `severity`, range, `message`) plus the supervisor's
53
- * ergonomic enrichment (`hint`, `suggestion`, `confidence`, `fix`, `see_also`).
54
- * Line/column are 1-based (converted from check-common's 0-based offsets in the
55
- * `result/` layer).
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
- /** A short, one-line "did you mean / use this" pointer. */
73
- suggestion?: string;
74
- /** Static confidence in [0, 1] that this diagnostic + its fix are correct. */
75
- confidence?: number;
76
- /** A single concrete fix for this diagnostic, when one is available. */
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
- /** A pointer to another supervisor tool / doc that helps resolve this. */
79
- see_also?: SeeAlso;
80
- }
81
- export interface SeeAlso {
82
- tool?: string;
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
- * A proposed fix shown to the agent at the top level of the result. Carries the
88
- * originating `check` so the agent can correlate it with a diagnostic.
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 ProposedFix = AgentFix & {
91
- check?: string | null;
92
- };
93
- /** A group of related diagnostics sharing a root cause, with a unified remedy. */
94
- export interface DiagnosticCluster {
95
- check: string;
96
- count: number;
97
- unified_fix?: string;
98
- }
99
- /** An advisory note about the file's architecture (doc-block coverage, layout correctness, …). */
100
- export interface ScorecardNote {
101
- category: string;
102
- status: 'ok' | 'warning' | 'info';
103
- reason: string;
104
- }
105
- export interface TipEntry {
106
- id: string;
107
- severity: string;
108
- message: string;
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
- export interface DomainGuideGotcha {
111
- id: string;
112
- message: string;
113
- severity: string;
114
- applies_to_errors?: string[];
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
- export interface DomainGuide {
117
- domain: string;
118
- rule?: string;
119
- triggered_gotchas: DomainGuideGotcha[];
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
- export interface ValidateCodeStructuralSnapshot {
122
- renders_used: string[];
123
- filters_used: string[];
124
- tags_used: string[];
125
- translation_keys: string[];
126
- doc_params: string[];
127
- slug: string | null;
128
- layout: string | null;
129
- method: string | null;
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
- * the MCP stdio transport.
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
- * "blocking" warning (the blocking set is defined explicitly in `result/`).
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
- proposed_fixes: ProposedFix[];
146
- clusters: DiagnosticCluster[];
147
- scorecard: ScorecardNote[];
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
- /** Parse-failure message when the file could not be parsed at all; null/absent otherwise. */
151
- parse_error?: string | null;
152
- tips?: TipEntry[];
153
- domain_guide?: DomainGuide | null;
154
- structural?: ValidateCodeStructuralSnapshot | null;
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
  }
@@ -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
- * This is intentionally separate from check-common's `Offense`: `Offense` is a
6
- * stable, minimal detection type consumed by editors/CLI/browser, whereas
7
- * `ValidateCodeResult` is an LLM-ergonomics surface that may churn as agent
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;