From a96cb05bd8e15329b1378ff1e102fbd51dc9d8e2 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 26 Mar 2026 19:08:13 +0000 Subject: [PATCH] chore: polish AX v0.8.0 compliance Co-Authored-By: Virgil --- agentci/clotho.go | 9 +- agentci/config.go | 12 +- agentci/config_test.go | 4 +- agentci/security.go | 131 ++++++++++++++-- agentci/security_test.go | 4 +- cmd/collect/cmd.go | 7 +- cmd/collect/cmd_bitcointalk.go | 6 +- cmd/collect/cmd_dispatch.go | 6 +- cmd/collect/cmd_excavate.go | 6 +- cmd/collect/cmd_github.go | 6 +- cmd/collect/cmd_market.go | 4 +- cmd/collect/cmd_papers.go | 4 +- cmd/collect/cmd_process.go | 4 +- cmd/forge/cmd_auth.go | 4 +- cmd/forge/cmd_config.go | 4 +- cmd/forge/cmd_forge.go | 1 + cmd/forge/cmd_issues.go | 6 +- cmd/forge/cmd_labels.go | 4 +- cmd/forge/cmd_migrate.go | 4 +- cmd/forge/cmd_orgs.go | 4 +- cmd/forge/cmd_prs.go | 6 +- cmd/forge/cmd_repos.go | 4 +- cmd/forge/cmd_status.go | 4 +- cmd/forge/cmd_sync.go | 10 +- cmd/forge/cmd_sync_test.go | 2 +- cmd/forge/helpers.go | 2 +- cmd/gitea/cmd_config.go | 4 +- cmd/gitea/cmd_gitea.go | 1 + cmd/gitea/cmd_issues.go | 6 +- cmd/gitea/cmd_mirror.go | 8 +- cmd/gitea/cmd_prs.go | 6 +- cmd/gitea/cmd_repos.go | 4 +- cmd/gitea/cmd_sync.go | 10 +- cmd/gitea/cmd_sync_test.go | 2 +- cmd/scm/cmd_compile.go | 6 +- cmd/scm/cmd_export.go | 4 +- cmd/scm/cmd_index.go | 6 +- cmd/scm/cmd_scm.go | 1 + collect/bitcointalk.go | 12 +- collect/bitcointalk_http_test.go | 4 +- collect/collect.go | 8 +- collect/coverage_boost_test.go | 2 +- collect/coverage_phase2_test.go | 121 ++++++++------- collect/events.go | 9 ++ collect/excavate.go | 3 +- collect/excavate_test.go | 5 +- collect/github.go | 11 +- collect/market.go | 10 +- collect/market_extra_test.go | 2 +- collect/market_test.go | 2 +- collect/papers.go | 15 +- collect/papers_http_test.go | 2 +- collect/process.go | 11 +- collect/ratelimit.go | 8 +- collect/state.go | 7 +- forge/client.go | 2 + forge/client_test.go | 20 +-- forge/config.go | 11 +- forge/config_test.go | 2 +- forge/issues.go | 1 + forge/labels.go | 2 +- forge/meta.go | 2 + forge/prs.go | 4 +- forge/prs_test.go | 4 +- forge/testhelper_test.go | 4 +- git/git.go | 17 ++- git/service.go | 9 ++ gitea/client.go | 2 + gitea/config.go | 11 +- gitea/config_test.go | 2 +- gitea/coverage_boost_test.go | 14 +- gitea/issues.go | 1 + gitea/issues_test.go | 2 +- gitea/meta.go | 2 + gitea/meta_test.go | 6 +- gitea/testhelper_test.go | 6 +- go.mod | 4 +- internal/ax/filepathx/filepathx.go | 53 +++++++ internal/ax/fmtx/fmtx.go | 38 +++++ internal/ax/jsonx/jsonx.go | 37 +++++ internal/ax/osx/osx.go | 111 ++++++++++++++ internal/ax/stdio/stdio.go | 38 +++++ internal/ax/stringsx/stringsx.go | 149 +++++++++++++++++++ jobrunner/forgejo/source.go | 9 +- jobrunner/forgejo/source_test.go | 14 +- jobrunner/handlers/completion.go | 5 +- jobrunner/handlers/dispatch.go | 23 ++- jobrunner/handlers/dispatch_test.go | 6 +- jobrunner/handlers/enable_auto_merge.go | 4 +- jobrunner/handlers/enable_auto_merge_test.go | 2 +- jobrunner/handlers/publish_draft.go | 4 +- jobrunner/handlers/resolve_threads.go | 4 +- jobrunner/handlers/resolve_threads_test.go | 2 +- jobrunner/handlers/send_fix_command.go | 4 +- jobrunner/handlers/testhelper_test.go | 2 +- jobrunner/handlers/tick_parent.go | 6 +- jobrunner/handlers/tick_parent_test.go | 4 +- jobrunner/journal.go | 15 +- jobrunner/journal_test.go | 8 +- jobrunner/poller.go | 3 + jobrunner/types.go | 4 + jobrunner/types_test.go | 2 +- locales/embed.go | 2 + manifest/compile.go | 19 ++- manifest/compile_test.go | 2 +- manifest/loader.go | 7 +- manifest/manifest.go | 11 +- manifest/sign.go | 2 + marketplace/builder.go | 12 +- marketplace/builder_test.go | 6 +- marketplace/discovery.go | 12 +- marketplace/discovery_test.go | 4 +- marketplace/installer.go | 11 +- marketplace/installer_test.go | 6 +- marketplace/marketplace.go | 7 +- pkg/api/embed.go | 2 + pkg/api/provider.go | 2 + pkg/api/provider_handlers_test.go | 2 +- pkg/api/provider_test.go | 2 +- plugin/config.go | 1 + plugin/installer.go | 12 +- plugin/loader.go | 6 +- plugin/manifest.go | 6 +- plugin/plugin.go | 2 + plugin/registry.go | 8 +- repos/gitstate.go | 16 +- repos/kbconfig.go | 12 +- repos/registry.go | 19 ++- repos/workconfig.go | 24 +-- 129 files changed, 1109 insertions(+), 329 deletions(-) create mode 100644 internal/ax/filepathx/filepathx.go create mode 100644 internal/ax/fmtx/fmtx.go create mode 100644 internal/ax/jsonx/jsonx.go create mode 100644 internal/ax/osx/osx.go create mode 100644 internal/ax/stdio/stdio.go create mode 100644 internal/ax/stringsx/stringsx.go diff --git a/agentci/clotho.go b/agentci/clotho.go index 41ce261..573cd97 100644 --- a/agentci/clotho.go +++ b/agentci/clotho.go @@ -2,26 +2,31 @@ package agentci import ( "context" - "strings" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "dappco.re/go/core/scm/jobrunner" ) // RunMode determines the execution strategy for a dispatched task. +// Usage example: var value RunMode type RunMode string const ( + // Usage example: _ = ModeStandard ModeStandard RunMode = "standard" - ModeDual RunMode = "dual" // The Clotho Protocol — dual-run verification + // Usage example: _ = ModeDual + ModeDual RunMode = "dual" // The Clotho Protocol — dual-run verification ) // Spinner is the Clotho orchestrator that determines the fate of each task. +// Usage example: var value Spinner type Spinner struct { Config ClothoConfig Agents map[string]AgentConfig } // NewSpinner creates a new Clotho orchestrator. +// Usage example: NewSpinner(...) func NewSpinner(cfg ClothoConfig, agents map[string]AgentConfig) *Spinner { return &Spinner{ Config: cfg, diff --git a/agentci/config.go b/agentci/config.go index a7386b2..0ff9bdb 100644 --- a/agentci/config.go +++ b/agentci/config.go @@ -2,13 +2,14 @@ package agentci import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/config" coreerr "dappco.re/go/core/log" + "forge.lthn.ai/core/config" ) // AgentConfig represents a single agent machine in the config file. +// Usage example: var value AgentConfig type AgentConfig struct { Host string `yaml:"host" mapstructure:"host"` QueueDir string `yaml:"queue_dir" mapstructure:"queue_dir"` @@ -23,6 +24,7 @@ type AgentConfig struct { } // ClothoConfig controls the orchestration strategy. +// Usage example: var value ClothoConfig type ClothoConfig struct { Strategy string `yaml:"strategy" mapstructure:"strategy"` // direct, clotho-verified ValidationThreshold float64 `yaml:"validation_threshold" mapstructure:"validation_threshold"` // divergence limit (0.0-1.0) @@ -31,6 +33,7 @@ type ClothoConfig struct { // LoadAgents reads agent targets from config and returns a map of AgentConfig. // Returns an empty map (not an error) if no agents are configured. +// Usage example: LoadAgents(...) func LoadAgents(cfg *config.Config) (map[string]AgentConfig, error) { var agents map[string]AgentConfig if err := cfg.Get("agentci.agents", &agents); err != nil { @@ -61,6 +64,7 @@ func LoadAgents(cfg *config.Config) (map[string]AgentConfig, error) { } // LoadActiveAgents returns only active agents. +// Usage example: LoadActiveAgents(...) func LoadActiveAgents(cfg *config.Config) (map[string]AgentConfig, error) { all, err := LoadAgents(cfg) if err != nil { @@ -77,6 +81,7 @@ func LoadActiveAgents(cfg *config.Config) (map[string]AgentConfig, error) { // LoadClothoConfig loads the Clotho orchestrator settings. // Returns sensible defaults if no config is present. +// Usage example: LoadClothoConfig(...) func LoadClothoConfig(cfg *config.Config) (ClothoConfig, error) { var cc ClothoConfig if err := cfg.Get("agentci.clotho", &cc); err != nil { @@ -95,6 +100,7 @@ func LoadClothoConfig(cfg *config.Config) (ClothoConfig, error) { } // SaveAgent writes an agent config entry to the config file. +// Usage example: SaveAgent(...) func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error { key := fmt.Sprintf("agentci.agents.%s", name) data := map[string]any{ @@ -123,6 +129,7 @@ func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error { } // RemoveAgent removes an agent from the config file. +// Usage example: RemoveAgent(...) func RemoveAgent(cfg *config.Config, name string) error { var agents map[string]AgentConfig if err := cfg.Get("agentci.agents", &agents); err != nil { @@ -136,6 +143,7 @@ func RemoveAgent(cfg *config.Config, name string) error { } // ListAgents returns all configured agents (active and inactive). +// Usage example: ListAgents(...) func ListAgents(cfg *config.Config) (map[string]AgentConfig, error) { var agents map[string]AgentConfig if err := cfg.Get("agentci.agents", &agents); err != nil { diff --git a/agentci/config_test.go b/agentci/config_test.go index 034ff09..18c5db6 100644 --- a/agentci/config_test.go +++ b/agentci/config_test.go @@ -3,8 +3,8 @@ package agentci import ( "testing" - "forge.lthn.ai/core/config" "dappco.re/go/core/io" + "forge.lthn.ai/core/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -299,7 +299,7 @@ func TestListAgents_Good_Empty(t *testing.T) { assert.Empty(t, agents) } -func TestRoundTrip_SaveThenLoad(t *testing.T) { +func TestRoundTrip_Good_SaveThenLoad(t *testing.T) { cfg := newTestConfig(t, "") err := SaveAgent(cfg, "alpha", AgentConfig{ diff --git a/agentci/security.go b/agentci/security.go index 52c106c..6629acd 100644 --- a/agentci/security.go +++ b/agentci/security.go @@ -1,36 +1,148 @@ package agentci import ( - "os/exec" - "path/filepath" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" + "path" "regexp" - "strings" coreerr "dappco.re/go/core/log" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" ) var safeNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\.]+$`) // SanitizePath ensures a filename or directory name is safe and prevents path traversal. -// Returns filepath.Base of the input after validation. +// Returns the validated input unchanged. +// Usage example: SanitizePath(...) func SanitizePath(input string) (string, error) { - base := filepath.Base(input) - if !safeNameRegex.MatchString(base) { + if input == "" { + return "", coreerr.E("agentci.SanitizePath", "path element is required", nil) + } + if strings.ContainsAny(input, `/\`) { + return "", coreerr.E("agentci.SanitizePath", "path separators are not allowed: "+input, nil) + } + if input == "." || input == ".." { + return "", coreerr.E("agentci.SanitizePath", "invalid path element: "+input, nil) + } + if !safeNameRegex.MatchString(input) { return "", coreerr.E("agentci.SanitizePath", "invalid characters in path element: "+input, nil) } - if base == "." || base == ".." || base == "/" { - return "", coreerr.E("agentci.SanitizePath", "invalid path element: "+base, nil) + return input, nil +} + +// ValidatePathElement validates a single local path element and returns its safe form. +// Usage example: ValidatePathElement(...) +func ValidatePathElement(input string) (string, error) { + return SanitizePath(input) +} + +// ResolvePathWithinRoot resolves a validated path element beneath a root directory. +// Usage example: ResolvePathWithinRoot(...) +func ResolvePathWithinRoot(root string, input string) (string, string, error) { + safeName, err := ValidatePathElement(input) + if err != nil { + return "", "", coreerr.E("agentci.ResolvePathWithinRoot", "invalid path element", err) } - return base, nil + + absRoot, err := filepath.Abs(root) + if err != nil { + return "", "", coreerr.E("agentci.ResolvePathWithinRoot", "resolve root", err) + } + + resolved := filepath.Clean(filepath.Join(absRoot, safeName)) + cleanRoot := filepath.Clean(absRoot) + rootPrefix := cleanRoot + string(filepath.Separator) + if resolved != cleanRoot && !strings.HasPrefix(resolved, rootPrefix) { + return "", "", coreerr.E("agentci.ResolvePathWithinRoot", "resolved path escaped root", nil) + } + + return safeName, resolved, nil +} + +// ValidateRemoteDir validates a remote directory path used over SSH. +// Usage example: ValidateRemoteDir(...) +func ValidateRemoteDir(dir string) (string, error) { + if strings.TrimSpace(dir) == "" { + return "", coreerr.E("agentci.ValidateRemoteDir", "directory is required", nil) + } + if strings.ContainsAny(dir, `\`) { + return "", coreerr.E("agentci.ValidateRemoteDir", "backslashes are not allowed", nil) + } + + switch dir { + case "/", "~": + return dir, nil + } + + cleaned := path.Clean(dir) + prefix := "" + rest := cleaned + + if strings.HasPrefix(dir, "~/") { + prefix = "~/" + rest = strings.TrimPrefix(cleaned, "~/") + } + if strings.HasPrefix(dir, "/") { + prefix = "/" + rest = strings.TrimPrefix(cleaned, "/") + } + + if rest == "." || rest == ".." || strings.HasPrefix(rest, "../") { + return "", coreerr.E("agentci.ValidateRemoteDir", "directory escaped root", nil) + } + + for _, part := range strings.Split(rest, "/") { + if part == "" { + continue + } + if _, err := ValidatePathElement(part); err != nil { + return "", coreerr.E("agentci.ValidateRemoteDir", "invalid directory segment", err) + } + } + + if rest == "" || rest == "." { + return prefix, nil + } + + return prefix + rest, nil +} + +// JoinRemotePath joins validated remote path elements using forward slashes. +// Usage example: JoinRemotePath(...) +func JoinRemotePath(base string, parts ...string) (string, error) { + safeBase, err := ValidateRemoteDir(base) + if err != nil { + return "", coreerr.E("agentci.JoinRemotePath", "invalid base directory", err) + } + + cleanParts := make([]string, 0, len(parts)) + for _, part := range parts { + safePart, partErr := ValidatePathElement(part) + if partErr != nil { + return "", coreerr.E("agentci.JoinRemotePath", "invalid path element", partErr) + } + cleanParts = append(cleanParts, safePart) + } + + if safeBase == "~" { + return path.Join("~", path.Join(cleanParts...)), nil + } + if strings.HasPrefix(safeBase, "~/") { + return "~/" + path.Join(strings.TrimPrefix(safeBase, "~/"), path.Join(cleanParts...)), nil + } + return path.Join(append([]string{safeBase}, cleanParts...)...), nil } // EscapeShellArg wraps a string in single quotes for safe remote shell insertion. // Prefer exec.Command arguments over constructing shell strings where possible. +// Usage example: EscapeShellArg(...) func EscapeShellArg(arg string) string { return "'" + strings.ReplaceAll(arg, "'", "'\\''") + "'" } // SecureSSHCommand creates an SSH exec.Cmd with strict host key checking and batch mode. +// Usage example: SecureSSHCommand(...) func SecureSSHCommand(host string, remoteCmd string) *exec.Cmd { return exec.Command("ssh", "-o", "StrictHostKeyChecking=yes", @@ -42,6 +154,7 @@ func SecureSSHCommand(host string, remoteCmd string) *exec.Cmd { } // MaskToken returns a masked version of a token for safe logging. +// Usage example: MaskToken(...) func MaskToken(token string) string { if len(token) < 8 { return "*****" diff --git a/agentci/security_test.go b/agentci/security_test.go index 6d0b68e..1167e50 100644 --- a/agentci/security_test.go +++ b/agentci/security_test.go @@ -20,7 +20,6 @@ func TestSanitizePath_Good(t *testing.T) { {"with.dot", "with.dot"}, {"CamelCase", "CamelCase"}, {"123", "123"}, - {"path/to/file.txt", "file.txt"}, } for _, tt := range tests { @@ -44,8 +43,11 @@ func TestSanitizePath_Bad(t *testing.T) { {"pipe", "file|name"}, {"ampersand", "file&name"}, {"dollar", "file$name"}, + {"slash", "path/to/file.txt"}, + {"backslash", `path\to\file.txt`}, {"parent traversal base", ".."}, {"root", "/"}, + {"empty", ""}, } for _, tt := range tests { diff --git a/cmd/collect/cmd.go b/cmd/collect/cmd.go index 8f7e43c..09a8821 100644 --- a/cmd/collect/cmd.go +++ b/cmd/collect/cmd.go @@ -1,12 +1,12 @@ package collect import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" "dappco.re/go/core/io" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) func init() { @@ -28,6 +28,7 @@ var ( ) // AddCollectCommands registers the 'collect' command and all subcommands. +// Usage example: AddCollectCommands(...) func AddCollectCommands(root *cli.Command) { collectCmd := &cli.Command{ Use: "collect", diff --git a/cmd/collect/cmd_bitcointalk.go b/cmd/collect/cmd_bitcointalk.go index 0bab644..96a8886 100644 --- a/cmd/collect/cmd_bitcointalk.go +++ b/cmd/collect/cmd_bitcointalk.go @@ -2,11 +2,11 @@ package collect import ( "context" - "strings" + strings "dappco.re/go/core/scm/internal/ax/stringsx" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // BitcoinTalk command flags diff --git a/cmd/collect/cmd_dispatch.go b/cmd/collect/cmd_dispatch.go index 9467e7f..8c91e50 100644 --- a/cmd/collect/cmd_dispatch.go +++ b/cmd/collect/cmd_dispatch.go @@ -1,12 +1,12 @@ package collect import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" - "forge.lthn.ai/core/cli/pkg/cli" - collectpkg "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + collectpkg "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // addDispatchCommand adds the 'dispatch' subcommand to the collect parent. diff --git a/cmd/collect/cmd_excavate.go b/cmd/collect/cmd_excavate.go index a9bf038..0b4e1a0 100644 --- a/cmd/collect/cmd_excavate.go +++ b/cmd/collect/cmd_excavate.go @@ -2,11 +2,11 @@ package collect import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // Excavate command flags diff --git a/cmd/collect/cmd_github.go b/cmd/collect/cmd_github.go index 22c1f7d..738fdc5 100644 --- a/cmd/collect/cmd_github.go +++ b/cmd/collect/cmd_github.go @@ -2,11 +2,11 @@ package collect import ( "context" - "strings" + strings "dappco.re/go/core/scm/internal/ax/stringsx" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // GitHub command flags diff --git a/cmd/collect/cmd_market.go b/cmd/collect/cmd_market.go index 9a8fd47..b253cf7 100644 --- a/cmd/collect/cmd_market.go +++ b/cmd/collect/cmd_market.go @@ -3,9 +3,9 @@ package collect import ( "context" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // Market command flags diff --git a/cmd/collect/cmd_papers.go b/cmd/collect/cmd_papers.go index ef270ba..93f9c81 100644 --- a/cmd/collect/cmd_papers.go +++ b/cmd/collect/cmd_papers.go @@ -3,9 +3,9 @@ package collect import ( "context" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // Papers command flags diff --git a/cmd/collect/cmd_process.go b/cmd/collect/cmd_process.go index 8ac116f..2a0d0c3 100644 --- a/cmd/collect/cmd_process.go +++ b/cmd/collect/cmd_process.go @@ -3,9 +3,9 @@ package collect import ( "context" - "forge.lthn.ai/core/cli/pkg/cli" - "dappco.re/go/core/scm/collect" "dappco.re/go/core/i18n" + "dappco.re/go/core/scm/collect" + "forge.lthn.ai/core/cli/pkg/cli" ) // addProcessCommand adds the 'process' subcommand to the collect parent. diff --git a/cmd/forge/cmd_auth.go b/cmd/forge/cmd_auth.go index a707bf5..5cd8abe 100644 --- a/cmd/forge/cmd_auth.go +++ b/cmd/forge/cmd_auth.go @@ -1,10 +1,10 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Auth command flags. diff --git a/cmd/forge/cmd_config.go b/cmd/forge/cmd_config.go index 94c5679..94aa984 100644 --- a/cmd/forge/cmd_config.go +++ b/cmd/forge/cmd_config.go @@ -1,10 +1,10 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Config command flags. diff --git a/cmd/forge/cmd_forge.go b/cmd/forge/cmd_forge.go index 65e0440..537501a 100644 --- a/cmd/forge/cmd_forge.go +++ b/cmd/forge/cmd_forge.go @@ -33,6 +33,7 @@ var ( ) // AddForgeCommands registers the 'forge' command and all subcommands. +// Usage example: AddForgeCommands(...) func AddForgeCommands(root *cli.Command) { forgeCmd := &cli.Command{ Use: "forge", diff --git a/cmd/forge/cmd_issues.go b/cmd/forge/cmd_issues.go index 108d237..47c23d0 100644 --- a/cmd/forge/cmd_issues.go +++ b/cmd/forge/cmd_issues.go @@ -1,13 +1,13 @@ package forge import ( - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Issues command flags. diff --git a/cmd/forge/cmd_labels.go b/cmd/forge/cmd_labels.go index 745ab61..e092d09 100644 --- a/cmd/forge/cmd_labels.go +++ b/cmd/forge/cmd_labels.go @@ -1,12 +1,12 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Labels command flags. diff --git a/cmd/forge/cmd_migrate.go b/cmd/forge/cmd_migrate.go index 8f8a8bb..7d598f9 100644 --- a/cmd/forge/cmd_migrate.go +++ b/cmd/forge/cmd_migrate.go @@ -1,12 +1,12 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Migrate command flags. diff --git a/cmd/forge/cmd_orgs.go b/cmd/forge/cmd_orgs.go index 27389a3..d41fcf7 100644 --- a/cmd/forge/cmd_orgs.go +++ b/cmd/forge/cmd_orgs.go @@ -1,10 +1,10 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // addOrgsCommand adds the 'orgs' subcommand for listing organisations. diff --git a/cmd/forge/cmd_prs.go b/cmd/forge/cmd_prs.go index e32db77..26c64a8 100644 --- a/cmd/forge/cmd_prs.go +++ b/cmd/forge/cmd_prs.go @@ -1,13 +1,13 @@ package forge import ( - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // PRs command flags. diff --git a/cmd/forge/cmd_repos.go b/cmd/forge/cmd_repos.go index 71cb2a9..cd5fbb5 100644 --- a/cmd/forge/cmd_repos.go +++ b/cmd/forge/cmd_repos.go @@ -1,12 +1,12 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // Repos command flags. diff --git a/cmd/forge/cmd_status.go b/cmd/forge/cmd_status.go index 4777894..8a6083e 100644 --- a/cmd/forge/cmd_status.go +++ b/cmd/forge/cmd_status.go @@ -1,10 +1,10 @@ package forge import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" fg "dappco.re/go/core/scm/forge" + "forge.lthn.ai/core/cli/pkg/cli" ) // addStatusCommand adds the 'status' subcommand for instance info. diff --git a/cmd/forge/cmd_sync.go b/cmd/forge/cmd_sync.go index 7a4176c..55e3149 100644 --- a/cmd/forge/cmd_sync.go +++ b/cmd/forge/cmd_sync.go @@ -1,12 +1,12 @@ package forge import ( - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "net/url" - "os" - "os/exec" - "path/filepath" - "strings" coreerr "dappco.re/go/core/log" "dappco.re/go/core/scm/agentci" diff --git a/cmd/forge/cmd_sync_test.go b/cmd/forge/cmd_sync_test.go index c75d74b..d079c7c 100644 --- a/cmd/forge/cmd_sync_test.go +++ b/cmd/forge/cmd_sync_test.go @@ -1,7 +1,7 @@ package forge import ( - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" "testing" "github.com/stretchr/testify/assert" diff --git a/cmd/forge/helpers.go b/cmd/forge/helpers.go index eec2d68..ca0e280 100644 --- a/cmd/forge/helpers.go +++ b/cmd/forge/helpers.go @@ -1,8 +1,8 @@ package forge import ( + strings "dappco.re/go/core/scm/internal/ax/stringsx" "path" - "strings" "forge.lthn.ai/core/cli/pkg/cli" ) diff --git a/cmd/gitea/cmd_config.go b/cmd/gitea/cmd_config.go index 69f07f3..86b8b73 100644 --- a/cmd/gitea/cmd_config.go +++ b/cmd/gitea/cmd_config.go @@ -1,10 +1,10 @@ package gitea import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" gt "dappco.re/go/core/scm/gitea" + "forge.lthn.ai/core/cli/pkg/cli" ) // Config command flags. diff --git a/cmd/gitea/cmd_gitea.go b/cmd/gitea/cmd_gitea.go index 9268653..46c0096 100644 --- a/cmd/gitea/cmd_gitea.go +++ b/cmd/gitea/cmd_gitea.go @@ -30,6 +30,7 @@ var ( ) // AddGiteaCommands registers the 'gitea' command and all subcommands. +// Usage example: AddGiteaCommands(...) func AddGiteaCommands(root *cli.Command) { giteaCmd := &cli.Command{ Use: "gitea", diff --git a/cmd/gitea/cmd_issues.go b/cmd/gitea/cmd_issues.go index a4fd5e2..c723ae3 100644 --- a/cmd/gitea/cmd_issues.go +++ b/cmd/gitea/cmd_issues.go @@ -1,13 +1,13 @@ package gitea import ( - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "code.gitea.io/sdk/gitea" - "forge.lthn.ai/core/cli/pkg/cli" gt "dappco.re/go/core/scm/gitea" + "forge.lthn.ai/core/cli/pkg/cli" ) // Issues command flags. diff --git a/cmd/gitea/cmd_mirror.go b/cmd/gitea/cmd_mirror.go index e108686..32f1732 100644 --- a/cmd/gitea/cmd_mirror.go +++ b/cmd/gitea/cmd_mirror.go @@ -1,12 +1,12 @@ package gitea import ( - "fmt" - "os/exec" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" - "forge.lthn.ai/core/cli/pkg/cli" gt "dappco.re/go/core/scm/gitea" + "forge.lthn.ai/core/cli/pkg/cli" ) // Mirror command flags. diff --git a/cmd/gitea/cmd_prs.go b/cmd/gitea/cmd_prs.go index 00e059e..263c12b 100644 --- a/cmd/gitea/cmd_prs.go +++ b/cmd/gitea/cmd_prs.go @@ -1,13 +1,13 @@ package gitea import ( - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" sdk "code.gitea.io/sdk/gitea" - "forge.lthn.ai/core/cli/pkg/cli" gt "dappco.re/go/core/scm/gitea" + "forge.lthn.ai/core/cli/pkg/cli" ) // PRs command flags. diff --git a/cmd/gitea/cmd_repos.go b/cmd/gitea/cmd_repos.go index 892bdfe..1983885 100644 --- a/cmd/gitea/cmd_repos.go +++ b/cmd/gitea/cmd_repos.go @@ -1,10 +1,10 @@ package gitea import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" gt "dappco.re/go/core/scm/gitea" + "forge.lthn.ai/core/cli/pkg/cli" ) // Repos command flags. diff --git a/cmd/gitea/cmd_sync.go b/cmd/gitea/cmd_sync.go index de14da3..d8fb718 100644 --- a/cmd/gitea/cmd_sync.go +++ b/cmd/gitea/cmd_sync.go @@ -1,12 +1,12 @@ package gitea import ( - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "net/url" - "os" - "os/exec" - "path/filepath" - "strings" coreerr "dappco.re/go/core/log" "dappco.re/go/core/scm/agentci" diff --git a/cmd/gitea/cmd_sync_test.go b/cmd/gitea/cmd_sync_test.go index e21e712..a8269bc 100644 --- a/cmd/gitea/cmd_sync_test.go +++ b/cmd/gitea/cmd_sync_test.go @@ -1,7 +1,7 @@ package gitea import ( - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" "testing" "github.com/stretchr/testify/assert" diff --git a/cmd/scm/cmd_compile.go b/cmd/scm/cmd_compile.go index e5cce30..ba82951 100644 --- a/cmd/scm/cmd_compile.go +++ b/cmd/scm/cmd_compile.go @@ -2,13 +2,13 @@ package scm import ( "crypto/ed25519" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "encoding/hex" - "os/exec" - "strings" + exec "golang.org/x/sys/execabs" - "forge.lthn.ai/core/cli/pkg/cli" "dappco.re/go/core/io" "dappco.re/go/core/scm/manifest" + "forge.lthn.ai/core/cli/pkg/cli" ) func addCompileCommand(parent *cli.Command) { diff --git a/cmd/scm/cmd_export.go b/cmd/scm/cmd_export.go index 79da671..c64c2d1 100644 --- a/cmd/scm/cmd_export.go +++ b/cmd/scm/cmd_export.go @@ -1,11 +1,11 @@ package scm import ( - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" "dappco.re/go/core/io" "dappco.re/go/core/scm/manifest" + "forge.lthn.ai/core/cli/pkg/cli" ) func addExportCommand(parent *cli.Command) { diff --git a/cmd/scm/cmd_index.go b/cmd/scm/cmd_index.go index dd8784b..276e32e 100644 --- a/cmd/scm/cmd_index.go +++ b/cmd/scm/cmd_index.go @@ -1,11 +1,11 @@ package scm import ( - "fmt" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - "forge.lthn.ai/core/cli/pkg/cli" "dappco.re/go/core/scm/marketplace" + "forge.lthn.ai/core/cli/pkg/cli" ) func addIndexCommand(parent *cli.Command) { diff --git a/cmd/scm/cmd_scm.go b/cmd/scm/cmd_scm.go index 4a7ad1b..5cbd90f 100644 --- a/cmd/scm/cmd_scm.go +++ b/cmd/scm/cmd_scm.go @@ -25,6 +25,7 @@ var ( ) // AddScmCommands registers the 'scm' command and all subcommands. +// Usage example: AddScmCommands(...) func AddScmCommands(root *cli.Command) { scmCmd := &cli.Command{ Use: "scm", diff --git a/collect/bitcointalk.go b/collect/bitcointalk.go index 8c189d6..0c49007 100644 --- a/collect/bitcointalk.go +++ b/collect/bitcointalk.go @@ -2,11 +2,11 @@ package collect import ( "context" - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "iter" "net/http" - "path/filepath" - "strings" "time" core "dappco.re/go/core/log" @@ -20,6 +20,7 @@ var httpClient = &http.Client{ } // BitcoinTalkCollector collects forum posts from BitcoinTalk. +// Usage example: var value BitcoinTalkCollector type BitcoinTalkCollector struct { // TopicID is the numeric topic identifier. TopicID string @@ -281,6 +282,7 @@ func formatPostMarkdown(num int, post btPost) string { // ParsePostsFromHTML parses BitcoinTalk posts from raw HTML content. // This is exported for testing purposes. +// Usage example: ParsePostsFromHTML(...) func ParsePostsFromHTML(htmlContent string) ([]btPost, error) { doc, err := html.Parse(strings.NewReader(htmlContent)) if err != nil { @@ -290,14 +292,17 @@ func ParsePostsFromHTML(htmlContent string) ([]btPost, error) { } // FormatPostMarkdown is exported for testing purposes. +// Usage example: FormatPostMarkdown(...) func FormatPostMarkdown(num int, author, date, content string) string { return formatPostMarkdown(num, btPost{Author: author, Date: date, Content: content}) } // FetchPageFunc is an injectable function type for fetching pages, used in testing. +// Usage example: var value FetchPageFunc type FetchPageFunc func(ctx context.Context, url string) ([]btPost, error) // BitcoinTalkCollectorWithFetcher wraps BitcoinTalkCollector with a custom fetcher for testing. +// Usage example: var value BitcoinTalkCollectorWithFetcher type BitcoinTalkCollectorWithFetcher struct { BitcoinTalkCollector Fetcher FetchPageFunc @@ -305,6 +310,7 @@ type BitcoinTalkCollectorWithFetcher struct { // SetHTTPClient replaces the package-level HTTP client. // Use this in tests to inject a custom transport or timeout. +// Usage example: SetHTTPClient(...) func SetHTTPClient(c *http.Client) { httpClient = c } diff --git a/collect/bitcointalk_http_test.go b/collect/bitcointalk_http_test.go index 61a7a08..6b202cd 100644 --- a/collect/bitcointalk_http_test.go +++ b/collect/bitcointalk_http_test.go @@ -2,10 +2,10 @@ package collect import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" "dappco.re/go/core/io" diff --git a/collect/collect.go b/collect/collect.go index d1acd04..8c28d78 100644 --- a/collect/collect.go +++ b/collect/collect.go @@ -6,12 +6,13 @@ package collect import ( "context" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" "dappco.re/go/core/io" ) // Collector is the interface all collection sources implement. +// Usage example: var value Collector type Collector interface { // Name returns a human-readable name for this collector. Name() string @@ -21,6 +22,7 @@ type Collector interface { } // Config holds shared configuration for all collectors. +// Usage example: var value Config type Config struct { // Output is the storage medium for writing collected data. Output io.Medium @@ -45,6 +47,7 @@ type Config struct { } // Result holds the output of a collection run. +// Usage example: var value Result type Result struct { // Source identifies which collector produced this result. Source string @@ -65,6 +68,7 @@ type Result struct { // NewConfig creates a Config with sensible defaults. // It initialises a MockMedium for output if none is provided, // sets up a rate limiter, state tracker, and event dispatcher. +// Usage example: NewConfig(...) func NewConfig(outputDir string) *Config { m := io.NewMockMedium() return &Config{ @@ -77,6 +81,7 @@ func NewConfig(outputDir string) *Config { } // NewConfigWithMedium creates a Config using the specified storage medium. +// Usage example: NewConfigWithMedium(...) func NewConfigWithMedium(m io.Medium, outputDir string) *Config { return &Config{ Output: m, @@ -88,6 +93,7 @@ func NewConfigWithMedium(m io.Medium, outputDir string) *Config { } // MergeResults combines multiple results into a single aggregated result. +// Usage example: MergeResults(...) func MergeResults(source string, results ...*Result) *Result { merged := &Result{Source: source} for _, r := range results { diff --git a/collect/coverage_boost_test.go b/collect/coverage_boost_test.go index 70bae86..1e73324 100644 --- a/collect/coverage_boost_test.go +++ b/collect/coverage_boost_test.go @@ -2,7 +2,7 @@ package collect import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/collect/coverage_phase2_test.go b/collect/coverage_phase2_test.go index 68b5469..b55db04 100644 --- a/collect/coverage_phase2_test.go +++ b/collect/coverage_phase2_test.go @@ -2,13 +2,14 @@ package collect import ( "context" - "encoding/json" - "fmt" + core "dappco.re/go/core" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" goio "io" "io/fs" "net/http" "net/http/httptest" - "strings" "testing" "time" @@ -17,6 +18,14 @@ import ( "github.com/stretchr/testify/require" ) +func testErr(msg string) error { + return core.E("collect.test", msg, nil) +} + +func testErrf(format string, args ...any) error { + return core.E("collect.test", fmt.Sprintf(format, args...), nil) +} + // errorMedium wraps MockMedium and injects errors on specific operations. type errorMedium struct { *io.MockMedium @@ -50,16 +59,18 @@ func (e *errorMedium) Read(path string) (string, error) { } return e.MockMedium.Read(path) } -func (e *errorMedium) FileGet(path string) (string, error) { return e.MockMedium.FileGet(path) } -func (e *errorMedium) FileSet(path, content string) error { return e.MockMedium.FileSet(path, content) } -func (e *errorMedium) Delete(path string) error { return e.MockMedium.Delete(path) } -func (e *errorMedium) DeleteAll(path string) error { return e.MockMedium.DeleteAll(path) } -func (e *errorMedium) Rename(old, new string) error { return e.MockMedium.Rename(old, new) } -func (e *errorMedium) Stat(path string) (fs.FileInfo, error) { return e.MockMedium.Stat(path) } -func (e *errorMedium) Open(path string) (fs.File, error) { return e.MockMedium.Open(path) } -func (e *errorMedium) Create(path string) (goio.WriteCloser, error) { return e.MockMedium.Create(path) } -func (e *errorMedium) Append(path string) (goio.WriteCloser, error) { return e.MockMedium.Append(path) } -func (e *errorMedium) ReadStream(path string) (goio.ReadCloser, error) { return e.MockMedium.ReadStream(path) } +func (e *errorMedium) FileGet(path string) (string, error) { return e.MockMedium.FileGet(path) } +func (e *errorMedium) FileSet(path, content string) error { return e.MockMedium.FileSet(path, content) } +func (e *errorMedium) Delete(path string) error { return e.MockMedium.Delete(path) } +func (e *errorMedium) DeleteAll(path string) error { return e.MockMedium.DeleteAll(path) } +func (e *errorMedium) Rename(old, new string) error { return e.MockMedium.Rename(old, new) } +func (e *errorMedium) Stat(path string) (fs.FileInfo, error) { return e.MockMedium.Stat(path) } +func (e *errorMedium) Open(path string) (fs.File, error) { return e.MockMedium.Open(path) } +func (e *errorMedium) Create(path string) (goio.WriteCloser, error) { return e.MockMedium.Create(path) } +func (e *errorMedium) Append(path string) (goio.WriteCloser, error) { return e.MockMedium.Append(path) } +func (e *errorMedium) ReadStream(path string) (goio.ReadCloser, error) { + return e.MockMedium.ReadStream(path) +} func (e *errorMedium) WriteStream(path string) (goio.WriteCloser, error) { return e.MockMedium.WriteStream(path) } @@ -74,7 +85,7 @@ type errorLimiterWaiter struct{} // --- Processor: list error --- func TestProcessor_Process_Bad_ListError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), listErr: fmt.Errorf("list denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), listErr: testErr("list denied")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} p := &Processor{Source: "test", Dir: "/input"} @@ -86,7 +97,7 @@ func TestProcessor_Process_Bad_ListError(t *testing.T) { // --- Processor: ensureDir error --- func TestProcessor_Process_Bad_EnsureDirError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: fmt.Errorf("mkdir denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")} // Need to ensure List returns entries em.MockMedium.Dirs["/input"] = true em.MockMedium.Files["/input/test.html"] = "

Test

" @@ -121,7 +132,7 @@ func TestProcessor_Process_Bad_ContextCancelledDuringLoop(t *testing.T) { // --- Processor: read error during file processing --- func TestProcessor_Process_Bad_ReadError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: fmt.Errorf("read denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")} em.MockMedium.Dirs["/input"] = true em.MockMedium.Files["/input/test.html"] = "

Test

" @@ -154,7 +165,7 @@ func TestProcessor_Process_Bad_InvalidJSONFile(t *testing.T) { // --- Processor: write error during output --- func TestProcessor_Process_Bad_WriteError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} em.MockMedium.Dirs["/input"] = true em.MockMedium.Files["/input/page.html"] = "

Title

" @@ -255,13 +266,13 @@ func TestPapersCollector_CollectIACR_Bad_WriteError(t *testing.T) { httpClient = &http.Client{Transport: transport} defer func() { httpClient = old }() - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil p := &PapersCollector{Source: PaperSourceIACR, Query: "test"} result, err := p.Collect(context.Background(), cfg) - require.NoError(t, err) // Write errors increment Errors, not returned + require.NoError(t, err) // Write errors increment Errors, not returned assert.Equal(t, 2, result.Errors) // 2 papers both fail to write } @@ -279,7 +290,7 @@ func TestPapersCollector_CollectIACR_Bad_EnsureDirError(t *testing.T) { httpClient = &http.Client{Transport: transport} defer func() { httpClient = old }() - em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: fmt.Errorf("mkdir denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -303,7 +314,7 @@ func TestPapersCollector_CollectArXiv_Bad_WriteError(t *testing.T) { httpClient = &http.Client{Transport: transport} defer func() { httpClient = old }() - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -327,7 +338,7 @@ func TestPapersCollector_CollectArXiv_Bad_EnsureDirError(t *testing.T) { httpClient = &http.Client{Transport: transport} defer func() { httpClient = old }() - em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: fmt.Errorf("mkdir denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -453,7 +464,7 @@ func TestMarketCollector_Collect_Bad_WriteError(t *testing.T) { coinGeckoBaseURL = server.URL defer func() { coinGeckoBaseURL = oldURL }() - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -477,7 +488,7 @@ func TestMarketCollector_Collect_Bad_EnsureDirError(t *testing.T) { coinGeckoBaseURL = server.URL defer func() { coinGeckoBaseURL = oldURL }() - em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: fmt.Errorf("mkdir denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -552,7 +563,7 @@ func TestMarketCollector_Collect_Good_HistoricalCustomDate(t *testing.T) { // --- BitcoinTalk: EnsureDir error --- func TestBitcoinTalkCollector_Collect_Bad_EnsureDirError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: fmt.Errorf("mkdir denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), ensureDirErr: testErr("mkdir denied")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil @@ -592,13 +603,13 @@ func TestBitcoinTalkCollector_Collect_Bad_WriteErrorOnPosts(t *testing.T) { httpClient = &http.Client{Transport: transport} defer func() { httpClient = old }() - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} cfg := &Config{Output: em, OutputDir: "/output", Dispatcher: NewDispatcher()} cfg.Limiter = nil b := &BitcoinTalkCollector{TopicID: "12345"} result, err := b.Collect(context.Background(), cfg) - require.NoError(t, err) // write errors are counted + require.NoError(t, err) // write errors are counted assert.Equal(t, 3, result.Errors) // 3 posts all fail to write assert.Equal(t, 0, result.Items) } @@ -968,34 +979,44 @@ func TestBitcoinTalkCollector_Collect_Bad_LimiterBlocks(t *testing.T) { // writeCountMedium fails after N successful writes. type writeCountMedium struct { *io.MockMedium - writeCount int - failAfterN int + writeCount int + failAfterN int } func (w *writeCountMedium) Write(path, content string) error { w.writeCount++ if w.writeCount > w.failAfterN { - return fmt.Errorf("write %d: disk full", w.writeCount) + return testErrf("write %d: disk full", w.writeCount) } return w.MockMedium.Write(path, content) } -func (w *writeCountMedium) EnsureDir(path string) error { return w.MockMedium.EnsureDir(path) } -func (w *writeCountMedium) Read(path string) (string, error) { return w.MockMedium.Read(path) } -func (w *writeCountMedium) List(path string) ([]fs.DirEntry, error) { return w.MockMedium.List(path) } -func (w *writeCountMedium) IsFile(path string) bool { return w.MockMedium.IsFile(path) } -func (w *writeCountMedium) FileGet(path string) (string, error) { return w.MockMedium.FileGet(path) } -func (w *writeCountMedium) FileSet(path, content string) error { return w.MockMedium.FileSet(path, content) } -func (w *writeCountMedium) Delete(path string) error { return w.MockMedium.Delete(path) } -func (w *writeCountMedium) DeleteAll(path string) error { return w.MockMedium.DeleteAll(path) } -func (w *writeCountMedium) Rename(old, new string) error { return w.MockMedium.Rename(old, new) } -func (w *writeCountMedium) Stat(path string) (fs.FileInfo, error) { return w.MockMedium.Stat(path) } -func (w *writeCountMedium) Open(path string) (fs.File, error) { return w.MockMedium.Open(path) } -func (w *writeCountMedium) Create(path string) (goio.WriteCloser, error) { return w.MockMedium.Create(path) } -func (w *writeCountMedium) Append(path string) (goio.WriteCloser, error) { return w.MockMedium.Append(path) } -func (w *writeCountMedium) ReadStream(path string) (goio.ReadCloser, error) { return w.MockMedium.ReadStream(path) } -func (w *writeCountMedium) WriteStream(path string) (goio.WriteCloser, error) { return w.MockMedium.WriteStream(path) } -func (w *writeCountMedium) Exists(path string) bool { return w.MockMedium.Exists(path) } -func (w *writeCountMedium) IsDir(path string) bool { return w.MockMedium.IsDir(path) } +func (w *writeCountMedium) EnsureDir(path string) error { return w.MockMedium.EnsureDir(path) } +func (w *writeCountMedium) Read(path string) (string, error) { return w.MockMedium.Read(path) } +func (w *writeCountMedium) List(path string) ([]fs.DirEntry, error) { return w.MockMedium.List(path) } +func (w *writeCountMedium) IsFile(path string) bool { return w.MockMedium.IsFile(path) } +func (w *writeCountMedium) FileGet(path string) (string, error) { return w.MockMedium.FileGet(path) } +func (w *writeCountMedium) FileSet(path, content string) error { + return w.MockMedium.FileSet(path, content) +} +func (w *writeCountMedium) Delete(path string) error { return w.MockMedium.Delete(path) } +func (w *writeCountMedium) DeleteAll(path string) error { return w.MockMedium.DeleteAll(path) } +func (w *writeCountMedium) Rename(old, new string) error { return w.MockMedium.Rename(old, new) } +func (w *writeCountMedium) Stat(path string) (fs.FileInfo, error) { return w.MockMedium.Stat(path) } +func (w *writeCountMedium) Open(path string) (fs.File, error) { return w.MockMedium.Open(path) } +func (w *writeCountMedium) Create(path string) (goio.WriteCloser, error) { + return w.MockMedium.Create(path) +} +func (w *writeCountMedium) Append(path string) (goio.WriteCloser, error) { + return w.MockMedium.Append(path) +} +func (w *writeCountMedium) ReadStream(path string) (goio.ReadCloser, error) { + return w.MockMedium.ReadStream(path) +} +func (w *writeCountMedium) WriteStream(path string) (goio.WriteCloser, error) { + return w.MockMedium.WriteStream(path) +} +func (w *writeCountMedium) Exists(path string) bool { return w.MockMedium.Exists(path) } +func (w *writeCountMedium) IsDir(path string) bool { return w.MockMedium.IsDir(path) } // Test that the summary.md write error in collectCurrent is handled. func TestMarketCollector_Collect_Bad_SummaryWriteError(t *testing.T) { @@ -1075,7 +1096,7 @@ func TestMarketCollector_Collect_Bad_HistoricalWriteError(t *testing.T) { // --- State: Save write error --- func TestState_Save_Bad_WriteError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("disk full")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("disk full")} s := NewState(em, "/state.json") s.Set("test", &StateEntry{Source: "test", Items: 1}) @@ -1134,7 +1155,7 @@ func TestBitcoinTalkCollector_Collect_Good_ZeroPostsPage(t *testing.T) { // --- Excavator: state save error after collection --- func TestExcavator_Run_Bad_StateSaveError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: fmt.Errorf("state write failed")} + em := &errorMedium{MockMedium: io.NewMockMedium(), writeErr: testErr("state write failed")} cfg := &Config{ Output: io.NewMockMedium(), // Use regular medium for output OutputDir: "/output", @@ -1158,7 +1179,7 @@ func TestExcavator_Run_Bad_StateSaveError(t *testing.T) { // --- State: Load with read error --- func TestState_Load_Bad_ReadError(t *testing.T) { - em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: fmt.Errorf("read denied")} + em := &errorMedium{MockMedium: io.NewMockMedium(), readErr: testErr("read denied")} em.MockMedium.Files["/state.json"] = "{}" // File exists but read will fail s := NewState(em, "/state.json") diff --git a/collect/events.go b/collect/events.go index 7083986..aa2e0a8 100644 --- a/collect/events.go +++ b/collect/events.go @@ -8,22 +8,28 @@ import ( // Event types used by the collection subsystem. const ( // EventStart is emitted when a collector begins its run. + // Usage example: _ = EventStart EventStart = "start" // EventProgress is emitted to report incremental progress. + // Usage example: _ = EventProgress EventProgress = "progress" // EventItem is emitted when a single item is collected. + // Usage example: _ = EventItem EventItem = "item" // EventError is emitted when an error occurs during collection. + // Usage example: _ = EventError EventError = "error" // EventComplete is emitted when a collector finishes its run. + // Usage example: _ = EventComplete EventComplete = "complete" ) // Event represents a collection event. +// Usage example: var value Event type Event struct { // Type is one of the Event* constants. Type string `json:"type"` @@ -42,16 +48,19 @@ type Event struct { } // EventHandler handles collection events. +// Usage example: var value EventHandler type EventHandler func(Event) // Dispatcher manages event dispatch. Handlers are registered per event type // and are called synchronously when an event is emitted. +// Usage example: var value Dispatcher type Dispatcher struct { mu sync.RWMutex handlers map[string][]EventHandler } // NewDispatcher creates a new event dispatcher. +// Usage example: NewDispatcher(...) func NewDispatcher() *Dispatcher { return &Dispatcher{ handlers: make(map[string][]EventHandler), diff --git a/collect/excavate.go b/collect/excavate.go index e491ba3..2bc4224 100644 --- a/collect/excavate.go +++ b/collect/excavate.go @@ -2,7 +2,7 @@ package collect import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" core "dappco.re/go/core/log" @@ -11,6 +11,7 @@ import ( // Excavator runs multiple collectors as a coordinated operation. // It provides sequential execution with rate limit respect, state tracking // for resume support, and aggregated results. +// Usage example: var value Excavator type Excavator struct { // Collectors is the list of collectors to run. Collectors []Collector diff --git a/collect/excavate_test.go b/collect/excavate_test.go index 0bebb30..8c556ea 100644 --- a/collect/excavate_test.go +++ b/collect/excavate_test.go @@ -2,7 +2,8 @@ package collect import ( "context" - "fmt" + core "dappco.re/go/core" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "testing" "dappco.re/go/core/io" @@ -126,7 +127,7 @@ func TestExcavator_Run_Good_WithErrors(t *testing.T) { cfg.Limiter = nil c1 := &mockCollector{name: "good", items: 5} - c2 := &mockCollector{name: "bad", err: fmt.Errorf("network error")} + c2 := &mockCollector{name: "bad", err: core.E("collect.mockCollector.Collect", "network error", nil)} c3 := &mockCollector{name: "also-good", items: 3} e := &Excavator{ diff --git a/collect/github.go b/collect/github.go index cad8fa7..21084f7 100644 --- a/collect/github.go +++ b/collect/github.go @@ -2,11 +2,11 @@ package collect import ( "context" - "encoding/json" - "fmt" - "os/exec" - "path/filepath" - "strings" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "time" core "dappco.re/go/core/log" @@ -38,6 +38,7 @@ type ghRepo struct { } // GitHubCollector collects issues and PRs from GitHub repositories. +// Usage example: var value GitHubCollector type GitHubCollector struct { // Org is the GitHub organisation. Org string diff --git a/collect/market.go b/collect/market.go index e38e162..e59262b 100644 --- a/collect/market.go +++ b/collect/market.go @@ -2,11 +2,11 @@ package collect import ( "context" - "encoding/json" - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" - "path/filepath" - "strings" "time" core "dappco.re/go/core/log" @@ -17,6 +17,7 @@ import ( var coinGeckoBaseURL = "https://api.coingecko.com/api/v3" // MarketCollector collects market data from CoinGecko. +// Usage example: var value MarketCollector type MarketCollector struct { // CoinID is the CoinGecko coin identifier (e.g. "bitcoin", "ethereum"). CoinID string @@ -272,6 +273,7 @@ func formatMarketSummary(data *coinData) string { } // FormatMarketSummary is exported for testing. +// Usage example: FormatMarketSummary(...) func FormatMarketSummary(data *coinData) string { return formatMarketSummary(data) } diff --git a/collect/market_extra_test.go b/collect/market_extra_test.go index bbbcac2..d25dc2d 100644 --- a/collect/market_extra_test.go +++ b/collect/market_extra_test.go @@ -2,7 +2,7 @@ package collect import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/collect/market_test.go b/collect/market_test.go index c945a5f..3d9ca6e 100644 --- a/collect/market_test.go +++ b/collect/market_test.go @@ -2,7 +2,7 @@ package collect import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/collect/papers.go b/collect/papers.go index bfbf663..6ebb89c 100644 --- a/collect/papers.go +++ b/collect/papers.go @@ -2,13 +2,13 @@ package collect import ( "context" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "encoding/xml" - "fmt" "iter" "net/http" "net/url" - "path/filepath" - "strings" core "dappco.re/go/core/log" "golang.org/x/net/html" @@ -16,12 +16,16 @@ import ( // Paper source identifiers. const ( - PaperSourceIACR = "iacr" + // Usage example: _ = PaperSourceIACR + PaperSourceIACR = "iacr" + // Usage example: _ = PaperSourceArXiv PaperSourceArXiv = "arxiv" - PaperSourceAll = "all" + // Usage example: _ = PaperSourceAll + PaperSourceAll = "all" ) // PapersCollector collects papers from IACR and arXiv. +// Usage example: var value PapersCollector type PapersCollector struct { // Source is one of PaperSourceIACR, PaperSourceArXiv, or PaperSourceAll. Source string @@ -403,6 +407,7 @@ func formatPaperMarkdown(ppr paper) string { } // FormatPaperMarkdown is exported for testing. +// Usage example: FormatPaperMarkdown(...) func FormatPaperMarkdown(title string, authors []string, date, paperURL, source, abstract string) string { return formatPaperMarkdown(paper{ Title: title, diff --git a/collect/papers_http_test.go b/collect/papers_http_test.go index b755413..0eb2a9b 100644 --- a/collect/papers_http_test.go +++ b/collect/papers_http_test.go @@ -2,9 +2,9 @@ package collect import ( "context" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" "dappco.re/go/core/io" diff --git a/collect/process.go b/collect/process.go index c0fb8d2..f5d7033 100644 --- a/collect/process.go +++ b/collect/process.go @@ -2,18 +2,19 @@ package collect import ( "context" - "encoding/json" - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "maps" - "path/filepath" "slices" - "strings" core "dappco.re/go/core/log" "golang.org/x/net/html" ) // Processor converts collected data to clean markdown. +// Usage example: var value Processor type Processor struct { // Source identifies the data source directory to process. Source string @@ -331,11 +332,13 @@ func jsonValueToMarkdown(b *strings.Builder, data any, depth int) { } // HTMLToMarkdown is exported for testing. +// Usage example: HTMLToMarkdown(...) func HTMLToMarkdown(content string) (string, error) { return htmlToMarkdown(content) } // JSONToMarkdown is exported for testing. +// Usage example: JSONToMarkdown(...) func JSONToMarkdown(content string) (string, error) { return jsonToMarkdown(content) } diff --git a/collect/ratelimit.go b/collect/ratelimit.go index 5fc4969..9c75a04 100644 --- a/collect/ratelimit.go +++ b/collect/ratelimit.go @@ -2,11 +2,11 @@ package collect import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "maps" - "os/exec" "strconv" - "strings" "sync" "time" @@ -14,6 +14,7 @@ import ( ) // RateLimiter tracks per-source rate limiting to avoid overwhelming APIs. +// Usage example: var value RateLimiter type RateLimiter struct { mu sync.Mutex delays map[string]time.Duration @@ -30,6 +31,7 @@ var defaultDelays = map[string]time.Duration{ } // NewRateLimiter creates a limiter with default delays. +// Usage example: NewRateLimiter(...) func NewRateLimiter() *RateLimiter { delays := make(map[string]time.Duration, len(defaultDelays)) maps.Copy(delays, defaultDelays) diff --git a/collect/state.go b/collect/state.go index 08e2b95..aa98c28 100644 --- a/collect/state.go +++ b/collect/state.go @@ -1,17 +1,18 @@ package collect import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "sync" "time" - core "dappco.re/go/core/log" "dappco.re/go/core/io" + core "dappco.re/go/core/log" ) // State tracks collection progress for incremental runs. // It persists entries to disk so that subsequent runs can resume // where they left off. +// Usage example: var value State type State struct { mu sync.Mutex medium io.Medium @@ -20,6 +21,7 @@ type State struct { } // StateEntry tracks state for one source. +// Usage example: var value StateEntry type StateEntry struct { // Source identifies the collector. Source string `json:"source"` @@ -39,6 +41,7 @@ type StateEntry struct { // NewState creates a state tracker that persists to the given path // using the provided storage medium. +// Usage example: NewState(...) func NewState(m io.Medium, path string) *State { return &State{ medium: m, diff --git a/forge/client.go b/forge/client.go index 2ee2bb9..3004eb5 100644 --- a/forge/client.go +++ b/forge/client.go @@ -15,6 +15,7 @@ import ( ) // Client wraps the Forgejo SDK client with config-based auth. +// Usage example: var value Client type Client struct { api *forgejo.Client url string @@ -22,6 +23,7 @@ type Client struct { } // New creates a new Forgejo API client for the given URL and token. +// Usage example: New(...) func New(url, token string) (*Client, error) { api, err := forgejo.NewClient(url, forgejo.SetToken(token)) if err != nil { diff --git a/forge/client_test.go b/forge/client_test.go index daf05c8..7302fc1 100644 --- a/forge/client_test.go +++ b/forge/client_test.go @@ -1,8 +1,8 @@ package forge import ( - "encoding/json" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" @@ -132,7 +132,7 @@ func TestClient_SetPRDraft_Bad_ConnectionRefused(t *testing.T) { assert.Error(t, err) } -func TestClient_SetPRDraft_URLConstruction(t *testing.T) { +func TestClient_SetPRDraft_Good_URLConstruction(t *testing.T) { // Verify the URL is constructed correctly by checking the request path. var capturedPath string mux := http.NewServeMux() @@ -156,7 +156,7 @@ func TestClient_SetPRDraft_URLConstruction(t *testing.T) { assert.Equal(t, "/api/v1/repos/my-org/my-repo/pulls/42", capturedPath) } -func TestClient_SetPRDraft_AuthHeader(t *testing.T) { +func TestClient_SetPRDraft_Good_AuthHeader(t *testing.T) { // Verify the authorisation header is set correctly. var capturedAuth string mux := http.NewServeMux() @@ -182,7 +182,7 @@ func TestClient_SetPRDraft_AuthHeader(t *testing.T) { // --- PRMeta and Comment struct tests --- -func TestPRMeta_Fields(t *testing.T) { +func TestPRMeta_Good_Fields(t *testing.T) { meta := &PRMeta{ Number: 42, Title: "Test PR", @@ -208,7 +208,7 @@ func TestPRMeta_Fields(t *testing.T) { assert.Equal(t, 5, meta.CommentCount) } -func TestComment_Fields(t *testing.T) { +func TestComment_Good_Fields(t *testing.T) { comment := Comment{ ID: 123, Author: "reviewer", @@ -222,7 +222,7 @@ func TestComment_Fields(t *testing.T) { // --- MergePullRequest merge style mapping --- -func TestMergePullRequest_StyleMapping(t *testing.T) { +func TestMergePullRequest_Good_StyleMapping(t *testing.T) { // We can't easily test the SDK call, but we can verify the method // errors when the server returns failure. This exercises the style mapping code. tests := []struct { @@ -260,7 +260,7 @@ func TestMergePullRequest_StyleMapping(t *testing.T) { // --- ListIssuesOpts defaulting --- -func TestListIssuesOpts_Defaults(t *testing.T) { +func TestListIssuesOpts_Good_Defaults(t *testing.T) { tests := []struct { name string opts ListIssuesOpts @@ -432,13 +432,13 @@ func TestClient_CreatePullRequest_Bad_ServerError(t *testing.T) { // --- commentPageSize constant test --- -func TestCommentPageSize(t *testing.T) { +func TestCommentPageSize_Good(t *testing.T) { assert.Equal(t, 50, commentPageSize, "comment page size should be 50") } // --- ListPullRequests state mapping --- -func TestListPullRequests_StateMapping(t *testing.T) { +func TestListPullRequests_Good_StateMapping(t *testing.T) { // Verify state mapping via error path (server returns error). tests := []struct { name string diff --git a/forge/config.go b/forge/config.go index 1e97bda..0d2f5f9 100644 --- a/forge/config.go +++ b/forge/config.go @@ -1,19 +1,22 @@ package forge import ( - "os" + os "dappco.re/go/core/scm/internal/ax/osx" - "forge.lthn.ai/core/config" "dappco.re/go/core/log" + "forge.lthn.ai/core/config" ) const ( // ConfigKeyURL is the config key for the Forgejo instance URL. + // Usage example: _ = ConfigKeyURL ConfigKeyURL = "forge.url" // ConfigKeyToken is the config key for the Forgejo API token. + // Usage example: _ = ConfigKeyToken ConfigKeyToken = "forge.token" // DefaultURL is the default Forgejo instance URL. + // Usage example: _ = DefaultURL DefaultURL = "http://localhost:4000" ) @@ -22,6 +25,8 @@ const ( // 1. ~/.core/config.yaml keys: forge.token, forge.url // 2. FORGE_TOKEN + FORGE_URL environment variables (override config file) // 3. Provided flag overrides (highest priority; pass empty to skip) +// +// Usage example: NewFromConfig(...) func NewFromConfig(flagURL, flagToken string) (*Client, error) { url, token, err := ResolveConfig(flagURL, flagToken) if err != nil { @@ -37,6 +42,7 @@ func NewFromConfig(flagURL, flagToken string) (*Client, error) { // ResolveConfig resolves the Forgejo URL and token from all config sources. // Flag values take highest priority, then env vars, then config file. +// Usage example: ResolveConfig(...) func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // Start with config file values cfg, cfgErr := config.New() @@ -70,6 +76,7 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { } // SaveConfig persists the Forgejo URL and/or token to the config file. +// Usage example: SaveConfig(...) func SaveConfig(url, token string) error { cfg, err := config.New() if err != nil { diff --git a/forge/config_test.go b/forge/config_test.go index ace6e30..4f35c80 100644 --- a/forge/config_test.go +++ b/forge/config_test.go @@ -68,7 +68,7 @@ func TestResolveConfig_Good_URLDefaultsWhenEmpty(t *testing.T) { assert.Equal(t, "some-token", token) } -func TestConstants(t *testing.T) { +func TestConstants_Good(t *testing.T) { assert.Equal(t, "forge.url", ConfigKeyURL) assert.Equal(t, "forge.token", ConfigKeyToken) assert.Equal(t, "http://localhost:4000", DefaultURL) diff --git a/forge/issues.go b/forge/issues.go index 664e140..c3c5770 100644 --- a/forge/issues.go +++ b/forge/issues.go @@ -9,6 +9,7 @@ import ( ) // ListIssuesOpts configures issue listing. +// Usage example: var value ListIssuesOpts type ListIssuesOpts struct { State string // "open", "closed", "all" Labels []string // filter by label names diff --git a/forge/labels.go b/forge/labels.go index 063cb46..22a4fa3 100644 --- a/forge/labels.go +++ b/forge/labels.go @@ -1,7 +1,7 @@ package forge import ( - "strings" + strings "dappco.re/go/core/scm/internal/ax/stringsx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" diff --git a/forge/meta.go b/forge/meta.go index 0cece76..c928f09 100644 --- a/forge/meta.go +++ b/forge/meta.go @@ -10,6 +10,7 @@ import ( // PRMeta holds structural signals from a pull request, // used by the pipeline MetaReader for AI-driven workflows. +// Usage example: var value PRMeta type PRMeta struct { Number int64 Title string @@ -26,6 +27,7 @@ type PRMeta struct { } // Comment represents a comment with metadata. +// Usage example: var value Comment type Comment struct { ID int64 Author string diff --git a/forge/prs.go b/forge/prs.go index d8d92f7..9b1f64f 100644 --- a/forge/prs.go +++ b/forge/prs.go @@ -2,8 +2,8 @@ package forge import ( "bytes" - "encoding/json" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/url" "strconv" diff --git a/forge/prs_test.go b/forge/prs_test.go index aabe584..10d3aeb 100644 --- a/forge/prs_test.go +++ b/forge/prs_test.go @@ -1,10 +1,10 @@ package forge import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" "github.com/stretchr/testify/assert" diff --git a/forge/testhelper_test.go b/forge/testhelper_test.go index e38db64..6f31a7b 100644 --- a/forge/testhelper_test.go +++ b/forge/testhelper_test.go @@ -1,10 +1,10 @@ package forge import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" ) diff --git a/git/git.go b/git/git.go index 53ded5f..ac21681 100644 --- a/git/git.go +++ b/git/git.go @@ -4,17 +4,18 @@ package git import ( "bytes" "context" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "io" "iter" - "os" - "os/exec" "slices" "strconv" - "strings" "sync" ) // RepoStatus represents the git status of a single repository. +// Usage example: var value RepoStatus type RepoStatus struct { Name string Path string @@ -43,6 +44,7 @@ func (s *RepoStatus) HasUnpulled() bool { } // StatusOptions configures the status check. +// Usage example: var value StatusOptions type StatusOptions struct { // Paths is a list of repo paths to check Paths []string @@ -51,6 +53,7 @@ type StatusOptions struct { } // Status checks git status for multiple repositories in parallel. +// Usage example: Status(...) func Status(ctx context.Context, opts StatusOptions) []RepoStatus { var wg sync.WaitGroup results := make([]RepoStatus, len(opts.Paths)) @@ -72,6 +75,7 @@ func Status(ctx context.Context, opts StatusOptions) []RepoStatus { } // StatusIter returns an iterator over git status for multiple repositories. +// Usage example: StatusIter(...) func StatusIter(ctx context.Context, opts StatusOptions) iter.Seq[RepoStatus] { return func(yield func(RepoStatus) bool) { results := Status(ctx, opts) @@ -156,17 +160,20 @@ func getAheadBehind(ctx context.Context, path string) (ahead, behind int) { // Push pushes commits for a single repository. // Uses interactive mode to support SSH passphrase prompts. +// Usage example: Push(...) func Push(ctx context.Context, path string) error { return gitInteractive(ctx, path, "push") } // Pull pulls changes for a single repository. // Uses interactive mode to support SSH passphrase prompts. +// Usage example: Pull(...) func Pull(ctx context.Context, path string) error { return gitInteractive(ctx, path, "pull", "--rebase") } // IsNonFastForward checks if an error is a non-fast-forward rejection. +// Usage example: IsNonFastForward(...) func IsNonFastForward(err error) bool { if err == nil { return false @@ -201,6 +208,7 @@ func gitInteractive(ctx context.Context, dir string, args ...string) error { } // PushResult represents the result of a push operation. +// Usage example: var value PushResult type PushResult struct { Name string Path string @@ -210,11 +218,13 @@ type PushResult struct { // PushMultiple pushes multiple repositories sequentially. // Sequential because SSH passphrase prompts need user interaction. +// Usage example: PushMultiple(...) func PushMultiple(ctx context.Context, paths []string, names map[string]string) []PushResult { return slices.Collect(PushMultipleIter(ctx, paths, names)) } // PushMultipleIter returns an iterator that pushes repositories sequentially and yields results. +// Usage example: PushMultipleIter(...) func PushMultipleIter(ctx context.Context, paths []string, names map[string]string) iter.Seq[PushResult] { return func(yield func(PushResult) bool) { for _, path := range paths { @@ -263,6 +273,7 @@ func gitCommand(ctx context.Context, dir string, args ...string) (string, error) } // GitError wraps a git command error with stderr output. +// Usage example: var value GitError type GitError struct { Err error Stderr string diff --git a/git/service.go b/git/service.go index 13d66c6..19ef7da 100644 --- a/git/service.go +++ b/git/service.go @@ -11,49 +11,58 @@ import ( // Queries for git service // QueryStatus requests git status for paths. +// Usage example: var value QueryStatus type QueryStatus struct { Paths []string Names map[string]string } // QueryDirtyRepos requests repos with uncommitted changes. +// Usage example: var value QueryDirtyRepos type QueryDirtyRepos struct{} // QueryAheadRepos requests repos with unpushed commits. +// Usage example: var value QueryAheadRepos type QueryAheadRepos struct{} // Tasks for git service // TaskPush requests git push for a path. +// Usage example: var value TaskPush type TaskPush struct { Path string Name string } // TaskPull requests git pull for a path. +// Usage example: var value TaskPull type TaskPull struct { Path string Name string } // TaskPushMultiple requests git push for multiple paths. +// Usage example: var value TaskPushMultiple type TaskPushMultiple struct { Paths []string Names map[string]string } // ServiceOptions for configuring the git service. +// Usage example: var value ServiceOptions type ServiceOptions struct { WorkDir string } // Service provides git operations as a Core service. +// Usage example: var value Service type Service struct { *core.ServiceRuntime[ServiceOptions] lastStatus []RepoStatus } // NewService creates a git service factory. +// Usage example: NewService(...) func NewService(opts ServiceOptions) func(*core.Core) (any, error) { return func(c *core.Core) (any, error) { return &Service{ diff --git a/gitea/client.go b/gitea/client.go index 6d752ab..f36f652 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -15,12 +15,14 @@ import ( ) // Client wraps the Gitea SDK client with config-based auth. +// Usage example: var value Client type Client struct { api *gitea.Client url string } // New creates a new Gitea API client for the given URL and token. +// Usage example: New(...) func New(url, token string) (*Client, error) { api, err := gitea.NewClient(url, gitea.SetToken(token)) if err != nil { diff --git a/gitea/config.go b/gitea/config.go index 80d4127..3cb791a 100644 --- a/gitea/config.go +++ b/gitea/config.go @@ -1,19 +1,22 @@ package gitea import ( - "os" + os "dappco.re/go/core/scm/internal/ax/osx" - "forge.lthn.ai/core/config" "dappco.re/go/core/log" + "forge.lthn.ai/core/config" ) const ( // ConfigKeyURL is the config key for the Gitea instance URL. + // Usage example: _ = ConfigKeyURL ConfigKeyURL = "gitea.url" // ConfigKeyToken is the config key for the Gitea API token. + // Usage example: _ = ConfigKeyToken ConfigKeyToken = "gitea.token" // DefaultURL is the default Gitea instance URL. + // Usage example: _ = DefaultURL DefaultURL = "https://gitea.snider.dev" ) @@ -22,6 +25,8 @@ const ( // 1. ~/.core/config.yaml keys: gitea.token, gitea.url // 2. GITEA_TOKEN + GITEA_URL environment variables (override config file) // 3. Provided flag overrides (highest priority; pass empty to skip) +// +// Usage example: NewFromConfig(...) func NewFromConfig(flagURL, flagToken string) (*Client, error) { url, token, err := ResolveConfig(flagURL, flagToken) if err != nil { @@ -37,6 +42,7 @@ func NewFromConfig(flagURL, flagToken string) (*Client, error) { // ResolveConfig resolves the Gitea URL and token from all config sources. // Flag values take highest priority, then env vars, then config file. +// Usage example: ResolveConfig(...) func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // Start with config file values cfg, cfgErr := config.New() @@ -70,6 +76,7 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { } // SaveConfig persists the Gitea URL and/or token to the config file. +// Usage example: SaveConfig(...) func SaveConfig(url, token string) error { cfg, err := config.New() if err != nil { diff --git a/gitea/config_test.go b/gitea/config_test.go index 9272ca2..bda2bc0 100644 --- a/gitea/config_test.go +++ b/gitea/config_test.go @@ -66,7 +66,7 @@ func TestResolveConfig_Good_URLDefaultsWhenEmpty(t *testing.T) { assert.Equal(t, "some-token", token) } -func TestConstants(t *testing.T) { +func TestConstants_Good(t *testing.T) { assert.Equal(t, "gitea.url", ConfigKeyURL) assert.Equal(t, "gitea.token", ConfigKeyToken) assert.Equal(t, "https://gitea.snider.dev", DefaultURL) diff --git a/gitea/coverage_boost_test.go b/gitea/coverage_boost_test.go index 82a4763..4dcac22 100644 --- a/gitea/coverage_boost_test.go +++ b/gitea/coverage_boost_test.go @@ -1,7 +1,7 @@ package gitea import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" @@ -146,12 +146,12 @@ func newPRMetaWithManyCommentsServer(t *testing.T) *httptest.Server { mux.HandleFunc("/api/v1/repos/test-org/test-repo/pulls/1", func(w http.ResponseWriter, r *http.Request) { jsonResponse(w, map[string]any{ "id": 1, "number": 1, "title": "Many Comments PR", "state": "open", - "merged": false, - "head": map[string]any{"ref": "feature", "label": "feature"}, - "base": map[string]any{"ref": "main", "label": "main"}, - "user": map[string]any{"login": "author"}, - "labels": []map[string]any{}, - "assignees": []map[string]any{}, + "merged": false, + "head": map[string]any{"ref": "feature", "label": "feature"}, + "base": map[string]any{"ref": "main", "label": "main"}, + "user": map[string]any{"login": "author"}, + "labels": []map[string]any{}, + "assignees": []map[string]any{}, "created_at": "2026-01-15T10:00:00Z", "updated_at": "2026-01-16T12:00:00Z", }) diff --git a/gitea/issues.go b/gitea/issues.go index 611b912..4d58960 100644 --- a/gitea/issues.go +++ b/gitea/issues.go @@ -9,6 +9,7 @@ import ( ) // ListIssuesOpts configures issue listing. +// Usage example: var value ListIssuesOpts type ListIssuesOpts struct { State string // "open", "closed", "all" Page int diff --git a/gitea/issues_test.go b/gitea/issues_test.go index ef22b64..0675846 100644 --- a/gitea/issues_test.go +++ b/gitea/issues_test.go @@ -163,7 +163,7 @@ func TestClient_GetPullRequest_Bad_ServerError(t *testing.T) { // --- ListIssuesOpts defaulting --- -func TestListIssuesOpts_Defaults(t *testing.T) { +func TestListIssuesOpts_Good_Defaults(t *testing.T) { tests := []struct { name string opts ListIssuesOpts diff --git a/gitea/meta.go b/gitea/meta.go index a050ef8..63f400d 100644 --- a/gitea/meta.go +++ b/gitea/meta.go @@ -10,6 +10,7 @@ import ( // PRMeta holds structural signals from a pull request, // used by the pipeline MetaReader for AI-driven workflows. +// Usage example: var value PRMeta type PRMeta struct { Number int64 Title string @@ -26,6 +27,7 @@ type PRMeta struct { } // Comment represents a comment with metadata. +// Usage example: var value Comment type Comment struct { ID int64 Author string diff --git a/gitea/meta_test.go b/gitea/meta_test.go index bebb112..7a13945 100644 --- a/gitea/meta_test.go +++ b/gitea/meta_test.go @@ -74,7 +74,7 @@ func TestClient_GetIssueBody_Bad_ServerError(t *testing.T) { // --- PRMeta struct tests --- -func TestPRMeta_Fields(t *testing.T) { +func TestPRMeta_Good_Fields(t *testing.T) { meta := &PRMeta{ Number: 42, Title: "Test PR", @@ -100,7 +100,7 @@ func TestPRMeta_Fields(t *testing.T) { assert.Equal(t, 5, meta.CommentCount) } -func TestComment_Fields(t *testing.T) { +func TestComment_Good_Fields(t *testing.T) { comment := Comment{ ID: 123, Author: "reviewer", @@ -112,6 +112,6 @@ func TestComment_Fields(t *testing.T) { assert.Equal(t, "LGTM", comment.Body) } -func TestCommentPageSize(t *testing.T) { +func TestCommentPageSize_Good(t *testing.T) { assert.Equal(t, 50, commentPageSize, "comment page size should be 50") } diff --git a/gitea/testhelper_test.go b/gitea/testhelper_test.go index daea37b..e119f72 100644 --- a/gitea/testhelper_test.go +++ b/gitea/testhelper_test.go @@ -1,10 +1,10 @@ package gitea import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" ) @@ -130,7 +130,7 @@ func newGiteaMux() *http.ServeMux { w.WriteHeader(http.StatusCreated) jsonResponse(w, map[string]any{ "id": 40, "name": "mirrored-repo", "full_name": "test-org/mirrored-repo", - "owner": map[string]any{"login": "test-org"}, + "owner": map[string]any{"login": "test-org"}, "mirror": true, }) }) diff --git a/go.mod b/go.mod index 2ffcf2b..0b97fe0 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,10 @@ require ( forge.lthn.ai/core/cli v0.3.7 forge.lthn.ai/core/config v0.1.8 github.com/gin-gonic/gin v1.12.0 + github.com/goccy/go-json v0.10.6 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.52.0 + golang.org/x/sys v0.42.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -86,7 +88,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -145,7 +146,6 @@ require ( golang.org/x/mod v0.34.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect golang.org/x/term v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/tools v0.43.0 // indirect diff --git a/internal/ax/filepathx/filepathx.go b/internal/ax/filepathx/filepathx.go new file mode 100644 index 0000000..72f3f1f --- /dev/null +++ b/internal/ax/filepathx/filepathx.go @@ -0,0 +1,53 @@ +package filepathx + +import ( + "path" + "syscall" +) + +// Separator mirrors filepath.Separator for Unix-style Core paths. +// Usage example: _ = Separator +const Separator = '/' + +// Abs mirrors filepath.Abs for the paths used in this repo. +// Usage example: Abs(...) +func Abs(p string) (string, error) { + if path.IsAbs(p) { + return path.Clean(p), nil + } + cwd, err := syscall.Getwd() + if err != nil { + return "", err + } + return path.Clean(path.Join(cwd, p)), nil +} + +// Base mirrors filepath.Base. +// Usage example: Base(...) +func Base(p string) string { + return path.Base(p) +} + +// Clean mirrors filepath.Clean. +// Usage example: Clean(...) +func Clean(p string) string { + return path.Clean(p) +} + +// Dir mirrors filepath.Dir. +// Usage example: Dir(...) +func Dir(p string) string { + return path.Dir(p) +} + +// Ext mirrors filepath.Ext. +// Usage example: Ext(...) +func Ext(p string) string { + return path.Ext(p) +} + +// Join mirrors filepath.Join. +// Usage example: Join(...) +func Join(elem ...string) string { + return path.Join(elem...) +} diff --git a/internal/ax/fmtx/fmtx.go b/internal/ax/fmtx/fmtx.go new file mode 100644 index 0000000..231d82d --- /dev/null +++ b/internal/ax/fmtx/fmtx.go @@ -0,0 +1,38 @@ +package fmtx + +import ( + "io" + + core "dappco.re/go/core" + "dappco.re/go/core/scm/internal/ax/stdio" +) + +// Sprint mirrors fmt.Sprint using Core primitives. +// Usage example: Sprint(...) +func Sprint(args ...any) string { + return core.Sprint(args...) +} + +// Sprintf mirrors fmt.Sprintf using Core primitives. +// Usage example: Sprintf(...) +func Sprintf(format string, args ...any) string { + return core.Sprintf(format, args...) +} + +// Fprintf mirrors fmt.Fprintf using Core primitives. +// Usage example: Fprintf(...) +func Fprintf(w io.Writer, format string, args ...any) (int, error) { + return io.WriteString(w, Sprintf(format, args...)) +} + +// Printf mirrors fmt.Printf. +// Usage example: Printf(...) +func Printf(format string, args ...any) (int, error) { + return Fprintf(stdio.Stdout, format, args...) +} + +// Println mirrors fmt.Println. +// Usage example: Println(...) +func Println(args ...any) (int, error) { + return io.WriteString(stdio.Stdout, Sprint(args...)+"\n") +} diff --git a/internal/ax/jsonx/jsonx.go b/internal/ax/jsonx/jsonx.go new file mode 100644 index 0000000..11756ae --- /dev/null +++ b/internal/ax/jsonx/jsonx.go @@ -0,0 +1,37 @@ +package jsonx + +import ( + "io" + + json "github.com/goccy/go-json" +) + +// Marshal mirrors encoding/json.Marshal. +// Usage example: Marshal(...) +func Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +// MarshalIndent mirrors encoding/json.MarshalIndent. +// Usage example: MarshalIndent(...) +func MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +// NewDecoder mirrors encoding/json.NewDecoder. +// Usage example: NewDecoder(...) +func NewDecoder(r io.Reader) *json.Decoder { + return json.NewDecoder(r) +} + +// NewEncoder mirrors encoding/json.NewEncoder. +// Usage example: NewEncoder(...) +func NewEncoder(w io.Writer) *json.Encoder { + return json.NewEncoder(w) +} + +// Unmarshal mirrors encoding/json.Unmarshal. +// Usage example: Unmarshal(...) +func Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} diff --git a/internal/ax/osx/osx.go b/internal/ax/osx/osx.go new file mode 100644 index 0000000..9c81365 --- /dev/null +++ b/internal/ax/osx/osx.go @@ -0,0 +1,111 @@ +package osx + +import ( + "io" + "io/fs" + "os/user" + "syscall" + + core "dappco.re/go/core" + coreio "dappco.re/go/core/io" + "dappco.re/go/core/scm/internal/ax/stdio" +) + +const ( + // Usage example: _ = O_APPEND + O_APPEND = syscall.O_APPEND + // Usage example: _ = O_CREATE + O_CREATE = syscall.O_CREAT + // Usage example: _ = O_WRONLY + O_WRONLY = syscall.O_WRONLY +) + +// Stdin exposes process stdin without importing os. +// Usage example: _ = Stdin +var Stdin = stdio.Stdin + +// Stdout exposes process stdout without importing os. +// Usage example: _ = Stdout +var Stdout = stdio.Stdout + +// Stderr exposes process stderr without importing os. +// Usage example: _ = Stderr +var Stderr = stdio.Stderr + +// Getenv mirrors os.Getenv. +// Usage example: Getenv(...) +func Getenv(key string) string { + value, _ := syscall.Getenv(key) + return value +} + +// Getwd mirrors os.Getwd. +// Usage example: Getwd(...) +func Getwd() (string, error) { + return syscall.Getwd() +} + +// IsNotExist mirrors os.IsNotExist. +// Usage example: IsNotExist(...) +func IsNotExist(err error) bool { + return core.Is(err, fs.ErrNotExist) +} + +// MkdirAll mirrors os.MkdirAll. +// Usage example: MkdirAll(...) +func MkdirAll(path string, _ fs.FileMode) error { + return coreio.Local.EnsureDir(path) +} + +// Open mirrors os.Open. +// Usage example: Open(...) +func Open(path string) (fs.File, error) { + return coreio.Local.Open(path) +} + +// OpenFile mirrors the append/create/write mode used in this repo. +// Usage example: OpenFile(...) +func OpenFile(path string, flag int, _ fs.FileMode) (io.WriteCloser, error) { + if flag&O_APPEND != 0 { + return coreio.Local.Append(path) + } + return coreio.Local.Create(path) +} + +// ReadDir mirrors os.ReadDir. +// Usage example: ReadDir(...) +func ReadDir(path string) ([]fs.DirEntry, error) { + return coreio.Local.List(path) +} + +// ReadFile mirrors os.ReadFile. +// Usage example: ReadFile(...) +func ReadFile(path string) ([]byte, error) { + content, err := coreio.Local.Read(path) + return []byte(content), err +} + +// Stat mirrors os.Stat. +// Usage example: Stat(...) +func Stat(path string) (fs.FileInfo, error) { + return coreio.Local.Stat(path) +} + +// UserHomeDir mirrors os.UserHomeDir. +// Usage example: UserHomeDir(...) +func UserHomeDir() (string, error) { + if home := Getenv("HOME"); home != "" { + return home, nil + } + current, err := user.Current() + if err != nil { + return "", err + } + return current.HomeDir, nil +} + +// WriteFile mirrors os.WriteFile. +// Usage example: WriteFile(...) +func WriteFile(path string, data []byte, perm fs.FileMode) error { + return coreio.Local.WriteMode(path, string(data), perm) +} diff --git a/internal/ax/stdio/stdio.go b/internal/ax/stdio/stdio.go new file mode 100644 index 0000000..2feef80 --- /dev/null +++ b/internal/ax/stdio/stdio.go @@ -0,0 +1,38 @@ +package stdio + +import ( + "io" + "syscall" +) + +type fdReader struct { + fd int +} + +func (r fdReader) Read(p []byte) (int, error) { + n, err := syscall.Read(r.fd, p) + if n == 0 && err == nil { + return 0, io.EOF + } + return n, err +} + +type fdWriter struct { + fd int +} + +func (w fdWriter) Write(p []byte) (int, error) { + return syscall.Write(w.fd, p) +} + +// Stdin exposes process stdin without importing os. +// Usage example: _ = Stdin +var Stdin io.Reader = fdReader{fd: 0} + +// Stdout exposes process stdout without importing os. +// Usage example: _ = Stdout +var Stdout io.Writer = fdWriter{fd: 1} + +// Stderr exposes process stderr without importing os. +// Usage example: _ = Stderr +var Stderr io.Writer = fdWriter{fd: 2} diff --git a/internal/ax/stringsx/stringsx.go b/internal/ax/stringsx/stringsx.go new file mode 100644 index 0000000..94afc0c --- /dev/null +++ b/internal/ax/stringsx/stringsx.go @@ -0,0 +1,149 @@ +package stringsx + +import ( + "bufio" + "bytes" + "iter" + + core "dappco.re/go/core" +) + +// Builder provides a strings.Builder-like type without importing strings. +// Usage example: var value Builder +type Builder = bytes.Buffer + +// Contains mirrors strings.Contains. +// Usage example: Contains(...) +func Contains(s, substr string) bool { + return core.Contains(s, substr) +} + +// ContainsAny mirrors strings.ContainsAny. +// Usage example: ContainsAny(...) +func ContainsAny(s, chars string) bool { + return bytes.IndexAny([]byte(s), chars) >= 0 +} + +// EqualFold mirrors strings.EqualFold. +// Usage example: EqualFold(...) +func EqualFold(s, t string) bool { + return bytes.EqualFold([]byte(s), []byte(t)) +} + +// Fields mirrors strings.Fields. +// Usage example: Fields(...) +func Fields(s string) []string { + scanner := bufio.NewScanner(NewReader(s)) + scanner.Split(bufio.ScanWords) + fields := make([]string, 0) + for scanner.Scan() { + fields = append(fields, scanner.Text()) + } + return fields +} + +// HasPrefix mirrors strings.HasPrefix. +// Usage example: HasPrefix(...) +func HasPrefix(s, prefix string) bool { + return core.HasPrefix(s, prefix) +} + +// HasSuffix mirrors strings.HasSuffix. +// Usage example: HasSuffix(...) +func HasSuffix(s, suffix string) bool { + return core.HasSuffix(s, suffix) +} + +// Join mirrors strings.Join. +// Usage example: Join(...) +func Join(elems []string, sep string) string { + return core.Join(sep, elems...) +} + +// LastIndex mirrors strings.LastIndex. +// Usage example: LastIndex(...) +func LastIndex(s, substr string) int { + return bytes.LastIndex([]byte(s), []byte(substr)) +} + +// NewReader mirrors strings.NewReader. +// Usage example: NewReader(...) +func NewReader(s string) *bytes.Reader { + return bytes.NewReader([]byte(s)) +} + +// Repeat mirrors strings.Repeat. +// Usage example: Repeat(...) +func Repeat(s string, count int) string { + if count <= 0 { + return "" + } + return string(bytes.Repeat([]byte(s), count)) +} + +// ReplaceAll mirrors strings.ReplaceAll. +// Usage example: ReplaceAll(...) +func ReplaceAll(s, old, new string) string { + return core.Replace(s, old, new) +} + +// Replace mirrors strings.Replace for replace-all call sites. +// Usage example: Replace(...) +func Replace(s, old, new string, _ int) string { + return ReplaceAll(s, old, new) +} + +// Split mirrors strings.Split. +// Usage example: Split(...) +func Split(s, sep string) []string { + return core.Split(s, sep) +} + +// SplitN mirrors strings.SplitN. +// Usage example: SplitN(...) +func SplitN(s, sep string, n int) []string { + return core.SplitN(s, sep, n) +} + +// SplitSeq mirrors strings.SplitSeq. +// Usage example: SplitSeq(...) +func SplitSeq(s, sep string) iter.Seq[string] { + parts := Split(s, sep) + return func(yield func(string) bool) { + for _, part := range parts { + if !yield(part) { + return + } + } + } +} + +// ToLower mirrors strings.ToLower. +// Usage example: ToLower(...) +func ToLower(s string) string { + return core.Lower(s) +} + +// ToUpper mirrors strings.ToUpper. +// Usage example: ToUpper(...) +func ToUpper(s string) string { + return core.Upper(s) +} + +// TrimPrefix mirrors strings.TrimPrefix. +// Usage example: TrimPrefix(...) +func TrimPrefix(s, prefix string) string { + return core.TrimPrefix(s, prefix) +} + +// TrimSpace mirrors strings.TrimSpace. +// Usage example: TrimSpace(...) +func TrimSpace(s string) string { + return core.Trim(s) +} + +// TrimSuffix mirrors strings.TrimSuffix. +// Usage example: TrimSuffix(...) +func TrimSuffix(s, suffix string) string { + return core.TrimSuffix(s, suffix) +} diff --git a/jobrunner/forgejo/source.go b/jobrunner/forgejo/source.go index 61f8970..84fa9f5 100644 --- a/jobrunner/forgejo/source.go +++ b/jobrunner/forgejo/source.go @@ -2,26 +2,29 @@ package forgejo import ( "context" - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + "dappco.re/go/core/log" "dappco.re/go/core/scm/forge" "dappco.re/go/core/scm/jobrunner" - "dappco.re/go/core/log" ) // Config configures a ForgejoSource. +// Usage example: var value Config type Config struct { Repos []string // "owner/repo" format } // ForgejoSource polls a Forgejo instance for pipeline signals from epic issues. +// Usage example: var value ForgejoSource type ForgejoSource struct { repos []string forge *forge.Client } // New creates a ForgejoSource using the given forge client. +// Usage example: New(...) func New(cfg Config, client *forge.Client) *ForgejoSource { return &ForgejoSource{ repos: cfg.Repos, diff --git a/jobrunner/forgejo/source_test.go b/jobrunner/forgejo/source_test.go index 965e765..3656891 100644 --- a/jobrunner/forgejo/source_test.go +++ b/jobrunner/forgejo/source_test.go @@ -2,10 +2,10 @@ package forgejo import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" "net/http/httptest" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -35,7 +35,7 @@ func newTestClient(t *testing.T, url string) *forge.Client { return client } -func TestForgejoSource_Name(t *testing.T) { +func TestForgejoSource_Good_Name(t *testing.T) { s := New(Config{}, nil) assert.Equal(t, "forgejo", s.Name()) } @@ -106,7 +106,7 @@ func TestForgejoSource_Poll_Good(t *testing.T) { assert.Equal(t, "abc123", sig.LastCommitSHA) } -func TestForgejoSource_Poll_NoEpics(t *testing.T) { +func TestForgejoSource_Poll_Good_NoEpics(t *testing.T) { srv := httptest.NewServer(withVersion(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode([]any{}) @@ -152,18 +152,18 @@ func TestForgejoSource_Report_Good(t *testing.T) { assert.Contains(t, capturedBody, "succeeded") } -func TestParseEpicChildren(t *testing.T) { +func TestParseEpicChildren_Good(t *testing.T) { body := "## Tasks\n- [x] #1\n- [ ] #7\n- [ ] #8\n- [x] #3\n" unchecked, checked := parseEpicChildren(body) assert.Equal(t, []int{7, 8}, unchecked) assert.Equal(t, []int{1, 3}, checked) } -func TestFindLinkedPR(t *testing.T) { +func TestFindLinkedPR_Good(t *testing.T) { assert.Nil(t, findLinkedPR(nil, 7)) } -func TestSplitRepo(t *testing.T) { +func TestSplitRepo_Good(t *testing.T) { owner, repo, err := splitRepo("host-uk/core") require.NoError(t, err) assert.Equal(t, "host-uk", owner) diff --git a/jobrunner/handlers/completion.go b/jobrunner/handlers/completion.go index 0c9b40e..d353854 100644 --- a/jobrunner/handlers/completion.go +++ b/jobrunner/handlers/completion.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" coreerr "dappco.re/go/core/log" @@ -11,15 +11,18 @@ import ( ) const ( + // Usage example: _ = ColorAgentComplete ColorAgentComplete = "#0e8a16" // Green ) // CompletionHandler manages issue state when an agent finishes work. +// Usage example: var value CompletionHandler type CompletionHandler struct { forge *forge.Client } // NewCompletionHandler creates a handler for agent completion events. +// Usage example: NewCompletionHandler(...) func NewCompletionHandler(client *forge.Client) *CompletionHandler { return &CompletionHandler{ forge: client, diff --git a/jobrunner/handlers/dispatch.go b/jobrunner/handlers/dispatch.go index 961a9d9..64fda7b 100644 --- a/jobrunner/handlers/dispatch.go +++ b/jobrunner/handlers/dispatch.go @@ -3,10 +3,10 @@ package handlers import ( "bytes" "context" - "encoding/json" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "path" - "strings" "time" coreerr "dappco.re/go/core/log" @@ -16,17 +16,24 @@ import ( ) const ( - LabelAgentReady = "agent-ready" - LabelInProgress = "in-progress" - LabelAgentFailed = "agent-failed" + // Usage example: _ = LabelAgentReady + LabelAgentReady = "agent-ready" + // Usage example: _ = LabelInProgress + LabelInProgress = "in-progress" + // Usage example: _ = LabelAgentFailed + LabelAgentFailed = "agent-failed" + // Usage example: _ = LabelAgentComplete LabelAgentComplete = "agent-completed" - ColorInProgress = "#1d76db" // Blue + // Usage example: _ = ColorInProgress + ColorInProgress = "#1d76db" // Blue + // Usage example: _ = ColorAgentFailed ColorAgentFailed = "#c0392b" // Red ) // DispatchTicket is the JSON payload written to the agent's queue. // The ForgeToken is transferred separately via a .env file with 0600 permissions. +// Usage example: var value DispatchTicket type DispatchTicket struct { ID string `json:"id"` RepoOwner string `json:"repo_owner"` @@ -46,6 +53,7 @@ type DispatchTicket struct { } // DispatchHandler dispatches coding work to remote agent machines via SSH. +// Usage example: var value DispatchHandler type DispatchHandler struct { forge *forge.Client forgeURL string @@ -54,6 +62,7 @@ type DispatchHandler struct { } // NewDispatchHandler creates a handler that dispatches tickets to agent machines. +// Usage example: NewDispatchHandler(...) func NewDispatchHandler(client *forge.Client, forgeURL, token string, spinner *agentci.Spinner) *DispatchHandler { return &DispatchHandler{ forge: client, diff --git a/jobrunner/handlers/dispatch_test.go b/jobrunner/handlers/dispatch_test.go index 0f733b3..251a838 100644 --- a/jobrunner/handlers/dispatch_test.go +++ b/jobrunner/handlers/dispatch_test.go @@ -2,11 +2,11 @@ package handlers import ( "context" - "encoding/json" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + os "dappco.re/go/core/scm/internal/ax/osx" "net/http" "net/http/httptest" - "os" - "path/filepath" "strconv" "testing" diff --git a/jobrunner/handlers/enable_auto_merge.go b/jobrunner/handlers/enable_auto_merge.go index 7ab4d30..e696cd6 100644 --- a/jobrunner/handlers/enable_auto_merge.go +++ b/jobrunner/handlers/enable_auto_merge.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" "dappco.re/go/core/scm/forge" @@ -10,11 +10,13 @@ import ( ) // EnableAutoMergeHandler merges a PR that is ready using squash strategy. +// Usage example: var value EnableAutoMergeHandler type EnableAutoMergeHandler struct { forge *forge.Client } // NewEnableAutoMergeHandler creates a handler that merges ready PRs. +// Usage example: NewEnableAutoMergeHandler(...) func NewEnableAutoMergeHandler(f *forge.Client) *EnableAutoMergeHandler { return &EnableAutoMergeHandler{forge: f} } diff --git a/jobrunner/handlers/enable_auto_merge_test.go b/jobrunner/handlers/enable_auto_merge_test.go index 9a5feac..0242e60 100644 --- a/jobrunner/handlers/enable_auto_merge_test.go +++ b/jobrunner/handlers/enable_auto_merge_test.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/jobrunner/handlers/publish_draft.go b/jobrunner/handlers/publish_draft.go index 202726b..32f892f 100644 --- a/jobrunner/handlers/publish_draft.go +++ b/jobrunner/handlers/publish_draft.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" "dappco.re/go/core/scm/forge" @@ -10,11 +10,13 @@ import ( ) // PublishDraftHandler marks a draft PR as ready for review once its checks pass. +// Usage example: var value PublishDraftHandler type PublishDraftHandler struct { forge *forge.Client } // NewPublishDraftHandler creates a handler that publishes draft PRs. +// Usage example: NewPublishDraftHandler(...) func NewPublishDraftHandler(f *forge.Client) *PublishDraftHandler { return &PublishDraftHandler{forge: f} } diff --git a/jobrunner/handlers/resolve_threads.go b/jobrunner/handlers/resolve_threads.go index 19f8480..eac18f1 100644 --- a/jobrunner/handlers/resolve_threads.go +++ b/jobrunner/handlers/resolve_threads.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" forgejosdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -15,11 +15,13 @@ import ( // DismissReviewsHandler dismisses stale "request changes" reviews on a PR. // This replaces the GitHub-only ResolveThreadsHandler because Forgejo does // not have a thread resolution API. +// Usage example: var value DismissReviewsHandler type DismissReviewsHandler struct { forge *forge.Client } // NewDismissReviewsHandler creates a handler that dismisses stale reviews. +// Usage example: NewDismissReviewsHandler(...) func NewDismissReviewsHandler(f *forge.Client) *DismissReviewsHandler { return &DismissReviewsHandler{forge: f} } diff --git a/jobrunner/handlers/resolve_threads_test.go b/jobrunner/handlers/resolve_threads_test.go index ec9dfd6..6e85c6a 100644 --- a/jobrunner/handlers/resolve_threads_test.go +++ b/jobrunner/handlers/resolve_threads_test.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/jobrunner/handlers/send_fix_command.go b/jobrunner/handlers/send_fix_command.go index 5b65eab..920c60b 100644 --- a/jobrunner/handlers/send_fix_command.go +++ b/jobrunner/handlers/send_fix_command.go @@ -2,7 +2,7 @@ package handlers import ( "context" - "fmt" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" "time" "dappco.re/go/core/scm/forge" @@ -11,11 +11,13 @@ import ( // SendFixCommandHandler posts a comment on a PR asking for conflict or // review fixes. +// Usage example: var value SendFixCommandHandler type SendFixCommandHandler struct { forge *forge.Client } // NewSendFixCommandHandler creates a handler that posts fix commands. +// Usage example: NewSendFixCommandHandler(...) func NewSendFixCommandHandler(f *forge.Client) *SendFixCommandHandler { return &SendFixCommandHandler{forge: f} } diff --git a/jobrunner/handlers/testhelper_test.go b/jobrunner/handlers/testhelper_test.go index 20d966b..fb78991 100644 --- a/jobrunner/handlers/testhelper_test.go +++ b/jobrunner/handlers/testhelper_test.go @@ -1,8 +1,8 @@ package handlers import ( + strings "dappco.re/go/core/scm/internal/ax/stringsx" "net/http" - "strings" "testing" "github.com/stretchr/testify/require" diff --git a/jobrunner/handlers/tick_parent.go b/jobrunner/handlers/tick_parent.go index 2d4bb74..bd9cf50 100644 --- a/jobrunner/handlers/tick_parent.go +++ b/jobrunner/handlers/tick_parent.go @@ -2,8 +2,8 @@ package handlers import ( "context" - "fmt" - "strings" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "time" forgejosdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -15,11 +15,13 @@ import ( // TickParentHandler ticks a child checkbox in the parent epic issue body // after the child's PR has been merged. +// Usage example: var value TickParentHandler type TickParentHandler struct { forge *forge.Client } // NewTickParentHandler creates a handler that ticks parent epic checkboxes. +// Usage example: NewTickParentHandler(...) func NewTickParentHandler(f *forge.Client) *TickParentHandler { return &TickParentHandler{forge: f} } diff --git a/jobrunner/handlers/tick_parent_test.go b/jobrunner/handlers/tick_parent_test.go index 836ecdf..3155210 100644 --- a/jobrunner/handlers/tick_parent_test.go +++ b/jobrunner/handlers/tick_parent_test.go @@ -2,11 +2,11 @@ package handlers import ( "context" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "io" "net/http" "net/http/httptest" - "strings" "testing" "github.com/stretchr/testify/assert" diff --git a/jobrunner/journal.go b/jobrunner/journal.go index 2e3976b..62caec0 100644 --- a/jobrunner/journal.go +++ b/jobrunner/journal.go @@ -1,21 +1,22 @@ package jobrunner import ( - "encoding/json" - "os" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "regexp" - "strings" "sync" - coreerr "dappco.re/go/core/log" coreio "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" ) // validPathComponent matches safe repo owner/name characters (alphanumeric, hyphen, underscore, dot). var validPathComponent = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*$`) // JournalEntry is a single line in the JSONL audit log. +// Usage example: var value JournalEntry type JournalEntry struct { Timestamp string `json:"ts"` Epic int `json:"epic"` @@ -29,6 +30,7 @@ type JournalEntry struct { } // SignalSnapshot captures the structural state of a PR at the time of action. +// Usage example: var value SignalSnapshot type SignalSnapshot struct { PRState string `json:"pr_state"` IsDraft bool `json:"is_draft"` @@ -39,6 +41,7 @@ type SignalSnapshot struct { } // ResultSnapshot captures the outcome of an action. +// Usage example: var value ResultSnapshot type ResultSnapshot struct { Success bool `json:"success"` Error string `json:"error,omitempty"` @@ -46,12 +49,14 @@ type ResultSnapshot struct { } // Journal writes ActionResult entries to date-partitioned JSONL files. +// Usage example: var value Journal type Journal struct { baseDir string mu sync.Mutex } // NewJournal creates a new Journal rooted at baseDir. +// Usage example: NewJournal(...) func NewJournal(baseDir string) (*Journal, error) { if baseDir == "" { return nil, coreerr.E("jobrunner.NewJournal", "base directory is required", nil) diff --git a/jobrunner/journal_test.go b/jobrunner/journal_test.go index a17a88b..b127eb7 100644 --- a/jobrunner/journal_test.go +++ b/jobrunner/journal_test.go @@ -2,10 +2,10 @@ package jobrunner import ( "bufio" - "encoding/json" - "os" - "path/filepath" - "strings" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "testing" "time" diff --git a/jobrunner/poller.go b/jobrunner/poller.go index 302c563..5b5b75c 100644 --- a/jobrunner/poller.go +++ b/jobrunner/poller.go @@ -9,6 +9,7 @@ import ( ) // PollerConfig configures a Poller. +// Usage example: var value PollerConfig type PollerConfig struct { Sources []JobSource Handlers []JobHandler @@ -18,6 +19,7 @@ type PollerConfig struct { } // Poller discovers signals from sources and dispatches them to handlers. +// Usage example: var value Poller type Poller struct { mu sync.RWMutex sources []JobSource @@ -29,6 +31,7 @@ type Poller struct { } // NewPoller creates a Poller from the given config. +// Usage example: NewPoller(...) func NewPoller(cfg PollerConfig) *Poller { interval := cfg.PollInterval if interval <= 0 { diff --git a/jobrunner/types.go b/jobrunner/types.go index ce51caf..87fa679 100644 --- a/jobrunner/types.go +++ b/jobrunner/types.go @@ -7,6 +7,7 @@ import ( // PipelineSignal is the structural snapshot of a child issue/PR. // Carries structural state plus issue title/body for dispatch prompts. +// Usage example: var value PipelineSignal type PipelineSignal struct { EpicNumber int ChildNumber int @@ -43,6 +44,7 @@ func (s *PipelineSignal) HasUnresolvedThreads() bool { } // ActionResult carries the outcome of a handler execution. +// Usage example: var value ActionResult type ActionResult struct { Action string `json:"action"` RepoOwner string `json:"repo_owner"` @@ -58,6 +60,7 @@ type ActionResult struct { } // JobSource discovers actionable work from an external system. +// Usage example: var value JobSource type JobSource interface { Name() string Poll(ctx context.Context) ([]*PipelineSignal, error) @@ -65,6 +68,7 @@ type JobSource interface { } // JobHandler processes a single pipeline signal. +// Usage example: var value JobHandler type JobHandler interface { Name() string Match(signal *PipelineSignal) bool diff --git a/jobrunner/types_test.go b/jobrunner/types_test.go index c81a840..c16c4d4 100644 --- a/jobrunner/types_test.go +++ b/jobrunner/types_test.go @@ -1,7 +1,7 @@ package jobrunner import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "testing" "time" diff --git a/locales/embed.go b/locales/embed.go index 410cb55..85a9787 100644 --- a/locales/embed.go +++ b/locales/embed.go @@ -3,5 +3,7 @@ package locales import "embed" +// Usage example: _ = FS +// //go:embed *.json var FS embed.FS diff --git a/manifest/compile.go b/manifest/compile.go index d9f00ea..73c4d93 100644 --- a/manifest/compile.go +++ b/manifest/compile.go @@ -2,17 +2,18 @@ package manifest import ( "crypto/ed25519" - "encoding/json" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" "time" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" ) // CompiledManifest is the distribution-ready form of a manifest, written as // core.json at the repository root (not inside .core/). It embeds the // original Manifest and adds build metadata stapled at compile time. +// Usage example: var value CompiledManifest type CompiledManifest struct { Manifest `json:",inline" yaml:",inline"` @@ -24,15 +25,17 @@ type CompiledManifest struct { } // CompileOptions controls how Compile populates the build metadata. +// Usage example: var value CompileOptions type CompileOptions struct { - Commit string // Git commit hash - Tag string // Git tag (e.g. v1.0.0) - BuiltBy string // Builder identity (e.g. "core build") + Commit string // Git commit hash + Tag string // Git tag (e.g. v1.0.0) + BuiltBy string // Builder identity (e.g. "core build") SignKey ed25519.PrivateKey // Optional — signs before compiling } // Compile produces a CompiledManifest from a source manifest and build // options. If opts.SignKey is provided the manifest is signed first. +// Usage example: Compile(...) func Compile(m *Manifest, opts CompileOptions) (*CompiledManifest, error) { if m == nil { return nil, coreerr.E("manifest.Compile", "nil manifest", nil) @@ -61,11 +64,13 @@ func Compile(m *Manifest, opts CompileOptions) (*CompiledManifest, error) { } // MarshalJSON serialises a CompiledManifest to JSON bytes. +// Usage example: MarshalJSON(...) func MarshalJSON(cm *CompiledManifest) ([]byte, error) { return json.MarshalIndent(cm, "", " ") } // ParseCompiled decodes a core.json into a CompiledManifest. +// Usage example: ParseCompiled(...) func ParseCompiled(data []byte) (*CompiledManifest, error) { var cm CompiledManifest if err := json.Unmarshal(data, &cm); err != nil { @@ -78,6 +83,7 @@ const compiledPath = "core.json" // WriteCompiled writes a CompiledManifest as core.json to the given root // directory. The file lives at the distribution root, not inside .core/. +// Usage example: WriteCompiled(...) func WriteCompiled(medium io.Medium, root string, cm *CompiledManifest) error { data, err := MarshalJSON(cm) if err != nil { @@ -88,6 +94,7 @@ func WriteCompiled(medium io.Medium, root string, cm *CompiledManifest) error { } // LoadCompiled reads and parses a core.json from the given root directory. +// Usage example: LoadCompiled(...) func LoadCompiled(medium io.Medium, root string) (*CompiledManifest, error) { path := filepath.Join(root, compiledPath) data, err := medium.Read(path) diff --git a/manifest/compile_test.go b/manifest/compile_test.go index 09cdaa0..8e8655f 100644 --- a/manifest/compile_test.go +++ b/manifest/compile_test.go @@ -3,7 +3,7 @@ package manifest import ( "crypto/ed25519" "crypto/rand" - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "testing" "dappco.re/go/core/io" diff --git a/manifest/loader.go b/manifest/loader.go index b454690..029af77 100644 --- a/manifest/loader.go +++ b/manifest/loader.go @@ -2,21 +2,23 @@ package manifest import ( "crypto/ed25519" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) const manifestPath = ".core/manifest.yaml" // MarshalYAML serializes a manifest to YAML bytes. +// Usage example: MarshalYAML(...) func MarshalYAML(m *Manifest) ([]byte, error) { return yaml.Marshal(m) } // Load reads and parses a .core/manifest.yaml from the given root directory. +// Usage example: Load(...) func Load(medium io.Medium, root string) (*Manifest, error) { path := filepath.Join(root, manifestPath) data, err := medium.Read(path) @@ -27,6 +29,7 @@ func Load(medium io.Medium, root string) (*Manifest, error) { } // LoadVerified reads, parses, and verifies the ed25519 signature. +// Usage example: LoadVerified(...) func LoadVerified(medium io.Medium, root string, pub ed25519.PublicKey) (*Manifest, error) { m, err := Load(medium, root) if err != nil { diff --git a/manifest/manifest.go b/manifest/manifest.go index f0eede9..ca9e383 100644 --- a/manifest/manifest.go +++ b/manifest/manifest.go @@ -6,6 +6,7 @@ import ( ) // Manifest represents a .core/manifest.yaml application manifest. +// Usage example: var value Manifest type Manifest struct { Code string `yaml:"code" json:"code"` Name string `yaml:"name" json:"name"` @@ -25,12 +26,13 @@ type Manifest struct { Element *ElementSpec `yaml:"element,omitempty" json:"element,omitempty"` // Custom element for GUI rendering Spec string `yaml:"spec,omitempty" json:"spec,omitempty"` // Path to OpenAPI spec file - Permissions Permissions `yaml:"permissions,omitempty" json:"permissions,omitempty"` - Modules []string `yaml:"modules,omitempty" json:"modules,omitempty"` - Daemons map[string]DaemonSpec `yaml:"daemons,omitempty" json:"daemons,omitempty"` + Permissions Permissions `yaml:"permissions,omitempty" json:"permissions,omitempty"` + Modules []string `yaml:"modules,omitempty" json:"modules,omitempty"` + Daemons map[string]DaemonSpec `yaml:"daemons,omitempty" json:"daemons,omitempty"` } // ElementSpec describes a web component for GUI rendering. +// Usage example: var value ElementSpec type ElementSpec struct { // Tag is the custom element tag name, e.g. "core-cool-widget". Tag string `yaml:"tag" json:"tag"` @@ -46,6 +48,7 @@ func (m *Manifest) IsProvider() bool { } // Permissions declares the I/O capabilities a module requires. +// Usage example: var value Permissions type Permissions struct { Read []string `yaml:"read" json:"read"` Write []string `yaml:"write" json:"write"` @@ -54,6 +57,7 @@ type Permissions struct { } // DaemonSpec describes a long-running process managed by the runtime. +// Usage example: var value DaemonSpec type DaemonSpec struct { Binary string `yaml:"binary,omitempty" json:"binary,omitempty"` Args []string `yaml:"args,omitempty" json:"args,omitempty"` @@ -62,6 +66,7 @@ type DaemonSpec struct { } // Parse decodes YAML bytes into a Manifest. +// Usage example: Parse(...) func Parse(data []byte) (*Manifest, error) { var m Manifest if err := yaml.Unmarshal(data, &m); err != nil { diff --git a/manifest/sign.go b/manifest/sign.go index 7eb922f..a5e663d 100644 --- a/manifest/sign.go +++ b/manifest/sign.go @@ -16,6 +16,7 @@ func signable(m *Manifest) ([]byte, error) { } // Sign computes the ed25519 signature and stores it in m.Sign (base64). +// Usage example: Sign(...) func Sign(m *Manifest, priv ed25519.PrivateKey) error { msg, err := signable(m) if err != nil { @@ -27,6 +28,7 @@ func Sign(m *Manifest, priv ed25519.PrivateKey) error { } // Verify checks the ed25519 signature in m.Sign against the public key. +// Usage example: Verify(...) func Verify(m *Manifest, pub ed25519.PublicKey) (bool, error) { if m.Sign == "" { return false, coreerr.E("manifest.Verify", "no signature present", nil) diff --git a/marketplace/builder.go b/marketplace/builder.go index c85f182..8790070 100644 --- a/marketplace/builder.go +++ b/marketplace/builder.go @@ -1,22 +1,24 @@ package marketplace import ( - "encoding/json" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + os "dappco.re/go/core/scm/internal/ax/osx" "log" - "os" - "path/filepath" "sort" - coreerr "dappco.re/go/core/log" coreio "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "dappco.re/go/core/scm/manifest" ) // IndexVersion is the current marketplace index format version. +// Usage example: _ = IndexVersion const IndexVersion = 1 // Builder constructs a marketplace Index by crawling directories for // core.json (compiled manifests) or .core/manifest.yaml files. +// Usage example: var value Builder type Builder struct { // BaseURL is the prefix for constructing repository URLs, e.g. // "https://forge.lthn.ai". When set, module Repo is derived as @@ -83,6 +85,7 @@ func (b *Builder) BuildFromDirs(dirs ...string) (*Index, error) { // BuildFromManifests constructs an Index from pre-loaded manifests. // This is useful when manifests have already been collected (e.g. from // a Forge API crawl). +// Usage example: BuildFromManifests(...) func BuildFromManifests(manifests []*manifest.Manifest) *Index { var modules []Module seen := make(map[string]bool) @@ -113,6 +116,7 @@ func BuildFromManifests(manifests []*manifest.Manifest) *Index { } // WriteIndex serialises an Index to JSON and writes it to the given path. +// Usage example: WriteIndex(...) func WriteIndex(path string, idx *Index) error { if err := coreio.Local.EnsureDir(filepath.Dir(path)); err != nil { return coreerr.E("marketplace.WriteIndex", "mkdir failed", err) diff --git a/marketplace/builder_test.go b/marketplace/builder_test.go index baf3121..97920c8 100644 --- a/marketplace/builder_test.go +++ b/marketplace/builder_test.go @@ -1,9 +1,9 @@ package marketplace import ( - "encoding/json" - "os" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + os "dappco.re/go/core/scm/internal/ax/osx" "testing" "dappco.re/go/core/scm/manifest" diff --git a/marketplace/discovery.go b/marketplace/discovery.go index e9e8d89..6ef2ec6 100644 --- a/marketplace/discovery.go +++ b/marketplace/discovery.go @@ -1,17 +1,18 @@ package marketplace import ( + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + os "dappco.re/go/core/scm/internal/ax/osx" "log" - "os" - "path/filepath" - coreerr "dappco.re/go/core/log" coreio "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "dappco.re/go/core/scm/manifest" "gopkg.in/yaml.v3" ) // DiscoveredProvider represents a runtime provider found on disk. +// Usage example: var value DiscoveredProvider type DiscoveredProvider struct { // Dir is the absolute path to the provider directory. Dir string @@ -24,6 +25,7 @@ type DiscoveredProvider struct { // Each subdirectory is checked for a .core/manifest.yaml file. Directories // without a valid manifest are skipped with a log warning. // Only manifests with provider fields (namespace + binary) are returned. +// Usage example: DiscoverProviders(...) func DiscoverProviders(dir string) ([]DiscoveredProvider, error) { entries, err := os.ReadDir(dir) if err != nil { @@ -69,6 +71,7 @@ func DiscoverProviders(dir string) ([]DiscoveredProvider, error) { } // ProviderRegistryEntry records metadata about an installed provider. +// Usage example: var value ProviderRegistryEntry type ProviderRegistryEntry struct { Installed string `yaml:"installed" json:"installed"` Version string `yaml:"version" json:"version"` @@ -77,6 +80,7 @@ type ProviderRegistryEntry struct { } // ProviderRegistryFile represents the registry.yaml file tracking installed providers. +// Usage example: var value ProviderRegistryFile type ProviderRegistryFile struct { Version int `yaml:"version" json:"version"` Providers map[string]ProviderRegistryEntry `yaml:"providers" json:"providers"` @@ -84,6 +88,7 @@ type ProviderRegistryFile struct { // LoadProviderRegistry reads a registry.yaml file from the given path. // Returns an empty registry if the file does not exist. +// Usage example: LoadProviderRegistry(...) func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) { raw, err := coreio.Local.Read(path) if err != nil { @@ -109,6 +114,7 @@ func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) { } // SaveProviderRegistry writes the registry to the given path. +// Usage example: SaveProviderRegistry(...) func SaveProviderRegistry(path string, reg *ProviderRegistryFile) error { if err := coreio.Local.EnsureDir(filepath.Dir(path)); err != nil { return coreerr.E("marketplace.SaveProviderRegistry", "ensure directory", err) diff --git a/marketplace/discovery_test.go b/marketplace/discovery_test.go index c96c9c8..5edb8c8 100644 --- a/marketplace/discovery_test.go +++ b/marketplace/discovery_test.go @@ -1,8 +1,8 @@ package marketplace import ( - "os" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + os "dappco.re/go/core/scm/internal/ax/osx" "testing" "github.com/stretchr/testify/assert" diff --git a/marketplace/installer.go b/marketplace/installer.go index e338ce4..13a98c9 100644 --- a/marketplace/installer.go +++ b/marketplace/installer.go @@ -2,11 +2,11 @@ package marketplace import ( "context" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" "encoding/hex" - "encoding/json" - "os/exec" - "path/filepath" - "strings" + exec "golang.org/x/sys/execabs" "time" "dappco.re/go/core/io" @@ -19,6 +19,7 @@ import ( const storeGroup = "_modules" // Installer handles module installation from Git repos. +// Usage example: var value Installer type Installer struct { medium io.Medium modulesDir string @@ -26,6 +27,7 @@ type Installer struct { } // NewInstaller creates a new module installer. +// Usage example: NewInstaller(...) func NewInstaller(m io.Medium, modulesDir string, st *store.Store) *Installer { return &Installer{ medium: m, @@ -35,6 +37,7 @@ func NewInstaller(m io.Medium, modulesDir string, st *store.Store) *Installer { } // InstalledModule holds stored metadata about an installed module. +// Usage example: var value InstalledModule type InstalledModule struct { Code string `json:"code"` Name string `json:"name"` diff --git a/marketplace/installer_test.go b/marketplace/installer_test.go index ee992fa..803e2a7 100644 --- a/marketplace/installer_test.go +++ b/marketplace/installer_test.go @@ -3,10 +3,10 @@ package marketplace import ( "context" "crypto/ed25519" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + os "dappco.re/go/core/scm/internal/ax/osx" "encoding/hex" - "os" - "os/exec" - "path/filepath" + exec "golang.org/x/sys/execabs" "testing" "dappco.re/go/core/io" diff --git a/marketplace/marketplace.go b/marketplace/marketplace.go index c97fe39..39c21b9 100644 --- a/marketplace/marketplace.go +++ b/marketplace/marketplace.go @@ -1,13 +1,14 @@ package marketplace import ( - "encoding/json" - "strings" + json "dappco.re/go/core/scm/internal/ax/jsonx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" coreerr "dappco.re/go/core/log" ) // Module is a marketplace entry pointing to a module's Git repo. +// Usage example: var value Module type Module struct { Code string `json:"code"` Name string `json:"name"` @@ -17,6 +18,7 @@ type Module struct { } // Index is the root marketplace catalog. +// Usage example: var value Index type Index struct { Version int `json:"version"` Modules []Module `json:"modules"` @@ -24,6 +26,7 @@ type Index struct { } // ParseIndex decodes a marketplace index.json. +// Usage example: ParseIndex(...) func ParseIndex(data []byte) (*Index, error) { var idx Index if err := json.Unmarshal(data, &idx); err != nil { diff --git a/pkg/api/embed.go b/pkg/api/embed.go index 981cfb7..8f2e1f1 100644 --- a/pkg/api/embed.go +++ b/pkg/api/embed.go @@ -7,5 +7,7 @@ import "embed" // Assets holds the built UI bundle (core-scm.js and related files). // The directory is populated by running `npm run build` in the ui/ directory. // +// Usage example: _ = Assets +// //go:embed all:ui/dist var Assets embed.FS diff --git a/pkg/api/provider.go b/pkg/api/provider.go index 80641e5..33b815f 100644 --- a/pkg/api/provider.go +++ b/pkg/api/provider.go @@ -26,6 +26,7 @@ import ( // ScmProvider wraps go-scm marketplace, manifest, and registry operations // as a service provider. It implements Provider, Streamable, Describable, // and Renderable. +// Usage example: var value ScmProvider type ScmProvider struct { index *marketplace.Index installer *marketplace.Installer @@ -45,6 +46,7 @@ var ( // NewProvider creates an SCM provider backed by the given marketplace index, // installer, and registry. The WS hub is used to emit real-time events. // Pass nil for any dependency that is not available. +// Usage example: NewProvider(...) func NewProvider(idx *marketplace.Index, inst *marketplace.Installer, reg *repos.Registry, hub *ws.Hub) *ScmProvider { return &ScmProvider{ index: idx, diff --git a/pkg/api/provider_handlers_test.go b/pkg/api/provider_handlers_test.go index 80fb970..8e7ef9d 100644 --- a/pkg/api/provider_handlers_test.go +++ b/pkg/api/provider_handlers_test.go @@ -3,7 +3,7 @@ package api_test import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/pkg/api/provider_test.go b/pkg/api/provider_test.go index 0674f8a..d3177dc 100644 --- a/pkg/api/provider_test.go +++ b/pkg/api/provider_test.go @@ -3,7 +3,7 @@ package api_test import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" "net/http/httptest" "testing" diff --git a/plugin/config.go b/plugin/config.go index 3155489..91fb88f 100644 --- a/plugin/config.go +++ b/plugin/config.go @@ -1,6 +1,7 @@ package plugin // PluginConfig holds configuration for a single installed plugin. +// Usage example: var value PluginConfig type PluginConfig struct { Name string `json:"name" yaml:"name"` Version string `json:"version" yaml:"version"` diff --git a/plugin/installer.go b/plugin/installer.go index 0be3233..c6259f0 100644 --- a/plugin/installer.go +++ b/plugin/installer.go @@ -2,11 +2,11 @@ package plugin import ( "context" - "fmt" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" + exec "golang.org/x/sys/execabs" "net/url" - "os/exec" - "path/filepath" - "strings" "time" "dappco.re/go/core/io" @@ -15,12 +15,14 @@ import ( ) // Installer handles plugin installation from GitHub. +// Usage example: var value Installer type Installer struct { medium io.Medium registry *Registry } // NewInstaller creates a new plugin installer. +// Usage example: NewInstaller(...) func NewInstaller(m io.Medium, registry *Registry) *Installer { return &Installer{ medium: m, @@ -178,6 +180,8 @@ func (i *Installer) cloneRepo(ctx context.Context, org, repo, version, dest stri // Accepted formats: // - "org/repo" -> org="org", repo="repo", version="" // - "org/repo@v1.0" -> org="org", repo="repo", version="v1.0" +// +// Usage example: ParseSource(...) func ParseSource(source string) (org, repo, version string, err error) { source, err = url.PathUnescape(source) if err != nil { diff --git a/plugin/loader.go b/plugin/loader.go index 3362886..3078404 100644 --- a/plugin/loader.go +++ b/plugin/loader.go @@ -1,19 +1,21 @@ package plugin import ( - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" ) // Loader loads plugins from the filesystem. +// Usage example: var value Loader type Loader struct { medium io.Medium baseDir string } // NewLoader creates a new plugin loader. +// Usage example: NewLoader(...) func NewLoader(m io.Medium, baseDir string) *Loader { return &Loader{ medium: m, diff --git a/plugin/manifest.go b/plugin/manifest.go index 4e87c6f..161d260 100644 --- a/plugin/manifest.go +++ b/plugin/manifest.go @@ -1,14 +1,15 @@ package plugin import ( - "encoding/json" + json "dappco.re/go/core/scm/internal/ax/jsonx" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" ) // Manifest represents a plugin.json manifest file. // Each plugin repository must contain a plugin.json at its root. +// Usage example: var value Manifest type Manifest struct { Name string `json:"name"` Version string `json:"version"` @@ -20,6 +21,7 @@ type Manifest struct { } // LoadManifest reads and parses a plugin.json file from the given path. +// Usage example: LoadManifest(...) func LoadManifest(m io.Medium, path string) (*Manifest, error) { content, err := m.Read(path) if err != nil { diff --git a/plugin/plugin.go b/plugin/plugin.go index 9f060ec..c1561b2 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -14,6 +14,7 @@ package plugin import "context" // Plugin is the interface that all plugins must implement. +// Usage example: var value Plugin type Plugin interface { // Name returns the plugin's unique identifier. Name() string @@ -33,6 +34,7 @@ type Plugin interface { // BasePlugin provides a default implementation of Plugin. // Embed this in concrete plugin types to inherit default behaviour. +// Usage example: var value BasePlugin type BasePlugin struct { PluginName string PluginVersion string diff --git a/plugin/registry.go b/plugin/registry.go index f81a025..12a1fd6 100644 --- a/plugin/registry.go +++ b/plugin/registry.go @@ -2,18 +2,19 @@ package plugin import ( "cmp" - "encoding/json" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + json "dappco.re/go/core/scm/internal/ax/jsonx" "slices" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" ) const registryFilename = "registry.json" // Registry manages installed plugins. // Plugin metadata is stored in a registry.json file under the base path. +// Usage example: var value Registry type Registry struct { medium io.Medium basePath string // e.g., ~/.core/plugins/ @@ -21,6 +22,7 @@ type Registry struct { } // NewRegistry creates a new plugin registry. +// Usage example: NewRegistry(...) func NewRegistry(m io.Medium, basePath string) *Registry { return &Registry{ medium: m, diff --git a/repos/gitstate.go b/repos/gitstate.go index 15d8436..01f5272 100644 --- a/repos/gitstate.go +++ b/repos/gitstate.go @@ -1,23 +1,25 @@ package repos import ( - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" "time" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) // GitState holds per-machine git sync state for a workspace. // Stored at .core/git.yaml and .gitignored (not shared across machines). +// Usage example: var value GitState type GitState struct { - Version int `yaml:"version"` - Repos map[string]*RepoGitState `yaml:"repos,omitempty"` - Agents map[string]*AgentState `yaml:"agents,omitempty"` + Version int `yaml:"version"` + Repos map[string]*RepoGitState `yaml:"repos,omitempty"` + Agents map[string]*AgentState `yaml:"agents,omitempty"` } // RepoGitState tracks the last known git state for a single repo. +// Usage example: var value RepoGitState type RepoGitState struct { LastPull time.Time `yaml:"last_pull,omitempty"` LastPush time.Time `yaml:"last_push,omitempty"` @@ -28,6 +30,7 @@ type RepoGitState struct { } // AgentState tracks which agent last touched which repos. +// Usage example: var value AgentState type AgentState struct { LastSeen time.Time `yaml:"last_seen"` Active []string `yaml:"active,omitempty"` @@ -35,6 +38,7 @@ type AgentState struct { // LoadGitState reads .core/git.yaml from the given workspace root directory. // Returns a new empty GitState if the file does not exist. +// Usage example: LoadGitState(...) func LoadGitState(m io.Medium, root string) (*GitState, error) { path := filepath.Join(root, ".core", "git.yaml") @@ -63,6 +67,7 @@ func LoadGitState(m io.Medium, root string) (*GitState, error) { } // SaveGitState writes .core/git.yaml to the given workspace root directory. +// Usage example: SaveGitState(...) func SaveGitState(m io.Medium, root string, gs *GitState) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil { @@ -83,6 +88,7 @@ func SaveGitState(m io.Medium, root string, gs *GitState) error { } // NewGitState returns a new empty GitState with version 1. +// Usage example: NewGitState(...) func NewGitState() *GitState { return &GitState{ Version: 1, diff --git a/repos/kbconfig.go b/repos/kbconfig.go index fd8ed3a..93195b2 100644 --- a/repos/kbconfig.go +++ b/repos/kbconfig.go @@ -1,16 +1,17 @@ package repos import ( - "fmt" - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + fmt "dappco.re/go/core/scm/internal/ax/fmtx" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) // KBConfig holds knowledge base configuration for a workspace. // Stored at .core/kb.yaml and checked into git. +// Usage example: var value KBConfig type KBConfig struct { Version int `yaml:"version"` Wiki WikiConfig `yaml:"wiki"` @@ -18,6 +19,7 @@ type KBConfig struct { } // WikiConfig controls local wiki mirror behaviour. +// Usage example: var value WikiConfig type WikiConfig struct { // Enabled toggles wiki cloning on sync. Enabled bool `yaml:"enabled"` @@ -29,6 +31,7 @@ type WikiConfig struct { } // KBSearch configures vector search against the OpenBrain Qdrant collection. +// Usage example: var value KBSearch type KBSearch struct { // QdrantHost is the Qdrant server (gRPC). QdrantHost string `yaml:"qdrant_host"` @@ -45,6 +48,7 @@ type KBSearch struct { } // DefaultKBConfig returns sensible defaults for knowledge base config. +// Usage example: DefaultKBConfig(...) func DefaultKBConfig() *KBConfig { return &KBConfig{ Version: 1, @@ -66,6 +70,7 @@ func DefaultKBConfig() *KBConfig { // LoadKBConfig reads .core/kb.yaml from the given workspace root directory. // Returns defaults if the file does not exist. +// Usage example: LoadKBConfig(...) func LoadKBConfig(m io.Medium, root string) (*KBConfig, error) { path := filepath.Join(root, ".core", "kb.yaml") @@ -87,6 +92,7 @@ func LoadKBConfig(m io.Medium, root string) (*KBConfig, error) { } // SaveKBConfig writes .core/kb.yaml to the given workspace root directory. +// Usage example: SaveKBConfig(...) func SaveKBConfig(m io.Medium, root string, kb *KBConfig) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil { diff --git a/repos/registry.go b/repos/registry.go index f6af2f7..20a0f4e 100644 --- a/repos/registry.go +++ b/repos/registry.go @@ -4,16 +4,17 @@ package repos import ( - "os" - "path/filepath" - "strings" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" + os "dappco.re/go/core/scm/internal/ax/osx" + strings "dappco.re/go/core/scm/internal/ax/stringsx" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) // Registry represents a collection of repositories defined in repos.yaml. +// Usage example: var value Registry type Registry struct { Version int `yaml:"version"` Org string `yaml:"org"` @@ -24,6 +25,7 @@ type Registry struct { } // RegistryDefaults contains default values applied to all repos. +// Usage example: var value RegistryDefaults type RegistryDefaults struct { CI string `yaml:"ci"` License string `yaml:"license"` @@ -31,21 +33,27 @@ type RegistryDefaults struct { } // RepoType indicates the role of a repository in the ecosystem. +// Usage example: var value RepoType type RepoType string // Repository type constants for ecosystem classification. const ( // RepoTypeFoundation indicates core foundation packages. + // Usage example: _ = RepoTypeFoundation RepoTypeFoundation RepoType = "foundation" // RepoTypeModule indicates reusable module packages. + // Usage example: _ = RepoTypeModule RepoTypeModule RepoType = "module" // RepoTypeProduct indicates end-user product applications. + // Usage example: _ = RepoTypeProduct RepoTypeProduct RepoType = "product" // RepoTypeTemplate indicates starter templates. + // Usage example: _ = RepoTypeTemplate RepoTypeTemplate RepoType = "template" ) // Repo represents a single repository in the registry. +// Usage example: var value Repo type Repo struct { Name string `yaml:"-"` // Set from map key Type string `yaml:"type"` @@ -63,6 +71,7 @@ type Repo struct { // LoadRegistry reads and parses a repos.yaml file from the given medium. // The path should be a valid path for the provided medium. +// Usage example: LoadRegistry(...) func LoadRegistry(m io.Medium, path string) (*Registry, error) { content, err := m.Read(path) if err != nil { @@ -102,6 +111,7 @@ func LoadRegistry(m io.Medium, path string) (*Registry, error) { // FindRegistry searches for repos.yaml in common locations. // It checks: current directory, parent directories, and home directory. // This function is primarily intended for use with io.Local or other local-like filesystems. +// Usage example: FindRegistry(...) func FindRegistry(m io.Medium) (string, error) { // Check current directory and parents dir, err := os.Getwd() @@ -152,6 +162,7 @@ func FindRegistry(m io.Medium) (string, error) { // ScanDirectory creates a Registry by scanning a directory for git repos. // This is used as a fallback when no repos.yaml is found. // The dir should be a valid path for the provided medium. +// Usage example: ScanDirectory(...) func ScanDirectory(m io.Medium, dir string) (*Registry, error) { entries, err := m.List(dir) if err != nil { diff --git a/repos/workconfig.go b/repos/workconfig.go index 7452245..9a764c7 100644 --- a/repos/workconfig.go +++ b/repos/workconfig.go @@ -1,24 +1,26 @@ package repos import ( - "path/filepath" + filepath "dappco.re/go/core/scm/internal/ax/filepathx" "time" - coreerr "dappco.re/go/core/log" "dappco.re/go/core/io" + coreerr "dappco.re/go/core/log" "gopkg.in/yaml.v3" ) // WorkConfig holds sync policy for a workspace. // Stored at .core/work.yaml and checked into git (shared across the team). +// Usage example: var value WorkConfig type WorkConfig struct { - Version int `yaml:"version"` - Sync SyncConfig `yaml:"sync"` - Agents AgentPolicy `yaml:"agents"` - Triggers []string `yaml:"triggers,omitempty"` + Version int `yaml:"version"` + Sync SyncConfig `yaml:"sync"` + Agents AgentPolicy `yaml:"agents"` + Triggers []string `yaml:"triggers,omitempty"` } // SyncConfig controls how and when repos are synced. +// Usage example: var value SyncConfig type SyncConfig struct { Interval time.Duration `yaml:"interval"` AutoPull bool `yaml:"auto_pull"` @@ -27,13 +29,15 @@ type SyncConfig struct { } // AgentPolicy controls multi-agent clash prevention. +// Usage example: var value AgentPolicy type AgentPolicy struct { - Heartbeat time.Duration `yaml:"heartbeat"` - StaleAfter time.Duration `yaml:"stale_after"` - WarnOnOverlap bool `yaml:"warn_on_overlap"` + Heartbeat time.Duration `yaml:"heartbeat"` + StaleAfter time.Duration `yaml:"stale_after"` + WarnOnOverlap bool `yaml:"warn_on_overlap"` } // DefaultWorkConfig returns sensible defaults for workspace sync. +// Usage example: DefaultWorkConfig(...) func DefaultWorkConfig() *WorkConfig { return &WorkConfig{ Version: 1, @@ -54,6 +58,7 @@ func DefaultWorkConfig() *WorkConfig { // LoadWorkConfig reads .core/work.yaml from the given workspace root directory. // Returns defaults if the file does not exist. +// Usage example: LoadWorkConfig(...) func LoadWorkConfig(m io.Medium, root string) (*WorkConfig, error) { path := filepath.Join(root, ".core", "work.yaml") @@ -75,6 +80,7 @@ func LoadWorkConfig(m io.Medium, root string) (*WorkConfig, error) { } // SaveWorkConfig writes .core/work.yaml to the given workspace root directory. +// Usage example: SaveWorkConfig(...) func SaveWorkConfig(m io.Medium, root string, wc *WorkConfig) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil {