@jphutchins/code-review 0.1.0-alpha.6 → 0.1.0-alpha.60

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.
@@ -5,7 +5,11 @@
5
5
  "description": "A date-stamped map of per-model token prices (USD per 1M tokens) the commenter uses to recompute canonical cost from the abstract result envelope's `models` array (SPEC §6.1). The CLI's `vendor_cost_usd` is vendor-priced and wrong for non-Anthropic backends; this map is the recomputation source. Kept free of $ref/$defs to match the findings schema's house style (inlining is not strictly required here — the price map is never passed to a model — but consistency with findings.schema.json is preferred).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
- "required": ["_updated", "_unit", "models"],
8
+ "required": [
9
+ "_updated",
10
+ "_unit",
11
+ "models"
12
+ ],
9
13
  "properties": {
10
14
  "_comment": {
11
15
  "type": "string",
@@ -21,33 +25,149 @@
21
25
  },
22
26
  "models": {
23
27
  "type": "object",
24
- "description": "Map of model identifier → per-token prices. Keys are the model identifiers as they appear in the result envelope's `models[].model`.",
28
+ "description": "Map of model identifier → per-token prices. Keys are the model identifiers as they appear in the result envelope's `models[].model`. A model's value is EITHER a flat all-day price (unchanged) OR a set of UTC time-of-day slots (issue #170), optionally with a second `weekend_slots` partition (issue #216), for a provider with peak/off-peak rates.",
25
29
  "additionalProperties": {
26
- "type": "object",
27
- "additionalProperties": false,
28
- "required": ["in", "out", "cache_read", "cache_write"],
29
- "properties": {
30
- "in": {
31
- "type": "number",
32
- "minimum": 0,
33
- "description": "Price per 1M input tokens."
30
+ "oneOf": [
31
+ {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": [
35
+ "in",
36
+ "out",
37
+ "cache_read",
38
+ "cache_write"
39
+ ],
40
+ "properties": {
41
+ "in": {
42
+ "type": "number",
43
+ "minimum": 0,
44
+ "description": "Price per 1M input tokens (cache-miss)."
45
+ },
46
+ "out": {
47
+ "type": "number",
48
+ "minimum": 0,
49
+ "description": "Price per 1M output tokens."
50
+ },
51
+ "cache_read": {
52
+ "type": "number",
53
+ "minimum": 0,
54
+ "description": "Price per 1M cache-read tokens (prompt-cache hits)."
55
+ },
56
+ "cache_write": {
57
+ "type": "number",
58
+ "minimum": 0,
59
+ "description": "Price per 1M cache-write tokens (prompt-cache population)."
60
+ }
61
+ }
34
62
  },
35
- "out": {
36
- "type": "number",
37
- "minimum": 0,
38
- "description": "Price per 1M output tokens."
39
- },
40
- "cache_read": {
41
- "type": "number",
42
- "minimum": 0,
43
- "description": "Price per 1M cache-read tokens (prompt-cache hits)."
44
- },
45
- "cache_write": {
46
- "type": "number",
47
- "minimum": 0,
48
- "description": "Price per 1M cache-write tokens (prompt-cache population)."
63
+ {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": [
67
+ "slots"
68
+ ],
69
+ "description": "UTC time-of-day pricing (issue #170). `slots` MUST partition the full 24h UTC day with no gap and no overlap: each is a half-open [utc_from, utc_to) window, and a slot whose utc_to <= utc_from wraps past midnight. Slot times are always UTC and there is no timezone or DST field in the data. `weekend_slots` is an optional second partition (issue #216), selected on Saturdays and Sundays in BEIJING time — a day-of-week RULE, not a timezone axis: the Beijing weekend runs Friday 16:00 UTC to Sunday 16:00 UTC, so weekend-ness is NOT a property of the UTC date. It must partition the day exactly as `slots` does. Like every rate here it states CURRENT pricing, so recomputing an envelope from before a pricing change reprices it at today's rates rather than what was billed. A model with no `weekend_slots` uses `slots` every day. Cost recomputation selects the map first, then the one slot covering the run's UTC instant; a run's time-of-day matching zero or more than one slot is a misconfiguration that warns loudly and prices that model at $0 (never silently wrong).",
70
+ "properties": {
71
+ "slots": {
72
+ "type": "array",
73
+ "minItems": 1,
74
+ "items": {
75
+ "type": "object",
76
+ "additionalProperties": false,
77
+ "required": [
78
+ "utc_from",
79
+ "utc_to",
80
+ "in",
81
+ "out",
82
+ "cache_read",
83
+ "cache_write"
84
+ ],
85
+ "properties": {
86
+ "utc_from": {
87
+ "type": "string",
88
+ "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
89
+ "description": "Slot start, HH:MM in UTC (inclusive)."
90
+ },
91
+ "utc_to": {
92
+ "type": "string",
93
+ "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
94
+ "description": "Slot end, HH:MM in UTC (exclusive); a value <= utc_from wraps past midnight."
95
+ },
96
+ "in": {
97
+ "type": "number",
98
+ "minimum": 0,
99
+ "description": "Price per 1M input tokens (cache-miss) in this slot."
100
+ },
101
+ "out": {
102
+ "type": "number",
103
+ "minimum": 0,
104
+ "description": "Price per 1M output tokens in this slot."
105
+ },
106
+ "cache_read": {
107
+ "type": "number",
108
+ "minimum": 0,
109
+ "description": "Price per 1M cache-read tokens (prompt-cache hits) in this slot."
110
+ },
111
+ "cache_write": {
112
+ "type": "number",
113
+ "minimum": 0,
114
+ "description": "Price per 1M cache-write tokens in this slot."
115
+ }
116
+ }
117
+ }
118
+ },
119
+ "weekend_slots": {
120
+ "type": "array",
121
+ "minItems": 1,
122
+ "items": {
123
+ "type": "object",
124
+ "additionalProperties": false,
125
+ "required": [
126
+ "utc_from",
127
+ "utc_to",
128
+ "in",
129
+ "out",
130
+ "cache_read",
131
+ "cache_write"
132
+ ],
133
+ "properties": {
134
+ "utc_from": {
135
+ "type": "string",
136
+ "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
137
+ "description": "Slot start, HH:MM in UTC (inclusive)."
138
+ },
139
+ "utc_to": {
140
+ "type": "string",
141
+ "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
142
+ "description": "Slot end, HH:MM in UTC (exclusive); a value <= utc_from wraps past midnight."
143
+ },
144
+ "in": {
145
+ "type": "number",
146
+ "minimum": 0,
147
+ "description": "Price per 1M input tokens (cache-miss) in this slot."
148
+ },
149
+ "out": {
150
+ "type": "number",
151
+ "minimum": 0,
152
+ "description": "Price per 1M output tokens in this slot."
153
+ },
154
+ "cache_read": {
155
+ "type": "number",
156
+ "minimum": 0,
157
+ "description": "Price per 1M cache-read tokens (prompt-cache hits) in this slot."
158
+ },
159
+ "cache_write": {
160
+ "type": "number",
161
+ "minimum": 0,
162
+ "description": "Price per 1M cache-write tokens in this slot."
163
+ }
164
+ }
165
+ },
166
+ "description": "Optional: the slot partition used on Beijing Saturdays and Sundays. Same shape and same 24h-partition requirement as `slots`."
167
+ }
168
+ }
49
169
  }
50
- }
170
+ ]
51
171
  }
52
172
  }
53
173
  }
@@ -0,0 +1,418 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/JPHutchins/code-review/schema-v0.9.0/schema/findings.schema.json",
4
+ "title": "Code Review \u2014 findings",
5
+ "description": "Structured output of an agentic PR code review. The review agent emits this object (e.g. via a coding-agent CLI's structured-output mode, such as Claude Code's `--json-schema`, whose result lands in `.structured_output`, or a file).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_version",
10
+ "summary",
11
+ "verdict",
12
+ "findings"
13
+ ],
14
+ "properties": {
15
+ "schema_version": {
16
+ "type": "string",
17
+ "pattern": "^(0|[1-9]\\d*)\\.(\\d+)\\.(\\d+)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$",
18
+ "description": "Semver version of this findings schema the object conforms to (e.g. \"0.1.0\")."
19
+ },
20
+ "summary": {
21
+ "type": "string",
22
+ "description": "1-4 sentence markdown justification of the overall verdict \u2014 the shape of the review and why the verdict follows from it. Do not restate individual findings (those live in `findings`) or cross-cutting observations (those live in `systemic_problems`); the summary explains the verdict, the arrays carry the evidence."
23
+ },
24
+ "verdict": {
25
+ "type": "string",
26
+ "enum": [
27
+ "approve",
28
+ "comment",
29
+ "changes",
30
+ "error"
31
+ ],
32
+ "description": "Overall stance. The review agent emits one of `approve`/`comment`/`changes`. `error` is reserved for the pipeline: it marks a run that produced no verdict about the diff (an operational failure, a security refusal, or an empty diff with nothing to review \u2014 always with `findings: []`), so a consumer switching on `verdict` gets a case it does not recognise rather than a false clean pass."
33
+ },
34
+ "systemic_problems": {
35
+ "type": "array",
36
+ "description": "Zero or more cross-cutting observations that tie several findings together or describe a pattern spanning the change, and are hard to express with a single line range. Omit the field when there are none; the summary must not restate them.",
37
+ "items": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": [
41
+ "title",
42
+ "description",
43
+ "severity",
44
+ "reasoning",
45
+ "confidence",
46
+ "likelihood"
47
+ ],
48
+ "properties": {
49
+ "title": {
50
+ "type": "string",
51
+ "description": "One-line summary of the systemic problem."
52
+ },
53
+ "description": {
54
+ "type": "string",
55
+ "description": "1-3 sentence markdown explanation of the pattern and why it matters \u2014 how it ties the related findings together. Details belong in the individual findings' `reasoning`."
56
+ },
57
+ "severity": {
58
+ "type": "string",
59
+ "enum": [
60
+ "critical",
61
+ "major",
62
+ "minor",
63
+ "nit"
64
+ ],
65
+ "description": "Overall severity of the systemic problem \u2014 the severity of the pattern itself, not of any single finding. It does not count toward the findings severity histogram."
66
+ },
67
+ "reasoning": {
68
+ "type": "string",
69
+ "description": "Rationale/evidence for why the systemic problem holds, so a human or downstream agent can judge its soundness \u2014 distinct from `description`, which explains the pattern to the reader. Mirrors the finding field of the same name."
70
+ },
71
+ "confidence": {
72
+ "type": "number",
73
+ "minimum": 0,
74
+ "maximum": 1,
75
+ "description": "0..1 confidence in the systemic problem itself. Mirrors the finding field of the same name."
76
+ },
77
+ "likelihood": {
78
+ "type": "number",
79
+ "minimum": 0,
80
+ "maximum": 1,
81
+ "description": "Set to 1 for a systemic problem. A structural/cross-cutting observation has no single triggering input \u2014 it is definitionally always present \u2014 so the convergence score treats a systemic problem's likelihood as 1 and never discounts it. (Required for schema symmetry with a finding's likelihood; the written value is not read by the score.)"
82
+ },
83
+ "code": {
84
+ "type": "string",
85
+ "description": "Stable rule identifier (e.g. \"repeated-null-check\") for rule-based filtering, suppression, and cross-run dedup \u2014 mirrors the finding field of the same name."
86
+ },
87
+ "code_url": {
88
+ "type": "string",
89
+ "format": "uri",
90
+ "description": "URL documenting the rule named by code."
91
+ },
92
+ "finding_codes": {
93
+ "type": "array",
94
+ "items": {
95
+ "type": "string"
96
+ },
97
+ "description": "The `code` values of the findings this systemic problem ties together, when those findings carry codes."
98
+ },
99
+ "paths": {
100
+ "type": "array",
101
+ "items": {
102
+ "type": "string"
103
+ },
104
+ "description": "Repo-relative file paths the systemic problem spans."
105
+ },
106
+ "id": {
107
+ "type": "string",
108
+ "description": "Stable mechanism identifier \u2014 see the findings-level `id` note; the legacy reader maps it from `code`."
109
+ },
110
+ "finding_ids": {
111
+ "type": "array",
112
+ "items": {
113
+ "type": "string"
114
+ },
115
+ "description": "The tolerant-in spelling of `finding_codes` (0.10 renamed it)."
116
+ }
117
+ }
118
+ }
119
+ },
120
+ "scope_metastasis": {
121
+ "type": "object",
122
+ "additionalProperties": false,
123
+ "description": "Advisory (issue #150): the mechanisms whose findings recurred in consecutive prior full-review rounds \u2014 per-code consecutive-round counts plus a decision prompt. The review agent does NOT write this field: the commenter re-derives it from the carried round history and delivers it in the prior review context of a re-review. When it is present, respond to the decision prompt \u2014 state in your summary whether you are committing to the expanding scope (planning the remaining facets of the recurring mechanism(s) as one unit) or narrowing the scope \u2014 so the decision is made explicitly rather than left to emerge piecemeal.",
124
+ "required": [
125
+ "decision_prompt",
126
+ "recurring"
127
+ ],
128
+ "properties": {
129
+ "decision_prompt": {
130
+ "type": "string",
131
+ "description": "The scope decision to make in the summary of the next review \u2014 a decision, not a directive."
132
+ },
133
+ "recurring": {
134
+ "type": "array",
135
+ "description": "The mechanisms flagged: one entry per code whose findings recurred in `consecutive_rounds` or more consecutive completed full-review rounds.",
136
+ "items": {
137
+ "type": "object",
138
+ "additionalProperties": false,
139
+ "required": [
140
+ "consecutive_rounds",
141
+ "start_round"
142
+ ],
143
+ "properties": {
144
+ "code": {
145
+ "type": "string",
146
+ "description": "The finding code of the recurring mechanism."
147
+ },
148
+ "consecutive_rounds": {
149
+ "type": "integer",
150
+ "minimum": 1,
151
+ "maximum": 9007199254740991,
152
+ "description": "How many consecutive completed full-review rounds (ending at the last completed round) carried a finding with this code. Same-head CI retries are collapsed by the streak detector, so this is the de-duplicated streak, not the raw span: it may be less than `round - start_round + 1` when the history contains retries."
153
+ },
154
+ "start_round": {
155
+ "type": "integer",
156
+ "minimum": 1,
157
+ "maximum": 9007199254740991,
158
+ "description": "The 1-indexed round where the consecutive streak began."
159
+ },
160
+ "id": {
161
+ "type": "string",
162
+ "description": "The tolerant-in spelling of `code` (0.10 renamed it)."
163
+ }
164
+ },
165
+ "anyOf": [
166
+ {
167
+ "required": [
168
+ "code"
169
+ ],
170
+ "properties": {
171
+ "code": {
172
+ "type": "string"
173
+ }
174
+ }
175
+ },
176
+ {
177
+ "required": [
178
+ "id"
179
+ ],
180
+ "properties": {
181
+ "id": {
182
+ "type": "string"
183
+ }
184
+ }
185
+ }
186
+ ]
187
+ }
188
+ }
189
+ }
190
+ },
191
+ "convergence": {
192
+ "type": "object",
193
+ "additionalProperties": false,
194
+ "description": "Advisory (issue #174): the current round's convergence score, the threshold it is judged against, whether it converged, and the per-round trajectory. The review agent does NOT write this field \u2014 the pipeline computes the score deterministically from the findings (floor(severity) + headroom \u00d7 confidence \u00d7 likelihood, summed) and stamps it, so this JSON document is the sole source of the convergence data a decoding agent reads. `score` and `threshold` are both carried so the number is interpretable on its own (converged when score <= threshold); prior rounds are carried verbatim, so changing the threshold never rewrites a past round's score.",
195
+ "required": [
196
+ "score",
197
+ "threshold",
198
+ "converged"
199
+ ],
200
+ "properties": {
201
+ "score": {
202
+ "type": "number",
203
+ "description": "This round's convergence score \u2014 the sum of every finding's and systemic problem's contribution, rounded to 2 decimals. Lower is closer to convergence."
204
+ },
205
+ "threshold": {
206
+ "type": "number",
207
+ "description": "The convergence tolerance in force for this round: the round is converged when score <= threshold."
208
+ },
209
+ "converged": {
210
+ "type": "boolean",
211
+ "description": "Whether this round converged (score <= threshold). Advisory only \u2014 never alters the verdict."
212
+ },
213
+ "rounds": {
214
+ "type": "array",
215
+ "description": "The convergence trajectory: one entry per completed full-review round, oldest first, this round's entry last. Each score is a historical snapshot carried verbatim.",
216
+ "items": {
217
+ "type": "object",
218
+ "additionalProperties": false,
219
+ "required": [
220
+ "round"
221
+ ],
222
+ "properties": {
223
+ "round": {
224
+ "type": "integer",
225
+ "minimum": 1,
226
+ "description": "The 1-indexed completed-round number this entry represents."
227
+ },
228
+ "score": {
229
+ "type": "number",
230
+ "description": "That round's convergence score, carried verbatim from when the round completed. Absent only for a round migrated from a pre-#174 sticky, which stored no score."
231
+ },
232
+ "codes": {
233
+ "type": "object",
234
+ "additionalProperties": {
235
+ "type": "integer",
236
+ "minimum": 0
237
+ },
238
+ "description": "That round's mechanism-frequency map: finding code -> number of findings carrying it. Feeds the same-mechanism and scope-metastasis recurrence signals."
239
+ },
240
+ "sha": {
241
+ "type": "string",
242
+ "description": "The reviewed head SHA (short form) for that round, so a same-head CI retry is recognised as the same iteration rather than new recurrence evidence."
243
+ },
244
+ "ids": {
245
+ "type": "object",
246
+ "additionalProperties": {
247
+ "type": "integer",
248
+ "minimum": 0
249
+ },
250
+ "description": "The tolerant-in spelling of `codes` (0.10 renamed the round's mechanism map to `ids`)."
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+ },
257
+ "change_size": {
258
+ "type": "object",
259
+ "additionalProperties": false,
260
+ "description": "Best-effort (issue #182): the change's added/removed line counts bucketed by ROLE. Fill this with a LOW-effort path heuristic, no file introspection: a file under a tests directory or matching *.test.* / *_test.* is `tests`; a *.md file, anything under docs/, or a license/config doc is `docs`; everything else is `code`. Sum the diff's added and removed lines per bucket. Omit a role that did not change, or omit the whole field if you cannot estimate it. Chrome only \u2014 never affects the verdict or the convergence score. (The per-language cloc table shown beside it is supplied deterministically by the pipeline, not by you.)",
261
+ "properties": {
262
+ "code": {
263
+ "type": "object",
264
+ "additionalProperties": false,
265
+ "required": [
266
+ "added",
267
+ "removed"
268
+ ],
269
+ "properties": {
270
+ "added": {
271
+ "type": "integer",
272
+ "minimum": 0
273
+ },
274
+ "removed": {
275
+ "type": "integer",
276
+ "minimum": 0
277
+ }
278
+ }
279
+ },
280
+ "tests": {
281
+ "type": "object",
282
+ "additionalProperties": false,
283
+ "required": [
284
+ "added",
285
+ "removed"
286
+ ],
287
+ "properties": {
288
+ "added": {
289
+ "type": "integer",
290
+ "minimum": 0
291
+ },
292
+ "removed": {
293
+ "type": "integer",
294
+ "minimum": 0
295
+ }
296
+ }
297
+ },
298
+ "docs": {
299
+ "type": "object",
300
+ "additionalProperties": false,
301
+ "required": [
302
+ "added",
303
+ "removed"
304
+ ],
305
+ "properties": {
306
+ "added": {
307
+ "type": "integer",
308
+ "minimum": 0
309
+ },
310
+ "removed": {
311
+ "type": "integer",
312
+ "minimum": 0
313
+ }
314
+ }
315
+ }
316
+ }
317
+ },
318
+ "findings": {
319
+ "type": "array",
320
+ "description": "Zero or more specific findings.",
321
+ "items": {
322
+ "type": "object",
323
+ "additionalProperties": false,
324
+ "required": [
325
+ "path",
326
+ "start_line",
327
+ "end_line",
328
+ "severity",
329
+ "title",
330
+ "description",
331
+ "reasoning",
332
+ "confidence",
333
+ "likelihood"
334
+ ],
335
+ "properties": {
336
+ "path": {
337
+ "type": "string",
338
+ "description": "Repo-relative file path the finding anchors to."
339
+ },
340
+ "start_line": {
341
+ "type": "integer",
342
+ "minimum": 1,
343
+ "description": "1-indexed first line of the anchored range (post-change state when side is RIGHT)."
344
+ },
345
+ "end_line": {
346
+ "type": "integer",
347
+ "minimum": 1,
348
+ "description": "1-indexed last line of the range; MUST be >= start_line. A suggestion replaces exactly start_line..end_line."
349
+ },
350
+ "side": {
351
+ "type": "string",
352
+ "enum": [
353
+ "RIGHT",
354
+ "LEFT"
355
+ ],
356
+ "default": "RIGHT",
357
+ "description": "As in a unified-diff view: RIGHT for added/changed lines, LEFT for removed lines."
358
+ },
359
+ "severity": {
360
+ "type": "string",
361
+ "enum": [
362
+ "critical",
363
+ "major",
364
+ "minor",
365
+ "nit"
366
+ ],
367
+ "description": "Used for grouping and for folding nits into a collapsed section."
368
+ },
369
+ "code": {
370
+ "type": "string",
371
+ "description": "Stable rule identifier (e.g. \"null-check-missing\") for rule-based filtering, suppression, and cross-run dedup."
372
+ },
373
+ "code_url": {
374
+ "type": "string",
375
+ "format": "uri",
376
+ "description": "URL documenting the rule named by code."
377
+ },
378
+ "title": {
379
+ "type": "string",
380
+ "description": "One-line summary of the finding."
381
+ },
382
+ "description": {
383
+ "type": "string",
384
+ "description": "1-3 sentence markdown explanation of what is wrong. Details belong in `reasoning`."
385
+ },
386
+ "recommendation": {
387
+ "type": "string",
388
+ "description": "1-2 sentence markdown prose describing what to do about the finding \u2014 distinct from `patch`, which is a machine-applicable unified diff. Use this when the fix is best explained rather than mechanically applied, or to accompany a `patch` with rationale."
389
+ },
390
+ "confidence": {
391
+ "type": "number",
392
+ "minimum": 0,
393
+ "maximum": 1,
394
+ "description": "0..1 confidence; a commenter MAY suppress findings below a configurable threshold to control noise, but MUST NOT suppress a critical-severity finding on confidence alone."
395
+ },
396
+ "likelihood": {
397
+ "type": "number",
398
+ "minimum": 0,
399
+ "maximum": 1,
400
+ "description": "0..1 probability the triggering input or state actually occurs in practice \u2014 from a normal user in routine use, OR from an adversary who chooses it when the finding is a security issue. Distinct from confidence (which rates whether the defect is real): a footgun that fires only on hostile-to-oneself input is high-confidence yet low-likelihood. Anchors: 0.9 = any normal input, or any input an attacker can choose (e.g. an injectable query parameter); 0.5 = a plausible-but-uncommon configuration; 0.1 = a genuinely obscure state no one seeks; 0.02 = requires deliberately hostile-to-oneself input (e.g. naming a struct field \"__slots__\"). Name the triggering input, then rate how routinely it occurs."
401
+ },
402
+ "reasoning": {
403
+ "type": "string",
404
+ "description": "Rationale/evidence for why the finding holds, so a human or downstream agent can judge its soundness \u2014 distinct from `description`, which explains the finding to the reader."
405
+ },
406
+ "patch": {
407
+ "type": "string",
408
+ "description": "Optional single-hunk unified diff of one mechanical fix to this file, expressed against its current (post-change) content \u2014 e.g. the `git diff` of just that edit after you apply it. It MUST be exactly one hunk and one contiguous change, and its context and removed lines MUST match the file verbatim so it applies cleanly. Omit this field when there is no clean mechanical fix \u2014 never invent one; put prose guidance in `recommendation` instead."
409
+ },
410
+ "id": {
411
+ "type": "string",
412
+ "description": "Stable mechanism identifier \u2014 0.10 renamed `code` to `id` and made it required. The frozen legacy schema accepts the tolerant-in `id` spelling too, so the ajv gate agrees with the legacy codec on the hybrid doc a mid-transition reviewer produces (new field, old version stamp)."
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }