Skip to content

chore: tooling refresh (type-aware oxlint, lefthook, CI-gated release, dependency bumps) - #153

Merged
BrainerVirus merged 12 commits into
mainfrom
chore/tooling-refresh
Oct 3, 2026
Merged

BrainerVirus merged 12 commits into
mainfrom
chore/tooling-refresh

Conversation

@BrainerVirus

@BrainerVirus BrainerVirus commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

What it does

Slice S7 from docs/workit-next/plan.md: tooling, CI and release refresh. Five focused commits:

  1. build(deps): zod 4.5.4 → 4.6.5, @modelcontextprotocol/sdk 1.30.0 → 1.32.0, @opencode-ai/plugin 1.18.30 → 1.18.34 (the support-matrix current and CI env move with it; the 1.18.30 floor stays), oxlint 1.86.0, oxfmt 0.71.0, knip 6.39.0, @semantic-release/npm 13.2.0 and /github 12.0.10. All pins stay exact.
    • conventional-changelog-conventionalcommits goes to 9.3.1, not 10.x. 10.x needs conventional-changelog-writer@9. @semantic-release/release-notes-generator 14.1.1 (the latest) still ships writer@8, so 10.x fails with a "Missing helper" error at render time. I checked this by rendering notes through the generator: 9.3.1 produces the same output as 8.0.0.
    • schema-depth.test.ts passes on zod 4.6.5 with no changes.
  2. chore(lint): adds .oxlintrc.json and .oxfmtrc.json, which now hold the list of paths to lint and format. The scripts are just oxlint, oxlint --fix, oxfmt and oxfmt --check. This drops the 4× duplicated path list and adds scripts/ and packages/*/scripts, which were not checked before.
    • Rule categories: correctness = error, suspicious and perf = warn, with denyWarnings on.
    • Type-aware linting uses oxlint-tsgolint 7.0.2003. It bundles typescript-go, so it works with TS 7, and the whole repo lints in about 2 s.
    • Most findings are fixed in code:
      • sort/reverse → toSorted/toReversed (tsconfig target is now ES2023)
      • no-shadow renames, including locals that shadowed the path module
      • filter()[0] → find/findLast
      • unbound methods wrapped in arrows
      • unnecessary awaits, assertions and conversions removed
    • The rules turned off each have a reason written in the config. Globally: no-await-in-loop, consistent-function-scoping, no-unsafe-type-assertion (about 1k casts at JSON boundaries; to tighten later), no-base-to-string (tsgolint does not honor checkUnknown yet) and consistent-return. In tests only: await-thenable (bun-types types .resolves/.rejects as void) and unbound-method.
  3. chore(hooks): lefthook, opt-in with bun run hooks:install.
    • pre-commit runs oxfmt and oxlint on staged files in parallel: about 0.8 s.
    • commit-msg runs commitlint with the conventional config: about 0.5 s.
    • no_auto_install: true is set because hooks are shared by every worktree of a clone.
  4. chore(ci): react-doctor is now a pinned devDependency (0.9.14). The two obsolete probe workflows are deleted (details under CI notes).
  5. chore(ci): CI restructure and a release that waits for CI (see below).

CI and release

  • fast checks: one install, then lint, format, knip, typecheck, react-doctor, actionlint and zizmor run in parallel inside one step. Each gate prints its own log section with its exit code and duration.
  • test (ubuntu-latest): one build, then a plain bun test. Every test directory runs. I confirmed that test/workit-codex, test/workit-mcp, test/opencode-v2 and test/acceptance were not in any CI job before.
  • test (macos-latest) / test (windows-latest): test/workit-core and test/artifacts, same as before.
  • Before → after:
    • typecheck: 6 jobs → 1
    • builds: 5 → 3 (one per OS)
    • jobs: 12 → 4, plus the release job
    • The candidate job is gone: it repeated phase-0-candidate, which test/artifacts already runs.
    • The artifacts job's ref: head_ref checkout is no longer needed: the phase-9 test already handles a detached HEAD.
  • Hardening:
    • Actions are pinned to commit SHAs.
    • persist-credentials: false everywhere.
    • permissions: {} at the top level, with only what each job needs added per job.
    • PR runs cancel superseded runs. Runs on main are grouped per commit and never cancelled.
  • Release (release.yml):
    • It is now a reusable workflow, called from the release job in ci.yml. That job only runs after fast, test and portability pass for the same commit on main.
    • Secrets are passed by name, not with inherit.
    • The job has a concurrency group with cancel-in-progress: false, so two merges cannot race on tags.
    • It runs bun run release, which uses the pinned semantic-release devDependency instead of an unpinned npx download.
    • npm provenance: id-token: write plus NPM_CONFIG_PROVENANCE=true. npm publish still authenticates with NPM_TOKEN; provenance only adds the OIDC attestation. The repo is public and every package's repository.url matches it.

Measured (local, Node 24.20.0, bun 1.4.1)

Gate Time
fast job gates, run in parallel (same script as CI) 4.6 s wall (lint 4.6 s, typecheck 4.6 s, react-doctor 4.1 s, knip 3.7 s, zizmor 0.5 s, format 0.3 s, actionlint 0.06 s)
oxlint with types, whole repo, run alone ~2.3 s
lefthook pre-commit / commit-msg 0.8 s / 0.5 s
full bun test 1570 pass / 2 fail, 161 s

The 2 local failures are environmental:

  • AR-14: the inner bun test picks up the sibling worktree ../s0-docs, which has no node_modules. This also fails on main in this layout.
  • Pi stock-supervisor smoke: a metadata.lock timeout under full-suite load. It passes when its file runs alone (3/3). This is the lock contention that S1 fixes.

Also: actionlint reports nothing, and zizmor 1.30.1 (--offline) reports no findings. One finding is ignored on purpose: zizmor suggests the $/ self-repository syntax, which actionlint 1.7.12 rejects. The ignore comment explains this.

CI notes / action needed

  • ⚠️ Branch protection still requires the old job names (check (workit-core) (…), check (workit-opencode), check (workit-cursor), check (workit-cli), check (shared)). Those jobs no longer exist, so this PR and the automated manifest-sync PRs will wait on checks that never report. Before merging, change the required checks to fast checks, test (ubuntu-latest), test (macos-latest), test (windows-latest). I did not change repository settings.
  • The old react-doctor gate did nothing. Since 0.9.x, npx react-doctor@latest prints "React Doctor is not installed in this project" and exits 0. As a pinned devDependency it now runs the real scan. It currently reports warnings only, no errors.
  • bun run check (not plain bun test) puts node_modules/.bin first on PATH. That bin folder contains the npm that @semantic-release/npm pulls in, so 2 packed-CLI tests fail to resolve the npm CLI when run that way. This already happens on main. CI calls bun test directly, so it is not affected. Follow-up for the test helpers.
  • Deleted sync-token-probe.yml: its own note said to delete it once the token was stable. The last probe passed on 2026-08-28, and RELEASE_SYNC_TOKEN opened manifest-sync PR chore(release): sync manifests to v2.1.0 #147.
  • Deleted windows-inner-suite-probe.yml: it runs handoff.test.ts and sdd.test.ts, which no longer exist.

Review follow-ups (3 more commits)

  • Restored strict gates the autofix had loosened. no-unnecessary-boolean-literal-compare changed confirmed !== true checks into plain truthiness checks in three places: requireConfirmed (OpenCode V2 workit_init_apply), migrateLegacyDocs, and YouTrack postUpdate. After that change, confirmed: "false", "no" or 1 would pass.
    • All 46 comparisons and coercions the autofix rewrote are back to how they are on main. That covers === true/!== true/=== false/!== false, String()/Boolean()/${}, and hostingApiHostMatches returning Boolean(...) again.
    • The no-map-spread suggestion had replaced spreads with Object.assign(...), which mutates the original objects (task-engine importedTask plus three tests). Those are restored to spreads.
    • Those four rules are now off, with the reason written in .oxlintrc.json.
    • New regression tests check that the three gates reject "false", "no", "true", 1 and {}. All three tests fail against the loosened code and pass with the fix.
  • CI: validate:cursor-marketplace now runs before any build, on a clean checkout (CA-21). The test job now also runs verify:release-candidate (about 1.5 s).
  • lefthook postinstall: the npm package's postinstall runs lefthook install, which ignores no_auto_install. It cannot run on dependency install anymore:
    • bun does not trust it (blocks it by default);
    • pnpm skips it via pnpm.neverBuiltDependencies and yarn via dependenciesMeta.built: false;
    • npm cannot install this workspace at all (verified: EUNSUPPORTEDPROTOCOL on workspace:*).
    • A project-config test locks this in.
  • Measured after the follow-ups (local, Node 24.20.0):
    • fast gates: 5.7 s wall in parallel; every gate exits 0;
    • full bun test: 1575 pass / 1 fail. The failure is AR-14, the environmental one caused by the sibling worktree, described above;
    • marketplace validation and the release-candidate check both pass;
    • commitlint passes for all commits.

Quality gates

  • Tests, typecheck, lint, formatting and isolated release-candidate checks pass locally (Node 24). The exceptions are the environmental failures listed above.
  • CONTRIBUTING describes the new checks, hooks and release flow.
  • No host behavior changes. The source edits are lint fixes that keep behavior the same.

Checklist

  • Existing scoped settings and local/version pins are preserved. The OpenCode floor stays 1.18.30. Pi, ink and @openclaw/fs-safe are not bumped.
  • No user-facing behavior change. semantic-release will cut a patch because product paths changed (dependency bumps).

🤖 Generated with Claude Code

BrainerVirus and others added 12 commits October 3, 2026 14:50
…ase plugins

- zod 4.5.4 -> 4.6.5 (core, mcp)
- @modelcontextprotocol/sdk 1.30.0 -> 1.32.0
- @opencode-ai/plugin 1.18.30 -> 1.18.34 (support-matrix current + CI env;
  the 1.18.30 floor is unchanged)
- oxlint 1.81.0 -> 1.86.0, oxfmt 0.66.0 -> 0.71.0, knip 6.35.1 -> 6.39.0
- @semantic-release/npm 13.2.0, @semantic-release/github 12.0.10
- conventional-changelog-conventionalcommits 8.0.0 -> 9.3.1: 10.x needs
  conventional-changelog-writer@9, but @semantic-release/release-notes-generator
  14.1.1 (latest) still ships writer@8 and fails at render time; 9.3.1 renders
  identical notes.

knip 6.39 now reports the `npm exec -c workit-cursor-session-start` call in
the packed-runtime test as an unlisted binary; it is the workit-cursor bin
installed into a temp project, so it is ignored like glab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aware) and oxfmt

- Add .oxlintrc.json: correctness=error, suspicious+perf=warn with
  denyWarnings, explicit eslint/typescript/unicorn/oxc plugins, and
  type-aware rules through oxlint-tsgolint 7.0.2003 (bundles typescript-go,
  works with TS 7; whole repo lints in ~2 s).
- Add .oxfmtrc.json. Lint and format now walk the repo root and skip
  gitignored paths plus ignorePatterns, so package.json no longer repeats the
  path list four times, and previously unchecked scripts/ and
  packages/*/scripts are covered.
- Fix findings instead of silencing them: sort/reverse -> toSorted/
  toReversed (tsconfig target ES2023), no-shadow renames (e.g. locals that
  shadowed the `path` module), filter()[0] -> find/findLast, unbound
  runtime methods wrapped, needless awaits/assertions/conversions removed,
  `${array}` -> join(",").
- Rules turned off carry a justification in the config: no-await-in-loop
  (ordered subprocess/lock steps), consistent-function-scoping (style),
  no-unsafe-type-assertion (~1k JSON-boundary casts, ratchet later),
  no-base-to-string (tsgolint ignores checkUnknown), consistent-return (tsc
  already enforces it); in tests, await-thenable (bun-types type
  `.resolves/.rejects` as void) and unbound-method (monkeypatch restore).

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

pre-commit formats (oxfmt, re-staged) and lints (oxlint, type-aware) only
the staged files in parallel: ~0.8 s measured. commit-msg runs commitlint
with the conventional config (~0.5 s), since semantic-release and
analyze-release-scope read Conventional Commits. Install is opt-in via
`bun run hooks:install` and `no_auto_install` is set, because git hooks are
shared by every worktree of a clone.

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

`npx -y react-doctor@latest` resolved a new version on every run, and since
0.9.x it prints "React Doctor is not installed in this project" and exits 0
when react-doctor is not a dependency, so the CI gate scanned nothing. As a
pinned devDependency it runs the real scan (~2 s; warnings only, no errors).

sync-token-probe.yml was a dispatch-only check to delete "once the token is
stable"; the last probe passed on 2026-08-28 and the v2.1.0 manifest-sync PR
(#147) was opened with RELEASE_SYNC_TOKEN. windows-inner-suite-probe.yml runs
test/workit-core/handoff.test.ts and sdd.test.ts, which no longer exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, CI-gated release

CI:
- `fast` job: one install, then lint (type-aware), format, knip, typecheck,
  react-doctor, actionlint and zizmor run concurrently (~5 s locally).
  Typecheck ran in 6 jobs and lint/format hid in a job named `shared`.
- `test` job runs a plain `bun test` on Linux after one build, so
  test/workit-codex, test/workit-mcp, test/opencode-v2 and test/acceptance
  (never run in CI before) are covered, and new directories cannot escape.
- `portability`: core + artifacts on macOS/Windows with one build each. The
  separate candidate job duplicated test/artifacts/phase-0-candidate.
- Builds drop from 5 to 3 (one per OS); typecheck from 6 to 1.
- Actions pinned to commit SHAs, persist-credentials: false, permissions
  default to none per job; actionlint and zizmor (offline) are clean.
- PR runs cancel superseded runs; main runs are grouped per commit.

Release:
- release.yml is now a reusable workflow called by the `release` job in
  ci.yml with needs: [fast, test, portability], so a commit only publishes
  after its own CI passed. Secrets are passed explicitly (no inherit).
- concurrency group with cancel-in-progress: false, so two merges cannot
  race on tags.
- semantic-release runs from the pinned devDependency (`bun run release`)
  instead of an unpinned `npx`.
- npm provenance: id-token: write plus NPM_CONFIG_PROVENANCE=true; npm still
  authenticates with NPM_TOKEN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loosened by autofix

The type-aware autofixes trusted declared types at untyped JSON/env/host
boundaries:
- no-unnecessary-boolean-literal-compare rewrote `confirmed !== true` to
  truthiness in requireConfirmed (OpenCode init apply), migrateLegacyDocs and
  YouTrack postUpdate, so `confirmed: "false"`, "no" or 1 passed the gate.
- no-unnecessary-type-conversion / -template-expression dropped String(),
  Boolean() and `${}` coercions (hostingApiHostMatches returned undefined
  instead of false).
- the no-map-spread suggestion turned `({ ...entry, ... })` inside map()
  into Object.assign(entry, ...), mutating the source decisions/fixtures.

Every rewritten `=== true`/`!== true`/`=== false`/`!== false` comparison and
coercion is restored as it was on main (46 lines plus vcs-config and the four
Object.assign sites). The four rules are turned off with the reason in
.oxlintrc.json. Regression tests: confirmation gates reject "false", "no",
"true", 1 and {} (all three fail against the loosened code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he release candidate pre-merge

The CA-21 marketplace check must run on a clean checkout, so it now runs
before `bun run build`. The test job also runs `verify:release-candidate`
(the same pack-only gate release.yml runs before publishing, ~1.5 s
locally), so a broken candidate fails the PR instead of the release.

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

The npm package's postinstall runs `lefthook install`, which ignores
no_auto_install and would write into the .git/hooks shared by every
worktree. bun already blocks it (lefthook is not in trustedDependencies);
pnpm (`pnpm.neverBuiltDependencies`) and yarn (`dependenciesMeta.built:
false`) are now opted out explicitly, and npm cannot install this workspace
at all (EUNSUPPORTEDPROTOCOL on workspace:*, verified). A project-config test
keeps lefthook untrusted and hooks opt-in via `bun run hooks:install`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two coercions dropped by no-unnecessary-type-conversion were missed by the
earlier restore (multi-line on main): config locale validation and the
YouTrack logTime date argument now match main exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved keeping both intents:
- package.json: config-driven lint/format scripts and pinned tooling from
  this branch; #152's knip reachability step and #155's test tiers
  (`test` = unit, `test:packaging`) and `check` from main; ink devDependency
  from main.
- ci.yml: the new fast/test/portability layout; the Linux test job runs
  both tiers, which covers #155's `remaining` job (codex, mcp, opencode-v2,
  acceptance) and every other directory.
- Source/test files main rewrote or deleted (#149, #151, #152, #155): main's
  version taken; lint fixes are re-applied in a follow-up commit.
- bun.lock regenerated with bun 1.4.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Safe autofixes (toSorted, unnecessary assertions) plus manual no-shadow
renames and typed sort keys on code that arrived with #149/#151/#152/#155.
The boolean-compare, type-conversion, template-expression and map-spread
rules stay off, so no strict comparison or coercion was rewritten (checked:
no removed `=== true`/`!== true`/`=== false`/`!== false`, String(),
Boolean() or new Object.assign in the diff). The confirmation-gate test drops
its migrateLegacyDocs case: #152 deleted docs-migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BrainerVirus
BrainerVirus merged commit cf05119 into main Oct 3, 2026
5 checks passed
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.1.5 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant