Repository navigation
chore: cover the behavioural edits with tests and add the new required checks #3782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eleshar
merged 34 commits into
develop
from
chore/ruleset-required-checks-and-behaviour-tests
Oct 4, 2026
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
210bed5
feat: improve code-quality tooling, OpenCode diagnostics, and lint pe…
3b6b683
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] 5fbd5c7
fix(ci): correct the actionlint file list and drop the unpassable for…
62a719e
fix(tooling): correct the .prettierignore bug, declare zod, and fix d…
24e1295
Address CodeRabbit review: top-level permissions, cjs and mjs lint sc…
eleshar 8660054
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] 5a70b1f
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] 2c1efe2
Keep permissions under metadata, which the skill and agent validator …
eleshar 0c8efbc
Remove legacy eslintrc files, lint cjs and mjs on commit, use a conte…
eleshar 823c454
Allow the schema-defined top-level permissions, status and author on …
eleshar e3d4683
chore(tooling): single-source the actionlint invocation and guard cov…
1b97d21
fix(ci): include scripts/validation in workflow-lint's sparse checkout
0ad4260
chore: cover the behavioural edits with tests and add the new require…
683a880
Run actionlint through a cross-platform wrapper that prefers an insta…
eleshar 80a8b13
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] 69e9652
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] 90199df
Merge branch 'develop' into chore/3774-lint-tooling
lightspeed-docs-bot[bot] b1a3fe9
feat(ci): detect branch ruleset drift between declaration and live state
4738de5
Merge remote-tracking branch 'origin/chore/3774-lint-tooling' into wt…
eleshar 65affb1
Merge remote-tracking branch 'origin/chore/3778-lint-cjs-coverage-and…
eleshar e29c865
Assert the parser error is preserved as cause and drop the duplicate …
eleshar 171bfcb
fix(ci): hydrate rulesets by id; align changelog test with run-action…
fcc42df
Test the dynamic actionlint list, add merge_group to lint, accept .ya…
eleshar bc6aab7
Merge remote-tracking branch 'origin/chore/3778-lint-cjs-coverage-and…
eleshar ebd5d60
Compare full ruleset conditions, declared rule parameters and bypass …
eleshar 201b925
Treat declared checks that cannot be required yet as pending, fix the…
eleshar 94f4601
Record the owner pull-request bypass actor in the develop ruleset dec…
eleshar 79841b3
Fail on a deleted or unmatched ruleset declaration and derive pending…
eleshar 3f093a7
Do not compare bypass actors a non-admin token cannot see
eleshar d889eb7
Require readable bypass actors on trusted runs and ignore inherited r…
eleshar ce8e9f7
Drop the admin-token requirement: bypass actors need ruleset write ac…
eleshar d56235f
Merge remote-tracking branch 'origin/develop' into wt-3782
eleshar 43efdca
Show a visible summary beside the Linear review block
eleshar 8e1c8e8
Print validator notes when ruleset drift is reported
eleshar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| name: Ruleset Drift | ||
|
|
||
| # Fails when a deployed branch ruleset no longer matches its version-controlled | ||
| # declaration in .github/rulesets/. | ||
| # | ||
| # The declarations are applied by hand (see .github/rulesets/README.md), so | ||
| # nothing else noticed when a required status check quietly stopped being | ||
| # enforced. A check that is no longer required is invisible in the JSON and only | ||
| # shows up as a PR merging that it should not have. | ||
| # | ||
| # This detects drift; it never applies, creates or mutates a ruleset. Pushing | ||
| # ruleset changes needs a token with ruleset-write, and letting a pull request | ||
| # rewrite branch protection is a privilege escalation. Detection and application | ||
| # stay separate, deliberate steps. | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - '.github/rulesets/**' | ||
| - 'scripts/validation/validate-ruleset-drift.cjs' | ||
| - '.github/workflows/ruleset-drift.yml' | ||
| push: | ||
| branches: | ||
| - develop | ||
| paths: | ||
| - '.github/rulesets/**' | ||
| - 'scripts/validation/validate-ruleset-drift.cjs' | ||
| - '.github/workflows/ruleset-drift.yml' | ||
| # Catch drift that does not come from a ruleset change, e.g. someone editing | ||
| # a ruleset in the GitHub UI without updating the declaration. | ||
| schedule: | ||
| - cron: '17 6 * * 1' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ruleset-drift-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| drift: | ||
| name: Ruleset declaration matches live | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Node.js | ||
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | ||
| with: | ||
| node-version-file: .nvmrc | ||
|
|
||
| - name: Check for ruleset drift | ||
| # Read-only: a repository GITHUB_TOKEN can read rulesets but cannot | ||
| # create or update them, which is the point of this job. | ||
| # | ||
| # Bypass actors are not compared here. GitHub returns `bypass_actors` only | ||
| # to a caller with WRITE access to the ruleset (Administration read is not | ||
| # enough), so reading them in CI would need a credential that can also | ||
| # change branch protection. This job deliberately has none. The script | ||
| # prints a note that actors were not compared; an owner checks them by | ||
| # running the script with their own token (see .github/rulesets/README.md). | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GITHUB_REPOSITORY: ${{ github.repository }} | ||
| run: node scripts/validation/validate-ruleset-drift.cjs | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
agents/changelog-agent/tests/unit/parse-yaml-entry.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /** | ||
| * Unit tests for parseYamlEntry. | ||
| * | ||
| * The YAML parse error is rethrown with the original error attached as | ||
| * `cause`. That is easy to lose: validateEntry catches everything and exits, | ||
| * so a plain `new Error(...)` would look identical from the CLI while dropping | ||
| * the underlying parser position. These assertions pin the chaining. | ||
| */ | ||
|
|
||
| const { parseYamlEntry } = require('../../changelog-validator.js'); | ||
|
|
||
| describe('parseYamlEntry', () => { | ||
| it('parses valid YAML into an object', () => { | ||
| expect(parseYamlEntry('id: 123\ntitle: "A change"\n')).toEqual({ | ||
| id: 123, | ||
| title: 'A change', | ||
| }); | ||
| }); | ||
|
|
||
| it('throws with the parser error preserved as cause', () => { | ||
| const input = 'id: 123\n bad indent: [unclosed\n'; | ||
|
|
||
| // The exception js-yaml itself raises for this input: the cause has to be that | ||
| // error, with its reason and position, not any other Error. | ||
| let original; | ||
| try { | ||
| require('js-yaml').load(input); | ||
| } catch (error) { | ||
| original = error; | ||
| } | ||
| expect(original).toBeDefined(); | ||
| expect(original.mark).toBeDefined(); | ||
|
|
||
| let thrown; | ||
| try { | ||
| parseYamlEntry(input); | ||
| } catch (error) { | ||
| thrown = error; | ||
| } | ||
|
|
||
| expect(thrown).toBeInstanceOf(Error); | ||
| expect(thrown.message).toBe(`Invalid YAML: ${original.message}`); | ||
| expect(thrown.cause).not.toBe(thrown); | ||
|
eleshar marked this conversation as resolved.
Resolved
|
||
| expect(thrown.cause.name).toBe(original.name); | ||
| expect(thrown.cause.reason).toBe(original.reason); | ||
| expect(thrown.cause.message).toBe(original.message); | ||
| expect(thrown.cause.mark).toEqual( | ||
| expect.objectContaining({ line: original.mark.line, column: original.mark.column }) | ||
| ); | ||
| }); | ||
|
|
||
| it('does not set cause when parsing succeeds', () => { | ||
| const parsed = parseYamlEntry('id: 1'); | ||
| expect(parsed).toEqual({ id: 1 }); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
scripts/validation/__tests__/agent-permissions-frontmatter.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * Pins the agent frontmatter shape the skill and agent validator actually | ||
| * accepts. | ||
| * | ||
| * mode-thinking.agent.md moved its permissions between a top-level | ||
| * `permissions:` list and a `metadata.permissions` string more than once while | ||
| * #3774 was in review, and the two forms are not interchangeable: the | ||
| * validator reads the top-level list. Commit 2c1efe2d56 settled it on the | ||
| * top-level form and 823c4544fe taught the schema to allow it. This asserts | ||
| * the agreed shape so a later "tidy-up" cannot silently break validation | ||
| * again. | ||
| */ | ||
|
|
||
| const fs = require('node:fs'); | ||
| const path = require('node:path'); | ||
| const yaml = require('js-yaml'); | ||
|
|
||
| const repositoryRoot = path.resolve(__dirname, '../../..'); | ||
| const AGENT = 'agents/mode-thinking.agent.md'; | ||
|
|
||
| function frontmatter(relative) { | ||
| const raw = fs.readFileSync(path.join(repositoryRoot, relative), 'utf8'); | ||
| const match = /^---\n([\s\S]*?)\n---\n/.exec(raw); | ||
|
|
||
| expect(match).not.toBeNull(); | ||
|
|
||
| return yaml.load(match[1]); | ||
| } | ||
|
|
||
| describe('mode-thinking agent frontmatter', () => { | ||
| it('parses', () => { | ||
| expect(() => frontmatter(AGENT)).not.toThrow(); | ||
| }); | ||
|
|
||
| it('declares permissions as a top-level list', () => { | ||
| const data = frontmatter(AGENT); | ||
|
|
||
| expect(Array.isArray(data.permissions)).toBe(true); | ||
| expect(data.permissions).toEqual(expect.arrayContaining(['read', 'write', 'github:repo'])); | ||
| }); | ||
|
|
||
| it('does not move permissions into metadata', () => { | ||
| const data = frontmatter(AGENT); | ||
|
|
||
| expect(data.metadata?.permissions).toBeUndefined(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.