Skip to content

chore: adopt external static-analysis gates - #1005

Merged
aviggiano merged 4 commits into
mainfrom
codex/static-analysis-libraries
Aug 31, 2026
Merged

aviggiano merged 4 commits into
mainfrom
codex/static-analysis-libraries

Conversation

@aviggiano

@aviggiano aviggiano commented Aug 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • extend the existing flat ESLint setup to JS/CJS/MJS/TS/TSX and apply typescript-eslint strict type-checked rules plus native complexity/size rules to changed code through eslint-plugin-diff
  • add exact-version Knip and Size Limit gates, with only entry-point configuration and dashboard Brotli budgets
  • add a full-SHA-pinned Super-Linter job for Actionlint/ShellCheck, Zizmor, Gitleaks, Hadolint, Stylelint, shfmt, HTML, JSON/JSONC, YAML, Markdown, and dotenv checks
  • delete the five orphan files, unused declarations, and unused dependencies surfaced during the library audit

This deliberately adds no custom analyzer, baseline, suppression registry, metric snapshot, or generated checking code. The resulting patch is 245 additions and 716 deletions (net -471 lines), and all production source changes are deletions.

Validation

  • pnpm install --frozen-lockfile
  • pnpm -w format:check
  • pnpm -w lint
  • ESLINT_PLUGIN_DIFF_COMMIT=origin/main pnpm -w lint:strict:ci
  • pnpm -w knip
  • pnpm -w build
  • pnpm -w size
  • Actionlint 1.7.12 with ShellCheck 0.11.0
  • Zizmor 1.29.0 and Super-Linter's bundled Zizmor 1.25.2
  • git diff --check

Fixes #997

Greptile Summary

The PR adopts external static-analysis gates and removes declarations, files, and dependencies identified as unused.

  • Adds strict changed-code ESLint, Knip, Size Limit, and Super-Linter checks.
  • Fetches complete Git history and disables persisted checkout credentials.
  • Correctly propagates comparison-revision failures from the strict lint pipeline.
  • Removes orphaned scripts, exports, types, and package dependencies.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported masked comparison-revision failure is fixed by enabling pipeline failure propagation.

Important Files Changed

Filename Overview
package.json Adds static-analysis scripts and dependencies; the revised strict-lint pipeline now propagates a failed Git comparison.
.github/workflows/ci.yml Adds external and in-repository analysis gates, complete history checkout, and non-persistent credentials.
eslint.config.js Extends lint coverage and conditionally enables strict type-aware and changed-line rules.
.size-limit.json Defines dashboard JavaScript and CSS bundle budgets.
knip.jsonc Configures workspace entry points and intentional runtime exceptions for dead-code analysis.

Reviews (3): Last reviewed commit: "ci: fail strict lint on invalid diff bas..." | Re-trigger Greptile

@socket-security

socket-security Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​size-limit/​file@​13.0.31001006791100
Addedsize-limit@​13.0.31001008291100
Addedeslint-plugin-diff@​2.1.79810010085100

View full report

Comment thread package.json Outdated
@aviggiano
aviggiano merged commit ab3c03f into main Aug 31, 2026
13 checks passed
@aviggiano
aviggiano deleted the codex/static-analysis-libraries branch August 31, 2026 11:14
aviggiano added a commit that referenced this pull request Aug 31, 2026
…is job

#1005 wired `external-static-analysis` into `release-validation.needs` and
`release-gates.needs` but left the assertions in ci-config.test.ts expecting the
previous two- and three-element arrays, so `main` is red on
"runs the release validation lanes the policy script selects, on pull requests
too". Both assertions live in one test, so the first failure masked the second.

Unrelated to the rest of this branch; needed for its CI to be readable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Aug 31, 2026
Two independent classes of failure, both surfaced by gates that landed after
this branch was written.

Strict lint (ours). `eslint-plugin-diff` scopes the strict ruleset to changed
lines, so new code gets rules existing code is grandfathered against. Replace
console with process.stdout/stderr.write, remove non-null assertions in favour
of guards that name what was missing, split two over-long functions, and move
the new recency tests into their own file so history.test.ts returns to being
untouched rather than dragging its pre-existing 2131 lines into max-lines.

Workflow linters (inherited). Super-Linter lints only changed files but lints
each in full, and the repo carried no linter config, so stock yamllint defaults
applied to lines nobody wrote: eval-benchmarks.yml alone has 146 lines over 80
columns. Any workflow edit by anyone was failing. Add a config that relaxes
only what prettier already owns or GitHub mandates. Suppress zizmor's
dangerous-triggers on the publication workflow's workflow_run, whose trust
model rejects non-default-branch and fork producers before anything executes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Aug 31, 2026
…of green (#1013)

Benchmark history published nothing for 19 days while the producer workflow reported success on nearly every push: 29 of 29 green push runs since 08-13 were soft-failed incompletes. The publication gate was refusing correctly the whole time; nothing measured whether history was advancing.

- Add a tokenless cohort reachability gate that runs before Modal spend, failing in seconds and naming the unreachable dependency rather than after a ~4h run that ends unpublishable.
- Add the repo's first scheduled freshness monitor, plus an opt-in `eval history --max-age-days`; default behaviour is unchanged.
- Annotate both silent publication no-ops without changing any readiness verdict, now pinned by tests.
- Carry a failing command's reason into the collected artifact through the existing eval-failure-diagnostics channel, and close two paths where one collector-illegal log line discarded a pair's whole artifact set.
- Satisfy the static-analysis gates #1005 added, including a yamllint config so workflow edits stop failing on pre-existing long lines.

Closes #1012.
mrthankyou pushed a commit that referenced this pull request Aug 31, 2026
The external static-analysis gate added in #1005 fails on pre-existing test
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that every
one is rejected; `packages/security/test/sensitive-redaction.test.ts` carries
another. They are the inputs the redaction tests exist to prove. One site
already carried a `// secretlint-disable` for the same reason, so the repository
already had the precedent.

Every such line is marked with `gitleaks:allow` and a reason. Forty-three lines
across five files:

  artifacts.test.ts                     21
  sensitive-redaction.test.ts           15
  runtime.test.ts                        4
  generated-workflow-verifier.test.ts    2
  verified-output.test.ts                1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it, which turns a suppression into a surprise later. Excluding
`packages/**/test/**` would also clear the failure, but that disables the gate
for the files most likely to carry a pasted credential by accident.

The rule stays active everywhere else, including elsewhere in these same files.
No test input or assertion changes: `@ultrafuzz/security` reports 23/23 and
`@ultrafuzz/artifacts` 312/324 both before and after, identical to main.
mrthankyou pushed a commit that referenced this pull request Aug 31, 2026
The external static-analysis gate added in #1005 fails on pre-existing
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that each is
rejected; `packages/security/test/sensitive-redaction.test.ts` carries another
covering vendor formats. The `references` tests use spelled-out placeholders
such as `ghs_examplereferencetokenvalue0123456789`. These are the inputs the
redaction tests exist to prove. One site already carried a
`// secretlint-disable` for the same reason.

Forty-nine lines across eight files are marked `gitleaks:allow` with a reason:

  artifacts/test/artifacts.test.ts                    22
  security/test/sensitive-redaction.test.ts           17
  runtime/test/runtime.test.ts                         4
  runtime/test/generated-workflow-verifier.test.ts     2
  runtime/test/verified-output.test.ts                 1
  references/test/git-credential.test.ts               1
  references/test/references.test.ts                   1
  references/test/vulnerability-database.test.ts       1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it. Excluding `packages/**/test/**` would also clear the failure, but that
disables the gate for the files most likely to carry a pasted credential by
accident.

The one finding in production source is fixed by rewording rather than
suppressed. `sensitive-redaction.ts` explained the supplemental PEM pattern by
quoting a BEGIN/END pair on one line, which reads as a private key; the prose
now names the markers instead. Nothing is suppressed outside test fixtures.

Verified by running gitleaks 8.28.0 over the tree: no tracked file reports a
finding. Remaining hits are all in gitignored `dist/` and `dist-test/` output.
`@ultrafuzz/security` reports 23/23 and `@ultrafuzz/references` 26/31, both
identical to main; comments and one comment rewording only.
aviggiano added a commit that referenced this pull request Sep 1, 2026
The external static-analysis gate added in #1005 fails on pre-existing
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that each is
rejected; `packages/security/test/sensitive-redaction.test.ts` carries another
covering vendor formats. The `references` tests use spelled-out placeholders
such as `ghs_examplereferencetokenvalue0123456789`. These are the inputs the
redaction tests exist to prove. One site already carried a
`// secretlint-disable` for the same reason.

Forty-nine lines across eight files are marked `gitleaks:allow` with a reason:

  artifacts/test/artifacts.test.ts                    22
  security/test/sensitive-redaction.test.ts           17
  runtime/test/runtime.test.ts                         4
  runtime/test/generated-workflow-verifier.test.ts     2
  runtime/test/verified-output.test.ts                 1
  references/test/git-credential.test.ts               1
  references/test/references.test.ts                   1
  references/test/vulnerability-database.test.ts       1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it. Excluding `packages/**/test/**` would also clear the failure, but that
disables the gate for the files most likely to carry a pasted credential by
accident.

The one finding in production source is fixed by rewording rather than
suppressed. `sensitive-redaction.ts` explained the supplemental PEM pattern by
quoting a BEGIN/END pair on one line, which reads as a private key; the prose
now names the markers instead. Nothing is suppressed outside test fixtures.

Verified by running gitleaks 8.28.0 over the tree: no tracked file reports a
finding. Remaining hits are all in gitignored `dist/` and `dist-test/` output.
`@ultrafuzz/security` reports 23/23 and `@ultrafuzz/references` 26/31, both
identical to main; comments and one comment rewording only.

Co-authored-by: thankyou <>
Co-authored-by: Antonio Viggiano <agfviggiano@gmail.com>
aviggiano added a commit that referenced this pull request Sep 1, 2026
#1019)

* test: mark the fake credential fixtures the secret scanner flags

The external static-analysis gate added in #1005 fails on pre-existing
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that each is
rejected; `packages/security/test/sensitive-redaction.test.ts` carries another
covering vendor formats. The `references` tests use spelled-out placeholders
such as `ghs_examplereferencetokenvalue0123456789`. These are the inputs the
redaction tests exist to prove. One site already carried a
`// secretlint-disable` for the same reason.

Forty-nine lines across eight files are marked `gitleaks:allow` with a reason:

  artifacts/test/artifacts.test.ts                    22
  security/test/sensitive-redaction.test.ts           17
  runtime/test/runtime.test.ts                         4
  runtime/test/generated-workflow-verifier.test.ts     2
  runtime/test/verified-output.test.ts                 1
  references/test/git-credential.test.ts               1
  references/test/references.test.ts                   1
  references/test/vulnerability-database.test.ts       1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it. Excluding `packages/**/test/**` would also clear the failure, but that
disables the gate for the files most likely to carry a pasted credential by
accident.

The one finding in production source is fixed by rewording rather than
suppressed. `sensitive-redaction.ts` explained the supplemental PEM pattern by
quoting a BEGIN/END pair on one line, which reads as a private key; the prose
now names the markers instead. Nothing is suppressed outside test fixtures.

Verified by running gitleaks 8.28.0 over the tree: no tracked file reports a
finding. Remaining hits are all in gitignored `dist/` and `dist-test/` output.
`@ultrafuzz/security` reports 23/23 and `@ultrafuzz/references` 26/31, both
identical to main; comments and one comment rewording only.

* test(runtime): create temporary roots that are canonical and removable

Every fixture built its temporary root with
`mkdtempSync(path.join(os.tmpdir(), ...))`, which has two problems that each
call site had to solve and none did.

The root was not canonical. On macOS `os.tmpdir()` is `/var/folders/...` and
`/var` is a symlink to `/private/var`, so the runtime rejected the fixture's
own root -- correctly, since that check exists to stop a component being
swapped underneath a resolved path. The suite therefore failed against itself
rather than against anything under test: 362 distinct failures on macOS
before any change, 44 of them literally `must be canonical`. Linux is
unaffected because `/tmp` is already canonical, which is why CI never saw it.

The root was not removable. Sealed execution snapshots are left `dr-x------`,
and unlinking an entry needs the write bit on its parent rather than on the
entry itself, so `rmSync` could not remove a tree containing one. Of 39 files
creating temporary roots, 25 attempted removal and 11 restored permissions
first. One run left 1,232 directories and 567,065 files behind; repeated runs
reached 11 GB, at which point the suite slowed from 2m30s to over 90 minutes
and individual tests from tens of milliseconds to 55-85 seconds.

`temporaryRoot` does both: it builds on a canonicalized tmpdir and registers
the root for removal, restoring owner write permission on directories on the
way down -- the same treatment `restoreRemovableDirectoryPermissions` applies
in `clean.ts` (#985). `registerTemporaryPath` covers directories a fixture
places beside its root rather than inside it, which root removal cannot reach.

The two production sites are canonicalized for the same reason the tests are:
the runtime requires canonical roots, so it should create them.

Measured on macOS, `main` versus this change, default tmpdir throughout:

  distinct failures     362 -> 219   (no new failures; the remaining 219 all
                                      fail on main, 159 of them on the darwin
                                      /proc limitation tracked in #1011)
  `must be canonical`    44 -> 0
  leaked directories  1,232 -> 0
  leaked files      567,065 -> 0

The one directory that survives is a deliberately retained native-continuation
controller root, which production removes through its own exit hook.

---------

Co-authored-by: thankyou <>
Co-authored-by: Antonio Viggiano <agfviggiano@gmail.com>
aviggiano added a commit that referenced this pull request Sep 1, 2026
…1025)

* test: mark the fake credential fixtures the secret scanner flags

The external static-analysis gate added in #1005 fails on pre-existing
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that each is
rejected; `packages/security/test/sensitive-redaction.test.ts` carries another
covering vendor formats. The `references` tests use spelled-out placeholders
such as `ghs_examplereferencetokenvalue0123456789`. These are the inputs the
redaction tests exist to prove. One site already carried a
`// secretlint-disable` for the same reason.

Forty-nine lines across eight files are marked `gitleaks:allow` with a reason:

  artifacts/test/artifacts.test.ts                    22
  security/test/sensitive-redaction.test.ts           17
  runtime/test/runtime.test.ts                         4
  runtime/test/generated-workflow-verifier.test.ts     2
  runtime/test/verified-output.test.ts                 1
  references/test/git-credential.test.ts               1
  references/test/references.test.ts                   1
  references/test/vulnerability-database.test.ts       1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it. Excluding `packages/**/test/**` would also clear the failure, but that
disables the gate for the files most likely to carry a pasted credential by
accident.

The one finding in production source is fixed by rewording rather than
suppressed. `sensitive-redaction.ts` explained the supplemental PEM pattern by
quoting a BEGIN/END pair on one line, which reads as a private key; the prose
now names the markers instead. Nothing is suppressed outside test fixtures.

Verified by running gitleaks 8.28.0 over the tree: no tracked file reports a
finding. Remaining hits are all in gitignored `dist/` and `dist-test/` output.
`@ultrafuzz/security` reports 23/23 and `@ultrafuzz/references` 26/31, both
identical to main; comments and one comment rewording only.

* fix: build temporary roots on a canonical system temporary directory

`os.tmpdir()` is `/var/folders/...` on macOS and `/var` is a symlink to
`/private/var`, so a root built directly on it is not canonical. Ultrafuzz
deliberately rejects non-canonical roots -- that check exists to stop a path
component being swapped underneath a resolved path -- so the code failed against
its own temporary directories. Linux is unaffected because `/tmp` is already
canonical, which is why CI never saw this.

#1019 fixes this for `packages/runtime/test`. The same defect was present in 92
files across ten packages, the release-validation script, and the CI scripts,
including seven production files. Those are worth fixing regardless of platform:
they encode an assumption that the system temporary directory is canonical, and
no platform guarantees that.

Measured on macOS, `main` versus this change, per package:

  @ultrafuzz/modal        440 -> 492 passing   (52 fixed)
  @ultrafuzz/dashboard     16 ->  34 passing   (18 fixed)
  @ultrafuzz/artifacts    312 -> 324 passing   (12 fixed)
  @ultrafuzz/cli          111 -> 116 passing    (5 fixed)
  @ultrafuzz/references    26 ->  31 passing    (5 fixed)

92 tests fixed, no new failures. `@ultrafuzz/config`, `@ultrafuzz/prompts`,
`@ultrafuzz/topology`, `@ultrafuzz/evals`, `@ultrafuzz/evmbench` and
`@ultrafuzz/security` passed before and still pass.

The failures named the cause directly, for example:

  Error [ArtifactPathError]: generated test file root must be canonical: /var/folders/...
  Error: prompt schema destination crosses a symlink: /var

`scripts/validate-packed-install.mjs` built its consumer and initialized project
under the same root, so `pnpm validate:pack` failed on macOS with
`SYMLINK_PATH: Topology path contains a symlink component: /var` before it
validated anything.

`packages/runtime/test/**` and the two runtime source files are left to #1019 so
the two changes stay independently mergeable.

---------

Co-authored-by: thankyou <>
Co-authored-by: Antonio Viggiano <agfviggiano@gmail.com>
aviggiano added a commit that referenced this pull request Sep 1, 2026
…tor paths (#1016)

* test: mark the fake credential fixtures the secret scanner flags

The external static-analysis gate added in #1005 fails on pre-existing
fixtures. `VALIDATE_ALL_CODEBASE` is false, so gitleaks scans changed *files*
rather than changed lines: touching any part of one of these files fails the
gate on content the change never edited, and `release-gates` fails with it.

The fixtures are deliberate. `packages/artifacts/test/artifacts.test.ts` carries
a catalogue of fifteen fake credentials, one per format, asserting that each is
rejected; `packages/security/test/sensitive-redaction.test.ts` carries another
covering vendor formats. The `references` tests use spelled-out placeholders
such as `ghs_examplereferencetokenvalue0123456789`. These are the inputs the
redaction tests exist to prove. One site already carried a
`// secretlint-disable` for the same reason.

Forty-nine lines across eight files are marked `gitleaks:allow` with a reason:

  artifacts/test/artifacts.test.ts                    22
  security/test/sensitive-redaction.test.ts           17
  runtime/test/runtime.test.ts                         4
  runtime/test/generated-workflow-verifier.test.ts     2
  runtime/test/verified-output.test.ts                 1
  references/test/git-credential.test.ts               1
  references/test/references.test.ts                   1
  references/test/vulnerability-database.test.ts       1

An inline marker travels with the line. A line-numbered fingerprint in
`.gitleaksignore` silently stops matching the moment anything is inserted above
it. Excluding `packages/**/test/**` would also clear the failure, but that
disables the gate for the files most likely to carry a pasted credential by
accident.

The one finding in production source is fixed by rewording rather than
suppressed. `sensitive-redaction.ts` explained the supplemental PEM pattern by
quoting a BEGIN/END pair on one line, which reads as a private key; the prose
now names the markers instead. Nothing is suppressed outside test fixtures.

Verified by running gitleaks 8.28.0 over the tree: no tracked file reports a
finding. Remaining hits are all in gitignored `dist/` and `dist-test/` output.
`@ultrafuzz/security` reports 23/23 and `@ultrafuzz/references` 26/31, both
identical to main; comments and one comment rewording only.

* feat(runtime): support darwin via volfs inode-rooted paths

Workflow submission was Linux-only. Snapshot publication, cleanup, and
permission sealing all anchor traversal to an open directory descriptor by
way of /proc/self/fd/<n>, so that resolving root/a/b/c cannot be
redirected by renaming or symlinking a component mid-flight.

macOS has no /proc. Its fdesc /dev/fd/<n> entry for a directory stats with
the correct inode but reports devfs's own st_dev, cannot be reopened with
O_DIRECTORY, and cannot be traversed at all (/dev/fd/<n>/child is ENOENT).
directoryDescriptorPath returned it anyway, on the strength of
isDirectory() alone, and verifiedDirectoryDescriptorPath then rejected it
with "workflow execution snapshots descriptor path changed at the
controller command boundary" -- a message describing tampering, for a
platform that simply has no /proc.

macOS does expose volfs. /.vol/<dev>/<ino> names a path rooted at an inode
rather than at a name, which is the property this code needs: renaming or
symlink-swapping any component cannot redirect resolution. Traversal,
mkdir, O_NOFOLLOW|O_DIRECTORY open, O_CREAT|O_EXCL, symlink, and chmod all
work through it. The descriptor stays open for as long as the path is
used, so the inode cannot be recycled and the volfs path resolves to
exactly the directory that was opened.

- Offer /.vol/<dev>/<ino> as a descriptor-path candidate, and apply the
  existing isDirectory + device + inode identity check during selection
  rather than only afterwards. Nothing is relaxed to make volfs pass: the
  same check that rejects /dev/fd accepts volfs.
- Use volfs for the cross-process descriptor path too. A volfs path is
  process-independent, so unlike /proc/<pid>/fd it satisfies that
  requirement on macOS.
- Canonicalize through the tracked lexical path where realpath(3) is
  needed, since volfs paths intentionally have no realpath resolution.
  Every such site already asserts that its access and lexical paths name
  the same device and inode.
- Verify the sealed Bun module root by comparing the held descriptor's
  device and inode when /proc is absent, instead of realpath equality.

Linux is unchanged: the /proc candidates are tried first and win, and the
added identity check is a strengthening /proc already satisfies. Verified
by running the selection logic on both platforms; Linux resolves to the
same /proc/self/fd/<n> and /proc/<pid>/fd/<n> strings as before.

* feat(runtime): address the detached engine snapshot by inode on darwin

The controller hands the execution snapshot to the detached engine as fd 3
and rewrites paths to /proc/self/fd/3, with the parent addressing the same
directory as /proc/<pid>/fd/<n>. macOS has neither, so the engine died
before admission on:

  ENOENT: /proc/self/fd/3/controls/bunfig.toml

A volfs path is process-independent, so one /.vol/<dev>/<ino> string is
valid in both parent and child and needs no pid or fd number. Four helpers
centralize what were nine scattered /proc literals, and
ultrafuzzBunStartupArgs becomes ultrafuzzBunStartupArgsFor(root) because
the root is no longer a compile-time constant.

fd 3 inheritance is unchanged: the child still receives the directory
atomically and the held descriptor still pins the inode against reuse.
Only the spelling used to address it differs by platform. Two realpath
comparisons became inode comparisons, which volfs forced but which is what
those checks already meant -- realpath equality was the proxy.

Because this changes the patched shape of the resume snapshot transfer,
the release Ultrafuzz shipped before it has to stay recognizable.
SMITHERS_CLI_RESUME_SNAPSHOT_TRANSFER_PATCH was derived from its
predecessor by dropping `--preserve-symlinks`; substituting the inline bun
startup arguments for a helper call removed that replacement's target, so
the derivation became a no-op and the patch equalled its own predecessor.
classifyRequiredSmithersPatch treats that as incompatible, and an
installation patched by the current release would have failed on its next
launch with "pinned workflow runner resume snapshot transfer
implementation is incompatible". The predecessor keeps the inline literal
it has always had, the current patch is derived from the post-0.35.0 form,
and that form is registered as a second predecessor so both previously
written shapes are recognized and rewritten in place.

The compatibility-patcher test asserted that the patched runner contains
the /proc bun-startup literals. That is the assumption this removes, so it
now checks that both patches route through the helper, that the anchor
defines it against a root parameter, and that the Linux spelling of that
root is still /proc/self/fd/3.

* test(runtime): gate the snapshot capability tests on the capability

The snapshot capability tests were gated on `fs.existsSync("/proc/self/fd")`,
so both of them skipped on darwin whether the runtime worked or not. That is
the only reason this change appeared to fix nothing: the suite never ran the
code it repairs. The /proc mount is a proxy for the capability, not the
capability itself.

Probe instead: open a directory, then require that the descriptor path both
names that exact directory and resolves a child through it. A path that
merely stats is not enough, which is what makes /dev/fd fail the probe rather
than pass it and break later.

On darwin these two tests now run, and they are the regression test this work
was missing:

  pristine main  0/2 pass -- "workflow execution snapshots descriptor path
                             changed at the controller command boundary"
  with this work 2/2 pass

Linux is unaffected: /proc/self/fd is the first candidate and satisfies the
probe, so the tests run there exactly as before.

The remaining eleven /proc gates in runtime.test.ts,
smithers-executable-capability.test.ts, and
generated-workflow-verifier.test.ts are deliberately left alone. Those tests
read /proc directly in their bodies -- enumerating /proc/self/fd, or building
/proc/<pid>/fd/<n> pathnames -- so the gate is describing what the test
itself needs rather than what the runtime supports. Widening it there only
runs Linux-specific assertions on a platform that cannot satisfy them.

---------

Co-authored-by: thankyou <>
Co-authored-by: Antonio Viggiano <agfviggiano@gmail.com>
aviggiano added a commit that referenced this pull request Sep 28, 2026
Since the external static-analysis gate was adopted (#1005), super-linter
lints every changed Markdown file with MD013 at 400 characters.
CHANGELOG.md keeps one entry per line, and 43 existing entries are longer
than that, so any change to the file fails `External static analysis`.
That also fails `release-gates` and skips the release validation lanes on
the pull request. No commit has changed CHANGELOG.md since the gate
landed. Disable MD013 for this file only; super-linter's pinned
markdownlint config otherwise passes it unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
Super-linter's markdownlint (MD013, 400 columns) validates every changed
Markdown file in full. The gate landed (#1005) after CHANGELOG.md was last
edited, and nearly every entry is one line longer than 400 columns, so
any pull request that adds a changelog entry fails "External static
analysis" on the file's existing lines. Ignore CHANGELOG.md the same way
LICENSE.md already is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
Super-linter's markdownlint (MD013, 400 columns) validates every changed
Markdown file in full. The gate landed (#1005) after CHANGELOG.md was last
edited, and nearly every entry is one line longer than 400 columns, so
any pull request that adds a changelog entry fails "External static
analysis" on the file's existing lines. Line length is the only rule the
file breaks under super-linter's configuration, so disable just MD013 for
this file and keep every other Markdown rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
Since the external static-analysis gate was adopted (#1005), super-linter
lints every changed Markdown file with MD013 at 400 characters.
CHANGELOG.md keeps one entry per line, and markdownlint reports 42 existing
entries on main as longer than that, so any change to the file fails
`External static analysis`.
That also fails `release-gates` and skips the release validation lanes on
the pull request. No commit has changed CHANGELOG.md since the gate
landed. Disable MD013 below the title of this file only, using the same
directive line that #1169, #1172 and #1181 add, so the branches do not
end up with two spellings of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
…y ceiling

#1005 deliberately added no baseline or suppression registry, and #960 asks for
a complexity ceiling in the style of modem-dev/hunk#861: one global maximum set
at today's worst function, with nothing grandfathered, lowered as hotspots are
simplified. Drop eslint-suppressions.json and the always-on size budgets, keep
the diff-limited strict budgets for changed lines, and fail any function above
complexity 90 (synchronizeLinkedWorkflowRun is at 90 once the pending
simplification PRs land). The changelog entry moves to the consolidated
release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
…r-render cost drops (#1168)

* fix(runtime): literal braces in prompt text no longer fail the workflow render

renderAgentPrompt substituted the operator and task prompts into the
trusted agent-prompt template and then regex-scanned the whole result for
leftover `{{word}}` placeholders. Inserted text that merely contained such
a sequence threw inside the Smithers render: an escaped `\{{word}}` example
in a prompt template (the prompt renderer emits it as literal `{{word}}`),
the same escape inside a dynamic item value (kept verbatim), or an
operator `--prompt` note. Every render builds every task's prompt, so the
run failed, and failed again on every resume.

Check only the template's own placeholders, inside the replace callback,
as renderAgentPreambleTemplate already does. A template placeholder with
no value still throws, and now names the placeholder.

The dynamic prompt renderer also resolves `{{...}}` inside goal-plan
replacement values, and an unbound name there throws in the same render.
The goal-plan contract now rejects `{{` in replacement values, which are
plain-text labels, so that failure lands on goal-plan's own verify instead
of on every render. The check is a zod refinement; goal-plan.schema.json
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): run the JSON validator preflight once per engine process

prepareArtifactMirror spawned `ultrafuzz json validate` against the smoke
fixture in every prepare, in every agent-attempt reset, when an agent is
built after a controller restart, and again in the zero-retry verify task.
The trusted launcher's cold start was measured at ~35 s under contention
(#1026), so each spawn was another chance to fail an attempt, including
one whose agent work had already finished.

What the spawn proves -- that this process can launch the agent-facing
validator -- does not depend on the task: materializePromptSchemas has
just digest-checked the workspace's schema copy, and start-run already
runs the trusted-launcher preflight at launch and resume. Remember the
first success in the engine process. A failure is not remembered, so the
next caller spawns again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


* perf(runtime): cut per-process and per-render cost of the generated workflow

agent-registry.ts and smithers.ts imported the TypeScript compiler at
module load, and both are reachable from @ultrafuzz/runtime's index. Only
registry analysis (validate and init) and the controller-refresh helper
that only tests call ever use it. Require it at first use instead.

The saving lands in processes that import the runtime without smthrs:
the ultrafuzz CLI, including every agent `ultrafuzz json validate` call.
Smithers engine processes still load the compiler: the generated
workflow imports smthrs, whose index re-exports @smthrs/scorers, and two
scorer modules import typescript at module load. Importing the built
runtime index alone on this host went from 528-547 ms / 248-250 MB RSS to
409-437 ms / 196-198 MB under Node 24, and from 449-483 ms / 252-261 MB
to 336-351 ms / 209-213 MB under Bun 1.3.14 (5 runs each).

Changing the parameter type on topLevelNameIsBound's signature makes the
strict diff lint report that function's existing complexity, so its
import-clause check moves into a helper with the same conditions.

materializeDynamicRuntime runs on every render and rewrote tasks.json and
graph.json, with fsyncs, even when it re-derived the bytes already on
disk. Skip the write in that case.

taskSpecsFromCompiled looked every task up with serializedTaskSpecs.find;
index the specs by id once per call instead. At the default topology's 65
static tasks the difference is not measurable.

Add a test that compiles the packaged default topology and holds the
generated workflow under 2.3 MB, measured with a fixed-length project root
(about 2.06-2.07 MB today, depending on the checkout path), so the size
#1146 describes cannot grow silently.

Refs #1146

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: changelog for render robustness and runtime footprint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: exempt CHANGELOG.md from the markdown line-length rule

Since the external static-analysis gate was adopted (#1005), super-linter
lints every changed Markdown file with MD013 at 400 characters.
CHANGELOG.md keeps one entry per line, and markdownlint reports 42 existing
entries on main as longer than that, so any change to the file fails
`External static analysis`.
That also fails `release-gates` and skips the release validation lanes on
the pull request. No commit has changed CHANGELOG.md since the gate
landed. Disable MD013 below the title of this file only, using the same
directive line that #1169, #1172 and #1181 add, so the branches do not
end up with two spellings of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): the post-agent verify pass no longer preflights the validator CLI

finalizeAndVerifyArtifacts calls prepareArtifactMirror with
pinnedSubmodules "verify", and that pass still ran the
`ultrafuzz json validate` preflight. Verify checks the finished agent's
outputs in-process and never runs the agent-facing CLI, and its task has
no retry. The per-process memo only helps later callers: after an engine
restart, the first preflight in the new process can be the verify of an
attempt whose agent had already finished, and a CLI cold start that timed
out there (the trusted launcher measured ~35 s under contention in #1026)
failed verification and discarded that work.

Skip the preflight in the verify pass. prepare:* and the reset before each
agent attempt, which run ahead of an agent that uses the CLI, keep it.

The new lifecycle test runs the production preparation body with the
options finalizeAndVerifyArtifacts passes. Without this change it counts
three preflights instead of two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: state where the TypeScript saving and the goal-plan brace rule apply

The comments, the footprint test and the changelog said Smithers engine
processes stop loading the TypeScript compiler. They do not: the
generated workflow imports smthrs, whose index re-exports
@smthrs/scorers, and its sideEffectAnalysis and workflowUiCompliance
modules import typescript at module load. The saving lands in ultrafuzz
CLI processes, including every agent `ultrafuzz json validate` call.

The prompt-variables reference and the changelog also said the
goal-plan@1 contract rejects `{{` in replacement values. Only goal-plan's
own verification (and the evals goal-plan reader) run that zod rule;
`ultrafuzz json validate` and `ultrafuzz artifact validate` accept such a
plan. Say so, replace the exact generated-workflow byte count, which does
not reproduce across environments, with a range, and describe the
preflight as running until its first success.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(runtime): cover imported Object bindings in registry shadowing

importClauseBindsName, split out of topLevelNameIsBound on this branch,
had no test: the registry-shadowing test only declared Object locally.
Add default, namespace, named, aliased and default-plus-named imports of
Object, each of which must stop Object.freeze from being read as the
global, and an unrelated named import that must not. Removing any one of
the helper's three checks, or making it match every named import, fails
the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
…nused agent profiles (#1179)

* perf(runtime): write execution-snapshot files with their final mode and flush each once

Snapshot publication wrote every file with mode 0400 and fsynced it, then
the permission seal reopened every file, fchmodded it (to 0500 for
executables, otherwise to the 0400 it already had) and fsynced it again.
A launch of the test fixture's 12,552-file snapshot therefore issued
27,876 fsyncs, two per file plus two per directory, and launch time is
fsync-bound.

writeSnapshotFile now creates each file with its final mode and sets that
mode through the creating descriptor (the umask can clear creation bits)
before its single flush, so the bytes and the mode are durable together,
as the second flush previously made them. The permission seal now only
makes directories read-only; the publication verification that already
follows it still checks every entry's type, mode, link count and bytes.
The same fixture now issues 15,324 fsyncs (one per file plus two per
directory).

The new test republishes a launched run's snapshot and bounds its fsync
calls at one per file plus two per directory; origin/main fails it with
27,876 flushes for 12,552 files in 1,386 directories.

Refs #921

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): doctor requires only the agent CLIs a run can dispatch to

doctor required the executable of every configured model profile, so a
fresh `ultrafuzz init` on a Codex-only host reported "needs attention"
because the scaffold's opt-in Claude, Kimi and Pi profiles were not
installed, although no node would run them. It now requires an agent's
CLI only when the selected topology or its [retry] agents fallback chain
can dispatch to that agent (the same selection the OpenRouter credential
check already used). Other configured profiles' CLIs are still probed and
listed, as not required, and the human output marks them "(not
required)". Agents that share a CLI (Codex and OpenRouter, Claude and
DeepSeek) require it if any of them is selected.

doctor also built DOCTOR_WORKFLOW_ENGINE_* diagnostics and check statuses
for the project-local engine and then discarded them, reporting both
checks as "unknown". The builders are deleted; layout_status and the
per-patch posture are still reported, and the reference docs no longer
list the five codes that were never emitted.

Launch and resume install the workflow engine controller under the OS
temporary directory, and a native resume keeps its install there for the
detached engine (#921 cost 1: a tmpfs /tmp exhausted a host's memory). A
new temporary-directory check warns, without failing the verdict, when
that directory is a tmpfs or has under 2 GiB free, and reports how many
ultrafuzz-controller-* directories it holds and their total size. It
never removes them, because a live engine may still use them.

Refs #921

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): keep refreshed controllers out of the governed target identity

`resume --refresh-controller` renders its current controller into
<project>/.smithers/continuations/<uuid>/, but that directory was missing
from controllerOwnedGovernancePaths. Unless the project ignores .smithers,
the rendered files are untracked target files: the next launch in that
project records the target as dirty, which a private campaign (the
default policy) and every cloud launch reject, and the changed worktree
digest invalidates disclosure acknowledgements computed for the target.

The directory is now controller-owned, like .smithers/workflows. The new
test renders files in that layout and checks the target identity is
unchanged; origin/main reports the target dirty.

Refs #921

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): parse the control seal with an item limit that covers its schema

Runtime documents are parsed with a 100,000-item limit that the strict
JSON parser counts across the whole document. The control seal's schema
admits 100,000 execution files plus three identity arrays of up to
100,000 entries each, and launch writes the seal after schema validation
alone. A closure near the execution-file bound plus the run's task
bindings would therefore produce a seal that every later command fails
to parse. (An earlier measurement put the production closure at about
51,000 files; it was not re-measured for this change.)

The seal is now parsed with a 400,000-item limit, the schema's total; its
property total (four per execution file plus 35 fixed) already fits the
default limit. Byte limits are unchanged. The new test parses a seal with
the schema's maximum execution files and the fixture's bindings;
origin/main rejects it with "JSON exceeds the item limit of 100000".

Refs #921

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: changelog entry for launch I/O and doctor fixes

Refs #921

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: disable only MD013 for the one-entry-per-line changelog

Super-linter's markdownlint (MD013, 400 columns) validates every changed
Markdown file in full. The gate landed (#1005) after CHANGELOG.md was last
edited, and nearly every entry is one line longer than 400 columns, so
any pull request that adds a changelog entry fails "External static
analysis" on the file's existing lines. Line length is the only rule the
file breaks under super-linter's configuration, so disable just MD013 for
this file and keep every other Markdown rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): count only required commands in doctor's toolchain summary

Doctor now lists the CLIs of configured but unselected agents with
required: false, so the toolchain summary's "N required commands
available" counted them too. On a fresh init with only Codex installed it
reported 7 required commands when 4 are required.

The selection test also gains the case the reviewers found missing: a
selected Codex profile beside an unused OpenRouter profile keeps codex
required. Because configured agent refs are sorted, the existing
OpenRouter-only case could not tell "any requirer wins" from "last
writer wins"; this case fails under the latter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(runtime): parse every runtime document with one 400,000-item limit

The previous commit gave the control seal its own limits constant, a
schema-ID import and a ternary. Raising the single shared item limit is
the same fix in one number: runtime documents stay bounded by the
unchanged 128 MiB parse cap (and the seal by the 64 MiB control-file read
cap), and 400,000 is what a seal whose arrays are all at their schema
bounds holds (100,000 execution files plus three identity arrays of
100,000), with 400,035 properties under the unchanged 500,000 limit.

The seal test's comment now claims only what it fills: execution_files at
its bound. Filling the three identity arrays too would pin the 400,000
figure, but their schema validation grows quadratically (uniqueItems on
$ref items): with 30,000 identities per array it alone took 6.1 s, and a
test with every array at its bound ran for 85 s, so the test does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: use the changelog MD013 directive sibling PRs add

#1181, #1172 and #1169 add `<!-- markdownlint-disable MD013 -->` right
after the changelog title. Adding the identical hunk here instead of a
disable-file comment at the end of the file means a merge of any two of
these PRs keeps one directive rather than two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(runtime): make the snapshot tests exercise the phases they name

- "publishing an execution snapshot flushes each file once" now counts
  only fsyncs of regular-file descriptors and asserts that count equals
  the published file count (12,552 on the fixture; main flushes 25,104).
  The old total bound also encoded how directories are flushed.
- The leaf-swap test's hook now fires on the write-time fchmod, since the
  seal no longer opens files, so it is renamed after what it checks.
- The parent-swap test's hook fires only on a directory fchmod, so the
  swap lands in the directory seal after every file is written, as it did
  on main. Without that, no test swapped anything while directories were
  being sealed.
- The seal's doc comment now says what publication verification checks
  for each entry type instead of "every entry's type, mode, link count,
  and bytes".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano added a commit that referenced this pull request Sep 29, 2026
…chet complexity with a lint baseline (#1184)

* fix(config): accept smol-toml 1.9 tables when loading project config

smol-toml 1.9.0 (released 2026-09-22) builds every parsed table with
Object.create(null). The config loader treats only objects whose
prototype is Object.prototype as tables, and it sorts
Object.entries(table) with the default comparator, which stringifies each
value. A packed install has no lockfile and resolves the ^1.7.1 range to
1.9.0, so `ultrafuzz init` failed while loading the shipped defaults
("project must be a table; run must be a table; ..."). This is the
packed-install failure that turned the package-gates lane red in main run
36495460966.

Rebuild the parsed document with structuredClone, which returns the same
data as ordinary objects, so the loader sees what it saw with 1.7.
`__proto__` keys stay own data properties.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run every release validation lane on pull requests and never cancel main runs

Pull requests ran only the five runtime lanes. package-gates, cli and
benchmark-history-typecheck (which holds the workspace typecheck) first ran
after merge, so their regressions landed on main: the latest main run,
36495460966, fails package-gates. Every lane now runs on every event. The lane
table keeps its test that it runs each gate scripts/validate-release.mjs
defines exactly once; the pull-request filter, PULL_REQUEST_REQUIRED_GATES and
the --event argument are gone.

The lanes no longer wait for the build and super-linter jobs, and the PR-only
runtime and CLI smoke runners are deleted because the lanes run those suites in
full. release-gates still requires every job. Measured on existing runs: a pull
request took about 60 minutes (8.0 min for build and smoke, then 51.5 min for
the slowest lane in run 36279711018); on the latest main push the new PR lanes
took 12.9 (package-gates), 41.5 (cli) and 9.8 minutes against 26.8-39.5 minutes
for the runtime lanes.

All pushes to main shared one concurrency group; 24 of the last 40 main push
runs were cancelled (as of 2026-09-29). cancel-in-progress: false alone would
not stop that, because GitHub also cancels a pending run in the group when a
newer run queues, and main pushes arrive in bursts (three within 9 seconds on
2026-09-28). Runs other than pull requests now get their own group, keyed by
github.run_id.

Delete the ci.yml line-by-line assertions in packages/modal/test/ci-config.test.ts
and the timeout_minutes === 120 change detector. The remaining guard parses
ci.yml and checks that release validation is not gated off pull requests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the release validation lanes under eatmydata

Every runtime and CLI test that launches a run copies a sealed execution
snapshot and fsyncs each file, twice (at write and again when the snapshot is
sealed). The deep analysis measured 55,998 fsyncs taking 222 s of a 163-261 s
test locally; under eatmydata the same test took 23-33 s, and a CLI test went
from 169 s to 35 s. A CI runner is discarded after the job, so durability
across a crash buys nothing there.

Install eatmydata in the release-validation job and run validate:release
under it; its LD_PRELOAD reaches every child the gate commands spawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: report unused exports without blocking the build

`pnpm -w knip` leaves out knip's exports, types and duplicates categories, so
unused exports accumulate unseen; on this tree knip reports 52 unused exports
and 38 unused exported types. Report them in a continue-on-error step that
reuses the pinned knip script. Several open pull requests delete dead code;
once they land, these categories can move into the blocking knip step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: give vitest packages a 30 s default test timeout

config, topology, prompts, evals, evmbench and modal run a bare `vitest run`
with no config file, so every test gets vitest's 5 s default. Main went red on
it in runs 34967618488 (modal ci-config and modal-documents: "Test timed out in
5000ms") and 34634052856 (modal worker-result), and #1126 raised individual
cases instead. Pass --testTimeout=30000 in the six package test scripts. Cases
that set their own timeout keep it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): remove each test's temporary project when the test ends

cli.test.ts, lifecycle-commands.test.ts and audit-profile-commands.test.ts
created their project with mkdtemp and never removed it. On main, one run-
launching test ("status surfaces a terminal product and live workflow
lifecycle divergence") left 446 MB and 34,091 entries in TMPDIR; the sealed
snapshot directories are mode dr-x, so a plain `rm -rf` cannot delete them.
A CI lane accumulated every test's run directories until the job ended.

temporaryRoot(prefix, t) creates the canonical directory and registers a
t.after hook that restores owner write permission on the way down and
removes it, together with the `<root>-fake-bin` directory the fake engine
fixtures create beside it. The tests pass their context to tempProject.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(lint): enforce complexity and size budgets on all code against a recorded baseline

The complexity and size rules ran only in the diff-limited strict lint, which
reports a message only when its line changed. ESLint reports these rules on a
function's first line (max-lines on the first line past the limit), so adding
branches inside an over-budget function passed, while editing only its
signature failed, forcing an unrelated refactor on a fix.

Move complexity, max-depth, max-lines, max-lines-per-function,
max-nested-callbacks, max-params and max-statements into the always-on config,
with the same relaxed values for tests and templates, and record the 1,003
existing violations (197 files; 701 of them in 108 packages/*/src files) with
ESLint's bulk suppressions in eslint-suppressions.json. `pnpm -w lint` now
fails when a file gains a violation of a rule and when a recorded count is
higher than the file's violations; `pnpm -w lint:prune` lowers the counts.

The diff-limited strict lint keeps the type-aware strict rules, no-console and
the TODO/FIXME check. It passes --pass-on-unpruned-suppressions because it
sees only changed lines, so its counts are below the recorded ones by design.
ESLint writes the suppressions file without a final newline, so prettier
ignores it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: record the CI, lint baseline and smol-toml changes in the changelog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: stop super-linter failing every edit to CHANGELOG.md on line length

Super-Linter lints each changed Markdown file in full with its default
markdownlint rules, including MD013 at 400 characters. Four of the 109
tracked Markdown files have longer lines: CHANGELOG.md keeps each entry on
one line (44 such lines), and docs/reference/agent-adapter-boundaries.md,
docs/explanation/provider-harness-research.md and docs/CODE_OF_CONDUCT.md
have long table rows or paragraphs. Any pull request that touched one of
them failed `External static analysis` on lines it did not change, and no
merged pull request has edited CHANGELOG.md since Super-Linter was added.

Use Super-Linter's default markdownlint rules from the pinned v8.7.0
template, with MD013 off, as .github/linters/.yamllint.yml already does for
YAML line length. markdownlint-cli 0.49.0 (the version Super-Linter runs)
reports 46 MD013 errors on this branch's changed Markdown with the default
template and none with this file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(modal): stop pinning the package test script to an exact string

The smoke-isolation test asserted `scripts.test === "vitest run"`, so adding
--testTimeout=30000 failed package-gates in this pull request's first run. The
property the test protects, that the unit-test script never runs the real
Modal smoke, is still asserted by `not.toContain("smoke")` two lines later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: note the Super-Linter line-length change in the changelog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): check that temporaryRoot removes a sealed tree and its fake-bin sibling

The per-test cleanup was only measured, so a regression would bring the
temporary-directory leak back silently. The new test builds the shape a
launched run leaves (read-only files in dr-x directories) plus the
`<root>-fake-bin` sibling inside a subtest and asserts both are gone once the
subtest ends. It fails if the helper skips restoring owner write permission
(the hook throws EACCES) or skips the sibling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: stale lint suppressions no longer fail lint

New violations still fail because a file's count for a rule rises above the
recorded count. Fixed violations leave a stale count, which lint:prune removes;
failing on them would make every parallel pull request that deletes code
rewrite eslint-suppressions.json and conflict with the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(lint): lint:fix reuses lint's flags, so stale counts no longer fail it

`pnpm -w lint` passes --pass-on-unpruned-suppressions, so a fixed violation
whose count was not pruned is accepted. `lint:fix` repeated the file globs
without that flag, so in exactly that state it applied its fixes and then
exited 2 with "There are suppressions left that do not occur anymore".
Defining it as `pnpm -w lint --fix` keeps the two scripts from drifting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: describe the lint ratchet as it behaves when a fix is not pruned

Since lint passes --pass-on-unpruned-suppressions, a recorded count only goes
down when someone runs `pnpm -w lint:prune`. Until then the room a fixed
violation leaves lets a new violation of the same rule in the same file pass.
contributing.md, the eslint.config.js comment and the changelog said a new
violation always fails, and contributing.md promised periodic pruning on
main that nothing performs. They now state the actual rule: lint fails when
a file has more violations of a rule than its recorded count.

The changelog also claimed every CLI test deletes its temporary project. Only
the tests in cli.test.ts, lifecycle-commands.test.ts and
audit-profile-commands.test.ts do; those include every CLI test that launches
a run, so the entry now says that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: stop the unused-exports report annotating every run as failed

The informational knip step exited 1 whenever it found anything, which is
every run until the dead-code removals land. continue-on-error kept the job
green, but each Build gates check still carried a "Process completed with
exit code 1." failure annotation. --no-exit-code (knip 6.33.0) prints the same
report (52 unused exports, 38 types, 4 duplicates on this tree) and exits 0.
continue-on-error stays so a knip crash in this step still cannot block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: install eatmydata only when the runner lacks it, and scope what it covers

The lane comment and docs/reference/development.md said eatmydata makes fsync
a no-op for the whole lane. It does so in the test processes, which is where
startRun and runCli copy and fsync the sealed snapshot, but
smithersCommandEnv rebuilds each Smithers engine child's environment from an
allowlist that has no LD_PRELOAD, so engine processes still sync.

`sudo apt-get install -y eatmydata` without `apt-get update` only worked
because the current image already ships the package. GitHub's notice on this
PR's Build gates job says ubuntu-latest migrates to Ubuntu 26 from
2026-10-19; on an image without eatmydata and without package lists, that
install would fail every lane. The step now skips apt entirely when eatmydata
is present, as it is today, and otherwise updates the index before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): catch step-level pull-request gating of release validation

The guard parsed ci.yml but checked only the release-validation job's own
`if`. Adding `if: github.event_name != 'pull_request'` to the "Validate release
lane" step, the step that runs the suites, still passed it; the text guard it
replaced caught that. It also pinned the exact `if` string of the release-gates
requirement step, so an equivalent rewrite failed it.

The guard now rejects an event or pull_request condition on the job and on
every one of its steps, and checks that the requirement step tests
`needs.release-validation.result` without an event condition. Mutations run
against the old and new test: a step-level PR `if` passes the old guard and
fails the new one; a job-level PR `if` and an event-gated requirement step
fail both; wrapping the requirement condition in `${{ }}` fails only the old.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(lint): replace the suppression baseline with a global complexity ceiling

#1005 deliberately added no baseline or suppression registry, and #960 asks for
a complexity ceiling in the style of modem-dev/hunk#861: one global maximum set
at today's worst function, with nothing grandfathered, lowered as hotspots are
simplified. Drop eslint-suppressions.json and the always-on size budgets, keep
the diff-limited strict budgets for changed lines, and fail any function above
complexity 90 (synchronizeLinkedWorkflowRun is at 90 once the pending
simplification PRs land). The changelog entry moves to the consolidated
release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt maintained static-analysis libraries and CI gates

1 participant