v0.42.0 82578de

ponytail_test.go RawHistoryFolder

1package agent
2
3import (
4	"os"
5	"path/filepath"
6	"strings"
7	"testing"
8)
9
10func TestConfigPonytailModeDefaultsToEnabled(t *testing.T) {
11	cfg := Config{}
12	if !cfg.PonytailModeEnabled() {
13		t.Fatal("Ponytail mode should default to enabled")
14	}
15
16	disabled := false
17	cfg.PonytailEnabled = &disabled
18	if cfg.PonytailModeEnabled() {
19		t.Fatal("Ponytail mode should honor an explicit false value")
20	}
21
22	enabled := true
23	cfg.PonytailEnabled = &enabled
24	if !cfg.PonytailModeEnabled() {
25		t.Fatal("Ponytail mode should honor an explicit true value")
26	}
27}
28
29func TestPonytailSystemAddendumIsCompact(t *testing.T) {
30	addendum := PonytailSystemAddendum()
31	if addendum == "" {
32		t.Fatal("Ponytail system addendum is empty")
33	}
34	if len(addendum) > 2000 {
35		t.Fatalf("Ponytail system addendum is %d bytes, want at most 2000", len(addendum))
36	}
37	for _, want := range []string{"YAGNI", "standard library", "validation", "ordinary conversation"} {
38		if !strings.Contains(addendum, want) {
39			t.Fatalf("Ponytail system addendum missing %q:\n%s", want, addendum)
40		}
41	}
42}
43
44func TestResolvePonytailModeDefaultAndExplicitDisable(t *testing.T) {
45	cases := []struct {
46		name     string
47		disabled bool
48		want     bool
49	}{
50		{name: "default enabled", want: true},
51		{name: "explicitly disabled", disabled: true, want: false},
52	}
53
54	for _, tc := range cases {
55		t.Run(tc.name, func(t *testing.T) {
56			root := t.TempDir()
57			t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
58			t.Setenv("HOME", filepath.Join(root, "home"))
59			if tc.disabled {
60				disabled := false
61				if err := SaveConfig(Config{PonytailEnabled: &disabled}); err != nil {
62					t.Fatal(err)
63				}
64			}
65
66			resolved, err := Resolve(Args{
67				Provider: "ollama",
68				Model:    "any-local-model",
69				CWD:      filepath.Join(root, "project"),
70				NoLSP:    true,
71				NoSkill:  true,
72				NoTools:  true,
73			}, false)
74			if err != nil {
75				t.Fatalf("Resolve failed: %v", err)
76			}
77			got := strings.Contains(resolved.SystemPrompt, PonytailSystemAddendum())
78			if got != tc.want {
79				t.Fatalf("Ponytail prompt inclusion = %v, want %v:\n%s", got, tc.want, resolved.SystemPrompt)
80			}
81			if count := strings.Count(resolved.SystemPrompt, PonytailSystemAddendum()); count != btoi(tc.want) {
82				t.Fatalf("Ponytail prompt occurrence count = %d, want %d", count, btoi(tc.want))
83			}
84		})
85	}
86}
87
88func btoi(value bool) int {
89	if value {
90		return 1
91	}
92	return 0
93}
94
95func TestResolvePonytailModeRetainsAddendumWithSystemFile(t *testing.T) {
96	root := t.TempDir()
97	zutHome := filepath.Join(root, "zut-home")
98	t.Setenv("ZUT_HOME", zutHome)
99	t.Setenv("HOME", filepath.Join(root, "home"))
100	if err := os.MkdirAll(zutHome, 0o755); err != nil {
101		t.Fatal(err)
102	}
103	if err := os.WriteFile(filepath.Join(zutHome, "SYSTEM.md"), []byte("custom system identity"), 0o600); err != nil {
104		t.Fatal(err)
105	}
106
107	resolved, err := Resolve(Args{
108		Provider: "ollama",
109		Model:    "any-local-model",
110		CWD:      filepath.Join(root, "project"),
111		NoLSP:    true,
112		NoSkill:  true,
113		NoTools:  true,
114	}, false)
115	if err != nil {
116		t.Fatalf("Resolve failed: %v", err)
117	}
118	if !strings.Contains(resolved.SystemPrompt, "custom system identity") {
119		t.Fatalf("SYSTEM.md content missing:\n%s", resolved.SystemPrompt)
120	}
121	if !strings.Contains(resolved.SystemPrompt, PonytailSystemAddendum()) {
122		t.Fatalf("SYSTEM.md replacement lost Ponytail addendum:\n%s", resolved.SystemPrompt)
123	}
124}
125
126func TestResolvePonytailModePrecedesExplicitAppendPrompt(t *testing.T) {
127	root := t.TempDir()
128	t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
129	t.Setenv("HOME", filepath.Join(root, "home"))
130
131	const explicit = "explicit invocation guidance"
132	resolved, err := Resolve(Args{
133		Provider:           "ollama",
134		Model:              "any-local-model",
135		CWD:                filepath.Join(root, "project"),
136		NoLSP:              true,
137		NoSkill:            true,
138		NoTools:            true,
139		AppendSystemPrompt: []string{explicit},
140	}, false)
141	if err != nil {
142		t.Fatalf("Resolve failed: %v", err)
143	}
144	ponytailIndex := strings.Index(resolved.SystemPrompt, PonytailSystemAddendum())
145	explicitIndex := strings.Index(resolved.SystemPrompt, explicit)
146	if ponytailIndex < 0 || explicitIndex < 0 || ponytailIndex > explicitIndex {
147		t.Fatalf("prompt ordering = Ponytail %d, explicit %d:\n%s", ponytailIndex, explicitIndex, resolved.SystemPrompt)
148	}
149}
150
151func TestResolvePonytailModeAppliesAcrossHeadlessModesAndCustomIdentity(t *testing.T) {
152	root := t.TempDir()
153	t.Setenv("ZUT_HOME", filepath.Join(root, "zut-home"))
154	t.Setenv("HOME", filepath.Join(root, "home"))
155
156	modes := []Mode{"", ModePrint, ModeStream, ModeJSON, ModeRPC}
157	for _, mode := range modes {
158		resolved, err := Resolve(Args{
159			Mode:         mode,
160			Provider:     "ollama",
161			Model:        "any-local-model",
162			CWD:          filepath.Join(root, "project"),
163			NoLSP:        true,
164			NoSkill:      true,
165			NoTools:      true,
166			SystemPrompt: "custom identity",
167		}, false)
168		if err != nil {
169			t.Fatalf("Resolve(%q) failed: %v", mode, err)
170		}
171		if !strings.Contains(resolved.SystemPrompt, "custom identity") {
172			t.Fatalf("Resolve(%q) lost custom identity:\n%s", mode, resolved.SystemPrompt)
173		}
174		if !strings.Contains(resolved.SystemPrompt, PonytailSystemAddendum()) {
175			t.Fatalf("Resolve(%q) omitted Ponytail guidance:\n%s", mode, resolved.SystemPrompt)
176		}
177	}
178}
179
180func TestConfigSettingsStorePersistsPonytailWithoutChangingKnownFields(t *testing.T) {
181	t.Setenv("ZUT_HOME", t.TempDir())
182	if err := SaveConfig(Config{Theme: "dark", Provider: "ollama", Model: "local"}); err != nil {
183		t.Fatal(err)
184	}
185
186	if err := (configSettingsStore{}).SetPonytailEnabled(false); err != nil {
187		t.Fatal(err)
188	}
189	cfg, err := LoadConfig()
190	if err != nil {
191		t.Fatal(err)
192	}
193	if cfg.PonytailEnabled == nil || *cfg.PonytailEnabled {
194		t.Fatal("Ponytail mode was not persisted as disabled")
195	}
196	if cfg.Theme != "dark" || cfg.Provider != "ollama" || cfg.Model != "local" {
197		t.Fatalf("known config fields changed: %#v", cfg)
198	}
199}

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 476

↑↓ moveenter openesc close