Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...

Expand Down
6 changes: 3 additions & 3 deletions cmd/ash/hook_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}`),
Expand Down
2 changes: 1 addition & 1 deletion cmd/ash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 0 additions & 2 deletions cmd/ash/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -491,4 +490,3 @@ func TestResolveAtFile_CoexistsWithStdin(t *testing.T) {
t.Errorf("new: got %q, want \"from-stdin\"", args["new"])
}
}

6 changes: 3 additions & 3 deletions cmd/ashd/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 21 additions & 21 deletions cmd/encexplore/subs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,40 @@ 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
},
},

// ─── CJK arbitrary glyphs (single-token, opaque sentinels) ───────────
{
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
},
},

// ─── Status-enum CJK (test verb) ─────────────────────────────────────
{
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.
},
},
Expand Down Expand Up @@ -145,7 +145,7 @@ var subSets = []subSet{
{
Name: "truncation_compact", Surface: "truncation",
Pairs: [][2]string{
{"TRUNCATED", "…"}, // header sentinel
{"TRUNCATED", "…"}, // header sentinel
{"[truncation:", "[…"}, // detail-prefix
},
},
Expand Down
20 changes: 10 additions & 10 deletions cmd/encexplore/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
Expand Down
34 changes: 17 additions & 17 deletions cmd/harnessbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
24 changes: 12 additions & 12 deletions cmd/mcpbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions cmd/wirecmp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down
Loading