fix(ax): continue AX naming cleanup

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-30 21:37:15 +00:00
parent e825550a90
commit da25b6f79f
17 changed files with 159 additions and 159 deletions

View file

@ -239,7 +239,7 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options)
Repo: workspaceStatus.Repo,
Branch: workspaceStatus.Branch,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
})
}
}
@ -270,13 +270,13 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options)
s.Core().ACTION(messages.PRMerged{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
})
} else if workspaceStatus.Question != "" {
s.Core().ACTION(messages.PRNeedsReview{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
Reason: workspaceStatus.Question,
})
}

View file

@ -23,18 +23,18 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
process := s.Core().Process()
// PRs target dev — agents never merge directly to main
base := "dev"
defaultBranch := "dev"
processResult := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", base, "..HEAD"))
processResult := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", defaultBranch, "..HEAD"))
if !processResult.OK {
return
}
out := core.Trim(processResult.Value.(string))
if out == "" {
commitLogOutput := core.Trim(processResult.Value.(string))
if commitLogOutput == "" {
return
}
commitCount := len(core.Split(out, "\n"))
commitCount := len(core.Split(commitLogOutput, "\n"))
org := workspaceStatus.Org
if org == "" {
@ -62,7 +62,7 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
prURL, _, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
pullRequestURL, _, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, defaultBranch, title, body)
if err != nil {
if result := ReadStatusResult(workspaceDir); result.OK {
workspaceStatusUpdate, ok := workspaceStatusValue(result)
@ -81,7 +81,7 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
if !ok {
return
}
workspaceStatusUpdate.PRURL = prURL
workspaceStatusUpdate.PRURL = pullRequestURL
writeStatusResult(workspaceDir, workspaceStatusUpdate)
}
}

View file

@ -42,7 +42,7 @@ func (s *PrepSubsystem) runTask(ctx context.Context, options core.Options) core.
repo := options.String("repo")
agent := options.String("agent")
task := options.String("task")
issueStr := options.String("issue")
issueValue := options.String("issue")
org := options.String("org")
if repo == "" || task == "" {
@ -56,7 +56,7 @@ func (s *PrepSubsystem) runTask(ctx context.Context, options core.Options) core.
org = "core"
}
issue := parseIntStr(issueStr)
issue := parseIntStr(issueValue)
core.Print(nil, "core-agent run task")
core.Print(nil, " repo: %s/%s", org, repo)
@ -131,30 +131,30 @@ func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result {
prepInput.Branch = "dev"
}
_, out, err := s.TestPrepWorkspace(context.Background(), prepInput)
_, prepOutput, err := s.TestPrepWorkspace(context.Background(), prepInput)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
core.Print(nil, "workspace: %s", out.WorkspaceDir)
core.Print(nil, "repo: %s", out.RepoDir)
core.Print(nil, "branch: %s", out.Branch)
core.Print(nil, "resumed: %v", out.Resumed)
core.Print(nil, "memories: %d", out.Memories)
core.Print(nil, "consumers: %d", out.Consumers)
if out.Prompt != "" {
core.Print(nil, "workspace: %s", prepOutput.WorkspaceDir)
core.Print(nil, "repo: %s", prepOutput.RepoDir)
core.Print(nil, "branch: %s", prepOutput.Branch)
core.Print(nil, "resumed: %v", prepOutput.Resumed)
core.Print(nil, "memories: %d", prepOutput.Memories)
core.Print(nil, "consumers: %d", prepOutput.Consumers)
if prepOutput.Prompt != "" {
core.Print(nil, "")
core.Print(nil, "--- prompt (%d chars) ---", len(out.Prompt))
core.Print(nil, "%s", out.Prompt)
core.Print(nil, "--- prompt (%d chars) ---", len(prepOutput.Prompt))
core.Print(nil, "%s", prepOutput.Prompt)
}
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdStatus(_ core.Options) core.Result {
workspaceRoot := WorkspaceRoot()
fsys := s.Core().Fs()
listResult := fsys.List(workspaceRoot)
filesystem := s.Core().Fs()
listResult := filesystem.List(workspaceRoot)
if !listResult.OK {
core.Print(nil, "no workspaces found at %s", workspaceRoot)
return core.Result{OK: true}
@ -231,12 +231,12 @@ func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), nil), OK: false}
}
fsys := s.Core().Fs()
filesystem := s.Core().Fs()
paths := core.PathGlob(core.JoinPath(target, "*"))
for _, p := range paths {
name := core.PathBase(p)
marker := " "
if fsys.IsDir(p) {
if filesystem.IsDir(p) {
marker = "/"
}
core.Print(nil, " %s%s", name, marker)

View file

@ -40,7 +40,7 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
workspaceRoot := WorkspaceRoot()
fsys := s.Core().Fs()
filesystem := s.Core().Fs()
filter := options.String("_arg")
if filter == "" {
filter = "all"
@ -86,7 +86,7 @@ func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
for _, name := range toRemove {
path := core.JoinPath(workspaceRoot, name)
fsys.DeleteAll(path)
filesystem.DeleteAll(path)
core.Print(nil, " removed %s", name)
}
core.Print(nil, "\n %d workspaces removed", len(toRemove))

View file

@ -114,8 +114,8 @@ func parseCoreDeps(gomod string) []coreDep {
repo := suffix
if core.HasPrefix(suffix, "core/") {
// core/process → go-process
sub := core.TrimPrefix(suffix, "core/")
repo = core.Concat("go-", sub)
repoSuffix := core.TrimPrefix(suffix, "core/")
repo = core.Concat("go-", repoSuffix)
} else if suffix == "core" {
repo = "go"
}

View file

@ -240,12 +240,12 @@ func agentOutputFile(workspaceDir, agent string) string {
// detectFinalStatus reads workspace state after agent exit to determine outcome.
// Returns (status, question) — "completed", "blocked", or "failed".
func detectFinalStatus(repoDir string, exitCode int, procStatus string) (string, string) {
func detectFinalStatus(repoDir string, exitCode int, processStatus string) (string, string) {
blockedPath := core.JoinPath(repoDir, "BLOCKED.md")
if blockedResult := fs.Read(blockedPath); blockedResult.OK && core.Trim(blockedResult.Value.(string)) != "" {
return "blocked", core.Trim(blockedResult.Value.(string))
}
if exitCode != 0 || procStatus == "failed" || procStatus == "killed" {
if exitCode != 0 || processStatus == "failed" || processStatus == "killed" {
question := ""
if exitCode != 0 {
question = core.Sprintf("Agent exited with code %d", exitCode)
@ -348,14 +348,14 @@ func (s *PrepSubsystem) broadcastComplete(agent, workspaceDir, finalStatus strin
// onAgentComplete handles all post-completion logic for a spawned agent.
// Called from the monitoring goroutine after the process exits.
func (s *PrepSubsystem) onAgentComplete(agent, workspaceDir, outputFile string, exitCode int, procStatus, output string) {
func (s *PrepSubsystem) onAgentComplete(agent, workspaceDir, outputFile string, exitCode int, processStatus, output string) {
// Save output
if output != "" {
fs.Write(outputFile, output)
}
repoDir := WorkspaceRepoDir(workspaceDir)
finalStatus, question := detectFinalStatus(repoDir, exitCode, procStatus)
finalStatus, question := detectFinalStatus(repoDir, exitCode, processStatus)
// Update workspace status (disk + registry)
result := ReadStatusResult(workspaceDir)

View file

@ -727,33 +727,33 @@ func TestHandlers_FindWorkspaceByPR_Ugly_CorruptStatusFile(t *testing.T) {
assert.Equal(t, "", result)
}
// --- extractPRNumber ---
// --- extractPullRequestNumber ---
func TestVerify_ExtractPRNumber_Good_FullURL(t *testing.T) {
assert.Equal(t, 42, extractPRNumber("https://forge.lthn.ai/core/agent/pulls/42"))
assert.Equal(t, 1, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/1"))
assert.Equal(t, 999, extractPRNumber("https://forge.lthn.ai/core/go-log/pulls/999"))
func TestVerify_ExtractPullRequestNumber_Good_FullURL(t *testing.T) {
assert.Equal(t, 42, extractPullRequestNumber("https://forge.lthn.ai/core/agent/pulls/42"))
assert.Equal(t, 1, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/1"))
assert.Equal(t, 999, extractPullRequestNumber("https://forge.lthn.ai/core/go-log/pulls/999"))
}
func TestVerify_ExtractPRNumber_Good_NumberOnly(t *testing.T) {
func TestVerify_ExtractPullRequestNumber_Good_NumberOnly(t *testing.T) {
// If someone passes a bare number as a URL it should still work
assert.Equal(t, 7, extractPRNumber("7"))
assert.Equal(t, 7, extractPullRequestNumber("7"))
}
func TestVerify_ExtractPRNumber_Bad_EmptyURL(t *testing.T) {
assert.Equal(t, 0, extractPRNumber(""))
func TestVerify_ExtractPullRequestNumber_Bad_EmptyURL(t *testing.T) {
assert.Equal(t, 0, extractPullRequestNumber(""))
}
func TestVerify_ExtractPRNumber_Bad_TrailingSlash(t *testing.T) {
func TestVerify_ExtractPullRequestNumber_Bad_TrailingSlash(t *testing.T) {
// URL ending with slash has empty last segment
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/"))
assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/"))
}
func TestVerify_ExtractPRNumber_Bad_NonNumericEnd(t *testing.T) {
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/abc"))
func TestVerify_ExtractPullRequestNumber_Bad_NonNumericEnd(t *testing.T) {
assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/abc"))
}
func TestVerify_ExtractPRNumber_Ugly_JustSlashes(t *testing.T) {
func TestVerify_ExtractPullRequestNumber_Ugly_JustSlashes(t *testing.T) {
// All slashes — last segment is empty
assert.Equal(t, 0, extractPRNumber("///"))
assert.Equal(t, 0, extractPullRequestNumber("///"))
}

View file

@ -128,11 +128,11 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
sync.Pushed = true
// Create PR: dev → main on GitHub
prURL, err := s.createGitHubPR(ctx, repoDir, repo, ahead, files)
pullRequestURL, err := s.createGitHubPR(ctx, repoDir, repo, ahead, files)
if err != nil {
sync.Skipped = core.Sprintf("PR creation failed: %v", err)
} else {
sync.PRURL = prURL
sync.PRURL = pullRequestURL
}
synced = append(synced, sync)

View file

@ -119,25 +119,25 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
}
// Create PR via Forge API
prURL, prNum, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
pullRequestURL, pullRequestNumber, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
if err != nil {
return nil, CreatePROutput{}, core.E("createPR", "failed to create PR", err)
}
// Update status with PR URL
workspaceStatus.PRURL = prURL
workspaceStatus.PRURL = pullRequestURL
writeStatusResult(workspaceDir, workspaceStatus)
// Comment on issue if tracked
if workspaceStatus.Issue > 0 {
comment := core.Sprintf("Pull request created: %s", prURL)
comment := core.Sprintf("Pull request created: %s", pullRequestURL)
s.commentOnIssue(ctx, org, workspaceStatus.Repo, workspaceStatus.Issue, comment)
}
return nil, CreatePROutput{
Success: true,
PRURL: prURL,
PRNum: prNum,
PRURL: pullRequestURL,
PRNum: pullRequestNumber,
Title: title,
Branch: workspaceStatus.Branch,
Repo: workspaceStatus.Repo,
@ -162,17 +162,17 @@ func (s *PrepSubsystem) buildPRBody(workspaceStatus *WorkspaceStatus) string {
}
func (s *PrepSubsystem) forgeCreatePR(ctx context.Context, org, repo, head, base, title, body string) (string, int, error) {
var pr pullRequestView
var pullRequest pullRequestView
err := s.forge.Client().Post(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls", org, repo), &forge_types.CreatePullRequestOption{
Title: title,
Body: body,
Head: head,
Base: base,
}, &pr)
}, &pullRequest)
if err != nil {
return "", 0, core.E("forgeCreatePR", "create PR failed", err)
}
return pr.HTMLURL, int(pullRequestNumber(pr)), nil
return pullRequest.HTMLURL, int(pullRequestNumber(pullRequest)), nil
}
func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, issue int, comment string) {
@ -275,36 +275,36 @@ func (s *PrepSubsystem) listPRs(ctx context.Context, _ *mcp.CallToolRequest, inp
}
func (s *PrepSubsystem) listRepoPRs(ctx context.Context, org, repo, state string) ([]PRInfo, error) {
var prs []pullRequestView
err := s.forge.Client().Get(ctx, core.Sprintf("/api/v1/repos/%s/%s/pulls?limit=50&page=1", org, repo), &prs)
var pullRequests []pullRequestView
err := s.forge.Client().Get(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)
}
var result []PRInfo
for _, pr := range prs {
prState := pr.State
if prState == "" {
prState = "open"
for _, pullRequest := range pullRequests {
pullRequestState := pullRequest.State
if pullRequestState == "" {
pullRequestState = "open"
}
if state != "" && state != "all" && prState != state {
if state != "" && state != "all" && pullRequestState != state {
continue
}
var labels []string
for _, l := range pr.Labels {
labels = append(labels, l.Name)
for _, label := range pullRequest.Labels {
labels = append(labels, label.Name)
}
result = append(result, PRInfo{
Repo: repo,
Number: int(pullRequestNumber(pr)),
Title: pr.Title,
State: prState,
Author: pullRequestAuthor(pr),
Branch: pr.Head.Ref,
Base: pr.Base.Ref,
Number: int(pullRequestNumber(pullRequest)),
Title: pullRequest.Title,
State: pullRequestState,
Author: pullRequestAuthor(pullRequest),
Branch: pullRequest.Head.Ref,
Base: pullRequest.Base.Ref,
Labels: labels,
Mergeable: pr.Mergeable,
URL: pr.HTMLURL,
Mergeable: pullRequest.Mergeable,
URL: pullRequest.HTMLURL,
})
}

View file

@ -92,7 +92,7 @@ func (s *PrepSubsystem) SetCore(c *core.Core) {
// OnStartup implements core.Startable — registers named Actions, starts the queue runner,
// and registers CLI commands. The Action registry IS the capability map.
//
// c.Action("agentic.dispatch").Run(ctx, opts)
// c.Action("agentic.dispatch").Run(ctx, options)
// c.Actions() // ["agentic.dispatch", "agentic.prep", "agentic.status", ...]
func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
c := s.Core()
@ -194,7 +194,7 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
})
// PerformAsync wrapper — runs the completion Task in background with progress tracking.
// c.PerformAsync("agentic.complete", opts) broadcasts ActionTaskStarted/Completed.
// c.PerformAsync("agentic.complete", options) broadcasts ActionTaskStarted/Completed.
c.Action("agentic.complete", s.handleComplete).Description = "Run completion pipeline (QA → PR → Verify) in background"
// Hydrate workspace registry from disk
@ -552,8 +552,8 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
// Maps repo name to plans directory: go-io → core/go/io, agent → core/agent, core-bio → core/php/bio.
// Preserves subdirectory structure so sub-package specs land in specs/{pkg}/RFC.md.
//
// 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/
// s.copyRepoSpecs("/tmp/workspace", "go-io") // copies plans/core/go/io/**/RFC*.md → /tmp/workspace/specs/
// s.copyRepoSpecs("/tmp/workspace", "core-bio") // copies plans/core/php/bio/**/RFC*.md → /tmp/workspace/specs/
func (s *PrepSubsystem) copyRepoSpecs(workspaceDir, repo string) {
fs := (&core.Fs{}).NewUnrestricted()
@ -586,7 +586,7 @@ func (s *PrepSubsystem) copyRepoSpecs(workspaceDir, 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
// Preserves subdirectory structure: specDir/pkg/nested/RFC.md → specs/pkg/nested/RFC.md
specsDir := core.JoinPath(workspaceDir, "specs")
fs.EnsureDir(specsDir)

View file

@ -29,8 +29,8 @@ func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
org = "core"
}
prNum := extractPRNumber(workspaceStatus.PRURL)
if prNum == 0 {
pullRequestNumber := extractPullRequestNumber(workspaceStatus.PRURL)
if pullRequestNumber == 0 {
return
}
@ -47,7 +47,7 @@ func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
}
// Attempt 1: run tests and try to merge
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum)
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, pullRequestNumber)
if mergeOutcome == mergeSuccess {
markMerged()
return
@ -56,7 +56,7 @@ func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
// Attempt 2: rebase onto main and retry
if mergeOutcome == mergeConflict || mergeOutcome == testFailed {
if s.rebaseBranch(repoDir, workspaceStatus.Branch) {
if s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum) == mergeSuccess {
if s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, pullRequestNumber) == mergeSuccess {
markMerged()
return
}
@ -64,7 +64,7 @@ func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
}
// Both attempts failed — flag for human review
s.flagForReview(org, workspaceStatus.Repo, prNum, mergeOutcome)
s.flagForReview(org, workspaceStatus.Repo, pullRequestNumber, mergeOutcome)
if result := ReadStatusResult(workspaceDir); result.OK {
workspaceStatusUpdate, ok := workspaceStatusValue(result)
@ -85,13 +85,13 @@ const (
)
// attemptVerifyAndMerge runs tests and tries to merge. Returns the outcome.
func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string, prNum int) mergeResult {
func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string, pullRequestNumber int) mergeResult {
testResult := s.runVerification(repoDir)
if !testResult.passed {
comment := core.Sprintf("## Verification Failed\n\n**Command:** `%s`\n\n```\n%s\n```\n\n**Exit code:** %d",
testResult.testCmd, truncate(testResult.output, 2000), testResult.exitCode)
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
return testFailed
}
@ -99,14 +99,14 @@ func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string,
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if mergeResult := s.forgeMergePR(ctx, org, repo, prNum); !mergeResult.OK {
if mergeAttempt := s.forgeMergePR(ctx, org, repo, pullRequestNumber); !mergeAttempt.OK {
comment := core.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed", testResult.testCmd)
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
return mergeConflict
}
comment := core.Sprintf("## Auto-Verified & Merged\n\n**Tests:** `%s` — PASS\n\nAuto-merged by core-agent dispatch system.", testResult.testCmd)
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
return mergeSuccess
}
@ -140,7 +140,7 @@ func (s *PrepSubsystem) rebaseBranch(repoDir, branch string) bool {
}
// flagForReview adds the "needs-review" label to the PR via Forge API.
func (s *PrepSubsystem) flagForReview(org, repo string, prNum int, result mergeResult) {
func (s *PrepSubsystem) flagForReview(org, repo string, pullRequestNumber int, mergeOutcome mergeResult) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
@ -151,16 +151,16 @@ func (s *PrepSubsystem) flagForReview(org, repo string, prNum int, result mergeR
payload := core.JSONMarshalString(map[string]any{
"labels": []int{s.getLabelID(ctx, org, repo, "needs-review")},
})
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/labels", s.forgeURL, org, repo, prNum)
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/labels", s.forgeURL, org, repo, pullRequestNumber)
HTTPPost(ctx, url, payload, s.forgeToken, "token")
// Comment explaining the situation
reason := "Tests failed after rebase"
if result == mergeConflict {
if mergeOutcome == mergeConflict {
reason = "Merge conflict persists after rebase"
}
comment := core.Sprintf("## Needs Review\n\n%s. Auto-merge gave up after retry.\n\nLabelled `needs-review` for human attention.", reason)
s.commentOnIssue(ctx, org, repo, prNum, comment)
s.commentOnIssue(ctx, org, repo, pullRequestNumber, comment)
}
// ensureLabel creates a label if it doesn't exist.
@ -278,20 +278,20 @@ func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
}
// forgeMergePR merges a PR via the Forge API.
func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, prNum int) core.Result {
func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, pullRequestNumber int) core.Result {
payload := core.JSONMarshalString(map[string]any{
"Do": "merge",
"merge_message_field": "Auto-merged by core-agent after verification\n\nCo-Authored-By: Virgil <virgil@lethean.io>",
"delete_branch_after_merge": true,
})
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/merge", s.forgeURL, org, repo, prNum)
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/merge", s.forgeURL, org, repo, pullRequestNumber)
return HTTPPost(ctx, url, payload, s.forgeToken, "token")
}
// extractPRNumber gets the PR number from a Forge PR URL.
func extractPRNumber(prURL string) int {
parts := core.Split(prURL, "/")
// extractPullRequestNumber gets the PR number from a Forge PR URL.
func extractPullRequestNumber(pullRequestURL string) int {
parts := core.Split(pullRequestURL, "/")
if len(parts) == 0 {
return 0
}

View file

@ -122,23 +122,23 @@ func TestVerify_ForgeMergePR_Bad_NetworkError(t *testing.T) {
assert.False(t, r.OK)
}
// --- extractPRNumber (additional _Ugly cases) ---
// --- extractPullRequestNumber (additional _Ugly cases) ---
func TestVerify_ExtractPRNumber_Ugly_DoubleSlashEnd(t *testing.T) {
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/42/"))
func TestVerify_ExtractPullRequestNumber_Ugly_DoubleSlashEnd(t *testing.T) {
assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/42/"))
}
func TestVerify_ExtractPRNumber_Ugly_VeryLargeNumber(t *testing.T) {
assert.Equal(t, 999999, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/999999"))
func TestVerify_ExtractPullRequestNumber_Ugly_VeryLargeNumber(t *testing.T) {
assert.Equal(t, 999999, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/999999"))
}
func TestVerify_ExtractPRNumber_Ugly_NegativeNumber(t *testing.T) {
func TestVerify_ExtractPullRequestNumber_Ugly_NegativeNumber(t *testing.T) {
// atoi of "-5" is -5, parseInt wraps atoi
assert.Equal(t, -5, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/-5"))
assert.Equal(t, -5, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/-5"))
}
func TestVerify_ExtractPRNumber_Ugly_ZeroExplicit(t *testing.T) {
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/0"))
func TestVerify_ExtractPullRequestNumber_Ugly_ZeroExplicit(t *testing.T) {
assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/0"))
}
// --- ensureLabel ---
@ -410,7 +410,7 @@ func TestVerify_AutoVerifyAndMerge_Bad_InvalidPRURL(t *testing.T) {
failCount: make(map[string]int),
}
// extractPRNumber returns 0 for invalid URL, so autoVerifyAndMerge returns early
// extractPullRequestNumber returns 0 for invalid URL, so autoVerifyAndMerge returns early
assert.NotPanics(t, func() {
s.autoVerifyAndMerge(dir)
})
@ -519,7 +519,7 @@ func TestAutopr_Truncate_Ugly_EmptyString(t *testing.T) {
func TestVerify_AutoVerifyAndMerge_Ugly(t *testing.T) {
// Workspace with status=completed, repo=test, PRURL="not-a-url"
// extractPRNumber returns 0 for "not-a-url" → early return, no panic
// extractPullRequestNumber returns 0 for "not-a-url" → early return, no panic
dir := t.TempDir()
require.NoError(t, writeStatus(dir, &WorkspaceStatus{
Status: "completed",
@ -580,17 +580,17 @@ func TestVerify_AttemptVerifyAndMerge_Ugly(t *testing.T) {
assert.True(t, commentCalled, "should have posted a comment about test failure")
}
// --- extractPRNumber (extended Ugly) ---
// --- extractPullRequestNumber (extended Ugly) ---
func TestVerify_ExtractPRNumber_Ugly(t *testing.T) {
func TestVerify_ExtractPullRequestNumber_Ugly(t *testing.T) {
// Just a bare number "5" → last segment is "5" → returns 5
assert.Equal(t, 5, extractPRNumber("5"))
assert.Equal(t, 5, extractPullRequestNumber("5"))
// Trailing slash → last segment is empty string → parseInt("") → 0
assert.Equal(t, 0, extractPRNumber("https://forge.lthn.ai/core/go-io/pulls/42/"))
assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/42/"))
// Non-numeric string → parseInt("abc") → 0
assert.Equal(t, 0, extractPRNumber("abc"))
assert.Equal(t, 0, extractPullRequestNumber("abc"))
}
// --- EnsureLabel Ugly ---

View file

@ -2,8 +2,8 @@
// Package brain gives MCP and HTTP services the same OpenBrain capability map.
//
// sub := brain.New(nil)
// core.Println(sub.Name())
// subsystem := brain.New(nil)
// core.Println(subsystem.Name())
package brain
import (
@ -33,36 +33,36 @@ var errBridgeNotAvailable = core.E("brain", "bridge not available", nil)
// Subsystem routes `brain_*` MCP tools through the shared IDE bridge.
//
// sub := brain.New(nil)
// core.Println(sub.Name()) // "brain"
// subsystem := brain.New(nil)
// core.Println(subsystem.Name()) // "brain"
type Subsystem struct {
bridge *ide.Bridge
}
// New builds the bridge-backed OpenBrain subsystem used by MCP.
//
// sub := brain.New(nil)
// core.Println(sub.Name())
// subsystem := brain.New(nil)
// core.Println(subsystem.Name())
func New(bridge *ide.Bridge) *Subsystem {
return &Subsystem{bridge: bridge}
}
// Name keeps the subsystem address stable for core.WithService and MCP.
//
// name := sub.Name() // "brain"
// name := subsystem.Name() // "brain"
func (s *Subsystem) Name() string { return "brain" }
// RegisterTools publishes the bridge-backed brain tools on an MCP server.
//
// sub := brain.New(nil)
// sub.RegisterTools(server)
// subsystem := brain.New(nil)
// subsystem.RegisterTools(server)
func (s *Subsystem) RegisterTools(server *mcp.Server) {
s.registerBrainTools(server)
}
// Shutdown satisfies the MCP subsystem lifecycle without extra cleanup.
//
// _ = sub.Shutdown(context.Background())
// _ = subsystem.Shutdown(context.Background())
func (s *Subsystem) Shutdown(_ context.Context) error {
return nil
}

View file

@ -14,8 +14,8 @@ import (
// DirectSubsystem talks to OpenBrain over HTTP without the IDE bridge.
//
// sub := brain.NewDirect()
// core.Println(sub.Name()) // "brain"
// subsystem := brain.NewDirect()
// core.Println(subsystem.Name()) // "brain"
type DirectSubsystem struct {
apiURL string
apiKey string
@ -25,8 +25,8 @@ var _ coremcp.Subsystem = (*DirectSubsystem)(nil)
// NewDirect builds the HTTP-backed OpenBrain subsystem.
//
// sub := brain.NewDirect()
// core.Println(sub.Name())
// subsystem := brain.NewDirect()
// core.Println(subsystem.Name())
func NewDirect() *DirectSubsystem {
apiURL := core.Env("CORE_BRAIN_URL")
if apiURL == "" {
@ -58,13 +58,13 @@ func NewDirect() *DirectSubsystem {
// Name keeps the direct subsystem address stable for core.WithService and MCP.
//
// name := sub.Name() // "brain"
// name := subsystem.Name() // "brain"
func (s *DirectSubsystem) Name() string { return "brain" }
// RegisterTools publishes the direct `brain_*` and `agent_*` tools on an MCP server.
//
// sub := brain.NewDirect()
// sub.RegisterTools(server)
// subsystem := brain.NewDirect()
// subsystem.RegisterTools(server)
func (s *DirectSubsystem) RegisterTools(server *mcp.Server) {
mcp.AddTool(server, &mcp.Tool{
Name: "brain_remember",
@ -87,7 +87,7 @@ func (s *DirectSubsystem) RegisterTools(server *mcp.Server) {
// Shutdown satisfies the MCP subsystem lifecycle without extra cleanup.
//
// _ = sub.Shutdown(context.Background())
// _ = subsystem.Shutdown(context.Background())
func (s *DirectSubsystem) Shutdown(_ context.Context) error { return nil }
func brainKeyPath(home string) string {

View file

@ -12,8 +12,8 @@ import (
// RegisterMessagingTools adds direct agent messaging tools to an MCP server.
//
// sub := brain.NewDirect()
// sub.RegisterMessagingTools(server)
// subsystem := brain.NewDirect()
// subsystem.RegisterMessagingTools(server)
func (s *DirectSubsystem) RegisterMessagingTools(server *mcp.Server) {
mcp.AddTool(server, &mcp.Tool{
Name: "agent_send",

View file

@ -18,7 +18,7 @@
// r := lib.Task("code/review") // r.Value.(string)
// r := lib.Persona("secops/dev") // r.Value.(string)
// r := lib.Flow("go") // r.Value.(string)
// r := lib.ExtractWorkspace("default", "/tmp/ws", data)
// r := lib.ExtractWorkspace("default", "/tmp/workspace", data)
// core.Println(r.OK)
package lib
@ -80,18 +80,18 @@ func ensureMounted() core.Result {
mountedData := &core.Data{Registry: core.NewRegistry[*core.Embed]()}
for _, item := range []struct {
name string
fsys embed.FS
basedir string
assign func(*core.Embed)
name string
filesystem embed.FS
baseDir string
assign func(*core.Embed)
}{
{name: "prompt", fsys: promptFiles, basedir: "prompt", assign: func(emb *core.Embed) { promptFS = emb }},
{name: "task", fsys: taskFiles, basedir: "task", assign: func(emb *core.Embed) { taskFS = emb }},
{name: "flow", fsys: flowFiles, basedir: "flow", assign: func(emb *core.Embed) { flowFS = emb }},
{name: "persona", fsys: personaFiles, basedir: "persona", assign: func(emb *core.Embed) { personaFS = emb }},
{name: "workspace", fsys: workspaceFiles, basedir: "workspace", assign: func(emb *core.Embed) { workspaceFS = emb }},
{name: "prompt", filesystem: promptFiles, baseDir: "prompt", assign: func(emb *core.Embed) { promptFS = emb }},
{name: "task", filesystem: taskFiles, baseDir: "task", assign: func(emb *core.Embed) { taskFS = emb }},
{name: "flow", filesystem: flowFiles, baseDir: "flow", assign: func(emb *core.Embed) { flowFS = emb }},
{name: "persona", filesystem: personaFiles, baseDir: "persona", assign: func(emb *core.Embed) { personaFS = emb }},
{name: "workspace", filesystem: workspaceFiles, baseDir: "workspace", assign: func(emb *core.Embed) { workspaceFS = emb }},
} {
mounted := mountEmbed(item.fsys, item.basedir)
mounted := mountEmbed(item.filesystem, item.baseDir)
if !mounted.OK {
mountResult = mounted
return
@ -109,21 +109,21 @@ func ensureMounted() core.Result {
return mountResult
}
func mountEmbed(fsys embed.FS, basedir string) core.Result {
result := core.Mount(fsys, basedir)
func mountEmbed(filesystem embed.FS, baseDir string) core.Result {
result := core.Mount(filesystem, baseDir)
if result.OK {
return result
}
if err, ok := result.Value.(error); ok {
return core.Result{
Value: core.E("lib.mountEmbed", core.Concat("mount ", basedir), err),
Value: core.E("lib.mountEmbed", core.Concat("mount ", baseDir), err),
OK: false,
}
}
return core.Result{
Value: core.E("lib.mountEmbed", core.Concat("mount ", basedir), nil),
Value: core.E("lib.mountEmbed", core.Concat("mount ", baseDir), nil),
OK: false,
}
}
@ -355,9 +355,9 @@ func ListTasks() []string {
}
result := listNamesRecursive("task", ".")
a := core.NewArray(result...)
a.Deduplicate()
return a.AsSlice()
names := core.NewArray(result...)
names.Deduplicate()
return names.AsSlice()
}
// ListPersonas returns available persona paths, including nested directories.
@ -368,9 +368,9 @@ func ListPersonas() []string {
return nil
}
a := core.NewArray(listNamesRecursive("persona", ".")...)
a.Deduplicate()
return a.AsSlice()
names := core.NewArray(listNamesRecursive("persona", ".")...)
names.Deduplicate()
return names.AsSlice()
}
// listNamesRecursive walks an embed tree via Data.ListNames.
@ -397,7 +397,7 @@ func listNamesRecursive(mount, dir string) []string {
subPath := core.JoinPath(mount, relPath)
// Try as directory — recurse if it has contents
if sub := data.ListNames(subPath); sub.OK {
if childNames := data.ListNames(subPath); childNames.OK {
slugs = append(slugs, listNamesRecursive(mount, relPath)...)
}

View file

@ -82,7 +82,7 @@ var _ coremcp.Subsystem = (*Subsystem)(nil)
// Deprecated: prefer Register with core.WithService(monitor.Register).
//
// mon.SetCore(c)
// monitorService.SetCore(c)
func (m *Subsystem) SetCore(coreApp *core.Core) {
m.ServiceRuntime = core.NewServiceRuntime(coreApp, Options{})
}