specshift 1.2.0__tar.gz → 1.4.0__tar.gz
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.
- {specshift-1.2.0/specshift.egg-info → specshift-1.4.0}/PKG-INFO +100 -12
- {specshift-1.2.0 → specshift-1.4.0}/README.md +97 -9
- {specshift-1.2.0 → specshift-1.4.0}/pyproject.toml +5 -2
- {specshift-1.2.0 → specshift-1.4.0}/specshift/__init__.py +6 -1
- {specshift-1.2.0 → specshift-1.4.0}/specshift/ai_summary.py +80 -13
- specshift-1.4.0/specshift/changelog.py +164 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/cli.py +73 -8
- {specshift-1.2.0 → specshift-1.4.0}/specshift/git_utils.py +63 -9
- specshift-1.4.0/specshift/linter.py +326 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/protobuf_differ.py +179 -46
- {specshift-1.2.0 → specshift-1.4.0}/specshift/reporter.py +146 -44
- {specshift-1.2.0 → specshift-1.4.0/specshift.egg-info}/PKG-INFO +100 -12
- {specshift-1.2.0 → specshift-1.4.0}/specshift.egg-info/SOURCES.txt +4 -0
- specshift-1.4.0/tests/test_changelog.py +72 -0
- specshift-1.4.0/tests/test_cli.py +110 -0
- specshift-1.4.0/tests/test_linter.py +166 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_protobuf_differ.py +32 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_reporter.py +38 -0
- specshift-1.2.0/tests/test_cli.py +0 -46
- {specshift-1.2.0 → specshift-1.4.0}/LICENSE +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/setup.cfg +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/config.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/differ.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/graphql_differ.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/models.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/notifier.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/spec_loader.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift/webapp.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift.egg-info/dependency_links.txt +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift.egg-info/entry_points.txt +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift.egg-info/requires.txt +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/specshift.egg-info/top_level.txt +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_differ.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_graphql_differ.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_spec_loader.py +0 -0
- {specshift-1.2.0 → specshift-1.4.0}/tests/test_webapp.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specshift
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Detects, classifies, and optionally summarizes breaking changes and best-practice issues in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI
|
|
5
5
|
Author: Lethe044
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Lethe044/specshift
|
|
8
8
|
Project-URL: Repository, https://github.com/Lethe044/specshift
|
|
9
9
|
Project-URL: Issues, https://github.com/Lethe044/specshift/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/Lethe044/specshift/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: openapi,swagger,graphql,grpc,protobuf,api,breaking-changes,contract-testing,api-diff,devtools,cli,ci-cd,github-actions
|
|
11
|
+
Keywords: openapi,swagger,graphql,grpc,protobuf,api,breaking-changes,contract-testing,api-diff,api-linter,devtools,cli,ci-cd,github-actions,sarif,changelog
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -124,9 +124,19 @@ requirement.
|
|
|
124
124
|
for comparing specs interactively (drag in files or paste text), with
|
|
125
125
|
zero extra dependencies since it runs entirely on Python's standard
|
|
126
126
|
library and stays on localhost.
|
|
127
|
-
- **
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
- **Auto-generated changelog**: `specshift changelog` walks your spec
|
|
128
|
+
file's history across git tags and produces a single Markdown document
|
|
129
|
+
summarizing what changed (and whether it was breaking) between every
|
|
130
|
+
release, plus an "Unreleased" section for the current working tree.
|
|
131
|
+
- **Spec linting**: `specshift lint` reviews a single OpenAPI document for
|
|
132
|
+
spec-validity problems (undeclared path parameters, missing response
|
|
133
|
+
descriptions, undefined security schemes, duplicate operationIds) and
|
|
134
|
+
best-practice gaps (missing summaries, missing error responses,
|
|
135
|
+
inconsistent path naming, unused schemas), independent of any diff.
|
|
136
|
+
- **Five output formats**: a colored console table, a Markdown report
|
|
137
|
+
(ideal for PR comments), JSON (for integrating with other tools), a
|
|
138
|
+
standalone filterable HTML report, and SARIF 2.1.0 for GitHub Code
|
|
139
|
+
Scanning.
|
|
130
140
|
|
|
131
141
|
## Installation
|
|
132
142
|
|
|
@@ -226,7 +236,7 @@ Useful options:
|
|
|
226
236
|
|
|
227
237
|
| Option | Description |
|
|
228
238
|
|---|---|
|
|
229
|
-
| `--format console\|markdown\|json\|html` | Output format (default: console) |
|
|
239
|
+
| `--format console\|markdown\|json\|html\|sarif` | Output format (default: console) |
|
|
230
240
|
| `--output <file>` | Writes the output to a file |
|
|
231
241
|
| `--ai` | Adds a natural-language summary |
|
|
232
242
|
| `--ai-provider groq\|gemini\|openai_compatible` | Chooses the AI provider |
|
|
@@ -290,6 +300,47 @@ specshift watch https://api.example.com/openapi.json \
|
|
|
290
300
|
--slack-webhook "$SLACK_WEBHOOK_URL"
|
|
291
301
|
```
|
|
292
302
|
|
|
303
|
+
### `specshift changelog <spec-path>`
|
|
304
|
+
|
|
305
|
+
Walks the given file's history across git tags and generates a single
|
|
306
|
+
Markdown changelog, diffing each consecutive pair of tagged versions. By
|
|
307
|
+
default it also appends an "Unreleased" section comparing the latest tag
|
|
308
|
+
to the current working tree, if they differ.
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
specshift changelog openapi.yaml --output CHANGELOG-API.md
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Options: `--tag-pattern` (glob, default `*`, e.g. `v*` to only consider
|
|
315
|
+
tags starting with `v`), `--no-working-tree` to skip the "Unreleased"
|
|
316
|
+
section, and `--output` to write to a file instead of stdout.
|
|
317
|
+
|
|
318
|
+
### `specshift lint <spec>`
|
|
319
|
+
|
|
320
|
+
Reviews a single OpenAPI/Swagger document for spec-validity problems and
|
|
321
|
+
best-practice gaps, independent of comparing it to any other version.
|
|
322
|
+
Supports the same `--format`, `--output`, `--ai`, `--fail-on`, and
|
|
323
|
+
`--artifact-path` options as `diff`.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
specshift lint openapi.yaml
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
Bookstore API : 1.0.0
|
|
331
|
+
2 breaking, 3 warning, 1 info issues found.
|
|
332
|
+
|
|
333
|
+
[BREAKING] GET /books/{bookId} :: Path template parameter '{bookId}' is used in the path but not declared as a path parameter for this operation.
|
|
334
|
+
[BREAKING] GET /books/{bookId} > response 200 :: Response has no description. The OpenAPI spec requires every response to have one.
|
|
335
|
+
[WARNING] POST /books :: Operation has no operationId; many code generators rely on this to name functions.
|
|
336
|
+
...
|
|
337
|
+
|
|
338
|
+
Result: 2 issue(s) should be fixed for spec validity.
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
See [When SpecShift calls something breaking](#when-specshift-calls-something-breaking)
|
|
342
|
+
below for what each severity means in lint results specifically.
|
|
343
|
+
|
|
293
344
|
### `specshift init`
|
|
294
345
|
|
|
295
346
|
Creates a sample `.specshift.yml` file.
|
|
@@ -330,9 +381,32 @@ jobs:
|
|
|
330
381
|
|
|
331
382
|
Available inputs: `spec-path`, `base-ref` (defaults to the PR base branch),
|
|
332
383
|
`fail-on` (`breaking` | `warning` | `none`), `ai-summary` (`true`/`false`),
|
|
333
|
-
`ai-provider`, `comment-on-pr` (`true`/`false`), `
|
|
334
|
-
`
|
|
335
|
-
`breaking-
|
|
384
|
+
`ai-provider`, `comment-on-pr` (`true`/`false`), `upload-sarif`
|
|
385
|
+
(`true`/`false`, see below), `github-token`, and `specshift-version` to
|
|
386
|
+
pin a specific release. Outputs: `has-breaking-changes`, `breaking-count`,
|
|
387
|
+
`warning-count`, `info-count`, `report-path`, and `sarif-path`.
|
|
388
|
+
|
|
389
|
+
To also surface results in GitHub's Code Scanning tab, enable
|
|
390
|
+
`upload-sarif` and grant the extra permission it needs:
|
|
391
|
+
|
|
392
|
+
```yaml
|
|
393
|
+
permissions:
|
|
394
|
+
pull-requests: write
|
|
395
|
+
security-events: write
|
|
396
|
+
|
|
397
|
+
jobs:
|
|
398
|
+
contract-check:
|
|
399
|
+
runs-on: ubuntu-latest
|
|
400
|
+
steps:
|
|
401
|
+
- uses: actions/checkout@v4
|
|
402
|
+
with:
|
|
403
|
+
fetch-depth: 0
|
|
404
|
+
|
|
405
|
+
- uses: Lethe044/SpecShift@v1
|
|
406
|
+
with:
|
|
407
|
+
spec-path: openapi.yaml
|
|
408
|
+
upload-sarif: 'true'
|
|
409
|
+
```
|
|
336
410
|
|
|
337
411
|
### Option 2: calling the CLI directly
|
|
338
412
|
|
|
@@ -408,6 +482,16 @@ than request/response direction: adding fields or rpc methods is always
|
|
|
408
482
|
safe, changing a field's number is always breaking, and type changes are
|
|
409
483
|
judged by wire type category (see the `diff-proto` section above).
|
|
410
484
|
|
|
485
|
+
`specshift lint` follows a different model too, since it reviews one
|
|
486
|
+
document rather than comparing two: **breaking** means the document
|
|
487
|
+
violates something the OpenAPI spec itself requires (undeclared path
|
|
488
|
+
parameters, a response with no description, an undefined security
|
|
489
|
+
scheme), which can cause strict tooling or code generators to reject the
|
|
490
|
+
document outright; **warning** means a common best-practice gap (missing
|
|
491
|
+
summaries, missing operationIds, undocumented error responses,
|
|
492
|
+
inconsistent naming); **info** means a minor, low-priority suggestion
|
|
493
|
+
(an apparently unused schema).
|
|
494
|
+
|
|
411
495
|
## Comparison with other tools
|
|
412
496
|
|
|
413
497
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
@@ -415,9 +499,12 @@ judged by wire type category (see the `diff-proto` section above).
|
|
|
415
499
|
| Context-aware classification | Yes | No | Partially |
|
|
416
500
|
| GraphQL support | Yes | No | Rarely |
|
|
417
501
|
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
502
|
+
| Single-document linting (not just diffing) | Yes | No | Rarely |
|
|
418
503
|
| Natural-language summary | Yes (optional) | No | No |
|
|
419
504
|
| Standalone HTML report | Yes | No | Rarely |
|
|
420
505
|
| Local interactive dashboard | Yes | No | Rarely |
|
|
506
|
+
| Auto-generated changelog across git tags | Yes | No | Rarely |
|
|
507
|
+
| GitHub Code Scanning (SARIF) integration | Yes | No | Rarely |
|
|
421
508
|
| Free to use | Fully free | Free | Usually free |
|
|
422
509
|
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
423
510
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
@@ -429,10 +516,11 @@ This project is under active development. Some planned areas:
|
|
|
429
516
|
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
430
517
|
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
431
518
|
scalar compatibility hints, and federation-aware schema composition
|
|
432
|
-
-
|
|
433
|
-
number isn't flagged the same way as an unreserved removal
|
|
519
|
+
- Extending `specshift lint` to GraphQL and Protobuf schemas
|
|
434
520
|
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
435
521
|
in-memory and per-session by design)
|
|
522
|
+
- Changelog generation from raw commit history (not just tags), for
|
|
523
|
+
repositories that don't tag every release
|
|
436
524
|
|
|
437
525
|
Feel free to open an issue if you have a feature request.
|
|
438
526
|
|
|
@@ -85,9 +85,19 @@ requirement.
|
|
|
85
85
|
for comparing specs interactively (drag in files or paste text), with
|
|
86
86
|
zero extra dependencies since it runs entirely on Python's standard
|
|
87
87
|
library and stays on localhost.
|
|
88
|
-
- **
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
- **Auto-generated changelog**: `specshift changelog` walks your spec
|
|
89
|
+
file's history across git tags and produces a single Markdown document
|
|
90
|
+
summarizing what changed (and whether it was breaking) between every
|
|
91
|
+
release, plus an "Unreleased" section for the current working tree.
|
|
92
|
+
- **Spec linting**: `specshift lint` reviews a single OpenAPI document for
|
|
93
|
+
spec-validity problems (undeclared path parameters, missing response
|
|
94
|
+
descriptions, undefined security schemes, duplicate operationIds) and
|
|
95
|
+
best-practice gaps (missing summaries, missing error responses,
|
|
96
|
+
inconsistent path naming, unused schemas), independent of any diff.
|
|
97
|
+
- **Five output formats**: a colored console table, a Markdown report
|
|
98
|
+
(ideal for PR comments), JSON (for integrating with other tools), a
|
|
99
|
+
standalone filterable HTML report, and SARIF 2.1.0 for GitHub Code
|
|
100
|
+
Scanning.
|
|
91
101
|
|
|
92
102
|
## Installation
|
|
93
103
|
|
|
@@ -187,7 +197,7 @@ Useful options:
|
|
|
187
197
|
|
|
188
198
|
| Option | Description |
|
|
189
199
|
|---|---|
|
|
190
|
-
| `--format console\|markdown\|json\|html` | Output format (default: console) |
|
|
200
|
+
| `--format console\|markdown\|json\|html\|sarif` | Output format (default: console) |
|
|
191
201
|
| `--output <file>` | Writes the output to a file |
|
|
192
202
|
| `--ai` | Adds a natural-language summary |
|
|
193
203
|
| `--ai-provider groq\|gemini\|openai_compatible` | Chooses the AI provider |
|
|
@@ -251,6 +261,47 @@ specshift watch https://api.example.com/openapi.json \
|
|
|
251
261
|
--slack-webhook "$SLACK_WEBHOOK_URL"
|
|
252
262
|
```
|
|
253
263
|
|
|
264
|
+
### `specshift changelog <spec-path>`
|
|
265
|
+
|
|
266
|
+
Walks the given file's history across git tags and generates a single
|
|
267
|
+
Markdown changelog, diffing each consecutive pair of tagged versions. By
|
|
268
|
+
default it also appends an "Unreleased" section comparing the latest tag
|
|
269
|
+
to the current working tree, if they differ.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
specshift changelog openapi.yaml --output CHANGELOG-API.md
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Options: `--tag-pattern` (glob, default `*`, e.g. `v*` to only consider
|
|
276
|
+
tags starting with `v`), `--no-working-tree` to skip the "Unreleased"
|
|
277
|
+
section, and `--output` to write to a file instead of stdout.
|
|
278
|
+
|
|
279
|
+
### `specshift lint <spec>`
|
|
280
|
+
|
|
281
|
+
Reviews a single OpenAPI/Swagger document for spec-validity problems and
|
|
282
|
+
best-practice gaps, independent of comparing it to any other version.
|
|
283
|
+
Supports the same `--format`, `--output`, `--ai`, `--fail-on`, and
|
|
284
|
+
`--artifact-path` options as `diff`.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
specshift lint openapi.yaml
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
Bookstore API : 1.0.0
|
|
292
|
+
2 breaking, 3 warning, 1 info issues found.
|
|
293
|
+
|
|
294
|
+
[BREAKING] GET /books/{bookId} :: Path template parameter '{bookId}' is used in the path but not declared as a path parameter for this operation.
|
|
295
|
+
[BREAKING] GET /books/{bookId} > response 200 :: Response has no description. The OpenAPI spec requires every response to have one.
|
|
296
|
+
[WARNING] POST /books :: Operation has no operationId; many code generators rely on this to name functions.
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
Result: 2 issue(s) should be fixed for spec validity.
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
See [When SpecShift calls something breaking](#when-specshift-calls-something-breaking)
|
|
303
|
+
below for what each severity means in lint results specifically.
|
|
304
|
+
|
|
254
305
|
### `specshift init`
|
|
255
306
|
|
|
256
307
|
Creates a sample `.specshift.yml` file.
|
|
@@ -291,9 +342,32 @@ jobs:
|
|
|
291
342
|
|
|
292
343
|
Available inputs: `spec-path`, `base-ref` (defaults to the PR base branch),
|
|
293
344
|
`fail-on` (`breaking` | `warning` | `none`), `ai-summary` (`true`/`false`),
|
|
294
|
-
`ai-provider`, `comment-on-pr` (`true`/`false`), `
|
|
295
|
-
`
|
|
296
|
-
`breaking-
|
|
345
|
+
`ai-provider`, `comment-on-pr` (`true`/`false`), `upload-sarif`
|
|
346
|
+
(`true`/`false`, see below), `github-token`, and `specshift-version` to
|
|
347
|
+
pin a specific release. Outputs: `has-breaking-changes`, `breaking-count`,
|
|
348
|
+
`warning-count`, `info-count`, `report-path`, and `sarif-path`.
|
|
349
|
+
|
|
350
|
+
To also surface results in GitHub's Code Scanning tab, enable
|
|
351
|
+
`upload-sarif` and grant the extra permission it needs:
|
|
352
|
+
|
|
353
|
+
```yaml
|
|
354
|
+
permissions:
|
|
355
|
+
pull-requests: write
|
|
356
|
+
security-events: write
|
|
357
|
+
|
|
358
|
+
jobs:
|
|
359
|
+
contract-check:
|
|
360
|
+
runs-on: ubuntu-latest
|
|
361
|
+
steps:
|
|
362
|
+
- uses: actions/checkout@v4
|
|
363
|
+
with:
|
|
364
|
+
fetch-depth: 0
|
|
365
|
+
|
|
366
|
+
- uses: Lethe044/SpecShift@v1
|
|
367
|
+
with:
|
|
368
|
+
spec-path: openapi.yaml
|
|
369
|
+
upload-sarif: 'true'
|
|
370
|
+
```
|
|
297
371
|
|
|
298
372
|
### Option 2: calling the CLI directly
|
|
299
373
|
|
|
@@ -369,6 +443,16 @@ than request/response direction: adding fields or rpc methods is always
|
|
|
369
443
|
safe, changing a field's number is always breaking, and type changes are
|
|
370
444
|
judged by wire type category (see the `diff-proto` section above).
|
|
371
445
|
|
|
446
|
+
`specshift lint` follows a different model too, since it reviews one
|
|
447
|
+
document rather than comparing two: **breaking** means the document
|
|
448
|
+
violates something the OpenAPI spec itself requires (undeclared path
|
|
449
|
+
parameters, a response with no description, an undefined security
|
|
450
|
+
scheme), which can cause strict tooling or code generators to reject the
|
|
451
|
+
document outright; **warning** means a common best-practice gap (missing
|
|
452
|
+
summaries, missing operationIds, undocumented error responses,
|
|
453
|
+
inconsistent naming); **info** means a minor, low-priority suggestion
|
|
454
|
+
(an apparently unused schema).
|
|
455
|
+
|
|
372
456
|
## Comparison with other tools
|
|
373
457
|
|
|
374
458
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
@@ -376,9 +460,12 @@ judged by wire type category (see the `diff-proto` section above).
|
|
|
376
460
|
| Context-aware classification | Yes | No | Partially |
|
|
377
461
|
| GraphQL support | Yes | No | Rarely |
|
|
378
462
|
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
463
|
+
| Single-document linting (not just diffing) | Yes | No | Rarely |
|
|
379
464
|
| Natural-language summary | Yes (optional) | No | No |
|
|
380
465
|
| Standalone HTML report | Yes | No | Rarely |
|
|
381
466
|
| Local interactive dashboard | Yes | No | Rarely |
|
|
467
|
+
| Auto-generated changelog across git tags | Yes | No | Rarely |
|
|
468
|
+
| GitHub Code Scanning (SARIF) integration | Yes | No | Rarely |
|
|
382
469
|
| Free to use | Fully free | Free | Usually free |
|
|
383
470
|
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
384
471
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
@@ -390,10 +477,11 @@ This project is under active development. Some planned areas:
|
|
|
390
477
|
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
391
478
|
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
392
479
|
scalar compatibility hints, and federation-aware schema composition
|
|
393
|
-
-
|
|
394
|
-
number isn't flagged the same way as an unreserved removal
|
|
480
|
+
- Extending `specshift lint` to GraphQL and Protobuf schemas
|
|
395
481
|
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
396
482
|
in-memory and per-session by design)
|
|
483
|
+
- Changelog generation from raw commit history (not just tags), for
|
|
484
|
+
repositories that don't tag every release
|
|
397
485
|
|
|
398
486
|
Feel free to open an issue if you have a feature request.
|
|
399
487
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specshift"
|
|
7
|
-
version = "1.
|
|
8
|
-
description = "Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI"
|
|
7
|
+
version = "1.4.0"
|
|
8
|
+
description = "Detects, classifies, and optionally summarizes breaking changes and best-practice issues in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -20,10 +20,13 @@ keywords = [
|
|
|
20
20
|
"breaking-changes",
|
|
21
21
|
"contract-testing",
|
|
22
22
|
"api-diff",
|
|
23
|
+
"api-linter",
|
|
23
24
|
"devtools",
|
|
24
25
|
"cli",
|
|
25
26
|
"ci-cd",
|
|
26
27
|
"github-actions",
|
|
28
|
+
"sarif",
|
|
29
|
+
"changelog",
|
|
27
30
|
]
|
|
28
31
|
classifiers = [
|
|
29
32
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -3,18 +3,23 @@ specshift - a CLI tool that detects, classifies, and (optionally)
|
|
|
3
3
|
summarizes changes in OpenAPI and Swagger contracts using AI.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from specshift.changelog import build_changelog, relevant_tags
|
|
6
7
|
from specshift.differ import diff_specs
|
|
7
8
|
from specshift.graphql_differ import diff_graphql, load_sdl as load_graphql_sdl
|
|
9
|
+
from specshift.linter import lint_spec
|
|
8
10
|
from specshift.models import Change, Severity, ChangeType, DiffResult
|
|
9
11
|
from specshift.protobuf_differ import diff_protos, load_proto
|
|
10
12
|
from specshift.spec_loader import load_spec
|
|
11
13
|
|
|
12
|
-
__version__ = "1.
|
|
14
|
+
__version__ = "1.4.0"
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"diff_specs",
|
|
16
18
|
"diff_graphql",
|
|
17
19
|
"diff_protos",
|
|
20
|
+
"lint_spec",
|
|
21
|
+
"build_changelog",
|
|
22
|
+
"relevant_tags",
|
|
18
23
|
"load_spec",
|
|
19
24
|
"load_graphql_sdl",
|
|
20
25
|
"load_proto",
|
|
@@ -36,13 +36,18 @@ def generate_summary(
|
|
|
36
36
|
provider: Optional[str] = None,
|
|
37
37
|
model: Optional[str] = None,
|
|
38
38
|
timeout: int = 30,
|
|
39
|
+
context: str = "diff",
|
|
39
40
|
) -> str:
|
|
40
41
|
"""
|
|
41
42
|
Returns a natural-language summary using a configured AI provider when
|
|
42
43
|
possible, otherwise falls back to a rule-based summary. Never raises an
|
|
43
44
|
exception that would halt execution; failures fall back silently.
|
|
45
|
+
|
|
46
|
+
context is either "diff" (comparing two versions, the default) or
|
|
47
|
+
"lint" (findings about a single specification), and selects which
|
|
48
|
+
prompt and fallback wording is used.
|
|
44
49
|
"""
|
|
45
|
-
prompt = _build_prompt(result)
|
|
50
|
+
prompt = _build_prompt(result, context=context)
|
|
46
51
|
|
|
47
52
|
chosen_provider = provider or _detect_provider()
|
|
48
53
|
|
|
@@ -64,7 +69,7 @@ def generate_summary(
|
|
|
64
69
|
except AISummaryError:
|
|
65
70
|
pass
|
|
66
71
|
|
|
67
|
-
return build_template_summary(result)
|
|
72
|
+
return build_template_summary(result, context=context)
|
|
68
73
|
|
|
69
74
|
|
|
70
75
|
def _detect_provider() -> Optional[str]:
|
|
@@ -77,19 +82,40 @@ def _detect_provider() -> Optional[str]:
|
|
|
77
82
|
return None
|
|
78
83
|
|
|
79
84
|
|
|
80
|
-
def _build_prompt(result: DiffResult) -> str:
|
|
85
|
+
def _build_prompt(result: DiffResult, context: str = "diff") -> str:
|
|
81
86
|
counts = result.summary_counts()
|
|
87
|
+
|
|
88
|
+
if context == "lint":
|
|
89
|
+
intro = (
|
|
90
|
+
"Below is a list of issues found while linting a single OpenAPI "
|
|
91
|
+
"specification for correctness and best practices. This is NOT a "
|
|
92
|
+
"comparison between two versions, it is a quality review of one "
|
|
93
|
+
"document. Read this list and write a short, clear, professional "
|
|
94
|
+
"summary in English that a developer maintaining this API would "
|
|
95
|
+
"understand. First highlight any spec-validity issues (things that "
|
|
96
|
+
"could break tooling or fail parsing), then best-practice gaps, "
|
|
97
|
+
"then minor style suggestions. Do not use lists or headings, write "
|
|
98
|
+
"in flowing paragraphs. Do not invent information, base the "
|
|
99
|
+
"summary only on the findings given.",
|
|
100
|
+
)
|
|
101
|
+
total_label = "issues"
|
|
102
|
+
else:
|
|
103
|
+
intro = (
|
|
104
|
+
"Below is a list of contract changes detected between two versions "
|
|
105
|
+
"of an API. Read this list and write a short, clear, professional "
|
|
106
|
+
"summary in English that a developer consuming this API would "
|
|
107
|
+
"understand. First highlight the most important breaking changes, "
|
|
108
|
+
"then anything worth paying attention to, and finally briefly "
|
|
109
|
+
"mention minor additions. Do not use lists or headings, write in "
|
|
110
|
+
"flowing paragraphs. Do not invent information, base the summary "
|
|
111
|
+
"only on the changes given.",
|
|
112
|
+
)
|
|
113
|
+
total_label = "changes"
|
|
114
|
+
|
|
82
115
|
lines = [
|
|
83
|
-
|
|
84
|
-
"of an API. Read this list and write a short, clear, professional "
|
|
85
|
-
"summary in English that a developer consuming this API would "
|
|
86
|
-
"understand. First highlight the most important breaking changes, "
|
|
87
|
-
"then anything worth paying attention to, and finally briefly "
|
|
88
|
-
"mention minor additions. Do not use lists or headings, write in "
|
|
89
|
-
"flowing paragraphs. Do not invent information, base the summary "
|
|
90
|
-
"only on the changes given.",
|
|
116
|
+
intro[0],
|
|
91
117
|
"",
|
|
92
|
-
f"Total: {counts['breaking']} breaking, {counts['warning']} warning, {counts['info']} informational
|
|
118
|
+
f"Total: {counts['breaking']} breaking, {counts['warning']} warning, {counts['info']} informational {total_label}.",
|
|
93
119
|
"",
|
|
94
120
|
]
|
|
95
121
|
for change in result.sorted_changes()[:60]:
|
|
@@ -160,8 +186,14 @@ def _call_openai_compatible(prompt: str, model: Optional[str], timeout: int) ->
|
|
|
160
186
|
raise AISummaryError(f"Custom endpoint request failed: {exc}") from exc
|
|
161
187
|
|
|
162
188
|
|
|
163
|
-
def build_template_summary(result: DiffResult) -> str:
|
|
189
|
+
def build_template_summary(result: DiffResult, context: str = "diff") -> str:
|
|
164
190
|
"""A deterministic, rule-based summary that works without any API key."""
|
|
191
|
+
if context == "lint":
|
|
192
|
+
return _build_lint_template_summary(result)
|
|
193
|
+
return _build_diff_template_summary(result)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _build_diff_template_summary(result: DiffResult) -> str:
|
|
165
197
|
counts = result.summary_counts()
|
|
166
198
|
|
|
167
199
|
if result.is_empty:
|
|
@@ -199,3 +231,38 @@ def build_template_summary(result: DiffResult) -> str:
|
|
|
199
231
|
)
|
|
200
232
|
|
|
201
233
|
return " ".join(parts)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _build_lint_template_summary(result: DiffResult) -> str:
|
|
237
|
+
counts = result.summary_counts()
|
|
238
|
+
|
|
239
|
+
if result.is_empty:
|
|
240
|
+
return "No issues were found; this specification looks clean."
|
|
241
|
+
|
|
242
|
+
parts: list[str] = []
|
|
243
|
+
|
|
244
|
+
if counts["breaking"] > 0:
|
|
245
|
+
top_issues = result.breaking_changes[:5]
|
|
246
|
+
issue_desc = "; ".join(f"{c.location} ({c.message.rstrip('.')})" for c in top_issues)
|
|
247
|
+
extra = counts["breaking"] - len(top_issues)
|
|
248
|
+
extra_note = f" and {extra} more" if extra > 0 else ""
|
|
249
|
+
parts.append(
|
|
250
|
+
f"This specification has {counts['breaking']} spec-validity issue(s) that should be "
|
|
251
|
+
f"fixed first, since they can break tooling or code generation. Notably: {issue_desc}{extra_note}."
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
parts.append("This specification has no spec-validity issues.")
|
|
255
|
+
|
|
256
|
+
if counts["warning"] > 0:
|
|
257
|
+
parts.append(
|
|
258
|
+
f"There are {counts['warning']} best-practice recommendation(s), such as missing "
|
|
259
|
+
"descriptions, operation IDs, or documented error responses, that would improve the "
|
|
260
|
+
"developer experience for anyone consuming this API."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if counts["info"] > 0:
|
|
264
|
+
parts.append(
|
|
265
|
+
f"The remaining {counts['info']} finding(s) are minor style or cleanup suggestions."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return " ".join(parts)
|