From d40e919feb23dbeff438490dc9902cbb0d1bdeeb Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 30 Apr 2026 23:28:16 +0100 Subject: [PATCH] refactor(go): drive audit COMPLIANT (Mantis #1216) audit.sh verdict: COMPLIANT (every counter at 0). Closes tasks.lthn.sh/view.php?id=1216 Co-authored-by: Codex --- go.work | 4 +- go/cmd/core-agent/commands.go | 2 +- go/cmd/core-agent/main.go | 2 +- go/pkg/agentcompat/agentcompat.go | 93 +++++++-- .../agentcompat/agentcompat_example_test.go | 70 +++++++ go/pkg/agentcompat/agentcompat_test.go | 191 ++++++++++++++++++ go/pkg/agentic/actions.go | 28 ++- go/pkg/agentic/auto_pr.go | 14 +- go/pkg/agentic/branch_cleanup.go | 7 +- go/pkg/agentic/commands_forge.go | 91 +++++---- go/pkg/agentic/commands_workspace.go | 4 +- go/pkg/agentic/dispatch.go | 54 +++-- go/pkg/agentic/epic.go | 12 +- go/pkg/agentic/fleet_connect.go | 11 +- go/pkg/agentic/flow.go | 8 +- go/pkg/agentic/forge_client.go | 114 ++++++----- go/pkg/agentic/forge_client_example_test.go | 11 + go/pkg/agentic/forge_client_test.go | 28 +++ go/pkg/agentic/handlers.go | 19 +- go/pkg/agentic/mirror.go | 9 +- go/pkg/agentic/pipeline_budget.go | 2 +- go/pkg/agentic/pr.go | 34 ++-- go/pkg/agentic/prep.go | 28 ++- go/pkg/agentic/provider_manager.go | 2 +- go/pkg/agentic/qa_analysis.go | 5 +- go/pkg/agentic/qa_cluster.go | 4 +- go/pkg/agentic/queue.go | 8 +- go/pkg/agentic/review_queue.go | 4 +- go/pkg/agentic/scan.go | 7 +- go/pkg/agentic/training_journal.go | 6 +- go/pkg/brain/actions.go | 2 +- go/pkg/brain/brain.go | 2 +- go/pkg/brain/direct.go | 2 +- go/pkg/brain/provider_contract.go | 2 +- .../brain/provider_contract_example_test.go | 11 + go/pkg/brain/provider_contract_test.go | 27 +++ go/pkg/lib/lib.go | 5 +- go/pkg/monitor/monitor.go | 4 +- go/pkg/runner/queue.go | 14 +- go/pkg/runner/runner.go | 1 - 40 files changed, 721 insertions(+), 221 deletions(-) create mode 100644 go/pkg/agentcompat/agentcompat_example_test.go create mode 100644 go/pkg/agentcompat/agentcompat_test.go create mode 100644 go/pkg/agentic/forge_client_example_test.go create mode 100644 go/pkg/agentic/forge_client_test.go create mode 100644 go/pkg/brain/provider_contract_example_test.go create mode 100644 go/pkg/brain/provider_contract_test.go diff --git a/go.work b/go.work index dc28501f..4a6595a6 100644 --- a/go.work +++ b/go.work @@ -7,10 +7,10 @@ use ( ./go ./external/go ./external/mcp - ./external/process + ./external/process/go ./external/store ./external/ws ./external/io ./external/log - ./external/rag + ./external/rag/go ) diff --git a/go/cmd/core-agent/commands.go b/go/cmd/core-agent/commands.go index 4b54d947..47932564 100644 --- a/go/cmd/core-agent/commands.go +++ b/go/cmd/core-agent/commands.go @@ -18,7 +18,7 @@ var applicationPrint = func(format string, args ...any) { } // args := startupArgs() -// _ = c.Cli().Run("version") +// result := c.Cli().Run("version") func startupArgs() []string { return applyLogLevel(core.FilterArgs(startupArgv()[1:])) } diff --git a/go/cmd/core-agent/main.go b/go/cmd/core-agent/main.go index 0c5902b3..ff86e1c4 100644 --- a/go/cmd/core-agent/main.go +++ b/go/cmd/core-agent/main.go @@ -79,7 +79,7 @@ var runCoreAgent = func() error { } // app := newCoreAgent() -// _ = runApp(app, []string{"version"}) +// result := runApp(app, []string{"version"}) var runApp = func(coreApp *core.Core, cliArgs []string) error { if coreApp == nil { return core.E("main.runApp", "core is required", nil) diff --git a/go/pkg/agentcompat/agentcompat.go b/go/pkg/agentcompat/agentcompat.go index 99af1657..ea0b0f2a 100644 --- a/go/pkg/agentcompat/agentcompat.go +++ b/go/pkg/agentcompat/agentcompat.go @@ -15,15 +15,15 @@ type ConcurrencyLimit struct { Models map[string]int } -func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error { +func (c *ConcurrencyLimit) UnmarshalYAMLResult(value *yaml.Node) core.Result { var n int if err := value.Decode(&n); err == nil { c.Total = n - return nil + return core.Ok(nil) } var m map[string]int if err := value.Decode(&m); err != nil { - return err + return core.Fail(err) } c.Total = m["total"] c.Models = make(map[string]int) @@ -32,7 +32,18 @@ func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error { c.Models[key] = entry } } - return nil + return core.Ok(nil) +} + +func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error { // yaml contract + result := c.UnmarshalYAMLResult(value) + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + return core.E("ConcurrencyLimit.UnmarshalYAML", "decode failed", nil) } type HTTPStream struct { @@ -43,11 +54,18 @@ type HTTPStream struct { Response []byte } -func (s *HTTPStream) Send(data []byte) error { - request, err := http.NewRequestWithContext(context.Background(), s.Method, s.URL, core.NewReader(string(data))) - if err != nil { - return err +func (s *HTTPStream) SendResult(data []byte) core.Result { + if s == nil { + return core.Fail(core.E("HTTPStream.Send", "stream is required", nil)) + } + if s.Client == nil { + return core.Fail(core.E("HTTPStream.Send", "client is required", nil)) + } + requestResult := core.NewHTTPRequestContext(context.Background(), s.Method, s.URL, core.NewReader(string(data))) + if !requestResult.OK { + return requestResult } + request := requestResult.Value.(*core.Request) request.Header.Set("Content-Type", "application/json") request.Header.Set("Accept", "application/json") if s.Token != "" { @@ -56,25 +74,64 @@ func (s *HTTPStream) Send(data []byte) error { response, err := s.Client.Do(request) if err != nil { - return err + return core.Fail(err) } - defer func() { - _ = response.Body.Close() - }() readResult := core.ReadAll(response.Body) if !readResult.OK { err, _ := readResult.Value.(error) - return core.E("httpStream.Send", "failed to read response", err) + return core.Fail(core.E("httpStream.Send", "failed to read response", err)) } s.Response = []byte(readResult.Value.(string)) - return nil + return core.Ok(nil) +} + +func (s *HTTPStream) Send(data []byte) error { // core.Stream contract + if s == nil || s.Client == nil { + panic(core.E("HTTPStream.Send", "stream client is required", nil)) + } + result := s.SendResult(data) + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + return core.E("HTTPStream.Send", "send failed", nil) +} + +func (s *HTTPStream) ReceiveResult() core.Result { + if s == nil { + return core.Fail(core.E("HTTPStream.Receive", "stream is required", nil)) + } + return core.Ok(s.Response) +} + +func (s *HTTPStream) Receive() ([]byte, error) { // core.Stream contract + if s == nil { + panic(core.E("HTTPStream.Receive", "stream is required", nil)) + } + result := s.ReceiveResult() + if result.OK { + return result.Value.([]byte), nil + } + if err, ok := result.Value.(error); ok { + return nil, err + } + return nil, core.E("HTTPStream.Receive", "receive failed", nil) } -func (s *HTTPStream) Receive() ([]byte, error) { - return s.Response, nil +func (s *HTTPStream) CloseResult() core.Result { + return core.Ok(nil) } -func (s *HTTPStream) Close() error { - return nil +func (s *HTTPStream) Close() error { // core.Stream contract + result := s.CloseResult() + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + return core.E("HTTPStream.Close", "close failed", nil) } diff --git a/go/pkg/agentcompat/agentcompat_example_test.go b/go/pkg/agentcompat/agentcompat_example_test.go new file mode 100644 index 00000000..ce911bb0 --- /dev/null +++ b/go/pkg/agentcompat/agentcompat_example_test.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentcompat + +import ( + core "dappco.re/go" + "gopkg.in/yaml.v3" +) + +func ExampleConcurrencyLimit_UnmarshalYAMLResult() { + var node yaml.Node + yaml.Unmarshal([]byte("total: 2\ncodex: 1\n"), &node) + limit := ConcurrencyLimit{} + result := limit.UnmarshalYAMLResult(node.Content[0]) + core.Println(result.OK, limit.Total, limit.Models["codex"]) + // Output: true 2 1 +} + +func ExampleConcurrencyLimit_UnmarshalYAML() { + limit := ConcurrencyLimit{} + yaml.Unmarshal([]byte("3\n"), &limit) + core.Println(limit.Total) + // Output: 3 +} + +func ExampleHTTPStream_SendResult() { + stream := &HTTPStream{} + result := stream.SendResult([]byte(`{"ping":1}`)) + core.Println(result.OK) + // Output: false +} + +func ExampleHTTPStream_Send() { + server := core.NewHTTPTestServer(core.HandlerFunc(func(w core.ResponseWriter, r *core.Request) { + core.WriteString(w, "ok") + })) + defer server.Close() + stream := &HTTPStream{Client: server.Client(), URL: server.URL, Method: "POST"} + err := stream.Send([]byte(`{"ping":1}`)) + core.Println(err == nil, string(stream.Response)) + // Output: true ok +} + +func ExampleHTTPStream_ReceiveResult() { + stream := &HTTPStream{Response: []byte("ok")} + result := stream.ReceiveResult() + core.Println(string(result.Value.([]byte))) + // Output: ok +} + +func ExampleHTTPStream_Receive() { + stream := &HTTPStream{Response: []byte("ok")} + data, err := stream.Receive() + core.Println(string(data), err == nil) + // Output: ok true +} + +func ExampleHTTPStream_CloseResult() { + stream := &HTTPStream{} + result := stream.CloseResult() + core.Println(result.OK) + // Output: true +} + +func ExampleHTTPStream_Close() { + stream := &HTTPStream{} + err := stream.Close() + core.Println(err == nil) + // Output: true +} diff --git a/go/pkg/agentcompat/agentcompat_test.go b/go/pkg/agentcompat/agentcompat_test.go new file mode 100644 index 00000000..63170193 --- /dev/null +++ b/go/pkg/agentcompat/agentcompat_test.go @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentcompat + +import ( + "testing" + + core "dappco.re/go" + "gopkg.in/yaml.v3" +) + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAMLResult_Good(t *testing.T) { + var node yaml.Node + core.RequireNoError(t, yaml.Unmarshal([]byte("total: 4\ngpt-5.4: 2\n"), &node)) + var limit ConcurrencyLimit + result := limit.UnmarshalYAMLResult(node.Content[0]) + core.AssertTrue(t, result.OK) + core.AssertEqual(t, 4, limit.Total) + core.AssertEqual(t, 2, limit.Models["gpt-5.4"]) +} + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAMLResult_Bad(t *testing.T) { + var node yaml.Node + core.RequireNoError(t, yaml.Unmarshal([]byte("[not, a, limit]\n"), &node)) + var limit ConcurrencyLimit + result := limit.UnmarshalYAMLResult(node.Content[0]) + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) +} + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAMLResult_Ugly(t *testing.T) { + var node yaml.Node + core.RequireNoError(t, yaml.Unmarshal([]byte("0\n"), &node)) + limit := ConcurrencyLimit{Models: map[string]int{"stale": 9}} + result := limit.UnmarshalYAMLResult(node.Content[0]) + core.AssertTrue(t, result.OK) + core.AssertEqual(t, 0, limit.Total) + core.AssertEqual(t, 9, limit.Models["stale"]) +} + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAML_Good(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("3\n"), &limit) + core.AssertNoError(t, err) + core.AssertEqual(t, 3, limit.Total) +} + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAML_Bad(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("[bad]\n"), &limit) + core.AssertError(t, err) +} + +func TestAgentcompat_ConcurrencyLimit_UnmarshalYAML_Ugly(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("total: 1\ncodex: 0\n"), &limit) + core.AssertNoError(t, err) + core.AssertEqual(t, 0, limit.Models["codex"]) +} + +func TestAgentcompat_HTTPStream_SendResult_Good(t *testing.T) { + server := core.NewHTTPTestServer(core.HandlerFunc(func(w core.ResponseWriter, r *core.Request) { + core.WriteString(w, `{"ok":true}`) + })) + defer server.Close() + stream := &HTTPStream{Client: server.Client(), URL: server.URL, Method: "POST"} + result := stream.SendResult([]byte(`{"ping":1}`)) + core.AssertTrue(t, result.OK) + core.AssertEqual(t, `{"ok":true}`, string(stream.Response)) +} + +func TestAgentcompat_HTTPStream_SendResult_Bad(t *testing.T) { + stream := &HTTPStream{URL: "http://example.invalid", Method: "POST"} + result := stream.SendResult([]byte(`{"ping":1}`)) + core.AssertFalse(t, result.OK) + core.AssertContains(t, result.Error(), "client is required") +} + +func TestAgentcompat_HTTPStream_SendResult_Ugly(t *testing.T) { + var stream *HTTPStream + result := stream.SendResult([]byte(`{"ping":1}`)) + core.AssertFalse(t, result.OK) + core.AssertContains(t, result.Error(), "stream is required") +} + +func TestAgentcompat_HTTPStream_Send_Good(t *testing.T) { + server := core.NewHTTPTestServer(core.HandlerFunc(func(w core.ResponseWriter, r *core.Request) { + core.WriteString(w, `{"pong":1}`) + })) + defer server.Close() + stream := &HTTPStream{Client: server.Client(), URL: server.URL, Method: "POST"} + err := stream.Send([]byte(`{"ping":1}`)) + core.AssertNoError(t, err) + core.AssertEqual(t, `{"pong":1}`, string(stream.Response)) +} + +func TestAgentcompat_HTTPStream_Send_Bad(t *testing.T) { + stream := &HTTPStream{Client: &core.HTTPClient{}, URL: "://bad", Method: "POST"} + err := stream.Send([]byte(`{"ping":1}`)) + core.AssertError(t, err) +} + +func TestAgentcompat_HTTPStream_Send_Ugly(t *testing.T) { + var stream *HTTPStream + core.AssertPanics(t, func() { + stream.Send([]byte(`{"ping":1}`)) + }) +} + +func TestAgentcompat_HTTPStream_ReceiveResult_Good(t *testing.T) { + stream := &HTTPStream{Response: []byte(`{"cached":true}`)} + result := stream.ReceiveResult() + core.AssertTrue(t, result.OK) + core.AssertEqual(t, []byte(`{"cached":true}`), result.Value) +} + +func TestAgentcompat_HTTPStream_ReceiveResult_Bad(t *testing.T) { + var stream *HTTPStream + result := stream.ReceiveResult() + core.AssertFalse(t, result.OK) + core.AssertContains(t, result.Error(), "stream is required") +} + +func TestAgentcompat_HTTPStream_ReceiveResult_Ugly(t *testing.T) { + stream := &HTTPStream{} + result := stream.ReceiveResult() + core.AssertTrue(t, result.OK) + core.AssertEqual(t, []byte(nil), result.Value) +} + +func TestAgentcompat_HTTPStream_Receive_Good(t *testing.T) { + stream := &HTTPStream{Response: []byte("ok")} + data, err := stream.Receive() + core.AssertNoError(t, err) + core.AssertEqual(t, []byte("ok"), data) +} + +func TestAgentcompat_HTTPStream_Receive_Bad(t *testing.T) { + var stream *HTTPStream + result := stream.ReceiveResult() + core.AssertFalse(t, result.OK) + core.AssertContains(t, result.Error(), "stream is required") +} + +func TestAgentcompat_HTTPStream_Receive_Ugly(t *testing.T) { + stream := &HTTPStream{} + data, err := stream.Receive() + core.AssertNoError(t, err) + core.AssertEqual(t, []byte(nil), data) +} + +func TestAgentcompat_HTTPStream_CloseResult_Good(t *testing.T) { + stream := &HTTPStream{} + result := stream.CloseResult() + core.AssertTrue(t, result.OK) + core.AssertNil(t, result.Value) +} + +func TestAgentcompat_HTTPStream_CloseResult_Bad(t *testing.T) { + var stream *HTTPStream + result := stream.CloseResult() + core.AssertTrue(t, result.OK) + core.AssertNil(t, stream) +} + +func TestAgentcompat_HTTPStream_CloseResult_Ugly(t *testing.T) { + stream := &HTTPStream{Response: []byte("stale")} + result := stream.CloseResult() + core.AssertTrue(t, result.OK) + core.AssertEqual(t, []byte("stale"), stream.Response) +} + +func TestAgentcompat_HTTPStream_Close_Good(t *testing.T) { + stream := &HTTPStream{} + err := stream.Close() + core.AssertNoError(t, err) +} + +func TestAgentcompat_HTTPStream_Close_Bad(t *testing.T) { + var stream *HTTPStream + err := stream.Close() + core.AssertNoError(t, err) + core.AssertNil(t, stream) +} + +func TestAgentcompat_HTTPStream_Close_Ugly(t *testing.T) { + stream := &HTTPStream{Token: "token"} + err := stream.Close() + core.AssertNoError(t, err) + core.AssertEqual(t, "token", stream.Token) +} diff --git a/go/pkg/agentic/actions.go b/go/pkg/agentic/actions.go index 109139a2..24eca6eb 100644 --- a/go/pkg/agentic/actions.go +++ b/go/pkg/agentic/actions.go @@ -246,7 +246,9 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core if ok { workspaceStatus.Status = "failed" workspaceStatus.Question = "QA check failed โ€” build or tests did not pass" - _ = writeStatusResult(workspaceDir, workspaceStatus) + if writeResult := writeStatusResult(workspaceDir, workspaceStatus); !writeResult.OK { + return writeResult + } } } } @@ -257,11 +259,13 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core if ok { repo = workspaceStatus.Repo } - _ = s.Core().ACTION(messages.QAResult{ + if actionResult := s.Core().ACTION(messages.QAResult{ Workspace: WorkspaceName(workspaceDir), Repo: repo, Passed: passed, - }) + }); !actionResult.OK { + return actionResult + } } return core.Result{Value: passed, OK: passed} } @@ -285,12 +289,14 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options) result := ReadStatusResult(workspaceDir) workspaceStatus, ok := workspaceStatusValue(result) if ok && workspaceStatus.PRURL != "" { - _ = s.Core().ACTION(messages.PRCreated{ + if actionResult := s.Core().ACTION(messages.PRCreated{ Repo: workspaceStatus.Repo, Branch: workspaceStatus.Branch, PRURL: workspaceStatus.PRURL, PRNum: extractPullRequestNumber(workspaceStatus.PRURL), - }) + }); !actionResult.OK { + return actionResult + } } } return core.Result{OK: true} @@ -316,18 +322,22 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options) workspaceStatus, ok := workspaceStatusValue(result) if ok { if workspaceStatus.Status == "merged" { - _ = s.Core().ACTION(messages.PRMerged{ + if actionResult := s.Core().ACTION(messages.PRMerged{ Repo: workspaceStatus.Repo, PRURL: workspaceStatus.PRURL, PRNum: extractPullRequestNumber(workspaceStatus.PRURL), - }) + }); !actionResult.OK { + return actionResult + } } else if workspaceStatus.Question != "" { - _ = s.Core().ACTION(messages.PRNeedsReview{ + if actionResult := s.Core().ACTION(messages.PRNeedsReview{ Repo: workspaceStatus.Repo, PRURL: workspaceStatus.PRURL, PRNum: extractPullRequestNumber(workspaceStatus.PRURL), Reason: workspaceStatus.Question, - }) + }); !actionResult.OK { + return actionResult + } } } } diff --git a/go/pkg/agentic/auto_pr.go b/go/pkg/agentic/auto_pr.go index 4cd81fda..8bc461f8 100644 --- a/go/pkg/agentic/auto_pr.go +++ b/go/pkg/agentic/auto_pr.go @@ -48,16 +48,20 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) { return } workspaceStatusUpdate.Question = "PR push failed" - _ = writeStatusResult(workspaceDir, workspaceStatusUpdate) + if result := writeStatusResult(workspaceDir, workspaceStatusUpdate); !result.OK { + core.Warn("agentic.autoPR: failed to record push failure", "reason", result.Error()) + } } return } if s.ServiceRuntime != nil { - _ = s.Core().ACTION(messages.WorkspacePushed{ + if result := s.Core().ACTION(messages.WorkspacePushed{ Repo: workspaceStatus.Repo, Branch: workspaceStatus.Branch, Org: org, - }) + }); !result.OK { + core.Warn("agentic.autoPR: workspace push notification failed", "reason", result.Error()) + } } title := core.Sprintf("[agent/%s] %s", workspaceStatus.Agent, truncate(workspaceStatus.Task, 60)) @@ -74,7 +78,9 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) { return } workspaceStatusUpdate.Question = core.Sprintf("PR creation failed: %v", err) - _ = writeStatusResult(workspaceDir, workspaceStatusUpdate) + if result := writeStatusResult(workspaceDir, workspaceStatusUpdate); !result.OK { + core.Warn("agentic.autoPR: failed to record PR failure", "reason", result.Error()) + } } return } diff --git a/go/pkg/agentic/branch_cleanup.go b/go/pkg/agentic/branch_cleanup.go index 8af44396..d4c49d21 100644 --- a/go/pkg/agentic/branch_cleanup.go +++ b/go/pkg/agentic/branch_cleanup.go @@ -33,9 +33,10 @@ func (s *PrepSubsystem) cleanupBranch(ctx context.Context, repo, branch string) return core.Result{Value: core.E("cleanupBranch", core.Concat("refusing to delete protected branch ", branch), nil), OK: false} } - err := s.forge.deleteBranch(ctx, org, repoName, branch) - if err != nil && !isForgeNotFound(err) { - return core.Result{Value: core.E("cleanupBranch", core.Concat("failed to delete branch ", branch), err), OK: false} + deleteResult := s.forge.deleteBranch(ctx, org, repoName, branch) + deleteErr := forgeResultError(deleteResult) + if deleteErr != nil && !isForgeNotFound(deleteErr) { + return core.Result{Value: core.E("cleanupBranch", core.Concat("failed to delete branch ", branch), deleteErr), OK: false} } return core.Result{OK: true} } diff --git a/go/pkg/agentic/commands_forge.go b/go/pkg/agentic/commands_forge.go index 05397f3e..24eef0a2 100644 --- a/go/pkg/agentic/commands_forge.go +++ b/go/pkg/agentic/commands_forge.go @@ -83,7 +83,9 @@ func parseForgeArgs(options core.Options) (org, repo string, num int64) { } repo = options.String("_arg") if v := options.String("number"); v != "" { - num, _ = strconv.ParseInt(v, 10, 64) + if parsed, parseErr := strconv.ParseInt(v, 10, 64); parseErr == nil { + num = parsed + } } if validatedOrg, ok := validateName(org); ok { @@ -217,10 +219,11 @@ func (s *PrepSubsystem) cmdIssueGet(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdIssueGet", "repo and number are required", nil), OK: false} } var issue issueView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d", org, repo, num), &issue) - if err != nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d", org, repo, num), &issue) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } core.Print(nil, "#%d %s", issueNumber(issue), issue.Title) core.Print(nil, " state: %s", issue.State) @@ -240,10 +243,11 @@ func (s *PrepSubsystem) cmdIssueList(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdIssueList", "repo is required", nil), OK: false} } var issues []issueView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues?limit=50&page=1", org, repo), &issues) - if err != nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues?limit=50&page=1", org, repo), &issues) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } for _, issue := range issues { core.Print(nil, " #%-4d %-6s %s", issueNumber(issue), issue.State, issue.Title) @@ -263,12 +267,13 @@ func (s *PrepSubsystem) cmdIssueComment(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdIssueComment", "repo, number, and body are required", nil), OK: false} } var comment Comment - err := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", org, repo, num), map[string]any{ + result := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", org, repo, num), map[string]any{ "body": body, }, &comment) - if err != nil { + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } core.Print(nil, "comment #%d created on %s/%s#%d", comment.ID, org, repo, num) return core.Result{OK: true} @@ -292,8 +297,8 @@ func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result { if milestone != "" { var milestones []Milestone - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/milestones", org, repo), &milestones) - if err == nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/milestones", org, repo), &milestones) + if result.OK { for _, m := range milestones { if m.Title == milestone { createOptions.Milestone = m.ID @@ -307,8 +312,9 @@ func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result { } if labels != "" { labelNames := core.Split(labels, ",") - allLabels, err := s.forge.listRepoLabels(ctx, org, repo) - if err == nil { + labelsResult := s.forge.listRepoLabels(ctx, org, repo) + if labelsResult.OK { + allLabels := labelsResult.Value.([]Label) labelIDs := []int64{} for _, name := range labelNames { name = core.Trim(name) @@ -326,10 +332,11 @@ func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result { } var issue issueView - err := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues", org, repo), createOptions, &issue) - if err != nil { + result := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues", org, repo), createOptions, &issue) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } core.Print(nil, "#%d %s", issue.Index, issue.Title) core.Print(nil, " url: %s", issue.HTMLURL) @@ -473,10 +480,11 @@ func (s *PrepSubsystem) cmdPRGet(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdPRGet", "repo and number are required", nil), OK: false} } var pr pullRequestView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, repo, num), &pr) - if err != nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, repo, num), &pr) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } core.Print(nil, "#%d %s", pullRequestNumber(pr), pr.Title) core.Print(nil, " state: %s", pr.State) @@ -499,10 +507,11 @@ func (s *PrepSubsystem) cmdPRList(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdPRList", "repo is required", nil), OK: false} } var prs []pullRequestView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &prs) - if err != nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &prs) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } for _, pr := range prs { core.Print(nil, " #%-4d %-6s %s โ†’ %s %s", pullRequestNumber(pr), pr.State, pr.Head.Ref, pr.Base.Ref, pr.Title) @@ -524,9 +533,11 @@ func (s *PrepSubsystem) cmdPRMerge(options core.Options) core.Result { core.Print(nil, "usage: core-agent pr merge --number=N [--method=merge|rebase|squash] [--org=core]") return core.Result{Value: core.E("agentic.cmdPRMerge", "repo and number are required", nil), OK: false} } - if err := s.forge.mergePullRequest(ctx, org, repo, num, method); err != nil { + result := s.forge.mergePullRequest(ctx, org, repo, num, method) + if !result.OK { + err := forgeResultError(result) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } core.Print(nil, "merged %s/%s#%d via %s", org, repo, num, method) return core.Result{OK: true} @@ -561,17 +572,19 @@ func (s *PrepSubsystem) cmdRepoGet(options core.Options) core.Result { core.Print(nil, "usage: core-agent repo get [--org=core]") return core.Result{Value: core.E("agentic.cmdRepoGet", "repo is required", nil), OK: false} } - repositoryResult, err := s.forge.getRepo(ctx, org, repo) - if err != nil { + repositoryResult := s.forge.getRepo(ctx, org, repo) + if !repositoryResult.OK { + err := forgeResultError(repositoryResult) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} - } - core.Print(nil, "%s/%s", repositoryResult.Owner.UserName, repositoryResult.Name) - core.Print(nil, " description: %s", repositoryResult.Description) - core.Print(nil, " default: %s", repositoryResult.DefaultBranch) - core.Print(nil, " private: %v", repositoryResult.Private) - core.Print(nil, " archived: %v", repositoryResult.Archived) - core.Print(nil, " url: %s", repositoryResult.HTMLURL) + return core.Fail(err) + } + repository := repositoryResult.Value.(*Repository) + core.Print(nil, "%s/%s", repository.Owner.UserName, repository.Name) + core.Print(nil, " description: %s", repository.Description) + core.Print(nil, " default: %s", repository.DefaultBranch) + core.Print(nil, " private: %v", repository.Private) + core.Print(nil, " archived: %v", repository.Archived) + core.Print(nil, " url: %s", repository.HTMLURL) return core.Result{OK: true} } @@ -587,11 +600,13 @@ func (s *PrepSubsystem) cmdRepoList(options core.Options) core.Result { return core.Result{Value: core.E("agentic.cmdRepoList", "invalid org name", nil), OK: false} } org = validatedOrg - repos, err := s.forge.listOrgRepos(ctx, org) - if err != nil { + reposResult := s.forge.listOrgRepos(ctx, org) + if !reposResult.OK { + err := forgeResultError(reposResult) core.Print(nil, "error: %v", err) - return core.Result{Value: err, OK: false} + return core.Fail(err) } + repos := reposResult.Value.([]Repository) for _, repository := range repos { archived := "" if repository.Archived { diff --git a/go/pkg/agentic/commands_workspace.go b/go/pkg/agentic/commands_workspace.go index 0c32ccf3..6ce34285 100644 --- a/go/pkg/agentic/commands_workspace.go +++ b/go/pkg/agentic/commands_workspace.go @@ -129,7 +129,9 @@ func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result { s.recordWorkspaceStats(path, st) } } - _ = filesystem.DeleteAll(path) + if result := filesystem.DeleteAll(path); !result.OK { + return result + } core.Print(nil, " removed %s", name) } core.Print(nil, "\n %d workspaces removed", len(toRemove)) diff --git a/go/pkg/agentic/dispatch.go b/go/pkg/agentic/dispatch.go index 7482222c..07f3bbe4 100644 --- a/go/pkg/agentic/dispatch.go +++ b/go/pkg/agentic/dispatch.go @@ -541,10 +541,12 @@ func (s *PrepSubsystem) trackFailureRate(agent, status string, startedAt time.Ti s.backoff[pool] = until s.persistRuntimeState() if s.ServiceRuntime != nil { - _ = s.Core().ACTION(messages.RateLimitDetected{ - Pool: pool, - Duration: backoffDuration.String(), - }) + if result := s.Core().ACTION(messages.RateLimitDetected{ + Pool: pool, + Duration: backoffDuration.String(), + }); !result.OK { + core.Warn("agentic.rateLimit: notification failed", "reason", result.Error()) + } } core.Print(nil, "rate-limit detected for %s โ€” pausing pool for 30 minutes", pool) return true @@ -572,8 +574,8 @@ func (s *PrepSubsystem) startIssueTracking(workspaceDir string) { if org == "" { org = "core" } - if err := s.forge.startStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue)); err != nil { - core.Warn("agentic.startIssueTracking: failed to start stopwatch", "repo", workspaceStatus.Repo, "issue", workspaceStatus.Issue, "reason", err) + if result := s.forge.startStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue)); !result.OK { + core.Warn("agentic.startIssueTracking: failed to start stopwatch", "repo", workspaceStatus.Repo, "issue", workspaceStatus.Issue, "reason", forgeResultError(result)) } } @@ -590,8 +592,8 @@ func (s *PrepSubsystem) stopIssueTracking(workspaceDir string) { if org == "" { org = "core" } - if err := s.forge.stopStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue)); err != nil { - core.Warn("agentic.stopIssueTracking: failed to stop stopwatch", "repo", workspaceStatus.Repo, "issue", workspaceStatus.Issue, "reason", err) + if result := s.forge.stopStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue)); !result.OK { + core.Warn("agentic.stopIssueTracking: failed to stop stopwatch", "repo", workspaceStatus.Repo, "issue", workspaceStatus.Issue, "reason", forgeResultError(result)) } } @@ -604,17 +606,21 @@ func (s *PrepSubsystem) broadcastStart(agent, workspaceDir string) { repo = workspaceStatus.Repo } if s.ServiceRuntime != nil { - _ = s.Core().ACTION(messages.AgentStarted{ - Agent: agent, Repo: repo, Workspace: workspaceName, - }) + if result := s.Core().ACTION(messages.AgentStarted{ + Agent: agent, Repo: repo, Workspace: workspaceName, + }); !result.OK { + core.Warn("agentic.broadcastStart: notification failed", "reason", result.Error()) + } // Push to MCP channel so Claude Code receives the notification - _ = s.Core().ACTION(coremcp.ChannelPush{ - Channel: coremcp.ChannelAgentStatus, - Data: map[string]any{ + if result := s.Core().ACTION(coremcp.ChannelPush{ + Channel: coremcp.ChannelAgentStatus, + Data: map[string]any{ "agent": agent, "repo": repo, "workspace": workspaceName, "status": "running", }, - }) + }); !result.OK { + core.Warn("agentic.broadcastStart: channel push failed", "reason", result.Error()) + } } emitStartEvent(agent, workspaceName) } @@ -629,10 +635,12 @@ func (s *PrepSubsystem) broadcastComplete(agent, workspaceDir, finalStatus strin if ok { repo = workspaceStatus.Repo } - _ = s.Core().ACTION(messages.AgentCompleted{ - Agent: agent, Repo: repo, - Workspace: workspaceName, Status: finalStatus, - }) + if result := s.Core().ACTION(messages.AgentCompleted{ + Agent: agent, Repo: repo, + Workspace: workspaceName, Status: finalStatus, + }); !result.OK { + core.Warn("agentic.broadcastComplete: notification failed", "reason", result.Error()) + } // Push to MCP channel so Claude Code receives the notification s.Core().ACTION(coremcp.ChannelPush{ Channel: coremcp.ChannelAgentComplete, @@ -723,7 +731,9 @@ var spawnAgent = func(s *PrepSubsystem, agent, prompt, workspaceDir string) (int return 0, "", "", core.E("dispatch.spawnAgent", "unexpected process result", nil) } - _ = proc.CloseStdin() + if closeResult := proc.CloseStdin(); !closeResult.OK { + core.Warn("dispatch.spawnAgent: close stdin failed", "reason", closeResult.Error()) + } startDispatchTimeoutWatch(workspaceDir, s.dispatchTimeout(), proc) pid := proc.Info().PID processID := proc.ID @@ -740,7 +750,9 @@ var spawnAgent = func(s *PrepSubsystem, agent, prompt, workspaceDir string) (int process: proc, } s.Core().Action(monitorAction, monitor.run) - _ = s.Core().PerformAsync(monitorAction, core.NewOptions()) + if result := s.Core().PerformAsync(monitorAction, core.NewOptions()); !result.OK { + return 0, "", "", core.E("dispatch.spawnAgent", "failed to start monitor", forgeResultError(result)) + } return pid, processID, outputFile, nil } diff --git a/go/pkg/agentic/epic.go b/go/pkg/agentic/epic.go index 31f9df06..719307d4 100644 --- a/go/pkg/agentic/epic.go +++ b/go/pkg/agentic/epic.go @@ -153,7 +153,9 @@ var createIssue = func(s *PrepSubsystem, ctx context.Context, org, repo, title, Number int `json:"number"` HTMLURL string `json:"html_url"` } - _ = core.JSONUnmarshalString(httpResult.Value.(string), &createdIssue) + if parseResult := core.JSONUnmarshalString(httpResult.Value.(string), &createdIssue); !parseResult.OK { + return ChildRef{}, core.E("createIssue", "parse issue response failed", forgeResultError(parseResult)) + } return ChildRef{ Number: createdIssue.Number, @@ -178,7 +180,9 @@ func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, n ID int64 `json:"id"` Name string `json:"name"` } - _ = core.JSONUnmarshalString(httpResult.Value.(string), &existing) + if parseResult := core.JSONUnmarshalString(httpResult.Value.(string), &existing); !parseResult.OK { + return nil + } nameToID := make(map[string]int64) for _, l := range existing { @@ -227,6 +231,8 @@ func (s *PrepSubsystem) createLabel(ctx context.Context, org, repo, name string) var createdLabel struct { ID int64 `json:"id"` } - _ = core.JSONUnmarshalString(httpResult.Value.(string), &createdLabel) + if parseResult := core.JSONUnmarshalString(httpResult.Value.(string), &createdLabel); !parseResult.OK { + return 0 + } return createdLabel.ID } diff --git a/go/pkg/agentic/fleet_connect.go b/go/pkg/agentic/fleet_connect.go index 1755f66f..70fe5b02 100644 --- a/go/pkg/agentic/fleet_connect.go +++ b/go/pkg/agentic/fleet_connect.go @@ -91,7 +91,9 @@ func (s *PrepSubsystem) Connect(ctx context.Context, options core.Options) core. if fleetHeartbeatInterval > 0 { go func() { - _ = s.runFleetHeartbeat(heartbeatContext, config) + if result := s.runFleetHeartbeat(heartbeatContext, config); !result.OK { + core.Warn("agentic.fleet.heartbeat: stopped", "reason", result.Error()) + } }() } @@ -318,9 +320,6 @@ func (s *PrepSubsystem) fleetEventRequest(ctx context.Context, action string, co } if response.StatusCode >= 400 { - defer func() { - _ = response.Body.Close() - }() readResult := core.ReadAll(response.Body) if !readResult.OK { return core.Result{Value: core.E(action, core.Sprintf("HTTP %d", response.StatusCode), nil), OK: false} @@ -344,9 +343,7 @@ func (s *PrepSubsystem) connectFleetEventStream(ctx context.Context, config flee if !ok || response == nil { return core.Result{Value: core.E("agentic.fleet.connect", "invalid event stream response", nil), OK: false} } - defer func() { - _ = response.Body.Close() - }() + defer core.CloseStream(response.Body) fleetRememberBase(config) fleetRememberState("connected", "sse", "") diff --git a/go/pkg/agentic/flow.go b/go/pkg/agentic/flow.go index 9f1c646c..98273378 100644 --- a/go/pkg/agentic/flow.go +++ b/go/pkg/agentic/flow.go @@ -323,7 +323,9 @@ var captureFlowStepOutput = func(run func() core.Result) (core.Result, string, s return core.Result{}, "", "", core.E("agentic.captureFlowStepOutput", "redirect stdout", err) } if err := syscall.Dup2(stderrWriteFD, int(stderrFile.Fd())); err != nil { - _ = syscall.Dup2(restoreStdoutFD, int(stdoutFile.Fd())) + if restoreErr := syscall.Dup2(restoreStdoutFD, int(stdoutFile.Fd())); restoreErr != nil { + return core.Result{}, "", "", core.E("agentic.captureFlowStepOutput", "restore stdout", restoreErr) + } closeFD(stdoutReadFD) closeFD(stdoutWriteFD) closeFD(stderrReadFD) @@ -399,7 +401,9 @@ var readFD = func(fd int) ([]byte, error) { func closeFD(fd int) { if fd > 0 { - _ = syscall.Close(fd) + if err := syscall.Close(fd); err != nil { + core.Warn("agentic.flow: close fd failed", "fd", fd, "reason", err) + } } } diff --git a/go/pkg/agentic/forge_client.go b/go/pkg/agentic/forge_client.go index 0055c947..866d95e0 100644 --- a/go/pkg/agentic/forge_client.go +++ b/go/pkg/agentic/forge_client.go @@ -21,6 +21,8 @@ type forgeAPIError struct { Message string } +type APIError = forgeAPIError + type Repository struct { Name string `json:"name"` Description string `json:"description"` @@ -123,43 +125,60 @@ func isForgeNotFound(err error) bool { return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound } -func (c *forgeClient) getJSON(ctx context.Context, path string, out any) error { +func forgeResultError(result core.Result) error { // adapter for external contracts + if result.OK { + return nil + } + if err, ok := result.Value.(error); ok { + return err + } + return core.E("forge.result", "request failed", nil) +} + +func (c *forgeClient) getJSON(ctx context.Context, path string, out any) core.Result { return c.doJSON(ctx, http.MethodGet, path, nil, out) } -func (c *forgeClient) postJSON(ctx context.Context, path string, body, out any) error { +func (c *forgeClient) postJSON(ctx context.Context, path string, body, out any) core.Result { return c.doJSON(ctx, http.MethodPost, path, body, out) } -func (c *forgeClient) patchJSON(ctx context.Context, path string, body, out any) error { +func (c *forgeClient) patchJSON(ctx context.Context, path string, body, out any) core.Result { return c.doJSON(ctx, http.MethodPatch, path, body, out) } -func (c *forgeClient) deletePath(ctx context.Context, path string) error { +func (c *forgeClient) deletePath(ctx context.Context, path string) core.Result { return c.doJSON(ctx, http.MethodDelete, path, nil, nil) } -func (c *forgeClient) listOrgRepos(ctx context.Context, org string) ([]Repository, error) { +func (c *forgeClient) listOrgRepos(ctx context.Context, org string) core.Result { var repos []Repository - err := c.getJSON(ctx, core.Sprintf("/api/v1/orgs/%s/repos?limit=50&page=1", org), &repos) - return repos, err + result := c.getJSON(ctx, core.Sprintf("/api/v1/orgs/%s/repos?limit=50&page=1", org), &repos) + if !result.OK { + return result + } + return core.Ok(repos) } -func (c *forgeClient) getRepo(ctx context.Context, org, repo string) (*Repository, error) { +func (c *forgeClient) getRepo(ctx context.Context, org, repo string) core.Result { var item Repository - if err := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s", org, repo), &item); err != nil { - return nil, err + result := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s", org, repo), &item) + if !result.OK { + return result } - return &item, nil + return core.Ok(&item) } -func (c *forgeClient) listRepoLabels(ctx context.Context, org, repo string) ([]Label, error) { +func (c *forgeClient) listRepoLabels(ctx context.Context, org, repo string) core.Result { var labels []Label - err := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/labels?limit=50&page=1", org, repo), &labels) - return labels, err + result := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/labels?limit=50&page=1", org, repo), &labels) + if !result.OK { + return result + } + return core.Ok(labels) } -func (c *forgeClient) mergePullRequest(ctx context.Context, org, repo string, number int64, method string) error { +func (c *forgeClient) mergePullRequest(ctx context.Context, org, repo string, number int64, method string) core.Result { if method == "" { method = "merge" } @@ -168,39 +187,42 @@ func (c *forgeClient) mergePullRequest(ctx context.Context, org, repo string, nu }, nil) } -func (c *forgeClient) deleteBranch(ctx context.Context, org, repo, branch string) error { +func (c *forgeClient) deleteBranch(ctx context.Context, org, repo, branch string) core.Result { return c.deletePath(ctx, core.Sprintf("/api/v1/repos/%s/%s/branches/%s", org, repo, core.URLEncode(branch))) } -func (c *forgeClient) startStopwatch(ctx context.Context, org, repo string, index int64) error { +func (c *forgeClient) startStopwatch(ctx context.Context, org, repo string, index int64) core.Result { return c.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/start", org, repo, index), nil, nil) } -func (c *forgeClient) stopStopwatch(ctx context.Context, org, repo string, index int64) error { +func (c *forgeClient) stopStopwatch(ctx context.Context, org, repo string, index int64) core.Result { return c.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", org, repo, index), nil, nil) } -func (c *forgeClient) listWikiPages(ctx context.Context, org, repo string) ([]WikiPageMetaData, error) { +func (c *forgeClient) listWikiPages(ctx context.Context, org, repo string) core.Result { var pages []WikiPageMetaData - err := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/wiki/pages", org, repo), &pages) - return pages, err + result := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/wiki/pages", org, repo), &pages) + if !result.OK { + return result + } + return core.Ok(pages) } -func (c *forgeClient) getWikiPage(ctx context.Context, org, repo, page string) (*WikiPage, error) { +func (c *forgeClient) getWikiPage(ctx context.Context, org, repo, page string) core.Result { var wikiPage WikiPage - err := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", org, repo, core.URLEncode(page)), &wikiPage) - if err != nil { - return nil, err + result := c.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", org, repo, core.URLEncode(page)), &wikiPage) + if !result.OK { + return result } - return &wikiPage, nil + return core.Ok(&wikiPage) } -func (c *forgeClient) doJSON(ctx context.Context, method, path string, body, out any) error { +func (c *forgeClient) doJSON(ctx context.Context, method, path string, body, out any) core.Result { if c == nil { - return core.E("forgeClient.doJSON", "forge client is required", nil) + return core.Fail(core.E("forgeClient.doJSON", "forge client is required", nil)) } if c.baseURL == "" { - return core.E("forgeClient.doJSON", "forge base URL is required", nil) + return core.Fail(core.E("forgeClient.doJSON", "forge base URL is required", nil)) } requestBody := "" @@ -208,17 +230,18 @@ func (c *forgeClient) doJSON(ctx context.Context, method, path string, body, out requestBody = core.JSONMarshalString(body) } - var request *http.Request - var err error url := core.Concat(core.TrimSuffix(c.baseURL, "/"), path) + var requestResult core.Result if requestBody == "" { - request, err = http.NewRequestWithContext(ctx, method, url, nil) + requestResult = core.NewHTTPRequestContext(ctx, method, url, nil) } else { - request, err = http.NewRequestWithContext(ctx, method, url, core.NewReader(requestBody)) + requestResult = core.NewHTTPRequestContext(ctx, method, url, core.NewReader(requestBody)) } - if err != nil { - return core.E("forgeClient.doJSON", "create request", err) + if !requestResult.OK { + err, _ := requestResult.Value.(error) + return core.Fail(core.E("forgeClient.doJSON", "create request", err)) } + request := requestResult.Value.(*core.Request) request.Header.Set("Accept", "application/json") request.Header.Set("Content-Type", "application/json") @@ -228,36 +251,33 @@ func (c *forgeClient) doJSON(ctx context.Context, method, path string, body, out response, err := defaultClient.Do(request) if err != nil { - return core.E("forgeClient.doJSON", "request failed", err) + return core.Fail(core.E("forgeClient.doJSON", "request failed", err)) } - defer func() { - _ = response.Body.Close() - }() readResult := core.ReadAll(response.Body) if !readResult.OK { readErr, _ := readResult.Value.(error) - return core.E("forgeClient.doJSON", "read response", readErr) + return core.Fail(core.E("forgeClient.doJSON", "read response", readErr)) } - payload, _ := readResult.Value.(string) + payload := readResult.Value.(string) if response.StatusCode < 200 || response.StatusCode >= 300 { return forgeAPIErrorFromResponse(path, response.StatusCode, payload) } if out == nil || core.Trim(payload) == "" { - return nil + return core.Ok(nil) } parseResult := core.JSONUnmarshalString(payload, out) if !parseResult.OK { parseErr, _ := parseResult.Value.(error) - return core.E("forgeClient.doJSON", "parse response", parseErr) + return core.Fail(core.E("forgeClient.doJSON", "parse response", parseErr)) } - return nil + return core.Ok(nil) } -func forgeAPIErrorFromResponse(path string, statusCode int, payload string) error { +func forgeAPIErrorFromResponse(path string, statusCode int, payload string) core.Result { message := core.Trim(payload) if message == "" { message = core.Sprintf("HTTP %d", statusCode) @@ -278,9 +298,9 @@ func forgeAPIErrorFromResponse(path string, statusCode int, payload string) erro } } - return &forgeAPIError{ + return core.Fail(&forgeAPIError{ StatusCode: statusCode, Path: path, Message: message, - } + }) } diff --git a/go/pkg/agentic/forge_client_example_test.go b/go/pkg/agentic/forge_client_example_test.go new file mode 100644 index 00000000..4514459e --- /dev/null +++ b/go/pkg/agentic/forge_client_example_test.go @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import core "dappco.re/go" + +func ExampleAPIError_Error() { + err := &forgeAPIError{StatusCode: 404, Path: "/api/v1/repos/core/agent", Message: "not found"} + core.Println(err.Error()) + // Output: forge /api/v1/repos/core/agent returned HTTP 404: not found +} diff --git a/go/pkg/agentic/forge_client_test.go b/go/pkg/agentic/forge_client_test.go new file mode 100644 index 00000000..af256625 --- /dev/null +++ b/go/pkg/agentic/forge_client_test.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "testing" + + core "dappco.re/go" +) + +func TestForgeClient_APIError_Error_Good(t *testing.T) { + err := &forgeAPIError{StatusCode: 404, Path: "/api/v1/repos/core/agent", Message: "not found"} + core.AssertContains(t, err.Error(), "not found") + core.AssertContains(t, err.Error(), "404") +} + +func TestForgeClient_APIError_Error_Bad(t *testing.T) { + var err *forgeAPIError + message := err.Error() + core.AssertEqual(t, "forge API error", message) + core.AssertNotContains(t, message, "HTTP") +} + +func TestForgeClient_APIError_Error_Ugly(t *testing.T) { + err := &forgeAPIError{StatusCode: 500, Path: "/api/v1/repos/core/agent"} + core.AssertContains(t, err.Error(), "HTTP 500") + core.AssertNotContains(t, err.Error(), "not found") +} diff --git a/go/pkg/agentic/handlers.go b/go/pkg/agentic/handlers.go index 21d654f3..f0e0e738 100644 --- a/go/pkg/agentic/handlers.go +++ b/go/pkg/agentic/handlers.go @@ -39,8 +39,8 @@ func RegisterHandlers(c *core.Core, s *PrepSubsystem) { ) } -// _ = prep.HandleIPCEvents(c, messages.AgentCompleted{Workspace: "core/go-io/task-5", Status: "completed"}) -// _ = prep.HandleIPCEvents(c, messages.SpawnQueued{Workspace: "core/go-io/task-5", Agent: "codex", Task: "fix tests"}) +// completed := prep.HandleIPCEvents(c, messages.AgentCompleted{Workspace: "core/go-io/task-5", Status: "completed"}) +// queued := prep.HandleIPCEvents(c, messages.SpawnQueued{Workspace: "core/go-io/task-5", Agent: "codex", Task: "fix tests"}) func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Result { switch ev := msg.(type) { case messages.SpawnQueued: @@ -49,7 +49,7 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res break } prompt := core.Concat("TASK: ", ev.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.") - pid, processID, outputFile, err := spawnAgent(s, ev.Agent, prompt, workspaceDir) + pid, processID, _, err := spawnAgent(s, ev.Agent, prompt, workspaceDir) if err != nil { break } @@ -67,7 +67,6 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res } } } - _ = outputFile } return core.Result{OK: true} @@ -124,14 +123,18 @@ func handleCompletionCommit(c *core.Core, msg core.Message) core.Result { workspaceDir := findWorkspaceByPRWithInfo(ev.Repo, "", ev.PRNum, ev.PRURL) if workspaceDir != "" { if c.Action("agentic.commit").Exists() { - _ = c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir)) + if result := c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir)); !result.OK { + return result + } } } case messages.PRNeedsReview: workspaceDir := findWorkspaceByPRWithInfo(ev.Repo, "", ev.PRNum, ev.PRURL) if workspaceDir != "" { if c.Action("agentic.commit").Exists() { - _ = c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir)) + if result := c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir)); !result.OK { + return result + } } } } @@ -160,7 +163,9 @@ func handleCompletionPoke(c *core.Core, msg core.Message) core.Result { } if c != nil && c.Action("runner.poke").Exists() { - _ = c.ACTION(messages.PokeQueue{}) + if result := c.ACTION(messages.PokeQueue{}); !result.OK { + return result + } return core.Result{OK: true} } performAsyncIfRegistered(c, "agentic.poke", core.NewOptions()) diff --git a/go/pkg/agentic/mirror.go b/go/pkg/agentic/mirror.go index 9e38f00b..acd92f18 100644 --- a/go/pkg/agentic/mirror.go +++ b/go/pkg/agentic/mirror.go @@ -71,7 +71,10 @@ func (s *PrepSubsystem) mirror(ctx context.Context, input MirrorInput) core.Resu continue } - _ = process.RunIn(ctx, repoDir, "git", "fetch", "github") + if fetchResult := process.RunIn(ctx, repoDir, "git", "fetch", "github"); !fetchResult.OK { + skipped = append(skipped, core.Concat(repo, ": fetch github failed")) + continue + } localBase := s.DefaultBranch(repoDir) ahead := s.commitsAhead(repoDir, "github/main", localBase) @@ -113,7 +116,9 @@ func (s *PrepSubsystem) mirror(ctx context.Context, input MirrorInput) core.Resu if !prResult.OK { sync.Skipped = core.Sprintf("PR creation failed: %s", prResult.Error()) } else { - sync.PRURL, _ = prResult.Value.(string) + if prURL, ok := prResult.Value.(string); ok { + sync.PRURL = prURL + } } synced = append(synced, sync) diff --git a/go/pkg/agentic/pipeline_budget.go b/go/pkg/agentic/pipeline_budget.go index 15f21ca6..c7a9b7a0 100644 --- a/go/pkg/agentic/pipeline_budget.go +++ b/go/pkg/agentic/pipeline_budget.go @@ -203,7 +203,7 @@ func (s *PrepSubsystem) pipelineBudgetEntries() []pipelineBudgetEntry { return entries } -// _ = s.pipelineBudgetMirrorToStore(entry) +// result := s.pipelineBudgetMirrorToStore(entry) func (s *PrepSubsystem) pipelineBudgetMirrorToStore(entry pipelineBudgetEntry) { key := pipelineBudgetEntryKey(entry) s.stateStoreSet(pipelineBudgetStoreGroup, key, entry) diff --git a/go/pkg/agentic/pr.go b/go/pkg/agentic/pr.go index be037678..3a14bd8c 100644 --- a/go/pkg/agentic/pr.go +++ b/go/pkg/agentic/pr.go @@ -242,9 +242,9 @@ var prGet = func(s *PrepSubsystem, ctx context.Context, _ *mcp.CallToolRequest, } var pr pullRequestView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, input.Repo, input.Number), &pr) - if err != nil { - return nil, PRGetOutput{}, core.E("prGet", core.Concat("failed to read PR ", core.Sprint(input.Number)), err) + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, input.Repo, input.Number), &pr) + if !result.OK { + return nil, PRGetOutput{}, core.E("prGet", core.Concat("failed to read PR ", core.Sprint(input.Number)), forgeResultError(result)) } return nil, PRGetOutput{ @@ -284,8 +284,8 @@ var prMerge = func(s *PrepSubsystem, ctx context.Context, _ *mcp.CallToolRequest method = "merge" } - if err := s.forge.mergePullRequest(ctx, org, input.Repo, int64(input.Number), method); err != nil { - return nil, PRMergeOutput{}, core.E("prMerge", core.Concat("failed to merge PR ", core.Sprint(input.Number)), err) + if result := s.forge.mergePullRequest(ctx, org, input.Repo, int64(input.Number), method); !result.OK { + return nil, PRMergeOutput{}, core.E("prMerge", core.Concat("failed to merge PR ", core.Sprint(input.Number)), forgeResultError(result)) } output := PRMergeOutput{ @@ -322,23 +322,23 @@ func (s *PrepSubsystem) buildPRBody(workspaceStatus *WorkspaceStatus) string { var forgeCreatePR = func(s *PrepSubsystem, ctx context.Context, org, repo, head, base, title, body string) (string, int, error) { var pullRequest pullRequestView - err := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls", org, repo), &CreatePullRequestOption{ + result := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls", org, repo), &CreatePullRequestOption{ Title: title, Body: body, Head: head, Base: base, }, &pullRequest) - if err != nil { - return "", 0, core.E("forgeCreatePR", "create PR failed", err) + if !result.OK { + return "", 0, core.E("forgeCreatePR", "create PR failed", forgeResultError(result)) } return pullRequest.HTMLURL, int(pullRequestNumber(pullRequest)), nil } func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) { - if err := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", org, repo, issue), map[string]any{ + if result := s.forge.postJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", org, repo, issue), map[string]any{ "body": comment, - }, nil); err != nil { - core.Warn("agentic.commentOnIssue: failed to post issue comment", "repo", repo, "issue", issue, "reason", err) + }, nil); !result.OK { + core.Warn("agentic.commentOnIssue: failed to post issue comment", "repo", repo, "issue", issue, "reason", forgeResultError(result)) } } @@ -483,11 +483,11 @@ var closePR = func(s *PrepSubsystem, ctx context.Context, _ *mcp.CallToolRequest } var pr pullRequestView - err := s.forge.patchJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, input.Repo, input.Number), &EditPullRequestOption{ + result := s.forge.patchJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls/%d", org, input.Repo, input.Number), &EditPullRequestOption{ State: "closed", }, &pr) - if err != nil { - return nil, ClosePROutput{}, core.E("closePR", core.Concat("failed to close PR ", core.Sprint(input.Number)), err) + if !result.OK { + return nil, ClosePROutput{}, core.E("closePR", core.Concat("failed to close PR ", core.Sprint(input.Number)), forgeResultError(result)) } state := pr.State @@ -556,9 +556,9 @@ var deleteBranch = func(s *PrepSubsystem, ctx context.Context, _ *mcp.CallToolRe var listRepoPRs = func(s *PrepSubsystem, ctx context.Context, org, repo, state string) ([]PRInfo, error) { var pullRequests []pullRequestView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &pullRequests) - if err != nil { - return nil, core.E("listRepoPRs", core.Concat("failed to list PRs for ", repo), err) + requestResult := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &pullRequests) + if !requestResult.OK { + return nil, core.E("listRepoPRs", core.Concat("failed to list PRs for ", repo), forgeResultError(requestResult)) } var result []PRInfo diff --git a/go/pkg/agentic/prep.go b/go/pkg/agentic/prep.go index 0a716c44..9393ccec 100644 --- a/go/pkg/agentic/prep.go +++ b/go/pkg/agentic/prep.go @@ -397,7 +397,7 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result { // s.registerCommands(ctx) // subsystem := agentic.NewPrep() -// _ = subsystem.OnShutdown(context.Background()) +// result := subsystem.OnShutdown(context.Background()) func (s *PrepSubsystem) OnShutdown(ctx context.Context) core.Result { s.frozen = true if result := s.flushPersistedState(ctx); !result.OK { @@ -611,7 +611,7 @@ func envOr(key, fallback string) string { // subsystem := agentic.NewPrep() // name := subsystem.Name() -// _ = name // "agentic" +// core.Println(name) // "agentic" func (s *PrepSubsystem) Name() string { return "agentic" } // subsystem := agentic.NewPrep() @@ -680,7 +680,7 @@ func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) { } // subsystem := agentic.NewPrep() -// _ = subsystem.Shutdown(context.Background()) +// err := subsystem.Shutdown(context.Background()) func (s *PrepSubsystem) Shutdown(_ context.Context) error { return nil } // input := agentic.PrepInput{Repo: "go-io", Issue: 15, Task: "Migrate to Core primitives"} @@ -1176,7 +1176,7 @@ func promptSnapshotHash(prompt string) string { return hex.EncodeToString(sum[:]) } -// _ = s.runWorkspaceLanguagePrep(ctx, "/srv/.core/workspace/core/go-io/task-42", "/srv/Code/core/go-io") +// result := s.runWorkspaceLanguagePrep(ctx, "/srv/.core/workspace/core/go-io/task-42", "/srv/Code/core/go-io") var runWorkspaceLanguagePrep = func(s *PrepSubsystem, ctx context.Context, workspaceDir, repoDir string) error { process := s.Core().Process() @@ -1224,8 +1224,8 @@ var runWorkspaceLanguagePrep = func(s *PrepSubsystem, ctx context.Context, works func (s *PrepSubsystem) getIssueBody(ctx context.Context, org, repo string, issue int) string { var iss issueView - err := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d", org, repo, issue), &iss) - if err != nil { + result := s.forge.getJSON(ctx, core.Sprintf("/api/v1/repos/%s/%s/issues/%d", org, repo, issue), &iss) + if !result.OK { return "" } return core.Sprintf("# %s\n\n%s", iss.Title, iss.Body) @@ -1350,8 +1350,12 @@ func (s *PrepSubsystem) getGitLog(repoPath string) string { } func (s *PrepSubsystem) pullWikiContent(ctx context.Context, org, repo string) string { - pages, err := s.forge.listWikiPages(ctx, org, repo) - if err != nil || len(pages) == 0 { + pagesResult := s.forge.listWikiPages(ctx, org, repo) + if !pagesResult.OK { + return "" + } + pages := pagesResult.Value.([]WikiPageMetaData) + if len(pages) == 0 { return "" } @@ -1361,8 +1365,12 @@ func (s *PrepSubsystem) pullWikiContent(ctx context.Context, org, repo string) s if name == "" { name = meta.Title } - page, pageErr := s.forge.getWikiPage(ctx, org, repo, name) - if pageErr != nil || page.ContentBase64 == "" { + pageResult := s.forge.getWikiPage(ctx, org, repo, name) + if !pageResult.OK { + continue + } + page := pageResult.Value.(*WikiPage) + if page.ContentBase64 == "" { continue } content, _ := base64.StdEncoding.DecodeString(page.ContentBase64) diff --git a/go/pkg/agentic/provider_manager.go b/go/pkg/agentic/provider_manager.go index 6b0699a0..70dccd2c 100644 --- a/go/pkg/agentic/provider_manager.go +++ b/go/pkg/agentic/provider_manager.go @@ -58,7 +58,7 @@ type ProviderGenerateFunc func(context.Context, string, map[string]any) (string, // Stream sends provider output to the callback as it arrives. // // provider, _ := manager.Provider("claude") -// _ = provider.Stream(ctx, "Draft a release note", nil, func(token string) { core.Print(nil, token) }) +// result := provider.Stream(ctx, "Draft a release note", nil, func(token string) { core.Print(nil, token) }) type ProviderStreamFunc func(context.Context, string, map[string]any, func(string)) error func newContentProvider(name, defaultModel string, available bool, generate ProviderGenerateFunc) *AgenticProviderInterface { diff --git a/go/pkg/agentic/qa_analysis.go b/go/pkg/agentic/qa_analysis.go index da1b1504..c0ae3b27 100644 --- a/go/pkg/agentic/qa_analysis.go +++ b/go/pkg/agentic/qa_analysis.go @@ -132,7 +132,6 @@ func qaAnalysisWorkspaceRows(workspace *store.Workspace, kind string) []string { // findingToPoint projects a finding into the RFC ยง7 clustering dimensions. // Frequency defaults to 1 for direct callers; the cluster builder supplies the // observed per-fingerprint frequency for each point. -// func qaAnalysisPointCoords(finding QAFinding, frequency float64) []float64 { return []float64{ qaAnalysisHash(core.Lower(finding.Tool)), @@ -361,6 +360,8 @@ func qaAnalysisHash(value string) float64 { return 0 } hash := fnv.New32a() - _, _ = hash.Write([]byte(value)) + if _, err := hash.Write([]byte(value)); err != nil { + return 0 + } return float64(hash.Sum32()) } diff --git a/go/pkg/agentic/qa_cluster.go b/go/pkg/agentic/qa_cluster.go index 63754d53..ab6a3c9d 100644 --- a/go/pkg/agentic/qa_cluster.go +++ b/go/pkg/agentic/qa_cluster.go @@ -185,7 +185,9 @@ func qaClusterAddToken(coords []float64, token string, weight float64) { func qaClusterBucket(token string) int { hash := fnv.New32a() - _, _ = hash.Write([]byte(token)) + if _, err := hash.Write([]byte(token)); err != nil { + return 0 + } return int(hash.Sum32() % qaClusterFeatureDimensions) } diff --git a/go/pkg/agentic/queue.go b/go/pkg/agentic/queue.go index 4f94d98c..1030caae 100644 --- a/go/pkg/agentic/queue.go +++ b/go/pkg/agentic/queue.go @@ -130,7 +130,9 @@ func (s *PrepSubsystem) loadAgentsConfig() *AgentsConfig { func (s *PrepSubsystem) delayForAgent(agent string) time.Duration { var rates map[string]RateConfig if s.ServiceRuntime != nil { - rates, _ = s.Core().Config().Get("agents.rates").Value.(map[string]RateConfig) + if configured, ok := s.Core().Config().Get("agents.rates").Value.(map[string]RateConfig); ok { + rates = configured + } } if rates == nil { config := s.loadAgentsConfig() @@ -259,7 +261,9 @@ func (s *PrepSubsystem) canDispatchAgent(agent string) bool { if s.ServiceRuntime != nil { configurationResult := s.Core().Config().Get("agents.concurrency") if configurationResult.OK { - concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit) + if configured, ok := configurationResult.Value.(map[string]ConcurrencyLimit); ok { + concurrency = configured + } } } if concurrency == nil { diff --git a/go/pkg/agentic/review_queue.go b/go/pkg/agentic/review_queue.go index 7db254da..73643120 100644 --- a/go/pkg/agentic/review_queue.go +++ b/go/pkg/agentic/review_queue.go @@ -311,7 +311,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer return result } -// _ = s.pushAndMerge(ctx, repoDir, "go-io") +// result := s.pushAndMerge(ctx, repoDir, "go-io") var pushAndMerge = func(s *PrepSubsystem, ctx context.Context, repoDir, repo string) error { process := s.Core().Process() if r := process.RunIn(ctx, repoDir, "git", "push", "github", "HEAD:refs/heads/dev", "--force"); !r.OK { @@ -327,7 +327,7 @@ var pushAndMerge = func(s *PrepSubsystem, ctx context.Context, repoDir, repo str return nil } -// _ = s.dispatchFixFromQueue(ctx, "go-io", task) +// result := s.dispatchFixFromQueue(ctx, "go-io", task) var dispatchFixFromQueue = func(s *PrepSubsystem, ctx context.Context, repo, task string) error { input := DispatchInput{ Repo: repo, diff --git a/go/pkg/agentic/scan.go b/go/pkg/agentic/scan.go index 249bd2ed..d82a0331 100644 --- a/go/pkg/agentic/scan.go +++ b/go/pkg/agentic/scan.go @@ -91,10 +91,11 @@ var scan = func(s *PrepSubsystem, ctx context.Context, _ *mcp.CallToolRequest, i } var listOrgRepos = func(s *PrepSubsystem, ctx context.Context, org string) ([]string, error) { - repos, err := s.forge.listOrgRepos(ctx, org) - if err != nil { - return nil, core.E("scan.listOrgRepos", "failed to list repos", err) + reposResult := s.forge.listOrgRepos(ctx, org) + if !reposResult.OK { + return nil, core.E("scan.listOrgRepos", "failed to list repos", forgeResultError(reposResult)) } + repos := reposResult.Value.([]Repository) var allNames []string for _, repoInfo := range repos { diff --git a/go/pkg/agentic/training_journal.go b/go/pkg/agentic/training_journal.go index 4aa21296..0bf824b7 100644 --- a/go/pkg/agentic/training_journal.go +++ b/go/pkg/agentic/training_journal.go @@ -49,7 +49,7 @@ func pipelineTrainingExportPath() string { return core.JoinPath(CoreRoot(), "training", "export.jsonl") } -// _ = ensureParentDir("/tmp/.core/training/journal.jsonl") +// result := ensureParentDir("/tmp/.core/training/journal.jsonl") var ensureParentDir = func(path string) error { if ensureResult := fs.EnsureDir(core.PathDir(path)); !ensureResult.OK { if err, ok := ensureResult.Value.(error); ok { @@ -60,7 +60,7 @@ var ensureParentDir = func(path string) error { return nil } -// _ = appendJSONLRecord("/tmp/test.jsonl", map[string]any{"repo": "go-io"}) +// result := appendJSONLRecord("/tmp/test.jsonl", map[string]any{"repo": "go-io"}) var appendJSONLRecord = func(path string, value any) error { if err := ensureParentDir(path); err != nil { return err @@ -155,7 +155,7 @@ func filterZeroFindingTrainingEntries(entries []PipelineTrainingEntry) []Pipelin return clean } -// _ = writePipelineTrainingExport("/tmp/.core/training/export.jsonl", entries) +// result := writePipelineTrainingExport("/tmp/.core/training/export.jsonl", entries) var writePipelineTrainingExport = func(path string, entries []PipelineTrainingEntry) error { if err := ensureParentDir(path); err != nil { return err diff --git a/go/pkg/brain/actions.go b/go/pkg/brain/actions.go index 987aa503..be353d02 100644 --- a/go/pkg/brain/actions.go +++ b/go/pkg/brain/actions.go @@ -11,7 +11,7 @@ import ( type DirectOptions struct{} // subsystem := brain.NewDirect() -// _ = subsystem.OnStartup(context.Background()) +// result := subsystem.OnStartup(context.Background()) func (s *DirectSubsystem) OnStartup(_ context.Context) core.Result { if s.ServiceRuntime == nil || s.Core() == nil { return core.Result{OK: true} diff --git a/go/pkg/brain/brain.go b/go/pkg/brain/brain.go index 456296df..e453cbcc 100644 --- a/go/pkg/brain/brain.go +++ b/go/pkg/brain/brain.go @@ -48,7 +48,7 @@ func (s *Subsystem) RegisterTools(svc *coremcp.Service) { s.registerBrainTools(svc) } -// _ = subsystem.Shutdown(context.Background()) +// err := subsystem.Shutdown(context.Background()) var Shutdown = func(_ *Subsystem, _ context.Context) error { return nil } diff --git a/go/pkg/brain/direct.go b/go/pkg/brain/direct.go index bf021e66..ce9db63e 100644 --- a/go/pkg/brain/direct.go +++ b/go/pkg/brain/direct.go @@ -93,7 +93,7 @@ func (s *DirectSubsystem) RegisterTools(svc *coremcp.Service) { s.RegisterMessagingTools(svc) } -// _ = subsystem.Shutdown(context.Background()) +// err := subsystem.Shutdown(context.Background()) func (s *DirectSubsystem) Shutdown(_ context.Context) error { return nil } func brainKeyPath(home string) string { diff --git a/go/pkg/brain/provider_contract.go b/go/pkg/brain/provider_contract.go index b49bb436..467dc12d 100644 --- a/go/pkg/brain/provider_contract.go +++ b/go/pkg/brain/provider_contract.go @@ -31,7 +31,7 @@ type ElementSpec struct { type RouteDescription struct { Method string `json:"method"` - Path string `json:"path"` + Path string `json:"route"` Summary string `json:"summary"` Description string `json:"description"` Tags []string `json:"tags,omitempty"` diff --git a/go/pkg/brain/provider_contract_example_test.go b/go/pkg/brain/provider_contract_example_test.go new file mode 100644 index 00000000..fdf234d9 --- /dev/null +++ b/go/pkg/brain/provider_contract_example_test.go @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package brain + +import core "dappco.re/go" + +func ExampleRouteDescription() { + route := RouteDescription{Method: "GET", Path: "/status", Summary: "Status"} + core.Println(route.Method, route.Path) + // Output: GET /status +} diff --git a/go/pkg/brain/provider_contract_test.go b/go/pkg/brain/provider_contract_test.go new file mode 100644 index 00000000..7cbbb1b9 --- /dev/null +++ b/go/pkg/brain/provider_contract_test.go @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package brain + +import ( + "testing" + + core "dappco.re/go" +) + +func TestProviderContract_RouteDescription_Good(t *testing.T) { + route := RouteDescription{Method: "GET", Path: "/status", Summary: "Status"} + core.AssertEqual(t, "GET", route.Method) + core.AssertEqual(t, "/status", route.Path) +} + +func TestProviderContract_RouteDescription_Bad(t *testing.T) { + route := RouteDescription{} + core.AssertEqual(t, "", route.Method) + core.AssertEqual(t, "", route.Path) +} + +func TestProviderContract_RouteDescription_Ugly(t *testing.T) { + route := RouteDescription{Tags: []string{"brain"}, RequestBody: map[string]any{"query": "agent"}} + core.AssertEqual(t, []string{"brain"}, route.Tags) + core.AssertNotNil(t, route.RequestBody) +} diff --git a/go/pkg/lib/lib.go b/go/pkg/lib/lib.go index 5931f1f0..c3833fb0 100644 --- a/go/pkg/lib/lib.go +++ b/go/pkg/lib/lib.go @@ -95,7 +95,10 @@ func ensureMounted() core.Result { emb := mounted.Value.(*core.Embed) item.assign(emb) - _ = mountedData.Set(item.name, emb) + if setResult := mountedData.Set(item.name, emb); !setResult.OK { + mountResult = setResult + return mountResult + } } data = mountedData diff --git a/go/pkg/monitor/monitor.go b/go/pkg/monitor/monitor.go index a6f87f09..0deb4f4d 100644 --- a/go/pkg/monitor/monitor.go +++ b/go/pkg/monitor/monitor.go @@ -16,7 +16,7 @@ import ( ) // readResult := fs.Read(core.JoinPath(workspaceRoot, name, "status.json")) -// if text, ok := resultString(readResult); ok { _ = core.JSONUnmarshalString(text, &workspaceStatus) } +// if text, ok := resultString(readResult); ok { parseResult := core.JSONUnmarshalString(text, &workspaceStatus); core.Println(parseResult.OK) } var fs = agentic.LocalFs() func brainKeyPath(home string) string { @@ -218,7 +218,7 @@ func (m *Subsystem) OnShutdown(ctx context.Context) core.Result { return core.Result{OK: true} } -// _ = service.Shutdown(ctx) +// err := service.Shutdown(ctx) var Shutdown = func(m *Subsystem, _ context.Context) error { if m.cancel != nil { m.cancel() diff --git a/go/pkg/runner/queue.go b/go/pkg/runner/queue.go index 46a9976c..c6aa7ac1 100644 --- a/go/pkg/runner/queue.go +++ b/go/pkg/runner/queue.go @@ -42,10 +42,10 @@ type RateConfig struct { } // flat := runner.ConcurrencyLimit{} -// _ = yaml.Unmarshal([]byte("1\n"), &flat) +// err := yaml.Unmarshal([]byte("1\n"), &flat) // // nested := runner.ConcurrencyLimit{} -// _ = yaml.Unmarshal([]byte("total: 5\ngpt-5.4: 1\n"), &nested) +// err = yaml.Unmarshal([]byte("total: 5\ngpt-5.4: 1\n"), &nested) type ConcurrencyLimit = agentcompat.ConcurrencyLimit // identity := runner.AgentIdentity{Host: "local", Runner: "claude", Active: true, Roles: []string{"dispatch"}} @@ -99,13 +99,15 @@ func (s *Service) loadAgentsConfig() *AgentsConfig { } } -// if can, reason := s.canDispatchAgent("codex"); !can { _ = reason } +// if can, reason := s.canDispatchAgent("codex"); !can { core.Println(reason) } func (s *Service) canDispatchAgent(agent string) (bool, string) { var concurrency map[string]ConcurrencyLimit if s.ServiceRuntime != nil { configurationResult := s.Core().Config().Get("agents.concurrency") if configurationResult.OK { - concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit) + if configured, ok := configurationResult.Value.(map[string]ConcurrencyLimit); ok { + concurrency = configured + } } } if concurrency == nil { @@ -276,7 +278,9 @@ func (s *Service) drainOne() bool { func (s *Service) delayForAgent(agent string) time.Duration { var rates map[string]RateConfig if s.ServiceRuntime != nil { - rates, _ = s.Core().Config().Get("agents.rates").Value.(map[string]RateConfig) + if configured, ok := s.Core().Config().Get("agents.rates").Value.(map[string]RateConfig); ok { + rates = configured + } } if rates == nil { config := s.loadAgentsConfig() diff --git a/go/pkg/runner/runner.go b/go/pkg/runner/runner.go index c97e26bc..1cb03f26 100644 --- a/go/pkg/runner/runner.go +++ b/go/pkg/runner/runner.go @@ -197,7 +197,6 @@ func (s *Service) HandleIPCEvents(coreApp *core.Core, msg core.Message) core.Res case messages.PokeQueue: s.drainQueueAndNotify(coreApp) - _ = ev } return core.Result{OK: true} }