agent/pkg/agentic/ingest.go
Snider a0dc9c32e7 refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package

All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli

Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00

120 lines
2.9 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"bytes"
"encoding/json"
"net/http"
"os"
"path/filepath"
core "dappco.re/go/core"
)
// ingestFindings reads the agent output log and creates issues via the API
// for scan/audit results. Only runs for conventions and security templates.
func (s *PrepSubsystem) ingestFindings(wsDir string) {
st, err := readStatus(wsDir)
if err != nil || st.Status != "completed" {
return
}
// Read the log file
logFiles, _ := filepath.Glob(core.JoinPath(wsDir, "agent-*.log"))
if len(logFiles) == 0 {
return
}
r := fs.Read(logFiles[0])
if !r.OK || len(r.Value.(string)) < 100 {
return
}
body := r.Value.(string)
// Skip quota errors
if core.Contains(body, "QUOTA_EXHAUSTED") || core.Contains(body, "QuotaError") {
return
}
// Only ingest if there are actual findings (file:line references)
findings := countFileRefs(body)
if findings < 2 {
return // No meaningful findings
}
// Determine issue type from the template used
issueType := "task"
priority := "normal"
if core.Contains(body, "security") || core.Contains(body, "Security") {
issueType = "bug"
priority = "high"
}
// Create a single issue per repo with all findings in the body
title := core.Sprintf("Scan findings for %s (%d items)", st.Repo, findings)
// Truncate body to reasonable size for issue description
description := body
if len(description) > 10000 {
description = description[:10000] + "\n\n... (truncated, see full log in workspace)"
}
s.createIssueViaAPI(st.Repo, title, description, issueType, priority, "scan")
}
// countFileRefs counts file:line references in the output (indicates real findings)
func countFileRefs(body string) int {
count := 0
for i := 0; i < len(body)-5; i++ {
if body[i] == '`' {
// Look for pattern: `file.go:123`
j := i + 1
for j < len(body) && body[j] != '`' && j-i < 100 {
j++
}
if j < len(body) && body[j] == '`' {
ref := body[i+1 : j]
if core.Contains(ref, ".go:") || core.Contains(ref, ".php:") {
count++
}
}
}
}
return count
}
// createIssueViaAPI posts an issue to the lthn.sh API
func (s *PrepSubsystem) createIssueViaAPI(repo, title, description, issueType, priority, source string) {
if s.brainKey == "" {
return
}
// Read the agent API key from file
home, _ := os.UserHomeDir()
r := fs.Read(core.JoinPath(home, ".claude", "agent-api.key"))
if !r.OK {
return
}
apiKey := core.Trim(r.Value.(string))
payload, _ := json.Marshal(map[string]string{
"title": title,
"description": description,
"type": issueType,
"priority": priority,
"reporter": "cladius",
})
req, _ := http.NewRequest("POST", s.brainURL+"/v1/issues", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := s.client.Do(req)
if err != nil {
return
}
resp.Body.Close()
}