From 9d5884d2e5906dba99926809580a342cdb8c2c3f Mon Sep 17 00:00:00 2001 From: cstaszak <6634045+cstaszak@users.noreply.github.com> Date: Thu, 21 May 2026 17:34:04 -0700 Subject: [PATCH] ASH-220: gofmt the repo + add a gofmt CI gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had accumulated gofmt drift across 71 files — struct-field and map-key alignment, and Go 1.19+ doc-comment list/bullet reformatting. ci.yml had no gofmt step, so it went unnoticed. - gofmt -w . — pure formatting, no behavior change. Full test suite (54 packages) passes unchanged. - ci.yml: add a gofmt gate (`test -z "$(gofmt -l .)"`) after `vet`, so formatting drift cannot re-accumulate. - Regenerate docs/vocab/inventory.json — it records source file:line locations, which shifted when gofmt re-flowed blank lines. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 5 + cmd/ash/hook_bench_test.go | 6 +- cmd/ash/main.go | 2 +- cmd/ash/main_test.go | 2 - cmd/ashd/emitter.go | 6 +- cmd/encexplore/subs.go | 42 ++++----- cmd/encexplore/validate.go | 20 ++-- cmd/harnessbench/main.go | 34 +++---- cmd/mcpbench/main.go | 24 ++--- cmd/wirecmp/main.go | 10 +- docs/vocab/inventory.json | 44 ++++----- internal/bench/cases.go | 23 +++-- internal/config/load.go | 17 ++-- internal/config/paths.go | 6 +- internal/config/test_helpers_test.go | 2 +- internal/daemoncleanup/daemoncleanup_test.go | 6 +- internal/gitignore/gitignore.go | 4 +- internal/gitignore/gitignore_test.go | 22 ++--- internal/jail/policy.go | 12 +-- internal/ledger/ledger.go | 10 +- internal/ledger/ledger_test.go | 1 - internal/lsp/lsp.go | 24 ++--- internal/proto/mcpenv_test.go | 6 +- internal/proto/proto.go | 3 +- internal/verbs/argutil/duration_test.go | 30 +++--- internal/verbs/bench/baseline.go | 48 +++++----- internal/verbs/bench/baseline_test.go | 4 +- internal/verbs/bench/bench.go | 43 ++++----- internal/verbs/bench/bench_test.go | 18 ++-- internal/verbs/bench/micro.go | 26 +++--- internal/verbs/bench/micro_test.go | 8 +- internal/verbs/bench/prettyresponse_test.go | 4 +- internal/verbs/bench/trend.go | 30 +++--- internal/verbs/bench/trend_test.go | 8 +- internal/verbs/build/build.go | 8 +- internal/verbs/diff/diff.go | 14 +-- internal/verbs/edit/edit.go | 6 +- internal/verbs/edit/edit_test.go | 12 +-- internal/verbs/find/find.go | 16 ++-- internal/verbs/find/find_test.go | 42 ++++----- internal/verbs/git/diff.go | 18 ++-- internal/verbs/git/git.go | 18 ++-- internal/verbs/git/git_test.go | 16 ++-- internal/verbs/git/gogit_blame.go | 1 - internal/verbs/git/gogit_diff_worktree.go | 12 +-- internal/verbs/git/log.go | 8 +- internal/verbs/grep/grep.go | 20 ++-- internal/verbs/grep/grep_stream_test.go | 8 +- internal/verbs/grep/grep_test.go | 40 ++++---- internal/verbs/help/help.go | 6 +- internal/verbs/help/help_test.go | 5 +- internal/verbs/hook/hook.go | 39 ++++---- internal/verbs/hook/hook_test.go | 96 ++++++++++---------- internal/verbs/initverb/initverb.go | 23 ++--- internal/verbs/metrics/metrics.go | 40 ++++---- internal/verbs/metrics/render_test.go | 6 +- internal/verbs/read/read.go | 21 +++-- internal/verbs/read/read_test.go | 8 +- internal/verbs/recap/recap.go | 4 +- internal/verbs/replay/replay.go | 8 +- internal/verbs/replay/replay_test.go | 6 +- internal/verbs/report/report.go | 16 ++-- internal/verbs/report/report_test.go | 33 ++++--- internal/verbs/stat/stat_test.go | 1 - internal/verbs/test/test.go | 2 +- internal/verbs/test/test_test.go | 1 - internal/verbs/uninit/uninit.go | 8 +- internal/verbs/usage/usage.go | 10 +- internal/verbs/verbs.go | 68 +++++++------- internal/verbs/workspace/workspace.go | 23 ++--- internal/verbs/workspace/workspace_test.go | 2 +- internal/verbs/write/write_test.go | 4 +- internal/vocab/types.go | 18 ++-- 73 files changed, 618 insertions(+), 619 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddff5e1..01482d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,11 @@ jobs: - name: vet run: go vet ./... + # gofmt gate (ASH-220): fail on any unformatted file so formatting + # drift cannot re-accumulate. + - name: gofmt + run: test -z "$(gofmt -l .)" || { echo 'unformatted:'; gofmt -l .; exit 1; } + - name: test run: go test ./... diff --git a/cmd/ash/hook_bench_test.go b/cmd/ash/hook_bench_test.go index b988efb..d4bb918 100644 --- a/cmd/ash/hook_bench_test.go +++ b/cmd/ash/hook_bench_test.go @@ -11,11 +11,11 @@ import ( // Representative PreToolUse payloads. Three cases pin down the hot path: // // - allowGrep: a Grep call (no exclude_verbs) — deny path through the -// read/grep/write/edit switch, the most common branch. +// read/grep/write/edit switch, the most common branch. // - allowBashLs: a Bash `ls -la` — passes the bash parser without -// matching any redirect rule, exits via the allow fallthrough. +// matching any redirect rule, exits via the allow fallthrough. // - denyHeredoc: a bash heredoc redirecting into a file — exercises the -// heaviest path through segments() + detectOutputRedirect(). +// heaviest path through segments() + detectOutputRedirect(). var hookBenchPayloads = map[string][]byte{ "deny_grep": []byte(`{"tool_name":"Grep","tool_input":{"pattern":"foo","path":"internal/"}}`), "allow_bash": []byte(`{"tool_name":"Bash","tool_input":{"command":"ls -la"}}`), diff --git a/cmd/ash/main.go b/cmd/ash/main.go index 75840ee..39c023d 100644 --- a/cmd/ash/main.go +++ b/cmd/ash/main.go @@ -261,6 +261,7 @@ func main() { func printUsage() { fmt.Fprint(os.Stderr, help.RenderUsage(0)) } + // extractFormat pulls --format out of argv before verb flag parsing so it // doesn't get forwarded to the daemon as an unknown arg. specified is true // when the caller explicitly passed --format; false means it defaulted to @@ -555,7 +556,6 @@ func resolvePatchFile(args map[string]any) error { return nil } - func dialOrStart(root, sock string) (net.Conn, error) { killStaleIfNeeded(root, sock) if conn, err := net.DialTimeout("unix", sock, 200*time.Millisecond); err == nil { diff --git a/cmd/ash/main_test.go b/cmd/ash/main_test.go index 44240d0..6d36872 100644 --- a/cmd/ash/main_test.go +++ b/cmd/ash/main_test.go @@ -8,7 +8,6 @@ import ( "testing" ) - func TestParseFlags_FlagOnly(t *testing.T) { got, err := parseFlags("read", []string{"--path", "foo.go", "--range", "10:20"}) if err != nil { @@ -491,4 +490,3 @@ func TestResolveAtFile_CoexistsWithStdin(t *testing.T) { t.Errorf("new: got %q, want \"from-stdin\"", args["new"]) } } - diff --git a/cmd/ashd/emitter.go b/cmd/ashd/emitter.go index 2c3f7ba..744ce20 100644 --- a/cmd/ashd/emitter.go +++ b/cmd/ashd/emitter.go @@ -34,9 +34,9 @@ type frameEmitter struct { reqID uint64 start time.Time // request decode time; t0 for time_to_first_chunk_us - mu sync.Mutex // protects buffer + sequence - buffer []any - seq uint32 + mu sync.Mutex // protects buffer + sequence + buffer []any + seq uint32 lastFlush time.Time firstFlushAt atomic.Int64 // unix nanos; 0 == no flush yet diff --git a/cmd/encexplore/subs.go b/cmd/encexplore/subs.go index ce1acf4..6e7f31f 100644 --- a/cmd/encexplore/subs.go +++ b/cmd/encexplore/subs.go @@ -19,15 +19,15 @@ var subSets = []subSet{ { Name: "errors_ascii", Surface: "errors", Pairs: [][2]string{ - {"path_denied", "denied"}, // 3→2 - {"not_found", "missing"}, // 2→1 - {"range_out_of_bounds", "oob"}, // 4→2 - {"range_returned", "rng"}, // 3→1 - {"build_failed", "broke"}, // 2→1 - {"git_failed", "gitfail"}, // 2→2 (no win) - {"no_tests", "notests"}, // 2→2 (no win) - {"permission denied", "denied"}, // 2→2 - {"no such file", "missing"}, // 3→1 + {"path_denied", "denied"}, // 3→2 + {"not_found", "missing"}, // 2→1 + {"range_out_of_bounds", "oob"}, // 4→2 + {"range_returned", "rng"}, // 3→1 + {"build_failed", "broke"}, // 2→1 + {"git_failed", "gitfail"}, // 2→2 (no win) + {"no_tests", "notests"}, // 2→2 (no win) + {"permission denied", "denied"}, // 2→2 + {"no such file", "missing"}, // 3→1 }, }, @@ -35,15 +35,15 @@ var subSets = []subSet{ { Name: "errors_cjk", Surface: "errors", Pairs: [][2]string{ - {"path_denied", "失"}, // 3→1 - {"not_found", "無"}, // 2→1 - {"range_out_of_bounds", "越"}, // 4→2 (越 is 2 toks alone — test confirms) - {"range_returned", "中"}, // 3→1 - {"build_failed", "壊"}, // 2→2 (壊 is 2 toks — no win, drop?) - {"git_failed", "敗"}, // 2→2 (no win) - {"no_tests", "空"}, // 2→1 - {"permission denied", "禁"}, // 2→2 (no win) - {"no such file", "無"}, // 3→1 + {"path_denied", "失"}, // 3→1 + {"not_found", "無"}, // 2→1 + {"range_out_of_bounds", "越"}, // 4→2 (越 is 2 toks alone — test confirms) + {"range_returned", "中"}, // 3→1 + {"build_failed", "壊"}, // 2→2 (壊 is 2 toks — no win, drop?) + {"git_failed", "敗"}, // 2→2 (no win) + {"no_tests", "空"}, // 2→1 + {"permission denied", "禁"}, // 2→2 (no win) + {"no such file", "無"}, // 3→1 }, }, @@ -51,8 +51,8 @@ var subSets = []subSet{ { Name: "status_cjk", Surface: "status", Pairs: [][2]string{ - {"build_failed", "壊"}, // 2→2 (no win) — kept for completeness - {"no_tests", "空"}, // 2→1 + {"build_failed", "壊"}, // 2→2 (no win) — kept for completeness + {"no_tests", "空"}, // 2→1 // pass/fail/skip/timeout/ok/err are already 1-token; do not touch. }, }, @@ -145,7 +145,7 @@ var subSets = []subSet{ { Name: "truncation_compact", Surface: "truncation", Pairs: [][2]string{ - {"TRUNCATED", "…"}, // header sentinel + {"TRUNCATED", "…"}, // header sentinel {"[truncation:", "[…"}, // detail-prefix }, }, diff --git a/cmd/encexplore/validate.go b/cmd/encexplore/validate.go index e8171c5..7568cde 100644 --- a/cmd/encexplore/validate.go +++ b/cmd/encexplore/validate.go @@ -59,13 +59,13 @@ func runValidate(args []string) { } type row struct { - Corpus, Set string - ClBefore, ClAfter int - ClDelta int - ClPct float64 - ClaudeBefore, ClaudeAfter int - ClaudeDelta int - ClaudePct float64 + Corpus, Set string + ClBefore, ClAfter int + ClDelta int + ClPct float64 + ClaudeBefore, ClaudeAfter int + ClaudeDelta int + ClaudePct float64 } var rows []row cache := map[string]int{} // body -> Claude tokens, to avoid duplicate API calls @@ -102,10 +102,10 @@ func runValidate(args []string) { die("claude (%s/%s): %v", fn, set.Name, err) } r := row{ - Corpus: strings.TrimSuffix(fn, ".txt"), - Set: set.Name, + Corpus: strings.TrimSuffix(fn, ".txt"), + Set: set.Name, ClBefore: clBefore, ClAfter: clAfter, - ClDelta: clBefore - clAfter, + ClDelta: clBefore - clAfter, ClaudeBefore: claudeBefore, ClaudeAfter: claudeAfter, ClaudeDelta: claudeBefore - claudeAfter, } diff --git a/cmd/harnessbench/main.go b/cmd/harnessbench/main.go index 2f74d25..83032af 100644 --- a/cmd/harnessbench/main.go +++ b/cmd/harnessbench/main.go @@ -10,18 +10,18 @@ // // Methodology // -// read → harness Read returns "cat -n" format (6-char right-padded -// line number, tab, content). We re-run the bash equivalent, -// transform stdout to cat -n shape, tokenize. -// grep → harness Grep wraps ripgrep; default output mode is -// file:line:content — byte-identical to `grep -rn`. We use -// the existing bash_tokens from bench/baseline.json. -// find → harness Glob returns paths matching the pattern. Content -// is the same set of paths as `find` (just sorted by mtime -// rather than walk order); token count is identical. We use -// the existing bash_tokens from bench/baseline.json. -// other → no clean harness equivalent (git/stat/diff/edit/write/test). -// Marked n/a. +// read → harness Read returns "cat -n" format (6-char right-padded +// line number, tab, content). We re-run the bash equivalent, +// transform stdout to cat -n shape, tokenize. +// grep → harness Grep wraps ripgrep; default output mode is +// file:line:content — byte-identical to `grep -rn`. We use +// the existing bash_tokens from bench/baseline.json. +// find → harness Glob returns paths matching the pattern. Content +// is the same set of paths as `find` (just sorted by mtime +// rather than walk order); token count is identical. We use +// the existing bash_tokens from bench/baseline.json. +// other → no clean harness equivalent (git/stat/diff/edit/write/test). +// Marked n/a. // // Not modeled: the tool-call envelope on the harness side (the JSON // framing around content blocks adds ~10–30 tokens per call). These @@ -55,12 +55,12 @@ type baselineCase struct { } type row struct { - name string - verb string - ashTok int - bashTok int + name string + verb string + ashTok int + bashTok int harnessTok int - note string + note string } func main() { diff --git a/cmd/mcpbench/main.go b/cmd/mcpbench/main.go index a431acb..b7d4b17 100644 --- a/cmd/mcpbench/main.go +++ b/cmd/mcpbench/main.go @@ -6,18 +6,18 @@ // // What this measures // -// ashmcp_env_tok: the json-serialized CallToolResult that ashmcp emits -// to an MCP harness (StructuredContent + meta + the -// truncation TextContent sentinel where applicable). -// This is the production cost an MCP-aware harness -// pays per tool call. -// cli_tok: direct ash CLI cost from bench/baseline.json. -// harness_env_tok: a CallToolResult of the same shape a hypothetical -// harness-native MCP tool (Read/Grep/Glob in -// another MCP server) would return: TextContent -// holding the cat-n / file:line:content / glob-paths -// payload, no StructuredContent. Built from the same -// simulator used by cmd/harnessbench. +// ashmcp_env_tok: the json-serialized CallToolResult that ashmcp emits +// to an MCP harness (StructuredContent + meta + the +// truncation TextContent sentinel where applicable). +// This is the production cost an MCP-aware harness +// pays per tool call. +// cli_tok: direct ash CLI cost from bench/baseline.json. +// harness_env_tok: a CallToolResult of the same shape a hypothetical +// harness-native MCP tool (Read/Grep/Glob in +// another MCP server) would return: TextContent +// holding the cat-n / file:line:content / glob-paths +// payload, no StructuredContent. Built from the same +// simulator used by cmd/harnessbench. // // Three-way comparison answers: "is ashmcp's envelope tax eating the // per-call payload wins we have over CLI?" If ashmcp_env_tok is close diff --git a/cmd/wirecmp/main.go b/cmd/wirecmp/main.go index 74fbd02..989b413 100644 --- a/cmd/wirecmp/main.go +++ b/cmd/wirecmp/main.go @@ -109,10 +109,10 @@ func main() { } type row struct { - Name string - CLIBytes, CLITokens, MCPBytes, MCPTokens int - CLITokensClaude, MCPTokensClaude int - CLILatencyUs, MCPLatencyUs int64 + Name string + CLIBytes, CLITokens, MCPBytes, MCPTokens int + CLITokensClaude, MCPTokensClaude int + CLILatencyUs, MCPLatencyUs int64 } var rows []row @@ -182,7 +182,7 @@ func main() { mcpLat := medianRoundtripUs(sock, f.Verb, f.Args, proto.TransportMCP, *repeat) r := row{ - Name: f.Name, + Name: f.Name, CLIBytes: cliBytes, CLITokens: cliTokens, MCPBytes: mcpBytes, MCPTokens: mcpTokens, CLILatencyUs: cliLat, MCPLatencyUs: mcpLat, diff --git a/docs/vocab/inventory.json b/docs/vocab/inventory.json index 1654072..734ac78 100644 --- a/docs/vocab/inventory.json +++ b/docs/vocab/inventory.json @@ -261,7 +261,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 304 + "line": 305 }, { "file": "internal/verbs/uninit/uninit.go", @@ -275,7 +275,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 317 + "line": 318 }, { "file": "internal/verbs/uninit/uninit.go", @@ -425,7 +425,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 108 + "line": 109 } ] }, @@ -519,7 +519,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 213 + "line": 214 }, { "file": "internal/verbs/uninit/uninit.go", @@ -931,7 +931,7 @@ }, { "file": "internal/verbs/initverb/initverb.go", - "line": 103 + "line": 104 }, { "file": "internal/verbs/lang/lang.go", @@ -989,7 +989,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 174 + "line": 175 }, { "file": "internal/verbs/uninit/uninit.go", @@ -1003,7 +1003,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 177 + "line": 178 }, { "file": "internal/verbs/uninit/uninit.go", @@ -1017,11 +1017,11 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 209 + "line": 210 }, { "file": "internal/verbs/initverb/initverb.go", - "line": 217 + "line": 218 }, { "file": "internal/verbs/uninit/uninit.go", @@ -1257,7 +1257,7 @@ }, { "file": "internal/verbs/initverb/initverb.go", - "line": 98 + "line": 99 }, { "file": "internal/verbs/lang/lang.go", @@ -1649,7 +1649,7 @@ }, { "file": "internal/verbs/initverb/initverb.go", - "line": 105 + "line": 106 }, { "file": "internal/verbs/lang/lang.go", @@ -2416,7 +2416,7 @@ "sites": [ { "file": "internal/verbs/bench/bench.go", - "line": 575 + "line": 574 } ] }, @@ -2502,11 +2502,11 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 332 + "line": 333 }, { "file": "internal/verbs/initverb/initverb.go", - "line": 334 + "line": 335 } ] }, @@ -2682,7 +2682,7 @@ "sites": [ { "file": "internal/verbs/bench/bench.go", - "line": 590 + "line": 589 } ] }, @@ -2692,7 +2692,7 @@ "sites": [ { "file": "internal/verbs/bench/bench.go", - "line": 593 + "line": 592 } ] }, @@ -2702,7 +2702,7 @@ "sites": [ { "file": "internal/verbs/bench/bench.go", - "line": 614 + "line": 613 } ] }, @@ -2722,7 +2722,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 337 + "line": 338 }, { "file": "internal/verbs/uninit/uninit.go", @@ -2746,7 +2746,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 341 + "line": 342 }, { "file": "internal/verbs/uninit/uninit.go", @@ -2926,7 +2926,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 343 + "line": 344 }, { "file": "internal/verbs/uninit/uninit.go", @@ -2954,7 +2954,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 336 + "line": 337 }, { "file": "internal/verbs/uninit/uninit.go", @@ -3018,7 +3018,7 @@ "sites": [ { "file": "internal/verbs/initverb/initverb.go", - "line": 345 + "line": 346 }, { "file": "internal/verbs/uninit/uninit.go", diff --git a/internal/bench/cases.go b/internal/bench/cases.go index 5e4057a..3969e0b 100644 --- a/internal/bench/cases.go +++ b/internal/bench/cases.go @@ -180,7 +180,6 @@ var Cases = []Case{ }, } - // ExpandArgs returns a copy of args with the literal placeholder // "{root}" replaced by root in every string-valued entry. Used by the // bench runner to express absolute-path cases without baking a @@ -220,17 +219,17 @@ var MeasuredVerbs = []string{ // ExemptVerbs are verbs the harness deliberately does not measure. // Each entry has a one-line justification that survives in code review. var ExemptVerbs = map[string]string{ - "metrics": "reads ledger; size depends on session, no honest bash equivalent", - "report": "reads ledger; size depends on session", - "help": "no honest bash equivalent; token budget enforced by TestNoArgTokenBudget in internal/verbs/help", - "init": "one-shot setup; mutates files", - "uninit": "one-shot teardown; mutates files", - "stop": "kills daemon", - "hook": "the redirector under test; circular", - "bench": "recursive", - "test": "no honest bash equivalent at the verb level", - "build": "wraps go build; verb output is the parsed error stream, not measurable against a bash equivalent", - "replay": "reads ledger and re-dispatches verbs; no honest bash equivalent", + "metrics": "reads ledger; size depends on session, no honest bash equivalent", + "report": "reads ledger; size depends on session", + "help": "no honest bash equivalent; token budget enforced by TestNoArgTokenBudget in internal/verbs/help", + "init": "one-shot setup; mutates files", + "uninit": "one-shot teardown; mutates files", + "stop": "kills daemon", + "hook": "the redirector under test; circular", + "bench": "recursive", + "test": "no honest bash equivalent at the verb level", + "build": "wraps go build; verb output is the parsed error stream, not measurable against a bash equivalent", + "replay": "reads ledger and re-dispatches verbs; no honest bash equivalent", "recap": "reads ledger; session-graph summary, no honest bash equivalent", "workspace": "reads ledger + git status; re-orientation snapshot, no honest bash equivalent", "usage": "annotates the ledger row of a prior call; no bash equivalent, and the call is trivial (one UPDATE)", diff --git a/internal/config/load.go b/internal/config/load.go index 44359ba..e779d11 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -13,10 +13,10 @@ import ( // // Layer order — last-wins: // -// 1. Defaults() — compiled-in baseline. -// 2. GlobalPath() — $XDG_CONFIG_HOME/ash/config.toml (if present). -// 3. ProjectPath(root) — /ash.toml (if present). -// 4. $ASH_CONFIG — explicit-path override (if non-empty). +// 1. Defaults() — compiled-in baseline. +// 2. GlobalPath() — $XDG_CONFIG_HOME/ash/config.toml (if present). +// 3. ProjectPath(root) — /ash.toml (if present). +// 4. $ASH_CONFIG — explicit-path override (if non-empty). // // Each successful TOML decode happens against the same *Config value, // so non-zero fields in higher layers override lower layers. Absent @@ -29,10 +29,11 @@ import ( // be surprising. // // The returned source label is one of: -// "defaults" — no file applied. -// "" — only the global file applied. -// "" — project file applied (whether or not global did). -// "$ASH_CONFIG=

" — explicit override applied. +// +// "defaults" — no file applied. +// "" — only the global file applied. +// "" — project file applied (whether or not global did). +// "$ASH_CONFIG=

" — explicit override applied. func Load(root string) (*Config, string, error) { cfg := Defaults() source := "defaults" diff --git a/internal/config/paths.go b/internal/config/paths.go index ddc9097..5e7311d 100644 --- a/internal/config/paths.go +++ b/internal/config/paths.go @@ -8,9 +8,9 @@ import ( // GlobalPath returns the user-global config file path. // // Resolution order mirrors internal/registry/registry.go: -// 1. $XDG_CONFIG_HOME/ash/config.toml -// 2. ~/.config/ash/config.toml -// 3. $TMPDIR/ash-config.toml — last-resort writable fallback +// 1. $XDG_CONFIG_HOME/ash/config.toml +// 2. ~/.config/ash/config.toml +// 3. $TMPDIR/ash-config.toml — last-resort writable fallback // // The directory is not created here; Load only reads. func GlobalPath() string { diff --git a/internal/config/test_helpers_test.go b/internal/config/test_helpers_test.go index de3b1ac..6e0e20d 100644 --- a/internal/config/test_helpers_test.go +++ b/internal/config/test_helpers_test.go @@ -5,7 +5,7 @@ import "os" // Small filesystem helpers shared across tests in this package. Kept // in a *_test.go file so they don't ship in the production binary. -func mkdirAll(path string) error { return os.MkdirAll(path, 0o755) } +func mkdirAll(path string) error { return os.MkdirAll(path, 0o755) } func writeBytes(path string, content []byte) error { return os.WriteFile(path, content, 0o644) } diff --git a/internal/daemoncleanup/daemoncleanup_test.go b/internal/daemoncleanup/daemoncleanup_test.go index a300b62..4194d7c 100644 --- a/internal/daemoncleanup/daemoncleanup_test.go +++ b/internal/daemoncleanup/daemoncleanup_test.go @@ -119,9 +119,9 @@ func TestScan_ClassifiesAliveZombieUnknown(t *testing.T) { {PID: 1001, Cmdline: "bin/ashd --root /alive --socket /tmp/alive.sock"}, {PID: 1002, Cmdline: "bin/ashd --root /deleted --socket /tmp/dead.sock"}, {PID: 1003, Cmdline: "bin/ashd --root=/alive --socket=/tmp/unreachable.sock"}, - {PID: 1004, Cmdline: "bin/ashd"}, // unknown — no flags - {PID: 1005, Cmdline: "tail -f /tmp/alive.sock"}, // not ashd - {PID: 1006, Cmdline: "/opt/myashd --socket /tmp/x"}, // suffix-only impostor + {PID: 1004, Cmdline: "bin/ashd"}, // unknown — no flags + {PID: 1005, Cmdline: "tail -f /tmp/alive.sock"}, // not ashd + {PID: 1006, Cmdline: "/opt/myashd --socket /tmp/x"}, // suffix-only impostor {PID: self, Cmdline: "bin/ashd --root /me --socket /tmp/me.sock"}, // self }) withFakeRootExists(t, map[string]bool{"/alive": true}) diff --git a/internal/gitignore/gitignore.go b/internal/gitignore/gitignore.go index 7d14934..d397e97 100644 --- a/internal/gitignore/gitignore.go +++ b/internal/gitignore/gitignore.go @@ -37,8 +37,8 @@ import ( // function. Memoization turns the hot per-walk regex loop into a map lookup // — see ASH-38. type Matcher struct { - rules *ignore.GitIgnore - root string + rules *ignore.GitIgnore + root string resCache sync.Map // key = normalized rel path (trailing "/" iff isDir); val = bool } diff --git a/internal/gitignore/gitignore_test.go b/internal/gitignore/gitignore_test.go index de85862..5d87244 100644 --- a/internal/gitignore/gitignore_test.go +++ b/internal/gitignore/gitignore_test.go @@ -40,16 +40,16 @@ func TestExcludes_BasicPatterns(t *testing.T) { isDir bool exclude bool }{ - {"bin/ash", false, true}, // file inside ignored dir - {"bin", true, true}, // the dir itself, with isDir=true - {"bin", false, false}, // a file literally named "bin" should not match "bin/" - {"foo.log", false, true}, // glob pattern - {"deep/sub/foo.log", false, true}, // glob recurses - {"important.log", false, false}, // negation pattern - {"node_modules", true, true}, // dir-only pattern fires on dir - {"node_modules/pkg/index.js", false, true}, // children of ignored dir - {"src/main.go", false, false}, // not matched - {"README.md", false, false}, // not matched + {"bin/ash", false, true}, // file inside ignored dir + {"bin", true, true}, // the dir itself, with isDir=true + {"bin", false, false}, // a file literally named "bin" should not match "bin/" + {"foo.log", false, true}, // glob pattern + {"deep/sub/foo.log", false, true}, // glob recurses + {"important.log", false, false}, // negation pattern + {"node_modules", true, true}, // dir-only pattern fires on dir + {"node_modules/pkg/index.js", false, true}, // children of ignored dir + {"src/main.go", false, false}, // not matched + {"README.md", false, false}, // not matched } for _, c := range cases { if got := m.Excludes(c.path, c.isDir); got != c.exclude { @@ -147,7 +147,7 @@ func TestExcludes_MemoizesSamePath(t *testing.T) { exclude bool }{ {"bin", true, true}, - {"bin", false, false}, // distinct cache key from "bin" with isDir=true + {"bin", false, false}, // distinct cache key from "bin" with isDir=true {"foo.log", false, true}, {"src/main.go", false, false}, } diff --git a/internal/jail/policy.go b/internal/jail/policy.go index a927cd6..e8efddd 100644 --- a/internal/jail/policy.go +++ b/internal/jail/policy.go @@ -194,12 +194,12 @@ func PathPrefixes() []string { // // The map shape is chosen to keep verb-side call sites short: // -// if perr := jail.CheckPaths(map[string]string{ -// "path": a.Path, -// "other": a.Other, -// }); perr != nil { -// return nil, perr -// } +// if perr := jail.CheckPaths(map[string]string{ +// "path": a.Path, +// "other": a.Other, +// }); perr != nil { +// return nil, perr +// } // // Empty values in the map are skipped — verbs use empty string to mean // "not provided" for optional path args. diff --git a/internal/ledger/ledger.go b/internal/ledger/ledger.go index 9179fb8..79e5c8c 100644 --- a/internal/ledger/ledger.go +++ b/internal/ledger/ledger.go @@ -269,11 +269,11 @@ type Call struct { // Sub-phase latencies (microseconds). Optional; verbs that don't // instrument leave them at 0. They overlap by design (walk_us is the // wall time of walker.Walk, which contains visitor IO/regex time). - WalkUs int64 - IOUs int64 - RegexUs int64 - RegexCompileUs int64 - LatencyDispatchUs int64 + WalkUs int64 + IOUs int64 + RegexUs int64 + RegexCompileUs int64 + LatencyDispatchUs int64 // ASH-106 streaming surface. Streaming is true when the response went // out as Chunk frames + a Final frame; the count and ttfc fields are // only meaningful when Streaming is true. diff --git a/internal/ledger/ledger_test.go b/internal/ledger/ledger_test.go index c591f55..13acdb3 100644 --- a/internal/ledger/ledger_test.go +++ b/internal/ledger/ledger_test.go @@ -416,4 +416,3 @@ func TestFindMostRecentNonUsageRow(t *testing.T) { t.Errorf("verb: got %q want %q", gotVerb, "grep") } } - diff --git a/internal/lsp/lsp.go b/internal/lsp/lsp.go index ccc1ba3..a3495e5 100644 --- a/internal/lsp/lsp.go +++ b/internal/lsp/lsp.go @@ -4,16 +4,16 @@ // // Responsibilities: // -// * Spawn the language server lazily on the first Ensure call. -// * Drive the LSP initialize / initialized handshake. -// * Route textDocument/didOpen and didChange notifications fired by +// - Spawn the language server lazily on the first Ensure call. +// - Drive the LSP initialize / initialized handshake. +// - Route textDocument/didOpen and didChange notifications fired by // ash write and ash edit (via package lsp's Notify entry point) // so the server's in-memory view stays in sync with disk. -// * Provide a typed Request method for verbs that issue LSP requests +// - Provide a typed Request method for verbs that issue LSP requests // (documentSymbol, definition, references, ...). -// * Re-spawn gopls if it dies mid-session; bail loudly after repeated +// - Re-spawn gopls if it dies mid-session; bail loudly after repeated // failure with a stable error code instead of crashing the daemon. -// * Shut down cleanly when the daemon exits — no orphan gopls. +// - Shut down cleanly when the daemon exits — no orphan gopls. // // Wire framing follows LSP §3.1: an HTTP-like Content-Length header, // CRLF separator, then a JSON-RPC 2.0 body. We carry just enough of the @@ -80,9 +80,9 @@ type Broker struct { openMu sync.Mutex open map[string]int // path -> version (1 = didOpen sent; >1 = didChange'd) - failMu sync.Mutex - failCount int - failWindow time.Time + failMu sync.Mutex + failCount int + failWindow time.Time permanentlyDisabled bool onInit func(d time.Duration, err error) @@ -468,12 +468,12 @@ func (b *Broker) start(ctx context.Context) error { Capabilities: map[string]any{ "textDocument": map[string]any{ "synchronization": map[string]any{ - "didSave": false, - "willSave": false, + "didSave": false, + "willSave": false, "dynamicRegistration": false, }, "documentSymbol": map[string]any{ - "dynamicRegistration": false, + "dynamicRegistration": false, "hierarchicalDocumentSymbolSupport": true, }, }, diff --git a/internal/proto/mcpenv_test.go b/internal/proto/mcpenv_test.go index 20d35d1..3556045 100644 --- a/internal/proto/mcpenv_test.go +++ b/internal/proto/mcpenv_test.go @@ -51,9 +51,9 @@ func TestMCPTruncationHint(t *testing.T) { // Defensive paths must all return nil so the sentinel stays off // in any error / pre-decode state. for name, r := range map[string]*Response{ - "nil response": nil, - "err response": {OK: false, Err: &Error{Code: "boom", Msg: "x"}}, - "empty data": {OK: true}, + "nil response": nil, + "err response": {OK: false, Err: &Error{Code: "boom", Msg: "x"}}, + "empty data": {OK: true}, "non-ok with data": { OK: false, Data: MustData(resWithTrunc{Count: 1, Hint: &TruncInfo{Trunc: 1, Limit: 1, Max: 1}}), diff --git a/internal/proto/proto.go b/internal/proto/proto.go index 71a2ab1..99427dc 100644 --- a/internal/proto/proto.go +++ b/internal/proto/proto.go @@ -88,7 +88,6 @@ type Request struct { Env []string `msgpack:"env,omitempty" json:"env,omitempty"` } - // Chunk is one batch of intermediate results emitted during a streaming // response. Data is the msgpack-encoded verb-specific chunk payload (e.g. // []grep.Match, []find.Record, test.Package). Seq numbers chunks from 1 @@ -264,7 +263,7 @@ func ReadKinded(r io.Reader) (byte, []byte, error) { return buf[0], buf[1:], nil } -func EncodeChunk(c *Chunk) ([]byte, error) { return msgpack.Marshal(c) } +func EncodeChunk(c *Chunk) ([]byte, error) { return msgpack.Marshal(c) } func EncodeCancel(c *Cancel) ([]byte, error) { return msgpack.Marshal(c) } func DecodeChunk(buf []byte) (*Chunk, error) { diff --git a/internal/verbs/argutil/duration_test.go b/internal/verbs/argutil/duration_test.go index 449c91a..840819d 100644 --- a/internal/verbs/argutil/duration_test.go +++ b/internal/verbs/argutil/duration_test.go @@ -27,13 +27,13 @@ func TestParseDuration_GoUnits(t *testing.T) { func TestParseDuration_CustomUnits(t *testing.T) { cases := map[string]time.Duration{ - "1d": 24 * time.Hour, - "7d": 7 * 24 * time.Hour, - "1w": 7 * 24 * time.Hour, - "2w": 2 * 7 * 24 * time.Hour, - "1mo": 30 * 24 * time.Hour, - "3mo": 3 * 30 * 24 * time.Hour, - "30d": 30 * 24 * time.Hour, // d=24h, so 30d == 1mo (the calendar approximation) + "1d": 24 * time.Hour, + "7d": 7 * 24 * time.Hour, + "1w": 7 * 24 * time.Hour, + "2w": 2 * 7 * 24 * time.Hour, + "1mo": 30 * 24 * time.Hour, + "3mo": 3 * 30 * 24 * time.Hour, + "30d": 30 * 24 * time.Hour, // d=24h, so 30d == 1mo (the calendar approximation) } for in, want := range cases { got, err := ParseDuration(in) @@ -69,14 +69,14 @@ func TestParseDuration_MoBeforeM(t *testing.T) { func TestParseDuration_Errors(t *testing.T) { cases := []string{ - "", // empty - "banana", // no unit, no integer prefix - "0d", // zero rejected (preserves prior per-verb behavior) - "-1d", // negative rejected - "1.5d", // decimal rejected by strconv.Atoi - "d", // no integer - "mow", // not a known suffix in any branch (mo matches, prefix "" Atoi fails) - "abc7d", // garbage prefix on a real suffix + "", // empty + "banana", // no unit, no integer prefix + "0d", // zero rejected (preserves prior per-verb behavior) + "-1d", // negative rejected + "1.5d", // decimal rejected by strconv.Atoi + "d", // no integer + "mow", // not a known suffix in any branch (mo matches, prefix "" Atoi fails) + "abc7d", // garbage prefix on a real suffix } for _, in := range cases { if _, err := ParseDuration(in); err == nil { diff --git a/internal/verbs/bench/baseline.go b/internal/verbs/bench/baseline.go index 94b000c..64b03e7 100644 --- a/internal/verbs/bench/baseline.go +++ b/internal/verbs/bench/baseline.go @@ -33,9 +33,9 @@ const ( baselineSchemaVersion = "ash-bench-baseline-v1" latencySnapshotSchemaVersion = "ash-bench-latency-snapshot-v1" - baselineDirName = "bench" - baselineJSONName = "baseline.json" - baselineMDName = "baseline.md" + baselineDirName = "bench" + baselineJSONName = "baseline.json" + baselineMDName = "baseline.md" latencySnapshotJSONName = "latency-snapshot.json" kindRecord = "record_baseline" @@ -75,12 +75,12 @@ type BaselineFile struct { // LatencyCase is one row in latency-snapshot.json. type LatencyCase struct { - Name string `json:"name"` - Verb string `json:"verb"` - AshUsP50 int64 `json:"ash_us_p50"` - AshUsMin int64 `json:"ash_us_min"` - BashUsP50 int64 `json:"bash_us_p50"` - BashUsMin int64 `json:"bash_us_min"` + Name string `json:"name"` + Verb string `json:"verb"` + AshUsP50 int64 `json:"ash_us_p50"` + AshUsMin int64 `json:"ash_us_min"` + BashUsP50 int64 `json:"bash_us_p50"` + BashUsMin int64 `json:"bash_us_min"` } // LatencySnapshotFile is the serialized form of latency-snapshot.json. @@ -100,12 +100,12 @@ type LatencySnapshotFile struct { // surfaces the file paths written + the same summary the user would // see from a regular bench run, so the immediate output isn't silent. type RecordBaselineResult struct { - Kind string `msgpack:"kind"` // always "record_baseline" - BaselinePath string `msgpack:"baseline_path"` - MarkdownPath string `msgpack:"markdown_path"` - LatencyPath string `msgpack:"latency_path"` - BytesWritten int `msgpack:"bytes_written"` - Run *Result `msgpack:"run"` + Kind string `msgpack:"kind"` // always "record_baseline" + BaselinePath string `msgpack:"baseline_path"` + MarkdownPath string `msgpack:"markdown_path"` + LatencyPath string `msgpack:"latency_path"` + BytesWritten int `msgpack:"bytes_written"` + Run *Result `msgpack:"run"` } // ExportMdResult is the response shape for --export-md. The Body is @@ -394,16 +394,16 @@ func loadBaselineFile(projectRoot string) (*BaselineFile, error) { // baseline file is tokens-only by design. func baselineToRunSummary(bf *BaselineFile) (RunSummary, []ledger.BenchCaseResult) { rs := RunSummary{ - RunUUID: "baseline", - AshVersion: bf.AshVersion, - AshCommitSHA: bf.AshCommitSHA, - CaseSetVersion: bf.CaseSetVersion, - RepoSHA: bf.RepoSHA, - RepoDirty: bf.RepoDirty, - Cases: bf.Summary.NCases, - AshTokensTotal: bf.Summary.AshTokensTotal, + RunUUID: "baseline", + AshVersion: bf.AshVersion, + AshCommitSHA: bf.AshCommitSHA, + CaseSetVersion: bf.CaseSetVersion, + RepoSHA: bf.RepoSHA, + RepoDirty: bf.RepoDirty, + Cases: bf.Summary.NCases, + AshTokensTotal: bf.Summary.AshTokensTotal, BashTokensTotal: bf.Summary.BashTokensTotal, - DeltaTokPct: bf.Summary.DeltaTokPct, + DeltaTokPct: bf.Summary.DeltaTokPct, } if t, err := time.Parse(time.RFC3339, bf.Timestamp); err == nil { rs.Timestamp = t diff --git a/internal/verbs/bench/baseline_test.go b/internal/verbs/bench/baseline_test.go index 3ea1e39..7bd1860 100644 --- a/internal/verbs/bench/baseline_test.go +++ b/internal/verbs/bench/baseline_test.go @@ -227,9 +227,9 @@ func TestRenderBaselineMarkdown_ContainsAllFields(t *testing.T) { "repo: `11223344`", "(dirty)", "| `c1` | find | 100 | 200 |", - "bash", // truncation column for c1 + "bash", // truncation column for c1 "| `c2` | grep | 50 | 0 |", // zero-bash row stays in table - "+0%", // c2's dpct should be the +0% inline guard + "+0%", // c2's dpct should be the +0% inline guard "3 cases, ash 225 tok, bash 250 tok", "-10.0%", "darwin/arm64", diff --git a/internal/verbs/bench/bench.go b/internal/verbs/bench/bench.go index 4d9361b..b1e07e2 100644 --- a/internal/verbs/bench/bench.go +++ b/internal/verbs/bench/bench.go @@ -59,19 +59,19 @@ type Args struct { Verb string Case string Limit int - Repeat int // measured iterations per case per side; default 1 - Warmup int // unmeasured iterations per case per side; default 1 when Repeat>1, else 0 + Repeat int // measured iterations per case per side; default 1 + Warmup int // unmeasured iterations per case per side; default 1 when Repeat>1, else 0 // Trend / comparison flags. When any of these is set, the verb does // not run a fresh bench by itself (except --baseline, which runs a // new bench then compares against the rolling baseline). - List bool // --list: list recent runs and exit - ListLimit int // --list-limit N (default 20) - CompareA string // --compare A,B → run_uuid A. Special tokens: "latest", "baseline" - CompareB string // run_uuid B - Baseline string // --baseline : run new bench, compare to rolling baseline window (e.g. "7d") - RegressTokPct int // --regress-tokens 10 (default 10, meaning Δtok > +10%) - RegressLatPct int // --regress-latency 20 (default 20, meaning Δlat > +20%) + List bool // --list: list recent runs and exit + ListLimit int // --list-limit N (default 20) + CompareA string // --compare A,B → run_uuid A. Special tokens: "latest", "baseline" + CompareB string // run_uuid B + Baseline string // --baseline : run new bench, compare to rolling baseline window (e.g. "7d") + RegressTokPct int // --regress-tokens 10 (default 10, meaning Δtok > +10%) + RegressLatPct int // --regress-latency 20 (default 20, meaning Δlat > +20%) // Publishable artifact flags. RecordBaseline bool // --record-baseline: run fresh bench, write bench/baseline.json + bench/latency-snapshot.json @@ -114,20 +114,20 @@ type CaseResult struct { // VerbSummary aggregates CaseResult rows by verb. type VerbSummary struct { - Verb string `msgpack:"verb"` - Cases int `msgpack:"cases"` - AshTokensTotal int `msgpack:"ash_tokens_total"` - BashTokensTotal int `msgpack:"bash_tokens_total"` - AshLatencyUsTotal int64 `msgpack:"ash_latency_us_total"` - BashLatencyUsTotal int64 `msgpack:"bash_latency_us_total"` + Verb string `msgpack:"verb"` + Cases int `msgpack:"cases"` + AshTokensTotal int `msgpack:"ash_tokens_total"` + BashTokensTotal int `msgpack:"bash_tokens_total"` + AshLatencyUsTotal int64 `msgpack:"ash_latency_us_total"` + BashLatencyUsTotal int64 `msgpack:"bash_latency_us_total"` } type Result struct { - RunUUID string `msgpack:"run_uuid,omitempty"` - Cases []CaseResult `msgpack:"cases"` - ByVerb []VerbSummary `msgpack:"by_verb"` - Overall VerbSummary `msgpack:"overall"` - NotRun []string `msgpack:"not_run,omitempty"` // case names skipped (e.g. translation gap) + RunUUID string `msgpack:"run_uuid,omitempty"` + Cases []CaseResult `msgpack:"cases"` + ByVerb []VerbSummary `msgpack:"by_verb"` + Overall VerbSummary `msgpack:"overall"` + NotRun []string `msgpack:"not_run,omitempty"` // case names skipped (e.g. translation gap) NotRunWhy map[string]string `msgpack:"not_run_why,omitempty"` } @@ -476,7 +476,6 @@ func runBashOnce(argv []string) bench.BashResult { return bench.RunBash(ctx, argv) } - func aggregateByVerb(rows []CaseResult) []VerbSummary { byVerb := map[string]*VerbSummary{} for _, r := range rows { @@ -701,5 +700,3 @@ func minUs(samples []int64) int64 { } return m } - - diff --git a/internal/verbs/bench/bench_test.go b/internal/verbs/bench/bench_test.go index 762c0fa..65ccf93 100644 --- a/internal/verbs/bench/bench_test.go +++ b/internal/verbs/bench/bench_test.go @@ -25,11 +25,11 @@ func TestParseArgs_WireShape(t *testing.T) { // and rejects non-benchmark lines. func TestParseBenchLine(t *testing.T) { cases := []struct { - line string - wantOK bool - wantBase string - wantNs float64 - wantAllocs float64 + line string + wantOK bool + wantBase string + wantNs float64 + wantAllocs float64 }{ { line: "BenchmarkHookDecide/deny_grep-8\t5614714\t213.8 ns/op\t0 B/op\t0 allocs/op", @@ -81,11 +81,11 @@ func TestParseBenchLine(t *testing.T) { // accept wire-shape inputs from the CLI parseFlags path. func TestParseArgs_MicroFlags(t *testing.T) { a, perr := ParseArgs(map[string]any{ - "micro": "true", - "record_micro": "false", - "micro_count": "3", + "micro": "true", + "record_micro": "false", + "micro_count": "3", "micro_benchtime": "2s", - "micro_packages": "cmd/ash,internal/walker", + "micro_packages": "cmd/ash,internal/walker", }) if perr != nil { t.Fatalf("parse failed: %v", perr) diff --git a/internal/verbs/bench/micro.go b/internal/verbs/bench/micro.go index 8f3df48..2b33d47 100644 --- a/internal/verbs/bench/micro.go +++ b/internal/verbs/bench/micro.go @@ -291,13 +291,13 @@ func runDiffMicro(d Deps, a *Args) (*DiffMicroResult, *proto.Error) { // averaged across them. func parseMicroRows(pkg string, tr *testverb.Result) []MicroBenchRow { type acc struct { - count int - nsSum float64 - bSum float64 - allocsSum float64 - n int64 - name string - baseName string + count int + nsSum float64 + bSum float64 + allocsSum float64 + n int64 + name string + baseName string } accs := map[string]*acc{} var order []string @@ -320,13 +320,13 @@ func parseMicroRows(pkg string, tr *testverb.Result) []MicroBenchRow { } else { order = append(order, key) accs[key] = &acc{ - count: 1, - nsSum: row.NsPerOp, - bSum: row.BPerOp, + count: 1, + nsSum: row.NsPerOp, + bSum: row.BPerOp, allocsSum: row.AllocsPerOp, - n: row.N, - name: row.Name, - baseName: row.BaseName, + n: row.N, + name: row.Name, + baseName: row.BaseName, } } } diff --git a/internal/verbs/bench/micro_test.go b/internal/verbs/bench/micro_test.go index 53608bc..4b2e0f8 100644 --- a/internal/verbs/bench/micro_test.go +++ b/internal/verbs/bench/micro_test.go @@ -15,10 +15,10 @@ func TestStripGOMAXPROCS(t *testing.T) { cases := []struct{ in, want string }{ {"BenchmarkFoo-8", "BenchmarkFoo"}, {"BenchmarkFoo/bar-16", "BenchmarkFoo/bar"}, - {"BenchmarkFoo", "BenchmarkFoo"}, // no suffix + {"BenchmarkFoo", "BenchmarkFoo"}, // no suffix {"BenchmarkFoo-not-a-num", "BenchmarkFoo-not-a-num"}, // non-numeric suffix - {"-8", ""}, // edge: just the suffix - {"BenchmarkFoo-", "BenchmarkFoo-"}, // empty after dash + {"-8", ""}, // edge: just the suffix + {"BenchmarkFoo-", "BenchmarkFoo-"}, // empty after dash } for _, c := range cases { if got := stripGOMAXPROCS(c.in); got != c.want { @@ -234,7 +234,7 @@ func TestPrettyMicro_WithRows(t *testing.T) { out := prettyMicro(r) for _, want := range []string{ "§bench --micro: 1 benchmark(s)", - "benchmark", // header line + "benchmark", // header line "BenchmarkFoo", // body "cmd/ash", "ERROR internal/broken", diff --git a/internal/verbs/bench/prettyresponse_test.go b/internal/verbs/bench/prettyresponse_test.go index acc12f2..5fc45bf 100644 --- a/internal/verbs/bench/prettyresponse_test.go +++ b/internal/verbs/bench/prettyresponse_test.go @@ -85,8 +85,8 @@ func TestPrettyResponse_StandardResult(t *testing.T) { {Verb: "find", Cases: 1, AshTokensTotal: 100, BashTokensTotal: 200, AshLatencyUsTotal: 50, BashLatencyUsTotal: 100}, {Verb: "grep", Cases: 1, AshTokensTotal: 50, BashTokensTotal: 50, AshLatencyUsTotal: 0, BashLatencyUsTotal: 0}, }, - Overall: VerbSummary{Verb: "overall", Cases: 2, AshTokensTotal: 150, BashTokensTotal: 250, AshLatencyUsTotal: 50, BashLatencyUsTotal: 100}, - NotRun: []string{"skipped_case"}, + Overall: VerbSummary{Verb: "overall", Cases: 2, AshTokensTotal: 150, BashTokensTotal: 250, AshLatencyUsTotal: 50, BashLatencyUsTotal: 100}, + NotRun: []string{"skipped_case"}, NotRunWhy: map[string]string{"skipped_case": "translation gap"}, } out := PrettyResponse(req(), okResponse(t, r)) diff --git a/internal/verbs/bench/trend.go b/internal/verbs/bench/trend.go index bcdbd62..a7a393e 100644 --- a/internal/verbs/bench/trend.go +++ b/internal/verbs/bench/trend.go @@ -44,20 +44,20 @@ type ListResult struct { // CaseDelta is one row in a CompareResult. type CaseDelta struct { - CaseName string `msgpack:"case_name"` - Verb string `msgpack:"verb"` - AshTokA int `msgpack:"ash_tok_a"` - AshTokB int `msgpack:"ash_tok_b"` - BashTokA int `msgpack:"bash_tok_a,omitempty"` - BashTokB int `msgpack:"bash_tok_b,omitempty"` - DeltaTokPct float64 `msgpack:"delta_tok_pct"` - AshLatUsP50A int64 `msgpack:"ash_lat_us_p50_a"` - AshLatUsP50B int64 `msgpack:"ash_lat_us_p50_b"` - DeltaLatPct float64 `msgpack:"delta_lat_pct"` - Regressed bool `msgpack:"regressed,omitempty"` - Improved bool `msgpack:"improved,omitempty"` - OnlyInA bool `msgpack:"only_in_a,omitempty"` - OnlyInB bool `msgpack:"only_in_b,omitempty"` + CaseName string `msgpack:"case_name"` + Verb string `msgpack:"verb"` + AshTokA int `msgpack:"ash_tok_a"` + AshTokB int `msgpack:"ash_tok_b"` + BashTokA int `msgpack:"bash_tok_a,omitempty"` + BashTokB int `msgpack:"bash_tok_b,omitempty"` + DeltaTokPct float64 `msgpack:"delta_tok_pct"` + AshLatUsP50A int64 `msgpack:"ash_lat_us_p50_a"` + AshLatUsP50B int64 `msgpack:"ash_lat_us_p50_b"` + DeltaLatPct float64 `msgpack:"delta_lat_pct"` + Regressed bool `msgpack:"regressed,omitempty"` + Improved bool `msgpack:"improved,omitempty"` + OnlyInA bool `msgpack:"only_in_a,omitempty"` + OnlyInB bool `msgpack:"only_in_b,omitempty"` } // CompareResult is the response shape for --compare and --baseline. @@ -461,5 +461,3 @@ func min(a, b int) int { } return b } - - diff --git a/internal/verbs/bench/trend_test.go b/internal/verbs/bench/trend_test.go index 18f8be3..518890c 100644 --- a/internal/verbs/bench/trend_test.go +++ b/internal/verbs/bench/trend_test.go @@ -52,10 +52,10 @@ func TestPctChangeInt64(t *testing.T) { // when latency improved (or vice versa). func TestClassifyDelta(t *testing.T) { cases := []struct { - name string - dTok, dLat float64 - regTok, regLat float64 - wantRegress, wantImpr bool + name string + dTok, dLat float64 + regTok, regLat float64 + wantRegress, wantImpr bool }{ // Below thresholds → neither. {name: "small_changes", dTok: 5, dLat: 10, regTok: 10, regLat: 20, wantRegress: false, wantImpr: false}, diff --git a/internal/verbs/build/build.go b/internal/verbs/build/build.go index 82d1d87..ef8113a 100644 --- a/internal/verbs/build/build.go +++ b/internal/verbs/build/build.go @@ -308,10 +308,10 @@ var errorLineRe = regexp.MustCompile(`^([^\s:][^:]*):(\d+)(?::(\d+))?:\s*(.+)$`) // determinism). Empty stderr yields nil. func parseStderr(stderr string) []Package { type acc struct { - path string - outBuf strings.Builder - errs []BuildError - order int + path string + outBuf strings.Builder + errs []BuildError + order int } pkgs := map[string]*acc{} order := []string{} diff --git a/internal/verbs/diff/diff.go b/internal/verbs/diff/diff.go index be1bc53..daf1e35 100644 --- a/internal/verbs/diff/diff.go +++ b/internal/verbs/diff/diff.go @@ -35,12 +35,12 @@ type Args struct { } type Result struct { - PathA string `msgpack:"path_a"` - PathB string `msgpack:"path_b"` - Additions int `msgpack:"additions"` - Deletions int `msgpack:"deletions"` - Patch string `msgpack:"patch"` - Unchanged bool `msgpack:"unchanged,omitempty"` // true when the two sides are identical + PathA string `msgpack:"path_a"` + PathB string `msgpack:"path_b"` + Additions int `msgpack:"additions"` + Deletions int `msgpack:"deletions"` + Patch string `msgpack:"patch"` + Unchanged bool `msgpack:"unchanged,omitempty"` // true when the two sides are identical } func ParseArgs(in map[string]any) (*Args, *proto.Error) { @@ -75,7 +75,7 @@ func ParseArgs(in map[string]any) (*Args, *proto.Error) { return nil, &proto.Error{Code: "args", Msg: "one of other or content is required"} } if perr := jail.CheckPaths(map[string]string{ - "path": a.Path, + "path": a.Path, "other": a.Other, }); perr != nil { return nil, perr diff --git a/internal/verbs/edit/edit.go b/internal/verbs/edit/edit.go index 55284cb..1b0cb6a 100644 --- a/internal/verbs/edit/edit.go +++ b/internal/verbs/edit/edit.go @@ -93,8 +93,8 @@ type Result struct { LinesTotal int `msgpack:"lines_total"` Occurrences int `msgpack:"occurrences"` // replacements made; hunk count in patch mode DryRun bool `msgpack:"dry_run,omitempty"` - Patch string `msgpack:"patch,omitempty"` // unified diff when dry_run=true - FuzzApplied int `msgpack:"fuzz_applied,omitempty"` // ASH-152: hunks placed via fuzz scan (line-number drift > 0); 0 when every hunk matched at its authored position + Patch string `msgpack:"patch,omitempty"` // unified diff when dry_run=true + FuzzApplied int `msgpack:"fuzz_applied,omitempty"` // ASH-152: hunks placed via fuzz scan (line-number drift > 0); 0 when every hunk matched at its authored position } // aliasArg lets a harness-style key stand in for an ash flag: when alias @@ -413,7 +413,7 @@ func parsePatch(patchText string) ([]patchHunk, error) { hunks = append(hunks, patchHunk{ oldStart: oldStart, oldCount: oldCount, newStart: newStart, newCount: newCount, - lines: bodyLines, + lines: bodyLines, }) } diff --git a/internal/verbs/edit/edit_test.go b/internal/verbs/edit/edit_test.go index 6f51f0d..4f8e892 100644 --- a/internal/verbs/edit/edit_test.go +++ b/internal/verbs/edit/edit_test.go @@ -76,10 +76,10 @@ func TestParseArgs_StringModeDefaults(t *testing.T) { // values (the wire shape from CLI parseFlags) and rejects garbage. func TestParseArgs_WireShape(t *testing.T) { a, perr := ParseArgs(map[string]any{ - "path": "f.go", + "path": "f.go", "old": "old", - "all": "true", - "dry": "true", + "all": "true", + "dry": "true", }) if perr != nil { t.Fatalf("valid string args rejected: %v", perr) @@ -96,9 +96,9 @@ func TestParseArgs_WireShape(t *testing.T) { {"dry", "maybe"}, } { _, perr := ParseArgs(map[string]any{ - "path": "f.go", - "old": "old", - bad.key: bad.val, + "path": "f.go", + "old": "old", + bad.key: bad.val, }) if perr == nil { t.Errorf("expected error for %s=%q", bad.key, bad.val) diff --git a/internal/verbs/find/find.go b/internal/verbs/find/find.go index a6639a0..fa92227 100644 --- a/internal/verbs/find/find.go +++ b/internal/verbs/find/find.go @@ -60,17 +60,17 @@ type Args struct { type Record struct { Path string `msgpack:"path"` - Type string `msgpack:"type"` // "file" | "dir" | "symlink" - Size int64 `msgpack:"size,omitempty"` // bytes; omitted for dirs/symlinks and when --meta=false (ASH-187) - Mtime int64 `msgpack:"mtime,omitempty"` // unix nanos; omitted when --meta=false (ASH-187) + Type string `msgpack:"type"` // "file" | "dir" | "symlink" + Size int64 `msgpack:"size,omitempty"` // bytes; omitted for dirs/symlinks and when --meta=false (ASH-187) + Mtime int64 `msgpack:"mtime,omitempty"` // unix nanos; omitted when --meta=false (ASH-187) } type Result struct { - Records []Record `msgpack:"records"` - Count int `msgpack:"count"` - FilesScanned int `msgpack:"files_scanned"` - Truncated bool `msgpack:"truncated,omitempty"` - TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` + Records []Record `msgpack:"records"` + Count int `msgpack:"count"` + FilesScanned int `msgpack:"files_scanned"` + Truncated bool `msgpack:"truncated,omitempty"` + TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` // WithMeta echoes the request's --meta flag so downstream shapers // (CompactResponse, MCP JSON envelope) can decide whether to emit // the size/mtime columns. Without it, CompactResponse would have diff --git a/internal/verbs/find/find_test.go b/internal/verbs/find/find_test.go index b5bee99..8d1616f 100644 --- a/internal/verbs/find/find_test.go +++ b/internal/verbs/find/find_test.go @@ -33,15 +33,15 @@ func makeTree(t *testing.T) string { t.Helper() root := t.TempDir() files := map[string]string{ - "a.go": "package a", - "b.go": "package b", - "README.md": "# r", - ".gitignore": "ignore", - "src/main.go": "package main", - "src/util.go": "package main", - "src/deep/x.go": "package deep", - ".git/HEAD": "ref: refs/heads/main", - "vendor/pkg/v.go": "package pkg", + "a.go": "package a", + "b.go": "package b", + "README.md": "# r", + ".gitignore": "ignore", + "src/main.go": "package main", + "src/util.go": "package main", + "src/deep/x.go": "package deep", + ".git/HEAD": "ref: refs/heads/main", + "vendor/pkg/v.go": "package pkg", } for rel, body := range files { full := filepath.Join(root, rel) @@ -416,12 +416,12 @@ func equalSlices(a, b []string) bool { // breaking the string-coercion path. func TestParseArgs_WireShape(t *testing.T) { a, perr := ParseArgs(map[string]any{ - "path": ".", - "limit": "10", - "depth": "3", - "hidden": "true", - "gi": "false", - "meta": "true", + "path": ".", + "limit": "10", + "depth": "3", + "hidden": "true", + "gi": "false", + "meta": "true", }) if perr != nil { t.Fatalf("valid string args rejected: %v", perr) @@ -653,7 +653,7 @@ func TestPrettyResponse_AliasTableSingleAllowPath(t *testing.T) { Data: proto.MustData(&Result{ Count: 2, Records: []Record{ - {Path: "internal/foo.go", Type: "file"}, // inside project root (bare) + {Path: "internal/foo.go", Type: "file"}, // inside project root (bare) {Path: scratch + "/notes.md", Type: "file"}, // outside project root (absolute) }, }), @@ -675,9 +675,9 @@ func TestPrettyResponse_AliasTableSingleAllowPath(t *testing.T) { // TestPrettyResponse_AliasTableTwoAllowPaths verifies the multi-prefix case: // two allow_paths entries produce @0 and @1 aliases in the same response. func TestPrettyResponse_AliasTableTwoAllowPaths(t *testing.T) { - root := t.TempDir() + root := t.TempDir() scratch := t.TempDir() - vendor := t.TempDir() + vendor := t.TempDir() canon := func(p string) string { if r, err := filepath.EvalSymlinks(p); err == nil { return r @@ -724,7 +724,7 @@ func TestPrettyResponse_NoAliasTableWithoutAllowPaths(t *testing.T) { rsp := &proto.Response{ OK: true, Data: proto.MustData(&Result{ - Count: 1, + Count: 1, Records: []Record{{Path: "internal/foo.go", Type: "file"}}, }), } @@ -739,7 +739,7 @@ func TestPrettyResponse_NoAliasTableWithoutAllowPaths(t *testing.T) { // TestPrettyResponse_AbsoluteFlagSkipsAliasTable confirms --absolute suppresses // the alias table even when allow_paths entries exist. func TestPrettyResponse_AbsoluteFlagSkipsAliasTable(t *testing.T) { - root := t.TempDir() + root := t.TempDir() scratch := t.TempDir() jail.SetPolicy(jail.FromConfig(false, root, []string{scratch}, nil)) defer jail.SetPolicy(nil) @@ -747,7 +747,7 @@ func TestPrettyResponse_AbsoluteFlagSkipsAliasTable(t *testing.T) { rsp := &proto.Response{ OK: true, Data: proto.MustData(&Result{ - Count: 1, + Count: 1, Records: []Record{{Path: scratch + "/notes.md", Type: "file"}}, }), } diff --git a/internal/verbs/git/diff.go b/internal/verbs/git/diff.go index f7bad22..ef0b00b 100644 --- a/internal/verbs/git/diff.go +++ b/internal/verbs/git/diff.go @@ -20,12 +20,12 @@ const ( // DiffResult is the structured replacement for `git diff` text scraping. type DiffResult struct { - Files []DiffFile `msgpack:"files,omitempty"` - TotalAdditions int `msgpack:"total_additions"` - TotalDeletions int `msgpack:"total_deletions"` - Truncated bool `msgpack:"truncated,omitempty"` + Files []DiffFile `msgpack:"files,omitempty"` + TotalAdditions int `msgpack:"total_additions"` + TotalDeletions int `msgpack:"total_deletions"` + Truncated bool `msgpack:"truncated,omitempty"` TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` - StatOnly bool `msgpack:"stat_only,omitempty"` + StatOnly bool `msgpack:"stat_only,omitempty"` } // DiffFile captures one changed file from a diff. In full (non-stat) mode, @@ -167,10 +167,10 @@ func parseDiffUnified(out []byte, limitBytes int) (*DiffResult, *proto.Error) { } var ( - files []DiffFile - current *DiffFile - patchBuf strings.Builder - inHunk bool + files []DiffFile + current *DiffFile + patchBuf strings.Builder + inHunk bool ) finalize := func() { diff --git a/internal/verbs/git/git.go b/internal/verbs/git/git.go index 6e83839..207a961 100644 --- a/internal/verbs/git/git.go +++ b/internal/verbs/git/git.go @@ -94,9 +94,9 @@ type Args struct { Rev string Lines string // diff-op flags - Staged bool - Context int - StatOnly bool + Staged bool + Context int + StatOnly bool LimitBytes int } @@ -331,11 +331,11 @@ func compactDiffResult(op string, d *DiffResult) any { cd.R[i] = []any{f.Path, f.OldPath, f.Status, f.Binary, f.Additions, f.Deletions, f.Patch} } return map[string]any{ - "op": op, - "k": cd.K, - "r": cd.R, - "total_additions": d.TotalAdditions, - "total_deletions": d.TotalDeletions, - "stat_only": d.StatOnly, + "op": op, + "k": cd.K, + "r": cd.R, + "total_additions": d.TotalAdditions, + "total_deletions": d.TotalDeletions, + "stat_only": d.StatOnly, } } diff --git a/internal/verbs/git/git_test.go b/internal/verbs/git/git_test.go index 207a464..0f91afc 100644 --- a/internal/verbs/git/git_test.go +++ b/internal/verbs/git/git_test.go @@ -197,14 +197,14 @@ func TestParseArgs_DefaultsAndStatus(t *testing.T) { // breaking the string-coercion path. func TestParseArgs_WireShape(t *testing.T) { a, perr := ParseArgs(map[string]any{ - "op": "log", - "limit": "5", - "context": "2", - "bytes": "512", - "untracked": "false", - "ignored": "true", - "staged": "true", - "stat": "true", + "op": "log", + "limit": "5", + "context": "2", + "bytes": "512", + "untracked": "false", + "ignored": "true", + "staged": "true", + "stat": "true", }) if perr != nil { t.Fatalf("valid string args rejected: %v", perr) diff --git a/internal/verbs/git/gogit_blame.go b/internal/verbs/git/gogit_blame.go index 20a9efa..2048ffb 100644 --- a/internal/verbs/git/gogit_blame.go +++ b/internal/verbs/git/gogit_blame.go @@ -176,4 +176,3 @@ func blameError(path, rev string, err error) *proto.Error { } return &proto.Error{Code: "git_failed", Msg: msg} } - diff --git a/internal/verbs/git/gogit_diff_worktree.go b/internal/verbs/git/gogit_diff_worktree.go index 30197ac..a13eca0 100644 --- a/internal/verbs/git/gogit_diff_worktree.go +++ b/internal/verbs/git/gogit_diff_worktree.go @@ -35,9 +35,9 @@ type customFile struct { path string } -func (f *customFile) Hash() plumbing.Hash { return f.hash } -func (f *customFile) Mode() filemode.FileMode { return f.mode } -func (f *customFile) Path() string { return f.path } +func (f *customFile) Hash() plumbing.Hash { return f.hash } +func (f *customFile) Mode() filemode.FileMode { return f.mode } +func (f *customFile) Path() string { return f.path } // customChunk implements diff.Chunk: one Equal / Add / Delete span of // the unified-diff transform. @@ -46,8 +46,8 @@ type customChunk struct { op diff.Operation } -func (c *customChunk) Content() string { return c.content } -func (c *customChunk) Type() diff.Operation { return c.op } +func (c *customChunk) Content() string { return c.content } +func (c *customChunk) Type() diff.Operation { return c.op } // customFilePatch implements diff.FilePatch for a single (before, after) // pair. New files set from=nil; deleted files set to=nil; modified @@ -58,7 +58,7 @@ type customFilePatch struct { isBinary bool } -func (p *customFilePatch) IsBinary() bool { return p.isBinary } +func (p *customFilePatch) IsBinary() bool { return p.isBinary } func (p *customFilePatch) Chunks() []diff.Chunk { return p.chunks } func (p *customFilePatch) Files() (diff.File, diff.File) { // Returning typed-nil through the interface is correct here: the diff --git a/internal/verbs/git/log.go b/internal/verbs/git/log.go index 4d84d84..479b978 100644 --- a/internal/verbs/git/log.go +++ b/internal/verbs/git/log.go @@ -39,10 +39,10 @@ const ( // LogResult is the structured replacement for `git log` text scraping. type LogResult struct { - Commits []Commit `msgpack:"commits,omitempty"` - Count int `msgpack:"count"` - Truncated bool `msgpack:"truncated,omitempty"` - TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` + Commits []Commit `msgpack:"commits,omitempty"` + Count int `msgpack:"count"` + Truncated bool `msgpack:"truncated,omitempty"` + TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` } // Commit captures one revision. Times are unix nanoseconds (consistent diff --git a/internal/verbs/grep/grep.go b/internal/verbs/grep/grep.go index 2a65843..9423dc4 100644 --- a/internal/verbs/grep/grep.go +++ b/internal/verbs/grep/grep.go @@ -92,15 +92,15 @@ type Match struct { } type Result struct { - Matches []Match `msgpack:"matches,omitempty"` - Files []string `msgpack:"files,omitempty"` // populated only when FilesOnly - Count int `msgpack:"count"` // number of records (matches+context, or files) - MatchCount int `msgpack:"match_count"` // number of "match" records (excludes context) - FileCount int `msgpack:"file_count"` // distinct files with at least one match - FilesScanned int `msgpack:"files_scanned"` // files actually opened and content-searched - FilesSkippedBinary int `msgpack:"files_skipped_binary,omitempty"` - FilesSkippedLarge int `msgpack:"files_skipped_large,omitempty"` - Truncated bool `msgpack:"truncated,omitempty"` + Matches []Match `msgpack:"matches,omitempty"` + Files []string `msgpack:"files,omitempty"` // populated only when FilesOnly + Count int `msgpack:"count"` // number of records (matches+context, or files) + MatchCount int `msgpack:"match_count"` // number of "match" records (excludes context) + FileCount int `msgpack:"file_count"` // distinct files with at least one match + FilesScanned int `msgpack:"files_scanned"` // files actually opened and content-searched + FilesSkippedBinary int `msgpack:"files_skipped_binary,omitempty"` + FilesSkippedLarge int `msgpack:"files_skipped_large,omitempty"` + Truncated bool `msgpack:"truncated,omitempty"` TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` } @@ -411,7 +411,7 @@ func (s *state) searchBody(path string, body []byte) bool { lines := splitLines(body) - pendingAfter := 0 // remaining after-context lines to emit + pendingAfter := 0 // remaining after-context lines to emit lastEmittedLine := -1 // appendRec appends a record and reports whether the global cap was hit. diff --git a/internal/verbs/grep/grep_stream_test.go b/internal/verbs/grep/grep_stream_test.go index e602d4d..f66f07e 100644 --- a/internal/verbs/grep/grep_stream_test.go +++ b/internal/verbs/grep/grep_stream_test.go @@ -12,10 +12,10 @@ import ( // chunk grep yields so tests can compare the streamed sequence against // the cumulative Result.Matches and prove they agree. type streamCapture struct { - mu sync.Mutex - items []Match - emits int - flush int + mu sync.Mutex + items []Match + emits int + flush int } func (s *streamCapture) Emit(c any) error { diff --git a/internal/verbs/grep/grep_test.go b/internal/verbs/grep/grep_test.go index 37f0e46..211c376 100644 --- a/internal/verbs/grep/grep_test.go +++ b/internal/verbs/grep/grep_test.go @@ -126,7 +126,7 @@ func TestRun_BasicLiteralCaseSensitive(t *testing.T) { // app.log is gitignored, .git/ is hidden, binary.dat is skipped. want := []string{ "a.go:2", - "b.go:3", // "Foo Foo" — only one record per matching line + "b.go:3", // "Foo Foo" — only one record per matching line "src/main.go:3", } if !equal(got, want) { @@ -162,8 +162,8 @@ func TestRun_SmartCaseLowercaseGoesInsensitive(t *testing.T) { "README.md:2", // "foo bar baz" "README.md:3", // "FOO bar" "a.go:2", - "b.go:2", // "// FOO marker" - "b.go:3", // "Foo Foo" + "b.go:2", // "// FOO marker" + "b.go:3", // "Foo Foo" "src/main.go:3", "src/util.go:2", } @@ -733,19 +733,19 @@ func equalInt(a, b []int) bool { func TestParseArgs_WireShape(t *testing.T) { root := makeTree(t) a, perr := ParseArgs(map[string]any{ - "pattern": "foo", - "path": root, - "limit": "100", - "mpf": "5", - "cb": "2", - "ca": "3", - "depth": "4", - "lit": "true", - "word": "false", - "fo": "true", - "no-text": "false", - "hidden": "true", - "gi": "false", + "pattern": "foo", + "path": root, + "limit": "100", + "mpf": "5", + "cb": "2", + "ca": "3", + "depth": "4", + "lit": "true", + "word": "false", + "fo": "true", + "no-text": "false", + "hidden": "true", + "gi": "false", }) if perr != nil { t.Fatalf("valid string args rejected: %v", perr) @@ -956,7 +956,7 @@ func TestParseArgs_AbsoluteFlag(t *testing.T) { // prepends a @0 alias table when allow_paths is configured and rewrites paths // under that root as @0/. func TestPrettyResponse_AliasTableSingleAllowPath(t *testing.T) { - root := t.TempDir() + root := t.TempDir() scratch := t.TempDir() canon := func(p string) string { if r, err := filepath.EvalSymlinks(p); err == nil { @@ -992,9 +992,9 @@ func TestPrettyResponse_AliasTableSingleAllowPath(t *testing.T) { // TestPrettyResponse_AliasTableTwoAllowPaths verifies the multi-root case: // two allow_paths entries produce @0 and @1 aliases in a single response. func TestPrettyResponse_AliasTableTwoAllowPaths(t *testing.T) { - root := t.TempDir() + root := t.TempDir() scratch := t.TempDir() - vendor := t.TempDir() + vendor := t.TempDir() canon := func(p string) string { if r, err := filepath.EvalSymlinks(p); err == nil { return r @@ -1035,7 +1035,7 @@ func TestPrettyResponse_AliasTableTwoAllowPaths(t *testing.T) { // TestPrettyResponse_FilesOnlyAliasTable verifies alias table works in files_only mode. func TestPrettyResponse_FilesOnlyAliasTable(t *testing.T) { - root := t.TempDir() + root := t.TempDir() scratch := t.TempDir() canon := func(p string) string { if r, err := filepath.EvalSymlinks(p); err == nil { diff --git a/internal/verbs/help/help.go b/internal/verbs/help/help.go index 147719a..033ff6d 100644 --- a/internal/verbs/help/help.go +++ b/internal/verbs/help/help.go @@ -19,7 +19,7 @@ import ( type ArgSchema struct { Name string `msgpack:"name"` - Type string `msgpack:"type"` // "string" | "int" | "bool" + Type string `msgpack:"type"` // "string" | "int" | "bool" Required bool `msgpack:"required,omitempty"` Default string `msgpack:"default,omitempty"` Description string `msgpack:"description"` @@ -31,8 +31,8 @@ type ArgSchema struct { } type VerbSchema struct { - Verb string `msgpack:"verb"` - Description string `msgpack:"description"` + Verb string `msgpack:"verb"` + Description string `msgpack:"description"` // Tier classifies the verb by usage pattern for optimization policy // (see docs/optimization-tiers.md). One of "A" (inner-loop agent), // "B" (episodic agent), "C" (bootstrap), "D" (instrumentation/meta). diff --git a/internal/verbs/help/help_test.go b/internal/verbs/help/help_test.go index 20548c4..a9d2441 100644 --- a/internal/verbs/help/help_test.go +++ b/internal/verbs/help/help_test.go @@ -70,8 +70,8 @@ func TestNoArgTokenBudget(t *testing.T) { // verb's arg schema. func TestGlobalFlagsFooter(t *testing.T) { for _, in := range []*Args{ - {Verb: ""}, // list-all branch - {Verb: "grep"}, // single-verb branch + {Verb: ""}, // list-all branch + {Verb: "grep"}, // single-verb branch } { result, perr := Run(in, nil) if perr != nil { @@ -138,4 +138,3 @@ func TestVerboseSurfacesLong(t *testing.T) { t.Errorf("verbose=true help missing Long marker @PATH:\n%s", verbose) } } - diff --git a/internal/verbs/hook/hook.go b/internal/verbs/hook/hook.go index 0e57234..629bf1c 100644 --- a/internal/verbs/hook/hook.go +++ b/internal/verbs/hook/hook.go @@ -46,14 +46,14 @@ import ( // values populate different subsets. Decide tolerates missing fields per // tool's expected shape. type Args struct { - ToolName string `msgpack:"tool"` - Pattern string `msgpack:"pattern,omitempty"` // Grep/Glob - Path string `msgpack:"path,omitempty"` // Grep/Glob (and harness Read in some shapes) - Glob string `msgpack:"glob,omitempty"` // Grep - Command string `msgpack:"command,omitempty"` // Bash - FilePath string `msgpack:"file,omitempty"` // Read/Edit/Write (harness key) - OldString string `msgpack:"old,omitempty"` // Edit - NewString string `msgpack:"new,omitempty"` // Edit + ToolName string `msgpack:"tool"` + Pattern string `msgpack:"pattern,omitempty"` // Grep/Glob + Path string `msgpack:"path,omitempty"` // Grep/Glob (and harness Read in some shapes) + Glob string `msgpack:"glob,omitempty"` // Grep + Command string `msgpack:"command,omitempty"` // Bash + FilePath string `msgpack:"file,omitempty"` // Read/Edit/Write (harness key) + OldString string `msgpack:"old,omitempty"` // Edit + NewString string `msgpack:"new,omitempty"` // Edit Content string `msgpack:"content,omitempty"` // Write ExcludeVerbs []string `msgpack:"exclude_verbs,omitempty"` // from ash.toml [hook].exclude_verbs } @@ -74,12 +74,12 @@ const nudgeTail = `See CLAUDE.md "When to prefer ash over bash". If ash genuinel // Read is denied for source-text files but allowed for image/PDF/notebook // formats that ash read can't render meaningfully. var allowedReadExts = map[string]bool{ - ".png": true, - ".jpg": true, - ".jpeg": true, - ".gif": true, - ".webp": true, - ".pdf": true, + ".png": true, + ".jpg": true, + ".jpeg": true, + ".gif": true, + ".webp": true, + ".pdf": true, ".ipynb": true, } @@ -435,10 +435,11 @@ func suggestStat(paths []string) string { // since pure stream-transform sed has no ash equivalent today. // // Recognized forms: -// sed -i 's|X|Y|[g]?' FILE → ash edit --old/--new [--replace_all true] -// sed -n 'A,Bp' FILE / 'Np' → ash read --range A:B -// sed -i 'A,Bd' FILE / 'Nd' → ash edit --range A:B --new '' -// anything else with a file arg → generic ash edit/read/write fallback +// +// sed -i 's|X|Y|[g]?' FILE → ash edit --old/--new [--replace_all true] +// sed -n 'A,Bp' FILE / 'Np' → ash read --range A:B +// sed -i 'A,Bd' FILE / 'Nd' → ash edit --range A:B --new '' +// anything else with a file arg → generic ash edit/read/write fallback func parseSedCommand(args []string) (file, suggestion string) { args = stripRedirections(args) var exprs []string @@ -585,7 +586,7 @@ func parseSedRange(expr string, cmd byte) (start, end int, ok bool) { // looksLikeBSDBackup returns true when s plausibly is a BSD sed -i // backup-suffix argument rather than a sed expression. The empty-string -// case (BSD's `-i ''` for no-backup) is handled by the caller before +// case (BSD's `-i ”` for no-backup) is handled by the caller before // this function is consulted. func looksLikeBSDBackup(s string) bool { if s == "~" { diff --git a/internal/verbs/hook/hook_test.go b/internal/verbs/hook/hook_test.go index 8b3bec8..adc1dba 100644 --- a/internal/verbs/hook/hook_test.go +++ b/internal/verbs/hook/hook_test.go @@ -181,91 +181,91 @@ func TestDecide_bash(t *testing.T) { // deny list and matches the actual real-world failure pattern. {name: "heredoc body with markdown table allows", command: "ash write --path docs/foo.md --content - <<'DOC_EOF'\n| find | path |\n| grep | path |\nDOC_EOF", - want: "allow"}, + want: "allow"}, {name: "unquoted heredoc body with operators allows", command: "ash write --path /tmp/x --content - < should route to ash write rather // than ash read. {name: "cat > FILE << EOF redirects to ash write", command: "cat > foo.txt << 'EOF'\nhi\nEOF", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path foo.txt --content - << 'EOF'"}, {name: "echo > FILE redirects to ash write", command: `echo "hello" > foo.txt`, - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path foo.txt"}, {name: "printf > FILE redirects to ash write", command: `printf "line1\n" > out.txt`, - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path out.txt"}, {name: "tee with output redirect routes to ash write", command: "tee >> log.txt", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path log.txt"}, {name: "cat foo > bar disambiguates to ash write (no malformed >)", command: "cat foo.txt > bar.txt", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path bar.txt"}, {name: "cat foo > /dev/null produces non-malformed write suggestion", command: "cat foo.txt > /dev/null", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path /dev/null"}, {name: "glued > suggests write", command: "echo hi >out.txt", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path out.txt"}, {name: "&> redirects also write", command: "echo hi &> out.txt", - want: "deny", wantRule: "Bash:redirect-write", + want: "deny", wantRule: "Bash:redirect-write", wantSugg: "ash write --path out.txt"}, {name: "cat with no redirect still denies as read", command: "cat foo.txt", - want: "deny", wantRule: "Bash:cat", + want: "deny", wantRule: "Bash:cat", wantSugg: "ash read --path foo.txt"}, {name: "grep with stderr redirect doesn't pollute path", command: "grep foo bar.txt 2>&1", - want: "deny", wantRule: "Bash:grep", + want: "deny", wantRule: "Bash:grep", wantSugg: "ash grep --pattern foo --path bar.txt"}, {name: "find with stderr redirect doesn't pollute path", command: "find . -name '*.go' 2>&1", - want: "deny", wantRule: "Bash:find"}, + want: "deny", wantRule: "Bash:find"}, {name: "ls -R with stdout redirect strips path target", command: "ls -R internal/ > /tmp/list", - want: "deny", wantRule: "Bash:ls-R", + want: "deny", wantRule: "Bash:ls-R", wantSugg: "ash find --path internal/"}, {name: "stat with stderr redirect doesn't pollute paths", command: "stat foo.go bar.go 2>/dev/null", - want: "deny", wantRule: "Bash:stat", + want: "deny", wantRule: "Bash:stat", wantSugg: "ash stat --paths foo.go,bar.go"}, // sed: file-mode forms route to ash edit / ash read; pure pipeline @@ -273,50 +273,50 @@ func TestDecide_bash(t *testing.T) { // ash equivalent yet. {name: "sed -i substitute routes to ash edit", command: "sed -i 's/foo/bar/' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --old foo --new bar"}, {name: "sed -i substitute with /g sets --replace_all", command: "sed -i 's|foo|bar|g' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "--replace_all true"}, {name: "sed -i.bak (BSD glued backup) routes to ash edit", command: "sed -i.bak 's/x/y/' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --old x --new y"}, {name: "sed -i '' (BSD empty backup) routes to ash edit", command: "sed -i '' 's/x/y/' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --old x --new y"}, {name: "sed -n print range routes to ash read --range", command: "sed -n '10,20p' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash read --path a.go --range 10:20"}, {name: "sed -n single-line print routes to ash read --range N:N", command: "sed -n '5p' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash read --path a.go --range 5:5"}, {name: "sed -i delete single line routes to ash edit --range", command: "sed -i '5d' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --range 5:5 --new ''"}, {name: "sed -i delete range routes to ash edit --range A:B", command: "sed -i '5,10d' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --range 5:10 --new ''"}, {name: "sed with regex address falls back to generic", command: "sed -i '/PATTERN/d' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --old "}, {name: "sed -e expression form routes to ash edit", command: "sed -e 's/x/y/' a.go", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go --old x --new y"}, {name: "sed pure pipeline (no file) allows", command: "echo hi | sed 's/x/y/'", - want: "allow"}, + want: "allow"}, {name: "sed with stderr redirect still routes via file arg", command: "sed -i 's/x/y/' a.go 2>/dev/null", - want: "deny", wantRule: "Bash:sed", + want: "deny", wantRule: "Bash:sed", wantSugg: "ash edit --path a.go"}, // ASH-142: grepLike / readLike pipeline forms (no file argument) @@ -524,14 +524,14 @@ func TestParseArgs_emptyMap(t *testing.T) { func TestShellquote(t *testing.T) { cases := map[string]string{ - "": "''", - "foo": "foo", - "foo.go": "foo.go", - "a/b/c": "a/b/c", - "foo bar": `'foo bar'`, - "can't": `'can'"'"'t'`, - "a&b": `'a&b'`, - "a*b": `'a*b'`, + "": "''", + "foo": "foo", + "foo.go": "foo.go", + "a/b/c": "a/b/c", + "foo bar": `'foo bar'`, + "can't": `'can'"'"'t'`, + "a&b": `'a&b'`, + "a*b": `'a*b'`, } for in, want := range cases { if got := shellquote(in); got != want { @@ -912,10 +912,10 @@ func TestInspectDiff_CountsSourceHeaders(t *testing.T) { return p } cases := []struct { - name string - body string - wantTarget string - wantMulti bool + name string + body string + wantTarget string + wantMulti bool }{ {"single git-style", "--- a/foo.go\n+++ b/foo.go\n@@ -1 +1 @@\n-x\n+y\n", "foo.go", false}, {"single bare path", "--- foo.go\n+++ foo.go\n@@ -1 +1 @@\n-x\n+y\n", "foo.go", false}, diff --git a/internal/verbs/initverb/initverb.go b/internal/verbs/initverb/initverb.go index 077efb3..7851ba0 100644 --- a/internal/verbs/initverb/initverb.go +++ b/internal/verbs/initverb/initverb.go @@ -8,17 +8,17 @@ // // `ash init` bootstraps a target repo for use with ash: // -// 1. Ensures a PreToolUse entry in /.claude/settings.json that -// runs `ash hook` (PATH form, so a single `make install` covers -// every target repo at once). -// 2. Appends `.ash/` to /.gitignore if a .gitignore exists. -// 3. Writes (or merges into) /CLAUDE.md the embedded -// agent-guidance section, bracketed by / -// markers so future updates are atomic. If the -// target repo already uses AGENTS.md and lacks a CLAUDE.md, the -// section is written there instead. -// 4. Records the absolute root in the global installed-repos registry -// so `ash report --all-roots` can find it. +// 1. Ensures a PreToolUse entry in /.claude/settings.json that +// runs `ash hook` (PATH form, so a single `make install` covers +// every target repo at once). +// 2. Appends `.ash/` to /.gitignore if a .gitignore exists. +// 3. Writes (or merges into) /CLAUDE.md the embedded +// agent-guidance section, bracketed by / +// markers so future updates are atomic. If the +// target repo already uses AGENTS.md and lacks a CLAUDE.md, the +// section is written there instead. +// 4. Records the absolute root in the global installed-repos registry +// so `ash report --all-roots` can find it. // // Idempotent: re-running on an already-installed repo is a no-op and // reports already_installed=true. A pre-existing settings.json entry @@ -70,6 +70,7 @@ type Result struct { AlreadyInstalled bool `msgpack:"already_installed,omitempty"` Warnings []string `msgpack:"warnings,omitempty"` } + func ParseArgs(in map[string]any) (*Args, *proto.Error) { a := &Args{} var perr *proto.Error diff --git a/internal/verbs/metrics/metrics.go b/internal/verbs/metrics/metrics.go index 1e3f6f6..fd9b735 100644 --- a/internal/verbs/metrics/metrics.go +++ b/internal/verbs/metrics/metrics.go @@ -30,16 +30,16 @@ type Args struct { } type Row struct { - Timestamp int64 `msgpack:"ts"` - Verb string `msgpack:"verb"` - OK bool `msgpack:"ok"` - ErrCode string `msgpack:"err_code,omitempty"` - TokensIn int `msgpack:"tokens_in"` - TokensOut int `msgpack:"tokens_out"` - LatencyExecUs int64 `msgpack:"latency_exec_us"` - BytesIn int `msgpack:"bytes_in"` - BytesOut int `msgpack:"bytes_out"` - Truncated bool `msgpack:"truncated,omitempty"` + Timestamp int64 `msgpack:"ts"` + Verb string `msgpack:"verb"` + OK bool `msgpack:"ok"` + ErrCode string `msgpack:"err_code,omitempty"` + TokensIn int `msgpack:"tokens_in"` + TokensOut int `msgpack:"tokens_out"` + LatencyExecUs int64 `msgpack:"latency_exec_us"` + BytesIn int `msgpack:"bytes_in"` + BytesOut int `msgpack:"bytes_out"` + Truncated bool `msgpack:"truncated,omitempty"` WalkUs int64 `msgpack:"walk_us,omitempty"` IOUs int64 `msgpack:"io_us,omitempty"` RegexUs int64 `msgpack:"regex_us,omitempty"` @@ -89,16 +89,16 @@ func ResultFromCalls(calls []ledger.Call) *Result { rows := make([]Row, 0, len(calls)) for _, c := range calls { rows = append(rows, Row{ - Timestamp: c.Timestamp.UnixNano(), - Verb: c.Verb, - OK: c.OK, - ErrCode: c.ErrCode, - TokensIn: c.TokensIn, - TokensOut: c.TokensOut, - LatencyExecUs: c.LatencyExecUs, - BytesIn: c.BytesIn, - BytesOut: c.BytesOut, - Truncated: c.Truncated, + Timestamp: c.Timestamp.UnixNano(), + Verb: c.Verb, + OK: c.OK, + ErrCode: c.ErrCode, + TokensIn: c.TokensIn, + TokensOut: c.TokensOut, + LatencyExecUs: c.LatencyExecUs, + BytesIn: c.BytesIn, + BytesOut: c.BytesOut, + Truncated: c.Truncated, WalkUs: c.WalkUs, IOUs: c.IOUs, RegexUs: c.RegexUs, diff --git a/internal/verbs/metrics/render_test.go b/internal/verbs/metrics/render_test.go index 2ca8cc7..eb22ba3 100644 --- a/internal/verbs/metrics/render_test.go +++ b/internal/verbs/metrics/render_test.go @@ -51,8 +51,8 @@ func TestPickColumns_EachFlagIndependent(t *testing.T) { // whole table — ensures we don't only check the first row. func TestPickColumns_AnyRowLightsTheColumn(t *testing.T) { rows := []Row{ - {Verb: "find"}, // no sub-phases - {Verb: "grep"}, // no sub-phases + {Verb: "find"}, // no sub-phases + {Verb: "grep"}, // no sub-phases {Verb: "stat", WalkUs: 123, IOUs: 456}, // these two should light up } cs := pickColumns(rows) @@ -158,7 +158,7 @@ func TestPrettyResponse_FullTable(t *testing.T) { TokensIn: 10, TokensOut: 100, LatencyExecUs: 500, WalkUs: 200, IOUs: 150, RegexUs: 80, RegexCompileUs: 30, LatencyDispatchUs: 20, - BytesOutEmit: 1024, TokensOutEmit: 42, + BytesOutEmit: 1024, TokensOutEmit: 42, TokensCacheHit: 500, TokensCacheMiss: 100, }, { diff --git a/internal/verbs/read/read.go b/internal/verbs/read/read.go index cd88509..f41a1ed 100644 --- a/internal/verbs/read/read.go +++ b/internal/verbs/read/read.go @@ -40,15 +40,15 @@ type Args struct { } type Result struct { - Path string `msgpack:"path"` - Content string `msgpack:"content"` - Encoding string `msgpack:"encoding"` // "utf-8" or "base64" - Size int64 `msgpack:"size"` // size of the file on disk - Mtime int64 `msgpack:"mtime"` // unix nanos - Lines int `msgpack:"lines,omitempty"` - RangeReturned string `msgpack:"range_returned,omitempty"` - Truncated bool `msgpack:"truncated,omitempty"` - TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` + Path string `msgpack:"path"` + Content string `msgpack:"content"` + Encoding string `msgpack:"encoding"` // "utf-8" or "base64" + Size int64 `msgpack:"size"` // size of the file on disk + Mtime int64 `msgpack:"mtime"` // unix nanos + Lines int `msgpack:"lines,omitempty"` + RangeReturned string `msgpack:"range_returned,omitempty"` + Truncated bool `msgpack:"truncated,omitempty"` + TruncInfo *proto.TruncInfo `msgpack:"truncation_hint,omitempty"` } // ParseArgs validates and normalizes the loosely-typed args from the wire. @@ -310,4 +310,5 @@ func PrettyResponse(req *proto.Request, rsp *proto.Response) string { } return b.String() } -// pointer or the loosely-decoded msgpack map a client receives over the wire. \ No newline at end of file + +// pointer or the loosely-decoded msgpack map a client receives over the wire. diff --git a/internal/verbs/read/read_test.go b/internal/verbs/read/read_test.go index 83d90a7..692f2f4 100644 --- a/internal/verbs/read/read_test.go +++ b/internal/verbs/read/read_test.go @@ -13,10 +13,10 @@ import ( func TestApplyRange_Lines(t *testing.T) { body := []byte("alpha\nbeta\ngamma\ndelta\nepsilon\n") cases := []struct { - name string - spec string - want string - wantRangeBack string + name string + spec string + want string + wantRangeBack string }{ {"first line", "1:1", "alpha\n", "1:1"}, {"middle range", "2:3", "beta\ngamma\n", "2:3"}, diff --git a/internal/verbs/recap/recap.go b/internal/verbs/recap/recap.go index f678e48..84efaf7 100644 --- a/internal/verbs/recap/recap.go +++ b/internal/verbs/recap/recap.go @@ -45,7 +45,7 @@ type Args struct { type FileTouch struct { Path string `msgpack:"path"` Reads int `msgpack:"reads,omitempty"` - Edits int `msgpack:"edits,omitempty"` // write+edit calls (any mutation) + Edits int `msgpack:"edits,omitempty"` // write+edit calls (any mutation) Searches int `msgpack:"searches,omitempty"` // grep/find calls whose --path matched } @@ -323,5 +323,3 @@ func prettyPath(p string) string { } return pp } - - diff --git a/internal/verbs/replay/replay.go b/internal/verbs/replay/replay.go index 5a1dba3..2fab163 100644 --- a/internal/verbs/replay/replay.go +++ b/internal/verbs/replay/replay.go @@ -122,10 +122,10 @@ type VerbSummary struct { } type Result struct { - Scope Scope `msgpack:"scope" json:"scope"` - Replayed int `msgpack:"replayed" json:"replayed"` - Skipped int `msgpack:"skipped" json:"skipped"` - SkipByReason map[string]int `msgpack:"skip_by_reason,omitempty" json:"skip_by_reason,omitempty"` + Scope Scope `msgpack:"scope" json:"scope"` + Replayed int `msgpack:"replayed" json:"replayed"` + Skipped int `msgpack:"skipped" json:"skipped"` + SkipByReason map[string]int `msgpack:"skip_by_reason,omitempty" json:"skip_by_reason,omitempty"` ByVerb []VerbSummary `msgpack:"by_verb" json:"by_verb"` Overall VerbSummary `msgpack:"overall" json:"overall"` TopRegressors []CallReplay `msgpack:"top_regressors,omitempty" json:"top_regressors,omitempty"` diff --git a/internal/verbs/replay/replay_test.go b/internal/verbs/replay/replay_test.go index a6b1e9a..b1c5023 100644 --- a/internal/verbs/replay/replay_test.go +++ b/internal/verbs/replay/replay_test.go @@ -153,9 +153,9 @@ func TestReplayOne_DeltaMath(t *testing.T) { }, } c := ledger.Call{ - Verb: "grep", - OK: true, - TokensOut: 10, + Verb: "grep", + OK: true, + TokensOut: 10, } row, _, skip := replayOne(deps, c, map[string]any{"pattern": "x", "path": "."}, 10) if skip != "" { diff --git a/internal/verbs/report/report.go b/internal/verbs/report/report.go index dcb39fb..14ef44d 100644 --- a/internal/verbs/report/report.go +++ b/internal/verbs/report/report.go @@ -70,12 +70,12 @@ type Scope struct { // Calls counts the rows that matched the scope filters; CallsTotal is // the unfiltered ledger size for context. type RootStats struct { - Root string `msgpack:"root"` - Calls int `msgpack:"calls"` - OK int `msgpack:"ok"` - TokensOut int64 `msgpack:"tokens_out"` - ExecSumUs int64 `msgpack:"exec_sum_us"` - OpenError string `msgpack:"open_error,omitempty"` + Root string `msgpack:"root"` + Calls int `msgpack:"calls"` + OK int `msgpack:"ok"` + TokensOut int64 `msgpack:"tokens_out"` + ExecSumUs int64 `msgpack:"exec_sum_us"` + OpenError string `msgpack:"open_error,omitempty"` } type Totals struct { @@ -104,7 +104,7 @@ type Totals struct { TokensCacheHit int64 `msgpack:"tokens_cache_hit,omitempty"` TokensCacheMiss int64 `msgpack:"tokens_cache_miss,omitempty"` CacheCalls int `msgpack:"cache_calls,omitempty"` - ExecSumUs int64 `msgpack:"exec_sum_us"` + ExecSumUs int64 `msgpack:"exec_sum_us"` } // VerbSubPhase holds the percentage of exec_us attributed to one named phase. @@ -134,7 +134,7 @@ type VerbArgDist struct { } type VerbStats struct { - Verb string `msgpack:"verb"` + Verb string `msgpack:"verb"` // Tier mirrors help.VerbSchema.Tier — "A" (inner-loop agent), "B" // (episodic agent), "C" (bootstrap), or "D" (instrumentation/meta). // Populated at aggregate time from the help registry; empty for diff --git a/internal/verbs/report/report_test.go b/internal/verbs/report/report_test.go index 8a44a00..b3dcb76 100644 --- a/internal/verbs/report/report_test.go +++ b/internal/verbs/report/report_test.go @@ -46,8 +46,8 @@ func TestPercentile(t *testing.T) { {[]int64{100}, 0.5, 100}, {[]int64{100}, 0.95, 100}, {[]int64{10, 20, 30, 40, 50}, 0.50, 30}, - {[]int64{10, 20, 30, 40, 50}, 0.95, 40}, // floor(0.95*4)=3 → sorted[3]=40 - {[]int64{50, 10, 30, 20, 40}, 0.50, 30}, // unsorted input + {[]int64{10, 20, 30, 40, 50}, 0.95, 40}, // floor(0.95*4)=3 → sorted[3]=40 + {[]int64{50, 10, 30, 20, 40}, 0.50, 30}, // unsorted input {[]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0.90, 9}, // floor(0.90*9)=8 → sorted[8]=9 } for _, tt := range tests { @@ -192,7 +192,10 @@ func TestAggregate_MultiVerb(t *testing.T) { } func TestFmtUs(t *testing.T) { - tests := []struct{ us int64; want string }{ + tests := []struct { + us int64 + want string + }{ {0, "0us"}, {142, "142us"}, {999, "999us"}, @@ -235,7 +238,10 @@ func TestAggregate_SubPhases_FindLike(t *testing.T) { t.Fatal("expected SubPhases to be populated") } - cases := []struct{ name string; want float64 }{ + cases := []struct { + name string + want float64 + }{ {"walk", 62}, {"io", 21}, {"regex", 0}, @@ -256,7 +262,10 @@ func TestAggregate_SubPhases_GrepLike(t *testing.T) { r := aggregate(calls, Scope{Session: "current"}) vs := r.ByVerb[0] - cases := []struct{ name string; want float64 }{ + cases := []struct { + name string + want float64 + }{ {"walk", 38}, {"io", 44}, {"regex", 8}, @@ -384,8 +393,8 @@ func TestAggregate_ErrHistogram_NoErrCode(t *testing.T) { } func TestAggregate_TruncHotspots(t *testing.T) { - calls := makeCalls("find", 3, 1000, true, true) // 3 truncated - calls = append(calls, makeCalls("grep", 2, 500, true, true)...) // 2 truncated + calls := makeCalls("find", 3, 1000, true, true) // 3 truncated + calls = append(calls, makeCalls("grep", 2, 500, true, true)...) // 2 truncated calls = append(calls, makeCalls("read", 4, 200, true, false)...) // 0 truncated r := aggregate(calls, Scope{Session: "current"}) @@ -836,12 +845,12 @@ func TestComputeHookDenials_EmptyAndAllowsOnly(t *testing.T) { func TestSuggestedAshVerb(t *testing.T) { cases := map[string]string{ - "ash grep --path . --pattern foo": "grep", - "ash read --path README.md": "read", + "ash grep --path . --pattern foo": "grep", + "ash read --path README.md": "read", "ash write --path x --content - << 'EOF'": "write", - "": "", - "grep --path .": "", - "echo ash": "", + "": "", + "grep --path .": "", + "echo ash": "", } for in, want := range cases { if got := suggestedAshVerb(in); got != want { diff --git a/internal/verbs/stat/stat_test.go b/internal/verbs/stat/stat_test.go index 64921b4..df9bcf5 100644 --- a/internal/verbs/stat/stat_test.go +++ b/internal/verbs/stat/stat_test.go @@ -390,4 +390,3 @@ func TestParseArgs_AbsoluteFlag_Stat(t *testing.T) { t.Error("Absolute default: want false") } } - diff --git a/internal/verbs/test/test.go b/internal/verbs/test/test.go index 7116a25..bb814cc 100644 --- a/internal/verbs/test/test.go +++ b/internal/verbs/test/test.go @@ -444,7 +444,7 @@ func scanEvents(r io.Reader, cap int, onPackage func(testEvent)) ([]testEvent, e // fileLineRe matches the conventional Go test failure prefix: // -// foo_test.go:42: error message +// foo_test.go:42: error message // // The path may include a leading directory (subtest output sometimes // has it). Anchored to start-of-line (multiline) so we pick the first diff --git a/internal/verbs/test/test_test.go b/internal/verbs/test/test_test.go index a876cc9..568c8ed 100644 --- a/internal/verbs/test/test_test.go +++ b/internal/verbs/test/test_test.go @@ -592,4 +592,3 @@ func TestExtractBenchLines(t *testing.T) { t.Errorf("lines[1]: %q", lines[1]) } } - diff --git a/internal/verbs/uninit/uninit.go b/internal/verbs/uninit/uninit.go index 99c1c46..e6822b7 100644 --- a/internal/verbs/uninit/uninit.go +++ b/internal/verbs/uninit/uninit.go @@ -6,10 +6,10 @@ // no_registry bool (optional) - skip the registry write // // uninit: -// 1. Removes any PreToolUse entry whose hooks invoke `ash hook` from -// /.claude/settings.json. -// 2. Removes the `.ash/` line from /.gitignore if present. -// 3. Drops the registry entry for the absolute root. +// 1. Removes any PreToolUse entry whose hooks invoke `ash hook` from +// /.claude/settings.json. +// 2. Removes the `.ash/` line from /.gitignore if present. +// 3. Drops the registry entry for the absolute root. // // Removing the only PreToolUse entry leaves the array empty (and we // strip an empty `hooks.PreToolUse` so the file doesn't grow stale diff --git a/internal/verbs/usage/usage.go b/internal/verbs/usage/usage.go index 13f8e4c..c16f064 100644 --- a/internal/verbs/usage/usage.go +++ b/internal/verbs/usage/usage.go @@ -57,11 +57,11 @@ type Args struct { // VerbStats is one row of the per-verb breakdown. type VerbStats struct { - Verb string `msgpack:"verb"` - Calls int `msgpack:"calls"` - UniqueArgs int `msgpack:"unique_args"` - CachePairs int `msgpack:"cache_pairs"` // consecutive (same verb, same args, within CacheTTL) - CacheRatio int `msgpack:"cache_ratio"` // percent — CachePairs / max(Calls-1, 1) * 100 + Verb string `msgpack:"verb"` + Calls int `msgpack:"calls"` + UniqueArgs int `msgpack:"unique_args"` + CachePairs int `msgpack:"cache_pairs"` // consecutive (same verb, same args, within CacheTTL) + CacheRatio int `msgpack:"cache_ratio"` // percent — CachePairs / max(Calls-1, 1) * 100 } // TurnsSummary is the harness-reported Anthropic cache accounting for diff --git a/internal/verbs/verbs.go b/internal/verbs/verbs.go index d0936ff..c1492fb 100644 --- a/internal/verbs/verbs.go +++ b/internal/verbs/verbs.go @@ -32,8 +32,8 @@ import ( "github.com/stazelabs/ash/internal/verbs/replay" "github.com/stazelabs/ash/internal/verbs/report" "github.com/stazelabs/ash/internal/verbs/stat" - "github.com/stazelabs/ash/internal/verbs/test" "github.com/stazelabs/ash/internal/verbs/stop" + "github.com/stazelabs/ash/internal/verbs/test" "github.com/stazelabs/ash/internal/verbs/turn" "github.com/stazelabs/ash/internal/verbs/uninit" "github.com/stazelabs/ash/internal/verbs/usage" @@ -66,30 +66,30 @@ type Runner struct { // the canonical pretty form). func PrettyHandlers() map[string]Pretty { return map[string]Pretty{ - "read": read.PrettyResponse, - "find": find.PrettyResponse, - "grep": grep.PrettyResponse, - "git": git.PrettyResponse, + "read": read.PrettyResponse, + "find": find.PrettyResponse, + "grep": grep.PrettyResponse, + "git": git.PrettyResponse, "metrics": metrics.PrettyResponse, "report": report.PrettyResponse, "recap": recap.PrettyResponse, "workspace": workspace.PrettyResponse, "replay": replay.PrettyResponse, "help": help.PrettyResponse, - "hook": hook.PrettyResponse, - "stat": stat.PrettyResponse, - "write": write.PrettyResponse, - "edit": edit.PrettyResponse, - "diff": diff.PrettyResponse, - "bench": bench.PrettyResponse, - "build": build.PrettyResponse, - "test": test.PrettyResponse, - "init": initverb.PrettyResponse, - "uninit": uninit.PrettyResponse, - "stop": stop.PrettyResponse, - "turn": turn.PrettyResponse, - "usage": usage.PrettyResponse, - "lang": lang.PrettyResponse, + "hook": hook.PrettyResponse, + "stat": stat.PrettyResponse, + "write": write.PrettyResponse, + "edit": edit.PrettyResponse, + "diff": diff.PrettyResponse, + "bench": bench.PrettyResponse, + "build": build.PrettyResponse, + "test": test.PrettyResponse, + "init": initverb.PrettyResponse, + "uninit": uninit.PrettyResponse, + "stop": stop.PrettyResponse, + "turn": turn.PrettyResponse, + "usage": usage.PrettyResponse, + "lang": lang.PrettyResponse, } } @@ -123,31 +123,31 @@ func Runners(led *ledger.Ledger, cfg *config.Config, daemonStart time.Time, proj } pretty := PrettyHandlers() runners := map[string]Runner{ - "read": wrap(read.ParseArgs, read.Run, func(r *read.Result) bool { return r.Truncated }), - "find": wrap(find.ParseArgs, find.Run, func(r *find.Result) bool { return r.Truncated }), - "grep": wrap(grep.ParseArgs, grep.Run, func(r *grep.Result) bool { return r.Truncated }), - "git": wrap(git.ParseArgs, git.Run, nil), + "read": wrap(read.ParseArgs, read.Run, func(r *read.Result) bool { return r.Truncated }), + "find": wrap(find.ParseArgs, find.Run, func(r *find.Result) bool { return r.Truncated }), + "grep": wrap(grep.ParseArgs, grep.Run, func(r *grep.Result) bool { return r.Truncated }), + "git": wrap(git.ParseArgs, git.Run, nil), "metrics": wrapLedger(led, metrics.ParseArgs, metrics.RunWithLedger), "report": wrapLedger(led, report.ParseArgs, report.RunWithLedger), "recap": wrapLedger(led, recap.ParseArgs, recap.RunWithLedger), "workspace": wrapLedger(led, workspace.ParseArgs, workspace.RunWithLedger), - "help": wrap(help.ParseArgs, help.Run, nil), - "hook": wrap(hook.ParseArgs, hook.Run, nil), - "stat": wrap(stat.ParseArgs, stat.Run, nil), - "write": wrap(write.ParseArgs, write.Run, nil), - "edit": wrap(edit.ParseArgs, edit.Run, nil), + "help": wrap(help.ParseArgs, help.Run, nil), + "hook": wrap(hook.ParseArgs, hook.Run, nil), + "stat": wrap(stat.ParseArgs, stat.Run, nil), + "write": wrap(write.ParseArgs, write.Run, nil), + "edit": wrap(edit.ParseArgs, edit.Run, nil), "build": wrap(build.ParseArgs, func(a *build.Args, tr *proto.Tracer) (*build.Result, *proto.Error) { return build.Run(a, tr, cfg.Runner.Build) }, func(r *build.Result) bool { return r.Truncated }), - "diff": wrap(diff.ParseArgs, diff.Run, nil), + "diff": wrap(diff.ParseArgs, diff.Run, nil), "test": wrap(test.ParseArgs, func(a *test.Args, tr *proto.Tracer) (*test.Result, *proto.Error) { return test.Run(a, tr, cfg.Runner.Test) }, func(r *test.Result) bool { return r.Truncated }), - "init": wrap(initverb.ParseArgs, initverb.Run, nil), - "uninit": wrap(uninit.ParseArgs, uninit.Run, nil), - "stop": wrap(stop.ParseArgs, stop.Run, nil), - "turn": wrapLedger(led, turn.ParseArgs, turn.RunWithLedger), - "usage": wrapLedger(led, usage.ParseArgs, usage.RunWithLedger), + "init": wrap(initverb.ParseArgs, initverb.Run, nil), + "uninit": wrap(uninit.ParseArgs, uninit.Run, nil), + "stop": wrap(stop.ParseArgs, stop.Run, nil), + "turn": wrapLedger(led, turn.ParseArgs, turn.RunWithLedger), + "usage": wrapLedger(led, usage.ParseArgs, usage.RunWithLedger), } runners["bench"] = Runner{ Run: func(args map[string]any, _ *proto.Tracer) (any, *proto.Error) { diff --git a/internal/verbs/workspace/workspace.go b/internal/verbs/workspace/workspace.go index 09488b2..41dce07 100644 --- a/internal/verbs/workspace/workspace.go +++ b/internal/verbs/workspace/workspace.go @@ -60,12 +60,12 @@ type SearchEntry struct { } type GitStatus struct { - Branch string `msgpack:"branch,omitempty"` - Head string `msgpack:"head,omitempty"` - Clean bool `msgpack:"clean"` - Staged int `msgpack:"staged,omitempty"` - Unstaged int `msgpack:"unstaged,omitempty"` - Untracked int `msgpack:"untracked,omitempty"` + Branch string `msgpack:"branch,omitempty"` + Head string `msgpack:"head,omitempty"` + Clean bool `msgpack:"clean"` + Staged int `msgpack:"staged,omitempty"` + Unstaged int `msgpack:"unstaged,omitempty"` + Untracked int `msgpack:"untracked,omitempty"` // Available is false when no git repo was detected; the rest of // the struct is then meaningless and PrettyResponse skips the line. Available bool `msgpack:"available"` @@ -73,10 +73,10 @@ type GitStatus struct { } type LastError struct { - Verb string `msgpack:"verb"` - Code string `msgpack:"code"` - Msg string `msgpack:"msg,omitempty"` - Ts int64 `msgpack:"ts"` + Verb string `msgpack:"verb"` + Code string `msgpack:"code"` + Msg string `msgpack:"msg,omitempty"` + Ts int64 `msgpack:"ts"` } type Result struct { @@ -386,6 +386,3 @@ func truncate(s string, n int) string { } return s[:n] + "…" } - - - diff --git a/internal/verbs/workspace/workspace_test.go b/internal/verbs/workspace/workspace_test.go index 6962835..fe37211 100644 --- a/internal/verbs/workspace/workspace_test.go +++ b/internal/verbs/workspace/workspace_test.go @@ -114,7 +114,7 @@ func TestRecent_Caps(t *testing.T) { var calls []ledger.Call for i := 0; i < 10; i++ { calls = append(calls, ledger.Call{ - Verb: "read", + Verb: "read", ArgsMsgpack: mkArgs(t, map[string]any{"path": "f" + string(rune('a'+i)) + ".go"}), Timestamp: now.Add(time.Duration(-i) * time.Second), }) diff --git a/internal/verbs/write/write_test.go b/internal/verbs/write/write_test.go index 7c2fe68..e0e3d32 100644 --- a/internal/verbs/write/write_test.go +++ b/internal/verbs/write/write_test.go @@ -45,8 +45,8 @@ func TestParseArgs_InvalidEncoding(t *testing.T) { // values (the wire shape from CLI parseFlags) and rejects garbage. func TestParseArgs_WireShape(t *testing.T) { a, perr := ParseArgs(map[string]any{ - "path": "f.go", - "mkdir": "false", + "path": "f.go", + "mkdir": "false", "no-clobber": "true", }) if perr != nil { diff --git a/internal/vocab/types.go b/internal/vocab/types.go index 30575a6..e0a58ee 100644 --- a/internal/vocab/types.go +++ b/internal/vocab/types.go @@ -6,18 +6,18 @@ // - flags: flag names per verb (also from help.Registry). // - enums: flag value enums (Values field on help.ArgSchema). // - errors: error codes from &proto.Error{Code: "..."} composite -// literals across internal/verbs/, internal/runner/, -// internal/jail/ (AST walk). +// literals across internal/verbs/, internal/runner/, +// internal/jail/ (AST walk). // - status: status enum values (test/stop/git-diff status fields). -// Hand-curated; the ~10 values are too few to AST-walk -// reliably and the source-of-truth comment lives next to -// the assignment sites. +// Hand-curated; the ~10 values are too few to AST-walk +// reliably and the source-of-truth comment lives next to +// the assignment sites. // - headers: the `§: …` sentinel from pretty renderers, -// plus the `[ash …]` -// metrics footer and the `[truncation: …]` annotation. -// AST scan for format-string literals. +// plus the `[ash …]` +// metrics footer and the `[truncation: …]` annotation. +// AST scan for format-string literals. // - labels: other label-shaped substrings inside PrettyResponse -// format strings — best-effort heuristic extraction. +// format strings — best-effort heuristic extraction. // // Output: Inventory.Markdown() and Inventory.JSON(). package vocab