@@ -163,6 +163,9 @@ func TestRegistryLargeSingleLineFileRetainsHeadAndTail(t *testing.T) {
163163 if len (result .Output ) > readOutputBudgetBytes || ! utf8 .ValidString (result .Output ) {
164164 t .Fatalf ("single-line file output is not safely bounded: bytes=%d valid=%t" , len (result .Output ), utf8 .ValidString (result .Output ))
165165 }
166+ if rawBytes , err := strconv .Atoi (result .Meta ["raw_bytes" ]); err != nil || rawBytes <= len (result .Output ) {
167+ t .Fatalf ("single-line file capture was not bounded head/tail output: raw=%q emitted=%d" , result .Meta ["raw_bytes" ], len (result .Output ))
168+ }
166169 for _ , want := range []string {"File: large.min.js" , "HEAD_SINGLE_LINE_MARK" , "TAIL_SINGLE_LINE_MARK" } {
167170 if ! strings .Contains (result .Output , want ) {
168171 t .Fatalf ("single-line file output missing %q" , want )
@@ -235,13 +238,72 @@ func TestShellOutputCategoryClassification(t *testing.T) {
235238 }
236239}
237240
238- func TestSelfBudgetedToolDeclaresSemanticCategoryWithoutRebudgeting (t * testing.T ) {
241+ func TestSelfManagedOutputBudgetUsesSemanticTestPolicy (t * testing.T ) {
242+ setTestTempDir (t )
243+ input := "stdout:\n " + strings .Repeat ("PASS progress\n " , 8_000 ) +
244+ "--- FAIL: TestImportant (0.01s)\n thing_test.go:42: expected 7, got 9\n FAIL\n exit_code: 1"
245+ tests := []struct {
246+ name string
247+ tool Tool
248+ args map [string ]any
249+ }{
250+ {name : "bash" , tool : NewBashTool (t .TempDir ()), args : map [string ]any {"command" : "go test ./..." }},
251+ {name : "exec" , tool : NewExecCommandTool (t .TempDir (), newExecSessionManager ()), args : map [string ]any {"cmd" : "go test ./..." , "max_output_tokens" : 16_000 }},
252+ }
253+ for _ , test := range tests {
254+ t .Run (test .name , func (t * testing.T ) {
255+ got := applySelfManagedOutputBudget (test .tool , test .tool .Name (), test .args , Result {Status : StatusError , Output : input , Meta : map [string ]string {}})
256+ if ! got .Truncated || got .Meta [outputBudgetCategoryMeta ] != string (outputCategoryTest ) {
257+ t .Fatalf ("semantic budget was not applied: %#v" , got )
258+ }
259+ for _ , want := range []string {"TestImportant" , "expected 7" , "FAIL" , "exit_code: 1" } {
260+ if ! strings .Contains (got .Output , want ) {
261+ t .Fatalf ("semantic test output missing %q:\n %s" , want , got .Output )
262+ }
263+ }
264+ })
265+ }
266+ }
267+
268+ type semanticBashFakeTool struct { ceilingFakeTool }
269+
270+ func (semanticBashFakeTool ) managesOutputBudget () {}
271+
272+ func (semanticBashFakeTool ) outputCategory (args map [string ]any ) outputCategory {
273+ command , _ := args ["command" ].(string )
274+ return shellOutputCategory (command )
275+ }
276+
277+ func TestRegistryAppliesSemanticBudgetToSelfManagedShellOutput (t * testing.T ) {
278+ setTestTempDir (t )
279+ input := "startup\n " + strings .Repeat ("PASS progress\n " , 8_000 ) +
280+ "--- FAIL: TestRegistryImportant (0.01s)\n expected 7, got 9\n FAIL\n exit_code: 1"
281+ registry := NewRegistry ()
282+ registry .Register (semanticBashFakeTool {newCeilingFakeTool ("bash" , input )})
283+ result := registry .Run (context .Background (), "bash" , map [string ]any {"command" : "go test ./..." })
284+ if ! result .Truncated || result .Meta [outputBudgetReasonMeta ] != "semantic_test_budget" {
285+ t .Fatalf ("registry did not apply semantic test budget: %#v" , result .Meta )
286+ }
287+ for _ , want := range []string {"TestRegistryImportant" , "expected 7" , "FAIL" , "exit_code: 1" } {
288+ if ! strings .Contains (result .Output , want ) {
289+ t .Fatalf ("registry semantic test output missing %q:\n %s" , want , result .Output )
290+ }
291+ }
292+ }
293+
294+ func TestRegistryAppliesSemanticBudgetToSelfManagedProcessOutput (t * testing.T ) {
295+ setTestTempDir (t )
296+ input := "starting build\n " + strings .Repeat ("progress\n " , 8_000 ) +
297+ "WARNING: cache is cold\n ERROR: compilation failed\n exit_code: 1"
239298 registry := NewRegistry ()
240- registry .Register (NewBashTool (t .TempDir ()))
241- tool , _ := registry .Get ("bash" )
242- result := Result {Status : StatusOK , Output : "already bounded" , Meta : map [string ]string {}}
243- got := annotateSelfBudgetedOutput (tool , "bash" , map [string ]any {"command" : "go test ./..." }, result )
244- if got .Output != result .Output || got .Meta [outputBudgetCategoryMeta ] != string (outputCategoryTest ) {
245- t .Fatalf ("self-budgeted category annotation changed output or lost category: %#v" , got )
299+ registry .Register (semanticBashFakeTool {newCeilingFakeTool ("bash" , input )})
300+ result := registry .Run (context .Background (), "bash" , map [string ]any {"command" : "make build" })
301+ if ! result .Truncated || result .Meta [outputBudgetReasonMeta ] != "semantic_process_budget" {
302+ t .Fatalf ("registry did not apply semantic process budget: %#v" , result .Meta )
303+ }
304+ for _ , want := range []string {"starting build" , "WARNING" , "ERROR" , "exit_code: 1" } {
305+ if ! strings .Contains (result .Output , want ) {
306+ t .Fatalf ("registry semantic process output missing %q:\n %s" , want , result .Output )
307+ }
246308 }
247309}
0 commit comments