@mrciphersmith/keryx 0.2.75 → 0.2.76
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/dist/cli.js +777 -161
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +100 -10
- package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +175 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +7 -0
- package/src/gdskills/contracts/review-finding.schema.json +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.76",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -52,7 +52,7 @@ unified report sorted by severity. It does not perform any review logic itself.
|
|
|
52
52
|
```
|
|
53
53
|
Review Orchestrator Progress:
|
|
54
54
|
- [ ] Step 0: On a PR target, collect external comments — `keryx review comments collect`
|
|
55
|
-
- [ ] Step 1: Build Review Context Pack
|
|
55
|
+
- [ ] Step 1: Build Review Context Pack — PR metadata AND the PR's own description, scope, rules, context_doc summary, accepted memory, and the cross-repo contracts the diff consumes
|
|
56
56
|
- [ ] Step 2: Detect review mode (diff mode vs. path mode)
|
|
57
57
|
- [ ] Step 3: Build the bounded scope with `keryx review scope` — never by hand
|
|
58
58
|
- [ ] Step 3b: On a deep round, compute scope B with `keryx review blast-radius` — never by browsing — and KEEP the `--json` file; `review ingest --blast-radius <file>` is refused without it
|
|
@@ -462,8 +462,8 @@ Required content:
|
|
|
462
462
|
- Git/PR metadata: repo, branch, base, head, merge-base, PR number/URL when available.
|
|
463
463
|
- Scope summary: changed files grouped by domain, high-risk files, generated/ignored files.
|
|
464
464
|
- Requirements: issue URL, linked task docs, acceptance criteria extracted from `context_doc` when available.
|
|
465
|
-
- **The PR's own description**, fetched not assumed: `gh pr view <n> --json title,body`. See below.
|
|
466
|
-
- **Cross-repo contracts the diff depends on**, each pinned to the
|
|
465
|
+
- **The PR's own description**, fetched not assumed: `gh pr view <n> --json title,body`, into the typed `pr.body`. See below.
|
|
466
|
+
- **Cross-repo contracts the diff depends on**, in the typed `cross_repo`: each pinned to the ref you read it at, and each recording whether the producer has merged. See below.
|
|
467
467
|
- Rules: matched repository rules and convention docs by path.
|
|
468
468
|
- **Memory: accepted project memory intersecting the changed paths.** See below — this step is required, not best-effort.
|
|
469
469
|
- Decisions: why each reviewer was selected or skipped.
|
|
@@ -487,8 +487,17 @@ and not a pleasantry because unfiled housekeeping is raised again every round an
|
|
|
487
487
|
fixed in none — three consecutive rounds of "still worth rewriting" is the
|
|
488
488
|
recorded outcome of leaving it out of the findings array.
|
|
489
489
|
|
|
490
|
+
**The Stage 1 gate files it.** Handing the body to every reviewer is what makes
|
|
491
|
+
the body available; it is not what makes the comparison happen. A rule addressed
|
|
492
|
+
to everyone is owned by no one, and that is the state this finding sat in for
|
|
493
|
+
three rounds. The gate already holds the stated intent and the diff side by side,
|
|
494
|
+
so the comparison belongs there — see `## Stage 1 Gate — Spec Compliance`.
|
|
495
|
+
|
|
490
496
|
Same class, same severity: an approach that depends on another repository's change
|
|
491
|
-
being deployed first, with no deploy note saying so.
|
|
497
|
+
being deployed first, with no deploy note saying so. Note the boundary — that
|
|
498
|
+
`minor` is about the *description*. When the dependency is real and this change
|
|
499
|
+
can merge first, the blocker is filed against the call site instead; see
|
|
500
|
+
`### A producer that has not merged yet`.
|
|
492
501
|
|
|
493
502
|
### Cross-repo contracts are read, not assumed
|
|
494
503
|
|
|
@@ -499,15 +508,21 @@ the SHA:
|
|
|
499
508
|
```yaml
|
|
500
509
|
cross_repo:
|
|
501
510
|
- repo: vantage-backend
|
|
511
|
+
state: merged
|
|
502
512
|
sha: f5219d5d4
|
|
513
|
+
merge_order: independent
|
|
503
514
|
reason: "DQ report payload: which halves are null vs 0"
|
|
515
|
+
facts_pinned_round: 1
|
|
504
516
|
facts:
|
|
505
517
|
- "sqlScore/schemaDriftScore stay null when the half is absent"
|
|
506
518
|
- "sqlWeightPercentage/schemaDriftWeightPercentage init to 0.0 and are set unconditionally"
|
|
507
519
|
```
|
|
508
520
|
|
|
509
|
-
Put the facts in `review_context` so every reviewer shares one reading,
|
|
510
|
-
later round can tell a contract that moved from a reviewer that misread
|
|
521
|
+
Put the facts in `review_context.cross_repo` so every reviewer shares one reading,
|
|
522
|
+
and so a later round can tell a contract that moved from a reviewer that misread
|
|
523
|
+
it. The shape is typed in `review-context.schema.json`: `repo`, `reason`, `facts`
|
|
524
|
+
and `state` are required, `state: merged` requires the `sha`, and `state: open`
|
|
525
|
+
requires the `pr`.
|
|
511
526
|
|
|
512
527
|
The rule that follows for reviewers, and that belongs in the dispatch prompt:
|
|
513
528
|
|
|
@@ -517,9 +532,49 @@ The rule that follows for reviewers, and that belongs in the dispatch prompt:
|
|
|
517
532
|
> producer disproves, and a defect missed because the producer's actual default was
|
|
518
533
|
> assumed rather than read.
|
|
519
534
|
|
|
520
|
-
If the other repository is not available to you,
|
|
521
|
-
the dependent findings at `info`.
|
|
522
|
-
one is not.
|
|
535
|
+
If the other repository is not available to you, record it as
|
|
536
|
+
`state: unavailable` in `cross_repo` and leave the dependent findings at `info`.
|
|
537
|
+
An unavailable producer is a result. Assuming one is not.
|
|
538
|
+
|
|
539
|
+
A finding whose evidence lives in the producer sets `repo` on the finding itself.
|
|
540
|
+
`file` and `line` alone name a path in the repository under review, so without
|
|
541
|
+
`repo` a cross-repo claim cannot say where its evidence is — and the `info` rule
|
|
542
|
+
above then rests on nothing a later round can check.
|
|
543
|
+
|
|
544
|
+
### A producer that has not merged yet
|
|
545
|
+
|
|
546
|
+
`state: merged` is the easy case: the contract is on the producer's base branch,
|
|
547
|
+
you pin the SHA, and it stays where you pinned it. A **parallel pull request** —
|
|
548
|
+
the backend change your change was written against, still open — is a different
|
|
549
|
+
object, and pinning it the same way is wrong in three separate ways.
|
|
550
|
+
|
|
551
|
+
**Pin the pull request, not just the commit.** An open branch gets rebased and
|
|
552
|
+
squashed. A SHA read from it today names a commit that will not exist after the
|
|
553
|
+
merge, so `state: open` requires `pr` and the SHA becomes a note about when you
|
|
554
|
+
read it rather than an address a later reader can follow.
|
|
555
|
+
|
|
556
|
+
**Merge order is a `blocker`, not a note.** A missing deploy note in the PR body
|
|
557
|
+
is `minor` — that is a documentation finding and it stays where it is. The
|
|
558
|
+
operational case is not that one: when an entry is `state: open` with
|
|
559
|
+
`merge_order: producer_first`, and this change can merge without the producer,
|
|
560
|
+
**production breaks the moment it does**. That is a `blocker` under the existing
|
|
561
|
+
enumeration, not a new shape — the consumer calls what is not there, or reads a
|
|
562
|
+
default the producer has not changed yet, which is shape 1 or shape 2 in
|
|
563
|
+
`### \`blocker\` — merge-blocking, and nothing else`. File it against the consuming
|
|
564
|
+
call site rather than the description, and write the fix as what it is: a merge
|
|
565
|
+
gate, a flag, or a fallback, never an edit to a diff that may be perfectly
|
|
566
|
+
correct. Scope it exactly — both facts have to be recorded, an open producer AND
|
|
567
|
+
a producer-first order, and no gate already holding the merge. A
|
|
568
|
+
`merge_order: unknown` is a question to resolve, not a blocker to file.
|
|
569
|
+
|
|
570
|
+
**Re-read an open producer every round.** `facts` pinned in round 1 are a
|
|
571
|
+
statement about a branch that is still being written; by round 3 the contract may
|
|
572
|
+
have changed under a finding that still cites it. On each round, re-read every
|
|
573
|
+
entry with `state: open` and set `revalidated_round`. An entry whose
|
|
574
|
+
`revalidated_round` is behind the current round has not been checked this round,
|
|
575
|
+
and findings resting on it drop to `info` until it is. This is the same discipline
|
|
576
|
+
`prior_findings` already imposes on the local diff, applied to the one input that
|
|
577
|
+
is most likely to have moved.
|
|
523
578
|
|
|
524
579
|
### Memory (required)
|
|
525
580
|
|
|
@@ -1117,7 +1172,8 @@ local frontend conventions reviewer.
|
|
|
1117
1172
|
|
|
1118
1173
|
## Stage 1 Gate — Spec Compliance
|
|
1119
1174
|
|
|
1120
|
-
**Run this FIRST, before dispatching quality reviewers,
|
|
1175
|
+
**Run this FIRST, before dispatching quality reviewers, whenever the change has a
|
|
1176
|
+
stated intent — an `issue_url`, a task doc, or a PR body.**
|
|
1121
1177
|
|
|
1122
1178
|
1. Fetch issue or task requirements.
|
|
1123
1179
|
2. Map changed files and functions to acceptance criteria.
|
|
@@ -1125,6 +1181,27 @@ local frontend conventions reviewer.
|
|
|
1125
1181
|
4. If there are unimplemented criteria: emit them as `blocker` findings in the final report and note them in `## Blockers`.
|
|
1126
1182
|
5. Continue dispatching the remaining reviewers regardless (spec gaps + quality issues both belong in the report).
|
|
1127
1183
|
|
|
1184
|
+
### With no issue and no task doc, the PR body is the spec
|
|
1185
|
+
|
|
1186
|
+
A pull request with nothing linked is the common case, not the exception, and the
|
|
1187
|
+
gate used to skip entirely on it — which meant the only written statement of what
|
|
1188
|
+
the change was for went unchecked precisely when it was the only one there. When
|
|
1189
|
+
`issue_url` and `context_doc` are both absent, read `pr.body` as the spec and run
|
|
1190
|
+
steps 2-4 against it. Nothing at all to read — no issue, no task doc, an empty
|
|
1191
|
+
body — is itself the finding: `minor`, that the change states no intent.
|
|
1192
|
+
|
|
1193
|
+
### This gate owns the description-vs-diff comparison
|
|
1194
|
+
|
|
1195
|
+
The `minor` for a description that promises an approach the diff does not take
|
|
1196
|
+
(see the Context Pack) is **filed here**. It was previously stated as a rule for
|
|
1197
|
+
"every reviewer" and owned by none, which is how it stayed a remark instead of a
|
|
1198
|
+
finding across three rounds. A domain reviewer reviews its domain; the one step
|
|
1199
|
+
that already holds intent and diff side by side is this one.
|
|
1200
|
+
|
|
1201
|
+
Run it on every round, not only the first. The drift the finding catches is
|
|
1202
|
+
created BY the rounds: the code moves to answer findings, the body does not, and
|
|
1203
|
+
whoever reads the merge commit a year later reads the body.
|
|
1204
|
+
|
|
1128
1205
|
---
|
|
1129
1206
|
|
|
1130
1207
|
## Dispatching Reviewers
|
|
@@ -1328,6 +1405,18 @@ Everything else is at most `major`. "This will definitely cause problems later"
|
|
|
1328
1405
|
is not one of the four. Neither is "this violates the architecture", "this fails
|
|
1329
1406
|
the linter", or "this is how the last outage started".
|
|
1330
1407
|
|
|
1408
|
+
**An unshipped dependency is shape 1 or shape 2, and the list stays closed.**
|
|
1409
|
+
When a producer is recorded `state: open` with `merge_order: producer_first` and
|
|
1410
|
+
nothing stops this change merging first, ask the enumeration's own question —
|
|
1411
|
+
what happens at runtime. The consumer calls what is not there (shape 1) or reads
|
|
1412
|
+
the default the producer has not changed yet (shape 2). That is why it is a
|
|
1413
|
+
`blocker` without a fifth shape being invented for it. Two things follow from
|
|
1414
|
+
its being an ordering fault rather than a coding one: file it against the
|
|
1415
|
+
consuming call site, and say in the fix that the remedy is a merge gate, a flag,
|
|
1416
|
+
or a fallback — never a change to the diff, which may be entirely correct. A
|
|
1417
|
+
missing deploy note with no recorded producer state behind it is not this; that
|
|
1418
|
+
is the `minor` in the Context Pack. See `### A producer that has not merged yet`.
|
|
1419
|
+
|
|
1331
1420
|
### `major` / `minor` / `info` — the boundary test
|
|
1332
1421
|
|
|
1333
1422
|
Ask one question, and ask it of the **finding**, not of the code:
|
|
@@ -1459,6 +1548,7 @@ All findings from all sub-reviewers must be normalized to this format before con
|
|
|
1459
1548
|
|
|
1460
1549
|
- **Severity**: blocker | major | minor | info
|
|
1461
1550
|
- **File**: path/to/file.ts:line
|
|
1551
|
+
- **Repo**: producer repository — only when the evidence is not in the repository under review
|
|
1462
1552
|
- **Problem**: what is wrong
|
|
1463
1553
|
- **Why it matters**: impact on correctness / safety / maintainability / UX
|
|
1464
1554
|
- **Fix**: concrete suggestion
|
|
@@ -23,6 +23,58 @@
|
|
|
23
23
|
"object",
|
|
24
24
|
"null"
|
|
25
25
|
],
|
|
26
|
+
"description": "The pull request under review, when there is one. `body` is the author's own statement of what the change does, and it is checkable against the diff — it is typed here rather than left to convention because the Context Pack prose told reviewers to read `pr.body` while the schema declared no such field, so nothing carried it and nothing could tell a body that was never fetched from one that was empty.",
|
|
27
|
+
"properties": {
|
|
28
|
+
"number": {
|
|
29
|
+
"type": [
|
|
30
|
+
"integer",
|
|
31
|
+
"null"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"url": {
|
|
35
|
+
"type": [
|
|
36
|
+
"string",
|
|
37
|
+
"null"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"title": {
|
|
41
|
+
"type": [
|
|
42
|
+
"string",
|
|
43
|
+
"null"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"body": {
|
|
47
|
+
"type": [
|
|
48
|
+
"string",
|
|
49
|
+
"null"
|
|
50
|
+
],
|
|
51
|
+
"description": "Fetched, not assumed: `gh pr view <n> --json title,body`. Null means the fetch did not happen or returned nothing; an empty string means the author wrote no description, which is a different fact and a reportable one."
|
|
52
|
+
},
|
|
53
|
+
"state": {
|
|
54
|
+
"type": [
|
|
55
|
+
"string",
|
|
56
|
+
"null"
|
|
57
|
+
],
|
|
58
|
+
"enum": [
|
|
59
|
+
"open",
|
|
60
|
+
"merged",
|
|
61
|
+
"closed",
|
|
62
|
+
null
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"base": {
|
|
66
|
+
"type": [
|
|
67
|
+
"string",
|
|
68
|
+
"null"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"head": {
|
|
72
|
+
"type": [
|
|
73
|
+
"string",
|
|
74
|
+
"null"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
26
78
|
"additionalProperties": true
|
|
27
79
|
},
|
|
28
80
|
"scope": {
|
|
@@ -71,6 +123,129 @@
|
|
|
71
123
|
"type": "string"
|
|
72
124
|
}
|
|
73
125
|
},
|
|
126
|
+
"cross_repo": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"description": "Contracts owned by another service that this diff consumes — a payload shape, a status enum, a timeout, a permission, a default. Each entry is one reading of the producer, shared by every reviewer, so that a later round can tell a contract that moved from a reviewer that misread it. An entry is a RECORD OF READING: an unavailable producer is a result and belongs here as `state: unavailable`, whereas an assumed one belongs nowhere.",
|
|
129
|
+
"items": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": [
|
|
132
|
+
"repo",
|
|
133
|
+
"reason",
|
|
134
|
+
"facts",
|
|
135
|
+
"state"
|
|
136
|
+
],
|
|
137
|
+
"properties": {
|
|
138
|
+
"repo": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1
|
|
141
|
+
},
|
|
142
|
+
"reason": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"minLength": 1,
|
|
145
|
+
"description": "What this diff needs from the producer, in one line."
|
|
146
|
+
},
|
|
147
|
+
"facts": {
|
|
148
|
+
"type": "array",
|
|
149
|
+
"minItems": 1,
|
|
150
|
+
"items": {
|
|
151
|
+
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
"description": "What the producer actually says, read at the pinned ref. A claim about another service's behaviour with no `file:line` behind it at a pinned ref is `info`, not `major`, however confident — both failure modes are recorded: a finding the producer disproves, and a defect missed because the producer's real default was assumed rather than read."
|
|
154
|
+
},
|
|
155
|
+
"state": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"enum": [
|
|
158
|
+
"merged",
|
|
159
|
+
"open",
|
|
160
|
+
"unavailable"
|
|
161
|
+
],
|
|
162
|
+
"description": "Where the contract lives right now. `merged` is on the producer's own base branch and is pinned by `sha`. `open` is a parallel pull request that has not landed: its branch will be rebased or squashed, so a sha pinned today names a commit that will not exist after the merge, and the contract itself can still change. `unavailable` means the producer could not be read."
|
|
163
|
+
},
|
|
164
|
+
"sha": {
|
|
165
|
+
"type": [
|
|
166
|
+
"string",
|
|
167
|
+
"null"
|
|
168
|
+
],
|
|
169
|
+
"description": "The commit the facts were read at. Required when `state` is `merged`."
|
|
170
|
+
},
|
|
171
|
+
"pr": {
|
|
172
|
+
"type": [
|
|
173
|
+
"string",
|
|
174
|
+
"null"
|
|
175
|
+
],
|
|
176
|
+
"description": "The producer's pull request, when the contract is not merged yet."
|
|
177
|
+
},
|
|
178
|
+
"branch": {
|
|
179
|
+
"type": [
|
|
180
|
+
"string",
|
|
181
|
+
"null"
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"merge_order": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"enum": [
|
|
187
|
+
"producer_first",
|
|
188
|
+
"consumer_first",
|
|
189
|
+
"independent",
|
|
190
|
+
"unknown"
|
|
191
|
+
],
|
|
192
|
+
"default": "unknown",
|
|
193
|
+
"description": "Which side has to ship first for production to stay correct. `producer_first` on a `state: open` entry is the case where merging THIS change first breaks production, and it is a blocker rather than the `minor` filed for a missing deploy note."
|
|
194
|
+
},
|
|
195
|
+
"facts_pinned_round": {
|
|
196
|
+
"type": [
|
|
197
|
+
"integer",
|
|
198
|
+
"null"
|
|
199
|
+
],
|
|
200
|
+
"description": "The review round the facts were read in."
|
|
201
|
+
},
|
|
202
|
+
"revalidated_round": {
|
|
203
|
+
"type": [
|
|
204
|
+
"integer",
|
|
205
|
+
"null"
|
|
206
|
+
],
|
|
207
|
+
"description": "The last round the producer was re-read. An entry with `state: open` that was pinned in round 1 and never revalidated is stale by round 3: a parallel pull request is precisely the thing that moves between rounds."
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"allOf": [
|
|
211
|
+
{
|
|
212
|
+
"if": {
|
|
213
|
+
"properties": {
|
|
214
|
+
"state": {
|
|
215
|
+
"const": "merged"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"required": [
|
|
219
|
+
"state"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
"then": {
|
|
223
|
+
"required": [
|
|
224
|
+
"sha"
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"if": {
|
|
230
|
+
"properties": {
|
|
231
|
+
"state": {
|
|
232
|
+
"const": "open"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"required": [
|
|
236
|
+
"state"
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
"then": {
|
|
240
|
+
"required": [
|
|
241
|
+
"pr"
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"additionalProperties": true
|
|
247
|
+
}
|
|
248
|
+
},
|
|
74
249
|
"memory": {
|
|
75
250
|
"type": "object",
|
|
76
251
|
"required": [
|
|
@@ -60,6 +60,13 @@
|
|
|
60
60
|
"info"
|
|
61
61
|
]
|
|
62
62
|
},
|
|
63
|
+
"repo": {
|
|
64
|
+
"type": [
|
|
65
|
+
"string",
|
|
66
|
+
"null"
|
|
67
|
+
],
|
|
68
|
+
"description": "Which repository `file` and `line` are in. Null, the default, means the repository under review. A cross-repo claim names the producer here and pins the ref it was read at in `evidence`; without this field a finding about another service's behaviour could not say where its evidence lived, so the rule that such a claim is `info` unless it has a `file:line` at a pinned ref could not be checked by anything but the reviewer's word. Kept identical to src/gdskills/contracts/review-finding.schema.json, which is stricter (additionalProperties: false) and would reject a finding carrying `repo` if the property were declared only here."
|
|
69
|
+
},
|
|
63
70
|
"file": {
|
|
64
71
|
"type": [
|
|
65
72
|
"string",
|
|
@@ -95,6 +95,10 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"severity": { "type": "string", "enum": ["blocker", "major", "minor", "info"] },
|
|
98
|
+
"repo": {
|
|
99
|
+
"type": ["string", "null"],
|
|
100
|
+
"description": "Which repository `file` and `line` are in. Null, the default, means the repository under review. A cross-repo claim names the producer here and pins the ref it was read at in `evidence`; without this field a finding about another service's behaviour could not say where its evidence lived, so the rule that such a claim is `info` unless it has a `file:line` at a pinned ref could not be checked by anything but the reviewer's word. Declared in BOTH this schema and skills/review/review-orchestrator/reviewer-finding.schema.json: this one is additionalProperties:false, so a finding carrying `repo` is rejected here if only the reviewer-side schema knows about it."
|
|
101
|
+
},
|
|
98
102
|
"file": { "type": ["string", "null"] },
|
|
99
103
|
"line": { "type": ["integer", "null"], "minimum": 1 },
|
|
100
104
|
"symbol": { "type": ["string", "null"] },
|