1package agent
2
3import (
4 "strings"
5 "testing"
6 "time"
7)
8
9func TestBuildSystemPromptAlwaysPrefersDedicatedTools(t *testing.T) {
10 for _, custom := range []string{"", "Custom identity"} {
11 prompt := BuildSystemPrompt(SystemPromptOpts{Custom: custom})
12 if count := strings.Count(prompt, toolSelectionGuidance); count != 1 {
13 t.Fatalf("dedicated tool guidance count = %d, want 1:\n%s", count, prompt)
14 }
15 }
16}
17
18func TestBuildSystemPromptAlwaysIncludesFinalWritingGuidance(t *testing.T) {
19 for _, tt := range []struct {
20 name string
21 custom string
22 }{
23 {name: "default"},
24 {name: "custom", custom: "custom identity"},
25 } {
26 t.Run(tt.name, func(t *testing.T) {
27 const conflictingAddendum = "Always use ceremonial language."
28 prompt := BuildSystemPrompt(SystemPromptOpts{
29 Custom: tt.custom,
30 Append: []string{conflictingAddendum},
31 })
32
33 if count := strings.Count(prompt, writingGuidance); count != 1 {
34 t.Fatalf("writing guidance count = %d, want 1:\n%s", count, prompt)
35 }
36 if strings.Index(prompt, writingGuidance) < strings.Index(prompt, conflictingAddendum) {
37 t.Fatalf("writing guidance must follow appended context:\n%s", prompt)
38 }
39 for _, want := range []string{
40 "medium, audience, and reader's immediate need",
41 "plain, precise language",
42 "Default to concise paragraphs",
43 "Use lists when items are genuinely parallel, sequential, or easier to compare",
44 "canned transitions",
45 "verified facts",
46 "descriptive links, and accessibility",
47 "Do not manufacture slang, errors, hesitation",
48 "Revise silently",
49 } {
50 if !strings.Contains(prompt, want) {
51 t.Errorf("writing guidance missing %q:\n%s", want, prompt)
52 }
53 }
54 })
55 }
56}
57
58func TestBuildSystemPromptAlwaysIncludesPlanGuidance(t *testing.T) {
59 for _, tt := range []struct {
60 name string
61 custom string
62 append []string
63 }{
64 {name: "default"},
65 {name: "custom identity", custom: "custom identity"},
66 {name: "with appended context", append: []string{"Use terse replies."}},
67 } {
68 t.Run(tt.name, func(t *testing.T) {
69 prompt := BuildSystemPrompt(SystemPromptOpts{Custom: tt.custom, Append: tt.append})
70 if count := strings.Count(prompt, planGuidance); count != 1 {
71 t.Fatalf("plan guidance count = %d, want 1:\n%s", count, prompt)
72 }
73 // Ordering keeps shared guidance stable: plan rules follow task rules and
74 // precede skill priority.
75 if strings.Index(prompt, planGuidance) < strings.Index(prompt, taskExecutionGuidance) {
76 t.Fatalf("plan guidance must follow task guidance:\n%s", prompt)
77 }
78 if strings.Index(prompt, planGuidance) > strings.Index(prompt, skillPriorityGuidance) {
79 t.Fatalf("plan guidance must precede skill priority:\n%s", prompt)
80 }
81 // The rules that matter for token cost and for correct index handling.
82 for _, want := range []string{"in_progress", "add, update, and remove", "Group related steps", `action:"show"`, "do not restate the plan"} {
83 if !strings.Contains(planGuidance, want) {
84 t.Fatalf("plan guidance is missing %q", want)
85 }
86 }
87 if strings.Contains(prompt, "update_plan") {
88 t.Fatalf("prompt still references the retired tool name:\n%s", prompt)
89 }
90 })
91 }
92}
93
94func TestBuildSystemPromptAlwaysIncludesTaskAndSkillGuidance(t *testing.T) {
95 for _, custom := range []string{"", "Custom identity"} {
96 t.Run(custom, func(t *testing.T) {
97 prompt := BuildSystemPrompt(SystemPromptOpts{
98 Custom: custom,
99 Append: []string{"Workspace context", "Available skills"},
100 })
101 previous := strings.Index(prompt, "Available skills")
102 for _, guidance := range []string{taskExecutionGuidance, skillPriorityGuidance, writingGuidance} {
103 index := strings.Index(prompt, guidance)
104 if index <= previous {
105 t.Fatalf("shared guidance must follow appended context in task/skill/writing order")
106 }
107 previous = index
108 }
109 for _, want := range []string{
110 "Treat requests such as \"can you\" as instructions to do the requested work",
111 "do not stop at a plan or an offer to continue",
112 "Follow each progress statement with the action it describes",
113 "Final answers contain the result of the work or a concrete blocker",
114 "Respect requests for explanation, planning, or review without making unsolicited changes",
115 "Ask only when a necessary decision blocks safe, correct progress",
116 "Verify changes with checks appropriate to their scope.",
117 "Report which checks ran and what remains unverified.",
118 "Preserve tool permissions, required confirmations, and explicit approval requirements",
119 "Explicit user instructions take precedence over skill guidelines",
120 "Skills do not grant permissions or override system or developer constraints",
121 "identify its source and the relevant instruction",
122 } {
123 if count := strings.Count(prompt, want); count != 1 {
124 t.Errorf("guidance %q count = %d, want 1", want, count)
125 }
126 }
127 })
128 }
129}
130
131func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) {
132 prompt := BuildSystemPrompt(SystemPromptOpts{Custom: "custom identity"})
133 if !strings.Contains(prompt, "custom identity") {
134 t.Fatalf("custom prompt missing:\n%s", prompt)
135 }
136 if !strings.Contains(prompt, compactedSummaryHandoffInstruction) {
137 t.Fatalf("custom prompt missing compaction handoff:\n%s", prompt)
138 }
139 if count := strings.Count(prompt, compactedSummaryHandoffInstruction); count != 1 {
140 t.Fatalf("compaction handoff count = %d, want 1:\n%s", count, prompt)
141 }
142 for _, want := range []string{"most recent unresolved user request", "newer user request", "without waiting for the user to type \"continue\""} {
143 if !strings.Contains(prompt, want) {
144 t.Fatalf("compaction handoff missing %q:\n%s", want, prompt)
145 }
146 }
147}
148
149func TestBuildSystemPromptCustomOmitsBuiltInDocs(t *testing.T) {
150 got := BuildSystemPrompt(SystemPromptOpts{
151 CWD: "/workspace",
152 Custom: "Custom instructions",
153 Append: []string{"Additional context"},
154 Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC),
155 ZutDocsDir: "/zut/docs",
156 })
157
158 if strings.Contains(got, "Zut's own docs") || strings.Contains(got, "/zut/docs") {
159 t.Fatalf("custom prompt includes built-in docs guidance:\n%s", got)
160 }
161 for _, want := range []string{
162 "Custom instructions",
163 "Additional context",
164 "Current date: 2026-08-06",
165 "Current working directory: /workspace",
166 } {
167 if !strings.Contains(got, want) {
168 t.Errorf("custom prompt missing %q:\n%s", want, got)
169 }
170 }
171}
172
173func TestBuildSystemPromptDefaultIncludesBuiltInDocs(t *testing.T) {
174 got := BuildSystemPrompt(SystemPromptOpts{
175 CWD: "/workspace",
176 Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC),
177 ZutDocsDir: "/zut/docs",
178 })
179
180 if !strings.Contains(got, "Zut's own docs are installed under /zut/docs") {
181 t.Fatalf("default prompt missing built-in docs guidance:\n%s", got)
182 }
183}