<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
  <title>OpenLint Blog</title>
  <link>https://openlint.org/blog/</link>
  <atom:link href="https://openlint.org/blog/feed.xml" rel="self" type="application/rss+xml"/>
  <description>News and stories from the OpenLint community.</description>
  <language>en</language>
  <lastBuildDate>Thu, 05 Nov 2026 01:34:07 +0000</lastBuildDate>
  <image><url>https://openlint.org/assets/openlint-card.jpg</url><title>OpenLint Blog</title><link>https://openlint.org/blog/</link></image>
  <item>
    <title>The Dutch government&apos;s developer portal now runs on OpenLint</title>
    <link>https://openlint.org/blog/2026/11/04/dutch-government-moves-to-openlint/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/11/04/dutch-government-moves-to-openlint/</guid>
    <pubDate>Wed, 04 Nov 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>developer.overheid.nl has switched all of its tooling, including the DON Checker, from Spectral to OpenLint. Their rulesets for the API Design Rules, OpenAPI, publiccode.yml and JSON-FG work without changes. An English translation of what they wrote.</description>
    <media:content url="https://openlint.org/assets/blog/2026-11-04-dutch-government.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-11-04-dutch-government.jpg" alt="Pill-shaped figures carrying API documents along an Amsterdam canal, past gabled houses, bicycles and a bridge, toward a mint-green squiggle resolving into a check mark"></p><p>This week <a href="https://developer.overheid.nl/">developer.overheid.nl</a>, the developer portal of the Dutch government, moved all of its tooling to OpenLint. Dimitri van Hees and Joost Farla wrote about it in Dutch: <a href="https://developer.overheid.nl/blog/2026/11/04/openlint-vervangt-spectral">Het is zover: OpenLint vervangt Spectral</a> (“It’s happening: OpenLint replaces Spectral”). Here’s what they said, in English, and why it matters to the rest of us.</p>

<h2 id="what-they-did">What they did</h2>

<blockquote>
  <p>“As of this week, all developer.overheid.nl tooling runs on OpenLint, the successor to Spectral. We’ve been actively involved since July, both in the specification of the ruleset format and in the discussions about the direction of the project. When version 1.0 came out recently, we switched our tooling over straight away, including the DON Checker. The rulesets we wrote for the API Design Rules (ADR), and for validating OAS, publiccode.yml and JSON-FG, work without any changes.”</p>
</blockquote>

<p>The <a href="https://gitdocumentatie.logius.nl/publicatie/api/adr/">API Design Rules</a> are the Dutch government’s standard for REST APIs, maintained by Logius. Spectral is written into the standard itself, so this isn’t a small team swapping a dev dependency. It’s a national API standard moving its reference tooling to OpenLint, and its rulesets running unchanged on it.</p>

<h2 id="why-they-moved">Why they moved</h2>

<p>Dimitri and Joost tie it back to <a href="https://developer.overheid.nl/blog/2026/07/31/spectral-onder-vuur">their July post</a>, written after the 14 July supply chain incident, when two malicious versions of <code class="language-plaintext highlighter-rouge">@asyncapi/specs</code>, a Spectral dependency, were published to npm:</p>

<blockquote>
  <p>“Anyone who did a fresh install in that window pulled in code that could exfiltrate SSH keys, tokens and credentials, among other things. The incident itself was resolved quickly, but the issue about it on the Spectral repository stayed silent. For many users that was the last straw, because hundreds of issues were already open and bugs in the VS Code plugin had gone unaddressed for a long time.”</p>
</blockquote>

<p>They didn’t rip Spectral out in a hurry. They pinned it, and looked at <a href="https://quobix.com/vacuum/">vacuum</a>, but it doesn’t fully support the ruleset format, is maintained in practice by one person, and is written in Go, while their tooling also has to run in the browser. So they helped build the fork that started under API Commons instead:</p>

<blockquote>
  <p>“For everyone who lay awake all summer over ‘Spectralgate’ wondering what the sensible next steps were, OpenLint is now the answer.”</p>
</blockquote>

<h2 id="the-specification-is-the-part-i-want-to-point-at">The specification is the part I want to point at</h2>

<p>Their biggest contribution isn’t the switch. It’s the <a href="https://spec.openlint.org/v1/draft/">specification</a>:</p>

<blockquote>
  <p>“With Spectral, the ruleset format was nothing more than whatever the linter accepted in practice. We rewrote the format from the existing Spectral code into a specification of its own, published with ReSpec, with a JSON Schema and requirements stated in terms of MUST and SHOULD. That separates the specification from the toolbox, so any linter can implement the format, and OpenLint is one of them.”</p>
</blockquote>

<p>They go further, and I agree with every word of it:</p>

<blockquote>
  <p>“Ideally vacuum becomes a Go implementation of that specification, so that a ruleset gives the same result everywhere, whichever linter you use.”</p>
</blockquote>

<p>They also want OpenLint to lint more than OpenAPI, AsyncAPI and Arazzo, including Dutch government standards such as NL-SBB and <a href="https://docs.geostandaarden.nl/dcat/dcat-ap-nl30/">DCAT-AP-NL</a>. That’s the work we’re scoping in <a href="https://github.com/openlint/openlint/issues/40">openlint#40</a>: new formats without breaking changes, and rulesets for standards.</p>

<p>And on where the project lives:</p>

<blockquote>
  <p>“In July we said we’d help find a place to land, because a fork isn’t yet a standard. We found that place at Open Source Europe, where OpenLint will be hosted. That puts the format the ADR ruleset is written in into European public hands.”</p>
</blockquote>

<h2 id="how-to-switch-the-way-they-describe-it">How to switch, the way they describe it</h2>

<p>If you call Spectral directly, swap the package and the command. Your rulesets don’t need converting, and an existing <code class="language-plaintext highlighter-rouge">.spectral.yaml</code> is still picked up. New projects can use <code class="language-plaintext highlighter-rouge">.openlint.yaml</code>.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">- npm install --save-dev @stoplight/spectral-cli
</span><span class="gi">+ npm install --save-dev openlint
</span>
- npx spectral lint openapi.yaml
<span class="gi">+ npx openlint lint openapi.yaml
</span></code></pre></div></div>

<p>If you call the linter from your own code, the import and the class name change. <code class="language-plaintext highlighter-rouge">RulesetDefinition</code> and your ruleset stay the same.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">- import { Spectral, type RulesetDefinition } from "@stoplight/spectral-core";
</span><span class="gi">+ import { OpenLint, type RulesetDefinition } from "@openlint/openlint-core";
</span></code></pre></div></div>

<p>For Dutch government APIs they recommend the <a href="https://github.com/developer-overheid-nl/don-checker">DON Checker</a>, which wraps the ADR and publiccode.yml checks and keeps the linter and rulesets up to date for you:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx @developer-overheid-nl/don-checker@latest <span class="se">\</span>
  validate <span class="nt">--standard</span> adr <span class="nt">--input</span> ./openapi.json
</code></pre></div></div>

<h2 id="what-theyre-taking-on-next">What they’re taking on next</h2>

<ul>
  <li><strong>Editors:</strong> a language server for OpenLint, so the linter works in every IDE without a separate plugin per editor. That’s the work in <a href="https://github.com/openlint/community/discussions/9">discussion #9</a> and <a href="https://github.com/openlint/language-server">openlint/language-server</a>.</li>
  <li><strong>vacuum:</strong> mapping where it differs from OpenLint. Most differences come from Spectral’s JavaScript legacy in regular expressions and JSONPath, and “on some points vacuum follows the underlying standards better than Spectral.”</li>
  <li><strong>A new version of the specification:</strong> backwards compatible with existing rulesets, written so a ruleset works the same in OpenLint and in vacuum, and open to formats beyond OpenAPI, AsyncAPI and Arazzo.</li>
  <li><strong>Education:</strong> tutorials and reusable aliases for common <code class="language-plaintext highlighter-rouge">given</code> paths and regular expressions. If you haven’t seen them, <a href="https://openlint.org/blog/2026/10/30/aliases-hidden-gem/">aliases already do a lot of this today</a>.</li>
  <li><strong>Backwards compatibility:</strong> testing that existing rulesets, ADR included, keep working with every change to the spec and toolbox.</li>
</ul>

<h2 id="what-this-means-for-openlint">What this means for OpenLint</h2>

<p>A government that wrote a linter into its own API standard has moved to OpenLint, contributed the specification that makes OpenLint more than one tool, and committed people to the roadmap. We’re aiming for the official launch on 3 December in the Europe API track at apidays Paris, but as Dimitri and Joost say, you don’t have to wait: version 1.0 is out and you can use it now.</p>

<p>Thank you, Dimitri and Joost, and the developer.overheid.nl team at Geonovum. Read <a href="https://developer.overheid.nl/blog/2026/11/04/openlint-vervangt-spectral">the original post in Dutch</a>, try OpenLint, and join us at <a href="https://github.com/openlint/community">office hours</a> on Thursdays.</p>

<p><em>The quotes are our English translation of the original Dutch post.</em></p>
]]></content:encoded>
  </item>
  <item>
    <title>Aliases: the hidden gem in OpenLint</title>
    <link>https://openlint.org/blog/2026/10/30/aliases-hidden-gem/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/30/aliases-hidden-gem/</guid>
    <pubDate>Fri, 30 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>Aliases let you name a JSONPath once and use that name in every rule. They work in OpenLint today, few people talk about them, and they take much of the pain out of writing rulesets.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-30-aliases.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-30-aliases.jpg" alt="Pill-shaped figures following long, tangled lines that a small signpost gathers into one straight route, ending in a mint-green squiggle resolving into a check mark"></p><p>Ask people what’s hard about writing API rulesets and JSONPath comes up quickly. It came up again at <a href="https://openlint.org/meetings/">Office Hours #5</a>. Deciding what a rule should check is usually the easy part. Working out the path that selects every place it applies is the hard part, and then you have to repeat that path in every rule.</p>

<p>OpenLint already has an answer to this, and it doesn’t get much attention. Aliases work today, in the version on npm, with nothing to install or turn on.</p>

<h2 id="name-it-once">Name it once</h2>

<p>An alias gives a name to one or more JSONPath expressions. Here are two rules without one:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">rules</span><span class="pi">:</span>
  <span class="na">operation-summary</span><span class="pi">:</span>
    <span class="na">given</span><span class="pi">:</span> <span class="s">$.paths[*][get,put,post,delete,options,head,patch,trace]</span>
    <span class="na">then</span><span class="pi">:</span>
      <span class="na">field</span><span class="pi">:</span> <span class="s">summary</span>
      <span class="na">function</span><span class="pi">:</span> <span class="s">truthy</span>
  <span class="na">operation-tags</span><span class="pi">:</span>
    <span class="na">given</span><span class="pi">:</span> <span class="s">$.paths[*][get,put,post,delete,options,head,patch,trace]</span>
    <span class="na">then</span><span class="pi">:</span>
      <span class="na">field</span><span class="pi">:</span> <span class="s">tags</span>
      <span class="na">function</span><span class="pi">:</span> <span class="s">truthy</span>
</code></pre></div></div>

<p>And here are the same two rules with one, reporting exactly the same problems:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">aliases</span><span class="pi">:</span>
  <span class="na">Operation</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">$.paths[*][get,put,post,delete,options,head,patch,trace]</span>
<span class="na">rules</span><span class="pi">:</span>
  <span class="na">operation-summary</span><span class="pi">:</span>
    <span class="na">given</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#Operation"</span>
    <span class="na">then</span><span class="pi">:</span>
      <span class="na">field</span><span class="pi">:</span> <span class="s">summary</span>
      <span class="na">function</span><span class="pi">:</span> <span class="s">truthy</span>
  <span class="na">operation-tags</span><span class="pi">:</span>
    <span class="na">given</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#Operation"</span>
    <span class="na">then</span><span class="pi">:</span>
      <span class="na">field</span><span class="pi">:</span> <span class="s">tags</span>
      <span class="na">function</span><span class="pi">:</span> <span class="s">truthy</span>
</code></pre></div></div>

<p>Now the rule reads as what it means: every operation needs a summary. Someone can write or review it without being able to read the path, and the path lives in one place, where the person who knows JSONPath can get it right once.</p>

<h2 id="change-it-once">Change it once</h2>

<p>That one place pays off when the format moves. OpenAPI 3.2 added a <code class="language-plaintext highlighter-rouge">query</code> method, and the path above doesn’t select it, so an operation using it passes both rules without being checked. With the path copied into every rule, you’d have to find and fix each copy. With an alias, you add <code class="language-plaintext highlighter-rouge">query</code> to one line and every rule picks it up. We tried it: the same document went from no problems to two.</p>

<h2 id="say-what-you-actually-mean">Say what you actually mean</h2>

<p>A single alias can hold several paths, which is where it gets really useful. Say you want every server URL to use https. You could reach for <code class="language-plaintext highlighter-rouge">$..servers</code>, but that also matches any property named <code class="language-plaintext highlighter-rouge">servers</code> in a schema or an example. So list the real locations and give them a name:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">aliases</span><span class="pi">:</span>
  <span class="na">Servers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">$.servers[*]</span>
    <span class="pi">-</span> <span class="s">$.paths[*].servers[*]</span>
    <span class="pi">-</span> <span class="s">$.paths[*][get,put,post,delete,options,head,patch,trace].servers[*]</span>
    <span class="pi">-</span> <span class="s">$..callbacks[*][*].servers[*]</span>
    <span class="pi">-</span> <span class="s">$..callbacks[*][*][get,put,post,delete,options,head,patch,trace].servers[*]</span>
<span class="na">rules</span><span class="pi">:</span>
  <span class="na">server-https</span><span class="pi">:</span>
    <span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{value}}</span><span class="nv"> </span><span class="s">must</span><span class="nv"> </span><span class="s">use</span><span class="nv"> </span><span class="s">https"</span>
    <span class="na">given</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#Servers.url"</span>
    <span class="na">then</span><span class="pi">:</span>
      <span class="na">function</span><span class="pi">:</span> <span class="s">pattern</span>
      <span class="na">functionOptions</span><span class="pi">:</span>
        <span class="na">match</span><span class="pi">:</span> <span class="s2">"</span><span class="s">^https://"</span>
</code></pre></div></div>

<p>Notice <code class="language-plaintext highlighter-rouge">#Servers.url</code>. You can carry on with more path after the name, and aliases can be built from other aliases, so a small set of well-named building blocks can serve a whole ruleset. This rule is also in the registry as <a href="https://rules.openlint.org/server-https/">server-https</a>.</p>

<h2 id="one-name-across-versions">One name across versions</h2>

<p>The same thing often lives in different places in different versions of a format. Reusable parameters are at <code class="language-plaintext highlighter-rouge">$.parameters</code> in Swagger 2.0 and <code class="language-plaintext highlighter-rouge">$.components.parameters</code> in OpenAPI 3. A scoped alias picks its paths by the format of the document being linted:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">aliases</span><span class="pi">:</span>
  <span class="na">ParameterObject</span><span class="pi">:</span>
    <span class="na">targets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">formats</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">oas2</span><span class="pi">]</span>
        <span class="na">given</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">$.parameters[*]</span>
      <span class="pi">-</span> <span class="na">formats</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">oas3</span><span class="pi">]</span>
        <span class="na">given</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">$.components.parameters[*]</span>
</code></pre></div></div>

<p>A rule that uses <code class="language-plaintext highlighter-rouge">#ParameterObject</code> doesn’t need to know which version it’s looking at, as long as it lists the same <code class="language-plaintext highlighter-rouge">formats</code>. The version differences stay inside the alias.</p>

<h2 id="where-they-stop">Where they stop</h2>

<p>Aliases have limits you should know about. The big one is that an alias belongs to the ruleset that declares it. <code class="language-plaintext highlighter-rouge">spectral:oas</code> has a good <code class="language-plaintext highlighter-rouge">OperationObject</code> alias, but extending that ruleset doesn’t let your rules use it, so for now you copy what you need. Everyone ends up writing their own <code class="language-plaintext highlighter-rouge">Servers</code>, and nothing helps you share them.</p>

<p>That’s an opportunity as much as a limit. Whether aliases should be shareable is an open question in the <a href="https://spec.openlint.org/v1/draft/#aliases">specification</a> and in <a href="https://github.com/orgs/openlint/discussions/2">discussion #2</a>, and shared, tested aliases for each format could take a lot of JSONPath off the plate of everyone writing rules. If you have opinions, please bring them there.</p>

<h2 id="try-it">Try it</h2>

<p>Next time you paste a path into a second rule, make it an alias. Our <a href="https://learn.openlint.org/aliases/">aliases guide</a> covers all of this, including the limits and a YAML gotcha that produces a confusing error, and every example in it was run against OpenLint 1.0.0.</p>

<p>If you’ve already written good aliases for your own rulesets, we’d love to see them. Share them in <a href="https://github.com/orgs/openlint/discussions">Discussions</a> or bring them to <a href="https://openlint.org/meetings/">office hours</a> on Thursdays.</p>
]]></content:encoded>
  </item>
  <item>
    <title>OpenLint has a home: Open Source Europe is our fiscal host</title>
    <link>https://openlint.org/blog/2026/10/29/open-source-europe-fiscal-host/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/29/open-source-europe-fiscal-host/</guid>
    <pubDate>Thu, 29 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>Open Source Europe has accepted OpenLint as a hosted collective on Open Collective. Here&apos;s why we chose it, what a fiscal host does, and how organizations that depend on API linting can now fund the work.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-29-open-source-europe.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-29-open-source-europe.jpg" alt="Pill-shaped figures gathered around a round table under a simple roof, one placing a mint-green coin into a shared bowl, with a mint-green squiggle resolving into a check mark on the roof"></p><p>OpenLint now has a home. <a href="https://opensourceeurope.org/">Open Source Europe</a> has approved our application, and OpenLint is a hosted collective at <a href="https://opencollective.com/openlint">opencollective.com/openlint</a>.</p>

<h2 id="what-a-fiscal-host-does">What a fiscal host does</h2>

<p>OpenLint is a community, not a company. A fiscal host is the legal entity that holds and accounts for money on a project’s behalf, so a community can take sponsorships and grants, pay contributors and cover costs without forming an organization of its own. Everything runs through Open Collective, so every contribution and every expense is public.</p>

<h2 id="why-open-source-europe">Why Open Source Europe</h2>

<p>We looked at the Linux Foundation and the OpenAPI Initiative as well, and the community favored Open Source Europe, for three reasons:</p>

<ul>
  <li><strong>The people and the demand are in Europe.</strong> Most of the people doing the governance work are based there. Much of the demand comes from European public institutions that use API linting to enforce national API design rules, and they need a standard no single vendor controls.</li>
  <li><strong>A neutral home.</strong> We wanted a host that can hold the project’s name and its intellectual property, and act as a neutral fiscal host for everyone who contributes.</li>
  <li><strong>Sovereign infrastructure.</strong> It fits the push for open source infrastructure that’s governed in Europe, in the open.</li>
</ul>

<h2 id="why-this-matters-for-openlint">Why this matters for OpenLint</h2>

<p>Spectral’s ruleset format became a de facto standard. Governments, banks and companies publish their API design rules as Spectral rulesets, and other tools import them. But the format was never specified, and its future depended on one vendor’s priorities. OpenLint is turning that work into a shared, vendor-neutral standard: a <a href="https://spec.openlint.org">specification</a> with a conformance suite, a toolbox carried over from Spectral under Apache 2.0, and a community that works in the open.</p>

<p>A standard needs a home that isn’t any one vendor. Now it has one.</p>

<h2 id="how-the-money-will-be-used">How the money will be used</h2>

<p>Open Collective lets us do what we said we would when we applied:</p>

<ul>
  <li><strong>Pay maintainers</strong> for the work that keeps the toolbox healthy: releases, reviews, security and the supply chain.</li>
  <li><strong>Facilitate the community:</strong> the weekly office hours, recordings and transcripts, and the work that moves from discussion to issue to pull request.</li>
  <li><strong>Fund the specification:</strong> we’re preparing grant proposals, starting with the Sovereign Tech Fund for the specification and its conformance suite.</li>
</ul>

<p>API Evangelist is the first sponsor. Everything we receive and spend shows up on the <a href="https://opencollective.com/openlint">collective’s page</a>, and we post <a href="https://opencollective.com/openlint/updates">updates there</a> as the work moves.</p>

<h2 id="support-openlint">Support OpenLint</h2>

<p>If your organization runs API linting in its pipelines, or publishes its API design rules as rulesets, please consider <a href="https://opencollective.com/openlint">sponsoring OpenLint on Open Collective</a>. It goes straight to the people doing the work.</p>

<p>You can also help without money: <a href="https://openlint.org/blog/2026/10/29/migrating-from-spectral-beta-testers/">try the migration from Spectral</a>, join us at <a href="https://openlint.org/meetings/">office hours</a> on Thursdays, or bring your questions to <a href="https://github.com/orgs/openlint/discussions">Discussions</a>.</p>

<p>Thank you to Open Source Europe for welcoming us.</p>
]]></content:encoded>
  </item>
  <item>
    <title>Migrating from Spectral to OpenLint: we&apos;re looking for beta testers</title>
    <link>https://openlint.org/blog/2026/10/29/migrating-from-spectral-beta-testers/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/29/migrating-from-spectral-beta-testers/</guid>
    <pubDate>Thu, 29 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>API Commons has moved from Spectral to OpenLint without changing its results. Now we want companies, organizations, institutions and government agencies to try the migration and tell us what breaks.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-29-migration-beta.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-29-migration-beta.jpg" alt="Pill-shaped figures carrying boxes across a bridge from a dim platform to a bright one, where one figure waves the others on, under a mint-green squiggle resolving into a check mark"></p><p>OpenLint 1.0.0 is on npm, the <a href="https://spec.openlint.org">specification</a> is published, and the <a href="https://docs.openlint.org/getting-started/">docs</a> are live. The next step is the one that matters most to anyone running Spectral today: moving over. We’re looking for beta testers to help us prove that path.</p>

<h2 id="api-commons-has-switched">API Commons has switched</h2>

<p>API Commons runs a stack of API governance tools on the linter, including <a href="https://validator.apicommons.org">validator.apicommons.org</a>, a governance MCP server and a catalog of more than 700 rules. All of them now run on OpenLint. We checked the results against Spectral 6.16.1 on every fixture we have, and they matched.</p>

<p>The move was mostly a rename. What tripped us up is what we want to hear about from you:</p>

<ul>
  <li><strong>Custom functions that import Spectral’s core.</strong> A function that imports <code class="language-plaintext highlighter-rouge">@stoplight/spectral-core</code> won’t load in OpenLint. Import from <code class="language-plaintext highlighter-rouge">@openlint/openlint-core</code> instead, and use named imports rather than a default import.</li>
  <li><strong>Extra metadata on rules.</strong> Rulesets that carry their own properties, such as a title or a prompt for an AI agent, are rejected, so we strip them from published rulesets for now.</li>
  <li><strong>JSON output on a clean run.</strong> With <code class="language-plaintext highlighter-rouge">-f json</code>, a run with no problems prints <code class="language-plaintext highlighter-rouge">[]</code> plus a line of text, which a script parsing the output has to allow for.</li>
</ul>

<p>These are in front of the community, and backwards compatibility is the subject of an <a href="https://github.com/orgs/openlint/discussions/12">open discussion</a>.</p>

<h2 id="how-to-migrate">How to migrate</h2>

<p>The <a href="https://docs.openlint.org/migration-guides/spectral/">Spectral migration guide</a> has the details. For most teams it’s two steps:</p>

<ol>
  <li>Install OpenLint: <code class="language-plaintext highlighter-rouge">npm install openlint</code>, or run it with <code class="language-plaintext highlighter-rouge">npx openlint lint</code>.</li>
  <li>Change <code class="language-plaintext highlighter-rouge">spectral</code> to <code class="language-plaintext highlighter-rouge">openlint</code> in your commands and pipelines.</li>
</ol>

<p>Your existing <code class="language-plaintext highlighter-rouge">.spectral.yaml</code> and <code class="language-plaintext highlighter-rouge">extends: spectral:oas</code> keep working. If you use the JavaScript API, the guide shows how to alias the old package names so you can try OpenLint without changing any code, and how to rename things properly when you’re ready.</p>

<h2 id="help-us-test-it">Help us test it</h2>

<p>We want companies, organizations, institutions and government agencies of every size to try the migration on their real rulesets and pipelines: custom functions, shared rulesets pulled over the network, CI setups, and editor workflows.</p>

<p>If you’d like to take part:</p>

<ul>
  <li><strong>Tell us you’re testing.</strong> Email <a href="mailto:info@openlint.org">info@openlint.org</a> or start a thread in <a href="https://github.com/orgs/openlint/discussions">Discussions</a>. Just knowing who’s trying it helps us decide what to fix first.</li>
  <li><strong>Report what breaks.</strong> Anything that behaves differently from Spectral, or anything the guide doesn’t cover, goes in <a href="https://github.com/openlint/openlint/issues">an issue on openlint/openlint</a>.</li>
  <li><strong>Come to office hours.</strong> We meet every Thursday at 9:00 ET. Agendas are in <a href="https://github.com/openlint/community/issues?q=label%3Ameetings">openlint/community</a>, and <a href="https://openlint.org/meetings/">recordings and transcripts</a> are on this site.</li>
</ul>

<p>We’re aiming to make the official launch at API Days Paris, and we’d like to go in with migration stories from more than one organization. If yours could be one of them, we’d love to hear from you.</p>
]]></content:encoded>
  </item>
  <item>
    <title>The OpenLint specification draft is ready for feedback</title>
    <link>https://openlint.org/blog/2026/10/22/draft-specification-ready-for-feedback/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/22/draft-specification-ready-for-feedback/</guid>
    <pubDate>Thu, 22 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>The first formal draft of the OpenLint Ruleset Specification is published at spec.openlint.org, with 97 numbered requirements, conformance tests, and 36 open questions we&apos;d like your help with.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-22-draft-spec.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-22-draft-spec.jpg" alt="Pill-shaped figures reviewing a two-page document of rule bars, with a mint-green squiggle resolving into a check mark above"></p><p>The first formal draft of the <strong>OpenLint Ruleset Specification</strong> is published at <strong><a href="https://spec.openlint.org">spec.openlint.org</a></strong>, and we’d like your feedback.</p>

<p>Rulesets are how teams write down their API style guides and governance: thousands of them already exist in the Spectral format. But that format was never specified; it was whatever one tool did. This draft changes that. It describes the ruleset format as it works today, so existing rulesets stay valid, and writes it down as a proper specification that any tool can implement.</p>

<h2 id="whats-in-the-draft">What’s in the draft</h2>

<p>Joost Farla rewrote the specification as a formal <a href="https://spec.openlint.org/v1/draft/">Working Draft</a>, built with Bikeshed, the tool behind many W3C-style specifications:</p>

<ul>
  <li><strong>97 numbered requirements</strong>, each written as a testable MUST, SHOULD or MAY, and each aimed at either a ruleset or a linter</li>
  <li><strong>Conformance tests</strong> listed for each requirement, so any linter can check that it behaves the same way</li>
  <li>Chapters on <strong>the ruleset format, documents, linting and functions</strong>, plus a <strong>security and privacy</strong> section</li>
  <li>A machine-readable <a href="https://spec.openlint.org/v1/draft/requirements.json">requirements index</a> and the <a href="https://spec.openlint.org/v1/ruleset.schema.json">JSON Schema</a> for tooling</li>
  <li>A version line: the working draft now, with versioned releases to follow</li>
</ul>

<h2 id="where-we-need-help">Where we need help</h2>

<p>Writing the format down surfaced a lot of behavior that was never documented, only implemented. The draft marks <strong>36 open questions</strong> inline, right where they occur. For example, a YAML file with several documents is accepted today, but only the first one is linted. Should it be?</p>

<p>These are exactly the decisions that need more than one voice:</p>

<ul>
  <li><strong>Read the draft</strong> at <a href="https://spec.openlint.org">spec.openlint.org</a> and look for the open questions in your area</li>
  <li><strong>Comment</strong> in <a href="https://github.com/orgs/openlint/discussions/13">discussion #13</a>, or open an issue in <a href="https://github.com/openlint/spec/issues">openlint/spec</a></li>
  <li><strong>Bring your rulesets</strong>: if a ruleset you rely on behaves differently from what the draft says, that’s the most useful report you can send</li>
</ul>

<p>Under our <a href="https://github.com/openlint/community/blob/main/GOVERNANCE.md">governance</a>, changes to the specification stay open for at least 14 days, and anything breaking for 30, with known implementers told directly. Nothing here changes what your rulesets do today.</p>

<p>Join us at <a href="https://openlint.org/meetings/">office hours</a> on Thursdays if you’d rather talk it through.</p>

<p><em>Drafted with Claude, and edited by me.</em></p>
]]></content:encoded>
  </item>
  <item>
    <title>Almost ready for business: the third OpenLint office hours</title>
    <link>https://openlint.org/blog/2026/10/15/ready-for-business/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/15/ready-for-business/</guid>
    <pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>Office Hours #3: a first release within days, a formal specification taking shape, Open Source Europe as our fiscal host, and a short list of what&apos;s left before teams can switch from Spectral.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-16-ready-for-business.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-16-ready-for-business.jpg" alt="A pill-shaped figure sending packages along a conveyor, with a mint-green squiggle resolving into a check mark above"></p><p>We held the third OpenLint office hours on 15 October, with me dialing in from Stockholm after the Nordic APIs Summit. The <a href="https://youtu.be/ZtJjr1HjFPA">recording is on YouTube</a> and the <a href="https://github.com/openlint/community/issues/33">agenda is on GitHub</a>.</p>

<p>Our focus hasn’t changed: set the table before adding anything new. This week the table is nearly set.</p>

<h2 id="a-first-release">A first release</h2>

<p>Phil Sturgeon plans to publish <strong>0.1.0</strong> within days: the rename from Spectral, with the tests passing. Then 0.2.0 by next week, and a fully backwards-compatible <strong>1.0</strong> in a month or two, with a new core ruleset and an export named <code class="language-plaintext highlighter-rouge">Spectral</code>, so JavaScript users can switch packages and nothing else. Breaking changes wait for 2.0. Installing it will be as simple as <code class="language-plaintext highlighter-rouge">npm install openlint</code>.</p>

<h2 id="a-formal-specification">A formal specification</h2>

<p>Joost Farla showed a prototype of the specification built with Bikeshed, the tool behind many W3C-style specs. Every rule is written as a testable requirement, each aimed at either a ruleset or a linter, with conformance tests listed alongside, versioned releases and a working draft. We agreed to publish it from the <a href="https://github.com/openlint/spec">spec repo</a> on its own subdomain. The first version documents the format as it works today; modular rulesets get their own discussion.</p>

<h2 id="funding">Funding</h2>

<p><a href="https://opencollective.com/openlint">Open Source Europe</a> is now our fiscal host, so we can accept donations. API Evangelist is putting in the first $1,000, and I’m going after European Commission grants. The first things we’d fund are small, well-scoped pieces like the release pipeline and the docs site.</p>

<h2 id="whats-left">What’s left</h2>

<ul>
  <li>The npm package and its release pipeline</li>
  <li>A docs site built from the Markdown in the code repo</li>
  <li>The specification site</li>
  <li>A migration guide, which Phil has started</li>
  <li>Editor support for VS Code <em>and</em> IntelliJ, ideally through a language server</li>
</ul>

<p>After that, the first new thing on the list is validating Markdown and agent skills (<a href="https://github.com/openlint/openlint/issues/11">openlint#11</a>), the most common request I hear.</p>

<p>A few companies have offered to be early adopters and tell us what migrating is like. I’ll be the first, switching my own tooling over as soon as 0.1.0 is out.</p>

<p>Next week’s office hours start an hour earlier, and we’ll alternate times after that so more people can join. Check the <a href="https://openlint.org/roadmap/">roadmap</a> for what’s unclaimed, and come say hello.</p>

<p><em>Drafted with Claude from the recording, and edited by me.</em></p>
]]></content:encoded>
  </item>
  <item>
    <title>Setting the table: the second OpenLint office hours</title>
    <link>https://openlint.org/blog/2026/10/09/setting-the-table/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/10/09/setting-the-table/</guid>
    <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>Office Hours #2 was about getting OpenLint open for business: the specification up for review, the code renamed and cleaned up, and a growing list of people doing the work.</description>
    <media:content url="https://openlint.org/assets/blog/2026-10-09-setting-the-table.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-10-09-setting-the-table.jpg" alt="Pill-shaped figures setting a long table with lines of code, a mint-green squiggle resolving into a check mark at its head"></p><p>We held the second OpenLint office hours on 8 October. The <a href="https://openlint.org/meetings/office-hours/2026-10-08/">recording, summary and full transcript</a> are up, and the <a href="https://github.com/openlint/community/issues/26">agenda is on GitHub</a>.</p>

<p>The theme this week was setting the table. Our goal right now isn’t new features. It’s getting OpenLint open for business: running on par with Spectral, with governance, so the people who depend on it can switch.</p>

<h2 id="the-specification">The specification</h2>

<p>The specification and its JSON Schema are up for review in <a href="https://github.com/openlint/spec/pull/2">openlint/spec#2</a>. It documents the ruleset format as it works today, without changing it. If you have feedback, now is the time; we’ll merge in a couple of weeks.</p>

<p>Backwards compatibility took most of the hour. Everyone agrees that every ruleset that works in Spectral today has to keep working. Jakub Rożek pushed us further: compatibility also covers the JavaScript API that editors embed, and the lint results themselves, the same messages and ranges from the same ruleset. Measuring that takes a conformance suite, and that conversation continues in <a href="https://github.com/orgs/openlint/discussions/12">discussion #12</a>. We also need to decide what our first version number is.</p>

<h2 id="the-code">The code</h2>

<p>Telemetry is gone. Phil Sturgeon’s rename and cleanup merged in <a href="https://github.com/openlint/openlint/pull/1">openlint#1</a> and continues in <a href="https://github.com/openlint/openlint/pull/6">#6</a>. Jakub is upgrading Node in <a href="https://github.com/openlint/openlint/pull/7">#7</a>, and Miguel Quintero sent his first pull request, upgrading TypeScript, in <a href="https://github.com/openlint/openlint/pull/8">#8</a>. Next comes supply chain health, cleaning up vendorisms, and a migration path off Spectral.</p>

<h2 id="new-discussions">New discussions</h2>

<p>People are bringing real needs to the table:</p>

<ul>
  <li>Dimitri van Hees asks for <a href="https://github.com/orgs/openlint/discussions/27">case-insensitive matching (#27)</a>, so a rule can check for an <code class="language-plaintext highlighter-rouge">api-version</code> header without listing every spelling.</li>
  <li>Andrzej Jarzyna raises <a href="https://github.com/orgs/openlint/discussions/28">custom functions and an expression language (#28)</a>, so rulesets don’t have to run someone else’s JavaScript.</li>
  <li>Andrzej also proposes <a href="https://github.com/orgs/openlint/discussions/29">comparing two versions of a document (#29)</a> to catch breaking changes.</li>
</ul>

<h2 id="running-the-project">Running the project</h2>

<p>Proposals for <a href="https://github.com/openlint/community/pull/17">governance</a> and a <a href="https://github.com/openlint/community/pull/21">maintainers page</a> are open, and everyone who comes to office hours is listed as a contributor. We’re on <a href="https://opencollective.com/openlint">Open Collective</a>, waiting on Open Source Europe to accept us as our fiscal host. I’m also drafting a simple way to disclose AI use on issues and pull requests, rated from 1 (none) to 5 (fully automated). This post was drafted with Claude; I’d rate it a 4.</p>

<h2 id="get-involved">Get involved</h2>

<p>The <a href="https://openlint.org/roadmap/">roadmap</a> shows what’s in progress and what’s still unclaimed, and the <a href="https://openlint.org/changelog/">changelog</a> shows what moved each week. Pick something up, start a <a href="https://github.com/orgs/openlint/discussions">discussion</a>, or join us at office hours on Thursday.</p>
]]></content:encoded>
  </item>
  <item>
    <title>The first OpenLint office hours</title>
    <link>https://openlint.org/blog/2026/09/24/first-openlint-office-hours/</link>
    <guid isPermaLink="true">https://openlint.org/blog/2026/09/24/first-openlint-office-hours/</guid>
    <pubDate>Thu, 24 Sep 2026 00:00:00 +0000</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kin Lane</dc:creator>
    <description>We held the first office hours under the OpenLint name on 24 September, walked through the starter roadmap in its three areas, and left with a longer list and a clear place to begin.</description>
    <media:content url="https://openlint.org/assets/blog/2026-09-24-first-office-hours.jpg" medium="image" type="image/jpeg" width="1376" height="768"/>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://openlint.org/assets/blog/2026-09-24-first-office-hours.jpg" alt="Pill-shaped figures seated around a round table, with a mint-green squiggle resolving into a check mark at its center"></p><p>On 24 September we held the first office hours under the OpenLint name. We’ve been meeting weekly since the summer, but those calls were mostly about what this project is and what to call it. This was the first one where we got to work.</p>

<p>The <a href="https://github.com/openlint/community/issues/1">agenda is on GitHub</a>, and the <a href="https://openlint.org/meetings/office-hours/2026-09-24/">recording, summary and full transcript</a> are here on the site.</p>

<h2 id="a-name-and-a-foundation">A name, and a foundation</h2>

<p>We have a name, chosen by community vote. It’s literal, and a little boring, but after watching OpenAPI succeed, I think a name that explains itself is worth more than a clever one. The website, the GitHub org, a <a href="https://github.com/openlint/community">community repo</a> and info@openlint.org are all in place. SmartBear may still decide to hand Spectral to the group. We’re not waiting on that.</p>

<h2 id="the-roadmap-in-three-areas">The roadmap, in three areas</h2>

<p>We walked through the starter roadmap: the <strong>specification</strong>, the <strong>toolbox</strong>, and the <strong>administration</strong> of the project. The list grew on the call:</p>

<ul>
  <li>The conformance suite and IDE integration each belong in both the specification and the toolbox.</li>
  <li>The specification should be friendly to AI tools, with prompts and skills.</li>
  <li>Migration, keeping up with Spectral upstream, and the implementation landscape become one item about interoperability with Spectral, Vacuum, Speakeasy and other tools, rather than one-way migration.</li>
  <li>Coverage turns out to be two features: how much of a ruleset its tests exercise, and how much of an API document the rules actually reached.</li>
  <li>A code of conduct, a website item, and a process item went on the administration list.</li>
</ul>

<p>Phil Sturgeon walked through the first three discussions: <a href="https://github.com/orgs/openlint/discussions/2">aliases</a>, <a href="https://github.com/openlint/community/issues/3">cleaning up Stoplightisms</a>, and <a href="https://github.com/openlint/community/issues/4">rules testing</a>.</p>

<h2 id="how-the-work-will-happen">How the work will happen</h2>

<p>Work starts as a discussion, becomes issues once we know who cares about it, and gets done in pull requests. My proposal for AI is to use it sparingly, agree on it pull request by pull request, and keep it out of our issues. That one is open for discussion too. The first discussions to open are the formal specification, the core libraries, the code of conduct and governance.</p>

<p>If any item on the roadmap matters to you, start a discussion on it, or join us at the next office hours.</p>
]]></content:encoded>
  </item>
</channel>
</rss>
