2026-03-17 17:45:04 +00:00
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package agentic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-03-22 03:45:50 +00:00
|
|
|
core "dappco.re/go/core"
|
2026-03-17 17:45:04 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.autoVerifyAndMerge("/srv/core/workspace/core/go-io/task-5")
|
2026-03-30 21:22:54 +00:00
|
|
|
func (s *PrepSubsystem) autoVerifyAndMerge(workspaceDir string) {
|
|
|
|
|
result := ReadStatusResult(workspaceDir)
|
2026-03-30 21:11:06 +00:00
|
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
|
|
|
if !ok || workspaceStatus.PRURL == "" || workspaceStatus.Repo == "" {
|
2026-03-17 17:45:04 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:22:54 +00:00
|
|
|
repoDir := WorkspaceRepoDir(workspaceDir)
|
2026-03-30 21:11:06 +00:00
|
|
|
org := workspaceStatus.Org
|
2026-03-17 17:45:04 +00:00
|
|
|
if org == "" {
|
|
|
|
|
org = "core"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:37:15 +00:00
|
|
|
pullRequestNumber := extractPullRequestNumber(workspaceStatus.PRURL)
|
|
|
|
|
if pullRequestNumber == 0 {
|
2026-03-17 17:45:04 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 19:35:15 +00:00
|
|
|
markMerged := func() {
|
2026-03-30 21:22:54 +00:00
|
|
|
if result := ReadStatusResult(workspaceDir); result.OK {
|
2026-03-30 19:40:02 +00:00
|
|
|
st2, ok := workspaceStatusValue(result)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-17 17:45:04 +00:00
|
|
|
st2.Status = "merged"
|
2026-03-30 21:22:54 +00:00
|
|
|
writeStatusResult(workspaceDir, st2)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-17 19:35:15 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:37:15 +00:00
|
|
|
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, pullRequestNumber)
|
2026-03-30 19:40:02 +00:00
|
|
|
if mergeOutcome == mergeSuccess {
|
2026-03-17 19:35:15 +00:00
|
|
|
markMerged()
|
2026-03-17 17:45:04 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 19:40:02 +00:00
|
|
|
if mergeOutcome == mergeConflict || mergeOutcome == testFailed {
|
2026-03-30 21:11:06 +00:00
|
|
|
if s.rebaseBranch(repoDir, workspaceStatus.Branch) {
|
2026-03-30 21:37:15 +00:00
|
|
|
if s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, pullRequestNumber) == mergeSuccess {
|
2026-03-17 19:35:15 +00:00
|
|
|
markMerged()
|
2026-03-17 17:45:04 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:37:15 +00:00
|
|
|
s.flagForReview(org, workspaceStatus.Repo, pullRequestNumber, mergeOutcome)
|
2026-03-17 17:45:04 +00:00
|
|
|
|
2026-03-30 21:22:54 +00:00
|
|
|
if result := ReadStatusResult(workspaceDir); result.OK {
|
2026-03-30 21:11:06 +00:00
|
|
|
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
2026-03-30 19:40:02 +00:00
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
workspaceStatusUpdate.Question = "Flagged for review — auto-merge failed after retry"
|
2026-03-30 21:22:54 +00:00
|
|
|
writeStatusResult(workspaceDir, workspaceStatusUpdate)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type mergeResult int
|
|
|
|
|
|
|
|
|
|
const (
|
2026-03-31 06:03:37 +00:00
|
|
|
mergeSuccess mergeResult = iota
|
|
|
|
|
testFailed
|
|
|
|
|
mergeConflict
|
2026-03-17 17:45:04 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.attemptVerifyAndMerge("/srv/core/workspace/core/go-io/task-5/repo", "core", "go-io", "feature/ax-cleanup", 42)
|
2026-03-30 21:37:15 +00:00
|
|
|
func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string, pullRequestNumber int) mergeResult {
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
testResult := s.runVerification(repoDir)
|
2026-03-17 17:45:04 +00:00
|
|
|
|
|
|
|
|
if !testResult.passed {
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
comment := core.Sprintf("## Verification Failed\n\n**Command:** `%s`\n\n```\n%s\n```\n\n**Exit code:** %d",
|
2026-03-17 17:45:04 +00:00
|
|
|
testResult.testCmd, truncate(testResult.output, 2000), testResult.exitCode)
|
2026-03-30 21:37:15 +00:00
|
|
|
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
|
2026-03-17 17:45:04 +00:00
|
|
|
return testFailed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
2026-03-30 21:37:15 +00:00
|
|
|
if mergeAttempt := s.forgeMergePR(ctx, org, repo, pullRequestNumber); !mergeAttempt.OK {
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
comment := core.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed", testResult.testCmd)
|
2026-03-30 21:37:15 +00:00
|
|
|
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
|
2026-03-17 17:45:04 +00:00
|
|
|
return mergeConflict
|
|
|
|
|
}
|
|
|
|
|
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
comment := core.Sprintf("## Auto-Verified & Merged\n\n**Tests:** `%s` — PASS\n\nAuto-merged by core-agent dispatch system.", testResult.testCmd)
|
2026-03-30 21:37:15 +00:00
|
|
|
s.commentOnIssue(context.Background(), org, repo, pullRequestNumber, comment)
|
2026-04-02 01:59:03 +00:00
|
|
|
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
|
|
|
|
|
s.cleanupForgeBranch(ctx, repoDir, forgeRemote, branch)
|
2026-03-17 17:45:04 +00:00
|
|
|
return mergeSuccess
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.rebaseBranch("/srv/core/workspace/core/go-io/task-5/repo", "feature/ax-cleanup")
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
func (s *PrepSubsystem) rebaseBranch(repoDir, branch string) bool {
|
2026-03-25 09:51:57 +00:00
|
|
|
ctx := context.Background()
|
2026-03-30 15:48:21 +00:00
|
|
|
process := s.Core().Process()
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
base := s.DefaultBranch(repoDir)
|
2026-03-21 16:22:18 +00:00
|
|
|
|
2026-03-30 15:48:21 +00:00
|
|
|
if !process.RunIn(ctx, repoDir, "git", "fetch", "origin", base).OK {
|
2026-03-17 17:45:04 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 15:48:21 +00:00
|
|
|
if !process.RunIn(ctx, repoDir, "git", "rebase", core.Concat("origin/", base)).OK {
|
|
|
|
|
process.RunIn(ctx, repoDir, "git", "rebase", "--abort")
|
2026-03-17 17:45:04 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 19:40:02 +00:00
|
|
|
result := ReadStatusResult(core.PathDir(repoDir))
|
2026-03-30 21:11:06 +00:00
|
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
2026-03-21 17:57:03 +00:00
|
|
|
org := "core"
|
|
|
|
|
repo := ""
|
2026-03-30 19:40:02 +00:00
|
|
|
if ok {
|
2026-03-30 21:11:06 +00:00
|
|
|
if workspaceStatus.Org != "" {
|
|
|
|
|
org = workspaceStatus.Org
|
2026-03-21 17:57:03 +00:00
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
repo = workspaceStatus.Repo
|
2026-03-21 17:57:03 +00:00
|
|
|
}
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
|
2026-03-30 15:48:21 +00:00
|
|
|
return process.RunIn(ctx, repoDir, "git", "push", "--force-with-lease", forgeRemote, branch).OK
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.flagForReview("core", "go-io", 42, mergeConflict)
|
2026-03-30 21:37:15 +00:00
|
|
|
func (s *PrepSubsystem) flagForReview(org, repo string, pullRequestNumber int, mergeOutcome mergeResult) {
|
2026-03-17 17:45:04 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
s.ensureLabel(ctx, org, repo, "needs-review", "e11d48")
|
|
|
|
|
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
payload := core.JSONMarshalString(map[string]any{
|
2026-03-17 17:45:04 +00:00
|
|
|
"labels": []int{s.getLabelID(ctx, org, repo, "needs-review")},
|
|
|
|
|
})
|
2026-03-30 21:37:15 +00:00
|
|
|
url := core.Sprintf("%s/api/v1/repos/%s/%s/issues/%d/labels", s.forgeURL, org, repo, pullRequestNumber)
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
HTTPPost(ctx, url, payload, s.forgeToken, "token")
|
2026-03-17 17:45:04 +00:00
|
|
|
|
|
|
|
|
reason := "Tests failed after rebase"
|
2026-03-30 21:37:15 +00:00
|
|
|
if mergeOutcome == mergeConflict {
|
2026-03-17 17:45:04 +00:00
|
|
|
reason = "Merge conflict persists after rebase"
|
|
|
|
|
}
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
comment := core.Sprintf("## Needs Review\n\n%s. Auto-merge gave up after retry.\n\nLabelled `needs-review` for human attention.", reason)
|
2026-03-30 21:37:15 +00:00
|
|
|
s.commentOnIssue(ctx, org, repo, pullRequestNumber, comment)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.ensureLabel(context.Background(), "core", "go-io", "needs-review", "e11d48")
|
2026-03-17 17:45:04 +00:00
|
|
|
func (s *PrepSubsystem) ensureLabel(ctx context.Context, org, repo, name, colour string) {
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
payload := core.JSONMarshalString(map[string]string{
|
2026-03-17 17:45:04 +00:00
|
|
|
"name": name,
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
"color": core.Concat("#", colour),
|
2026-03-17 17:45:04 +00:00
|
|
|
})
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
HTTPPost(ctx, url, payload, s.forgeToken, "token")
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.getLabelID(context.Background(), "core", "go-io", "needs-review")
|
2026-03-17 17:45:04 +00:00
|
|
|
func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string) int {
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
2026-03-30 21:11:06 +00:00
|
|
|
getResult := HTTPGet(ctx, url, s.forgeToken, "token")
|
|
|
|
|
if !getResult.OK {
|
2026-03-17 17:45:04 +00:00
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var labels []struct {
|
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
core.JSONUnmarshalString(getResult.Value.(string), &labels)
|
2026-03-17 17:45:04 +00:00
|
|
|
for _, l := range labels {
|
|
|
|
|
if l.Name == name {
|
|
|
|
|
return l.ID
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type verifyResult struct {
|
|
|
|
|
passed bool
|
|
|
|
|
output string
|
|
|
|
|
exitCode int
|
|
|
|
|
testCmd string
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:11:06 +00:00
|
|
|
func resultText(result core.Result) string {
|
|
|
|
|
if text, ok := result.Value.(string); ok {
|
2026-03-29 20:15:58 +00:00
|
|
|
return text
|
|
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
if result.Value != nil {
|
|
|
|
|
return core.Sprint(result.Value)
|
2026-03-29 20:15:58 +00:00
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.runVerification("/srv/core/workspace/core/go-io/task-5/repo")
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
func (s *PrepSubsystem) runVerification(repoDir string) verifyResult {
|
|
|
|
|
if fileExists(core.JoinPath(repoDir, "go.mod")) {
|
|
|
|
|
return s.runGoTests(repoDir)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
if fileExists(core.JoinPath(repoDir, "composer.json")) {
|
|
|
|
|
return s.runPHPTests(repoDir)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
if fileExists(core.JoinPath(repoDir, "package.json")) {
|
|
|
|
|
return s.runNodeTests(repoDir)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
return verifyResult{passed: true, testCmd: "none", output: "No test runner detected"}
|
|
|
|
|
}
|
|
|
|
|
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
func (s *PrepSubsystem) runGoTests(repoDir string) verifyResult {
|
2026-03-25 09:51:57 +00:00
|
|
|
ctx := context.Background()
|
2026-03-30 15:48:21 +00:00
|
|
|
process := s.Core().Process()
|
2026-03-30 21:11:06 +00:00
|
|
|
processResult := process.RunWithEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
|
|
|
|
|
out := resultText(processResult)
|
2026-03-17 17:45:04 +00:00
|
|
|
exitCode := 0
|
2026-03-30 21:11:06 +00:00
|
|
|
if !processResult.OK {
|
2026-03-25 09:51:57 +00:00
|
|
|
exitCode = 1
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
return verifyResult{passed: processResult.OK, output: out, exitCode: exitCode, testCmd: "go test ./..."}
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
func (s *PrepSubsystem) runPHPTests(repoDir string) verifyResult {
|
2026-03-25 09:51:57 +00:00
|
|
|
ctx := context.Background()
|
2026-03-30 15:48:21 +00:00
|
|
|
process := s.Core().Process()
|
2026-03-30 21:11:06 +00:00
|
|
|
composerResult := process.RunIn(ctx, repoDir, "composer", "test", "--no-interaction")
|
|
|
|
|
if !composerResult.OK {
|
|
|
|
|
fallbackResult := process.RunIn(ctx, repoDir, "./vendor/bin/pest", "--no-interaction")
|
|
|
|
|
if !fallbackResult.OK {
|
2026-03-25 09:51:57 +00:00
|
|
|
return verifyResult{passed: false, testCmd: "none", output: "No PHP test runner found (composer test and vendor/bin/pest both unavailable)", exitCode: 1}
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
return verifyResult{passed: true, output: resultText(fallbackResult), exitCode: 0, testCmd: "vendor/bin/pest"}
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
return verifyResult{passed: true, output: resultText(composerResult), exitCode: 0, testCmd: "composer test"}
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
|
|
|
func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
|
2026-03-30 21:11:06 +00:00
|
|
|
packageResult := fs.Read(core.JoinPath(repoDir, "package.json"))
|
|
|
|
|
if !packageResult.OK {
|
2026-03-17 17:45:04 +00:00
|
|
|
return verifyResult{passed: true, testCmd: "none", output: "Could not read package.json"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pkg struct {
|
|
|
|
|
Scripts map[string]string `json:"scripts"`
|
|
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
if parseResult := core.JSONUnmarshalString(packageResult.Value.(string), &pkg); !parseResult.OK || pkg.Scripts["test"] == "" {
|
2026-03-17 17:45:04 +00:00
|
|
|
return verifyResult{passed: true, testCmd: "none", output: "No test script in package.json"}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:51:57 +00:00
|
|
|
ctx := context.Background()
|
2026-03-30 15:48:21 +00:00
|
|
|
process := s.Core().Process()
|
2026-03-30 21:11:06 +00:00
|
|
|
testResult := process.RunIn(ctx, repoDir, "npm", "test")
|
|
|
|
|
out := resultText(testResult)
|
2026-03-17 17:45:04 +00:00
|
|
|
exitCode := 0
|
2026-03-30 21:11:06 +00:00
|
|
|
if !testResult.OK {
|
2026-03-25 09:51:57 +00:00
|
|
|
exitCode = 1
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
2026-03-30 21:11:06 +00:00
|
|
|
return verifyResult{passed: testResult.OK, output: out, exitCode: exitCode, testCmd: "npm test"}
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// s.forgeMergePR(context.Background(), "core", "go-io", 42)
|
2026-03-30 21:37:15 +00:00
|
|
|
func (s *PrepSubsystem) forgeMergePR(ctx context.Context, org, repo string, pullRequestNumber int) core.Result {
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
payload := core.JSONMarshalString(map[string]any{
|
2026-03-17 17:45:04 +00:00
|
|
|
"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,
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-30 21:37:15 +00:00
|
|
|
url := core.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/merge", s.forgeURL, org, repo, pullRequestNumber)
|
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
|
|
|
return HTTPPost(ctx, url, payload, s.forgeToken, "token")
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 22:04:36 +00:00
|
|
|
// extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/42")
|
2026-03-30 21:37:15 +00:00
|
|
|
func extractPullRequestNumber(pullRequestURL string) int {
|
|
|
|
|
parts := core.Split(pullRequestURL, "/")
|
2026-03-17 17:45:04 +00:00
|
|
|
if len(parts) == 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
|
|
|
return parseInt(parts[len(parts)-1])
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func fileExists(path string) bool {
|
2026-03-22 03:41:07 +00:00
|
|
|
return fs.IsFile(path)
|
2026-03-17 17:45:04 +00:00
|
|
|
}
|