2026-03-16 11:10:33 +00:00
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
2026-03-22 03:41:07 +00:00
core "dappco.re/go/core"
2026-03-16 11:10:33 +00:00
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type EpicInput struct {
2026-03-31 06:03:37 +00:00
Repo string ` json:"repo" `
Org string ` json:"org,omitempty" `
Title string ` json:"title" `
Body string ` json:"body,omitempty" `
Tasks [ ] string ` json:"tasks" `
Labels [ ] string ` json:"labels,omitempty" `
Dispatch bool ` json:"dispatch,omitempty" `
Agent string ` json:"agent,omitempty" `
Template string ` json:"template,omitempty" `
2026-03-16 11:10:33 +00:00
}
type EpicOutput struct {
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
Success bool ` json:"success" `
EpicNumber int ` json:"epic_number" `
EpicURL string ` json:"epic_url" `
Children [ ] ChildRef ` json:"children" `
Dispatched int ` json:"dispatched,omitempty" `
2026-03-16 11:10:33 +00:00
}
type ChildRef struct {
Number int ` json:"number" `
Title string ` json:"title" `
URL string ` json:"url" `
}
func ( s * PrepSubsystem ) registerEpicTool ( server * mcp . Server ) {
mcp . AddTool ( server , & mcp . Tool {
Name : "agentic_create_epic" ,
Description : "Create an epic issue with child issues on Forge. Each task becomes a child issue linked via checklist. Optionally auto-dispatch agents to work each child." ,
} , s . createEpic )
}
2026-03-30 22:40:28 +00:00
func ( s * PrepSubsystem ) createEpic ( ctx context . Context , callRequest * mcp . CallToolRequest , input EpicInput ) ( * mcp . CallToolResult , EpicOutput , error ) {
2026-03-16 11:10:33 +00:00
if input . Title == "" {
2026-03-22 03:41:07 +00:00
return nil , EpicOutput { } , core . E ( "createEpic" , "title is required" , nil )
2026-03-16 11:10:33 +00:00
}
if len ( input . Tasks ) == 0 {
2026-03-22 03:41:07 +00:00
return nil , EpicOutput { } , core . E ( "createEpic" , "at least one task is required" , nil )
2026-03-16 11:10:33 +00:00
}
if s . forgeToken == "" {
2026-03-22 03:41:07 +00:00
return nil , EpicOutput { } , core . E ( "createEpic" , "no Forge token configured" , nil )
2026-03-16 11:10:33 +00:00
}
if input . Org == "" {
input . Org = "core"
}
if input . Agent == "" {
input . Agent = "claude"
}
if input . Template == "" {
input . Template = "coding"
}
labels := input . Labels
hasAgentic := false
for _ , l := range labels {
if l == "agentic" {
hasAgentic = true
break
}
}
if ! hasAgentic {
labels = append ( labels , "agentic" )
}
labelIDs := s . resolveLabelIDs ( ctx , input . Org , input . Repo , labels )
var children [ ] ChildRef
for _ , task := range input . Tasks {
child , err := s . createIssue ( ctx , input . Org , input . Repo , task , "" , labelIDs )
if err != nil {
2026-03-31 05:49:11 +00:00
continue
2026-03-16 11:10:33 +00:00
}
children = append ( children , child )
}
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
body := core . NewBuilder ( )
2026-03-16 11:10:33 +00:00
if input . Body != "" {
body . WriteString ( input . Body )
body . WriteString ( "\n\n" )
}
body . WriteString ( "## Tasks\n\n" )
for _ , child := range children {
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
body . WriteString ( core . Sprintf ( "- [ ] #%d %s\n" , child . Number , child . Title ) )
2026-03-16 11:10:33 +00:00
}
epicLabels := append ( labelIDs , s . resolveLabelIDs ( ctx , input . Org , input . Repo , [ ] string { "epic" } ) ... )
epic , err := s . createIssue ( ctx , input . Org , input . Repo , input . Title , body . String ( ) , epicLabels )
if err != nil {
2026-03-22 03:41:07 +00:00
return nil , EpicOutput { } , core . E ( "createEpic" , "failed to create epic" , err )
2026-03-16 11:10:33 +00:00
}
out := EpicOutput {
Success : true ,
EpicNumber : epic . Number ,
EpicURL : epic . URL ,
Children : children ,
}
if input . Dispatch {
for _ , child := range children {
2026-03-30 22:40:28 +00:00
_ , _ , err := s . dispatch ( ctx , callRequest , DispatchInput {
2026-03-16 11:10:33 +00:00
Repo : input . Repo ,
Org : input . Org ,
Task : child . Title ,
Agent : input . Agent ,
Template : input . Template ,
Issue : child . Number ,
} )
if err == nil {
out . Dispatched ++
}
}
}
return nil , out , nil
}
2026-03-30 22:54:19 +00:00
// child, err := s.createIssue(ctx, "core", "go-scm", "Port agentic plans", "", nil)
2026-03-16 11:10:33 +00:00
func ( s * PrepSubsystem ) createIssue ( ctx context . Context , org , repo , title , body string , labelIDs [ ] int64 ) ( ChildRef , error ) {
payload := map [ string ] any {
"title" : title ,
}
if body != "" {
payload [ "body" ] = body
}
if len ( labelIDs ) > 0 {
payload [ "labels" ] = labelIDs
}
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
data := core . JSONMarshalString ( payload )
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/issues" , s . forgeURL , org , repo )
2026-03-30 22:40:28 +00:00
httpResult := HTTPPost ( ctx , url , data , s . forgeToken , "token" )
if ! httpResult . 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
return ChildRef { } , core . E ( "createIssue" , "create issue request failed" , nil )
2026-03-16 11:10:33 +00:00
}
2026-03-30 22:40:28 +00:00
var createdIssue struct {
2026-03-16 11:10:33 +00:00
Number int ` json:"number" `
HTMLURL string ` json:"html_url" `
}
2026-03-30 22:40:28 +00:00
core . JSONUnmarshalString ( httpResult . Value . ( string ) , & createdIssue )
2026-03-16 11:10:33 +00:00
return ChildRef {
2026-03-30 22:40:28 +00:00
Number : createdIssue . Number ,
2026-03-16 11:10:33 +00:00
Title : title ,
2026-03-30 22:40:28 +00:00
URL : createdIssue . HTMLURL ,
2026-03-16 11:10:33 +00:00
} , nil
}
2026-03-30 22:54:19 +00:00
// labelIDs := s.resolveLabelIDs(ctx, "core", "go-scm", []string{"agentic", "epic"})
2026-03-16 11:10:33 +00:00
func ( s * PrepSubsystem ) resolveLabelIDs ( ctx context . Context , org , repo string , names [ ] string ) [ ] int64 {
if len ( names ) == 0 {
return nil
}
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?limit=50" , s . forgeURL , org , repo )
2026-03-30 22:40:28 +00:00
httpResult := HTTPGet ( ctx , url , s . forgeToken , "token" )
if ! httpResult . OK {
2026-03-16 11:10:33 +00:00
return nil
}
var existing [ ] struct {
ID int64 ` json:"id" `
Name string ` json:"name" `
}
2026-03-30 22:40:28 +00:00
core . JSONUnmarshalString ( httpResult . Value . ( string ) , & existing )
2026-03-16 11:10:33 +00:00
nameToID := make ( map [ string ] int64 )
for _ , l := range existing {
nameToID [ l . Name ] = l . ID
}
var ids [ ] int64
for _ , name := range names {
if id , ok := nameToID [ name ] ; ok {
ids = append ( ids , id )
} else {
id := s . createLabel ( ctx , org , repo , name )
if id > 0 {
ids = append ( ids , id )
}
}
}
return ids
}
2026-03-30 22:54:19 +00:00
// id := s.createLabel(ctx, "core", "go-scm", "agentic")
2026-03-16 11:10:33 +00:00
func ( s * PrepSubsystem ) createLabel ( ctx context . Context , org , repo , name string ) int64 {
colours := map [ string ] string {
"agentic" : "#7c3aed" ,
"epic" : "#dc2626" ,
"bug" : "#ef4444" ,
"help-wanted" : "#22c55e" ,
}
colour := colours [ name ]
if colour == "" {
colour = "#6b7280"
}
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-16 11:10:33 +00:00
"name" : name ,
"color" : colour ,
} )
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 22:40:28 +00:00
httpResult := HTTPPost ( ctx , url , payload , s . forgeToken , "token" )
if ! httpResult . OK {
2026-03-16 11:10:33 +00:00
return 0
}
2026-03-30 22:40:28 +00:00
var createdLabel struct {
2026-03-16 11:10:33 +00:00
ID int64 ` json:"id" `
}
2026-03-30 22:40:28 +00:00
core . JSONUnmarshalString ( httpResult . Value . ( string ) , & createdLabel )
return createdLabel . ID
2026-03-16 11:10:33 +00:00
}