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 {