agent/pkg/setup/config.go

231 lines
5.1 KiB
Go
Raw Permalink Normal View History

2026-03-20 19:31:45 +00:00
// SPDX-License-Identifier: EUPL-1.2
package setup
import (
core "dappco.re/go/core"
"gopkg.in/yaml.v3"
2026-03-20 19:31:45 +00:00
)
// data := setup.ConfigData{Name: "agent", Type: "go", Repository: "core/agent"}
2026-03-20 19:31:45 +00:00
type ConfigData struct {
Name string
Description string
Type string
Module string
Repository string
GoVersion string
Targets []Target
Commands []Command
Env map[string]string
}
// target := setup.Target{OS: "linux", Arch: "amd64"}
2026-03-20 19:31:45 +00:00
type Target struct {
OS string
Arch string
}
// command := setup.Command{Name: "unit", Run: "go test ./..."}
2026-03-20 19:31:45 +00:00
type Command struct {
Name string
Run string
}
type configSection struct {
Key string
Values []configValue
}
type configValue struct {
Key string
Value any
}
// r := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
// if r.OK { content := r.Value.(string) }
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(projectType)},
2026-03-20 19:31:45 +00:00
},
},
}
switch projectType {
2026-03-20 19:31:45 +00:00
case TypeGo, TypeWails:
sections = append(sections, configSection{
Key: "build",
Values: []configValue{
{Key: "main", Value: core.Concat("./cmd/", name)},
{Key: "binary", Value: name},
{Key: "cgo", Value: false},
2026-03-20 19:31:45 +00:00
},
})
2026-03-20 19:31:45 +00:00
case TypePHP:
sections = append(sections, configSection{
Key: "build",
Values: []configValue{
{Key: "dockerfile", Value: "Dockerfile"},
{Key: "image", Value: name},
2026-03-20 19:31:45 +00:00
},
})
2026-03-20 19:31:45 +00:00
case TypeNode:
sections = append(sections, configSection{
Key: "build",
Values: []configValue{
{Key: "script", Value: "npm run build"},
{Key: "output", Value: "dist"},
2026-03-20 19:31:45 +00:00
},
})
2026-03-20 19:31:45 +00:00
}
feat: AX v0.8.0 upgrade — Core features + quality gates AX Quality Gates (RFC-025): - Eliminate os/exec from all test + production code (12+ files) - Eliminate encoding/json from all test files (15 files, 66 occurrences) - Eliminate os from all test files except TestMain (Go runtime contract) - Eliminate path/filepath, net/url from all files - String concat: 39 violations replaced with core.Concat() - Test naming AX-7: 264 test functions renamed across all 6 packages - Example test 1:1 coverage complete Core Features Adopted: - Task Composition: agent.completion pipeline (QA → PR → Verify → Ingest → Poke) - PerformAsync: completion pipeline runs with WaitGroup + progress tracking - Config: agents.yaml loaded once, feature flags (auto-qa/pr/merge/ingest) - Named Locks: c.Lock("drain") for queue serialisation - Registry: workspace state with cross-package QUERY access - QUERY: c.QUERY(WorkspaceQuery{Status: "running"}) for cross-service queries - Action descriptions: 25+ Actions self-documenting - Data mounts: prompts/tasks/flows/personas/workspaces via c.Data() - Content Actions: agentic.prompt/task/flow/persona callable via IPC - Drive endpoints: forge + brain registered with tokens - Drive REST helpers: DriveGet/DrivePost/DriveDo for Drive-aware HTTP - HandleIPCEvents: auto-discovered by WithService (no manual wiring) - Entitlement: frozen-queue gate on write Actions - CLI dispatch: workspace dispatch wired to real dispatch method - CLI: --quiet/-q and --debug/-d global flags - CLI: banner, version, check (with service/action/command counts), env - main.go: minimal — 5 services + c.Run(), no os import - cmd tests: 84.2% coverage (was 0%) Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 06:38:02 +00:00
return renderConfig(core.Concat(name, " build configuration"), sections)
2026-03-20 19:31:45 +00:00
}
// r := setup.GenerateTestConfig(setup.TypeGo)
// if r.OK { content := r.Value.(string) }
func GenerateTestConfig(projectType ProjectType) core.Result {
var sections []configSection
2026-03-20 19:31:45 +00:00
switch projectType {
2026-03-20 19:31:45 +00:00
case TypeGo, TypeWails:
sections = []configSection{
2026-03-20 19:31:45 +00:00
{
Key: "commands",
Values: []configValue{
{Key: "unit", Value: "go test ./..."},
{Key: "coverage", Value: "go test -coverprofile=coverage.out ./..."},
{Key: "race", Value: "go test -race ./..."},
2026-03-20 19:31:45 +00:00
},
},
}
case TypePHP:
sections = []configSection{
2026-03-20 19:31:45 +00:00
{
Key: "commands",
Values: []configValue{
{Key: "unit", Value: "vendor/bin/pest --parallel"},
{Key: "lint", Value: "vendor/bin/pint --test"},
2026-03-20 19:31:45 +00:00
},
},
}
case TypeNode:
sections = []configSection{
2026-03-20 19:31:45 +00:00
{
Key: "commands",
Values: []configValue{
{Key: "unit", Value: "npm test"},
{Key: "lint", Value: "npm run lint"},
2026-03-20 19:31:45 +00:00
},
},
}
}
return renderConfig("Test configuration", sections)
}
func renderConfig(headerComment string, sections []configSection) core.Result {
builder := core.NewBuilder()
if headerComment != "" {
builder.WriteString("# ")
builder.WriteString(headerComment)
builder.WriteString("\n\n")
}
for sectionIndex, section := range sections {
builder.WriteString(section.Key)
builder.WriteString(":\n")
for _, value := range section.Values {
scalar := marshalConfigValue(value.Value)
if !scalar.OK {
err, _ := scalar.Value.(error)
return core.Result{
Value: core.E("setup.renderConfig", core.Concat("marshal ", section.Key, ".", value.Key), err),
OK: false,
}
}
builder.WriteString(" ")
builder.WriteString(value.Key)
builder.WriteString(": ")
builder.WriteString(scalar.Value.(string))
builder.WriteString("\n")
}
if sectionIndex < len(sections)-1 {
builder.WriteString("\n")
}
}
return core.Result{Value: builder.String(), OK: true}
2026-03-20 19:31:45 +00:00
}
func marshalConfigValue(value any) (result core.Result) {
defer func() {
if recovered := recover(); recovered != nil {
result = core.Result{
Value: core.E("setup.marshalConfigValue", core.Sprint(recovered), nil),
OK: false,
}
}
}()
data, err := yaml.Marshal(value)
if err != nil {
return core.Result{
Value: core.E("setup.marshalConfigValue", "yaml marshal value", err),
OK: false,
}
}
return core.Result{Value: core.Trim(string(data)), OK: true}
}
func parseGitRemote(remote string) string {
remote = core.Trim(remote)
if remote == "" {
return ""
2026-03-20 19:31:45 +00:00
}
if core.Contains(remote, "://") {
schemeParts := core.SplitN(remote, "://", 2)
if len(schemeParts) == 2 {
rest := schemeParts[1]
if pathSegments := core.Split(rest, "/"); len(pathSegments) > 1 {
pathStart := len(pathSegments[0]) + 1
if pathStart < len(rest) {
return trimRemotePath(rest[pathStart:])
}
}
}
}
pathParts := core.SplitN(remote, ":", 2)
if len(pathParts) == 2 && core.Contains(pathParts[0], "@") {
return trimRemotePath(pathParts[1])
}
if core.Contains(remote, "/") {
return trimRemotePath(remote)
2026-03-20 19:31:45 +00:00
}
return ""
}
func trimRemotePath(remote string) string {
trimmed := core.Trim(remote)
for core.HasPrefix(trimmed, "/") {
trimmed = core.TrimPrefix(trimmed, "/")
}
for core.HasSuffix(trimmed, "/") {
trimmed = core.TrimSuffix(trimmed, "/")
}
return core.TrimSuffix(trimmed, ".git")
}