refactor(agentic): route file I/O through core.Fs

Replace raw os.* file operations with Core Fs equivalents:
- os.Stat → fs.Exists/fs.IsFile/fs.IsDir (resume, pr, plan, mirror, prep)
- os.ReadDir → fs.List (queue, status, plan, mirror, review_queue)
- os.Remove → fs.Delete (dispatch)
- os.OpenFile(append) → fs.Append (events, review_queue)
- strings.Replace → core.Replace (scan)

Eliminates os import from resume.go, pr.go. Eliminates strings
import from scan.go. Trades os for io in events.go.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-22 09:08:45 +00:00
parent ede5d6f561
commit 3022f05fb8
11 changed files with 61 additions and 46 deletions

View file

@ -125,7 +125,7 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir, srcDir string) (int, st
// Clean up stale BLOCKED.md from previous runs so it doesn't
// prevent this run from completing
os.Remove(core.JoinPath(srcDir, "BLOCKED.md"))
fs.Delete(core.JoinPath(srcDir, "BLOCKED.md"))
proc, err := process.StartWithOptions(context.Background(), process.RunOptions{
Command: command,

View file

@ -4,7 +4,7 @@ package agentic
import (
"encoding/json"
"os"
"io"
"time"
core "dappco.re/go/core"
@ -42,10 +42,11 @@ func emitCompletionEvent(agent, workspace, status string) {
}
// Append to events log
f, err := os.OpenFile(eventsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
r := fs.Append(eventsFile)
if !r.OK {
return
}
defer f.Close()
f.Write(append(data, '\n'))
wc := r.Value.(io.WriteCloser)
defer wc.Close()
wc.Write(append(data, '\n'))
}

View file

@ -246,17 +246,18 @@ func filesChanged(repoDir, base, head string) int {
// listLocalRepos returns repo names that exist as directories in basePath.
func (s *PrepSubsystem) listLocalRepos(basePath string) []string {
entries, err := os.ReadDir(basePath)
if err != nil {
r := fs.List(basePath)
if !r.OK {
return nil
}
entries := r.Value.([]os.DirEntry)
var repos []string
for _, e := range entries {
if !e.IsDir() {
continue
}
// Must have a .git directory
if _, err := os.Stat(core.JoinPath(basePath, e.Name(), ".git")); err == nil {
if fs.IsDir(core.JoinPath(basePath, e.Name(), ".git")) {
repos = append(repos, e.Name())
}
}

View file

@ -279,7 +279,7 @@ func (s *PrepSubsystem) planDelete(_ context.Context, _ *mcp.CallToolRequest, in
}
path := planPath(PlansRoot(), input.ID)
if _, err := os.Stat(path); err != nil {
if !fs.Exists(path) {
return nil, PlanDeleteOutput{}, core.E("planDelete", "plan not found: "+input.ID, nil)
}
@ -301,10 +301,11 @@ func (s *PrepSubsystem) planList(_ context.Context, _ *mcp.CallToolRequest, inpu
return nil, PlanListOutput{}, core.E("planList", "failed to access plans directory", err)
}
entries, err := os.ReadDir(dir)
if err != nil {
return nil, PlanListOutput{}, core.E("planList", "failed to read plans directory", err)
r := fs.List(dir)
if !r.OK {
return nil, PlanListOutput{}, nil
}
entries := r.Value.([]os.DirEntry)
var plans []Plan
for _, entry := range entries {

View file

@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"net/http"
"os"
"os/exec"
core "dappco.re/go/core"
@ -58,7 +57,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
wsDir := core.JoinPath(WorkspaceRoot(), input.Workspace)
srcDir := core.JoinPath(wsDir, "src")
if _, err := os.Stat(srcDir); err != nil {
if !fs.IsDir(srcDir) {
return nil, CreatePROutput{}, core.E("createPR", "workspace not found: "+input.Workspace, nil)
}

View file

@ -250,12 +250,20 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
wsTmpl = "review"
}
promptContent, _ := lib.Prompt(input.Template)
promptContent := ""
if r := lib.Prompt(input.Template); r.OK {
promptContent = r.Value.(string)
}
personaContent := ""
if input.Persona != "" {
personaContent, _ = lib.Persona(input.Persona)
if r := lib.Persona(input.Persona); r.OK {
personaContent = r.Value.(string)
}
}
flowContent := ""
if r := lib.Flow(detectLanguage(repoPath)); r.OK {
flowContent = r.Value.(string)
}
flowContent, _ := lib.Flow(detectLanguage(repoPath))
wsData := &lib.WorkspaceData{
Repo: input.Repo,
@ -319,13 +327,13 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
// --- Prompt templates ---
func (s *PrepSubsystem) writePromptTemplate(template, wsDir string) {
prompt, err := lib.Template(template)
if err != nil {
// Fallback to default template
prompt, _ = lib.Template("default")
if prompt == "" {
prompt = "Read TODO.md and complete the task. Work in src/.\n"
}
r := lib.Template(template)
if !r.OK {
r = lib.Template("default")
}
prompt := "Read TODO.md and complete the task. Work in src/.\n"
if r.OK {
prompt = r.Value.(string)
}
fs.Write(core.JoinPath(wsDir, "src", "PROMPT.md"), prompt)
@ -337,12 +345,12 @@ func (s *PrepSubsystem) writePromptTemplate(template, wsDir string) {
// and writes PLAN.md into the workspace src/ directory.
func (s *PrepSubsystem) writePlanFromTemplate(templateSlug string, variables map[string]string, task string, wsDir string) {
// Load template from embedded prompts package
data, err := lib.Template(templateSlug)
if err != nil {
r := lib.Template(templateSlug)
if !r.OK {
return // Template not found, skip silently
}
content := data
content := r.Value.(string)
// Substitute variables ({{variable_name}} → value)
for key, value := range variables {
@ -649,7 +657,7 @@ func detectLanguage(repoPath string) string {
{"Dockerfile", "docker"},
}
for _, c := range checks {
if _, err := os.Stat(core.JoinPath(repoPath, c.file)); err == nil {
if fs.IsFile(core.JoinPath(repoPath, c.file)) {
return c.lang
}
}

View file

@ -119,10 +119,11 @@ func (s *PrepSubsystem) delayForAgent(agent string) time.Duration {
func (s *PrepSubsystem) countRunningByAgent(agent string) int {
wsRoot := WorkspaceRoot()
entries, err := os.ReadDir(wsRoot)
if err != nil {
r := fs.List(wsRoot)
if !r.OK {
return 0
}
entries := r.Value.([]os.DirEntry)
count := 0
for _, entry := range entries {
@ -171,10 +172,11 @@ func (s *PrepSubsystem) drainQueue() {
wsRoot := WorkspaceRoot()
entries, err := os.ReadDir(wsRoot)
if err != nil {
r := fs.List(wsRoot)
if !r.OK {
return
}
entries := r.Value.([]os.DirEntry)
for _, entry := range entries {
if !entry.IsDir() {

View file

@ -4,7 +4,6 @@ package agentic
import (
"context"
"os"
core "dappco.re/go/core"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -48,7 +47,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
srcDir := core.JoinPath(wsDir, "src")
// Verify workspace exists
if _, err := os.Stat(srcDir); err != nil {
if !fs.IsDir(srcDir) {
return nil, ResumeOutput{}, core.E("resume", "workspace not found: "+input.Workspace, nil)
}

View file

@ -5,6 +5,7 @@ package agentic
import (
"context"
"encoding/json"
"io"
"os"
"os/exec"
"regexp"
@ -134,10 +135,11 @@ func (s *PrepSubsystem) reviewQueue(ctx context.Context, _ *mcp.CallToolRequest,
// findReviewCandidates returns repos that are ahead of GitHub main.
func (s *PrepSubsystem) findReviewCandidates(basePath string) []string {
entries, err := os.ReadDir(basePath)
if err != nil {
r := fs.List(basePath)
if !r.OK {
return nil
}
entries := r.Value.([]os.DirEntry)
var candidates []string
for _, e := range entries {
@ -361,11 +363,13 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string
jsonLine, _ := json.Marshal(entry)
jsonlPath := core.JoinPath(dataDir, "reviews.jsonl")
f, err := os.OpenFile(jsonlPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
defer f.Close()
f.Write(append(jsonLine, '\n'))
r := fs.Append(jsonlPath)
if !r.OK {
return
}
wc := r.Value.(io.WriteCloser)
defer wc.Close()
wc.Write(append(jsonLine, '\n'))
}
// saveRateLimitState persists rate limit info for cross-run awareness.

View file

@ -6,7 +6,6 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
core "dappco.re/go/core"
"github.com/modelcontextprotocol/go-sdk/mcp"
@ -197,7 +196,7 @@ func (s *PrepSubsystem) listRepoIssues(ctx context.Context, org, repo, label str
Title: issue.Title,
Labels: labels,
Assignee: assignee,
URL: strings.Replace(issue.HTMLURL, "https://forge.lthn.ai", s.forgeURL, 1),
URL: core.Replace(issue.HTMLURL, "https://forge.lthn.ai", s.forgeURL),
})
}

View file

@ -113,10 +113,11 @@ func (s *PrepSubsystem) registerStatusTool(server *mcp.Server) {
func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, input StatusInput) (*mcp.CallToolResult, StatusOutput, error) {
wsRoot := WorkspaceRoot()
entries, err := os.ReadDir(wsRoot)
if err != nil {
return nil, StatusOutput{}, core.E("status", "no workspaces found", err)
r := fs.List(wsRoot)
if !r.OK {
return nil, StatusOutput{}, core.E("status", "no workspaces found", nil)
}
entries := r.Value.([]os.DirEntry)
var workspaces []WorkspaceInfo