diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 091c03ac..10ce0694 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: args: [--timeout=5m] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/AGENT_INSTRUCTIONS.md b/AGENT_INSTRUCTIONS.md index 7be1057b..72aacf5a 100644 --- a/AGENT_INSTRUCTIONS.md +++ b/AGENT_INSTRUCTIONS.md @@ -99,7 +99,10 @@ The 30-second debounce provides a **transaction window** for batch operations - **MANDATORY WORKFLOW - COMPLETE ALL STEPS:** 1. **File beads issues for any remaining work** that needs follow-up -2. **Ensure all quality gates pass** (only if code changes were made) - run tests, linters, builds (file P0 issues if broken) +2. **Ensure all quality gates pass** (only if code changes were made): + - Run `make lint` or `golangci-lint run ./...` (if pre-commit installed: `pre-commit run --all-files`) + - Run `make test` or `go test ./...` + - File P0 issues if quality gates are broken 3. **Update beads issues** - close finished work, update status 4. **PUSH TO REMOTE - NON-NEGOTIABLE** - This step is MANDATORY. Execute ALL commands below: ```bash diff --git a/cmd/bd/cook.go b/cmd/bd/cook.go index c2fa4652..3bc05758 100644 --- a/cmd/bd/cook.go +++ b/cmd/bd/cook.go @@ -365,6 +365,8 @@ type cookFormulaResult struct { // cookFormulaToSubgraph creates an in-memory TemplateSubgraph from a resolved formula. // This is the ephemeral proto implementation - no database storage. // The returned subgraph can be passed directly to cloneSubgraph for instantiation. +// +//nolint:unparam // error return kept for API consistency with future error handling func cookFormulaToSubgraph(f *formula.Formula, protoID string) (*TemplateSubgraph, error) { // Map step ID -> created issue issueMap := make(map[string]*types.Issue) diff --git a/cmd/bd/doctor/maintenance.go b/cmd/bd/doctor/maintenance.go index 1d6d0229..c80e6cb2 100644 --- a/cmd/bd/doctor/maintenance.go +++ b/cmd/bd/doctor/maintenance.go @@ -312,7 +312,7 @@ func CheckCompactionCandidates(path string) DoctorCheck { // the actual beads directory location. func resolveBeadsDir(beadsDir string) string { redirectFile := filepath.Join(beadsDir, "redirect") - data, err := os.ReadFile(redirectFile) + data, err := os.ReadFile(redirectFile) //nolint:gosec // redirect file path is constructed from known beadsDir if err != nil { // No redirect file - use original path return beadsDir diff --git a/cmd/bd/formula.go b/cmd/bd/formula.go index c7a09216..e12af71b 100644 --- a/cmd/bd/formula.go +++ b/cmd/bd/formula.go @@ -564,7 +564,7 @@ func runFormulaConvert(cmd *cobra.Command, args []string) { tomlPath := strings.TrimSuffix(jsonPath, formula.FormulaExtJSON) + formula.FormulaExtTOML // Write the TOML file - if err := os.WriteFile(tomlPath, tomlData, 0644); err != nil { + if err := os.WriteFile(tomlPath, tomlData, 0600); err != nil { fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", tomlPath, err) os.Exit(1) } @@ -623,7 +623,7 @@ func convertAllFormulas() { continue } - if err := os.WriteFile(tomlPath, tomlData, 0644); err != nil { + if err := os.WriteFile(tomlPath, tomlData, 0600); err != nil { fmt.Fprintf(os.Stderr, "✗ Error writing %s: %v\n", tomlPath, err) errors++ continue diff --git a/cmd/bd/gate.go b/cmd/bd/gate.go index e86fb220..f79dafa7 100644 --- a/cmd/bd/gate.go +++ b/cmd/bd/gate.go @@ -870,7 +870,7 @@ func evalTimerGate(gate *types.Issue, now time.Time) (bool, string) { // ghRunStatus represents the JSON output of `gh run view --json` type ghRunStatus struct { Status string `json:"status"` // queued, in_progress, completed - Conclusion string `json:"conclusion"` // success, failure, cancelled, skipped, etc. + Conclusion string `json:"conclusion"` // success, failure, canceled, skipped, etc. } // evalGHRunGate checks if a GitHub Actions run has completed. @@ -882,7 +882,7 @@ func evalGHRunGate(gate *types.Issue) (bool, string) { } // Run gh CLI to get run status - cmd := exec.Command("gh", "run", "view", runID, "--json", "status,conclusion") + cmd := exec.Command("gh", "run", "view", runID, "--json", "status,conclusion") //nolint:gosec // runID is from trusted issue.AwaitID field output, err := cmd.Output() if err != nil { // gh CLI failed - could be network issue, invalid run ID, or gh not installed @@ -924,7 +924,7 @@ func evalGHPRGate(gate *types.Issue) (bool, string) { } // Run gh CLI to get PR status - cmd := exec.Command("gh", "pr", "view", prNumber, "--json", "state,mergedAt") + cmd := exec.Command("gh", "pr", "view", prNumber, "--json", "state,mergedAt") //nolint:gosec // prNumber is from trusted issue.AwaitID field output, err := cmd.Output() if err != nil { // gh CLI failed - could be network issue, invalid PR, or gh not installed diff --git a/cmd/bd/mol_distill.go b/cmd/bd/mol_distill.go index 49e5f834..f101397e 100644 --- a/cmd/bd/mol_distill.go +++ b/cmd/bd/mol_distill.go @@ -254,9 +254,9 @@ func findWritableFormulaDir(formulaName string) string { if err := os.MkdirAll(dir, 0755); err == nil { // Check if we can write to it testPath := filepath.Join(dir, ".write-test") - if f, err := os.Create(testPath); err == nil { - f.Close() - os.Remove(testPath) + if f, err := os.Create(testPath); err == nil { //nolint:gosec // testPath is constructed from known search paths + _ = f.Close() + _ = os.Remove(testPath) return filepath.Join(dir, formulaName+formula.FormulaExt) } } diff --git a/cmd/bd/routed.go b/cmd/bd/routed.go index ee1dd31c..7dc951f6 100644 --- a/cmd/bd/routed.go +++ b/cmd/bd/routed.go @@ -50,14 +50,14 @@ func resolveAndGetIssueWithRouting(ctx context.Context, localStore storage.Stora // Step 2: Resolve and get from routed store result, err := resolveAndGetFromStore(ctx, routedStorage.Storage, id, true) if err != nil { - routedStorage.Close() + _ = routedStorage.Close() return nil, err } if result != nil { - result.closeFn = func() { routedStorage.Close() } + result.closeFn = func() { _ = routedStorage.Close() } return result, nil } - routedStorage.Close() + _ = routedStorage.Close() } // Step 3: Fall back to local store @@ -133,7 +133,7 @@ func getIssueWithRouting(ctx context.Context, localStore storage.Storage, id str // Step 3: Try the routed storage routedIssue, routedErr := routedStorage.Storage.GetIssue(ctx, id) if routedErr != nil || routedIssue == nil { - routedStorage.Close() + _ = routedStorage.Close() // Return the original error if routing also failed if err != nil { return nil, err @@ -148,7 +148,7 @@ func getIssueWithRouting(ctx context.Context, localStore storage.Storage, id str Routed: true, ResolvedID: id, closeFn: func() { - routedStorage.Close() + _ = routedStorage.Close() }, }, nil } diff --git a/internal/formula/controlflow.go b/internal/formula/controlflow.go index a83034f0..633cd109 100644 --- a/internal/formula/controlflow.go +++ b/internal/formula/controlflow.go @@ -212,6 +212,8 @@ func expandLoopWithVars(step *Step, vars map[string]string) ([]*Step, error) { // expandLoopIteration expands a single iteration of a loop. // The iteration index is used to generate unique step IDs. // The iterVars map contains loop variable bindings for this iteration (gt-8tmz.27). +// +//nolint:unparam // error return kept for API consistency with future error handling func expandLoopIteration(step *Step, iteration int, iterVars map[string]string) ([]*Step, error) { result := make([]*Step, 0, len(step.Loop.Body)) diff --git a/internal/routing/routes.go b/internal/routing/routes.go index eae2d4ad..ae823498 100644 --- a/internal/routing/routes.go +++ b/internal/routing/routes.go @@ -26,7 +26,7 @@ type Route struct { // Returns an empty slice if the file doesn't exist. func LoadRoutes(beadsDir string) ([]Route, error) { routesPath := filepath.Join(beadsDir, RoutesFileName) - file, err := os.Open(routesPath) + file, err := os.Open(routesPath) //nolint:gosec // routesPath is constructed from known beadsDir if err != nil { if os.IsNotExist(err) { return nil, nil // No routes file is not an error @@ -116,7 +116,7 @@ func ResolveBeadsDirForID(ctx context.Context, id, currentBeadsDir string) (stri // and resolves the redirect path if present. func resolveRedirect(beadsDir string) string { redirectFile := filepath.Join(beadsDir, "redirect") - data, err := os.ReadFile(redirectFile) + data, err := os.ReadFile(redirectFile) //nolint:gosec // redirectFile is constructed from known beadsDir if err != nil { if os.Getenv("BD_DEBUG_ROUTING") != "" { fmt.Fprintf(os.Stderr, "[routing] No redirect file at %s: %v\n", redirectFile, err)