@opsydyn/elysia-spectral 0.2.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ ## [0.2.4](https://github.com/opsydyn/elysia-spectral/compare/v0.2.3...v0.2.4) (2026-04-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * clarify bun and npm install commands in tutorial ([535ca1e](https://github.com/opsydyn/elysia-spectral/commit/535ca1e77568801dbdc85a7b4e733ed9eb4e0486))
9
+
10
+ ## [0.2.3](https://github.com/opsydyn/elysia-spectral/compare/v0.2.2...v0.2.3) (2026-04-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * :wrench: remove stale biome suppressions and unused import ([e81e392](https://github.com/opsydyn/elysia-spectral/commit/e81e3925c66687678be5c9834bdb76880399753c))
16
+
17
+ ## [0.2.2](https://github.com/opsydyn/elysia-spectral/compare/v0.2.1...v0.2.2) (2026-04-14)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * :wrench: migrate biome config to 2.4.11 and pin version ([327d089](https://github.com/opsydyn/elysia-spectral/commit/327d08969af3e394ef6ea992cdba9e5189129f39))
23
+
24
+ ## [0.2.1](https://github.com/opsydyn/elysia-spectral/compare/v0.2.0...v0.2.1) (2026-04-14)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * :wrench: resolve all biome lint and format errors ([cb3ff8d](https://github.com/opsydyn/elysia-spectral/commit/cb3ff8dd5464b5a7036861c5550390dbf79f2dd5))
30
+
31
+ ## [0.2.0](https://github.com/opsydyn/elysia-spectral/compare/v0.1.0...v0.2.0) (2026-04-14)
32
+
33
+
34
+ ### Features
35
+
36
+ * :memo: update readme ([721a832](https://github.com/opsydyn/elysia-spectral/commit/721a832aa5a2596cb7fd26b02a575be495cc316a))
37
+ * :sparkles: cli enhance ([aa97001](https://github.com/opsydyn/elysia-spectral/commit/aa970015e18af9b7a5aff8d5242f2fad787ef17d))
38
+ * :sparkles: enhance logging ([bfbc5bb](https://github.com/opsydyn/elysia-spectral/commit/bfbc5bbfd828b07d26c6b69fcf27676b58754b31))
39
+ * :sparkles: healthcheck pkg opt in ([41df9dd](https://github.com/opsydyn/elysia-spectral/commit/41df9ddd1ba8f1b2dbcd7b90bec6fdeef3ed3cd8))
40
+ * :sparkles: logging refactor ([4a0f98c](https://github.com/opsydyn/elysia-spectral/commit/4a0f98c09f1f3e6a71b5ba20ad11fdb35f5c42e4))
41
+ * :sparkles: plugin bootstrap ([a1bcdd4](https://github.com/opsydyn/elysia-spectral/commit/a1bcdd48f5a105735d97bbfacec8159ff58816a3))
42
+ * :sparkles: rename to @opsydyn/elysia-spectral and prep for npm publish ([87ffdda](https://github.com/opsydyn/elysia-spectral/commit/87ffdda2d1c844753ee50d8e3c1800d0cafdda99))
43
+ * :sparkles: rule set update ([efb85c6](https://github.com/opsydyn/elysia-spectral/commit/efb85c62edabb17a9852b43009d9fbc044345a9c))
44
+
45
+ ## Changelog
46
+
47
+ All notable changes to this package will be documented in this file.
package/README.md ADDED
@@ -0,0 +1,637 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/opsydyn/elysia-spectral/main/api-lint-repo-logo.png" alt="@opsydyn/elysia-spectral" width="480" />
3
+ </p>
4
+
5
+ # @opsydyn/elysia-spectral
6
+
7
+ Thin Elysia plugin that lints the OpenAPI document generated by `@elysiajs/openapi` with Spectral.
8
+
9
+ ## What Is Spectral?
10
+
11
+ Spectral is an open-source linter and style guide engine for API descriptions. It was built with OpenAPI, AsyncAPI, and JSON Schema in mind, and is commonly used to enforce API style guides, catch weak or inconsistent contract definitions, and improve the usefulness of generated API descriptions.
12
+
13
+ For API teams, that matters because a spec can be technically valid while still being poor for:
14
+
15
+ - generated documentation
16
+ - client generation
17
+ - contract testing
18
+ - consistency across teams and services
19
+ - long-term API governance
20
+
21
+ `@opsydyn/elysia-spectral` uses Spectral to turn the OpenAPI document generated by `@elysiajs/openapi` into an automated quality gate for Elysia apps.
22
+
23
+ Official Spectral references:
24
+
25
+ - Stoplight overview: <https://stoplight.io/open-source/spectral>
26
+ - GitHub repository: <https://github.com/stoplightio/spectral>
27
+
28
+ This README is organized using the Diataxis documentation model:
29
+
30
+ - Tutorial: learn by building a working setup
31
+ - How-to guides: solve specific tasks
32
+ - Reference: look up exact behavior and API shape
33
+ - Explanation: understand the design choices
34
+
35
+ Current package scope:
36
+
37
+ - startup linting
38
+ - threshold-based failure
39
+ - repo-level and local rulesets
40
+ - YAML, JS, TS, and in-memory rulesets
41
+ - resolver pipeline for advanced ruleset loading
42
+ - console output
43
+ - JSON report output
44
+ - JUnit report output
45
+ - SARIF report output
46
+ - OpenAPI snapshot output
47
+ - reusable runtime for CI and tests
48
+ - opt-in healthcheck endpoint for cached and fresh runs
49
+
50
+ ## Tutorial
51
+
52
+ ### Add OpenAPI linting to an Elysia app
53
+
54
+ This tutorial takes a minimal Elysia app and adds startup OpenAPI linting with a repo-level ruleset and JSON artifacts.
55
+
56
+ 1. Install the dependencies.
57
+
58
+ ```bash
59
+ # bun
60
+ bun add elysia @elysiajs/openapi @opsydyn/elysia-spectral
61
+
62
+ # npm
63
+ npm install elysia @elysiajs/openapi @opsydyn/elysia-spectral
64
+ ```
65
+
66
+ 2. Create a repo-level `spectral.yaml`.
67
+
68
+ ```yaml
69
+ extends:
70
+ - spectral:oas
71
+
72
+ rules:
73
+ operation-description:
74
+ severity: error
75
+
76
+ operation-tags:
77
+ severity: warn
78
+
79
+ elysia-operation-summary:
80
+ severity: error
81
+ ```
82
+
83
+ 3. Add `@elysiajs/openapi` and `spectralPlugin` to your app.
84
+
85
+ ```ts
86
+ import { Elysia, t } from 'elysia'
87
+ import { openapi } from '@elysiajs/openapi'
88
+ import { spectralPlugin } from '@opsydyn/elysia-spectral'
89
+
90
+ new Elysia()
91
+ .use(
92
+ openapi({
93
+ documentation: {
94
+ info: {
95
+ title: 'Example API',
96
+ version: '1.0.0'
97
+ },
98
+ tags: [{ name: 'Users', description: 'User operations' }]
99
+ }
100
+ })
101
+ )
102
+ .use(
103
+ spectralPlugin({
104
+ failOn: 'error',
105
+ startup: {
106
+ mode: 'enforce'
107
+ },
108
+ output: {
109
+ console: true,
110
+ jsonReportPath: './artifacts/openapi-lint.json',
111
+ specSnapshotPath: true
112
+ }
113
+ })
114
+ )
115
+ .get('/users', () => [{ id: '1', name: 'Ada Lovelace' }], {
116
+ response: {
117
+ 200: t.Array(
118
+ t.Object({
119
+ id: t.String(),
120
+ name: t.String()
121
+ })
122
+ )
123
+ },
124
+ detail: {
125
+ summary: 'List users',
126
+ description: 'Return all users.',
127
+ operationId: 'listUsers',
128
+ tags: ['Users']
129
+ }
130
+ })
131
+ .listen(3000)
132
+ ```
133
+
134
+ 4. Start the app.
135
+
136
+ ```bash
137
+ bun run src/index.ts
138
+ ```
139
+
140
+ 5. Confirm the outcome.
141
+
142
+ - the app serves OpenAPI JSON at `/openapi/json`
143
+ - the plugin lints that generated document during startup
144
+ - a clean run prints Signale-style success and hype lines in the terminal
145
+ - `./artifacts/openapi-lint.json` contains the lint result
146
+ - `./<package-name>.open-api.json` contains the generated OpenAPI snapshot
147
+
148
+ If startup fails, the terminal output includes:
149
+
150
+ - the failing rule code
151
+ - the affected operation
152
+ - a fix hint when one is known
153
+ - a spec reference in `open-api.json#/json/pointer` form
154
+
155
+ ## How-to Guides
156
+
157
+ ### Use a repo-level ruleset
158
+
159
+ Use a ruleset file at the consuming app root:
160
+
161
+ ```ts
162
+ spectralPlugin({
163
+ ruleset: './spectral.yaml'
164
+ })
165
+ ```
166
+
167
+ Or let the plugin autodiscover a standard ruleset filename:
168
+
169
+ ```ts
170
+ spectralPlugin()
171
+ ```
172
+
173
+ Autodiscovery looks for these files in order:
174
+
175
+ - `spectral.yaml`
176
+ - `spectral.yml`
177
+ - `spectral.ts`
178
+ - `spectral.mts`
179
+ - `spectral.cts`
180
+ - `spectral.js`
181
+ - `spectral.mjs`
182
+ - `spectral.cjs`
183
+ - `spectral.config.yaml`
184
+ - `spectral.config.yml`
185
+ - `spectral.config.ts`
186
+ - `spectral.config.mts`
187
+ - `spectral.config.cts`
188
+ - `spectral.config.js`
189
+ - `spectral.config.mjs`
190
+ - `spectral.config.cjs`
191
+
192
+ Autodiscovered repo-level rulesets are merged with the package default ruleset.
193
+
194
+ ### Use a JS or TS ruleset module
195
+
196
+ Module rulesets can export the ruleset as the default export or as a named `ruleset` export.
197
+
198
+ ```ts
199
+ export default {
200
+ extends: ['spectral:oas'],
201
+ rules: {
202
+ operation-description: {
203
+ severity: 'error'
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ Module rulesets can also export custom Spectral functions:
210
+
211
+ ```ts
212
+ const startsWithPrefix = (input, options) => {
213
+ if (typeof input !== 'string' || !input.startsWith(options.prefix)) {
214
+ return [{ message: `OperationId must start with "${options.prefix}".` }]
215
+ }
216
+ }
217
+
218
+ export const functions = {
219
+ startsWithPrefix
220
+ }
221
+
222
+ export default {
223
+ extends: ['spectral:oas'],
224
+ rules: {
225
+ 'operation-id-prefix': {
226
+ given: '$.paths[*][get,put,post,delete,options,head,patch,trace]',
227
+ then: {
228
+ field: 'operationId',
229
+ function: 'startsWithPrefix',
230
+ functionOptions: { prefix: 'fetch' }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ### Keep the app running when lint fails at startup
238
+
239
+ Use `startup.mode: 'report'` when you want the full package-owned terminal report but do not want lint findings to block boot.
240
+
241
+ ```ts
242
+ spectralPlugin({
243
+ failOn: 'warn',
244
+ startup: {
245
+ mode: 'report'
246
+ }
247
+ })
248
+ ```
249
+
250
+ This is useful for local development and intentionally broken fixtures.
251
+
252
+ ### Disable startup lint entirely
253
+
254
+ Use `startup.mode: 'off'` when you only want manual or healthcheck-triggered runs.
255
+
256
+ ```ts
257
+ spectralPlugin({
258
+ startup: {
259
+ mode: 'off'
260
+ }
261
+ })
262
+ ```
263
+
264
+ `enabled: false` is still supported as a backwards-compatible way to disable startup lint.
265
+
266
+ ### Add a lint healthcheck endpoint
267
+
268
+ The healthcheck route is opt-in.
269
+
270
+ ```ts
271
+ spectralPlugin({
272
+ healthcheck: {
273
+ path: '/health/openapi-lint'
274
+ }
275
+ })
276
+ ```
277
+
278
+ Behavior:
279
+
280
+ - `GET /health/openapi-lint` returns the cached startup result when available
281
+ - `GET /health/openapi-lint?fresh=1` forces a fresh lint run
282
+ - the route returns `200` when findings stay below `failOn`
283
+ - the route returns `503` when findings meet or exceed `failOn`
284
+ - the route is hidden from generated OpenAPI docs
285
+
286
+ ### Persist JSON reports and OpenAPI snapshots
287
+
288
+ ```ts
289
+ spectralPlugin({
290
+ output: {
291
+ jsonReportPath: './artifacts/openapi-lint.json',
292
+ specSnapshotPath: true
293
+ }
294
+ })
295
+ ```
296
+
297
+ Snapshot behavior:
298
+
299
+ - `specSnapshotPath: true` derives `./<package-name>.open-api.json` from the consuming app's `package.json` name
300
+ - `specSnapshotPath: './contracts/openapi.json'` writes to the exact relative path you provide
301
+ - scoped package names are sanitized, so `@acme/orders-api` becomes `./acme-orders-api.open-api.json`
302
+
303
+ All report and snapshot paths resolve from the consuming app's `process.cwd()`.
304
+
305
+ ### Emit SARIF for code scanning tools
306
+
307
+ ```ts
308
+ spectralPlugin({
309
+ output: {
310
+ sarifReportPath: './artifacts/openapi-lint.sarif'
311
+ }
312
+ })
313
+ ```
314
+
315
+ SARIF is useful when you want lint results in a standard machine-readable format for platforms such as GitHub code scanning and other static analysis tooling.
316
+
317
+ ### Emit JUnit XML for CI test reporting
318
+
319
+ ```ts
320
+ spectralPlugin({
321
+ output: {
322
+ junitReportPath: './artifacts/openapi-lint.junit.xml'
323
+ }
324
+ })
325
+ ```
326
+
327
+ JUnit is useful when your CI system expects test-style XML output and you want lint findings to appear in standard test reports.
328
+
329
+ ### Add a custom sink
330
+
331
+ The existing `output` convenience flags are built on top of sink abstractions. You can add your own sink for custom reporting or automation:
332
+
333
+ ```ts
334
+ spectralPlugin({
335
+ output: {
336
+ sinks: [
337
+ {
338
+ name: 'capture',
339
+ async write(result, context) {
340
+ console.log(result.summary.total)
341
+ console.log(context.spec.openapi)
342
+ }
343
+ }
344
+ ]
345
+ }
346
+ })
347
+ ```
348
+
349
+ Custom sinks run after linting and can read:
350
+
351
+ - the normalized lint result
352
+ - the resolved OpenAPI document
353
+ - artifact paths already produced by earlier built-in sinks
354
+
355
+ ### Use a custom ruleset resolver pipeline
356
+
357
+ If you use the runtime programmatically, you can provide your own resolver pipeline to `loadRuleset` or `loadResolvedRuleset`.
358
+
359
+ ```ts
360
+ import { loadRuleset } from '@opsydyn/elysia-spectral/core'
361
+
362
+ const ruleset = await loadRuleset('virtual://team-ruleset', {
363
+ resolvers: [
364
+ async (input) =>
365
+ input === 'virtual://team-ruleset'
366
+ ? {
367
+ ruleset: {
368
+ extends: ['spectral:oas'],
369
+ rules: {
370
+ 'team-summary': {
371
+ given: '$.paths[*][get,put,post,delete,options,head,patch,trace]',
372
+ then: {
373
+ field: 'summary',
374
+ function: 'truthy'
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+ : undefined
381
+ ]
382
+ })
383
+ ```
384
+
385
+ This is an advanced extension point. Most apps should continue using repo-level `spectral.*` files.
386
+
387
+ ### Make artifact write failures fatal in CI
388
+
389
+ Artifact writes are non-fatal by default. In CI, set them to fatal:
390
+
391
+ ```ts
392
+ spectralPlugin({
393
+ output: {
394
+ jsonReportPath: './artifacts/openapi-lint.json',
395
+ specSnapshotPath: true,
396
+ artifactWriteFailures: 'error'
397
+ }
398
+ })
399
+ ```
400
+
401
+ Use `'warn'` for local development and `'error'` when artifact generation is required.
402
+
403
+ ### Run the runtime in CI or tests
404
+
405
+ ```ts
406
+ import { Elysia } from 'elysia'
407
+ import { openapi } from '@elysiajs/openapi'
408
+ import { createOpenApiLintRuntime } from '@opsydyn/elysia-spectral'
409
+
410
+ const app = new Elysia().use(openapi())
411
+
412
+ const runtime = createOpenApiLintRuntime({
413
+ ruleset: './spectral.yaml',
414
+ failOn: 'error',
415
+ output: {
416
+ console: true,
417
+ jsonReportPath: './artifacts/openapi-lint.json',
418
+ specSnapshotPath: true,
419
+ artifactWriteFailures: 'error'
420
+ }
421
+ })
422
+
423
+ await runtime.run(app)
424
+ ```
425
+
426
+ ### Work on this repository locally
427
+
428
+ From the monorepo root:
429
+
430
+ ```bash
431
+ bun install
432
+ bun run dev
433
+ ```
434
+
435
+ That starts `apps/dev-app` with:
436
+
437
+ - OpenAPI UI at `/openapi`
438
+ - raw OpenAPI JSON at `/openapi/json`
439
+ - opt-in lint healthcheck at `/health/openapi-lint`
440
+ - JSON lint report output at `./artifacts/openapi-lint.json`
441
+ - OpenAPI snapshot output at `./elysia-spectral-dev-app.open-api.json`
442
+
443
+ To run an intentionally failing fixture:
444
+
445
+ ```bash
446
+ bun run dev:unhappy
447
+ ```
448
+
449
+ That example uses `startup.mode: 'report'`, so the app still boots while the package prints the full lint report during startup.
450
+
451
+ ## Reference
452
+
453
+ ### Package API
454
+
455
+ ```ts
456
+ type SeverityThreshold = 'error' | 'warn' | 'info' | 'hint' | 'never'
457
+
458
+ type StartupLintMode = 'enforce' | 'report' | 'off'
459
+
460
+ type ArtifactWriteFailureMode = 'warn' | 'error'
461
+
462
+ type OpenApiLintArtifacts = {
463
+ jsonReportPath?: string
464
+ junitReportPath?: string
465
+ sarifReportPath?: string
466
+ specSnapshotPath?: string
467
+ }
468
+
469
+ type OpenApiLintSink = {
470
+ name: string
471
+ write: (
472
+ result: LintRunResult,
473
+ context: {
474
+ spec: Record<string, unknown>
475
+ logger: SpectralLogger
476
+ }
477
+ ) => void | Partial<OpenApiLintArtifacts> | Promise<void | Partial<OpenApiLintArtifacts>>
478
+ }
479
+
480
+ type RulesetResolver = (
481
+ input: string | RulesetDefinition | Record<string, unknown> | undefined,
482
+ context: {
483
+ baseDir: string
484
+ defaultRuleset: RulesetDefinition
485
+ mergeAutodiscoveredWithDefault: boolean
486
+ }
487
+ ) => Promise<LoadedRuleset | undefined>
488
+
489
+ type LoadResolvedRulesetOptions = {
490
+ baseDir?: string
491
+ resolvers?: RulesetResolver[]
492
+ mergeAutodiscoveredWithDefault?: boolean
493
+ }
494
+
495
+ type SpectralPluginOptions = {
496
+ ruleset?: string | RulesetDefinition | Record<string, unknown>
497
+ failOn?: SeverityThreshold
498
+ healthcheck?: false | { path?: string }
499
+ output?: {
500
+ console?: boolean
501
+ jsonReportPath?: string
502
+ junitReportPath?: string
503
+ sarifReportPath?: string
504
+ specSnapshotPath?: string | true
505
+ pretty?: boolean
506
+ artifactWriteFailures?: ArtifactWriteFailureMode
507
+ sinks?: OpenApiLintSink[]
508
+ }
509
+ source?: {
510
+ specPath?: string
511
+ baseUrl?: string
512
+ }
513
+ enabled?: boolean | ((env: Record<string, string | undefined>) => boolean)
514
+ startup?: {
515
+ mode?: StartupLintMode
516
+ }
517
+ logger?: {
518
+ info: (message: string) => void
519
+ warn: (message: string) => void
520
+ error: (message: string) => void
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### Runtime state
526
+
527
+ The runtime object exposes:
528
+
529
+ - `status`: `idle | running | passed | failed`
530
+ - `startedAt`: ISO timestamp for the most recent run start
531
+ - `completedAt`: ISO timestamp for the most recent run completion
532
+ - `durationMs`: duration of the most recent run
533
+ - `latest`: last completed lint result
534
+ - `lastSuccess`: last successful lint result
535
+ - `lastFailure`: last thrown runtime error summary
536
+ - `running`: boolean convenience flag
537
+
538
+ When used as a plugin, the runtime is also available on `app.store.openApiLint`.
539
+
540
+ ### Healthcheck response shape
541
+
542
+ Example successful response:
543
+
544
+ ```json
545
+ {
546
+ "ok": true,
547
+ "cached": true,
548
+ "threshold": "error",
549
+ "result": {
550
+ "ok": true,
551
+ "generatedAt": "2026-04-06T12:00:00.000Z",
552
+ "summary": {
553
+ "error": 0,
554
+ "warn": 0,
555
+ "info": 0,
556
+ "hint": 0,
557
+ "total": 0
558
+ },
559
+ "findings": []
560
+ }
561
+ }
562
+ ```
563
+
564
+ ### Ruleset resolution
565
+
566
+ - ruleset paths resolve from the consuming app's `process.cwd()`
567
+ - in a typical service repo, `./spectral.yaml` means the repo root
568
+ - in this monorepo, `apps/dev-app` resolves `./spectral.yaml` from `apps/dev-app`
569
+ - supported local ruleset files are `.yaml`, `.yml`, `.js`, `.mjs`, `.cjs`, `.ts`, `.mts`, and `.cts`
570
+ - module rulesets may export the ruleset as the default export or a named `ruleset` export
571
+ - module rulesets may export `functions` to register custom Spectral functions
572
+ - in-memory ruleset objects are supported
573
+ - `loadRuleset` and `loadResolvedRuleset` also accept an options object with a custom `resolvers` pipeline
574
+ - autodiscovered rulesets merge with the package default ruleset by default and can opt out with `mergeAutodiscoveredWithDefault: false`
575
+
576
+ ### Error behavior
577
+
578
+ - startup mode `enforce` throws on threshold failures
579
+ - startup mode `report` prints the same lint report but allows boot to continue on threshold failures
580
+ - startup mode `off` skips startup lint
581
+ - bad `source.specPath` or invalid spec JSON produces an actionable provider error
582
+ - artifact writes warn by default and can be made fatal with `output.artifactWriteFailures: 'error'`
583
+
584
+ ### Output model
585
+
586
+ The current output model has two layers:
587
+
588
+ - convenience options such as `jsonReportPath`, `junitReportPath`, `specSnapshotPath`, and `sarifReportPath`
589
+ - sink abstractions under `output.sinks`
590
+
591
+ The convenience options compile down to built-in sinks so the current API stays simple while the internal output model becomes extensible.
592
+
593
+ ## Explanation
594
+
595
+ ### Why this package exists
596
+
597
+ `@opsydyn/elysia-spectral` is meant to add contract-quality feedback to Elysia APIs without inventing a second schema system. The source of truth remains the route metadata and response schemas you already define for `@elysiajs/openapi`.
598
+
599
+ ### How it works
600
+
601
+ The plugin does not inspect private `@elysiajs/openapi` internals. It resolves the generated OpenAPI JSON document through Elysia's public `app.handle(new Request(...))` API, using `source.specPath` or the default `/openapi/json`.
602
+
603
+ If `source.baseUrl` is configured, the provider can also fall back to loopback HTTP fetch. This keeps spec resolution on public surfaces rather than framework internals.
604
+
605
+ ### Why startup and healthcheck are separate
606
+
607
+ Startup linting and route exposure solve different problems:
608
+
609
+ - startup linting protects boot and local feedback loops
610
+ - healthchecks expose operational state to external callers
611
+
612
+ Separating them avoids a production surprise where enabling linting also adds a route you did not intend to expose.
613
+
614
+ ### Why repo-level rulesets are the default customization path
615
+
616
+ Teams usually want policy to live with the service, not inside library code. A repo-level ruleset keeps API governance close to the app, easy to review in pull requests, and easy to override without forking this package.
617
+
618
+ That is why `spectralPlugin()` can autodiscover a repo-level ruleset and merge it with the package defaults.
619
+
620
+ ### Why the runtime is exported separately
621
+
622
+ The runtime exists so the same linting behavior can run in:
623
+
624
+ - app startup
625
+ - healthcheck-triggered manual runs
626
+ - CI pipelines
627
+ - tests
628
+
629
+ That keeps the plugin thin while still giving teams a stable programmatic surface for automation.
630
+
631
+ ### Why runtime state is tracked explicitly
632
+
633
+ Production-grade linting needs more than a pass/fail boolean. The runtime tracks status, timestamps, last success, and last failure so operators and automated systems can understand what happened without re-running lint blindly.
634
+
635
+ ### Project status
636
+
637
+ This package currently implements the narrowed v0.1 scope from [project.md](../../project.md) and the ongoing hardening work tracked in [roadmap.md](../../roadmap.md).
@@ -0,0 +1,2 @@
1
+ import { _ as defaultRulesetResolvers, a as OpenApiLintArtifactWriteError, b as lintOpenApi, c as resolveStartupMode, d as LoadedRuleset, f as ResolvedRulesetCandidate, g as RulesetResolverInput, h as RulesetResolverContext, i as shouldFail, l as normalizeFindings, m as RulesetResolver, n as enforceThreshold, o as createOpenApiLintRuntime, p as RulesetLoadError, r as exceedsThreshold, s as isEnabled, t as OpenApiLintThresholdError, u as LoadResolvedRulesetOptions, v as loadResolvedRuleset, y as loadRuleset } from "../index-BrFQCFDI.mjs";
2
+ export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };
@@ -0,0 +1,2 @@
1
+ import { a as OpenApiLintThresholdError, c as shouldFail, d as defaultRulesetResolvers, f as loadResolvedRuleset, h as normalizeFindings, i as resolveStartupMode, m as lintOpenApi, n as createOpenApiLintRuntime, o as enforceThreshold, p as loadRuleset, r as isEnabled, s as exceedsThreshold, t as OpenApiLintArtifactWriteError, u as RulesetLoadError } from "../core-Czin3kvK.mjs";
2
+ export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };