diff --git a/agentci/clotho.go b/agentci/clotho.go index e687a43..ac70b04 100644 --- a/agentci/clotho.go +++ b/agentci/clotho.go @@ -26,6 +26,7 @@ type Spinner struct { } // NewSpinner creates a new Clotho orchestrator. +// Usage: NewSpinner(...) func NewSpinner(cfg ClothoConfig, agents map[string]AgentConfig) *Spinner { return &Spinner{ Config: cfg, @@ -35,6 +36,7 @@ func NewSpinner(cfg ClothoConfig, agents map[string]AgentConfig) *Spinner { // DeterminePlan decides if a signal requires dual-run verification based on // the global strategy, agent configuration, and repository criticality. +// Usage: DeterminePlan(...) func (s *Spinner) DeterminePlan(signal *jobrunner.PipelineSignal, agentName string) RunMode { if s.Config.Strategy != "clotho-verified" { return ModeStandard @@ -57,6 +59,7 @@ func (s *Spinner) DeterminePlan(signal *jobrunner.PipelineSignal, agentName stri } // GetVerifierModel returns the model for the secondary "signed" verification run. +// Usage: GetVerifierModel(...) func (s *Spinner) GetVerifierModel(agentName string) string { agent, ok := s.Agents[agentName] if !ok || agent.VerifyModel == "" { @@ -67,6 +70,7 @@ func (s *Spinner) GetVerifierModel(agentName string) string { // FindByForgejoUser resolves a Forgejo username to the agent config key and config. // This decouples agent naming (mythological roles) from Forgejo identity. +// Usage: FindByForgejoUser(...) func (s *Spinner) FindByForgejoUser(forgejoUser string) (string, AgentConfig, bool) { if forgejoUser == "" { return "", AgentConfig{}, false @@ -86,6 +90,7 @@ func (s *Spinner) FindByForgejoUser(forgejoUser string) (string, AgentConfig, bo // Weave compares primary and verifier outputs. Returns true if they converge. // This is a placeholder for future semantic diff logic. +// Usage: Weave(...) func (s *Spinner) Weave(ctx context.Context, primaryOutput, signedOutput []byte) (bool, error) { return string(primaryOutput) == string(signedOutput), nil } diff --git a/agentci/config.go b/agentci/config.go index fdd1940..a43a84f 100644 --- a/agentci/config.go +++ b/agentci/config.go @@ -33,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: LoadAgents(...) func LoadAgents(cfg *config.Config) (map[string]AgentConfig, error) { var agents map[string]AgentConfig if err := cfg.Get("agentci.agents", &agents); err != nil { @@ -63,6 +64,7 @@ func LoadAgents(cfg *config.Config) (map[string]AgentConfig, error) { } // LoadActiveAgents returns only active agents. +// Usage: LoadActiveAgents(...) func LoadActiveAgents(cfg *config.Config) (map[string]AgentConfig, error) { all, err := LoadAgents(cfg) if err != nil { @@ -79,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: LoadClothoConfig(...) func LoadClothoConfig(cfg *config.Config) (ClothoConfig, error) { var cc ClothoConfig if err := cfg.Get("agentci.clotho", &cc); err != nil { @@ -97,6 +100,7 @@ func LoadClothoConfig(cfg *config.Config) (ClothoConfig, error) { } // SaveAgent writes an agent config entry to the config file. +// Usage: SaveAgent(...) func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error { key := fmt.Sprintf("agentci.agents.%s", name) data := map[string]any{ @@ -125,6 +129,7 @@ func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error { } // RemoveAgent removes an agent from the config file. +// Usage: RemoveAgent(...) func RemoveAgent(cfg *config.Config, name string) error { var agents map[string]AgentConfig if err := cfg.Get("agentci.agents", &agents); err != nil { @@ -138,6 +143,7 @@ func RemoveAgent(cfg *config.Config, name string) error { } // ListAgents returns all configured agents (active and inactive). +// Usage: 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/security.go b/agentci/security.go index 356d248..16ae397 100644 --- a/agentci/security.go +++ b/agentci/security.go @@ -16,6 +16,7 @@ var safeNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\.]+$`) // SanitizePath ensures a filename or directory name is safe and prevents path traversal. // Returns the validated input unchanged. +// Usage: SanitizePath(...) func SanitizePath(input string) (string, error) { if input == "" { return "", coreerr.E("agentci.SanitizePath", "path element is required", nil) @@ -33,11 +34,13 @@ func SanitizePath(input string) (string, error) { } // ValidatePathElement validates a single local path element and returns its safe form. +// Usage: ValidatePathElement(...) func ValidatePathElement(input string) (string, error) { return SanitizePath(input) } // ResolvePathWithinRoot resolves a validated path element beneath a root directory. +// Usage: ResolvePathWithinRoot(...) func ResolvePathWithinRoot(root string, input string) (string, string, error) { safeName, err := ValidatePathElement(input) if err != nil { @@ -60,6 +63,7 @@ func ResolvePathWithinRoot(root string, input string) (string, string, error) { } // ValidateRemoteDir validates a remote directory path used over SSH. +// Usage: ValidateRemoteDir(...) func ValidateRemoteDir(dir string) (string, error) { if strings.TrimSpace(dir) == "" { return "", coreerr.E("agentci.ValidateRemoteDir", "directory is required", nil) @@ -107,6 +111,7 @@ func ValidateRemoteDir(dir string) (string, error) { } // JoinRemotePath joins validated remote path elements using forward slashes. +// Usage: JoinRemotePath(...) func JoinRemotePath(base string, parts ...string) (string, error) { safeBase, err := ValidateRemoteDir(base) if err != nil { @@ -133,11 +138,13 @@ func JoinRemotePath(base string, parts ...string) (string, error) { // EscapeShellArg wraps a string in single quotes for safe remote shell insertion. // Prefer exec.Command arguments over constructing shell strings where possible. +// Usage: 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: SecureSSHCommand(...) func SecureSSHCommand(host string, remoteCmd string) *exec.Cmd { return exec.Command("ssh", "-o", "StrictHostKeyChecking=yes", @@ -149,6 +156,7 @@ func SecureSSHCommand(host string, remoteCmd string) *exec.Cmd { } // MaskToken returns a masked version of a token for safe logging. +// Usage: MaskToken(...) func MaskToken(token string) string { if len(token) < 8 { return "*****" diff --git a/cmd/collect/cmd.go b/cmd/collect/cmd.go index 79a31d1..bdbc87c 100644 --- a/cmd/collect/cmd.go +++ b/cmd/collect/cmd.go @@ -30,6 +30,7 @@ var ( ) // AddCollectCommands registers the 'collect' command and all subcommands. +// Usage: AddCollectCommands(...) func AddCollectCommands(root *cli.Command) { collectCmd := &cli.Command{ Use: "collect", diff --git a/cmd/forge/cmd_forge.go b/cmd/forge/cmd_forge.go index 37ee9f5..6bcb112 100644 --- a/cmd/forge/cmd_forge.go +++ b/cmd/forge/cmd_forge.go @@ -35,6 +35,7 @@ var ( ) // AddForgeCommands registers the 'forge' command and all subcommands. +// Usage: AddForgeCommands(...) func AddForgeCommands(root *cli.Command) { forgeCmd := &cli.Command{ Use: "forge", diff --git a/cmd/gitea/cmd_gitea.go b/cmd/gitea/cmd_gitea.go index 2772d25..34c1381 100644 --- a/cmd/gitea/cmd_gitea.go +++ b/cmd/gitea/cmd_gitea.go @@ -32,6 +32,7 @@ var ( ) // AddGiteaCommands registers the 'gitea' command and all subcommands. +// Usage: AddGiteaCommands(...) func AddGiteaCommands(root *cli.Command) { giteaCmd := &cli.Command{ Use: "gitea", diff --git a/cmd/scm/cmd_scm.go b/cmd/scm/cmd_scm.go index 937222d..611ddc1 100644 --- a/cmd/scm/cmd_scm.go +++ b/cmd/scm/cmd_scm.go @@ -27,6 +27,7 @@ var ( ) // AddScmCommands registers the 'scm' command and all subcommands. +// Usage: AddScmCommands(...) func AddScmCommands(root *cli.Command) { scmCmd := &cli.Command{ Use: "scm", diff --git a/collect/bitcointalk.go b/collect/bitcointalk.go index 8c5aa00..1e3b335 100644 --- a/collect/bitcointalk.go +++ b/collect/bitcointalk.go @@ -35,6 +35,7 @@ type BitcoinTalkCollector struct { } // Name returns the collector name. +// Usage: Name(...) func (b *BitcoinTalkCollector) Name() string { id := b.TopicID if id == "" && b.URL != "" { @@ -44,6 +45,7 @@ func (b *BitcoinTalkCollector) Name() string { } // Collect gathers posts from a BitcoinTalk topic. +// Usage: Collect(...) func (b *BitcoinTalkCollector) Collect(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: b.Name()} @@ -283,6 +285,7 @@ func formatPostMarkdown(num int, post btPost) string { // ParsePostsFromHTML parses BitcoinTalk posts from raw HTML content. // This is exported for testing purposes. +// Usage: ParsePostsFromHTML(...) func ParsePostsFromHTML(htmlContent string) ([]btPost, error) { doc, err := html.Parse(strings.NewReader(htmlContent)) if err != nil { @@ -292,6 +295,7 @@ func ParsePostsFromHTML(htmlContent string) ([]btPost, error) { } // FormatPostMarkdown is exported for testing purposes. +// Usage: FormatPostMarkdown(...) func FormatPostMarkdown(num int, author, date, content string) string { return formatPostMarkdown(num, btPost{Author: author, Date: date, Content: content}) } @@ -307,6 +311,7 @@ type BitcoinTalkCollectorWithFetcher struct { // SetHTTPClient replaces the package-level HTTP client. // Use this in tests to inject a custom transport or timeout. +// Usage: SetHTTPClient(...) func SetHTTPClient(c *http.Client) { httpClient = c } diff --git a/collect/collect.go b/collect/collect.go index dd48b3b..df01992 100644 --- a/collect/collect.go +++ b/collect/collect.go @@ -67,6 +67,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: NewConfig(...) func NewConfig(outputDir string) *Config { m := io.NewMockMedium() return &Config{ @@ -79,6 +80,7 @@ func NewConfig(outputDir string) *Config { } // NewConfigWithMedium creates a Config using the specified storage medium. +// Usage: NewConfigWithMedium(...) func NewConfigWithMedium(m io.Medium, outputDir string) *Config { return &Config{ Output: m, @@ -90,6 +92,7 @@ func NewConfigWithMedium(m io.Medium, outputDir string) *Config { } // MergeResults combines multiple results into a single aggregated result. +// Usage: MergeResults(...) func MergeResults(source string, results ...*Result) *Result { merged := &Result{Source: source} for _, r := range results { diff --git a/collect/events.go b/collect/events.go index c2b0ac3..63fcd15 100644 --- a/collect/events.go +++ b/collect/events.go @@ -59,6 +59,7 @@ type Dispatcher struct { } // NewDispatcher creates a new event dispatcher. +// Usage: NewDispatcher(...) func NewDispatcher() *Dispatcher { return &Dispatcher{ handlers: make(map[string][]EventHandler), @@ -67,6 +68,7 @@ func NewDispatcher() *Dispatcher { // On registers a handler for an event type. Multiple handlers can be // registered for the same event type and will be called in order. +// Usage: On(...) func (d *Dispatcher) On(eventType string, handler EventHandler) { d.mu.Lock() defer d.mu.Unlock() @@ -76,6 +78,7 @@ func (d *Dispatcher) On(eventType string, handler EventHandler) { // Emit dispatches an event to all registered handlers for that event type. // If no handlers are registered for the event type, the event is silently dropped. // The event's Time field is set to now if it is zero. +// Usage: Emit(...) func (d *Dispatcher) Emit(event Event) { if event.Time.IsZero() { event.Time = time.Now() @@ -91,6 +94,7 @@ func (d *Dispatcher) Emit(event Event) { } // EmitStart emits a start event for the given source. +// Usage: EmitStart(...) func (d *Dispatcher) EmitStart(source, message string) { d.Emit(Event{ Type: EventStart, @@ -100,6 +104,7 @@ func (d *Dispatcher) EmitStart(source, message string) { } // EmitProgress emits a progress event. +// Usage: EmitProgress(...) func (d *Dispatcher) EmitProgress(source, message string, data any) { d.Emit(Event{ Type: EventProgress, @@ -110,6 +115,7 @@ func (d *Dispatcher) EmitProgress(source, message string, data any) { } // EmitItem emits an item event. +// Usage: EmitItem(...) func (d *Dispatcher) EmitItem(source, message string, data any) { d.Emit(Event{ Type: EventItem, @@ -120,6 +126,7 @@ func (d *Dispatcher) EmitItem(source, message string, data any) { } // EmitError emits an error event. +// Usage: EmitError(...) func (d *Dispatcher) EmitError(source, message string, data any) { d.Emit(Event{ Type: EventError, @@ -130,6 +137,7 @@ func (d *Dispatcher) EmitError(source, message string, data any) { } // EmitComplete emits a complete event. +// Usage: EmitComplete(...) func (d *Dispatcher) EmitComplete(source, message string, data any) { d.Emit(Event{ Type: EventComplete, diff --git a/collect/excavate.go b/collect/excavate.go index 8a5a693..20506ae 100644 --- a/collect/excavate.go +++ b/collect/excavate.go @@ -25,12 +25,14 @@ type Excavator struct { } // Name returns the orchestrator name. +// Usage: Name(...) func (e *Excavator) Name() string { return "excavator" } // Run executes all collectors sequentially, respecting rate limits and // using state for resume support. Results are aggregated from all collectors. +// Usage: Run(...) func (e *Excavator) Run(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: e.Name()} diff --git a/collect/github.go b/collect/github.go index d2619fa..497a4aa 100644 --- a/collect/github.go +++ b/collect/github.go @@ -55,6 +55,7 @@ type GitHubCollector struct { } // Name returns the collector name. +// Usage: Name(...) func (g *GitHubCollector) Name() string { if g.Repo != "" { return fmt.Sprintf("github:%s/%s", g.Org, g.Repo) @@ -63,6 +64,7 @@ func (g *GitHubCollector) Name() string { } // Collect gathers issues and/or PRs from GitHub repositories. +// Usage: Collect(...) func (g *GitHubCollector) Collect(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: g.Name()} diff --git a/collect/market.go b/collect/market.go index 84c15e6..cbeec05 100644 --- a/collect/market.go +++ b/collect/market.go @@ -31,6 +31,7 @@ type MarketCollector struct { } // Name returns the collector name. +// Usage: Name(...) func (m *MarketCollector) Name() string { return fmt.Sprintf("market:%s", m.CoinID) } @@ -65,6 +66,7 @@ type historicalData struct { } // Collect gathers market data from CoinGecko. +// Usage: Collect(...) func (m *MarketCollector) Collect(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: m.Name()} @@ -274,6 +276,7 @@ func formatMarketSummary(data *coinData) string { } // FormatMarketSummary is exported for testing. +// Usage: FormatMarketSummary(...) func FormatMarketSummary(data *coinData) string { return formatMarketSummary(data) } diff --git a/collect/papers.go b/collect/papers.go index d4eb9ce..853e209 100644 --- a/collect/papers.go +++ b/collect/papers.go @@ -39,6 +39,7 @@ type PapersCollector struct { } // Name returns the collector name. +// Usage: Name(...) func (p *PapersCollector) Name() string { return fmt.Sprintf("papers:%s", p.Source) } @@ -55,6 +56,7 @@ type paper struct { } // Collect gathers papers from the configured sources. +// Usage: Collect(...) func (p *PapersCollector) Collect(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: p.Name()} @@ -408,6 +410,7 @@ func formatPaperMarkdown(ppr paper) string { } // FormatPaperMarkdown is exported for testing. +// Usage: FormatPaperMarkdown(...) func FormatPaperMarkdown(title string, authors []string, date, paperURL, source, abstract string) string { return formatPaperMarkdown(paper{ Title: title, diff --git a/collect/process.go b/collect/process.go index 911d212..ad76eda 100644 --- a/collect/process.go +++ b/collect/process.go @@ -25,12 +25,14 @@ type Processor struct { } // Name returns the processor name. +// Usage: Name(...) func (p *Processor) Name() string { return fmt.Sprintf("process:%s", p.Source) } // Process reads files from the source directory, converts HTML or JSON // to clean markdown, and writes the results to the output directory. +// Usage: Process(...) func (p *Processor) Process(ctx context.Context, cfg *Config) (*Result, error) { result := &Result{Source: p.Name()} @@ -333,11 +335,13 @@ func jsonValueToMarkdown(b *strings.Builder, data any, depth int) { } // HTMLToMarkdown is exported for testing. +// Usage: HTMLToMarkdown(...) func HTMLToMarkdown(content string) (string, error) { return htmlToMarkdown(content) } // JSONToMarkdown is exported for testing. +// Usage: JSONToMarkdown(...) func JSONToMarkdown(content string) (string, error) { return jsonToMarkdown(content) } diff --git a/collect/ratelimit.go b/collect/ratelimit.go index 2406c5b..2c3d0e1 100644 --- a/collect/ratelimit.go +++ b/collect/ratelimit.go @@ -32,6 +32,7 @@ var defaultDelays = map[string]time.Duration{ } // NewRateLimiter creates a limiter with default delays. +// Usage: 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 17f2886..403412f 100644 --- a/collect/state.go +++ b/collect/state.go @@ -41,6 +41,7 @@ type StateEntry struct { // NewState creates a state tracker that persists to the given path // using the provided storage medium. +// Usage: NewState(...) func NewState(m io.Medium, path string) *State { return &State{ medium: m, @@ -51,6 +52,7 @@ func NewState(m io.Medium, path string) *State { // Load reads state from disk. If the file does not exist, the state // is initialised as empty without error. +// Usage: Load(...) func (s *State) Load() error { s.mu.Lock() defer s.mu.Unlock() @@ -77,6 +79,7 @@ func (s *State) Load() error { } // Save writes state to disk. +// Usage: Save(...) func (s *State) Save() error { s.mu.Lock() defer s.mu.Unlock() @@ -95,6 +98,7 @@ func (s *State) Save() error { // Get returns a copy of the state for a source. The second return value // indicates whether the entry was found. +// Usage: Get(...) func (s *State) Get(source string) (*StateEntry, bool) { s.mu.Lock() defer s.mu.Unlock() @@ -108,6 +112,7 @@ func (s *State) Get(source string) (*StateEntry, bool) { } // Set updates state for a source. +// Usage: Set(...) func (s *State) Set(source string, entry *StateEntry) { s.mu.Lock() defer s.mu.Unlock() diff --git a/forge/client.go b/forge/client.go index aa760ac..1f73852 100644 --- a/forge/client.go +++ b/forge/client.go @@ -24,6 +24,7 @@ type Client struct { } // New creates a new Forgejo API client for the given URL and token. +// Usage: New(...) func New(url, token string) (*Client, error) { api, err := forgejo.NewClient(url, forgejo.SetToken(token)) if err != nil { diff --git a/forge/config.go b/forge/config.go index 41059fd..cbcbc7c 100644 --- a/forge/config.go +++ b/forge/config.go @@ -27,6 +27,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: NewFromConfig(...) func NewFromConfig(flagURL, flagToken string) (*Client, error) { url, token, err := ResolveConfig(flagURL, flagToken) if err != nil { @@ -42,6 +44,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: ResolveConfig(...) func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // Start with config file values cfg, cfgErr := config.New() @@ -75,6 +78,7 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { } // SaveConfig persists the Forgejo URL and/or token to the config file. +// Usage: SaveConfig(...) func SaveConfig(url, token string) error { cfg, err := config.New() if err != nil { diff --git a/forge/issues.go b/forge/issues.go index 87c579d..30cf1ef 100644 --- a/forge/issues.go +++ b/forge/issues.go @@ -19,6 +19,7 @@ type ListIssuesOpts struct { } // ListIssues returns issues for the given repository. +// Usage: ListIssues(...) func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo.Issue, error) { state := forgejo.StateOpen switch opts.State { @@ -54,6 +55,7 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo } // GetIssue returns a single issue by number. +// Usage: GetIssue(...) func (c *Client) GetIssue(owner, repo string, number int64) (*forgejo.Issue, error) { issue, _, err := c.api.GetIssue(owner, repo, number) if err != nil { @@ -64,6 +66,7 @@ func (c *Client) GetIssue(owner, repo string, number int64) (*forgejo.Issue, err } // CreateIssue creates a new issue in the given repository. +// Usage: CreateIssue(...) func (c *Client) CreateIssue(owner, repo string, opts forgejo.CreateIssueOption) (*forgejo.Issue, error) { issue, _, err := c.api.CreateIssue(owner, repo, opts) if err != nil { @@ -74,6 +77,7 @@ func (c *Client) CreateIssue(owner, repo string, opts forgejo.CreateIssueOption) } // EditIssue edits an existing issue. +// Usage: EditIssue(...) func (c *Client) EditIssue(owner, repo string, number int64, opts forgejo.EditIssueOption) (*forgejo.Issue, error) { issue, _, err := c.api.EditIssue(owner, repo, number, opts) if err != nil { @@ -84,6 +88,7 @@ func (c *Client) EditIssue(owner, repo string, number int64, opts forgejo.EditIs } // AssignIssue assigns an issue to the specified users. +// Usage: AssignIssue(...) func (c *Client) AssignIssue(owner, repo string, number int64, assignees []string) error { _, _, err := c.api.EditIssue(owner, repo, number, forgejo.EditIssueOption{ Assignees: assignees, @@ -95,6 +100,7 @@ func (c *Client) AssignIssue(owner, repo string, number int64, assignees []strin } // ListPullRequests returns pull requests for the given repository. +// Usage: ListPullRequests(...) func (c *Client) ListPullRequests(owner, repo string, state string) ([]*forgejo.PullRequest, error) { st := forgejo.StateOpen switch state { @@ -128,6 +134,7 @@ func (c *Client) ListPullRequests(owner, repo string, state string) ([]*forgejo. } // ListPullRequestsIter returns an iterator over pull requests for the given repository. +// Usage: ListPullRequestsIter(...) func (c *Client) ListPullRequestsIter(owner, repo string, state string) iter.Seq2[*forgejo.PullRequest, error] { st := forgejo.StateOpen switch state { @@ -162,6 +169,7 @@ func (c *Client) ListPullRequestsIter(owner, repo string, state string) iter.Seq } // GetPullRequest returns a single pull request by number. +// Usage: GetPullRequest(...) func (c *Client) GetPullRequest(owner, repo string, number int64) (*forgejo.PullRequest, error) { pr, _, err := c.api.GetPullRequest(owner, repo, number) if err != nil { @@ -172,6 +180,7 @@ func (c *Client) GetPullRequest(owner, repo string, number int64) (*forgejo.Pull } // CreateIssueComment posts a comment on an issue or pull request. +// Usage: CreateIssueComment(...) func (c *Client) CreateIssueComment(owner, repo string, issue int64, body string) error { _, _, err := c.api.CreateIssueComment(owner, repo, issue, forgejo.CreateIssueCommentOption{ Body: body, @@ -183,6 +192,7 @@ func (c *Client) CreateIssueComment(owner, repo string, issue int64, body string } // ListIssueComments returns comments for an issue. +// Usage: ListIssueComments(...) func (c *Client) ListIssueComments(owner, repo string, number int64) ([]*forgejo.Comment, error) { var all []*forgejo.Comment page := 1 @@ -207,6 +217,7 @@ func (c *Client) ListIssueComments(owner, repo string, number int64) ([]*forgejo } // CloseIssue closes an issue by setting its state to closed. +// Usage: CloseIssue(...) func (c *Client) CloseIssue(owner, repo string, number int64) error { closed := forgejo.StateClosed _, _, err := c.api.EditIssue(owner, repo, number, forgejo.EditIssueOption{ diff --git a/forge/labels.go b/forge/labels.go index 377a526..0b7ea5c 100644 --- a/forge/labels.go +++ b/forge/labels.go @@ -14,6 +14,7 @@ import ( // Note: The Forgejo SDK does not have a dedicated org-level labels endpoint. // This lists labels from the first repo found, which works when orgs use shared label sets. // For org-wide label management, use ListRepoLabels with a specific repo. +// Usage: ListOrgLabels(...) func (c *Client) ListOrgLabels(org string) ([]*forgejo.Label, error) { // Forgejo doesn't expose org-level labels via SDK — list repos and aggregate unique labels. repos, err := c.ListOrgRepos(org) @@ -30,6 +31,7 @@ func (c *Client) ListOrgLabels(org string) ([]*forgejo.Label, error) { } // ListRepoLabels returns all labels for a repository. +// Usage: ListRepoLabels(...) func (c *Client) ListRepoLabels(owner, repo string) ([]*forgejo.Label, error) { var all []*forgejo.Label page := 1 @@ -54,6 +56,7 @@ func (c *Client) ListRepoLabels(owner, repo string) ([]*forgejo.Label, error) { } // CreateRepoLabel creates a label on a repository. +// Usage: CreateRepoLabel(...) func (c *Client) CreateRepoLabel(owner, repo string, opts forgejo.CreateLabelOption) (*forgejo.Label, error) { label, _, err := c.api.CreateLabel(owner, repo, opts) if err != nil { @@ -64,6 +67,7 @@ func (c *Client) CreateRepoLabel(owner, repo string, opts forgejo.CreateLabelOpt } // GetLabelByName retrieves a specific label by name from a repository. +// Usage: GetLabelByName(...) func (c *Client) GetLabelByName(owner, repo, name string) (*forgejo.Label, error) { labels, err := c.ListRepoLabels(owner, repo) if err != nil { @@ -80,6 +84,7 @@ func (c *Client) GetLabelByName(owner, repo, name string) (*forgejo.Label, error } // EnsureLabel checks if a label exists, and creates it if it doesn't. +// Usage: EnsureLabel(...) func (c *Client) EnsureLabel(owner, repo, name, color string) (*forgejo.Label, error) { label, err := c.GetLabelByName(owner, repo, name) if err == nil { @@ -93,6 +98,7 @@ func (c *Client) EnsureLabel(owner, repo, name, color string) (*forgejo.Label, e } // AddIssueLabels adds labels to an issue. +// Usage: AddIssueLabels(...) func (c *Client) AddIssueLabels(owner, repo string, number int64, labelIDs []int64) error { _, _, err := c.api.AddIssueLabels(owner, repo, number, forgejo.IssueLabelsOption{ Labels: labelIDs, @@ -104,6 +110,7 @@ func (c *Client) AddIssueLabels(owner, repo string, number int64, labelIDs []int } // RemoveIssueLabel removes a label from an issue. +// Usage: RemoveIssueLabel(...) func (c *Client) RemoveIssueLabel(owner, repo string, number int64, labelID int64) error { _, err := c.api.DeleteIssueLabel(owner, repo, number, labelID) if err != nil { diff --git a/forge/orgs.go b/forge/orgs.go index dfea3d2..1e59c3c 100644 --- a/forge/orgs.go +++ b/forge/orgs.go @@ -9,6 +9,7 @@ import ( ) // ListMyOrgs returns all organisations for the authenticated user. +// Usage: ListMyOrgs(...) func (c *Client) ListMyOrgs() ([]*forgejo.Organization, error) { var all []*forgejo.Organization page := 1 @@ -33,6 +34,7 @@ func (c *Client) ListMyOrgs() ([]*forgejo.Organization, error) { } // GetOrg returns a single organisation by name. +// Usage: GetOrg(...) func (c *Client) GetOrg(name string) (*forgejo.Organization, error) { org, _, err := c.api.GetOrg(name) if err != nil { @@ -43,6 +45,7 @@ func (c *Client) GetOrg(name string) (*forgejo.Organization, error) { } // CreateOrg creates a new organisation. +// Usage: CreateOrg(...) func (c *Client) CreateOrg(opts forgejo.CreateOrgOption) (*forgejo.Organization, error) { org, _, err := c.api.CreateOrg(opts) if err != nil { diff --git a/forge/prs.go b/forge/prs.go index 771c5ee..b8af916 100644 --- a/forge/prs.go +++ b/forge/prs.go @@ -17,6 +17,7 @@ import ( ) // MergePullRequest merges a pull request with the given method ("squash", "rebase", "merge"). +// Usage: MergePullRequest(...) func (c *Client) MergePullRequest(owner, repo string, index int64, method string) error { style := forgejo.MergeStyleMerge switch method { @@ -42,6 +43,7 @@ func (c *Client) MergePullRequest(owner, repo string, index int64, method string // SetPRDraft sets or clears the draft status on a pull request. // The Forgejo SDK v2.2.0 doesn't expose the draft field on EditPullRequestOption, // so we use a raw HTTP PATCH request. +// Usage: SetPRDraft(...) func (c *Client) SetPRDraft(owner, repo string, index int64, draft bool) error { safeOwner, err := agentci.ValidatePathElement(owner) if err != nil { @@ -83,6 +85,7 @@ func (c *Client) SetPRDraft(owner, repo string, index int64, draft bool) error { } // ListPRReviews returns all reviews for a pull request. +// Usage: ListPRReviews(...) func (c *Client) ListPRReviews(owner, repo string, index int64) ([]*forgejo.PullReview, error) { var all []*forgejo.PullReview page := 1 @@ -107,6 +110,7 @@ func (c *Client) ListPRReviews(owner, repo string, index int64) ([]*forgejo.Pull } // GetCombinedStatus returns the combined commit status for a ref (SHA or branch). +// Usage: GetCombinedStatus(...) func (c *Client) GetCombinedStatus(owner, repo string, ref string) (*forgejo.CombinedStatus, error) { status, _, err := c.api.GetCombinedStatus(owner, repo, ref) if err != nil { @@ -116,6 +120,7 @@ func (c *Client) GetCombinedStatus(owner, repo string, ref string) (*forgejo.Com } // DismissReview dismisses a pull request review by ID. +// Usage: DismissReview(...) func (c *Client) DismissReview(owner, repo string, index, reviewID int64, message string) error { _, err := c.api.DismissPullReview(owner, repo, index, reviewID, forgejo.DismissPullReviewOptions{ Message: message, diff --git a/forge/repos.go b/forge/repos.go index 5cd07c3..33ed7e9 100644 --- a/forge/repos.go +++ b/forge/repos.go @@ -11,6 +11,7 @@ import ( ) // ListOrgRepos returns all repositories for the given organisation. +// Usage: ListOrgRepos(...) func (c *Client) ListOrgRepos(org string) ([]*forgejo.Repository, error) { var all []*forgejo.Repository page := 1 @@ -35,6 +36,7 @@ func (c *Client) ListOrgRepos(org string) ([]*forgejo.Repository, error) { } // ListOrgReposIter returns an iterator over repositories for the given organisation. +// Usage: ListOrgReposIter(...) func (c *Client) ListOrgReposIter(org string) iter.Seq2[*forgejo.Repository, error] { return func(yield func(*forgejo.Repository, error) bool) { page := 1 @@ -60,6 +62,7 @@ func (c *Client) ListOrgReposIter(org string) iter.Seq2[*forgejo.Repository, err } // ListUserRepos returns all repositories for the authenticated user. +// Usage: ListUserRepos(...) func (c *Client) ListUserRepos() ([]*forgejo.Repository, error) { var all []*forgejo.Repository page := 1 @@ -84,6 +87,7 @@ func (c *Client) ListUserRepos() ([]*forgejo.Repository, error) { } // ListUserReposIter returns an iterator over repositories for the authenticated user. +// Usage: ListUserReposIter(...) func (c *Client) ListUserReposIter() iter.Seq2[*forgejo.Repository, error] { return func(yield func(*forgejo.Repository, error) bool) { page := 1 @@ -109,6 +113,7 @@ func (c *Client) ListUserReposIter() iter.Seq2[*forgejo.Repository, error] { } // GetRepo returns a single repository by owner and name. +// Usage: GetRepo(...) func (c *Client) GetRepo(owner, name string) (*forgejo.Repository, error) { repo, _, err := c.api.GetRepo(owner, name) if err != nil { @@ -119,6 +124,7 @@ func (c *Client) GetRepo(owner, name string) (*forgejo.Repository, error) { } // CreateOrgRepo creates a new empty repository under an organisation. +// Usage: CreateOrgRepo(...) func (c *Client) CreateOrgRepo(org string, opts forgejo.CreateRepoOption) (*forgejo.Repository, error) { repo, _, err := c.api.CreateOrgRepo(org, opts) if err != nil { @@ -129,6 +135,7 @@ func (c *Client) CreateOrgRepo(org string, opts forgejo.CreateRepoOption) (*forg } // DeleteRepo deletes a repository from Forgejo. +// Usage: DeleteRepo(...) func (c *Client) DeleteRepo(owner, name string) error { _, err := c.api.DeleteRepo(owner, name) if err != nil { @@ -140,6 +147,7 @@ func (c *Client) DeleteRepo(owner, name string) error { // MigrateRepo migrates a repository from an external service using the Forgejo migration API. // Unlike CreateMirror, this supports importing issues, labels, PRs, and more. +// Usage: MigrateRepo(...) func (c *Client) MigrateRepo(opts forgejo.MigrateRepoOption) (*forgejo.Repository, error) { repo, _, err := c.api.MigrateRepo(opts) if err != nil { diff --git a/forge/webhooks.go b/forge/webhooks.go index 9dcf0fe..3c3abb1 100644 --- a/forge/webhooks.go +++ b/forge/webhooks.go @@ -9,6 +9,7 @@ import ( ) // CreateRepoWebhook creates a webhook on a repository. +// Usage: CreateRepoWebhook(...) func (c *Client) CreateRepoWebhook(owner, repo string, opts forgejo.CreateHookOption) (*forgejo.Hook, error) { hook, _, err := c.api.CreateRepoHook(owner, repo, opts) if err != nil { @@ -19,6 +20,7 @@ func (c *Client) CreateRepoWebhook(owner, repo string, opts forgejo.CreateHookOp } // ListRepoWebhooks returns all webhooks for a repository. +// Usage: ListRepoWebhooks(...) func (c *Client) ListRepoWebhooks(owner, repo string) ([]*forgejo.Hook, error) { var all []*forgejo.Hook page := 1 diff --git a/git/git.go b/git/git.go index 7fab6d0..fd455c4 100644 --- a/git/git.go +++ b/git/git.go @@ -30,16 +30,19 @@ type RepoStatus struct { } // IsDirty returns true if there are uncommitted changes. +// Usage: IsDirty(...) func (s *RepoStatus) IsDirty() bool { return s.Modified > 0 || s.Untracked > 0 || s.Staged > 0 } // HasUnpushed returns true if there are commits to push. +// Usage: HasUnpushed(...) func (s *RepoStatus) HasUnpushed() bool { return s.Ahead > 0 } // HasUnpulled returns true if there are commits to pull. +// Usage: HasUnpulled(...) func (s *RepoStatus) HasUnpulled() bool { return s.Behind > 0 } @@ -53,6 +56,7 @@ type StatusOptions struct { } // Status checks git status for multiple repositories in parallel. +// Usage: Status(...) func Status(ctx context.Context, opts StatusOptions) []RepoStatus { var wg sync.WaitGroup results := make([]RepoStatus, len(opts.Paths)) @@ -74,6 +78,7 @@ func Status(ctx context.Context, opts StatusOptions) []RepoStatus { } // StatusIter returns an iterator over git status for multiple repositories. +// Usage: StatusIter(...) func StatusIter(ctx context.Context, opts StatusOptions) iter.Seq[RepoStatus] { return func(yield func(RepoStatus) bool) { results := Status(ctx, opts) @@ -158,17 +163,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: 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: 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: IsNonFastForward(...) func IsNonFastForward(err error) bool { if err == nil { return false @@ -212,11 +220,13 @@ type PushResult struct { // PushMultiple pushes multiple repositories sequentially. // Sequential because SSH passphrase prompts need user interaction. +// Usage: 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: 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 { @@ -271,6 +281,7 @@ type GitError struct { } // Error returns the git error message, preferring stderr output. +// Usage: Error(...) func (e *GitError) Error() string { // Return just the stderr message, trimmed msg := strings.TrimSpace(e.Stderr) @@ -281,6 +292,7 @@ func (e *GitError) Error() string { } // Unwrap returns the underlying error for error chain inspection. +// Usage: Unwrap(...) func (e *GitError) Unwrap() error { return e.Err } diff --git a/git/service.go b/git/service.go index 86ba0c4..c719a31 100644 --- a/git/service.go +++ b/git/service.go @@ -56,6 +56,7 @@ type Service struct { } // NewService creates a git service factory. +// Usage: NewService(...) func NewService(opts ServiceOptions) func(*core.Core) (any, error) { return func(c *core.Core) (any, error) { return &Service{ @@ -65,6 +66,7 @@ func NewService(opts ServiceOptions) func(*core.Core) (any, error) { } // OnStartup registers query and task handlers. +// Usage: OnStartup(...) func (s *Service) OnStartup(ctx context.Context) error { s.Core().RegisterQuery(s.handleQuery) s.Core().RegisterTask(s.handleTask) @@ -103,14 +105,17 @@ func (s *Service) handleTask(c *core.Core, t core.Task) core.Result { } // Status returns last status result. +// Usage: Status(...) func (s *Service) Status() []RepoStatus { return s.lastStatus } // StatusIter returns an iterator over last status result. +// Usage: StatusIter(...) func (s *Service) StatusIter() iter.Seq[RepoStatus] { return slices.Values(s.lastStatus) } // DirtyRepos returns repos with uncommitted changes. +// Usage: DirtyRepos(...) func (s *Service) DirtyRepos() []RepoStatus { var dirty []RepoStatus for _, st := range s.lastStatus { @@ -122,6 +127,7 @@ func (s *Service) DirtyRepos() []RepoStatus { } // DirtyReposIter returns an iterator over repos with uncommitted changes. +// Usage: DirtyReposIter(...) func (s *Service) DirtyReposIter() iter.Seq[RepoStatus] { return func(yield func(RepoStatus) bool) { for _, st := range s.lastStatus { @@ -135,6 +141,7 @@ func (s *Service) DirtyReposIter() iter.Seq[RepoStatus] { } // AheadRepos returns repos with unpushed commits. +// Usage: AheadRepos(...) func (s *Service) AheadRepos() []RepoStatus { var ahead []RepoStatus for _, st := range s.lastStatus { @@ -146,6 +153,7 @@ func (s *Service) AheadRepos() []RepoStatus { } // AheadReposIter returns an iterator over repos with unpushed commits. +// Usage: AheadReposIter(...) func (s *Service) AheadReposIter() iter.Seq[RepoStatus] { return func(yield func(RepoStatus) bool) { for _, st := range s.lastStatus { diff --git a/gitea/client.go b/gitea/client.go index db89702..335da97 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -23,6 +23,7 @@ type Client struct { } // New creates a new Gitea API client for the given URL and token. +// Usage: 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 a8bacf2..7efe8b2 100644 --- a/gitea/config.go +++ b/gitea/config.go @@ -27,6 +27,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: NewFromConfig(...) func NewFromConfig(flagURL, flagToken string) (*Client, error) { url, token, err := ResolveConfig(flagURL, flagToken) if err != nil { @@ -42,6 +44,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: ResolveConfig(...) func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // Start with config file values cfg, cfgErr := config.New() @@ -75,6 +78,7 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { } // SaveConfig persists the Gitea URL and/or token to the config file. +// Usage: SaveConfig(...) func SaveConfig(url, token string) error { cfg, err := config.New() if err != nil { diff --git a/gitea/issues.go b/gitea/issues.go index 24be5f9..ce6d8d0 100644 --- a/gitea/issues.go +++ b/gitea/issues.go @@ -18,6 +18,7 @@ type ListIssuesOpts struct { } // ListIssues returns issues for the given repository. +// Usage: ListIssues(...) func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.Issue, error) { state := gitea.StateOpen switch opts.State { @@ -50,6 +51,7 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.I } // GetIssue returns a single issue by number. +// Usage: GetIssue(...) func (c *Client) GetIssue(owner, repo string, number int64) (*gitea.Issue, error) { issue, _, err := c.api.GetIssue(owner, repo, number) if err != nil { @@ -60,6 +62,7 @@ func (c *Client) GetIssue(owner, repo string, number int64) (*gitea.Issue, error } // CreateIssue creates a new issue in the given repository. +// Usage: CreateIssue(...) func (c *Client) CreateIssue(owner, repo string, opts gitea.CreateIssueOption) (*gitea.Issue, error) { issue, _, err := c.api.CreateIssue(owner, repo, opts) if err != nil { @@ -70,6 +73,7 @@ func (c *Client) CreateIssue(owner, repo string, opts gitea.CreateIssueOption) ( } // ListPullRequests returns pull requests for the given repository. +// Usage: ListPullRequests(...) func (c *Client) ListPullRequests(owner, repo string, state string) ([]*gitea.PullRequest, error) { st := gitea.StateOpen switch state { @@ -103,6 +107,7 @@ func (c *Client) ListPullRequests(owner, repo string, state string) ([]*gitea.Pu } // ListPullRequestsIter returns an iterator over pull requests for the given repository. +// Usage: ListPullRequestsIter(...) func (c *Client) ListPullRequestsIter(owner, repo string, state string) iter.Seq2[*gitea.PullRequest, error] { st := gitea.StateOpen switch state { @@ -137,6 +142,7 @@ func (c *Client) ListPullRequestsIter(owner, repo string, state string) iter.Seq } // GetPullRequest returns a single pull request by number. +// Usage: GetPullRequest(...) func (c *Client) GetPullRequest(owner, repo string, number int64) (*gitea.PullRequest, error) { pr, _, err := c.api.GetPullRequest(owner, repo, number) if err != nil { diff --git a/gitea/repos.go b/gitea/repos.go index 7eede16..356e47d 100644 --- a/gitea/repos.go +++ b/gitea/repos.go @@ -11,6 +11,7 @@ import ( ) // ListOrgRepos returns all repositories for the given organisation. +// Usage: ListOrgRepos(...) func (c *Client) ListOrgRepos(org string) ([]*gitea.Repository, error) { var all []*gitea.Repository page := 1 @@ -35,6 +36,7 @@ func (c *Client) ListOrgRepos(org string) ([]*gitea.Repository, error) { } // ListOrgReposIter returns an iterator over repositories for the given organisation. +// Usage: ListOrgReposIter(...) func (c *Client) ListOrgReposIter(org string) iter.Seq2[*gitea.Repository, error] { return func(yield func(*gitea.Repository, error) bool) { page := 1 @@ -60,6 +62,7 @@ func (c *Client) ListOrgReposIter(org string) iter.Seq2[*gitea.Repository, error } // ListUserRepos returns all repositories for the authenticated user. +// Usage: ListUserRepos(...) func (c *Client) ListUserRepos() ([]*gitea.Repository, error) { var all []*gitea.Repository page := 1 @@ -84,6 +87,7 @@ func (c *Client) ListUserRepos() ([]*gitea.Repository, error) { } // ListUserReposIter returns an iterator over repositories for the authenticated user. +// Usage: ListUserReposIter(...) func (c *Client) ListUserReposIter() iter.Seq2[*gitea.Repository, error] { return func(yield func(*gitea.Repository, error) bool) { page := 1 @@ -109,6 +113,7 @@ func (c *Client) ListUserReposIter() iter.Seq2[*gitea.Repository, error] { } // GetRepo returns a single repository by owner and name. +// Usage: GetRepo(...) func (c *Client) GetRepo(owner, name string) (*gitea.Repository, error) { repo, _, err := c.api.GetRepo(owner, name) if err != nil { @@ -121,6 +126,7 @@ func (c *Client) GetRepo(owner, name string) (*gitea.Repository, error) { // CreateMirror creates a mirror repository on Gitea from a GitHub clone URL. // This uses the Gitea migration API to set up a pull mirror. // If authToken is provided, it is used to authenticate against the source (e.g. for private GitHub repos). +// Usage: CreateMirror(...) func (c *Client) CreateMirror(owner, name, cloneURL, authToken string) (*gitea.Repository, error) { opts := gitea.MigrateRepoOption{ RepoName: name, @@ -144,6 +150,7 @@ func (c *Client) CreateMirror(owner, name, cloneURL, authToken string) (*gitea.R } // DeleteRepo deletes a repository from Gitea. +// Usage: DeleteRepo(...) func (c *Client) DeleteRepo(owner, name string) error { _, err := c.api.DeleteRepo(owner, name) if err != nil { @@ -154,6 +161,7 @@ func (c *Client) DeleteRepo(owner, name string) error { } // CreateOrgRepo creates a new empty repository under an organisation. +// Usage: CreateOrgRepo(...) func (c *Client) CreateOrgRepo(org string, opts gitea.CreateRepoOption) (*gitea.Repository, error) { repo, _, err := c.api.CreateOrgRepo(org, opts) if err != nil { diff --git a/internal/ax/filepathx/filepathx.go b/internal/ax/filepathx/filepathx.go index 2d18e2e..a2c7452 100644 --- a/internal/ax/filepathx/filepathx.go +++ b/internal/ax/filepathx/filepathx.go @@ -11,6 +11,7 @@ import ( const Separator = '/' // Abs mirrors filepath.Abs for the paths used in this repo. +// Usage: Abs(...) func Abs(p string) (string, error) { if path.IsAbs(p) { return path.Clean(p), nil @@ -23,26 +24,31 @@ func Abs(p string) (string, error) { } // Base mirrors filepath.Base. +// Usage: Base(...) func Base(p string) string { return path.Base(p) } // Clean mirrors filepath.Clean. +// Usage: Clean(...) func Clean(p string) string { return path.Clean(p) } // Dir mirrors filepath.Dir. +// Usage: Dir(...) func Dir(p string) string { return path.Dir(p) } // Ext mirrors filepath.Ext. +// Usage: Ext(...) func Ext(p string) string { return path.Ext(p) } // Join mirrors filepath.Join. +// Usage: Join(...) func Join(elem ...string) string { return path.Join(elem...) } diff --git a/internal/ax/fmtx/fmtx.go b/internal/ax/fmtx/fmtx.go index 347a52a..4e1ce55 100644 --- a/internal/ax/fmtx/fmtx.go +++ b/internal/ax/fmtx/fmtx.go @@ -10,26 +10,31 @@ import ( ) // Sprint mirrors fmt.Sprint using Core primitives. +// Usage: Sprint(...) func Sprint(args ...any) string { return core.Sprint(args...) } // Sprintf mirrors fmt.Sprintf using Core primitives. +// Usage: Sprintf(...) func Sprintf(format string, args ...any) string { return core.Sprintf(format, args...) } // Fprintf mirrors fmt.Fprintf using Core primitives. +// Usage: Fprintf(...) func Fprintf(w io.Writer, format string, args ...any) (int, error) { return io.WriteString(w, Sprintf(format, args...)) } // Printf mirrors fmt.Printf. +// Usage: Printf(...) func Printf(format string, args ...any) (int, error) { return Fprintf(stdio.Stdout, format, args...) } // Println mirrors fmt.Println. +// Usage: 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 index 396faba..b9db4a6 100644 --- a/internal/ax/jsonx/jsonx.go +++ b/internal/ax/jsonx/jsonx.go @@ -9,26 +9,31 @@ import ( ) // Marshal mirrors encoding/json.Marshal. +// Usage: Marshal(...) func Marshal(v any) ([]byte, error) { return json.Marshal(v) } // MarshalIndent mirrors encoding/json.MarshalIndent. +// Usage: MarshalIndent(...) func MarshalIndent(v any, prefix, indent string) ([]byte, error) { return json.MarshalIndent(v, prefix, indent) } // NewDecoder mirrors encoding/json.NewDecoder. +// Usage: NewDecoder(...) func NewDecoder(r io.Reader) *json.Decoder { return json.NewDecoder(r) } // NewEncoder mirrors encoding/json.NewEncoder. +// Usage: NewEncoder(...) func NewEncoder(w io.Writer) *json.Encoder { return json.NewEncoder(w) } // Unmarshal mirrors encoding/json.Unmarshal. +// Usage: 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 index 475e547..7c1fc5a 100644 --- a/internal/ax/osx/osx.go +++ b/internal/ax/osx/osx.go @@ -32,32 +32,38 @@ var Stdout = stdio.Stdout var Stderr = stdio.Stderr // Getenv mirrors os.Getenv. +// Usage: Getenv(...) func Getenv(key string) string { value, _ := syscall.Getenv(key) return value } // Getwd mirrors os.Getwd. +// Usage: Getwd(...) func Getwd() (string, error) { return syscall.Getwd() } // IsNotExist mirrors os.IsNotExist. +// Usage: IsNotExist(...) func IsNotExist(err error) bool { return core.Is(err, fs.ErrNotExist) } // MkdirAll mirrors os.MkdirAll. +// Usage: MkdirAll(...) func MkdirAll(path string, _ fs.FileMode) error { return coreio.Local.EnsureDir(path) } // Open mirrors os.Open. +// Usage: 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: OpenFile(...) func OpenFile(path string, flag int, _ fs.FileMode) (io.WriteCloser, error) { if flag&O_APPEND != 0 { return coreio.Local.Append(path) @@ -66,22 +72,26 @@ func OpenFile(path string, flag int, _ fs.FileMode) (io.WriteCloser, error) { } // ReadDir mirrors os.ReadDir. +// Usage: ReadDir(...) func ReadDir(path string) ([]fs.DirEntry, error) { return coreio.Local.List(path) } // ReadFile mirrors os.ReadFile. +// Usage: ReadFile(...) func ReadFile(path string) ([]byte, error) { content, err := coreio.Local.Read(path) return []byte(content), err } // Stat mirrors os.Stat. +// Usage: Stat(...) func Stat(path string) (fs.FileInfo, error) { return coreio.Local.Stat(path) } // UserHomeDir mirrors os.UserHomeDir. +// Usage: UserHomeDir(...) func UserHomeDir() (string, error) { if home := Getenv("HOME"); home != "" { return home, nil @@ -94,6 +104,7 @@ func UserHomeDir() (string, error) { } // WriteFile mirrors os.WriteFile. +// Usage: 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 index b434dfb..53fe5af 100644 --- a/internal/ax/stdio/stdio.go +++ b/internal/ax/stdio/stdio.go @@ -11,6 +11,8 @@ type fdReader struct { fd int } +// Read implements io.Reader for stdin without importing os. +// Usage: Read(...) func (r fdReader) Read(p []byte) (int, error) { n, err := syscall.Read(r.fd, p) if n == 0 && err == nil { @@ -23,6 +25,8 @@ type fdWriter struct { fd int } +// Write implements io.Writer for stdout and stderr without importing os. +// Usage: Write(...) func (w fdWriter) Write(p []byte) (int, error) { return syscall.Write(w.fd, p) } diff --git a/internal/ax/stringsx/stringsx.go b/internal/ax/stringsx/stringsx.go index f67f3a1..6fe0eef 100644 --- a/internal/ax/stringsx/stringsx.go +++ b/internal/ax/stringsx/stringsx.go @@ -14,21 +14,25 @@ import ( type Builder = bytes.Buffer // Contains mirrors strings.Contains. +// Usage: Contains(...) func Contains(s, substr string) bool { return core.Contains(s, substr) } // ContainsAny mirrors strings.ContainsAny. +// Usage: ContainsAny(...) func ContainsAny(s, chars string) bool { return bytes.IndexAny([]byte(s), chars) >= 0 } // EqualFold mirrors strings.EqualFold. +// Usage: EqualFold(...) func EqualFold(s, t string) bool { return bytes.EqualFold([]byte(s), []byte(t)) } // Fields mirrors strings.Fields. +// Usage: Fields(...) func Fields(s string) []string { scanner := bufio.NewScanner(NewReader(s)) scanner.Split(bufio.ScanWords) @@ -40,31 +44,37 @@ func Fields(s string) []string { } // HasPrefix mirrors strings.HasPrefix. +// Usage: HasPrefix(...) func HasPrefix(s, prefix string) bool { return core.HasPrefix(s, prefix) } // HasSuffix mirrors strings.HasSuffix. +// Usage: HasSuffix(...) func HasSuffix(s, suffix string) bool { return core.HasSuffix(s, suffix) } // Join mirrors strings.Join. +// Usage: Join(...) func Join(elems []string, sep string) string { return core.Join(sep, elems...) } // LastIndex mirrors strings.LastIndex. +// Usage: LastIndex(...) func LastIndex(s, substr string) int { return bytes.LastIndex([]byte(s), []byte(substr)) } // NewReader mirrors strings.NewReader. +// Usage: NewReader(...) func NewReader(s string) *bytes.Reader { return bytes.NewReader([]byte(s)) } // Repeat mirrors strings.Repeat. +// Usage: Repeat(...) func Repeat(s string, count int) string { if count <= 0 { return "" @@ -73,26 +83,31 @@ func Repeat(s string, count int) string { } // ReplaceAll mirrors strings.ReplaceAll. +// Usage: ReplaceAll(...) func ReplaceAll(s, old, new string) string { return core.Replace(s, old, new) } // Replace mirrors strings.Replace for replace-all call sites. +// Usage: Replace(...) func Replace(s, old, new string, _ int) string { return ReplaceAll(s, old, new) } // Split mirrors strings.Split. +// Usage: Split(...) func Split(s, sep string) []string { return core.Split(s, sep) } // SplitN mirrors strings.SplitN. +// Usage: SplitN(...) func SplitN(s, sep string, n int) []string { return core.SplitN(s, sep, n) } // SplitSeq mirrors strings.SplitSeq. +// Usage: SplitSeq(...) func SplitSeq(s, sep string) iter.Seq[string] { parts := Split(s, sep) return func(yield func(string) bool) { @@ -105,26 +120,31 @@ func SplitSeq(s, sep string) iter.Seq[string] { } // ToLower mirrors strings.ToLower. +// Usage: ToLower(...) func ToLower(s string) string { return core.Lower(s) } // ToUpper mirrors strings.ToUpper. +// Usage: ToUpper(...) func ToUpper(s string) string { return core.Upper(s) } // TrimPrefix mirrors strings.TrimPrefix. +// Usage: TrimPrefix(...) func TrimPrefix(s, prefix string) string { return core.TrimPrefix(s, prefix) } // TrimSpace mirrors strings.TrimSpace. +// Usage: TrimSpace(...) func TrimSpace(s string) string { return core.Trim(s) } // TrimSuffix mirrors strings.TrimSuffix. +// Usage: 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 947faa7..c4f1e7c 100644 --- a/jobrunner/forgejo/source.go +++ b/jobrunner/forgejo/source.go @@ -24,6 +24,7 @@ type ForgejoSource struct { } // New creates a ForgejoSource using the given forge client. +// Usage: New(...) func New(cfg Config, client *forge.Client) *ForgejoSource { return &ForgejoSource{ repos: cfg.Repos, @@ -32,12 +33,14 @@ func New(cfg Config, client *forge.Client) *ForgejoSource { } // Name returns the source identifier. +// Usage: Name(...) func (s *ForgejoSource) Name() string { return "forgejo" } // Poll fetches epics and their linked PRs from all configured repositories, // returning a PipelineSignal for each unchecked child that has a linked PR. +// Usage: Poll(...) func (s *ForgejoSource) Poll(ctx context.Context) ([]*jobrunner.PipelineSignal, error) { var signals []*jobrunner.PipelineSignal @@ -61,6 +64,7 @@ func (s *ForgejoSource) Poll(ctx context.Context) ([]*jobrunner.PipelineSignal, } // Report posts the action result as a comment on the epic issue. +// Usage: Report(...) func (s *ForgejoSource) Report(ctx context.Context, result *jobrunner.ActionResult) error { if result == nil { return nil diff --git a/jobrunner/handlers/completion.go b/jobrunner/handlers/completion.go index 5beeca7..46fd8af 100644 --- a/jobrunner/handlers/completion.go +++ b/jobrunner/handlers/completion.go @@ -23,6 +23,7 @@ type CompletionHandler struct { } // NewCompletionHandler creates a handler for agent completion events. +// Usage: NewCompletionHandler(...) func NewCompletionHandler(client *forge.Client) *CompletionHandler { return &CompletionHandler{ forge: client, diff --git a/jobrunner/handlers/dispatch.go b/jobrunner/handlers/dispatch.go index df7c2cc..c7b5138 100644 --- a/jobrunner/handlers/dispatch.go +++ b/jobrunner/handlers/dispatch.go @@ -62,6 +62,7 @@ type DispatchHandler struct { } // NewDispatchHandler creates a handler that dispatches tickets to agent machines. +// Usage: NewDispatchHandler(...) func NewDispatchHandler(client *forge.Client, forgeURL, token string, spinner *agentci.Spinner) *DispatchHandler { return &DispatchHandler{ forge: client, @@ -72,12 +73,14 @@ func NewDispatchHandler(client *forge.Client, forgeURL, token string, spinner *a } // Name returns the handler identifier. +// Usage: Name(...) func (h *DispatchHandler) Name() string { return "dispatch" } // Match returns true for signals where a child issue needs coding (no PR yet) // and the assignee is a known agent (by config key or Forgejo username). +// Usage: Match(...) func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool { if !signal.NeedsCoding { return false @@ -87,6 +90,7 @@ func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool { } // Execute creates a ticket JSON and transfers it securely to the agent's queue directory. +// Usage: Execute(...) func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) { start := time.Now() diff --git a/jobrunner/handlers/enable_auto_merge.go b/jobrunner/handlers/enable_auto_merge.go index 4aaf987..5dcc652 100644 --- a/jobrunner/handlers/enable_auto_merge.go +++ b/jobrunner/handlers/enable_auto_merge.go @@ -17,6 +17,7 @@ type EnableAutoMergeHandler struct { } // NewEnableAutoMergeHandler creates a handler that merges ready PRs. +// Usage: NewEnableAutoMergeHandler(...) func NewEnableAutoMergeHandler(f *forge.Client) *EnableAutoMergeHandler { return &EnableAutoMergeHandler{forge: f} } diff --git a/jobrunner/handlers/publish_draft.go b/jobrunner/handlers/publish_draft.go index ffb9e5a..c28a7e4 100644 --- a/jobrunner/handlers/publish_draft.go +++ b/jobrunner/handlers/publish_draft.go @@ -17,6 +17,7 @@ type PublishDraftHandler struct { } // NewPublishDraftHandler creates a handler that publishes draft PRs. +// Usage: 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 f55e568..80d2509 100644 --- a/jobrunner/handlers/resolve_threads.go +++ b/jobrunner/handlers/resolve_threads.go @@ -22,6 +22,7 @@ type DismissReviewsHandler struct { } // NewDismissReviewsHandler creates a handler that dismisses stale reviews. +// Usage: NewDismissReviewsHandler(...) func NewDismissReviewsHandler(f *forge.Client) *DismissReviewsHandler { return &DismissReviewsHandler{forge: f} } diff --git a/jobrunner/handlers/send_fix_command.go b/jobrunner/handlers/send_fix_command.go index c412cd5..324629f 100644 --- a/jobrunner/handlers/send_fix_command.go +++ b/jobrunner/handlers/send_fix_command.go @@ -18,6 +18,7 @@ type SendFixCommandHandler struct { } // NewSendFixCommandHandler creates a handler that posts fix commands. +// Usage: NewSendFixCommandHandler(...) func NewSendFixCommandHandler(f *forge.Client) *SendFixCommandHandler { return &SendFixCommandHandler{forge: f} } diff --git a/jobrunner/handlers/tick_parent.go b/jobrunner/handlers/tick_parent.go index ba1b0e5..3032198 100644 --- a/jobrunner/handlers/tick_parent.go +++ b/jobrunner/handlers/tick_parent.go @@ -22,6 +22,7 @@ type TickParentHandler struct { } // NewTickParentHandler creates a handler that ticks parent epic checkboxes. +// Usage: NewTickParentHandler(...) func NewTickParentHandler(f *forge.Client) *TickParentHandler { return &TickParentHandler{forge: f} } diff --git a/jobrunner/journal.go b/jobrunner/journal.go index 9cfa9af..596d154 100644 --- a/jobrunner/journal.go +++ b/jobrunner/journal.go @@ -54,6 +54,7 @@ type Journal struct { } // NewJournal creates a new Journal rooted at baseDir. +// Usage: NewJournal(...) func NewJournal(baseDir string) (*Journal, error) { if baseDir == "" { return nil, coreerr.E("jobrunner.NewJournal", "base directory is required", nil) @@ -92,6 +93,7 @@ func sanitizePathComponent(name string) (string, error) { } // Append writes a journal entry for the given signal and result. +// Usage: Append(...) func (j *Journal) Append(signal *PipelineSignal, result *ActionResult) error { if signal == nil { return coreerr.E("jobrunner.Journal.Append", "signal is required", nil) diff --git a/jobrunner/poller.go b/jobrunner/poller.go index aef9db3..c592d8e 100644 --- a/jobrunner/poller.go +++ b/jobrunner/poller.go @@ -31,6 +31,7 @@ type Poller struct { } // NewPoller creates a Poller from the given config. +// Usage: NewPoller(...) func NewPoller(cfg PollerConfig) *Poller { interval := cfg.PollInterval if interval <= 0 { @@ -47,6 +48,7 @@ func NewPoller(cfg PollerConfig) *Poller { } // Cycle returns the number of completed poll-dispatch cycles. +// Usage: Cycle(...) func (p *Poller) Cycle() int { p.mu.RLock() defer p.mu.RUnlock() @@ -54,6 +56,7 @@ func (p *Poller) Cycle() int { } // DryRun returns whether dry-run mode is enabled. +// Usage: DryRun(...) func (p *Poller) DryRun() bool { p.mu.RLock() defer p.mu.RUnlock() @@ -61,6 +64,7 @@ func (p *Poller) DryRun() bool { } // SetDryRun enables or disables dry-run mode. +// Usage: SetDryRun(...) func (p *Poller) SetDryRun(v bool) { p.mu.Lock() p.dryRun = v @@ -68,6 +72,7 @@ func (p *Poller) SetDryRun(v bool) { } // AddSource appends a source to the poller. +// Usage: AddSource(...) func (p *Poller) AddSource(s JobSource) { p.mu.Lock() p.sources = append(p.sources, s) @@ -75,6 +80,7 @@ func (p *Poller) AddSource(s JobSource) { } // AddHandler appends a handler to the poller. +// Usage: AddHandler(...) func (p *Poller) AddHandler(h JobHandler) { p.mu.Lock() p.handlers = append(p.handlers, h) @@ -84,6 +90,7 @@ func (p *Poller) AddHandler(h JobHandler) { // Run starts a blocking poll-dispatch loop. It runs one cycle immediately, // then repeats on each tick of the configured interval until the context // is cancelled. +// Usage: Run(...) func (p *Poller) Run(ctx context.Context) error { if err := p.RunOnce(ctx); err != nil { return err @@ -106,6 +113,7 @@ func (p *Poller) Run(ctx context.Context) error { // RunOnce performs a single poll-dispatch cycle: iterate sources, poll each, // find the first matching handler for each signal, and execute it. +// Usage: RunOnce(...) func (p *Poller) RunOnce(ctx context.Context) error { p.mu.Lock() p.cycle++ diff --git a/jobrunner/types.go b/jobrunner/types.go index 4f9d0e7..d159a87 100644 --- a/jobrunner/types.go +++ b/jobrunner/types.go @@ -35,11 +35,13 @@ type PipelineSignal struct { } // RepoFullName returns "owner/repo". +// Usage: RepoFullName(...) func (s *PipelineSignal) RepoFullName() string { return s.RepoOwner + "/" + s.RepoName } // HasUnresolvedThreads returns true if there are unresolved review threads. +// Usage: HasUnresolvedThreads(...) func (s *PipelineSignal) HasUnresolvedThreads() bool { return s.ThreadsTotal > s.ThreadsResolved } diff --git a/manifest/compile.go b/manifest/compile.go index 50c6dfd..4d571ba 100644 --- a/manifest/compile.go +++ b/manifest/compile.go @@ -35,6 +35,7 @@ type CompileOptions struct { // Compile produces a CompiledManifest from a source manifest and build // options. If opts.SignKey is provided the manifest is signed first. +// Usage: Compile(...) func Compile(m *Manifest, opts CompileOptions) (*CompiledManifest, error) { if m == nil { return nil, coreerr.E("manifest.Compile", "nil manifest", nil) @@ -63,11 +64,13 @@ func Compile(m *Manifest, opts CompileOptions) (*CompiledManifest, error) { } // MarshalJSON serialises a CompiledManifest to JSON bytes. +// Usage: MarshalJSON(...) func MarshalJSON(cm *CompiledManifest) ([]byte, error) { return json.MarshalIndent(cm, "", " ") } // ParseCompiled decodes a core.json into a CompiledManifest. +// Usage: ParseCompiled(...) func ParseCompiled(data []byte) (*CompiledManifest, error) { var cm CompiledManifest if err := json.Unmarshal(data, &cm); err != nil { @@ -80,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: WriteCompiled(...) func WriteCompiled(medium io.Medium, root string, cm *CompiledManifest) error { data, err := MarshalJSON(cm) if err != nil { @@ -90,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: LoadCompiled(...) func LoadCompiled(medium io.Medium, root string) (*CompiledManifest, error) { path := filepath.Join(root, compiledPath) data, err := medium.Read(path) diff --git a/manifest/loader.go b/manifest/loader.go index 6adab1f..6d7d147 100644 --- a/manifest/loader.go +++ b/manifest/loader.go @@ -14,11 +14,13 @@ import ( const manifestPath = ".core/manifest.yaml" // MarshalYAML serializes a manifest to YAML bytes. +// Usage: 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: Load(...) func Load(medium io.Medium, root string) (*Manifest, error) { path := filepath.Join(root, manifestPath) data, err := medium.Read(path) @@ -29,6 +31,7 @@ func Load(medium io.Medium, root string) (*Manifest, error) { } // LoadVerified reads, parses, and verifies the ed25519 signature. +// Usage: 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 7bcf5c2..7093d25 100644 --- a/manifest/manifest.go +++ b/manifest/manifest.go @@ -67,6 +67,8 @@ type DaemonSpec struct { // Parse decodes YAML bytes into a Manifest. // // m, err := manifest.Parse(yamlBytes) +// +// Usage: Parse(...) func Parse(data []byte) (*Manifest, error) { var m Manifest if err := yaml.Unmarshal(data, &m); err != nil { @@ -76,6 +78,7 @@ func Parse(data []byte) (*Manifest, error) { } // SlotNames returns a deduplicated list of component names from slots. +// Usage: SlotNames(...) func (m *Manifest) SlotNames() []string { seen := make(map[string]bool) var names []string @@ -92,6 +95,7 @@ func (m *Manifest) SlotNames() []string { // A daemon is the default if it has Default:true, or if it is the only daemon // in the map. If multiple daemons have Default:true, returns false (ambiguous). // Returns empty values and false if no default can be determined. +// Usage: DefaultDaemon(...) func (m *Manifest) DefaultDaemon() (string, DaemonSpec, bool) { if len(m.Daemons) == 0 { return "", DaemonSpec{}, false diff --git a/manifest/sign.go b/manifest/sign.go index 24bff4b..c803c84 100644 --- a/manifest/sign.go +++ b/manifest/sign.go @@ -18,6 +18,7 @@ func signable(m *Manifest) ([]byte, error) { } // Sign computes the ed25519 signature and stores it in m.Sign (base64). +// Usage: Sign(...) func Sign(m *Manifest, priv ed25519.PrivateKey) error { msg, err := signable(m) if err != nil { @@ -29,6 +30,7 @@ func Sign(m *Manifest, priv ed25519.PrivateKey) error { } // Verify checks the ed25519 signature in m.Sign against the public key. +// Usage: 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 3f8a7a9..ecd8add 100644 --- a/marketplace/builder.go +++ b/marketplace/builder.go @@ -33,6 +33,7 @@ type Builder struct { // BuildFromDirs scans each directory for subdirectories containing either // core.json (preferred) or .core/manifest.yaml. Each valid manifest is // added to the resulting Index as a Module. +// Usage: BuildFromDirs(...) func (b *Builder) BuildFromDirs(dirs ...string) (*Index, error) { var modules []Module seen := make(map[string]bool) @@ -86,6 +87,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: BuildFromManifests(...) func BuildFromManifests(manifests []*manifest.Manifest) *Index { var modules []Module seen := make(map[string]bool) @@ -116,6 +118,7 @@ func BuildFromManifests(manifests []*manifest.Manifest) *Index { } // WriteIndex serialises an Index to JSON and writes it to the given path. +// Usage: 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/discovery.go b/marketplace/discovery.go index 37cf227..e08466a 100644 --- a/marketplace/discovery.go +++ b/marketplace/discovery.go @@ -26,6 +26,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: DiscoverProviders(...) func DiscoverProviders(dir string) ([]DiscoveredProvider, error) { entries, err := os.ReadDir(dir) if err != nil { @@ -86,6 +87,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: LoadProviderRegistry(...) func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) { raw, err := coreio.Local.Read(path) if err != nil { @@ -111,6 +113,7 @@ func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) { } // SaveProviderRegistry writes the registry to the given path. +// Usage: 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) @@ -125,6 +128,7 @@ func SaveProviderRegistry(path string, reg *ProviderRegistryFile) error { } // Add adds or updates a provider entry in the registry. +// Usage: Add(...) func (r *ProviderRegistryFile) Add(code string, entry ProviderRegistryEntry) { if r.Providers == nil { r.Providers = make(map[string]ProviderRegistryEntry) @@ -133,17 +137,20 @@ func (r *ProviderRegistryFile) Add(code string, entry ProviderRegistryEntry) { } // Remove removes a provider entry from the registry. +// Usage: Remove(...) func (r *ProviderRegistryFile) Remove(code string) { delete(r.Providers, code) } // Get returns a provider entry and true if found, or zero value and false. +// Usage: Get(...) func (r *ProviderRegistryFile) Get(code string) (ProviderRegistryEntry, bool) { entry, ok := r.Providers[code] return entry, ok } // List returns all provider codes in the registry. +// Usage: List(...) func (r *ProviderRegistryFile) List() []string { codes := make([]string, 0, len(r.Providers)) for code := range r.Providers { @@ -153,6 +160,7 @@ func (r *ProviderRegistryFile) List() []string { } // AutoStartProviders returns codes of providers with auto_start enabled. +// Usage: AutoStartProviders(...) func (r *ProviderRegistryFile) AutoStartProviders() []string { var codes []string for code, entry := range r.Providers { diff --git a/marketplace/installer.go b/marketplace/installer.go index ea40ef1..b173b5d 100644 --- a/marketplace/installer.go +++ b/marketplace/installer.go @@ -28,6 +28,7 @@ type Installer struct { } // NewInstaller creates a new module installer. +// Usage: NewInstaller(...) func NewInstaller(m io.Medium, modulesDir string, st *store.Store) *Installer { return &Installer{ medium: m, @@ -49,6 +50,7 @@ type InstalledModule struct { } // Install clones a module repo, verifies its manifest signature, and registers it. +// Usage: Install(...) func (i *Installer) Install(ctx context.Context, mod Module) error { safeCode, dest, err := i.resolveModulePath(mod.Code) if err != nil { @@ -111,6 +113,7 @@ func (i *Installer) Install(ctx context.Context, mod Module) error { } // Remove uninstalls a module by deleting its files and store entry. +// Usage: Remove(...) func (i *Installer) Remove(code string) error { safeCode, dest, err := i.resolveModulePath(code) if err != nil { @@ -129,6 +132,7 @@ func (i *Installer) Remove(code string) error { } // Update pulls latest changes and re-verifies the manifest. +// Usage: Update(...) func (i *Installer) Update(ctx context.Context, code string) error { safeCode, dest, err := i.resolveModulePath(code) if err != nil { @@ -175,6 +179,7 @@ func (i *Installer) Update(ctx context.Context, code string) error { } // Installed returns all installed module metadata. +// Usage: Installed(...) func (i *Installer) Installed() ([]InstalledModule, error) { all, err := i.store.GetAll(storeGroup) if err != nil { diff --git a/marketplace/marketplace.go b/marketplace/marketplace.go index 5e51366..aa5edfb 100644 --- a/marketplace/marketplace.go +++ b/marketplace/marketplace.go @@ -26,6 +26,7 @@ type Index struct { } // ParseIndex decodes a marketplace index.json. +// Usage: ParseIndex(...) func ParseIndex(data []byte) (*Index, error) { var idx Index if err := json.Unmarshal(data, &idx); err != nil { @@ -35,6 +36,7 @@ func ParseIndex(data []byte) (*Index, error) { } // Search returns modules matching the query in code, name, or category. +// Usage: Search(...) func (idx *Index) Search(query string) []Module { q := strings.ToLower(query) var results []Module @@ -49,6 +51,7 @@ func (idx *Index) Search(query string) []Module { } // ByCategory returns all modules in the given category. +// Usage: ByCategory(...) func (idx *Index) ByCategory(category string) []Module { var results []Module for _, m := range idx.Modules { @@ -60,6 +63,7 @@ func (idx *Index) ByCategory(category string) []Module { } // Find returns the module with the given code, or false if not found. +// Usage: Find(...) func (idx *Index) Find(code string) (Module, bool) { for _, m := range idx.Modules { if m.Code == code { diff --git a/pkg/api/provider.go b/pkg/api/provider.go index af6f512..9e92aab 100644 --- a/pkg/api/provider.go +++ b/pkg/api/provider.go @@ -45,6 +45,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: NewProvider(...) func NewProvider(idx *marketplace.Index, inst *marketplace.Installer, reg *repos.Registry, hub *ws.Hub) *ScmProvider { return &ScmProvider{ index: idx, @@ -56,12 +57,15 @@ func NewProvider(idx *marketplace.Index, inst *marketplace.Installer, reg *repos } // Name implements api.RouteGroup. +// Usage: Name(...) func (p *ScmProvider) Name() string { return "scm" } // BasePath implements api.RouteGroup. +// Usage: BasePath(...) func (p *ScmProvider) BasePath() string { return "/api/v1/scm" } // Element implements provider.Renderable. +// Usage: Element(...) func (p *ScmProvider) Element() provider.ElementSpec { return provider.ElementSpec{ Tag: "core-scm-panel", @@ -70,6 +74,7 @@ func (p *ScmProvider) Element() provider.ElementSpec { } // Channels implements provider.Streamable. +// Usage: Channels(...) func (p *ScmProvider) Channels() []string { return []string{ "scm.marketplace.refreshed", @@ -81,6 +86,7 @@ func (p *ScmProvider) Channels() []string { } // RegisterRoutes implements api.RouteGroup. +// Usage: RegisterRoutes(...) func (p *ScmProvider) RegisterRoutes(rg *gin.RouterGroup) { // Marketplace rg.GET("/marketplace", p.listMarketplace) @@ -103,6 +109,7 @@ func (p *ScmProvider) RegisterRoutes(rg *gin.RouterGroup) { } // Describe implements api.DescribableGroup. +// Usage: Describe(...) func (p *ScmProvider) Describe() []api.RouteDescription { return []api.RouteDescription{ { diff --git a/plugin/installer.go b/plugin/installer.go index 74f3cfb..c2d2f66 100644 --- a/plugin/installer.go +++ b/plugin/installer.go @@ -23,6 +23,7 @@ type Installer struct { } // NewInstaller creates a new plugin installer. +// Usage: NewInstaller(...) func NewInstaller(m io.Medium, registry *Registry) *Installer { return &Installer{ medium: m, @@ -183,6 +184,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: 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 d9149eb..403e23f 100644 --- a/plugin/loader.go +++ b/plugin/loader.go @@ -16,6 +16,7 @@ type Loader struct { } // NewLoader creates a new plugin loader. +// Usage: NewLoader(...) func NewLoader(m io.Medium, baseDir string) *Loader { return &Loader{ medium: m, diff --git a/plugin/manifest.go b/plugin/manifest.go index dee658d..7d465d0 100644 --- a/plugin/manifest.go +++ b/plugin/manifest.go @@ -22,6 +22,7 @@ type Manifest struct { } // LoadManifest reads and parses a plugin.json file from the given path. +// Usage: LoadManifest(...) func LoadManifest(m io.Medium, path string) (*Manifest, error) { content, err := m.Read(path) if err != nil { diff --git a/plugin/registry.go b/plugin/registry.go index 5dcab50..9f03674 100644 --- a/plugin/registry.go +++ b/plugin/registry.go @@ -23,6 +23,7 @@ type Registry struct { } // NewRegistry creates a new plugin registry. +// Usage: NewRegistry(...) func NewRegistry(m io.Medium, basePath string) *Registry { return &Registry{ medium: m, diff --git a/repos/gitstate.go b/repos/gitstate.go index bb77961..4538dbc 100644 --- a/repos/gitstate.go +++ b/repos/gitstate.go @@ -37,6 +37,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: LoadGitState(...) func LoadGitState(m io.Medium, root string) (*GitState, error) { path := filepath.Join(root, ".core", "git.yaml") @@ -65,6 +66,7 @@ func LoadGitState(m io.Medium, root string) (*GitState, error) { } // SaveGitState writes .core/git.yaml to the given workspace root directory. +// Usage: SaveGitState(...) func SaveGitState(m io.Medium, root string, gs *GitState) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil { @@ -85,6 +87,7 @@ func SaveGitState(m io.Medium, root string, gs *GitState) error { } // NewGitState returns a new empty GitState with version 1. +// Usage: NewGitState(...) func NewGitState() *GitState { return &GitState{ Version: 1, @@ -94,16 +97,19 @@ func NewGitState() *GitState { } // Touch records a pull timestamp for the named repo. +// Usage: TouchPull(...) func (gs *GitState) TouchPull(name string) { gs.ensureRepo(name).LastPull = time.Now() } // TouchPush records a push timestamp for the named repo. +// Usage: TouchPush(...) func (gs *GitState) TouchPush(name string) { gs.ensureRepo(name).LastPush = time.Now() } // UpdateRepo records the current git status for a repo. +// Usage: UpdateRepo(...) func (gs *GitState) UpdateRepo(name, branch, remote string, ahead, behind int) { r := gs.ensureRepo(name) r.Branch = branch @@ -113,6 +119,7 @@ func (gs *GitState) UpdateRepo(name, branch, remote string, ahead, behind int) { } // Heartbeat records an agent's presence and active packages. +// Usage: Heartbeat(...) func (gs *GitState) Heartbeat(agentName string, active []string) { if gs.Agents == nil { gs.Agents = make(map[string]*AgentState) @@ -124,6 +131,7 @@ func (gs *GitState) Heartbeat(agentName string, active []string) { } // StaleAgents returns agent names whose last heartbeat is older than the given duration. +// Usage: StaleAgents(...) func (gs *GitState) StaleAgents(staleAfter time.Duration) []string { cutoff := time.Now().Add(-staleAfter) var stale []string @@ -137,6 +145,7 @@ func (gs *GitState) StaleAgents(staleAfter time.Duration) []string { // ActiveAgentsFor returns agent names that have the given repo in their active list // and are not stale. +// Usage: ActiveAgentsFor(...) func (gs *GitState) ActiveAgentsFor(repoName string, staleAfter time.Duration) []string { cutoff := time.Now().Add(-staleAfter) var agents []string @@ -155,6 +164,7 @@ func (gs *GitState) ActiveAgentsFor(repoName string, staleAfter time.Duration) [ } // NeedsPull returns true if the repo has never been pulled or was pulled before the given duration. +// Usage: NeedsPull(...) func (gs *GitState) NeedsPull(name string, maxAge time.Duration) bool { r, ok := gs.Repos[name] if !ok { diff --git a/repos/kbconfig.go b/repos/kbconfig.go index ee196c9..b2175c9 100644 --- a/repos/kbconfig.go +++ b/repos/kbconfig.go @@ -47,6 +47,7 @@ type KBSearch struct { } // DefaultKBConfig returns sensible defaults for knowledge base config. +// Usage: DefaultKBConfig(...) func DefaultKBConfig() *KBConfig { return &KBConfig{ Version: 1, @@ -68,6 +69,7 @@ func DefaultKBConfig() *KBConfig { // LoadKBConfig reads .core/kb.yaml from the given workspace root directory. // Returns defaults if the file does not exist. +// Usage: LoadKBConfig(...) func LoadKBConfig(m io.Medium, root string) (*KBConfig, error) { path := filepath.Join(root, ".core", "kb.yaml") @@ -89,6 +91,7 @@ func LoadKBConfig(m io.Medium, root string) (*KBConfig, error) { } // SaveKBConfig writes .core/kb.yaml to the given workspace root directory. +// Usage: SaveKBConfig(...) func SaveKBConfig(m io.Medium, root string, kb *KBConfig) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil { @@ -109,11 +112,13 @@ func SaveKBConfig(m io.Medium, root string, kb *KBConfig) error { } // WikiRepoURL returns the full clone URL for a repo's wiki. +// Usage: WikiRepoURL(...) func (kb *KBConfig) WikiRepoURL(repoName string) string { return fmt.Sprintf("%s/%s.wiki.git", kb.Wiki.Remote, repoName) } // WikiLocalPath returns the local path for a repo's wiki clone. +// Usage: WikiLocalPath(...) func (kb *KBConfig) WikiLocalPath(root, repoName string) string { return filepath.Join(root, ".core", kb.Wiki.Dir, repoName) } diff --git a/repos/registry.go b/repos/registry.go index 62f7b6c..4f4162c 100644 --- a/repos/registry.go +++ b/repos/registry.go @@ -71,6 +71,8 @@ type Repo struct { // The path should be a valid path for the provided medium. // // reg, err := repos.LoadRegistry(io.Local, ".core/repos.yaml") +// +// Usage: LoadRegistry(...) func LoadRegistry(m io.Medium, path string) (*Registry, error) { content, err := m.Read(path) if err != nil { @@ -112,6 +114,8 @@ func LoadRegistry(m io.Medium, path string) (*Registry, error) { // This function is primarily intended for use with io.Local or other local-like filesystems. // // path, err := repos.FindRegistry(io.Local) +// +// Usage: FindRegistry(...) func FindRegistry(m io.Medium) (string, error) { // Check current directory and parents dir, err := os.Getwd() @@ -164,6 +168,8 @@ func FindRegistry(m io.Medium) (string, error) { // The dir should be a valid path for the provided medium. // // reg, err := repos.ScanDirectory(io.Local, "/home/user/Code/core") +// +// Usage: ScanDirectory(...) func ScanDirectory(m io.Medium, dir string) (*Registry, error) { entries, err := m.List(dir) if err != nil { @@ -255,6 +261,8 @@ func detectOrg(m io.Medium, repoPath string) string { // List returns all repos in the registry. // // repos := reg.List() +// +// Usage: List(...) func (r *Registry) List() []*Repo { repos := make([]*Repo, 0, len(r.Repos)) for _, repo := range r.Repos { @@ -267,6 +275,8 @@ func (r *Registry) List() []*Repo { // Get returns a repo by name. // // repo, ok := reg.Get("go-io") +// +// Usage: Get(...) func (r *Registry) Get(name string) (*Repo, bool) { repo, ok := r.Repos[name] return repo, ok @@ -275,6 +285,8 @@ func (r *Registry) Get(name string) (*Repo, bool) { // ByType returns repos filtered by type. // // goRepos := reg.ByType("go") +// +// Usage: ByType(...) func (r *Registry) ByType(t string) []*Repo { var repos []*Repo for _, repo := range r.Repos { @@ -289,6 +301,8 @@ func (r *Registry) ByType(t string) []*Repo { // Foundation repos come first, then modules, then products. // // ordered, err := reg.TopologicalOrder() +// +// Usage: TopologicalOrder(...) func (r *Registry) TopologicalOrder() ([]*Repo, error) { // Build dependency graph visited := make(map[string]bool) @@ -331,11 +345,13 @@ func (r *Registry) TopologicalOrder() ([]*Repo, error) { } // Exists checks if the repo directory exists on disk. +// Usage: Exists(...) func (repo *Repo) Exists() bool { return repo.getMedium().IsDir(repo.Path) } // IsGitRepo checks if the repo directory contains a .git folder. +// Usage: IsGitRepo(...) func (repo *Repo) IsGitRepo() bool { gitPath := filepath.Join(repo.Path, ".git") return repo.getMedium().IsDir(gitPath) diff --git a/repos/workconfig.go b/repos/workconfig.go index f76c3e7..e169de2 100644 --- a/repos/workconfig.go +++ b/repos/workconfig.go @@ -36,6 +36,7 @@ type AgentPolicy struct { } // DefaultWorkConfig returns sensible defaults for workspace sync. +// Usage: DefaultWorkConfig(...) func DefaultWorkConfig() *WorkConfig { return &WorkConfig{ Version: 1, @@ -56,6 +57,7 @@ func DefaultWorkConfig() *WorkConfig { // LoadWorkConfig reads .core/work.yaml from the given workspace root directory. // Returns defaults if the file does not exist. +// Usage: LoadWorkConfig(...) func LoadWorkConfig(m io.Medium, root string) (*WorkConfig, error) { path := filepath.Join(root, ".core", "work.yaml") @@ -77,6 +79,7 @@ func LoadWorkConfig(m io.Medium, root string) (*WorkConfig, error) { } // SaveWorkConfig writes .core/work.yaml to the given workspace root directory. +// Usage: SaveWorkConfig(...) func SaveWorkConfig(m io.Medium, root string, wc *WorkConfig) error { coreDir := filepath.Join(root, ".core") if err := m.EnsureDir(coreDir); err != nil { @@ -97,6 +100,7 @@ func SaveWorkConfig(m io.Medium, root string, wc *WorkConfig) error { } // HasTrigger returns true if the given trigger name is in the triggers list. +// Usage: HasTrigger(...) func (wc *WorkConfig) HasTrigger(name string) bool { for _, t := range wc.Triggers { if t == name {