main 4509ec7

schema_compat_test.go RawHistoryFolder

1package tools
2
3import (
4	"encoding/json"
5	"testing"
6)
7
8// TestToolSchemasAnthropicCompatible guards the wire contract with the
9// Claude API: recent models (Opus 5.5, Fable 5.1) reject composition
10// keywords (oneOf/anyOf/allOf) at the root of a tool input_schema with a
11// 400. Every built-in schema must keep a plain object root.
12func TestToolSchemasAnthropicCompatible(t *testing.T) {
13	schemas := map[string]json.RawMessage{
14		"ask_user_question":   (&AskUserQuestionTool{}).Schema(),
15		"ast":                 (&ASTTool{}).Schema(),
16		"bash":                (&BashTool{}).Schema(),
17		"create_worktree":     (&CreateWorktreeTool{}).Schema(),
18		"edit":                (&EditTool{}).Schema(),
19		"glob":                (&GlobTool{}).Schema(),
20		"grep":                (&GrepTool{}).Schema(),
21		"lsp":                 (&LSPTool{}).Schema(),
22		"manage_worktrees":    (&ManageWorktreesTool{}).Schema(),
23		"plan":                (&PlanTool{}).Schema(),
24		"python":              (&PythonTool{}).Schema(),
25		"read":                (&ReadTool{}).Schema(),
26		"schedule":            (&ScheduleTool{}).Schema(),
27		"subagent":            (&SubagentTool{}).Schema(),
28		"telegram_send_file":  (&TelegramSendFileTool{}).Schema(),
29		"telegram_send_image": (&TelegramSendImageTool{}).Schema(),
30		"update_goal":         (&UpdateGoalTool{}).Schema(),
31		"web_click":           (&WebClickTool{}).Schema(),
32		"web_find":            (&WebFindTool{}).Schema(),
33		"web_open":            (&WebOpenTool{}).Schema(),
34		"web_search":          (&WebSearchTool{}).Schema(),
35		"worktree":            (&WorktreeTool{}).Schema(),
36		"write":               (&WriteTool{}).Schema(),
37	}
38	for name, raw := range schemas {
39		var schema map[string]any
40		if err := json.Unmarshal(raw, &schema); err != nil {
41			t.Fatalf("%s: invalid JSON schema: %v", name, err)
42		}
43		for _, key := range []string{"oneOf", "anyOf", "allOf"} {
44			if _, ok := schema[key]; ok {
45				t.Errorf("%s: top-level %q rejected by Claude API", name, key)
46			}
47		}
48		if schema["type"] != "object" {
49			t.Errorf("%s: root type = %v, want object", name, schema["type"])
50		}
51	}
52}
53
54// TestWebOpenExactlyOneRefOrURL keeps the exactly-one-of contract after
55// the oneOf keyword was removed from the web_open schema (Claude API
56// rejects top-level composition). parseWebOpenArgs remains the enforcer.
57func TestWebOpenExactlyOneRefOrURL(t *testing.T) {
58	for _, tc := range []struct {
59		name  string
60		raw   string
61		want  bool
62		refID string
63		url   string
64	}{
65		{"ref only", `{"ref_id":"web-1"}`, true, "web-1", ""},
66		{"url only", `{"url":"https://example.com"}`, true, "", "https://example.com"},
67		{"both", `{"ref_id":"web-1","url":"https://example.com"}`, false, "", ""},
68		{"neither", `{}`, false, "", ""},
69	} {
70		t.Run(tc.name, func(t *testing.T) {
71			args, ok := parseWebOpenArgs(json.RawMessage(tc.raw))
72			if ok != tc.want {
73				t.Fatalf("ok = %v, want %v", ok, tc.want)
74			}
75			if ok && (args.refID != tc.refID || args.url != tc.url) {
76				t.Fatalf("args = %+v", args)
77			}
78		})
79	}
80}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close