Commit Graph
5 Commits
Author SHA1 Message Date
YHH 4fbbca6a99 refactor(go): adopt modern stdlib idioms and gate them (#7831)
* refactor(go): adopt modern stdlib idioms across kernel and desktop

Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/
usestdlibvars fixers plus hand-fixes for what they could not rewrite:
slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor,
maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int.
Net -549 lines with no behavior change.

Three loops in session.go stay index-based: slices.Backward yields element
copies, so the fixer's rewrite silently dropped writes to DecisionReceipts
and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49
slices.Backward sites were audited for that shape; only these three write
through the index, and they carry a nolint with the reason.

CaptureAfter's scan loop had an empty body and never ran any logic, so the
fixer could not express it. Drop it and the accessor it was the only caller
of. omitzero is deliberately untouched: turning omitempty into omitzero
changes the JSON written for nested structs, which is a wire-format change
to session files and the extension protocol.

* build(lint): gate the modern-idiom linters

Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both
modules are now clean for modernize, intrange, copyloopvar, and
usestdlibvars, so enable them rather than leaving the sweep as a one-off.

omitzero stays disabled with the reason inline; usetesting, perfsprint, and
errorlint still have residue and land separately.
2026-08-07 15:30:33 +08:00
SivanCola 2405a7c108 fix(shell): keep combined output ordered and stop blocking && chains
Problem
-------
Two regressions rode along with the execution contract.

1. Every shell call lost stdout/stderr interleaving. A command printing
   progress on stdout and errors on stderr handed the model two separate
   blocks instead of the order the child actually wrote, so an error no
   longer sat next to the line that caused it.
2. Ordinary (non-Delivery) mode rejected the most common shell shape in
   real projects: `go build ./... && go test ./...`, `npm install && npm
   test`, `cargo build && cargo test`, `make build && make test`,
   `git pull && go test ./...`. Promoting the Delivery-only mixed and
   non-terminal-inline classifiers to global made every short-circuit
   chain a preflight block.

Root cause
----------
1. shellrun assigned two different io.MultiWriter values to cmd.Stdout and
   cmd.Stderr so stderr could be teed into its own tail. os/exec reuses a
   single pipe only while the two hold the same writer value; distinct
   values mean two pipes and two copy goroutines, and the merge order
   becomes a scheduling artifact. The pre-contract code passed one writer
   to both fields, which is why ordering held before.
2. The mixed/inline classifiers ask whether a call combines mutation with
   verification, not whether the exit status can lie. With `&&` bash
   short-circuits and reports the failing step, so those chains never had
   the failure-swallowing problem the contract exists to prevent.

Fix
---
- shellrun shares one writer between Stdout and Stderr, restoring
  child-write ordering and a single Progress caller. The bounded tail now
  covers combined output and is dropped on success, so a successful run
  no longer persists up to 16 KiB of ordinary stdout per tool card.
- Add shellparse.CanMaskEarlierFailure: `&&` chains are exempt, while `;`,
  newline, `||`, `|`, `|&`, and `&` can all hide an earlier failure. It
  reports ok=false for input it cannot analyze rather than guessing.
- Ordinary mode uses evidence.BashToolCallMixesMutationAndMaskableVerification
  and a maskability gate on the non-terminal inline interpreter rule.
  Delivery keeps the broad classifier: there a mutation invalidates the
  verification receipt even when the exit status is honest.
- Reword both recovery messages to name the real defect and offer '&&' as
  the fix, instead of telling the model never to combine the two.

Verification
------------
- Both regressions have failing-first guards: reverting either fix fails
  TestRunForegroundSharesOnePipeForStdoutAndStderr,
  TestRunForegroundPreservesInterleaving (10 runs), and the evidence and
  agent allow-list tests.
- go test ./internal/agent/... ./internal/evidence/... ./internal/shellrun/...
  ./internal/shellparse/... ./internal/tool/... ./internal/provider/...
  ./internal/control/... ./internal/acp/... — pass, except a pre-existing
  root-only failure (TestMissingReasoningWarnStateReadFailureDoesNotOverwrite-
  ExistingIncidents) that fails identically on main-v2.
- go build ./... and cd desktop && go build ./... pass; git diff --check clean.
2026-08-06 05:47:09 -07:00
SivanCola e57297959b Harden dynamic Bash authorization without disrupting Auto / 加固动态 Bash 授权并保持 Auto 低干预 (#6985)
* fix(permission): harden dynamic Bash authorization

Problem:
Broad Bash allow rules could silently authorize nested command and process substitutions, while treating every dynamic shell form as human-required would make Auto unnecessarily disruptive.

Root cause:
Bash rule matching did not distinguish reusable static commands, exact-only dynamic syntax, and nested or indirect execution before applying scoped allows and controller bypasses.

Fix:
Classify Bash ASTs into reusable, exact-only, and human-required tiers. Restrict all dynamic approvals to exact Bash=<literal> grants, require a human for nested or indirect execution outside YOLO, preserve Auto fallback for expansions, assignments, redirects, heredocs, and globs, and apply the same contract across interactive, headless, persistence, CLI, and ACP paths.

Verification:
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- env -u DEEPSEEK_API_KEY go test ./...
- gofmt -l .
- git diff --check

Fixes #6963

* fix(control): remove unused approval wrapper

Problem:
The CI lint job rejected PR #6985 because requestApprovalDecision was left unused after approval paths moved to the option-aware helper.

Root cause:
The dynamic Bash approval refactor replaced the wrapper call sites but did not remove the obsolete wrapper itself.

Fix:
Remove the unused wrapper without changing approval behavior.

Verification:
- go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- gofmt -l .
- git diff --check

* fix(permission): preserve dynamic compound rule precedence

Problem:
Dynamic compound Bash commands could reach fallback before per-segment Deny or Ask rules were evaluated. Raw rule matching also lost the established :* prefix semantics, and brace expansion or extglob arguments could be treated as reusable static commands.

Root cause:
The approval class short-circuited compound decomposition, the raw matcher interpreted Bash prefix markers as ordinary glob text, and the AST feature pass did not normalize brace expansion before testing whether words were static.

Fix:
Run compound segments through the same complete policy recursively, preserve Bash prefix semantics with AST-aware raw matching, prioritize wildcard tokens in glob matching, and classify brace/extglob or other non-static arguments as exact-only expansions using mvdan syntax nodes.

Verification:
- go run github.com/golangci/golangci-lint/v2/cmd/[email protected].2 run --timeout=5m
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- env -u DEEPSEEK_API_KEY go test ./...
- gofmt -l .
- git diff --check

* fix(permission): detect indirect builtin execution

Problem:
Bash builtin eval and builtin source could inherit broad Bash authorization even though their direct forms require human approval.

Root cause:
The indirect-execution classifier recursively handled command, exec, env, and other wrappers, but omitted Bash's builtin wrapper.

Fix:
Treat builtin as a recursive command wrapper and add positive and negative regression cases so eval/source require a human while ordinary builtins stay reusable.

Verification:
- go test ./internal/shellparse ./internal/permission ./internal/control ./internal/boot ./internal/cli ./internal/acp
- go test -race ./internal/control
- go vet ./...
- gofmt -l .
- git diff --check
2026-07-27 22:09:03 +08:00
SivanColaandJuly-X 174ea10844 refactor: harden parsers with maintained libraries
Replace several hand-rolled parsers and text processors with maintained libraries while preserving Reasonix-specific policy boundaries and fallbacks.

Add focused regression coverage for shell argv policy, HTML extraction, frontmatter decoding, dotenv parsing, glob matching, diff output, grapheme clipping, and optional tree-sitter code indexing.

Co-authored-by: July-X <[email protected]>
2026-07-04 01:11:34 +08:00
SivanColaandJuly-X 8664212c45 fix(bash): parse shell syntax with Bash AST
Replace hand-rolled Bash scanners with mvdan.cc/sh AST parsing for background keepalive detection, permission matching, plan-mode read-only checks, safe redirect normalization, and verification command matching.

Add heredoc regressions for issue #5624 so command execution and cancellation both release turns promptly.

Co-authored-by: July-X <[email protected]>
2026-07-03 22:41:16 +08:00