fix(ax): continue workspace naming cleanup
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
e82112024c
commit
1cc8fb50e1
25 changed files with 277 additions and 277 deletions
|
|
@ -116,12 +116,12 @@ func (commands appCommandSet) check(_ core.Options) core.Result {
|
|||
core.Print(nil, " agents: %s (MISSING)", agentsPath)
|
||||
}
|
||||
|
||||
wsRoot := agentic.WorkspaceRoot()
|
||||
if fs.IsDir(wsRoot) {
|
||||
workspaceRoot := agentic.WorkspaceRoot()
|
||||
if fs.IsDir(workspaceRoot) {
|
||||
statusFiles := agentic.WorkspaceStatusPaths()
|
||||
core.Print(nil, " workspace: %s (%d workspaces)", wsRoot, len(statusFiles))
|
||||
core.Print(nil, " workspace: %s (%d workspaces)", workspaceRoot, len(statusFiles))
|
||||
} else {
|
||||
core.Print(nil, " workspace: %s (MISSING)", wsRoot)
|
||||
core.Print(nil, " workspace: %s (MISSING)", workspaceRoot)
|
||||
}
|
||||
|
||||
core.Print(nil, " services: %d registered", len(commands.core.Services()))
|
||||
|
|
|
|||
|
|
@ -183,31 +183,31 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core
|
|||
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-qa") {
|
||||
return core.Result{Value: true, OK: true}
|
||||
}
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
workspaceDir := options.String("workspace")
|
||||
if workspaceDir == "" {
|
||||
return core.Result{Value: core.E("agentic.qa", "workspace is required", nil), OK: false}
|
||||
}
|
||||
passed := s.runQA(wsDir)
|
||||
passed := s.runQA(workspaceDir)
|
||||
if !passed {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
workspaceStatus.Status = "failed"
|
||||
workspaceStatus.Question = "QA check failed — build or tests did not pass"
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Emit QA result for observability (monitor picks this up)
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
repo = workspaceStatus.Repo
|
||||
}
|
||||
s.Core().ACTION(messages.QAResult{
|
||||
Workspace: WorkspaceName(wsDir),
|
||||
Workspace: WorkspaceName(workspaceDir),
|
||||
Repo: repo,
|
||||
Passed: passed,
|
||||
})
|
||||
|
|
@ -224,15 +224,15 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options)
|
|||
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-pr") {
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
workspaceDir := options.String("workspace")
|
||||
if workspaceDir == "" {
|
||||
return core.Result{Value: core.E("agentic.auto-pr", "workspace is required", nil), OK: false}
|
||||
}
|
||||
s.autoCreatePR(wsDir)
|
||||
s.autoCreatePR(workspaceDir)
|
||||
|
||||
// Emit PRCreated for observability
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok && workspaceStatus.PRURL != "" {
|
||||
s.Core().ACTION(messages.PRCreated{
|
||||
|
|
@ -255,15 +255,15 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options)
|
|||
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-merge") {
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
workspaceDir := options.String("workspace")
|
||||
if workspaceDir == "" {
|
||||
return core.Result{Value: core.E("agentic.verify", "workspace is required", nil), OK: false}
|
||||
}
|
||||
s.autoVerifyAndMerge(wsDir)
|
||||
s.autoVerifyAndMerge(workspaceDir)
|
||||
|
||||
// Emit merge/review events for observability
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
if workspaceStatus.Status == "merged" {
|
||||
|
|
@ -291,11 +291,11 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options)
|
|||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIngest(ctx context.Context, options core.Options) core.Result {
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
workspaceDir := options.String("workspace")
|
||||
if workspaceDir == "" {
|
||||
return core.Result{Value: core.E("agentic.ingest", "workspace is required", nil), OK: false}
|
||||
}
|
||||
s.ingestFindings(wsDir)
|
||||
s.ingestFindings(workspaceDir)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ import (
|
|||
|
||||
// autoCreatePR pushes the agent's branch and creates a PR on Forge
|
||||
// if the agent made any commits beyond the initial clone.
|
||||
func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
||||
result := ReadStatusResult(wsDir)
|
||||
func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Branch == "" || workspaceStatus.Repo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
process := s.Core().Process()
|
||||
|
||||
// PRs target dev — agents never merge directly to main
|
||||
|
|
@ -44,13 +44,13 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
// Push the branch to forge
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, workspaceStatus.Repo)
|
||||
if !process.RunIn(ctx, repoDir, "git", "push", forgeRemote, workspaceStatus.Branch).OK {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
workspaceStatusUpdate.Question = "PR push failed"
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
writeStatusResult(workspaceDir, workspaceStatusUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -64,25 +64,25 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
|
||||
prURL, _, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
|
||||
if err != nil {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
workspaceStatusUpdate.Question = core.Sprintf("PR creation failed: %v", err)
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
writeStatusResult(workspaceDir, workspaceStatusUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update status with PR URL
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
workspaceStatusUpdate.PRURL = prURL
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
writeStatusResult(workspaceDir, workspaceStatusUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,11 +152,11 @@ func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdStatus(_ core.Options) core.Result {
|
||||
wsRoot := WorkspaceRoot()
|
||||
workspaceRoot := WorkspaceRoot()
|
||||
fsys := s.Core().Fs()
|
||||
listResult := fsys.List(wsRoot)
|
||||
listResult := fsys.List(workspaceRoot)
|
||||
if !listResult.OK {
|
||||
core.Print(nil, "no workspaces found at %s", wsRoot)
|
||||
core.Print(nil, "no workspaces found at %s", workspaceRoot)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
|
|||
statusFiles := WorkspaceStatusPaths()
|
||||
count := 0
|
||||
for _, sf := range statusFiles {
|
||||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(workspaceDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
continue
|
||||
|
|
@ -39,7 +39,7 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
|
|||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
|
||||
wsRoot := WorkspaceRoot()
|
||||
workspaceRoot := WorkspaceRoot()
|
||||
fsys := s.Core().Fs()
|
||||
filter := options.String("_arg")
|
||||
if filter == "" {
|
||||
|
|
@ -50,9 +50,9 @@ func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
|
|||
var toRemove []string
|
||||
|
||||
for _, sf := range statusFiles {
|
||||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(workspaceDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
continue
|
||||
|
|
@ -85,7 +85,7 @@ func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
|
|||
}
|
||||
|
||||
for _, name := range toRemove {
|
||||
path := core.JoinPath(wsRoot, name)
|
||||
path := core.JoinPath(workspaceRoot, name)
|
||||
fsys.DeleteAll(path)
|
||||
core.Print(nil, " removed %s", name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import (
|
|||
// After this, the workspace go.work includes ./repo and all ./dep-* dirs,
|
||||
// giving the agent everything needed to build and test.
|
||||
//
|
||||
// s.cloneWorkspaceDeps(ctx, wsDir, repoDir, "core")
|
||||
func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, wsDir, repoDir, org string) {
|
||||
// s.cloneWorkspaceDeps(ctx, workspaceDir, repoDir, "core")
|
||||
func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, workspaceDir, repoDir, org string) {
|
||||
goModPath := core.JoinPath(repoDir, "go.mod")
|
||||
r := fs.Read(goModPath)
|
||||
if !r.OK {
|
||||
|
|
@ -48,14 +48,14 @@ func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, wsDir, repoDir,
|
|||
// Clone each dependency
|
||||
var cloned []string
|
||||
for _, dep := range deps {
|
||||
depDir := core.JoinPath(wsDir, dep.dir)
|
||||
depDir := core.JoinPath(workspaceDir, dep.dir)
|
||||
if fs.IsDir(core.JoinPath(depDir, ".git")) {
|
||||
cloned = append(cloned, dep.dir) // already cloned (resume)
|
||||
continue
|
||||
}
|
||||
|
||||
repoURL := forgeSSHURL(org, dep.repo)
|
||||
if result := process.RunIn(ctx, wsDir, "git", "clone", "--depth=1", repoURL, dep.dir); result.OK {
|
||||
if result := process.RunIn(ctx, workspaceDir, "git", "clone", "--depth=1", repoURL, dep.dir); result.OK {
|
||||
cloned = append(cloned, dep.dir)
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, wsDir, repoDir,
|
|||
b.WriteString(core.Concat("\t./", dir, "\n"))
|
||||
}
|
||||
b.WriteString(")\n")
|
||||
fs.Write(core.JoinPath(wsDir, "go.work"), b.String())
|
||||
fs.Write(core.JoinPath(workspaceDir, "go.work"), b.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,9 +233,9 @@ func containerCommand(agentType, command string, args []string, repoDir, metaDir
|
|||
// --- spawnAgent: decomposed into testable steps ---
|
||||
|
||||
// agentOutputFile returns the log file path for an agent's output.
|
||||
func agentOutputFile(wsDir, agent string) string {
|
||||
func agentOutputFile(workspaceDir, agent string) string {
|
||||
agentBase := core.SplitN(agent, ":", 2)[0]
|
||||
return core.JoinPath(WorkspaceMetaDir(wsDir), core.Sprintf("agent-%s.log", agentBase))
|
||||
return core.JoinPath(WorkspaceMetaDir(workspaceDir), core.Sprintf("agent-%s.log", agentBase))
|
||||
}
|
||||
|
||||
// detectFinalStatus reads workspace state after agent exit to determine outcome.
|
||||
|
|
@ -278,11 +278,11 @@ func (s *PrepSubsystem) trackFailureRate(agent, status string, startedAt time.Ti
|
|||
}
|
||||
|
||||
// startIssueTracking starts a Forge stopwatch on the workspace's issue.
|
||||
func (s *PrepSubsystem) startIssueTracking(wsDir string) {
|
||||
func (s *PrepSubsystem) startIssueTracking(workspaceDir string) {
|
||||
if s.forge == nil {
|
||||
return
|
||||
}
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Issue == 0 {
|
||||
return
|
||||
|
|
@ -295,11 +295,11 @@ func (s *PrepSubsystem) startIssueTracking(wsDir string) {
|
|||
}
|
||||
|
||||
// stopIssueTracking stops a Forge stopwatch on the workspace's issue.
|
||||
func (s *PrepSubsystem) stopIssueTracking(wsDir string) {
|
||||
func (s *PrepSubsystem) stopIssueTracking(workspaceDir string) {
|
||||
if s.forge == nil {
|
||||
return
|
||||
}
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Issue == 0 {
|
||||
return
|
||||
|
|
@ -312,9 +312,9 @@ func (s *PrepSubsystem) stopIssueTracking(wsDir string) {
|
|||
}
|
||||
|
||||
// broadcastStart emits IPC + audit events for agent start.
|
||||
func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
func (s *PrepSubsystem) broadcastStart(agent, workspaceDir string) {
|
||||
wsName := WorkspaceName(workspaceDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
|
|
@ -329,11 +329,11 @@ func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
|
|||
}
|
||||
|
||||
// broadcastComplete emits IPC + audit events for agent completion.
|
||||
func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
|
||||
wsName := WorkspaceName(wsDir)
|
||||
func (s *PrepSubsystem) broadcastComplete(agent, workspaceDir, finalStatus string) {
|
||||
wsName := WorkspaceName(workspaceDir)
|
||||
emitCompletionEvent(agent, wsName, finalStatus)
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
|
|
@ -348,34 +348,34 @@ func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
|
|||
|
||||
// onAgentComplete handles all post-completion logic for a spawned agent.
|
||||
// Called from the monitoring goroutine after the process exits.
|
||||
func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCode int, procStatus, output string) {
|
||||
func (s *PrepSubsystem) onAgentComplete(agent, workspaceDir, outputFile string, exitCode int, procStatus, output string) {
|
||||
// Save output
|
||||
if output != "" {
|
||||
fs.Write(outputFile, output)
|
||||
}
|
||||
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
finalStatus, question := detectFinalStatus(repoDir, exitCode, procStatus)
|
||||
|
||||
// Update workspace status (disk + registry)
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
workspaceStatus.Status = finalStatus
|
||||
workspaceStatus.PID = 0
|
||||
workspaceStatus.Question = question
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus)
|
||||
|
||||
// Rate-limit tracking
|
||||
s.trackFailureRate(agent, finalStatus, workspaceStatus.StartedAt)
|
||||
}
|
||||
|
||||
// Forge time tracking
|
||||
s.stopIssueTracking(wsDir)
|
||||
s.stopIssueTracking(workspaceDir)
|
||||
|
||||
// Broadcast completion
|
||||
s.broadcastComplete(agent, wsDir, finalStatus)
|
||||
s.broadcastComplete(agent, workspaceDir, finalStatus)
|
||||
|
||||
// Run completion pipeline via PerformAsync for successful agents.
|
||||
// Gets ActionTaskStarted/Completed broadcasts + WaitGroup integration for graceful shutdown.
|
||||
|
|
@ -383,7 +383,7 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
|
|||
// c.PerformAsync("agentic.complete", options) → runs agent.completion Task in background
|
||||
if finalStatus == "completed" && s.ServiceRuntime != nil {
|
||||
s.Core().PerformAsync("agentic.complete", core.NewOptions(
|
||||
core.Option{Key: "workspace", Value: wsDir},
|
||||
core.Option{Key: "workspace", Value: workspaceDir},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -391,18 +391,18 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
|
|||
// spawnAgent launches an agent inside a Docker container.
|
||||
// The repo/ directory is mounted at /workspace, agent runs sandboxed.
|
||||
// Output is captured and written to .meta/agent-{agent}.log on completion.
|
||||
func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, string, error) {
|
||||
func (s *PrepSubsystem) spawnAgent(agent, prompt, workspaceDir string) (int, string, string, error) {
|
||||
command, args, err := agentCommand(agent, prompt)
|
||||
if err != nil {
|
||||
return 0, "", "", err
|
||||
}
|
||||
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
metaDir := WorkspaceMetaDir(wsDir)
|
||||
outputFile := agentOutputFile(wsDir, agent)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
metaDir := WorkspaceMetaDir(workspaceDir)
|
||||
outputFile := agentOutputFile(workspaceDir, agent)
|
||||
|
||||
// Clean up stale BLOCKED.md from previous runs
|
||||
fs.Delete(WorkspaceBlockedPath(wsDir))
|
||||
fs.Delete(WorkspaceBlockedPath(workspaceDir))
|
||||
|
||||
// All agents run containerised
|
||||
agentBase := core.SplitN(agent, ":", 2)[0]
|
||||
|
|
@ -426,16 +426,16 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, st
|
|||
pid := proc.Info().PID
|
||||
processID := proc.ID
|
||||
|
||||
s.broadcastStart(agent, wsDir)
|
||||
s.startIssueTracking(wsDir)
|
||||
s.broadcastStart(agent, workspaceDir)
|
||||
s.startIssueTracking(workspaceDir)
|
||||
|
||||
// Register a one-shot Action that monitors this agent, then run it via PerformAsync.
|
||||
// PerformAsync tracks it in Core's WaitGroup — ServiceShutdown waits for it.
|
||||
monitorAction := core.Concat("agentic.monitor.", core.Replace(WorkspaceName(wsDir), "/", "."))
|
||||
monitorAction := core.Concat("agentic.monitor.", core.Replace(WorkspaceName(workspaceDir), "/", "."))
|
||||
monitor := &agentCompletionMonitor{
|
||||
service: s,
|
||||
agent: agent,
|
||||
workspaceDir: wsDir,
|
||||
workspaceDir: workspaceDir,
|
||||
outputFile: outputFile,
|
||||
process: proc,
|
||||
}
|
||||
|
|
@ -453,7 +453,7 @@ type completionProcess interface {
|
|||
|
||||
// agentCompletionMonitor waits for a spawned process to finish, then finalises the workspace.
|
||||
//
|
||||
// monitor := &agentCompletionMonitor{service: s, agent: "codex", workspaceDir: wsDir, outputFile: outputFile, process: proc}
|
||||
// monitor := &agentCompletionMonitor{service: s, agent: "codex", workspaceDir: workspaceDir, outputFile: outputFile, process: proc}
|
||||
// s.Core().Action("agentic.monitor.core.go-io.task-5", monitor.run)
|
||||
type agentCompletionMonitor struct {
|
||||
service *PrepSubsystem
|
||||
|
|
@ -479,9 +479,9 @@ func (m *agentCompletionMonitor) run(_ context.Context, _ core.Options) core.Res
|
|||
|
||||
// runQA runs build + test checks on the repo after agent completion.
|
||||
// Returns true if QA passes, false if build or tests fail.
|
||||
func (s *PrepSubsystem) runQA(wsDir string) bool {
|
||||
func (s *PrepSubsystem) runQA(workspaceDir string) bool {
|
||||
ctx := context.Background()
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
process := s.Core().Process()
|
||||
|
||||
if fs.IsFile(core.JoinPath(repoDir, "go.mod")) {
|
||||
|
|
@ -552,7 +552,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
return nil, DispatchOutput{}, core.E("dispatch", "prep workspace failed", err)
|
||||
}
|
||||
|
||||
wsDir := prepOut.WorkspaceDir
|
||||
workspaceDir := prepOut.WorkspaceDir
|
||||
prompt := prepOut.Prompt
|
||||
|
||||
if input.DryRun {
|
||||
|
|
@ -560,7 +560,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
Success: true,
|
||||
Agent: input.Agent,
|
||||
Repo: input.Repo,
|
||||
WorkspaceDir: wsDir,
|
||||
WorkspaceDir: workspaceDir,
|
||||
Prompt: prompt,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -584,22 +584,22 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
StartedAt: time.Now(),
|
||||
Runs: 0,
|
||||
}
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus)
|
||||
}
|
||||
return nil, DispatchOutput{
|
||||
Success: true,
|
||||
Agent: input.Agent,
|
||||
Repo: input.Repo,
|
||||
WorkspaceDir: wsDir,
|
||||
WorkspaceDir: workspaceDir,
|
||||
OutputFile: "queued — at concurrency limit or frozen",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Spawn agent in repo/ directory
|
||||
pid, processID, outputFile, err := s.spawnAgent(input.Agent, prompt, wsDir)
|
||||
pid, processID, outputFile, err := s.spawnAgent(input.Agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
return nil, DispatchOutput{}, err
|
||||
}
|
||||
|
|
@ -616,11 +616,11 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
StartedAt: time.Now(),
|
||||
Runs: 1,
|
||||
}
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
// Track in runner's registry (runner owns workspace state)
|
||||
if s.ServiceRuntime != nil {
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -628,7 +628,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
Success: true,
|
||||
Agent: input.Agent,
|
||||
Repo: input.Repo,
|
||||
WorkspaceDir: wsDir,
|
||||
WorkspaceDir: workspaceDir,
|
||||
PID: pid,
|
||||
OutputFile: outputFile,
|
||||
}, nil
|
||||
|
|
|
|||
|
|
@ -56,14 +56,14 @@ func (s *PrepSubsystem) DispatchSync(ctx context.Context, input DispatchSyncInpu
|
|||
return DispatchSyncResult{Err: core.E("agentic.DispatchSync", "prep failed", nil)}
|
||||
}
|
||||
|
||||
wsDir := prepOut.WorkspaceDir
|
||||
workspaceDir := prepOut.WorkspaceDir
|
||||
prompt := prepOut.Prompt
|
||||
|
||||
core.Print(nil, " workspace: %s", wsDir)
|
||||
core.Print(nil, " workspace: %s", workspaceDir)
|
||||
core.Print(nil, " branch: %s", prepOut.Branch)
|
||||
|
||||
// Spawn agent directly — no queue, no concurrency check
|
||||
pid, processID, _, err := s.spawnAgent(input.Agent, prompt, wsDir)
|
||||
pid, processID, _, err := s.spawnAgent(input.Agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
return DispatchSyncResult{Err: core.E("agentic.DispatchSync", "spawn agent failed", err)}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ func (s *PrepSubsystem) DispatchSync(ctx context.Context, input DispatchSyncInpu
|
|||
case <-ticker.C:
|
||||
if pid > 0 && !ProcessAlive(runtime, processID, pid) {
|
||||
// Process exited — read final status
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
err, _ := result.Value.(error)
|
||||
|
|
|
|||
|
|
@ -21,33 +21,33 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
|
|||
case messages.AgentCompleted:
|
||||
// Ingest findings (feature-flag gated)
|
||||
if c.Config().Enabled("auto-ingest") {
|
||||
if wsDir := resolveWorkspace(ev.Workspace); wsDir != "" {
|
||||
s.ingestFindings(wsDir)
|
||||
if workspaceDir := resolveWorkspace(ev.Workspace); workspaceDir != "" {
|
||||
s.ingestFindings(workspaceDir)
|
||||
}
|
||||
}
|
||||
|
||||
case messages.SpawnQueued:
|
||||
// Runner asks agentic to spawn a queued workspace
|
||||
wsDir := resolveWorkspace(ev.Workspace)
|
||||
if wsDir == "" {
|
||||
workspaceDir := resolveWorkspace(ev.Workspace)
|
||||
if workspaceDir == "" {
|
||||
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 := s.spawnAgent(ev.Agent, prompt, wsDir)
|
||||
pid, processID, outputFile, err := s.spawnAgent(ev.Agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// Update status with real PID
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.ProcessID = processID
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](c, "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus)
|
||||
}
|
||||
}
|
||||
_ = outputFile
|
||||
|
|
@ -59,10 +59,10 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
|
|||
// SpawnFromQueue spawns an agent in a pre-prepped workspace.
|
||||
// Called by runner.Service via ServiceFor interface matching.
|
||||
//
|
||||
// spawnResult := prep.SpawnFromQueue("codex", prompt, wsDir)
|
||||
// spawnResult := prep.SpawnFromQueue("codex", prompt, workspaceDir)
|
||||
// pid := spawnResult.Value.(int)
|
||||
func (s *PrepSubsystem) SpawnFromQueue(agent, prompt, wsDir string) core.Result {
|
||||
pid, _, _, err := s.spawnAgent(agent, prompt, wsDir)
|
||||
func (s *PrepSubsystem) SpawnFromQueue(agent, prompt, workspaceDir string) core.Result {
|
||||
pid, _, _, err := s.spawnAgent(agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
return core.Result{
|
||||
Value: core.E("agentic.SpawnFromQueue", "failed to spawn queued agent", err),
|
||||
|
|
@ -75,8 +75,8 @@ func (s *PrepSubsystem) SpawnFromQueue(agent, prompt, wsDir string) core.Result
|
|||
//
|
||||
// resolveWorkspace("core/go-io/task-5") → "/Users/snider/Code/.core/workspace/core/go-io/task-5"
|
||||
func resolveWorkspace(name string) string {
|
||||
wsRoot := WorkspaceRoot()
|
||||
path := core.JoinPath(wsRoot, name)
|
||||
workspaceRoot := WorkspaceRoot()
|
||||
path := core.JoinPath(workspaceRoot, name)
|
||||
if fs.IsDir(path) {
|
||||
return path
|
||||
}
|
||||
|
|
@ -87,14 +87,14 @@ func resolveWorkspace(name string) string {
|
|||
// Scans running/completed workspaces for a matching repo+branch combination.
|
||||
func findWorkspaceByPR(repo, branch string) string {
|
||||
for _, path := range WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(path)
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(path)
|
||||
statusResult := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(statusResult)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if workspaceStatus.Repo == repo && workspaceStatus.Branch == branch {
|
||||
return wsDir
|
||||
return workspaceDir
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func (s *PrepSubsystem) ingestFindings(wsDir string) {
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
func (s *PrepSubsystem) ingestFindings(workspaceDir string) {
|
||||
statusResult := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(statusResult)
|
||||
if !ok || workspaceStatus.Status != "completed" {
|
||||
return
|
||||
}
|
||||
|
||||
logFiles := workspaceLogFiles(wsDir)
|
||||
logFiles := workspaceLogFiles(workspaceDir)
|
||||
if len(logFiles) == 0 {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func LocalFs() *core.Fs { return fs }
|
|||
// WorkspaceRoot returns the root directory for agent workspaces.
|
||||
// Checks CORE_WORKSPACE env var first, falls back to HomeDir()/Code/.core/workspace.
|
||||
//
|
||||
// wsDir := core.JoinPath(agentic.WorkspaceRoot(), "core", "go-io", "task-42")
|
||||
// workspaceDir := core.JoinPath(agentic.WorkspaceRoot(), "core", "go-io", "task-42")
|
||||
func WorkspaceRoot() string {
|
||||
return core.JoinPath(CoreRoot(), "workspace")
|
||||
}
|
||||
|
|
@ -41,20 +41,20 @@ func WorkspaceStatusPaths() []string {
|
|||
// WorkspaceStatusPath returns the status file for a workspace directory.
|
||||
//
|
||||
// path := agentic.WorkspaceStatusPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceStatusPath(wsDir string) string {
|
||||
return core.JoinPath(wsDir, "status.json")
|
||||
func WorkspaceStatusPath(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, "status.json")
|
||||
}
|
||||
|
||||
// WorkspaceName extracts the unique workspace name from a full path.
|
||||
// Given /Users/snider/Code/.core/workspace/core/go-io/dev → core/go-io/dev
|
||||
//
|
||||
// name := agentic.WorkspaceName("/Users/snider/Code/.core/workspace/core/go-io/dev")
|
||||
func WorkspaceName(wsDir string) string {
|
||||
func WorkspaceName(workspaceDir string) string {
|
||||
root := WorkspaceRoot()
|
||||
name := core.TrimPrefix(wsDir, root)
|
||||
name := core.TrimPrefix(workspaceDir, root)
|
||||
name = core.TrimPrefix(name, "/")
|
||||
if name == "" {
|
||||
return core.PathBase(wsDir)
|
||||
return core.PathBase(workspaceDir)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
@ -83,8 +83,8 @@ func HomeDir() string {
|
|||
return core.Env("DIR_HOME")
|
||||
}
|
||||
|
||||
func workspaceStatusPaths(wsRoot string) []string {
|
||||
if wsRoot == "" {
|
||||
func workspaceStatusPaths(workspaceRoot string) []string {
|
||||
if workspaceRoot == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ func workspaceStatusPaths(wsRoot string) []string {
|
|||
}
|
||||
}
|
||||
|
||||
walk(wsRoot, 0)
|
||||
walk(workspaceRoot, 0)
|
||||
sort.Strings(paths)
|
||||
return paths
|
||||
}
|
||||
|
|
@ -130,56 +130,56 @@ func workspaceStatusPaths(wsRoot string) []string {
|
|||
// WorkspaceRepoDir returns the checked-out repo directory for a workspace.
|
||||
//
|
||||
// repoDir := agentic.WorkspaceRepoDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceRepoDir(wsDir string) string {
|
||||
return core.JoinPath(wsDir, "repo")
|
||||
func WorkspaceRepoDir(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, "repo")
|
||||
}
|
||||
|
||||
func workspaceRepoDir(wsDir string) string {
|
||||
return WorkspaceRepoDir(wsDir)
|
||||
func workspaceRepoDir(workspaceDir string) string {
|
||||
return WorkspaceRepoDir(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceMetaDir returns the metadata directory for a workspace.
|
||||
//
|
||||
// metaDir := agentic.WorkspaceMetaDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceMetaDir(wsDir string) string {
|
||||
return core.JoinPath(wsDir, ".meta")
|
||||
func WorkspaceMetaDir(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, ".meta")
|
||||
}
|
||||
|
||||
func workspaceMetaDir(wsDir string) string {
|
||||
return WorkspaceMetaDir(wsDir)
|
||||
func workspaceMetaDir(workspaceDir string) string {
|
||||
return WorkspaceMetaDir(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceBlockedPath returns the BLOCKED.md path for a workspace.
|
||||
//
|
||||
// blocked := agentic.WorkspaceBlockedPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceBlockedPath(wsDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(wsDir), "BLOCKED.md")
|
||||
func WorkspaceBlockedPath(workspaceDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(workspaceDir), "BLOCKED.md")
|
||||
}
|
||||
|
||||
func workspaceBlockedPath(wsDir string) string {
|
||||
return WorkspaceBlockedPath(wsDir)
|
||||
func workspaceBlockedPath(workspaceDir string) string {
|
||||
return WorkspaceBlockedPath(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceAnswerPath returns the ANSWER.md path for a workspace.
|
||||
//
|
||||
// answer := agentic.WorkspaceAnswerPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceAnswerPath(wsDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(wsDir), "ANSWER.md")
|
||||
func WorkspaceAnswerPath(workspaceDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(workspaceDir), "ANSWER.md")
|
||||
}
|
||||
|
||||
func workspaceAnswerPath(wsDir string) string {
|
||||
return WorkspaceAnswerPath(wsDir)
|
||||
func workspaceAnswerPath(workspaceDir string) string {
|
||||
return WorkspaceAnswerPath(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceLogFiles returns captured agent log files for a workspace.
|
||||
//
|
||||
// logs := agentic.WorkspaceLogFiles("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceLogFiles(wsDir string) []string {
|
||||
return core.PathGlob(core.JoinPath(WorkspaceMetaDir(wsDir), "agent-*.log"))
|
||||
func WorkspaceLogFiles(workspaceDir string) []string {
|
||||
return core.PathGlob(core.JoinPath(WorkspaceMetaDir(workspaceDir), "agent-*.log"))
|
||||
}
|
||||
|
||||
func workspaceLogFiles(wsDir string) []string {
|
||||
return WorkspaceLogFiles(wsDir)
|
||||
func workspaceLogFiles(workspaceDir string) []string {
|
||||
return WorkspaceLogFiles(workspaceDir)
|
||||
}
|
||||
|
||||
// PlansRoot returns the root directory for agent plans.
|
||||
|
|
|
|||
|
|
@ -51,15 +51,15 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
return nil, CreatePROutput{}, core.E("createPR", "no Forge token configured", nil)
|
||||
}
|
||||
|
||||
wsDir := core.JoinPath(WorkspaceRoot(), input.Workspace)
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
workspaceDir := core.JoinPath(WorkspaceRoot(), input.Workspace)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
|
||||
if !fs.IsDir(core.JoinPath(repoDir, ".git")) {
|
||||
return nil, CreatePROutput{}, core.E("createPR", core.Concat("workspace not found: ", input.Workspace), nil)
|
||||
}
|
||||
|
||||
// Read workspace status for repo, branch, issue context
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
err, _ := result.Value.(error)
|
||||
|
|
@ -126,7 +126,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
|
||||
// Update status with PR URL
|
||||
workspaceStatus.PRURL = prURL
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
|
||||
// Comment on issue if tracked
|
||||
if workspaceStatus.Issue > 0 {
|
||||
|
|
|
|||
|
|
@ -234,13 +234,13 @@ func (s *PrepSubsystem) hydrateWorkspaces() {
|
|||
s.workspaces = core.NewRegistry[*WorkspaceStatus]()
|
||||
}
|
||||
for _, path := range WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(path)
|
||||
result := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(path)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s.workspaces.Set(WorkspaceName(wsDir), st)
|
||||
s.workspaces.Set(WorkspaceName(workspaceDir), st)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,17 +362,17 @@ func workspaceDir(org, repo string, input PrepInput) (string, error) {
|
|||
}
|
||||
return "", err
|
||||
}
|
||||
wsDir, ok := r.Value.(string)
|
||||
if !ok || wsDir == "" {
|
||||
workspaceDir, ok := r.Value.(string)
|
||||
if !ok || workspaceDir == "" {
|
||||
return "", core.E("workspaceDir", "invalid workspace directory result", nil)
|
||||
}
|
||||
return wsDir, nil
|
||||
return workspaceDir, nil
|
||||
}
|
||||
|
||||
// workspaceDirResult resolves the workspace path and returns core.Result.
|
||||
//
|
||||
// r := workspaceDirResult("core", "go-io", PrepInput{Issue: 15})
|
||||
// if r.OK { wsDir := r.Value.(string) }
|
||||
// if r.OK { workspaceDir := r.Value.(string) }
|
||||
func workspaceDirResult(org, repo string, input PrepInput) core.Result {
|
||||
orgName := core.ValidateName(org)
|
||||
if !orgName.OK {
|
||||
|
|
@ -421,14 +421,14 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
}
|
||||
return nil, PrepOutput{}, err
|
||||
}
|
||||
wsDir, ok := wsDirResult.Value.(string)
|
||||
if !ok || wsDir == "" {
|
||||
workspaceDir, ok := wsDirResult.Value.(string)
|
||||
if !ok || workspaceDir == "" {
|
||||
return nil, PrepOutput{}, core.E("prepWorkspace", "invalid workspace path", nil)
|
||||
}
|
||||
|
||||
repoDir := workspaceRepoDir(wsDir)
|
||||
metaDir := workspaceMetaDir(wsDir)
|
||||
out := PrepOutput{WorkspaceDir: wsDir, RepoDir: repoDir}
|
||||
repoDir := workspaceRepoDir(workspaceDir)
|
||||
metaDir := workspaceMetaDir(workspaceDir)
|
||||
out := PrepOutput{WorkspaceDir: workspaceDir, RepoDir: repoDir}
|
||||
|
||||
// Source repo path — org and repo were validated by workspaceDirResult.
|
||||
repoPath := core.JoinPath(s.codePath, input.Org, input.Repo)
|
||||
|
|
@ -467,7 +467,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
}
|
||||
|
||||
// Extract default workspace template (go.work etc.)
|
||||
if result := lib.ExtractWorkspace("default", wsDir, &lib.WorkspaceData{
|
||||
if result := lib.ExtractWorkspace("default", workspaceDir, &lib.WorkspaceData{
|
||||
Repo: input.Repo,
|
||||
Branch: "",
|
||||
Task: input.Task,
|
||||
|
|
@ -515,7 +515,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
lang := detectLanguage(repoPath)
|
||||
if lang == "php" {
|
||||
if r := lib.WorkspaceFile("default", "CODEX-PHP.md.tmpl"); r.OK {
|
||||
codexPath := core.JoinPath(wsDir, "CODEX.md")
|
||||
codexPath := core.JoinPath(workspaceDir, "CODEX.md")
|
||||
fs.Write(codexPath, r.Value.(string))
|
||||
}
|
||||
}
|
||||
|
|
@ -523,11 +523,11 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
// Clone workspace dependencies — Core modules needed to build the repo.
|
||||
// Reads go.mod, finds dappco.re/go/core/* imports, clones from Forge,
|
||||
// and updates go.work so the agent can build inside the workspace.
|
||||
s.cloneWorkspaceDeps(ctx, wsDir, repoDir, input.Org)
|
||||
s.cloneWorkspaceDeps(ctx, workspaceDir, repoDir, input.Org)
|
||||
|
||||
// Clone ecosystem docs into .core/reference/ so agents have full documentation.
|
||||
// The docs site (core.help) has architecture guides, specs, and API references.
|
||||
docsDir := core.JoinPath(wsDir, ".core", "reference", "docs")
|
||||
docsDir := core.JoinPath(workspaceDir, ".core", "reference", "docs")
|
||||
if !fs.IsDir(docsDir) {
|
||||
docsRepo := core.JoinPath(s.codePath, input.Org, "docs")
|
||||
if fs.IsDir(core.JoinPath(docsRepo, ".git")) {
|
||||
|
|
@ -537,7 +537,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
|
||||
// Copy RFC specs from plans repo into workspace specs/ folder.
|
||||
// Maps repo name to plans directory: go-io → core/go/io/, go-process → core/go/process/, etc.
|
||||
s.copyRepoSpecs(wsDir, input.Repo)
|
||||
s.copyRepoSpecs(workspaceDir, input.Repo)
|
||||
|
||||
// Build the rich prompt with all context
|
||||
out.Prompt, out.Memories, out.Consumers = s.buildPrompt(ctx, input, out.Branch, repoPath)
|
||||
|
|
@ -554,7 +554,7 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
//
|
||||
// s.copyRepoSpecs("/tmp/ws", "go-io") // copies plans/core/go/io/**/RFC*.md → /tmp/ws/specs/
|
||||
// s.copyRepoSpecs("/tmp/ws", "core-bio") // copies plans/core/php/bio/**/RFC*.md → /tmp/ws/specs/
|
||||
func (s *PrepSubsystem) copyRepoSpecs(wsDir, repo string) {
|
||||
func (s *PrepSubsystem) copyRepoSpecs(workspaceDir, repo string) {
|
||||
fs := (&core.Fs{}).NewUnrestricted()
|
||||
|
||||
// Plans repo base — look for it relative to codePath
|
||||
|
|
@ -587,7 +587,7 @@ func (s *PrepSubsystem) copyRepoSpecs(wsDir, repo string) {
|
|||
|
||||
// Glob RFC*.md at each depth level (root, 1 deep, 2 deep, 3 deep).
|
||||
// Preserves subdirectory structure: specDir/pkg/sub/RFC.md → specs/pkg/sub/RFC.md
|
||||
specsDir := core.JoinPath(wsDir, "specs")
|
||||
specsDir := core.JoinPath(workspaceDir, "specs")
|
||||
fs.EnsureDir(specsDir)
|
||||
|
||||
patterns := []string{
|
||||
|
|
|
|||
|
|
@ -327,8 +327,8 @@ func (s *PrepSubsystem) drainQueue() {
|
|||
// Returns true if a task was spawned, false if nothing to do.
|
||||
func (s *PrepSubsystem) drainOne() bool {
|
||||
for _, statusPath := range WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(statusPath)
|
||||
result := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(statusPath)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Status != "queued" {
|
||||
continue
|
||||
|
|
@ -357,7 +357,7 @@ func (s *PrepSubsystem) drainOne() bool {
|
|||
|
||||
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
|
||||
pid, processID, _, err := s.spawnAgent(workspaceStatus.Agent, prompt, wsDir)
|
||||
pid, processID, _, err := s.spawnAgent(workspaceStatus.Agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -366,8 +366,8 @@ func (s *PrepSubsystem) drainOne() bool {
|
|||
workspaceStatus.PID = pid
|
||||
workspaceStatus.ProcessID = processID
|
||||
workspaceStatus.Runs++
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(workspaceDir), workspaceStatus)
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
return nil, ResumeOutput{}, core.E("resume", "workspace is required", nil)
|
||||
}
|
||||
|
||||
wsDir := core.JoinPath(WorkspaceRoot(), input.Workspace)
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
workspaceDir := core.JoinPath(WorkspaceRoot(), input.Workspace)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
|
||||
// Verify workspace exists
|
||||
if !fs.IsDir(core.JoinPath(repoDir, ".git")) {
|
||||
|
|
@ -52,7 +52,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
}
|
||||
|
||||
// Read current status
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
err, _ := result.Value.(error)
|
||||
|
|
@ -71,7 +71,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
|
||||
// Write ANSWER.md if answer provided
|
||||
if input.Answer != "" {
|
||||
answerPath := workspaceAnswerPath(wsDir)
|
||||
answerPath := workspaceAnswerPath(workspaceDir)
|
||||
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
|
||||
if writeResult := fs.Write(answerPath, content); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
|
|
@ -96,7 +96,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
}
|
||||
|
||||
// Spawn agent via go-process
|
||||
pid, processID, _, err := s.spawnAgent(agent, prompt, wsDir)
|
||||
pid, processID, _, err := s.spawnAgent(agent, prompt, workspaceDir)
|
||||
if err != nil {
|
||||
return nil, ResumeOutput{}, err
|
||||
}
|
||||
|
|
@ -107,13 +107,13 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
workspaceStatus.ProcessID = processID
|
||||
workspaceStatus.Runs++
|
||||
workspaceStatus.Question = ""
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
|
||||
return nil, ResumeOutput{
|
||||
Success: true,
|
||||
Workspace: input.Workspace,
|
||||
Agent: agent,
|
||||
PID: pid,
|
||||
OutputFile: agentOutputFile(wsDir, agent),
|
||||
OutputFile: agentOutputFile(workspaceDir, agent),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import (
|
|||
|
||||
// WorkspaceStatus represents the current state of an agent workspace.
|
||||
//
|
||||
// result := ReadStatusResult(wsDir)
|
||||
// if result.OK && result.Value.(*WorkspaceStatus).Status == "completed" { autoCreatePR(wsDir) }
|
||||
// result := ReadStatusResult(workspaceDir)
|
||||
// if result.OK && result.Value.(*WorkspaceStatus).Status == "completed" { autoCreatePR(workspaceDir) }
|
||||
type WorkspaceStatus struct {
|
||||
Status string `json:"status"` // running, completed, blocked, failed
|
||||
Agent string `json:"agent"` // gemini, claude, codex
|
||||
|
|
@ -56,8 +56,8 @@ type WorkspaceQuery struct {
|
|||
Status string // filter by status (empty = all)
|
||||
}
|
||||
|
||||
func writeStatus(wsDir string, status *WorkspaceStatus) error {
|
||||
r := writeStatusResult(wsDir, status)
|
||||
func writeStatus(workspaceDir string, status *WorkspaceStatus) error {
|
||||
r := writeStatusResult(workspaceDir, status)
|
||||
if !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if err == nil {
|
||||
|
|
@ -72,12 +72,12 @@ func writeStatus(wsDir string, status *WorkspaceStatus) error {
|
|||
//
|
||||
// result := writeStatusResult("/srv/core/workspace/core/go-io/task-5", &WorkspaceStatus{Status: "running"})
|
||||
// if result.OK { return }
|
||||
func writeStatusResult(wsDir string, status *WorkspaceStatus) core.Result {
|
||||
func writeStatusResult(workspaceDir string, status *WorkspaceStatus) core.Result {
|
||||
if status == nil {
|
||||
return core.Result{Value: core.E("writeStatus", "status is required", nil), OK: false}
|
||||
}
|
||||
status.UpdatedAt = time.Now()
|
||||
statusPath := WorkspaceStatusPath(wsDir)
|
||||
statusPath := WorkspaceStatusPath(workspaceDir)
|
||||
if r := fs.WriteAtomic(statusPath, core.JSONMarshalString(status)); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if err == nil {
|
||||
|
|
@ -94,14 +94,14 @@ func writeStatusResult(wsDir string, status *WorkspaceStatus) core.Result {
|
|||
//
|
||||
// result := ReadStatusResult("/path/to/workspace")
|
||||
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
|
||||
func ReadStatusResult(wsDir string) core.Result {
|
||||
r := fs.Read(WorkspaceStatusPath(wsDir))
|
||||
func ReadStatusResult(workspaceDir string) core.Result {
|
||||
r := fs.Read(WorkspaceStatusPath(workspaceDir))
|
||||
if !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if err == nil {
|
||||
return core.Result{Value: core.E("ReadStatusResult", "status not found", nil), OK: false}
|
||||
}
|
||||
return core.Result{Value: core.E("ReadStatusResult", core.Concat("status not found for ", wsDir), err), OK: false}
|
||||
return core.Result{Value: core.E("ReadStatusResult", core.Concat("status not found for ", workspaceDir), err), OK: false}
|
||||
}
|
||||
var s WorkspaceStatus
|
||||
if parseResult := core.JSONUnmarshalString(r.Value.(string), &s); !parseResult.OK {
|
||||
|
|
@ -177,10 +177,10 @@ func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
var out StatusOutput
|
||||
|
||||
for _, statusPath := range statusFiles {
|
||||
wsDir := core.PathDir(statusPath)
|
||||
name := WorkspaceName(wsDir)
|
||||
workspaceDir := core.PathDir(statusPath)
|
||||
name := WorkspaceName(workspaceDir)
|
||||
|
||||
result := ReadStatusResult(wsDir)
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
out.Total++
|
||||
|
|
@ -191,19 +191,19 @@ func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
// If status is "running", check whether the managed process is still alive.
|
||||
if workspaceStatus.Status == "running" && (workspaceStatus.ProcessID != "" || workspaceStatus.PID > 0) {
|
||||
if !ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
|
||||
blockedPath := workspaceBlockedPath(wsDir)
|
||||
blockedPath := workspaceBlockedPath(workspaceDir)
|
||||
if r := fs.Read(blockedPath); r.OK {
|
||||
workspaceStatus.Status = "blocked"
|
||||
workspaceStatus.Question = core.Trim(r.Value.(string))
|
||||
} else {
|
||||
if len(workspaceLogFiles(wsDir)) == 0 {
|
||||
if len(workspaceLogFiles(workspaceDir)) == 0 {
|
||||
workspaceStatus.Status = "failed"
|
||||
workspaceStatus.Question = "Agent process died (no output log)"
|
||||
} else {
|
||||
workspaceStatus.Status = "completed"
|
||||
}
|
||||
}
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
writeStatusResult(workspaceDir, workspaceStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ import (
|
|||
// For deeper review (security, conventions), dispatch a separate task:
|
||||
//
|
||||
// agentic_dispatch repo=go-crypt template=verify persona=engineering/engineering-security-engineer
|
||||
func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
||||
result := ReadStatusResult(wsDir)
|
||||
func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.PRURL == "" || workspaceStatus.Repo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
|
|
@ -36,13 +36,13 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
|||
|
||||
// markMerged is a helper to avoid repeating the status update.
|
||||
markMerged := func() {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
st2, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st2.Status = "merged"
|
||||
writeStatusResult(wsDir, st2)
|
||||
writeStatusResult(workspaceDir, st2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,13 +66,13 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
|||
// Both attempts failed — flag for human review
|
||||
s.flagForReview(org, workspaceStatus.Repo, prNum, mergeOutcome)
|
||||
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
if result := ReadStatusResult(workspaceDir); result.OK {
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
workspaceStatusUpdate.Question = "Flagged for review — auto-merge failed after retry"
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
writeStatusResult(workspaceDir, workspaceStatusUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,30 +269,30 @@ type WorkspaceData struct {
|
|||
// Repo: "go-io", Task: "fix tests", Agent: "codex",
|
||||
// })
|
||||
// core.Println(r.OK)
|
||||
func ExtractWorkspace(tmplName, targetDir string, data *WorkspaceData) core.Result {
|
||||
func ExtractWorkspace(templateName, targetDir string, data *WorkspaceData) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("mount workspace template ", tmplName), err),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("mount workspace template ", templateName), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("mount workspace template ", tmplName), nil),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("mount workspace template ", templateName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
||||
r := workspaceFS.Sub(tmplName)
|
||||
r := workspaceFS.Sub(templateName)
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("template not found: ", tmplName), err),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("template not found: ", templateName), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("template not found: ", tmplName), nil),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("template not found: ", templateName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -300,12 +300,12 @@ func ExtractWorkspace(tmplName, targetDir string, data *WorkspaceData) core.Resu
|
|||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("extract workspace template ", tmplName), err),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("extract workspace template ", templateName), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Result{
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("extract workspace template ", tmplName), nil),
|
||||
Value: core.E("lib.ExtractWorkspace", core.Concat("extract workspace template ", templateName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -317,11 +317,11 @@ func ExtractWorkspace(tmplName, targetDir string, data *WorkspaceData) core.Resu
|
|||
//
|
||||
// r := lib.WorkspaceFile("default", "CODEX-PHP.md.tmpl")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func WorkspaceFile(tmplName, filename string) core.Result {
|
||||
func WorkspaceFile(templateName, filename string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
}
|
||||
r := workspaceFS.Sub(tmplName)
|
||||
r := workspaceFS.Sub(templateName)
|
||||
if !r.OK {
|
||||
return r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
var harvested []harvestResult
|
||||
|
||||
for _, entry := range agentic.WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(entry)
|
||||
result := m.harvestWorkspace(wsDir)
|
||||
workspaceDir := core.PathDir(entry)
|
||||
result := m.harvestWorkspace(workspaceDir)
|
||||
if result != nil {
|
||||
harvested = append(harvested, *result)
|
||||
}
|
||||
|
|
@ -61,8 +61,8 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
}
|
||||
|
||||
// harvestWorkspace checks a single workspace and pushes if ready.
|
||||
func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
func (m *Subsystem) harvestWorkspace(workspaceDir string) *harvestResult {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(workspaceDir))
|
||||
if !statusResult.OK {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
return nil
|
||||
}
|
||||
|
||||
repoDir := agentic.WorkspaceRepoDir(wsDir)
|
||||
repoDir := agentic.WorkspaceRepoDir(workspaceDir)
|
||||
if !fs.IsDir(repoDir) {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
|
||||
// Safety checks before pushing
|
||||
if reason := m.checkSafety(repoDir); reason != "" {
|
||||
updateStatus(wsDir, "rejected", reason)
|
||||
updateStatus(workspaceDir, "rejected", reason)
|
||||
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, rejected: reason}
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
// Mark ready for review — do NOT auto-push.
|
||||
// Pushing is a high-impact mutation that should happen during
|
||||
// explicit review (/review command), not silently in the background.
|
||||
updateStatus(wsDir, "ready-for-review", "")
|
||||
updateStatus(workspaceDir, "ready-for-review", "")
|
||||
|
||||
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, files: files}
|
||||
}
|
||||
|
|
@ -247,9 +247,9 @@ func (m *Subsystem) pushBranch(srcDir, branch string) error {
|
|||
|
||||
// updateStatus rewrites status.json after a harvest decision.
|
||||
//
|
||||
// updateStatus(wsDir, "ready-for-review", "")
|
||||
func updateStatus(wsDir, status, question string) {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
// updateStatus(workspaceDir, "ready-for-review", "")
|
||||
func updateStatus(workspaceDir, status, question string) {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(workspaceDir))
|
||||
if !statusResult.OK {
|
||||
return
|
||||
}
|
||||
|
|
@ -267,7 +267,7 @@ func updateStatus(wsDir, status, question string) {
|
|||
} else {
|
||||
delete(workspaceStatus, "question") // clear stale question from previous state
|
||||
}
|
||||
statusPath := agentic.WorkspaceStatusPath(wsDir)
|
||||
statusPath := agentic.WorkspaceStatusPath(workspaceDir)
|
||||
if writeResult := fs.WriteAtomic(statusPath, core.JSONMarshalString(workspaceStatus)); !writeResult.OK {
|
||||
if err, ok := writeResult.Value.(error); ok {
|
||||
core.Warn("monitor.updateStatus: failed to write status", "path", statusPath, "reason", err)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// readResult := fs.Read(core.JoinPath(wsRoot, name, "status.json"))
|
||||
// readResult := fs.Read(core.JoinPath(workspaceRoot, name, "status.json"))
|
||||
// if text, ok := resultString(readResult); ok { _ = core.JSONUnmarshalString(text, &workspaceStatus) }
|
||||
var fs = agentic.LocalFs()
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ func CoreRoot() string {
|
|||
//
|
||||
// result := ReadStatusResult("/srv/core/workspace/core/go-io/task-5")
|
||||
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
|
||||
func ReadStatusResult(wsDir string) core.Result {
|
||||
statusResult := agentic.ReadStatusResult(wsDir)
|
||||
func ReadStatusResult(workspaceDir string) core.Result {
|
||||
statusResult := agentic.ReadStatusResult(workspaceDir)
|
||||
if !statusResult.OK {
|
||||
err, _ := statusResult.Value.(error)
|
||||
if err == nil {
|
||||
|
|
@ -93,7 +93,7 @@ func ReadStatusResult(wsDir string) core.Result {
|
|||
//
|
||||
// result := runner.WriteStatus("/srv/core/workspace/core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// core.Println(result.OK)
|
||||
func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
|
||||
func WriteStatus(workspaceDir string, status *WorkspaceStatus) core.Result {
|
||||
if status == nil {
|
||||
return core.Result{Value: core.E("runner.WriteStatus", "status is required", nil), OK: false}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
|
|||
return core.Result{Value: core.E("runner.WriteStatus", "status conversion failed", nil), OK: false}
|
||||
}
|
||||
agenticStatus.UpdatedAt = time.Now()
|
||||
if writeResult := fs.WriteAtomic(agentic.WorkspaceStatusPath(wsDir), core.JSONMarshalString(agenticStatus)); !writeResult.OK {
|
||||
if writeResult := fs.WriteAtomic(agentic.WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(agenticStatus)); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
if err == nil {
|
||||
return core.Result{Value: core.E("runner.WriteStatus", "failed to write status", nil), OK: false}
|
||||
|
|
|
|||
|
|
@ -220,8 +220,8 @@ func (s *Service) drainQueue() {
|
|||
|
||||
func (s *Service) drainOne() bool {
|
||||
for _, statusPath := range agentic.WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(statusPath)
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
workspaceDir := core.PathDir(statusPath)
|
||||
statusResult := ReadStatusResult(workspaceDir)
|
||||
if !statusResult.OK {
|
||||
continue
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ func (s *Service) drainOne() bool {
|
|||
// Ask agentic to spawn — runner owns the gate,
|
||||
// agentic owns the actual process launch.
|
||||
// Workspace name is relative path from workspace root (e.g. "core/go-ai/dev")
|
||||
wsName := agentic.WorkspaceName(wsDir)
|
||||
wsName := agentic.WorkspaceName(workspaceDir)
|
||||
core.Info("drainOne: found queued workspace", "workspace", wsName, "agent", workspaceStatus.Agent)
|
||||
|
||||
// Spawn directly — agentic is a Core service, use ServiceFor to get it
|
||||
|
|
@ -259,7 +259,7 @@ func (s *Service) drainOne() bool {
|
|||
continue
|
||||
}
|
||||
type spawner interface {
|
||||
SpawnFromQueue(agent, prompt, wsDir string) core.Result
|
||||
SpawnFromQueue(agent, prompt, workspaceDir string) core.Result
|
||||
}
|
||||
agenticService, ok := core.ServiceFor[spawner](s.Core(), "agentic")
|
||||
if !ok {
|
||||
|
|
@ -267,7 +267,7 @@ func (s *Service) drainOne() bool {
|
|||
continue
|
||||
}
|
||||
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
spawnResult := agenticService.SpawnFromQueue(workspaceStatus.Agent, prompt, wsDir)
|
||||
spawnResult := agenticService.SpawnFromQueue(workspaceStatus.Agent, prompt, workspaceDir)
|
||||
if !spawnResult.OK {
|
||||
core.Error("drainOne: spawn failed", "workspace", wsName, "reason", core.Sprint(spawnResult.Value))
|
||||
continue
|
||||
|
|
@ -282,7 +282,7 @@ func (s *Service) drainOne() bool {
|
|||
workspaceStatus.Status = "running"
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.Runs++
|
||||
if writeResult := WriteStatus(wsDir, workspaceStatus); !writeResult.OK {
|
||||
if writeResult := WriteStatus(workspaceDir, workspaceStatus); !writeResult.OK {
|
||||
core.Error("drainOne: failed to write workspace status", "workspace", wsName, "reason", core.Sprint(writeResult.Value))
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,19 +52,19 @@ type configValue struct {
|
|||
//
|
||||
// r := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateBuildConfig(path string, projType ProjectType) core.Result {
|
||||
func GenerateBuildConfig(path string, projectType ProjectType) core.Result {
|
||||
name := core.PathBase(path)
|
||||
sections := []configSection{
|
||||
{
|
||||
Key: "project",
|
||||
Values: []configValue{
|
||||
{Key: "name", Value: name},
|
||||
{Key: "type", Value: string(projType)},
|
||||
{Key: "type", Value: string(projectType)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch projType {
|
||||
switch projectType {
|
||||
case TypeGo, TypeWails:
|
||||
sections = append(sections, configSection{
|
||||
Key: "build",
|
||||
|
|
@ -99,10 +99,10 @@ func GenerateBuildConfig(path string, projType ProjectType) core.Result {
|
|||
//
|
||||
// r := setup.GenerateTestConfig(setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateTestConfig(projType ProjectType) core.Result {
|
||||
func GenerateTestConfig(projectType ProjectType) core.Result {
|
||||
var sections []configSection
|
||||
|
||||
switch projType {
|
||||
switch projectType {
|
||||
case TypeGo, TypeWails:
|
||||
sections = []configSection{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import (
|
|||
|
||||
// ProjectType records what setup detected in a repository path.
|
||||
//
|
||||
// projType := setup.Detect("/srv/repos/agent")
|
||||
// if projType == setup.TypeGo { /* generate Go defaults */ }
|
||||
// projectType := setup.Detect("/srv/repos/agent")
|
||||
// if projectType == setup.TypeGo { /* generate Go defaults */ }
|
||||
type ProjectType string
|
||||
|
||||
const (
|
||||
|
|
@ -28,12 +28,12 @@ var fs = (&core.Fs{}).NewUnrestricted()
|
|||
|
||||
// Detect inspects a repository path and returns the primary project type.
|
||||
//
|
||||
// projType := setup.Detect("./repo")
|
||||
// projectType := setup.Detect("./repo")
|
||||
func Detect(path string) ProjectType {
|
||||
base := absolutePath(path)
|
||||
checks := []struct {
|
||||
file string
|
||||
projType ProjectType
|
||||
file string
|
||||
projectType ProjectType
|
||||
}{
|
||||
{"wails.json", TypeWails},
|
||||
{"go.mod", TypeGo},
|
||||
|
|
@ -42,7 +42,7 @@ func Detect(path string) ProjectType {
|
|||
}
|
||||
for _, candidate := range checks {
|
||||
if fs.IsFile(core.JoinPath(base, candidate.file)) {
|
||||
return candidate.projType
|
||||
return candidate.projectType
|
||||
}
|
||||
}
|
||||
return TypeUnknown
|
||||
|
|
@ -55,8 +55,8 @@ func DetectAll(path string) []ProjectType {
|
|||
base := absolutePath(path)
|
||||
var projectTypes []ProjectType
|
||||
checks := []struct {
|
||||
file string
|
||||
projType ProjectType
|
||||
file string
|
||||
projectType ProjectType
|
||||
}{
|
||||
{"go.mod", TypeGo},
|
||||
{"composer.json", TypePHP},
|
||||
|
|
@ -65,7 +65,7 @@ func DetectAll(path string) []ProjectType {
|
|||
}
|
||||
for _, candidate := range checks {
|
||||
if fs.IsFile(core.JoinPath(base, candidate.file)) {
|
||||
projectTypes = append(projectTypes, candidate.projType)
|
||||
projectTypes = append(projectTypes, candidate.projectType)
|
||||
}
|
||||
}
|
||||
return projectTypes
|
||||
|
|
|
|||
|
|
@ -28,45 +28,45 @@ func (s *Service) Run(options Options) core.Result {
|
|||
}
|
||||
options.Path = absolutePath(options.Path)
|
||||
|
||||
projType := Detect(options.Path)
|
||||
projectType := Detect(options.Path)
|
||||
allTypes := DetectAll(options.Path)
|
||||
|
||||
core.Print(nil, "Project: %s", core.PathBase(options.Path))
|
||||
core.Print(nil, "Type: %s", projType)
|
||||
core.Print(nil, "Type: %s", projectType)
|
||||
if len(allTypes) > 1 {
|
||||
core.Print(nil, "Also: %v (polyglot)", allTypes)
|
||||
}
|
||||
|
||||
var tmplName string
|
||||
var templateName string
|
||||
if options.Template != "" {
|
||||
templateResult := resolveTemplateName(options.Template, projType)
|
||||
templateResult := resolveTemplateName(options.Template, projectType)
|
||||
if !templateResult.OK {
|
||||
return templateResult
|
||||
}
|
||||
tmplName = templateResult.Value.(string)
|
||||
if !templateExists(tmplName) {
|
||||
templateName = templateResult.Value.(string)
|
||||
if !templateExists(templateName) {
|
||||
return core.Result{
|
||||
Value: core.E("setup.Run", core.Concat("template not found: ", tmplName), nil),
|
||||
Value: core.E("setup.Run", core.Concat("template not found: ", templateName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate .core/ config files
|
||||
if result := setupCoreDir(options, projType); !result.OK {
|
||||
if result := setupCoreDir(options, projectType); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
// Scaffold from dir template if requested
|
||||
if tmplName != "" {
|
||||
return s.scaffoldTemplate(options, projType, tmplName)
|
||||
if templateName != "" {
|
||||
return s.scaffoldTemplate(options, projectType, templateName)
|
||||
}
|
||||
|
||||
return core.Result{Value: options.Path, OK: true}
|
||||
}
|
||||
|
||||
// setupCoreDir creates .core/ with build.yaml and test.yaml.
|
||||
func setupCoreDir(options Options, projType ProjectType) core.Result {
|
||||
func setupCoreDir(options Options, projectType ProjectType) core.Result {
|
||||
coreDir := core.JoinPath(options.Path, ".core")
|
||||
|
||||
if options.DryRun {
|
||||
|
|
@ -83,7 +83,7 @@ func setupCoreDir(options Options, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
// build.yaml
|
||||
buildConfig := GenerateBuildConfig(options.Path, projType)
|
||||
buildConfig := GenerateBuildConfig(options.Path, projectType)
|
||||
if !buildConfig.OK {
|
||||
err, _ := buildConfig.Value.(error)
|
||||
return core.Result{
|
||||
|
|
@ -96,7 +96,7 @@ func setupCoreDir(options Options, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
// test.yaml
|
||||
testConfig := GenerateTestConfig(projType)
|
||||
testConfig := GenerateTestConfig(projectType)
|
||||
if !testConfig.OK {
|
||||
err, _ := testConfig.Value.(error)
|
||||
return core.Result{
|
||||
|
|
@ -112,37 +112,37 @@ func setupCoreDir(options Options, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
// scaffoldTemplate extracts a dir template into the target path.
|
||||
func (s *Service) scaffoldTemplate(options Options, projType ProjectType, tmplName string) core.Result {
|
||||
core.Print(nil, "Template: %s", tmplName)
|
||||
func (s *Service) scaffoldTemplate(options Options, projectType ProjectType, templateName string) core.Result {
|
||||
core.Print(nil, "Template: %s", templateName)
|
||||
|
||||
data := &lib.WorkspaceData{
|
||||
Repo: core.PathBase(options.Path),
|
||||
Branch: "main",
|
||||
Task: core.Sprintf("Initialise %s project tooling.", projType),
|
||||
Task: core.Sprintf("Initialise %s project tooling.", projectType),
|
||||
Agent: "setup",
|
||||
Language: string(projType),
|
||||
Language: string(projectType),
|
||||
Prompt: "This workspace was scaffolded by pkg/setup. Review the repository and continue from the generated context files.",
|
||||
Flow: formatFlow(projType),
|
||||
Flow: formatFlow(projectType),
|
||||
RepoDescription: s.DetectGitRemote(options.Path),
|
||||
BuildCmd: defaultBuildCommand(projType),
|
||||
TestCmd: defaultTestCommand(projType),
|
||||
BuildCmd: defaultBuildCommand(projectType),
|
||||
TestCmd: defaultTestCommand(projectType),
|
||||
}
|
||||
|
||||
if options.DryRun {
|
||||
core.Print(nil, "Would extract workspace/%s to %s", tmplName, options.Path)
|
||||
core.Print(nil, " Template found: %s", tmplName)
|
||||
core.Print(nil, "Would extract workspace/%s to %s", templateName, options.Path)
|
||||
core.Print(nil, " Template found: %s", templateName)
|
||||
return core.Result{Value: options.Path, OK: true}
|
||||
}
|
||||
|
||||
if result := lib.ExtractWorkspace(tmplName, options.Path, data); !result.OK {
|
||||
if result := lib.ExtractWorkspace(templateName, options.Path, data); !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return core.Result{
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), err),
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", templateName), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Result{
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), nil),
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", templateName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ func writeConfig(path, content string, options Options) core.Result {
|
|||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
func resolveTemplateName(name string, projType ProjectType) core.Result {
|
||||
func resolveTemplateName(name string, projectType ProjectType) core.Result {
|
||||
if name == "" {
|
||||
return core.Result{
|
||||
Value: core.E("setup.resolveTemplateName", "template is required", nil),
|
||||
|
|
@ -180,7 +180,7 @@ func resolveTemplateName(name string, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
if name == "auto" {
|
||||
switch projType {
|
||||
switch projectType {
|
||||
case TypeGo, TypeWails, TypePHP, TypeNode, TypeUnknown:
|
||||
return core.Result{Value: "default", OK: true}
|
||||
}
|
||||
|
|
@ -205,8 +205,8 @@ func templateExists(name string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func defaultBuildCommand(projType ProjectType) string {
|
||||
switch projType {
|
||||
func defaultBuildCommand(projectType ProjectType) string {
|
||||
switch projectType {
|
||||
case TypeGo, TypeWails:
|
||||
return "go build ./..."
|
||||
case TypePHP:
|
||||
|
|
@ -218,8 +218,8 @@ func defaultBuildCommand(projType ProjectType) string {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultTestCommand(projType ProjectType) string {
|
||||
switch projType {
|
||||
func defaultTestCommand(projectType ProjectType) string {
|
||||
switch projectType {
|
||||
case TypeGo, TypeWails:
|
||||
return "go test ./..."
|
||||
case TypePHP:
|
||||
|
|
@ -231,13 +231,13 @@ func defaultTestCommand(projType ProjectType) string {
|
|||
}
|
||||
}
|
||||
|
||||
func formatFlow(projType ProjectType) string {
|
||||
func formatFlow(projectType ProjectType) string {
|
||||
builder := core.NewBuilder()
|
||||
builder.WriteString("- Build: `")
|
||||
builder.WriteString(defaultBuildCommand(projType))
|
||||
builder.WriteString(defaultBuildCommand(projectType))
|
||||
builder.WriteString("`\n")
|
||||
builder.WriteString("- Test: `")
|
||||
builder.WriteString(defaultTestCommand(projType))
|
||||
builder.WriteString(defaultTestCommand(projectType))
|
||||
builder.WriteString("`")
|
||||
return builder.String()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue