ag-ui-validate 0.1.0 → 0.3.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 +77 -10
- package/dist/{catalog-Ci9dqc1a.cjs → catalog-AB5vuZ8j.cjs} +3 -3
- package/dist/catalog-AB5vuZ8j.cjs.map +1 -0
- package/dist/{catalog-BglXBNbL.js → catalog-DqTI4VLt.js} +3 -3
- package/dist/catalog-DqTI4VLt.js.map +1 -0
- package/dist/cli.js +107 -55
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/report.cjs +43 -1
- package/dist/report.cjs.map +1 -1
- package/dist/report.d.cts +7 -1
- package/dist/report.d.ts +7 -1
- package/dist/report.js +43 -2
- package/dist/report.js.map +1 -1
- package/dist/{src-HmI-kxef.cjs → src-D04i3Vso.cjs} +2 -2
- package/dist/src-D04i3Vso.cjs.map +1 -0
- package/dist/{src-rGZ2G4qA.js → src-Dcuqz2QC.js} +2 -2
- package/dist/src-Dcuqz2QC.js.map +1 -0
- package/dist/transport.cjs +1 -1
- package/dist/transport.cjs.map +1 -1
- package/dist/transport.js +1 -1
- package/dist/transport.js.map +1 -1
- package/dist/vitest.js +2 -2
- package/dist/vitest.js.map +1 -1
- package/{src → js/src}/cli-args.ts +11 -0
- package/{src → js/src}/cli.ts +8 -3
- package/{src → js/src}/report/index.ts +1 -1
- package/{src → js/src}/report/pretty.ts +43 -0
- package/{src → js/src}/rules/catalog.ts +1 -1
- package/{src → js/src}/transport/index.ts +1 -1
- package/package.json +14 -13
- package/{src/rules → spec}/catalog.json +1 -1
- package/spec/event-categories.json +56 -0
- package/spec/fixtures/README.md +91 -0
- package/spec/fixtures/invalid/AGUI001-first-event-not-run-started/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI001-first-event-not-run-started/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI002-duplicate-run-started/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI002-duplicate-run-started/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI003-run-never-terminated/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI003-run-never-terminated/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI004-event-after-terminal/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI004-event-after-terminal/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI005-finished-and-error/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI005-finished-and-error/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI006-step-finished-unmatched/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI006-step-finished-unmatched/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI007-step-unterminated/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI007-step-unterminated/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI008-unstable-run-ids/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI008-unstable-run-ids/stream.jsonl +2 -0
- package/spec/fixtures/invalid/AGUI101-content-without-start/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI101-content-without-start/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI102-end-without-start/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI102-end-without-start/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI103-message-unterminated/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI103-message-unterminated/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI104-duplicate-message-id/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI104-duplicate-message-id/stream.jsonl +6 -0
- package/spec/fixtures/invalid/AGUI105-empty-content-delta/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI105-empty-content-delta/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI106-interleaved-same-message-id/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI106-interleaved-same-message-id/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI201-args-without-start/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI201-args-without-start/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI202-end-without-start/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI202-end-without-start/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI203-unterminated-tool-call/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI203-unterminated-tool-call/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI204-args-not-json/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI204-args-not-json/stream.jsonl +6 -0
- package/spec/fixtures/invalid/AGUI205-duplicate-tool-call-id/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI205-duplicate-tool-call-id/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI206-result-before-end/expected.json +12 -0
- package/spec/fixtures/invalid/AGUI206-result-before-end/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI207-result-unknown-id/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI207-result-unknown-id/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI208-unknown-parent-message-id/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI208-unknown-parent-message-id/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI301-delta-before-snapshot/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI301-delta-before-snapshot/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI302-delta-failed-to-apply/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI302-delta-failed-to-apply/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI303-invalid-patch-document/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI303-invalid-patch-document/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI304-midrun-snapshot-discards-deltas/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI304-midrun-snapshot-discards-deltas/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI305-shared-state-never-established/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI305-shared-state-never-established/options.json +5 -0
- package/spec/fixtures/invalid/AGUI305-shared-state-never-established/stream.jsonl +5 -0
- package/spec/fixtures/invalid/AGUI401-reasoning-content-without-start/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI401-reasoning-content-without-start/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI402-reasoning-unterminated/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI402-reasoning-unterminated/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI501-missing-data-prefix/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI501-missing-data-prefix/scenario.json +17 -0
- package/spec/fixtures/invalid/AGUI502-payload-not-json/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI502-payload-not-json/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI503-unknown-event-type/expected.json +10 -0
- package/spec/fixtures/invalid/AGUI503-unknown-event-type/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI504-schema-violation/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI504-schema-violation/stream.jsonl +4 -0
- package/spec/fixtures/invalid/AGUI505-unexpected-content-type/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI505-unexpected-content-type/scenario.json +25 -0
- package/spec/fixtures/invalid/AGUI506-keepalive-gap/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI506-keepalive-gap/scenario.json +25 -0
- package/spec/fixtures/invalid/AGUI507-buffered-response/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI507-buffered-response/scenario.json +9 -0
- package/spec/fixtures/invalid/AGUI508-connection-dropped/expected.json +17 -0
- package/spec/fixtures/invalid/AGUI508-connection-dropped/scenario.json +10 -0
- package/spec/fixtures/invalid/AGUI901-raw-wraps-typed-event/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI901-raw-wraps-typed-event/stream.jsonl +3 -0
- package/spec/fixtures/invalid/AGUI902-no-timestamps/expected.json +9 -0
- package/spec/fixtures/invalid/AGUI902-no-timestamps/stream.jsonl +2 -0
- package/spec/fixtures/invalid/AGUI903-custom-name-not-namespaced/expected.json +11 -0
- package/spec/fixtures/invalid/AGUI903-custom-name-not-namespaced/stream.jsonl +3 -0
- package/spec/fixtures/valid/agentic-chat.jsonl +14 -0
- package/spec/fixtures/valid/agentic-generative-ui.jsonl +13 -0
- package/spec/fixtures/valid/backend-tool-rendering.jsonl +14 -0
- package/spec/fixtures/valid/human-in-the-loop.jsonl +13 -0
- package/spec/fixtures/valid/predictive-state-updates.expected.json +11 -0
- package/spec/fixtures/valid/predictive-state-updates.jsonl +11 -0
- package/spec/fixtures/valid/shared-state.jsonl +8 -0
- package/spec/fixtures/valid/tool-based-generative-ui.jsonl +5 -0
- package/dist/catalog-BglXBNbL.js.map +0 -1
- package/dist/catalog-Ci9dqc1a.cjs.map +0 -1
- package/dist/src-HmI-kxef.cjs.map +0 -1
- package/dist/src-rGZ2G4qA.js.map +0 -1
- /package/{src → js/src}/index.ts +0 -0
- /package/{src → js/src}/protocol/event-table.ts +0 -0
- /package/{src → js/src}/protocol/jsonpatch.ts +0 -0
- /package/{src → js/src}/report/json.ts +0 -0
- /package/{src → js/src}/report/junit.ts +0 -0
- /package/{src → js/src}/report/sarif.ts +0 -0
- /package/{src → js/src}/rules/checks/context.ts +0 -0
- /package/{src → js/src}/rules/checks/lifecycle.ts +0 -0
- /package/{src → js/src}/rules/checks/reasoning.ts +0 -0
- /package/{src → js/src}/rules/checks/state.ts +0 -0
- /package/{src → js/src}/rules/checks/text.ts +0 -0
- /package/{src → js/src}/rules/checks/toolcalls.ts +0 -0
- /package/{src → js/src}/rules/checks/transport.ts +0 -0
- /package/{src → js/src}/transport/ndjson.ts +0 -0
- /package/{src → js/src}/transport/sse.ts +0 -0
- /package/{src → js/src}/types.ts +0 -0
- /package/{src → js/src}/vitest/index.ts +0 -0
- /package/{src → js/src}/vitest/matcher.ts +0 -0
package/README.md
CHANGED
|
@@ -15,10 +15,11 @@ spec section.
|
|
|
15
15
|
2 errors, 0 warnings, 1 info — 3 of 7 AG-UI features exercised
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
> **Status
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
>
|
|
18
|
+
> **Status:** the TypeScript implementation is released to npm
|
|
19
|
+
> (`npm install ag-ui-validate`). A native Python port — same rule catalog,
|
|
20
|
+
> same fixture corpus, byte-identical CLI — is fully implemented and tested
|
|
21
|
+
> but not yet published to PyPI as a functional release; see
|
|
22
|
+
> [Python](#python) below.
|
|
22
23
|
|
|
23
24
|
## Why
|
|
24
25
|
|
|
@@ -66,6 +67,7 @@ Useful flags (see `--help` for all):
|
|
|
66
67
|
| Flag | Effect |
|
|
67
68
|
| --- | --- |
|
|
68
69
|
| `--json` / `--sarif` / `--junit` | machine-readable report on stdout (SARIF 2.1.0 for code scanning, JUnit XML for CI) |
|
|
70
|
+
| `--group` | one line per rule with a count — for large streams with repeated findings (totals stay exact) |
|
|
69
71
|
| `--rule AGUI105=error`, `--off AGUI902` | per-rule severity overrides |
|
|
70
72
|
| `--features shared-state,...` | declare exercised features (enables e.g. AGUI305) |
|
|
71
73
|
| `--max-warnings 0` | fail CI on any warning |
|
|
@@ -123,7 +125,7 @@ const { diagnostics, summary, features, skipped } = v.report()
|
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
The core is a pure function over an event sequence: zero I/O, zero runtime
|
|
126
|
-
dependencies, isomorphic across Node
|
|
128
|
+
dependencies, isomorphic across Node 22+, browsers, Deno, and Workers.
|
|
127
129
|
|
|
128
130
|
### Validate a live endpoint
|
|
129
131
|
|
|
@@ -169,12 +171,71 @@ import { formatReportSummary, toSarif, toJUnit } from "ag-ui-validate/report"
|
|
|
169
171
|
}
|
|
170
172
|
```
|
|
171
173
|
|
|
174
|
+
## Python
|
|
175
|
+
|
|
176
|
+
A native Python port ships in [py/](py/) — the same rule catalog, the same
|
|
177
|
+
fixture corpus, and a CLI with byte-identical flags, error messages, and
|
|
178
|
+
JSON/SARIF/JUnit output to the TypeScript one above. It's fully implemented
|
|
179
|
+
and tested, and checked against the TypeScript implementation on every PR
|
|
180
|
+
by Parity CI (see [docs/TESTING.md](docs/TESTING.md) for how, and
|
|
181
|
+
[docs/PYTHON-PORT-PLAN.md](docs/PYTHON-PORT-PLAN.md) for the port's full
|
|
182
|
+
milestone history). **Not yet published to PyPI as a functional release**
|
|
183
|
+
— only a name-reservation placeholder (`0.0.1`) is live today.
|
|
184
|
+
Install from source until a real release ships:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/langport-dev/ag-ui-validate
|
|
188
|
+
cd ag-ui-validate/py
|
|
189
|
+
pip install -e ".[dev]" # or ".[transport]" for just the endpoint-validating extras
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### CLI
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
ag-ui-validate http://localhost:8000/agui # live endpoint (POSTs a RunAgentInput)
|
|
196
|
+
ag-ui-validate run.jsonl # recorded stream (NDJSON/JSONL or SSE capture)
|
|
197
|
+
cat run.jsonl | ag-ui-validate - # stdin
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Same exit codes and the same flags as the [CLI](#cli) above (see
|
|
201
|
+
`ag-ui-validate --help`) — the Python argument parser is a direct,
|
|
202
|
+
hand-rolled port of the TypeScript one rather than `argparse`/`click`,
|
|
203
|
+
specifically to keep both CLIs' invocation and error messages
|
|
204
|
+
byte-for-byte identical.
|
|
205
|
+
|
|
206
|
+
### Test your agent in pytest
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from ag_ui_validate.pytest_plugin import assert_valid_agui
|
|
210
|
+
|
|
211
|
+
def test_my_agent_stream(captured_events):
|
|
212
|
+
assert_valid_agui(captured_events, features=["shared-state"], max_warnings=0)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`assert_valid_agui` accepts a list of events (dicts or JSON strings) or a
|
|
216
|
+
whole JSONL capture as one string, and raises `AssertionError` with every
|
|
217
|
+
finding's rule ID and spec link on failure. It registers automatically as a
|
|
218
|
+
pytest plugin on install — no `conftest.py` setup needed. An async
|
|
219
|
+
counterpart validates a live endpoint directly from a test:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from ag_ui_validate.pytest_plugin import assert_valid_agui_endpoint
|
|
223
|
+
|
|
224
|
+
async def test_my_live_agent():
|
|
225
|
+
await assert_valid_agui_endpoint("http://localhost:8000/agui", max_warnings=0)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`assert_valid_agui` has zero runtime dependencies; only the
|
|
229
|
+
endpoint-validating helpers (`assert_valid_agui_endpoint`,
|
|
230
|
+
`validate_agui_endpoint`) pull in `httpx`
|
|
231
|
+
(`pip install ag-ui-validate[transport]`).
|
|
232
|
+
|
|
172
233
|
## The rule catalog
|
|
173
234
|
|
|
174
235
|
40 rules, maintained as **data** in
|
|
175
|
-
[
|
|
176
|
-
(
|
|
177
|
-
|
|
236
|
+
[spec/catalog.json](spec/catalog.json), shared by both implementations in
|
|
237
|
+
this repo ([TypeScript](.) and [Python](py/)) rather than duplicated.
|
|
238
|
+
Every rule has its own page — spec grounding,
|
|
178
239
|
**[rule index](docs/rules/README.md)** (generated from the catalog,
|
|
179
240
|
drift-checked in CI):
|
|
180
241
|
|
|
@@ -194,7 +255,7 @@ drift-tested against the installed SDK on every run.
|
|
|
194
255
|
|
|
195
256
|
## The fixture corpus
|
|
196
257
|
|
|
197
|
-
[fixtures/](fixtures/README.md) is a language-neutral conformance corpus:
|
|
258
|
+
[spec/fixtures/](spec/fixtures/README.md) is a language-neutral conformance corpus:
|
|
198
259
|
7 valid streams (one per canonical AG-UI feature — the false-positive guards)
|
|
199
260
|
and 40 invalid fixtures (one per rule) with exact expected diagnostics. Any
|
|
200
261
|
validator implementation that consumes the shared catalog can be tested
|
|
@@ -218,12 +279,18 @@ Component-by-component instructions live in
|
|
|
218
279
|
rules are tracked in [docs/spec-questions.md](docs/spec-questions.md).
|
|
219
280
|
|
|
220
281
|
Adding a rule: add the catalog entry (with its `specUrl`), add the fixture
|
|
221
|
-
stream + intended findings to `scripts/build-fixtures.mjs`, and run
|
|
282
|
+
stream + intended findings to `js/scripts/build-fixtures.mjs`, and run
|
|
222
283
|
`npm run fixtures:build` — the meta-tests fail until both exist. Rule
|
|
223
284
|
*proposals* belong upstream as issues on
|
|
224
285
|
[`ag-ui-protocol/ag-ui`](https://github.com/ag-ui-protocol/ag-ui) first; this
|
|
225
286
|
project does not invent rules the spec doesn't support.
|
|
226
287
|
|
|
288
|
+
Releasing: merge the pending changesets (`npx changeset version`) via a PR,
|
|
289
|
+
then publish a GitHub release tagged `vX.Y.Z` (matching `package.json`) — the
|
|
290
|
+
[Publish workflow](.github/workflows/publish.yml) typechecks, builds, tests,
|
|
291
|
+
and publishes to npm with provenance via trusted publishing. The workflow
|
|
292
|
+
fails fast if the tag and `package.json` disagree.
|
|
293
|
+
|
|
227
294
|
## License
|
|
228
295
|
|
|
229
296
|
MIT — maintained by [Faraz](https://langport.dev).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../spec/catalog.json
|
|
2
2
|
var catalog_default = {
|
|
3
3
|
$comment: "AG-UI conformance rule catalog. Data, not code: a Python/Go implementation shares these rules. Severities follow the working agreement that behaviour the spec does not clearly govern is at most 'info' — entries with 'specQuestion' were downgraded accordingly; see docs/spec-questions.md.",
|
|
4
4
|
catalogVersion: "0.1.0",
|
|
@@ -342,7 +342,7 @@ var catalog_default = {
|
|
|
342
342
|
"id": "AGUI503",
|
|
343
343
|
"severity": "error",
|
|
344
344
|
"title": "Unknown event type",
|
|
345
|
-
"messageTemplate": "Unknown event type '{type}' (not in
|
|
345
|
+
"messageTemplate": "Unknown event type '{type}' (not in the installed AG-UI SDK v{sdkVersion}, and not RAW or CUSTOM)",
|
|
346
346
|
"specUrl": "https://docs.ag-ui.com/concepts/events#event-types-overview",
|
|
347
347
|
"specQuote": "Events in the protocol are categorized by their purpose.",
|
|
348
348
|
"since": "0.x",
|
|
@@ -492,4 +492,4 @@ Object.defineProperty(exports, "validateCatalog", {
|
|
|
492
492
|
}
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
-
//# sourceMappingURL=catalog-
|
|
495
|
+
//# sourceMappingURL=catalog-AB5vuZ8j.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-AB5vuZ8j.cjs","names":["catalogJson"],"sources":["../spec/catalog.json","../js/src/rules/catalog.ts"],"sourcesContent":["","// Typed loader for the rule catalog. The catalog itself is data\n// (catalog.json) so other implementations can share it; this module gives it\n// types and validates its invariants once at load time.\n//\n// Note the asymmetry: the validator never throws on stream input, but a\n// malformed catalog is a programming error in this package, so the loader\n// throws loudly at import time.\n\nimport catalogJson from \"../../../spec/catalog.json\"\n\nexport type Severity = \"error\" | \"warning\" | \"info\"\nexport type SeverityOrOff = Severity | \"off\"\n\nconst SEVERITIES: readonly string[] = [\"error\", \"warning\", \"info\"]\nconst LAYERS: readonly string[] = [\"core\", \"transport\"]\n\nexport interface RuleDefinition {\n /** e.g. \"AGUI203\" */\n id: string\n severity: Severity\n title: string\n /** Human template with {placeholder} slots filled per diagnostic. */\n messageTemplate: string\n /** Governing spec section. Mandatory: rules that cannot cite one don't ship. */\n specUrl: string\n /** Exact sentence from the spec section, where one exists. */\n specQuote?: string\n since: string\n /** Canonical AG-UI feature this rule relates to, if any. */\n feature?: string\n /** True when the rule only fires if opts.features declares `feature`. */\n requiresFeature?: boolean\n /** Cross-reference into docs/spec-questions.md for downgraded/ambiguous rules. */\n specQuestion?: string\n /** Where the rule is evaluated. Transport rules are skipped (and the skip\n * reported) when validating recorded input with no transport in play. */\n checkedIn: \"core\" | \"transport\"\n}\n\nexport interface Catalog {\n catalogVersion: string\n spec: string\n rules: readonly RuleDefinition[]\n}\n\n/** Validates catalog data and returns it typed. Throws on structural problems. */\nexport function validateCatalog(data: unknown): Catalog {\n const problems: string[] = []\n const cat = data as Catalog\n if (typeof cat !== \"object\" || cat === null || !Array.isArray(cat.rules)) {\n throw new Error(\"rule catalog: expected an object with a rules array\")\n }\n const seen = new Set<string>()\n for (const rule of cat.rules) {\n const where = rule?.id ?? \"<missing id>\"\n if (!/^AGUI\\d{3}$/.test(rule.id ?? \"\")) problems.push(`${where}: id must match AGUI###`)\n if (seen.has(rule.id)) problems.push(`${where}: duplicate id`)\n seen.add(rule.id)\n if (!SEVERITIES.includes(rule.severity)) problems.push(`${where}: bad severity '${rule.severity}'`)\n if (!rule.title) problems.push(`${where}: missing title`)\n if (!rule.messageTemplate) problems.push(`${where}: missing messageTemplate`)\n if (!rule.specUrl?.startsWith(\"https://\")) problems.push(`${where}: specUrl must be an https URL`)\n if (!rule.since) problems.push(`${where}: missing since`)\n if (!LAYERS.includes(rule.checkedIn)) problems.push(`${where}: bad checkedIn '${rule.checkedIn}'`)\n if (rule.requiresFeature && !rule.feature) problems.push(`${where}: requiresFeature without feature`)\n }\n if (problems.length > 0) {\n throw new Error(`rule catalog is invalid:\\n ${problems.join(\"\\n \")}`)\n }\n return cat\n}\n\nexport const CATALOG: Catalog = validateCatalog(catalogJson)\n\nexport const RULES: ReadonlyMap<string, RuleDefinition> = new Map(\n CATALOG.rules.map((r) => [r.id, r]),\n)\n\n/** Fills a rule's messageTemplate. Unknown placeholders are left intact. */\nexport function formatMessage(rule: RuleDefinition, params: Record<string, unknown>): string {\n return rule.messageTemplate.replace(/\\{(\\w+)\\}/g, (whole, key: string) =>\n key in params ? String(params[key]) : whole,\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACaA,MAAM,aAAgC;CAAC;CAAS;CAAW;AAAM;AACjE,MAAM,SAA4B,CAAC,QAAQ,WAAW;;AAgCtD,SAAgB,gBAAgB,MAAwB;CACtD,MAAM,WAAqB,CAAC;CAC5B,MAAM,MAAM;CACZ,IAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,IAAI,KAAK,GACrE,MAAM,IAAI,MAAM,qDAAqD;CAEvE,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,QAAQ,IAAI,OAAO;EAC5B,MAAM,QAAQ,MAAM,MAAM;EAC1B,IAAI,CAAC,cAAc,KAAK,KAAK,MAAM,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,wBAAwB;EACvF,IAAI,KAAK,IAAI,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,eAAe;EAC7D,KAAK,IAAI,KAAK,EAAE;EAChB,IAAI,CAAC,WAAW,SAAS,KAAK,QAAQ,GAAG,SAAS,KAAK,GAAG,MAAM,kBAAkB,KAAK,SAAS,EAAE;EAClG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,KAAK,iBAAiB,SAAS,KAAK,GAAG,MAAM,0BAA0B;EAC5E,IAAI,CAAC,KAAK,SAAS,WAAW,UAAU,GAAG,SAAS,KAAK,GAAG,MAAM,+BAA+B;EACjG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,OAAO,SAAS,KAAK,SAAS,GAAG,SAAS,KAAK,GAAG,MAAM,mBAAmB,KAAK,UAAU,EAAE;EACjG,IAAI,KAAK,mBAAmB,CAAC,KAAK,SAAS,SAAS,KAAK,GAAG,MAAM,kCAAkC;CACtG;CACA,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,MAAM,+BAA+B,SAAS,KAAK,MAAM,GAAG;CAExE,OAAO;AACT;AAEA,MAAa,UAAmB,gBAAgBA,eAAW;AAE3D,MAAa,QAA6C,IAAI,IAC5D,QAAQ,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CACpC;;AAGA,SAAgB,cAAc,MAAsB,QAAyC;CAC3F,OAAO,KAAK,gBAAgB,QAAQ,eAAe,OAAO,QACxD,OAAO,SAAS,OAAO,OAAO,IAAI,IAAI,KACxC;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../spec/catalog.json
|
|
2
2
|
var catalog_default = {
|
|
3
3
|
$comment: "AG-UI conformance rule catalog. Data, not code: a Python/Go implementation shares these rules. Severities follow the working agreement that behaviour the spec does not clearly govern is at most 'info' — entries with 'specQuestion' were downgraded accordingly; see docs/spec-questions.md.",
|
|
4
4
|
catalogVersion: "0.1.0",
|
|
@@ -342,7 +342,7 @@ var catalog_default = {
|
|
|
342
342
|
"id": "AGUI503",
|
|
343
343
|
"severity": "error",
|
|
344
344
|
"title": "Unknown event type",
|
|
345
|
-
"messageTemplate": "Unknown event type '{type}' (not in
|
|
345
|
+
"messageTemplate": "Unknown event type '{type}' (not in the installed AG-UI SDK v{sdkVersion}, and not RAW or CUSTOM)",
|
|
346
346
|
"specUrl": "https://docs.ag-ui.com/concepts/events#event-types-overview",
|
|
347
347
|
"specQuote": "Events in the protocol are categorized by their purpose.",
|
|
348
348
|
"since": "0.x",
|
|
@@ -469,4 +469,4 @@ function formatMessage(rule, params) {
|
|
|
469
469
|
//#endregion
|
|
470
470
|
export { validateCatalog as i, RULES as n, formatMessage as r, CATALOG as t };
|
|
471
471
|
|
|
472
|
-
//# sourceMappingURL=catalog-
|
|
472
|
+
//# sourceMappingURL=catalog-DqTI4VLt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-DqTI4VLt.js","names":["catalogJson"],"sources":["../spec/catalog.json","../js/src/rules/catalog.ts"],"sourcesContent":["","// Typed loader for the rule catalog. The catalog itself is data\n// (catalog.json) so other implementations can share it; this module gives it\n// types and validates its invariants once at load time.\n//\n// Note the asymmetry: the validator never throws on stream input, but a\n// malformed catalog is a programming error in this package, so the loader\n// throws loudly at import time.\n\nimport catalogJson from \"../../../spec/catalog.json\"\n\nexport type Severity = \"error\" | \"warning\" | \"info\"\nexport type SeverityOrOff = Severity | \"off\"\n\nconst SEVERITIES: readonly string[] = [\"error\", \"warning\", \"info\"]\nconst LAYERS: readonly string[] = [\"core\", \"transport\"]\n\nexport interface RuleDefinition {\n /** e.g. \"AGUI203\" */\n id: string\n severity: Severity\n title: string\n /** Human template with {placeholder} slots filled per diagnostic. */\n messageTemplate: string\n /** Governing spec section. Mandatory: rules that cannot cite one don't ship. */\n specUrl: string\n /** Exact sentence from the spec section, where one exists. */\n specQuote?: string\n since: string\n /** Canonical AG-UI feature this rule relates to, if any. */\n feature?: string\n /** True when the rule only fires if opts.features declares `feature`. */\n requiresFeature?: boolean\n /** Cross-reference into docs/spec-questions.md for downgraded/ambiguous rules. */\n specQuestion?: string\n /** Where the rule is evaluated. Transport rules are skipped (and the skip\n * reported) when validating recorded input with no transport in play. */\n checkedIn: \"core\" | \"transport\"\n}\n\nexport interface Catalog {\n catalogVersion: string\n spec: string\n rules: readonly RuleDefinition[]\n}\n\n/** Validates catalog data and returns it typed. Throws on structural problems. */\nexport function validateCatalog(data: unknown): Catalog {\n const problems: string[] = []\n const cat = data as Catalog\n if (typeof cat !== \"object\" || cat === null || !Array.isArray(cat.rules)) {\n throw new Error(\"rule catalog: expected an object with a rules array\")\n }\n const seen = new Set<string>()\n for (const rule of cat.rules) {\n const where = rule?.id ?? \"<missing id>\"\n if (!/^AGUI\\d{3}$/.test(rule.id ?? \"\")) problems.push(`${where}: id must match AGUI###`)\n if (seen.has(rule.id)) problems.push(`${where}: duplicate id`)\n seen.add(rule.id)\n if (!SEVERITIES.includes(rule.severity)) problems.push(`${where}: bad severity '${rule.severity}'`)\n if (!rule.title) problems.push(`${where}: missing title`)\n if (!rule.messageTemplate) problems.push(`${where}: missing messageTemplate`)\n if (!rule.specUrl?.startsWith(\"https://\")) problems.push(`${where}: specUrl must be an https URL`)\n if (!rule.since) problems.push(`${where}: missing since`)\n if (!LAYERS.includes(rule.checkedIn)) problems.push(`${where}: bad checkedIn '${rule.checkedIn}'`)\n if (rule.requiresFeature && !rule.feature) problems.push(`${where}: requiresFeature without feature`)\n }\n if (problems.length > 0) {\n throw new Error(`rule catalog is invalid:\\n ${problems.join(\"\\n \")}`)\n }\n return cat\n}\n\nexport const CATALOG: Catalog = validateCatalog(catalogJson)\n\nexport const RULES: ReadonlyMap<string, RuleDefinition> = new Map(\n CATALOG.rules.map((r) => [r.id, r]),\n)\n\n/** Fills a rule's messageTemplate. Unknown placeholders are left intact. */\nexport function formatMessage(rule: RuleDefinition, params: Record<string, unknown>): string {\n return rule.messageTemplate.replace(/\\{(\\w+)\\}/g, (whole, key: string) =>\n key in params ? String(params[key]) : whole,\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACaA,MAAM,aAAgC;CAAC;CAAS;CAAW;AAAM;AACjE,MAAM,SAA4B,CAAC,QAAQ,WAAW;;AAgCtD,SAAgB,gBAAgB,MAAwB;CACtD,MAAM,WAAqB,CAAC;CAC5B,MAAM,MAAM;CACZ,IAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,IAAI,KAAK,GACrE,MAAM,IAAI,MAAM,qDAAqD;CAEvE,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,QAAQ,IAAI,OAAO;EAC5B,MAAM,QAAQ,MAAM,MAAM;EAC1B,IAAI,CAAC,cAAc,KAAK,KAAK,MAAM,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,wBAAwB;EACvF,IAAI,KAAK,IAAI,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG,MAAM,eAAe;EAC7D,KAAK,IAAI,KAAK,EAAE;EAChB,IAAI,CAAC,WAAW,SAAS,KAAK,QAAQ,GAAG,SAAS,KAAK,GAAG,MAAM,kBAAkB,KAAK,SAAS,EAAE;EAClG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,KAAK,iBAAiB,SAAS,KAAK,GAAG,MAAM,0BAA0B;EAC5E,IAAI,CAAC,KAAK,SAAS,WAAW,UAAU,GAAG,SAAS,KAAK,GAAG,MAAM,+BAA+B;EACjG,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,GAAG,MAAM,gBAAgB;EACxD,IAAI,CAAC,OAAO,SAAS,KAAK,SAAS,GAAG,SAAS,KAAK,GAAG,MAAM,mBAAmB,KAAK,UAAU,EAAE;EACjG,IAAI,KAAK,mBAAmB,CAAC,KAAK,SAAS,SAAS,KAAK,GAAG,MAAM,kCAAkC;CACtG;CACA,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,MAAM,+BAA+B,SAAS,KAAK,MAAM,GAAG;CAExE,OAAO;AACT;AAEA,MAAa,UAAmB,gBAAgBA,eAAW;AAE3D,MAAa,QAA6C,IAAI,IAC5D,QAAQ,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CACpC;;AAGA,SAAgB,cAAc,MAAsB,QAAyC;CAC3F,OAAO,KAAK,gBAAgB,QAAQ,eAAe,OAAO,QACxD,OAAO,SAAS,OAAO,OAAO,IAAI,IAAI,KACxC;AACF"}
|
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ Output (default: human-readable):
|
|
|
16
16
|
--json-file <path> additionally write the JSON report to a file
|
|
17
17
|
--sarif-file <path> additionally write a SARIF log to a file
|
|
18
18
|
--junit-file <path> additionally write JUnit XML to a file
|
|
19
|
+
--group one line per rule with a count (large streams)
|
|
19
20
|
--no-color disable ANSI colors
|
|
20
21
|
|
|
21
22
|
Rules:
|
|
@@ -50,6 +51,10 @@ const FLAGS = {
|
|
|
50
51
|
takesValue: false,
|
|
51
52
|
apply: (c) => (c.color = false, null)
|
|
52
53
|
},
|
|
54
|
+
"--group": {
|
|
55
|
+
takesValue: false,
|
|
56
|
+
apply: (c) => (c.group = true, null)
|
|
57
|
+
},
|
|
53
58
|
"--json": {
|
|
54
59
|
takesValue: false,
|
|
55
60
|
apply: (c) => setFormat(c, "json")
|
|
@@ -142,6 +147,7 @@ function parseCliArgs(argv) {
|
|
|
142
147
|
color: null,
|
|
143
148
|
headers: {},
|
|
144
149
|
severityOverrides: {},
|
|
150
|
+
group: false,
|
|
145
151
|
help: false,
|
|
146
152
|
version: false
|
|
147
153
|
};
|
|
@@ -182,6 +188,10 @@ function parseCliArgs(argv) {
|
|
|
182
188
|
error
|
|
183
189
|
};
|
|
184
190
|
}
|
|
191
|
+
if (config.group && config.format !== "pretty") return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: "--group only applies to the default pretty output, not --json/--sarif/--junit"
|
|
194
|
+
};
|
|
185
195
|
if (targets.length > 1) return {
|
|
186
196
|
ok: false,
|
|
187
197
|
error: `expected exactly one target, got ${targets.length}`
|
|
@@ -202,58 +212,7 @@ function decideExitCode(summary, maxWarnings) {
|
|
|
202
212
|
return 0;
|
|
203
213
|
}
|
|
204
214
|
//#endregion
|
|
205
|
-
//#region
|
|
206
|
-
const SYMBOL = {
|
|
207
|
-
error: "✖",
|
|
208
|
-
warning: "⚠",
|
|
209
|
-
info: "ℹ"
|
|
210
|
-
};
|
|
211
|
-
const SGR = {
|
|
212
|
-
error: "31",
|
|
213
|
-
warning: "33",
|
|
214
|
-
info: "36"
|
|
215
|
-
};
|
|
216
|
-
function paint(code, s, on) {
|
|
217
|
-
return on ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
218
|
-
}
|
|
219
|
-
function formatDiagnosticLine(d, opts) {
|
|
220
|
-
const where = d.eventIndex >= 0 ? `event ${d.eventIndex}` : "—";
|
|
221
|
-
const head = paint(SGR[d.severity], `${SYMBOL[d.severity]} ${d.rule}`, opts.color);
|
|
222
|
-
const meta = paint("2", `${d.severity.padEnd(7)} ${where.padEnd(10)}`, opts.color);
|
|
223
|
-
const cite = paint("2", ` ↳ ${d.specUrl}`, opts.color);
|
|
224
|
-
return `${head} ${meta} ${d.message}\n${cite}`;
|
|
225
|
-
}
|
|
226
|
-
function count(n, noun) {
|
|
227
|
-
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
228
|
-
}
|
|
229
|
-
function formatReportSummary(report, opts) {
|
|
230
|
-
const { errors, warnings, info } = report.summary;
|
|
231
|
-
const lines = [];
|
|
232
|
-
if (errors + warnings + info === 0) lines.push(paint("32", `✔ no conformance violations across ${count(report.eventCount, "event")}`, opts.color));
|
|
233
|
-
else lines.push(`${count(errors, "error")}, ${count(warnings, "warning")}, ${info} info across ${count(report.eventCount, "event")}`);
|
|
234
|
-
const features = Object.entries(report.features);
|
|
235
|
-
const exercised = features.filter(([, s]) => s === "exercised").map(([f]) => f);
|
|
236
|
-
const suffix = exercised.length > 0 ? `: ${exercised.join(", ")}` : "";
|
|
237
|
-
lines.push(`${exercised.length} of ${features.length} AG-UI features exercised${suffix}`);
|
|
238
|
-
if (report.skipped.length > 0) {
|
|
239
|
-
lines.push(`${count(report.skipped.length, "rule")} not evaluated:`);
|
|
240
|
-
for (const s of report.skipped) lines.push(paint("2", ` – ${s.rule}: ${s.reason}`, opts.color));
|
|
241
|
-
}
|
|
242
|
-
for (const e of report.internalErrors) lines.push(paint("31", `! internal validator error: ${e}`, opts.color));
|
|
243
|
-
return lines.join("\n");
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
246
|
-
//#region src/report/json.ts
|
|
247
|
-
function toJsonReport(report, opts) {
|
|
248
|
-
const doc = {
|
|
249
|
-
tool: opts.tool,
|
|
250
|
-
...report
|
|
251
|
-
};
|
|
252
|
-
if (opts.target !== void 0) doc.target = opts.target;
|
|
253
|
-
return doc;
|
|
254
|
-
}
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region src/rules/catalog.json
|
|
215
|
+
//#region ../spec/catalog.json
|
|
257
216
|
var catalog_default = {
|
|
258
217
|
$comment: "AG-UI conformance rule catalog. Data, not code: a Python/Go implementation shares these rules. Severities follow the working agreement that behaviour the spec does not clearly govern is at most 'info' — entries with 'specQuestion' were downgraded accordingly; see docs/spec-questions.md.",
|
|
259
218
|
catalogVersion: "0.1.0",
|
|
@@ -597,7 +556,7 @@ var catalog_default = {
|
|
|
597
556
|
"id": "AGUI503",
|
|
598
557
|
"severity": "error",
|
|
599
558
|
"title": "Unknown event type",
|
|
600
|
-
"messageTemplate": "Unknown event type '{type}' (not in
|
|
559
|
+
"messageTemplate": "Unknown event type '{type}' (not in the installed AG-UI SDK v{sdkVersion}, and not RAW or CUSTOM)",
|
|
601
560
|
"specUrl": "https://docs.ag-ui.com/concepts/events#event-types-overview",
|
|
602
561
|
"specQuote": "Events in the protocol are categorized by their purpose.",
|
|
603
562
|
"since": "0.x",
|
|
@@ -722,6 +681,98 @@ function formatMessage(rule, params) {
|
|
|
722
681
|
return rule.messageTemplate.replace(/\{(\w+)\}/g, (whole, key) => key in params ? String(params[key]) : whole);
|
|
723
682
|
}
|
|
724
683
|
//#endregion
|
|
684
|
+
//#region src/report/pretty.ts
|
|
685
|
+
const SYMBOL = {
|
|
686
|
+
error: "✖",
|
|
687
|
+
warning: "⚠",
|
|
688
|
+
info: "ℹ"
|
|
689
|
+
};
|
|
690
|
+
const SGR = {
|
|
691
|
+
error: "31",
|
|
692
|
+
warning: "33",
|
|
693
|
+
info: "36"
|
|
694
|
+
};
|
|
695
|
+
function paint(code, s, on) {
|
|
696
|
+
return on ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
697
|
+
}
|
|
698
|
+
function formatDiagnosticLine(d, opts) {
|
|
699
|
+
const where = d.eventIndex >= 0 ? `event ${d.eventIndex}` : "—";
|
|
700
|
+
const head = paint(SGR[d.severity], `${SYMBOL[d.severity]} ${d.rule}`, opts.color);
|
|
701
|
+
const meta = paint("2", `${d.severity.padEnd(7)} ${where.padEnd(10)}`, opts.color);
|
|
702
|
+
const cite = paint("2", ` ↳ ${d.specUrl}`, opts.color);
|
|
703
|
+
return `${head} ${meta} ${d.message}\n${cite}`;
|
|
704
|
+
}
|
|
705
|
+
function count(n, noun) {
|
|
706
|
+
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
707
|
+
}
|
|
708
|
+
const SEVERITY_RANK = {
|
|
709
|
+
error: 0,
|
|
710
|
+
warning: 1,
|
|
711
|
+
info: 2
|
|
712
|
+
};
|
|
713
|
+
const SAMPLE_INDEXES = 3;
|
|
714
|
+
/**
|
|
715
|
+
* One line per rule instead of one per occurrence — for large streams where
|
|
716
|
+
* the same violation repeats. Totals stay honest in the summary; this only
|
|
717
|
+
* changes what is listed.
|
|
718
|
+
*/
|
|
719
|
+
function formatGroupedDiagnostics(diagnostics, opts) {
|
|
720
|
+
const groups = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const d of diagnostics) {
|
|
722
|
+
const list = groups.get(d.rule);
|
|
723
|
+
if (list === void 0) groups.set(d.rule, [d]);
|
|
724
|
+
else list.push(d);
|
|
725
|
+
}
|
|
726
|
+
const sorted = [...groups.values()].sort((a, b) => {
|
|
727
|
+
const bySeverity = SEVERITY_RANK[a[0].severity] - SEVERITY_RANK[b[0].severity];
|
|
728
|
+
return bySeverity !== 0 ? bySeverity : a[0].rule.localeCompare(b[0].rule);
|
|
729
|
+
});
|
|
730
|
+
const lines = [];
|
|
731
|
+
for (const group of sorted) {
|
|
732
|
+
const first = group[0];
|
|
733
|
+
const title = RULES.get(first.rule)?.title ?? first.message;
|
|
734
|
+
const indexes = group.map((d) => d.eventIndex).filter((i) => i >= 0);
|
|
735
|
+
let where;
|
|
736
|
+
if (indexes.length === 0) where = "stream-level";
|
|
737
|
+
else {
|
|
738
|
+
const sample = indexes.slice(0, SAMPLE_INDEXES).join(", ");
|
|
739
|
+
const rest = indexes.length - SAMPLE_INDEXES;
|
|
740
|
+
where = `events ${sample}${rest > 0 ? ` (+${rest} more)` : ""}`;
|
|
741
|
+
}
|
|
742
|
+
const head = paint(SGR[first.severity], `${SYMBOL[first.severity]} ${first.rule}`, opts.color);
|
|
743
|
+
const meta = paint("2", `${first.severity.padEnd(7)} ×${group.length}`, opts.color);
|
|
744
|
+
const cite = paint("2", ` ↳ ${first.specUrl}`, opts.color);
|
|
745
|
+
lines.push(`${head} ${meta} ${title} — ${where}`, cite);
|
|
746
|
+
}
|
|
747
|
+
return lines.join("\n");
|
|
748
|
+
}
|
|
749
|
+
function formatReportSummary(report, opts) {
|
|
750
|
+
const { errors, warnings, info } = report.summary;
|
|
751
|
+
const lines = [];
|
|
752
|
+
if (errors + warnings + info === 0) lines.push(paint("32", `✔ no conformance violations across ${count(report.eventCount, "event")}`, opts.color));
|
|
753
|
+
else lines.push(`${count(errors, "error")}, ${count(warnings, "warning")}, ${info} info across ${count(report.eventCount, "event")}`);
|
|
754
|
+
const features = Object.entries(report.features);
|
|
755
|
+
const exercised = features.filter(([, s]) => s === "exercised").map(([f]) => f);
|
|
756
|
+
const suffix = exercised.length > 0 ? `: ${exercised.join(", ")}` : "";
|
|
757
|
+
lines.push(`${exercised.length} of ${features.length} AG-UI features exercised${suffix}`);
|
|
758
|
+
if (report.skipped.length > 0) {
|
|
759
|
+
lines.push(`${count(report.skipped.length, "rule")} not evaluated:`);
|
|
760
|
+
for (const s of report.skipped) lines.push(paint("2", ` – ${s.rule}: ${s.reason}`, opts.color));
|
|
761
|
+
}
|
|
762
|
+
for (const e of report.internalErrors) lines.push(paint("31", `! internal validator error: ${e}`, opts.color));
|
|
763
|
+
return lines.join("\n");
|
|
764
|
+
}
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/report/json.ts
|
|
767
|
+
function toJsonReport(report, opts) {
|
|
768
|
+
const doc = {
|
|
769
|
+
tool: opts.tool,
|
|
770
|
+
...report
|
|
771
|
+
};
|
|
772
|
+
if (opts.target !== void 0) doc.target = opts.target;
|
|
773
|
+
return doc;
|
|
774
|
+
}
|
|
775
|
+
//#endregion
|
|
725
776
|
//#region src/report/sarif.ts
|
|
726
777
|
const LEVEL = {
|
|
727
778
|
error: "error",
|
|
@@ -2707,7 +2758,7 @@ async function main() {
|
|
|
2707
2758
|
const color = config.color ?? (process.stdout.isTTY === true && process.env.NO_COLOR === void 0);
|
|
2708
2759
|
const pretty = config.format === "pretty";
|
|
2709
2760
|
let printed = 0;
|
|
2710
|
-
const onDiagnostic = pretty ? (d) => {
|
|
2761
|
+
const onDiagnostic = pretty && !config.group ? (d) => {
|
|
2711
2762
|
printed += 1;
|
|
2712
2763
|
process.stdout.write(`${formatDiagnosticLine(d, { color })}\n`);
|
|
2713
2764
|
} : void 0;
|
|
@@ -2755,7 +2806,8 @@ async function main() {
|
|
|
2755
2806
|
if (config.sarifFile !== void 0) writeFileSync(config.sarifFile, `${JSON.stringify(toSarif(report, sarifOptions), null, 2)}\n`);
|
|
2756
2807
|
if (config.junitFile !== void 0) writeFileSync(config.junitFile, toJUnit(report, { name: targetLabel }));
|
|
2757
2808
|
if (pretty) {
|
|
2758
|
-
if (
|
|
2809
|
+
if (config.group && report.diagnostics.length > 0) process.stdout.write(`${formatGroupedDiagnostics(report.diagnostics, { color })}\n\n`);
|
|
2810
|
+
else if (printed > 0) process.stdout.write("\n");
|
|
2759
2811
|
process.stdout.write(`${formatReportSummary(report, { color })}\n`);
|
|
2760
2812
|
} else if (config.format === "json") {
|
|
2761
2813
|
const doc = toJsonReport(report, {
|