Skip to content

Commit bb151ef

Browse files
authored
Merge pull request #4 from codellm-devkit/feat/initial-implementation
feat: initial implementation of codeanalyzer-go
2 parents 4f22d02 + 52b87b6 commit bb151ef

92 files changed

Lines changed: 10017 additions & 5 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/configuration.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"categories": [
3+
{ "title": "### 🚀 Features", "labels": ["feature"] },
4+
{ "title": "### 🐛 Fixes", "labels": ["fix"] },
5+
{ "title": "### ♻️ Refactoring", "labels": ["refactor"] },
6+
{ "title": "### 📝 Documentation", "labels": ["docs"] },
7+
{ "title": "### 🧪 Tests", "labels": ["test"] },
8+
{ "title": "### 🔧 Maintenance", "labels": ["ci", "chore"] },
9+
{ "title": "### 📦 Other", "labels": [] }
10+
],
11+
"ignore_labels": ["ignore-for-release"],
12+
"sort": { "order": "ASC", "on_property": "mergedAt" },
13+
"template": "#{{CHANGELOG}}",
14+
"pr_template": "- #{{TITLE}} (##{{NUMBER}}) @#{{AUTHOR}}",
15+
"empty_template": "- _No user-facing changes._",
16+
"max_pull_requests": 200,
17+
"max_back_track_time_days": 365
18+
}

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, "feat/**"]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
name: Test
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Go
17+
uses: actions/setup-go@v5
18+
with:
19+
go-version-file: go.mod
20+
21+
- name: Build
22+
run: go build ./...
23+
24+
- name: Test
25+
run: go test ./... -count=1
26+
27+
lint:
28+
name: Lint
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Set up Go
34+
uses: actions/setup-go@v5
35+
with:
36+
go-version-file: go.mod
37+
38+
- name: Install golangci-lint
39+
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
40+
41+
- name: golangci-lint
42+
run: golangci-lint run --timeout=5m

.github/workflows/release.yml

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
name: Release (binaries + Python wheels)
2+
3+
# Cross-compiles the self-contained cango (codeanalyzer-go) binary for every
4+
# supported platform and publishes:
5+
# 1. the raw binaries as GitHub Release assets (for analysis_backend_path /
6+
# shell-script / Homebrew install), and
7+
# 2. platform-tagged Python wheels to PyPI as `codeanalyzer-go`.
8+
#
9+
# Go cross-compiles all targets from one host (GOOS/GOARCH, CGO_ENABLED=0), so a
10+
# single Linux job suffices -- the same shape as codeanalyzer-typescript.
11+
12+
on:
13+
push:
14+
tags:
15+
- "v*.*.*"
16+
workflow_dispatch: {}
17+
18+
permissions:
19+
contents: write # create GitHub Release + delete tag on failure
20+
id-token: write # PyPI Trusted Publishing (OIDC) -- no API token needed
21+
discussions: write # attach the release-linked repo Discussion (Announcements)
22+
23+
jobs:
24+
release:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Check out code
28+
uses: actions/checkout@v4
29+
30+
# Derive the release version from the tag (vX.Y.Z -> X.Y.Z). On a manual
31+
# workflow_dispatch run there is no tag, so fall back to a dev version.
32+
- name: Determine version
33+
id: ver
34+
run: |
35+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
36+
version="${GITHUB_REF#refs/tags/v}"
37+
else
38+
version="0.0.0-dev"
39+
fi
40+
echo "version=$version" >> "$GITHUB_OUTPUT"
41+
echo ">>> Releasing version $version"
42+
43+
- name: Set up Go
44+
uses: actions/setup-go@v5
45+
with:
46+
go-version: "1.25"
47+
check-latest: true
48+
49+
# ----- test gate: a broken build must not produce a release -----
50+
- name: Run tests
51+
id: test
52+
continue-on-error: true
53+
run: go test ./... -count=1
54+
55+
- name: Delete tag on failure
56+
if: steps.test.conclusion == 'failure' && startsWith(github.ref, 'refs/tags/')
57+
run: |
58+
echo "Tests failed. Deleting tag ${GITHUB_REF#refs/tags/}..."
59+
git config --global user.name "github-actions[bot]"
60+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
61+
git push --delete origin "${GITHUB_REF#refs/tags/}"
62+
exit 1
63+
64+
- name: Fail if tests failed (non-tag runs)
65+
if: steps.test.conclusion == 'failure'
66+
run: exit 1
67+
68+
- name: Set up Python
69+
uses: actions/setup-python@v5
70+
with:
71+
python-version: "3.11"
72+
73+
- name: Install Python build tooling
74+
# hatchling is the build backend; build_wheels.sh runs `python -m build
75+
# --no-isolation`, so it must be present in this env (no auto-install).
76+
run: python -m pip install --upgrade build wheel hatchling
77+
78+
- name: Build platform wheels (cross-compiles every target via go build)
79+
working-directory: packaging/python
80+
env:
81+
PKG_VERSION: ${{ steps.ver.outputs.version }}
82+
run: ./build_wheels.sh
83+
84+
- name: Extract raw binaries from wheels (for GitHub Release)
85+
working-directory: packaging/python
86+
run: |
87+
mkdir -p ../../release-bins
88+
for whl in dist/*.whl; do
89+
plat="$(basename "$whl" .whl | sed 's/.*-py3-none-//')"
90+
tmp="$(mktemp -d)"
91+
python -m zipfile -e "$whl" "$tmp"
92+
bin="$(find "$tmp/codeanalyzer_go/_bin" -type f -name 'cango*')"
93+
ext=""; [[ "$bin" == *.exe ]] && ext=".exe"
94+
cp "$bin" "../../release-bins/cango-${plat}${ext}"
95+
done
96+
ls -lh ../../release-bins
97+
98+
# Publish the install script so users can:
99+
# curl --proto '=https' --tlsv1.2 -LsSf .../releases/latest/download/cango-installer.sh | sh
100+
- name: Stage the install script (release asset)
101+
run: cp packaging/install/cango-installer.sh release-bins/cango-installer.sh
102+
103+
- name: Build changelog (auto-generated from commits/PRs)
104+
id: changelog
105+
if: startsWith(github.ref, 'refs/tags/')
106+
uses: mikepenz/release-changelog-builder-action@v5
107+
with:
108+
configuration: ".github/configuration.json"
109+
failOnError: "false"
110+
env:
111+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
113+
# Install one-liners + a download table, then the auto-generated changelog
114+
# from the step above. Indented code blocks avoid backticks in the heredoc.
115+
- name: Compose release notes
116+
id: notes
117+
if: startsWith(github.ref, 'refs/tags/')
118+
env:
119+
VERSION: ${{ steps.ver.outputs.version }}
120+
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
121+
run: |
122+
REPO="codellm-devkit/codeanalyzer-go"
123+
BASE="https://github.com/$REPO/releases/download/v$VERSION"
124+
cat > "$RUNNER_TEMP/RELEASE_BODY.md" <<EOF
125+
## Install codeanalyzer-go v$VERSION
126+
127+
Shell script (prebuilt binary; macOS and Linux):
128+
129+
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/$REPO/releases/latest/download/cango-installer.sh | sh
130+
131+
Homebrew:
132+
133+
brew install codellm-devkit/homebrew-tap/codeanalyzer-go
134+
135+
PyPI:
136+
137+
pip install codeanalyzer-go==$VERSION
138+
139+
## Download
140+
141+
| File | Platform |
142+
| --- | --- |
143+
| [cango-macosx_11_0_arm64]($BASE/cango-macosx_11_0_arm64) | Apple Silicon macOS |
144+
| [cango-macosx_10_12_x86_64]($BASE/cango-macosx_10_12_x86_64) | Intel macOS |
145+
| [cango-manylinux2014_x86_64]($BASE/cango-manylinux2014_x86_64) | x86-64 Linux |
146+
| [cango-manylinux2014_aarch64]($BASE/cango-manylinux2014_aarch64) | ARM64 Linux |
147+
| [cango-win_amd64.exe]($BASE/cango-win_amd64.exe) | x64 Windows |
148+
| [cango-installer.sh]($BASE/cango-installer.sh) | Shell installer |
149+
150+
## Changelog
151+
152+
$CHANGELOG
153+
EOF
154+
echo "----- composed release body -----"; cat "$RUNNER_TEMP/RELEASE_BODY.md"
155+
156+
- name: Publish GitHub Release (raw binaries)
157+
uses: softprops/action-gh-release@v2
158+
if: startsWith(github.ref, 'refs/tags/')
159+
with:
160+
files: release-bins/*
161+
body_path: ${{ runner.temp }}/RELEASE_BODY.md
162+
# Auto-open a repo-level Discussion linked to this release, seeded with
163+
# the same notes. Requires Discussions enabled and this category to exist.
164+
discussion_category_name: Announcements
165+
env:
166+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
167+
168+
# Mirror the release announcement into the ORG-level discussions, which are
169+
# backed by codellm-devkit/.github. GITHUB_TOKEN can't write cross-repo, so
170+
# this uses a PAT/App token (ORG_DISCUSSIONS_TOKEN) with discussions:write
171+
# on that repo, and posts via the createDiscussion GraphQL mutation. Reuses
172+
# the already-composed RELEASE_BODY.md so org and repo posts stay identical.
173+
- name: Announce in org-level discussions (codellm-devkit/.github)
174+
if: startsWith(github.ref, 'refs/tags/')
175+
continue-on-error: true # a failed org post must not fail an otherwise-good release
176+
env:
177+
GH_TOKEN: ${{ secrets.ORG_DISCUSSIONS_TOKEN }}
178+
VERSION: ${{ steps.ver.outputs.version }}
179+
run: |
180+
set -uo pipefail
181+
OWNER="codellm-devkit"; REPO=".github"; CATEGORY="Announcements"
182+
# The mutation needs GraphQL node IDs, not names — resolve them first.
183+
RESP=$(gh api graphql \
184+
-f query='query($o:String!,$r:String!){repository(owner:$o,name:$r){id discussionCategories(first:25){nodes{id name}}}}' \
185+
-f o="$OWNER" -f r="$REPO") \
186+
|| { echo "::warning::org discussion lookup failed — skipping org announcement."; exit 0; }
187+
REPO_ID=$(echo "$RESP" | jq -r '.data.repository.id')
188+
CAT_ID=$(echo "$RESP" | jq -r --arg c "$CATEGORY" '.data.repository.discussionCategories.nodes[]|select(.name==$c)|.id')
189+
if [[ -z "$REPO_ID" || "$REPO_ID" == "null" || -z "$CAT_ID" ]]; then
190+
echo "::warning::could not resolve $OWNER/$REPO discussion category '$CATEGORY' — skipping org announcement."
191+
exit 0
192+
fi
193+
gh api graphql \
194+
-f query='mutation($rid:ID!,$cid:ID!,$t:String!,$b:String!){createDiscussion(input:{repositoryId:$rid,categoryId:$cid,title:$t,body:$b}){discussion{url}}}' \
195+
-f rid="$REPO_ID" -f cid="$CAT_ID" \
196+
-f t="codeanalyzer-go v$VERSION" \
197+
-f b="$(cat "$RUNNER_TEMP/RELEASE_BODY.md")"
198+
199+
- name: Publish wheels to PyPI (Trusted Publishing / OIDC)
200+
if: startsWith(github.ref, 'refs/tags/')
201+
uses: pypa/gh-action-pypi-publish@release/v1
202+
with:
203+
packages-dir: packaging/python/dist
204+
# Make re-runs idempotent: a version already on PyPI is skipped rather
205+
# than failing the whole job with a 400 (PyPI rejects duplicate uploads).
206+
skip-existing: true
207+
208+
# Hand the raw binaries to the separate `homebrew` job below. Passing them
209+
# as an artifact (rather than re-downloading the GitHub Release) keeps the
210+
# formula checksums byte-identical to what was just published.
211+
- name: Upload release binaries for the homebrew job
212+
if: startsWith(github.ref, 'refs/tags/')
213+
uses: actions/upload-artifact@v4
214+
with:
215+
name: release-bins
216+
path: release-bins/
217+
218+
# ----- Homebrew: regenerate the formula from the just-built binaries and push
219+
# it to the shared tap. Split into its own job (needs: release) so a tap-push
220+
# failure -- e.g. a missing HOMEBREW_TAP_TOKEN -- is isolated from the PyPI and
221+
# GitHub Release steps above, and can be re-run on its own without re-uploading
222+
# wheels.
223+
homebrew:
224+
needs: release
225+
if: startsWith(github.ref, 'refs/tags/')
226+
runs-on: ubuntu-latest
227+
steps:
228+
- name: Check out code
229+
uses: actions/checkout@v4
230+
231+
- name: Derive version from tag
232+
id: ver
233+
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
234+
235+
- name: Download release binaries
236+
uses: actions/download-artifact@v4
237+
with:
238+
name: release-bins
239+
path: release-bins
240+
241+
- name: Generate Homebrew formula
242+
env:
243+
REPO: ${{ github.repository }}
244+
VERSION: ${{ steps.ver.outputs.version }}
245+
run: |
246+
./packaging/homebrew/generate_formula.sh release-bins > codeanalyzer-go.rb
247+
cat codeanalyzer-go.rb
248+
249+
- name: Push formula to codellm-devkit/homebrew-tap
250+
env:
251+
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} # PAT with write access to homebrew-tap
252+
VERSION: ${{ steps.ver.outputs.version }}
253+
run: |
254+
git clone "https://x-access-token:${TAP_TOKEN}@github.com/codellm-devkit/homebrew-tap.git" tap
255+
mkdir -p tap/Formula
256+
cp codeanalyzer-go.rb tap/Formula/codeanalyzer-go.rb
257+
cd tap
258+
git config user.name "github-actions[bot]"
259+
git config user.email "github-actions[bot]@users.noreply.github.com"
260+
git add Formula/codeanalyzer-go.rb
261+
git commit -m "codeanalyzer-go ${VERSION}" || { echo "no formula change"; exit 0; }
262+
git push

.gitignore

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
# If you prefer the allow list template instead of the deny list, see community template:
2-
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3-
#
41
# Binaries for programs and plugins
52
*.exe
63
*.exe~
74
*.dll
85
*.so
96
*.dylib
107

8+
# Project-specific binaries
9+
/codeanalyzer
10+
/codeanalyzer-go
11+
/cango
12+
13+
# Build output
14+
/dist/
15+
/bin/
16+
17+
# Release packaging artifacts (produced by packaging/python/build_wheels.sh and
18+
# the release workflow; the bundled per-platform binary is ignored via
19+
# packaging/python/src/codeanalyzer_go/_bin/.gitignore).
20+
/release-bins/
21+
/packaging/python/dist/
22+
/packaging/python/README.md
23+
codeanalyzer-go.rb
24+
1125
# Test binary, built with `go test -c`
1226
*.test
1327

1428
# Output of the go coverage tool, specifically when used with LiteIDE
1529
*.out
30+
coverage.txt
1631

1732
# Dependency directories (remove the comment below to include it)
1833
# vendor/
@@ -23,3 +38,12 @@ go.work.sum
2338

2439
# env file
2540
.env
41+
42+
# Claude Code session data
43+
.claude/
44+
45+
# macOS
46+
.DS_Store
47+
48+
# Generated analysis output
49+
/output/

0 commit comments

Comments
 (0)