From e6b1840ffcdf13de30593920c5cf744743f8ab76 Mon Sep 17 00:00:00 2001 From: Rodin Date: Mon, 11 May 2026 06:24:05 -0700 Subject: [PATCH 1/2] fix(gitea): normalize "." path to empty string in ListContents Gitea API rejects "." with HTTP 500 (malformed path component). When patterns-files is set to ".", normalize it to empty string before making the API call. Fixes #70 --- gitea/client.go | 4 ++++ gitea/client_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/gitea/client.go b/gitea/client.go index b016349..9cef17d 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -435,6 +435,10 @@ type ContentEntry struct { // ListContents lists files and directories at a given path in a repo. // Pass an empty path to list the repository root. func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]ContentEntry, error) { + // Normalize "." to empty string — Gitea API rejects "." with 500 + if path == "." { + path = "" + } var reqURL string if path == "" { reqURL = fmt.Sprintf("%s/api/v1/repos/%s/%s/contents", c.baseURL, url.PathEscape(owner), url.PathEscape(repo)) diff --git a/gitea/client_test.go b/gitea/client_test.go index 76156e7..ba49fd5 100644 --- a/gitea/client_test.go +++ b/gitea/client_test.go @@ -280,6 +280,30 @@ func TestListContents(t *testing.T) { } } +func TestListContents_DotPath(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // "." should be normalized to empty path, which hits the root contents endpoint + if r.URL.Path != "/api/v1/repos/owner/repo/contents" { + t.Errorf("expected root contents path, got: %s", r.URL.Path) + } + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `[{"name":"README.md","path":"README.md","type":"file"}]`) + })) + defer server.Close() + + client := NewClient(server.URL, "test-token") + entries, err := client.ListContents(context.Background(), "owner", "repo", ".") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(entries) != 1 { + t.Fatalf("expected 1 entry, got %d", len(entries)) + } + if entries[0].Name != "README.md" { + t.Errorf("expected README.md, got %s", entries[0].Name) + } +} + func TestGetAllFilesInPath_File(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/v1/repos/owner/repo/contents/README.md" { From ea74f7e088c44fa0cd240b1bb0568c6c52ef05f4 Mon Sep 17 00:00:00 2001 From: Rodin Date: Mon, 11 May 2026 07:12:25 -0700 Subject: [PATCH 2/2] ci: use rodin/security-patterns with '.' path for security reviewer Tests the dot path normalization fix end-to-end. --- .gitea/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fa80d7b..d6f2299 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: - name: security token_secret: SECURITY_REVIEW_TOKEN model: gpt-5 + patterns_repo: rodin/security-patterns + patterns_files: "." system_prompt_file: SECURITY_REVIEW.md steps: - uses: actions/checkout@v4 @@ -60,8 +62,8 @@ jobs: AICORE_API_URL: ${{ secrets.AICORE_API_URL }} AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }} CONVENTIONS_FILE: "CONVENTIONS.md" - PATTERNS_REPO: "rodin/go-patterns" - PATTERNS_FILES: "README.md,patterns/" + PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }} + PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }} LLM_TIMEOUT: "600" SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }} run: ./review-bot