Repository navigation
Description
Relocated from dezren39/darwin-nix#141 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).
Parent: #95
Summary
Put every formatter (check mode) and every linter (at warning severity or stricter, not the minimal preset) for mcpx into the test matrix, with tool versions pinned by the flake so nix fmt / the dev shell locally gives the same answer as CI. Also run the one TypeScript test file CI has never run (plugin/opencode/mcpx/daemon.test.ts). The repo already has most of the plumbing -- treefmt.nix, a flake formatter, checks.formatting, checks.tidy -- but none of it covers Go, TypeScript or shell, and none of it runs in CI.
Original notes
- testing
- make a comprehensive github action ci.yaml workflow (or test.yaml) that is as fast as possible,
- all formatters and linters in test matrix, formatters in check mode, linters set to warning or more than minimal.
Current state
Existing plumbing (repo root, main d0b7e01):
treefmt.nix:programs.nixfmtandprograms.rustfmtenabled;# programs.shellcheck.enable = true; # failures.projectRootFile = "flake.nix", so it formats the whole repo.flake.nix:422-429:formatter= the treefmt wrapper (nix fmt);checks.formatting= treefmt's check derivation overinputs.self;checks.tidy=flake-tidy all --check.justfilefmt:nixfmt *.nix pkgs/*.nixthennix run .#flake-tidy -- all || true-- the glob does not reachpkgs/mcpx/package.nix, and the|| trueswallows tidy failures.tidy-check:nix run .#flake-tidy -- all --check. Sojust fmtandnix fmtdisagree aboutpackage.nix, and a CI check-mode formatter would fail on a file the documented local command cannot fix. Repair (from Phase 2: keep_prior revives earlier grants for a re-added member #169, see the comment below):just fmtcallsnix fmt, which also picks up every program later added totreefmt.nixwithout a second edit.pkgs/mcpx/docs/release-runbook.md:158-168"Checks" listsgofmt -l .,go vet ./...,go test ./... -count=1,scripts/stress.sh,scripts/bench.sh,nix build .#mcpx, and "README.mdmust stay a symlink todocs/story.md" (test -L README.md). None of these exceptgo testandnix buildrun in CI.go testalready runs a subset of vet (atomic, bool, buildtags, directive, errorsas, ifaceassert, nilfunc, printf, stringintconv, tests;go help test). Fullgo vetadds copylocks, loopclosure, lostcancel, unusedresult and others.- No
.golangci.yml, nodeno.json, no.shellcheckrc, no.editorconfiginpkgs/mcpx. plugin/opencode/mcpx/daemon.test.ts: 21 tests at 05c78b2 (Phase 5: tag event bodies must travel with the event #182 added two) over the plugin's daemon discovery ladder, written forbun test(import ... from "bun:test",Bun.serve({unix}); the header saysbun test plugin/opencode/mcpx/daemon.test.ts). Still never run by CI:.github/workflows/has nobun testordeno teststep. Plugin direction: build(deps): bump actions/checkout from 3 to 4 #47 (opencode v2), Update Rust crate anyhow to 1.0.75 #48 (harness-free core).
Baseline, run read-only on d0b7e01 with the tools installed on a dev machine (not the flake's pinned versions):
| Check | Result |
|---|---|
gofmt -l . |
clean |
deno fmt --check plugin (deno 2.9.6, defaults) |
9 files differ: 4 TS/TSX (daemon.ts, daemon.test.ts, mcpx-session.ts, mcpx-tui.tsx) + 5 Markdown (README.md, four SKILL.md). The plugin is written in opencode's style (no semicolons); deno's default adds them. With --no-semicolons --line-width=120 all 9 still differ. |
deno lint plugin |
13 problems in 4 files: no-explicit-any x6, require-await x5, no-unreachable x1, no-unused-vars x1 (summarise) |
shellcheck scripts/*.sh |
3: SC2034 and SC2319 (warnings, stress.sh:13,87), SC2012 (info, bench.sh:60) |
shfmt -l scripts/*.sh (defaults) |
both files differ |
nixfmt --check pkgs/mcpx/package.nix |
"not formatted" (system nixfmt; the flake's may differ) |
| golangci-lint, staticcheck, gofumpt, actionlint | not installed; unknown |
So turning any of this on needs one clean-up commit first (or a baseline), and a style decision for TypeScript.
The tool set
| Area | Formatter (check mode) | Linter (>= warning) | Notes |
|---|---|---|---|
| Go | gofmt -l (via treefmt programs.gofmt) |
go vet ./... + golangci-lint v2 |
gofumpt: stricter, reformats; opt-in, not needed |
| TypeScript (plugin, docs examples) | deno fmt --check with a deno.json matching the existing style |
deno lint (recommended rules) + deno check |
Deno is already a CI runtime; prettier/eslint would need node_modules |
Shell (scripts/*.sh) |
shfmt -d (pick -i to match the files) |
shellcheck at default severity (style, the strictest) |
the treefmt comment says shellcheck "failures" -- for the whole repo; scope to mcpx first |
Nix (pkgs/mcpx/package.nix, flake) |
nixfmt (already in treefmt) |
statix, deadnix (optional) |
|
| Workflows | -- | actionlint (also shellchecks run: blocks when shellcheck is on PATH) |
covers mcpx-test.yml, format.yaml; zizmor is security -> deferred |
| Flake | -- | flake-tidy all --check (exists: checks.tidy, just tidy-check) |
repo-wide; run when flake.nix/flake.lock change |
| Repo invariants | -- | test -L pkgs/mcpx/README.md |
from the release runbook; one line |
golangci-lint configuration, "warning or more than minimal": v2 with linters.default: standard (errcheck, govet, ineffassign, staticcheck, unused) plus a curated set that fits this codebase -- e.g. errorlint, bodyclose, noctx, misspell, unconvert, usestdlibvars, gocritic (diagnostic tags), revive (a few rules). From #169: staticcheck with checks: ["all"] rather than its default subset, and copyloopvar (Go 1.22 made the rt := rt idiom redundant; internal/e2e/runtime_test.go:15,47 still carry it). Not gosec in the blocking set: security is deferred, and on a process-spawning CLI it yields a large low-signal backlog. Not default: all: it enables style linters that contradict each other and would turn the first PR into a rewrite. Commit pkgs/mcpx/.golangci.yml; new linters are added one PR at a time with their fixes.
TypeScript style decision: either adopt deno's defaults and reformat the plugin once, or write a deno.json fmt block that matches the current opencode style. The plugin is loaded by opencode and #47/#48 may move or reshape it -- coordinate there before a mass reformat; lint can start immediately since it is independent of style.
Markdown: deno fmt also formats .md. The book is the docs tree's; exclude docs/** and AGENTS.md until the docs tree decides its own formatting.
Approaches -- how tools are installed and pinned
A -- marketplace actions per tool (least recommended)
golangci/golangci-lint-action, reviewdog/action-shellcheck, raven-actions/actionlint, each with its own version input. Fast to write, and golangci-lint-action caches well. But every tool's version lives in YAML, local runs use whatever is installed (the baseline above already used unpinned local tools), and Renovate bumps them independently of the flake. This is how "passes locally, fails in CI" starts.
B -- Go tools via the tool directive, the rest via actions
Go 1.24+ tool directives in go.mod pin Go tools and go tool golangci-lint run works locally and in CI. But it adds golangci-lint's large dependency graph to pkgs/mcpx/go.mod/go.sum, which changes the Nix vendorHash and Renovate's grouped Go bumps get much bigger (the vendorHash repair exists precisely because those bumps are painful). Pins Go tools only; shell/Nix/TS tools stay unpinned.
C -- the flake pins everything; treefmt for formatters, a dev shell for linters (recommended)
- Formatters: extend
treefmt.nixwithprograms.gofmt,programs.deno(fmt),programs.shfmt, keepingnixfmt; scope the new ones topkgs/mcpx/**with per-programincludesso the rest of the repo is not reformatted by this issue. treefmt-nix ships modules for gofmt, gofumpt, goimports, golangci-lint, deno, prettier, biome, shfmt, shellcheck, actionlint, nixfmt, statix, deadnix (seen in a local copy of the input; confirm against the pinned rev). CI runsnix fmt -- --ci(or buildschecks.x86_64-linux.formatting) -- check mode, non-zero on any diff, prints the diff. Locallynix fmtgives byte-identical results because it is the same derivation. - Linters: the
devShells.mcpxthat Update Rust crate rand to v0.9.3 [SECURITY] #134 proposes (go, gopls, deno, bun, node, git, python3, golangci-lint), extended withshellcheck,actionlint,statix,deadnix-- one shell, pinned byflake.lock; CI runsnix develop .#mcpx -c <linter>; locally the same command. Do not run golangci-lint through treefmt: its treefmt module runsgolangci-lint run --fixper file, which needs module downloads (not available in thecheckssandbox) and rewrites code. - Costs: the Nix install (~8 s) plus substitution per lint job; competitive only with a store cache (Update Rust crate tokio to v1.38.2 [SECURITY] #135 decision 7). One
flake.lockbump updates every tool at once -- that is the point, and also a risk (a formatter upgrade reformats; ⚡ Bolt: Optimize search match counting performance #142 handles that).
Recommended because it is the only option where local == CI by construction, it reuses what the repo already has, and it keeps Go tool dependencies out of mcpx's go.mod.
Matrix layout
Three lint legs, each small, plus a plugin test leg:
| Leg | Runs | Expected |
|---|---|---|
fmt |
nix fmt -- --ci (all formatters, scoped) |
seconds after Nix setup |
lint-go |
go vet ./..., golangci-lint run (with Go build cache from #135) |
the slowest lint leg; type-checks the module |
lint-other |
deno lint, deno check, shellcheck, actionlint, statix/deadnix, README symlink, flake-tidy --check when the flake changed |
seconds |
plugin |
bun test plugin/opencode/mcpx/daemon.test.ts (needs Bun only; pin the flake's Bun) |
seconds |
If queue pressure shows up (#140), fmt and lint-other merge first. Run plugin under Deno and Node too only if #48 makes the core runtime-neutral (today the test imports bun:test and Bun.serve, so it is Bun-only). scripts/stress.sh and scripts/bench.sh stay manual (they use real servers from .mcpx.json); #97 decides whether a stress leg exists.
Implementation order (small PRs)
.golangci.ymlwithdefault: standard,go vet, gofmt check, shellcheck/actionlint -- fix what they find (small: gofmt is clean, shellcheck has 3).pluginleg:bun test(no reformat needed).deno lint(13 fixes) anddeno check.- TS/shell/Nix formatting once the style is agreed: one reformat commit per language, then the check turns on. Add the reformat commits to
.git-blame-ignore-revs. - Curated extra golangci linters, one per PR.
Alternative sequencing (from #169), for any linter whose backlog is too big for one fix-up PR: land it continue-on-error: true with ::warning:: annotations, fix the backlog in a dedicated PR, then flip it to blocking. Formatters block from day one either way -- a formatter has no judgement calls. Whichever path, the end state must be "clean and enforced", never another # ... # failures line like treefmt.nix:6.
Open questions
- TypeScript style: deno defaults vs matching opencode's (no semicolons, width 120)? Depends on build(deps): bump actions/checkout from 3 to 4 #47/Update Rust crate anyhow to 1.0.75 #48 plans for the plugin.
- shfmt flags for
scripts/*.sh(indent width) -- match the existing files. - Scope: mcpx only, or turn the whole-repo
checks.formattinginto a CI job too (build(deps): bump build/buck/preludes/buck2-prelude fromd956689to3cfc766#49's territory)? - Is
nix flake checkusable as a CI step for mcpx, or does it evaluate/build too much of the repo (opencode patches, darwin-only packages)? Prefer building the singlechecks.x86_64-linux.formattingattribute. deno checkof the plugin needs opencode's plugin types -- available offline, or does it need Update Rust crate anyhow to 1.0.75 #48 first?- Does
checks.formattingpass onmaintoday? Nothing runs it; the baseline above already foundpkgs/mcpx/package.nixunformatted by a system nixfmt. If it is red, step one is the backlog, and its size is unknown (from Phase 2: keep_prior revives earlier grants for a re-added member #169).
Ease · Impact · Related
Ease M (mostly clean-up and one style decision) · Impact med · Depends on / related: #95, #135 (legs, Nix cache, tool-version decision), #142 (auto-applies the same formatters), #140 (merge legs under queue pressure), #134 (the shared mcpx dev shell), #47, #48 (plugin shape and its tests), #49 (repo-wide format/tidy checks), #99 (which of these agents run locally: formatters yes, full lint maybe), #35 (version drift), #129 (its settings-effect guard needs this issue's plugin leg to cover plugin-scoped settings, which only the plugin's TypeScript reads).
Security/auth deferred; see tracker.
Acceptance criteria
- Every formatter/linter named in the body's CI matrix runs in CI pinned by the flake, the plugin's bun test runs, and the workflow is green.
- The work described in the Summary/Scope above is delivered via a branch + PR (no direct push to main); verification runs in GitHub Actions CI, not on the sandbox.
- Evidence (CI links, timings, or probe output as applicable) is recorded on this issue before it is closed.
Activity
developing-today commentedon Oct 28, 2026
CODE-921
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#141 (verbatim, oldest first):
@dezren39 · 2026-09-30T06:50:28Z
Salvaged from a duplicate issue (#169, now closed) -- one fact it had that this issue does not.
just fmtandnix fmtdisagree aboutpkgs/mcpx/package.nix.justfile:120-122:pkgs/*.nixis one level deep.pkgs/mcpx/package.nixis two, so thejust fmta contributor runs locally never touches it, whiletreefmt.nix(projectRootFile = "flake.nix",programs.nixfmt.enable = true) walks the tree andnix fmtdoes. A CI check-mode formatter job will therefore fail on a file that the documented local format command cannot fix, and the contributor's only recourse is to know to runnix fmtinstead.Whichever this issue lands on, the two must be made the same command.
just fmtcallingnix fmtis the obvious repair, and it also picks upprograms.rustfmtand anything added totreefmt.nixlater without a second edit.Note also that
nix run .#flake-tidy -- all || trueswallows its own failure -- the same shape as the commented-out# programs.shellcheck.enable = true; # failuresontreefmt.nix:6that this issue already cites.@dezren39 · 2026-09-30T06:51:37Z
Overlaps #169, written concurrently by a second worker on the same slice. Both are kept until reconciled; see the tracker (#95) for the differences and which one to keep.
@dezren39 · 2026-09-30T07:21:00Z
Integration pass. Placed in the DAG rooted at #181.
d956689toe7457f6#52 blocks sharding@dezren39 · 2026-09-30T12:03:17Z
Issues pass (2026-09-30, at 05c78b2).
bun test(or any plugin) step;.github/workflows/holds onlymcpx.ymlandopencode.yml.daemon.test.tsnow has 21 tests (Phase 5: tag event bodies must travel with the event #182 added two), not 19.just fmtmissespkgs/mcpx/package.nixand should callnix fmt.staticcheck checks: all,copyloopvar(rt := rtstill atinternal/e2e/runtime_test.go:15,47),gosecnot blocking, the warn-then-flip sequencing for large backlogs, and "doeschecks.formattingpass on main today?". Did not carry Phase 2: keep_prior revives earlier grants for a re-added member #169'sdeno testsuggestion -- the file importsbun:test.@dezren39 · 2026-09-30T12:09:47Z
Issues pass (2026-09-30, architect follow-up). Related adds #129: its settings-effect guard (#181 collision 15) cannot cover plugin-scoped settings without this issue's
plugin(bun test) leg.dezren39 commentedon Oct 28, 2026
Intake review
🤖 Agent note · state: filed
73 remaining items
dezren39 commentedon Oct 30, 2026
🤖 Agent note · parked
pkgs/mcpx/internal/testsupport/quarantine_test.go— pre-existing on main, surfaced by the new leg); merged origin/main (152 commits, clean), gofmt-fixed the file (777620d), then diagnosed the next lint failure as repo-wide actionlint findings and scoped the leg's actionlint to the mcpx workflow set atshellcheck -S warningper mcpx testing: every formatter and linter in the CI matrix, pinned by the flake (and run the plugin's bun test) #1996's own coverage line (f8653b4). Repo-wide findings filed as actionlint: repo-wide findings outside the mcpx workflow set (jules concurrency.queue, SC2129, SC2016/SC2086 in run blocks) #2845 (same treatment as golangci -> mcpx: fix the 119 golangci findings the new lint leg surfaced, then make the leg blocking #2836).basic: lintPASS (2m50s, run 41136217447),basic: plugin testsPASS;stress: PR changed testsstill pending at park time. Codex threads on the PR: 0 unresolved at last check (re-verify on resume — new head may draw a fresh round).mcpx: CI lint leg (vet, golangci-lint, gofmt, shellcheck, actionlint)…
dezren39 commentedon Oct 30, 2026
Agent note — done: PR #2718 merged (queue task 3291)
Lane B shift resumed from the
implementation_completecheckpoint and re-verified fresh:f8653b45(unchanged from checkpoint). CI run 41136217447: lint PASS, plugin tests PASS, stress (PR changed tests) PASS, budget checks pass.MERGEABLE/CLEAN. Squash-merged: merge commit00050726695e635449c109cf9ba16266a6f696b8. Issue mcpx testing: every formatter and linter in the CI matrix, pinned by the flake (and run the plugin's bun test) #1996 closed as COMPLETED by the merge (repo-wide actionlint remainder already filed separately as actionlint: repo-wide findings outside the mcpx workflow set (jules concurrency.queue, SC2129, SC2016/SC2086 in run blocks) #2845).