Brings the project to a clean `make lint` baseline (0 issues).
Mechanical:
- Wrap deferred resp.Body.Close() in closures (router/discovery.go,
router/probe.go) so the unchecked return surfaces as `_ = ...`.
- Apply `_ = ...` (single or multi-return blank) to test-file calls
that intentionally ignore errors: os.MkdirAll / os.WriteFile / os.Chdir
in setup paths, Close / Shutdown in teardown, Submit / Spawn / Send /
LoadDir in tests that assert on side effects.
Structural:
- engine.handleRequestTooLarge drops the unused req parameter and
rebuilds the request from compacted history (SA4009 — argument was
overwritten before first use).
- provider.ClassifyHTTPStatus and google.applyCapabilityOverrides switch
to tagged switches over the discriminator (QF1002).
- tui.app.go MouseWheel + inputMode and cmd/gnoma main slm-status use
tagged switches in place of equality chains (QF1003).
- cmd/gnoma main.go merges a var decl with its immediate assignment
(S1021).
- Three empty-branch sites (dispatcher_test, loader_test,
coordinator_test) become real assertions or get the dead `if` removed
(SA9003).
Removes five unused funcs/vars/fields that golangci-lint had been
flagging (anthropic.toolCallDoneEvent, mistral.translateMessages,
hook.newError, subprocess.vibeParser.lastAssistantMsgID, tui.cBase),
two ineffectual assignments (tui/rendering.go visible-window loop,
subprocess stream_test setup), and a stale if/HasPrefix that's now a
strings.TrimPrefix.
Wires errcheck onto every subprocess / stream lifecycle path so a
failed close or shutdown is at least logged rather than silently
dropped:
- engine/loop.go: stream.Close on both the error and success paths
- mcp/manager.go: Shutdown when StartAll partial-fails; Transport
close after Initialize failure
- mcp/transport.go: stdin.Close + syscall.Kill on graceful-timeout
fallback
- slm/download.go: Close propagated as a named-return error on the
success path; explicitly discarded on the rollback path
- slm/classifier.go, slm/manager.go, hook/prompt.go, context/summarize.go,
config/write.go, cmd/gnoma/main.go, tool/fs/grep.go: explicit
ignores or error logging on Close / Shutdown / WalkDir / Scanln
Production-code errcheck and ineffassign are now zero. Remaining
golangci-lint output is test-only Close-in-defer noise plus
stylistic staticcheck QF suggestions, left alone.
- M2: stop echoing the matched pattern name in the user-visible
[BLOCKED: ...] message returned by the firewall. The pattern (and
the matched secret class) still appear in the operator log, but the
string sent back into the prompt is now generic.
- H1: document Rule.Pattern semantics on the Rule type and pin them
with a regression test. Pattern is a case-sensitive, exact substring
match against the JSON-serialised tool arguments — not a glob,
regex, or whitespace-insensitive match. The new test exercises both
matches and the documented gotchas (double-space, case drift, tab).
- H3: every code path in CommandExecutor.Execute that converts a hook
failure into Allow via FailOpen now emits a WARN naming the hook
and the failure mode (timeout / launch_error / parse_error), so
chronic hook failure or abuse is visible in operator logs.
Also tightens errcheck on permission/rule.go (Printer.Print on a
strings.Builder cannot error in practice; make the intent explicit).
Plugin loader resolves HookSpec.Exec as a relative path joined to the
plugin directory, and manifest.checkSafePath rejects absolute paths and
'..' traversal — Exec was always meant to be an executable path.
The hook executor was wrapping it in 'sh -c', adding a redundant shell
interpretation step that turned any space, quote, or metacharacter in
the path into command-injection surface. Switch to exec.Command(path)
with no shell wrapping.
Closes audit finding C3. Adds a regression test that fails under the
old 'sh -c' code path: a canary file created via shell sequencing
remains absent when the executor treats Exec as a literal filename.
Hook command tests now write small /bin/sh scripts to t.TempDir and
point Exec at those — matching production semantics (resolved binary
path) rather than inline shell strings.