diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..61243b1 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,375 @@ +name: Release + +# PRD F8, Gitea edition. On a v* tag push, build the single-file kigi binary +# for the five supported targets, package kigi--. +# {tar.gz|zip} (binary + LICENSE + NOTICE + THIRD-PARTY-NOTICES), and publish +# them as a Gitea Release alongside SHA256SUMS. install.sh, install.ps1 and +# auto_update::release_asset_name() all resolve these exact names — keep the +# naming in lockstep with them. +# +# RUNNERS. Gitea has no hosted runners; every label below must be a runner you +# registered yourself (docs/RELEASE.md has the register commands): +# +# ubuntu-latest Linux x86_64, container mode — the label the Linux server's +# runner already advertises. Builds both Linux targets; +# aarch64 is a cross-compile, so this leg installs the +# aarch64 GNU toolchain. Because the image is a bare +# runner-image, the Linux legs provision their own apt +# packages and rustup (see the provision steps below). +# Registering a dedicated `linux-x86_64:docker://...` runner +# instead is fine — swap the three labels back if you do. +# macos-arm64 Apple Silicon Mac, host mode. Builds both Darwin targets; +# x86_64 is a cross-compile against the same SDK. macOS +# CANNOT be built anywhere else — Apple's SDK licence +# restricts it to Apple hardware. +# windows-amd64 Windows x86_64, host mode, with VS 2022 Build Tools +# (C++ workload). Needed because the tree links ring, +# mimalloc, zstd-sys and libz-sys against a static MSVC CRT +# (-C target-feature=+crt-static). +# +# SHELLS. Every step names its shell explicitly and the Windows leg never uses +# bash. On a normal Windows box `bash` resolves to WSL (not Git Bash), and +# `pwsh` (PowerShell 7) is usually absent — so Windows steps use `powershell`, +# which is the built-in 5.1. Keep those steps 5.1-compatible: no `&&`, no +# ternary, no null-coalescing. +# +# Note: because this file exists, Gitea ignores .github/workflows entirely. +# +# PUBLISH ORDER. The release is created as a draft and only made public once +# SHA256SUMS is attached, so `releases/latest` never hands the installer a +# release whose checksum manifest is still missing. + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + # Matches the version pinned by the dotslash launcher in bin/protoc. That + # launcher is not used here: it has no Windows entry and needs a `cargo + # install dotslash` first, so every platform fetches protoc directly. + PROTOC_VERSION: "29.3" + +jobs: + prepare: + name: prepare draft release + runs-on: ubuntu-latest + outputs: + release_id: ${{ steps.draft.outputs.release_id }} + steps: + - uses: actions/checkout@v4 + + - name: Check tag matches workspace version + shell: bash + run: | + tag_version="${GITHUB_REF_NAME#v}" + cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" + if [ "$tag_version" != "$cargo_version" ]; then + echo "Tag $GITHUB_REF_NAME does not match workspace version $cargo_version" >&2 + exit 1 + fi + + - name: Create or reuse draft release + id: draft + shell: bash + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + run: | + set -eu + # The runner-image is bare; jq and curl are both used below. + if ! command -v jq >/dev/null || ! command -v curl >/dev/null; then + SUDO=""; command -v sudo >/dev/null && SUDO=sudo + export DEBIAN_FRONTEND=noninteractive + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq --no-install-recommends jq curl ca-certificates + fi + + # Idempotent so a re-run of a failed tag build reuses the same draft + # instead of erroring on a duplicate tag_name. + existing="$(curl -sS -H "Authorization: token $TOKEN" \ + "$API/releases/tags/$GITHUB_REF_NAME")" + release_id="$(printf '%s' "$existing" | jq -r '.id // empty')" + + if [ -z "$release_id" ]; then + prerelease=false + case "$GITHUB_REF_NAME" in *-*) prerelease=true ;; esac + created="$(curl -fsS -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\",\"draft\":true,\"prerelease\":$prerelease}" \ + "$API/releases")" + release_id="$(printf '%s' "$created" | jq -r '.id')" + echo "Created draft release $release_id (prerelease=$prerelease)" + else + echo "Reusing release $release_id for $GITHUB_REF_NAME" + fi + + echo "release_id=$release_id" >> "$GITHUB_OUTPUT" + + build: + name: build (${{ matrix.target }}) + needs: prepare + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-latest + protoc: linux-x86_64 + ext: tar.gz + linux: true + - target: aarch64-unknown-linux-gnu + runner: ubuntu-latest + protoc: linux-x86_64 + ext: tar.gz + linux: true + cross_gnu: true + - target: aarch64-apple-darwin + runner: macos-arm64 + protoc: osx-aarch_64 + ext: tar.gz + - target: x86_64-apple-darwin + runner: macos-arm64 + protoc: osx-aarch_64 + ext: tar.gz + - target: x86_64-pc-windows-msvc + runner: windows-amd64 + protoc: win64 + ext: zip + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + # ── linux container provisioning ────────────────────────────────────── + # The Linux legs run in a bare gitea runner-image, which ships git and + # node for the JavaScript actions but no compiler, no unzip (protoc) and + # no rustup. Host-mode macOS and Windows already have their toolchains + # installed, so this step is Linux-only. + - name: Provision Linux build deps + if: matrix.linux + shell: bash + run: | + set -eu + SUDO=""; command -v sudo >/dev/null && SUDO=sudo + export DEBIAN_FRONTEND=noninteractive + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq --no-install-recommends \ + build-essential pkg-config ca-certificates curl unzip tar jq + + # ── protoc ──────────────────────────────────────────────────────────── + - name: Install protoc (unix) + if: matrix.ext == 'tar.gz' + shell: bash + run: | + set -eu + url="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${{ matrix.protoc }}.zip" + curl -fsSL -o protoc.zip "$url" + rm -rf "$HOME/protoc" + unzip -q -o protoc.zip -d "$HOME/protoc" + chmod +x "$HOME/protoc/bin/protoc" + echo "$HOME/protoc/bin" >> "$GITHUB_PATH" + + - name: Install protoc (windows) + if: matrix.ext == 'zip' + shell: powershell + run: | + $ErrorActionPreference = "Stop" + $url = "https://github.com/protocolbuffers/protobuf/releases/download/v$env:PROTOC_VERSION/protoc-$env:PROTOC_VERSION-${{ matrix.protoc }}.zip" + Invoke-WebRequest -Uri $url -OutFile protoc.zip + $dest = Join-Path $env:USERPROFILE "protoc" + if (Test-Path $dest) { Remove-Item $dest -Recurse -Force } + Expand-Archive protoc.zip -DestinationPath $dest -Force + Add-Content -Path $env:GITHUB_PATH -Value (Join-Path $dest "bin") + + # ── toolchain ───────────────────────────────────────────────────────── + # rust-toolchain.toml pins the channel (1.97.0) and preinstalls both + # Linux targets; `rustup show` materialises exactly that. + - name: Install toolchain (unix) + if: matrix.ext == 'tar.gz' + shell: bash + run: | + set -eu + # No-op on the macOS host runner, which has rustup installed already. + # `--default-toolchain none` leaves the channel choice to + # rust-toolchain.toml, which `rustup show` then materialises. + if ! command -v rustup >/dev/null; then + curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal --default-toolchain none + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.cargo/bin:$PATH" + fi + rustup show + rustup target add ${{ matrix.target }} + + - name: Install toolchain (windows) + if: matrix.ext == 'zip' + shell: powershell + run: | + $ErrorActionPreference = "Stop" + rustup show + rustup target add ${{ matrix.target }} + + # ring, mimalloc, zstd-sys and libz-sys are all cc-rs builds, so the + # cross target needs its own C toolchain — cc-rs picks these up from the + # target-suffixed vars. + - name: Install aarch64 cross toolchain + if: matrix.cross_gnu + shell: bash + run: | + set -eu + SUDO=""; command -v sudo >/dev/null && SUDO=sudo + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + { + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" + echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" + echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" + echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar" + } >> "$GITHUB_ENV" + + # ── build ───────────────────────────────────────────────────────────── + - name: Build kigi (unix) + if: matrix.ext == 'tar.gz' + shell: bash + run: cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} + + - name: Build kigi (windows) + if: matrix.ext == 'zip' + shell: powershell + run: | + $ErrorActionPreference = "Stop" + cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + # ── package, upload, checksum ───────────────────────────────────────── + # Archives go straight to the release rather than through the artifact + # store: they are large, and the release is the only place install.sh + # can read them from anyway. Only the checksum line travels as an + # artifact, for the publish job to concatenate. + - name: Package and upload (unix) + if: matrix.ext == 'tar.gz' + shell: bash + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + RELEASE_ID: ${{ needs.prepare.outputs.release_id }} + run: | + set -eu + version="${GITHUB_REF_NAME#v}" + archive="kigi-${version}-${{ matrix.target }}.tar.gz" + staging="$(mktemp -d)" + cp "target/${{ matrix.target }}/release-dist/kigi" "$staging/kigi" + cp LICENSE "$staging/" + [ -f NOTICE ] && cp NOTICE "$staging/" + for f in THIRD-PARTY-NOTICES THIRD-PARTY-NOTICES.md; do + [ -f "$f" ] && cp "$f" "$staging/" + done + tar -C "$staging" -czf "$archive" . + + curl -fsS -X POST \ + -H "Authorization: token $TOKEN" \ + -F "attachment=@${archive}" \ + "$API/releases/$RELEASE_ID/assets?name=${archive}" > /dev/null + echo "Uploaded $archive" + + if command -v sha256sum >/dev/null; then + sha256sum "$archive" > "checksum-${{ matrix.target }}.txt" + else + shasum -a 256 "$archive" > "checksum-${{ matrix.target }}.txt" + fi + cat "checksum-${{ matrix.target }}.txt" + + - name: Package and upload (windows) + if: matrix.ext == 'zip' + shell: powershell + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + RELEASE_ID: ${{ needs.prepare.outputs.release_id }} + run: | + $ErrorActionPreference = "Stop" + $version = $env:GITHUB_REF_NAME.TrimStart("v") + $archive = "kigi-$version-${{ matrix.target }}.zip" + $staging = Join-Path $env:RUNNER_TEMP "staging" + if (Test-Path $staging) { Remove-Item $staging -Recurse -Force } + New-Item -ItemType Directory -Force -Path $staging | Out-Null + Copy-Item "target/${{ matrix.target }}/release-dist/kigi.exe" (Join-Path $staging "kigi.exe") + Copy-Item LICENSE $staging + if (Test-Path NOTICE) { Copy-Item NOTICE $staging } + foreach ($f in @("THIRD-PARTY-NOTICES", "THIRD-PARTY-NOTICES.md")) { + if (Test-Path $f) { Copy-Item $f $staging } + } + Compress-Archive -Path (Join-Path $staging "*") -DestinationPath $archive -Force + + # System32\curl.exe, not PowerShell's `curl` alias for + # Invoke-WebRequest, which cannot do multipart uploads. + $api = "$env:API/releases/$env:RELEASE_ID/assets?name=$archive" + & "$env:SystemRoot\System32\curl.exe" -fsS -X POST ` + -H "Authorization: token $env:TOKEN" ` + -F "attachment=@$archive" $api + if ($LASTEXITCODE -ne 0) { throw "asset upload failed ($LASTEXITCODE)" } + Write-Host "Uploaded $archive" + + # sha256sum's exact format, so the publish job can just concatenate. + # ASCII keeps the BOM out — a BOM would corrupt the first hash. + $hash = (Get-FileHash -Algorithm SHA256 -Path $archive).Hash.ToLowerInvariant() + "$hash $archive" | Out-File -FilePath "checksum-${{ matrix.target }}.txt" -Encoding ascii + Get-Content "checksum-${{ matrix.target }}.txt" + + - uses: actions/upload-artifact@v3 + with: + name: checksum-${{ matrix.target }} + path: checksum-${{ matrix.target }}.txt + if-no-files-found: error + + publish: + name: publish release + needs: [prepare, build] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + path: checksums + + - name: Assemble SHA256SUMS + shell: bash + run: | + set -eu + # download-artifact v3 nests each artifact in its own directory. The + # Windows line arrives CRLF-terminated; strip CR so every line is LF + # for install.sh's `read -r hash name`. + find checksums -name 'checksum-*.txt' -exec cat {} + \ + | tr -d '\r' | grep -v '^$' | sort -k2 > SHA256SUMS + lines="$(wc -l < SHA256SUMS)" + if [ "$lines" -ne 5 ]; then + echo "expected 5 checksum lines, got $lines" >&2 + cat SHA256SUMS >&2 + exit 1 + fi + cat SHA256SUMS + + - name: Attach SHA256SUMS and publish + shell: bash + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + RELEASE_ID: ${{ needs.prepare.outputs.release_id }} + run: | + set -eu + curl -fsS -X POST \ + -H "Authorization: token $TOKEN" \ + -F "attachment=@SHA256SUMS" \ + "$API/releases/$RELEASE_ID/assets?name=SHA256SUMS" > /dev/null + + # Flip the draft public only now that every asset install.sh looks + # for is attached. + curl -fsS -X PATCH \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"draft":false}' \ + "$API/releases/$RELEASE_ID" > /dev/null + echo "Published $GITHUB_REF_NAME" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 7febfd5..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,147 +0,0 @@ -name: Release - -# PRD F8: on tag push v*, build the single-file kigi binary for the five -# supported targets, package per-target archives named -# kigi--.{tar.gz|zip} (binary + LICENSE + NOTICE + -# THIRD-PARTY-NOTICES), generate SHA256SUMS, and publish everything as a -# GitHub Release. install.sh / install.ps1 and the in-app self-updater -# (kigi-update) both resolve these exact asset names — keep the naming in -# lockstep with auto_update::release_asset_name(). - -on: - push: - tags: ["v*"] - -permissions: - contents: write - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - name: build (${{ matrix.target }}) - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-apple-darwin - os: macos-14 - # macos-14 runners are arm64; the x86_64 slice is a cross-compile - # against the same SDK (macos-13 Intel runners are deprecated). - - target: x86_64-apple-darwin - os: macos-14 - - target: x86_64-unknown-linux-gnu - os: ubuntu-24.04 - - target: aarch64-unknown-linux-gnu - os: ubuntu-24.04-arm - - target: x86_64-pc-windows-msvc - os: windows-2022 - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - - name: Check tag matches workspace version - shell: bash - run: | - tag_version="${GITHUB_REF_NAME#v}" - cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" - if [ "$tag_version" != "$cargo_version" ]; then - echo "Tag $GITHUB_REF_NAME does not match workspace version $cargo_version" >&2 - exit 1 - fi - - - name: Install toolchain (rust-toolchain.toml) - run: rustup show - - - name: Add build target - run: rustup target add ${{ matrix.target }} - - - name: Install dotslash (protoc launcher) - run: cargo install dotslash --locked - - # Windows cannot exec the dotslash launcher directly (no shebang - # support → os error 193), and the build script's documented fallback - # is a protoc on PATH — install the same version the dotslash file - # pins (v29.3). - - name: Install protoc (Windows PATH fallback) - if: runner.os == 'Windows' - shell: pwsh - run: | - $url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.3/protoc-29.3-win64.zip" - Invoke-WebRequest -Uri $url -OutFile protoc.zip - Expand-Archive protoc.zip -DestinationPath "$env:USERPROFILE\protoc" - Add-Content $env:GITHUB_PATH "$env:USERPROFILE\protoc\bin" - - - uses: Swatinem/rust-cache@v2 - with: - key: ${{ matrix.target }} - - - name: Build kigi (release-dist) - run: cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} - - - name: Package archive (tar.gz) - if: runner.os != 'Windows' - shell: bash - run: | - version="${GITHUB_REF_NAME#v}" - archive="kigi-${version}-${{ matrix.target }}.tar.gz" - staging="$(mktemp -d)" - cp "target/${{ matrix.target }}/release-dist/kigi" "$staging/kigi" - cp LICENSE "$staging/" - [ -f NOTICE ] && cp NOTICE "$staging/" - for f in THIRD-PARTY-NOTICES THIRD-PARTY-NOTICES.md; do - [ -f "$f" ] && cp "$f" "$staging/" - done - tar -C "$staging" -czf "$archive" . - shasum -a 256 "$archive" || sha256sum "$archive" - echo "ARCHIVE=$archive" >> "$GITHUB_ENV" - - - name: Package archive (zip) - if: runner.os == 'Windows' - shell: pwsh - run: | - $version = $env:GITHUB_REF_NAME.TrimStart("v") - $archive = "kigi-$version-${{ matrix.target }}.zip" - $staging = New-Item -ItemType Directory -Path (Join-Path $env:RUNNER_TEMP "staging") - Copy-Item "target/${{ matrix.target }}/release-dist/kigi.exe" (Join-Path $staging "kigi.exe") - Copy-Item LICENSE $staging - if (Test-Path NOTICE) { Copy-Item NOTICE $staging } - foreach ($f in @("THIRD-PARTY-NOTICES", "THIRD-PARTY-NOTICES.md")) { - if (Test-Path $f) { Copy-Item $f $staging } - } - Compress-Archive -Path (Join-Path $staging "*") -DestinationPath $archive - Get-FileHash -Algorithm SHA256 $archive - Add-Content -Path $env:GITHUB_ENV -Value "ARCHIVE=$archive" - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ env.ARCHIVE }} - path: ${{ env.ARCHIVE }} - if-no-files-found: error - - release: - name: publish GitHub Release - needs: build - runs-on: ubuntu-24.04 - steps: - - uses: actions/download-artifact@v4 - with: - path: dist - merge-multiple: true - - - name: Generate SHA256SUMS - working-directory: dist - run: | - ls -l - sha256sum kigi-* > SHA256SUMS - cat SHA256SUMS - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: dist/* - prerelease: ${{ contains(github.ref_name, '-') }} - generate_release_notes: true - fail_on_unmatched_files: true diff --git a/.github/workflows/warm-cache.yml b/.github/workflows/warm-cache.yml deleted file mode 100644 index 35f1809..0000000 --- a/.github/workflows/warm-cache.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Warm build cache - -# Release builds run on TAG refs, and GitHub Actions cache isolation only -# lets a run restore caches created on its OWN ref or the DEFAULT branch. -# No workflow ran on `main`, so every release compiled the whole workspace -# cold on all five targets (~50 min wall clock, gated by Windows). This -# workflow builds the same `release-dist` profile on `main` so tag builds -# restore a warm default-branch cache. -# -# Triggers: dependency-affecting pushes to main (each release's version-bump -# commit warms the cache for the NEXT release), a weekly refresh so the -# cache never hits GitHub's 7-day unused-eviction, and manual dispatch. -# -# The setup steps mirror release.yml's build job (toolchain, target, -# dotslash/protoc, rust-cache key) — keep them in lockstep, or the cache -# key won't match and releases go back to cold builds. - -on: - push: - branches: ["main"] - paths: - - "Cargo.lock" - - "Cargo.toml" - - "rust-toolchain.toml" - - ".github/workflows/warm-cache.yml" - schedule: - - cron: "17 5 * * 1" - workflow_dispatch: - -permissions: - contents: read - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: warm-cache - cancel-in-progress: true - -jobs: - warm: - name: warm (${{ matrix.target }}) - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-apple-darwin - os: macos-14 - - target: x86_64-apple-darwin - os: macos-14 - - target: x86_64-unknown-linux-gnu - os: ubuntu-24.04 - - target: aarch64-unknown-linux-gnu - os: ubuntu-24.04-arm - - target: x86_64-pc-windows-msvc - os: windows-2022 - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - - name: Install toolchain (rust-toolchain.toml) - run: rustup show - - - name: Add build target - run: rustup target add ${{ matrix.target }} - - - name: Install dotslash (protoc launcher) - run: cargo install dotslash --locked - - - name: Install protoc (Windows PATH fallback) - if: runner.os == 'Windows' - shell: pwsh - run: | - $url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.3/protoc-29.3-win64.zip" - Invoke-WebRequest -Uri $url -OutFile protoc.zip - Expand-Archive protoc.zip -DestinationPath "$env:USERPROFILE\protoc" - Add-Content $env:GITHUB_PATH "$env:USERPROFILE\protoc\bin" - - # Same key as release.yml so tag builds restore this cache verbatim. - - uses: Swatinem/rust-cache@v2 - with: - key: ${{ matrix.target }} - - - name: Build kigi (release-dist) - run: cargo build --profile release-dist -p kigi-bin --locked --target ${{ matrix.target }} diff --git a/AGENTS.md b/AGENTS.md index e9009a0..3efcae1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,7 @@ a design against the 0.1.15 teardown), MCP servers on protocol 2026-07-28 ## Hard constraints - **Zero egress**: only `auth.kimi.com`, `api.kimi.com`, `api.moonshot.cn`, - `api.moonshot.ai`, GitHub Releases, user-configured MCP servers, credentialed + `api.moonshot.ai`, Gitea Releases (`git.zacharyzhang.com`), user-configured MCP servers, credentialed provider endpoints, and `models.dev` (metadata refresh, ONLY when an enabled platform's `/models` lacks it — `wire_serves_metadata=false`, never Kimi/Moonshot, `KIGI_MODELS_DEV_URL=0` disables). No telemetry, no analytics, @@ -640,4 +640,4 @@ client-side, no backend surface. grok.com references live only there and in `kigi-sampler`/proxy types. - M2: server-side search/fetch (F5), command parity with kimi-cli 1.49.0 (F6), one-time `~/.kimi/config.toml` import (F7), F9 smoke list, perf CI. -- M3: GitHub Releases distribution, install scripts, self-update (F8). +- M3: Gitea Releases distribution, install scripts, self-update (F8). diff --git a/NOTICE b/NOTICE index f0233d7..d841538 100644 --- a/NOTICE +++ b/NOTICE @@ -14,7 +14,7 @@ Modifications relative to the upstream work (Apache License 2.0 §4(b) notice): subscription-proxy endpoints. - Endpoints re-targeted at the Kimi Code subscription API (api.kimi.com/coding/v1), the Moonshot open platform (api.moonshot.cn/v1, - api.moonshot.ai/v1), and GitHub Releases for self-update. + api.moonshot.ai/v1), and Gitea Releases (git.zacharyzhang.com) for self-update. This product includes code ported from openai/codex and sst/opencode; see crates/codegen/kigi-tools/THIRD_PARTY_NOTICES.md for the license terms and diff --git a/SECURITY.md b/SECURITY.md index 2192c5d..978e13f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,7 +1,7 @@ # Security Policy Kigi is a community build; report vulnerabilities on the -[Kigi issue tracker](https://github.com/ZacharyZhang-NY/Kigi-CLI/issues). +[Kigi issue tracker](https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues). Issues there are public, so please leave out any detail that would make a report directly exploitable — open with the affected area and impact, and a maintainer will follow up for the rest. diff --git a/crates/codegen/kigi-env/src/lib.rs b/crates/codegen/kigi-env/src/lib.rs index e39bf16..8a9ed24 100644 --- a/crates/codegen/kigi-env/src/lib.rs +++ b/crates/codegen/kigi-env/src/lib.rs @@ -21,7 +21,7 @@ pub struct KigiEndpoints { pub const PRODUCTION_ENDPOINTS: KigiEndpoints = KigiEndpoints { coding_api_base_url: "https://api.kimi.com/coding/v1", oauth_host: "https://auth.kimi.com", - update_base_url: "https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases", + update_base_url: "https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases", upgrade_page_url: "https://www.kimi.com/code/", }; diff --git a/crates/codegen/kigi-shell/src/agent/feedback_client.rs b/crates/codegen/kigi-shell/src/agent/feedback_client.rs index 0a5a578..258caf7 100644 --- a/crates/codegen/kigi-shell/src/agent/feedback_client.rs +++ b/crates/codegen/kigi-shell/src/agent/feedback_client.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use serde::Serialize; /// Where non-subscription users (no OAuth session) submit feedback instead. -pub const FEEDBACK_ISSUES_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/issues"; +pub const FEEDBACK_ISSUES_URL: &str = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues"; /// HTTP error from the feedback endpoint with a preserved status code, so /// callers can distinguish auth failures (401) without string matching. diff --git a/crates/codegen/kigi-tui/src/slash/commands/docs.rs b/crates/codegen/kigi-tui/src/slash/commands/docs.rs index 76d9929..9c6fae9 100644 --- a/crates/codegen/kigi-tui/src/slash/commands/docs.rs +++ b/crates/codegen/kigi-tui/src/slash/commands/docs.rs @@ -10,7 +10,7 @@ use crate::slash::command::{AppCtx, ArgItem, CommandExecCtx, CommandResult, Slas /// Online docs landing page (hardcoded like other TUI deep-links; the /// project README is the docs entry point for the repo-hosted docs). -pub const BUILD_DOCS_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/blob/main/README.md"; +pub const BUILD_DOCS_URL: &str = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/src/branch/main/README.md"; /// Open How-to Guides or online Build docs. pub struct DocsCommand; diff --git a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs index 122f8c2..009d56b 100644 --- a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs +++ b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs @@ -6,7 +6,7 @@ use crate::slash::command::{CommandExecCtx, CommandResult, SlashCommand}; /// Where feedback goes: the project's own issue tracker. Kigi is a community /// build, so its feedback belongs on its own repo — mirroring the official /// kimi-cli, whose `/feedback` opens its repo's issues page. -pub const FEEDBACK_ISSUES_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/issues"; +pub const FEEDBACK_ISSUES_URL: &str = "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/issues"; /// Open the Kigi issue tracker in the browser. pub struct FeedbackCommand; diff --git a/crates/codegen/kigi-update/src/auto_update.rs b/crates/codegen/kigi-update/src/auto_update.rs index f26e1d7..0dc9ece 100644 --- a/crates/codegen/kigi-update/src/auto_update.rs +++ b/crates/codegen/kigi-update/src/auto_update.rs @@ -695,7 +695,7 @@ pub(crate) fn detect_platform() -> Result<(&'static str, &'static str)> { /// Rust target triple for this build — the key that maps a platform to its /// release-asset name. Must stay in lockstep with the five targets built by -/// `.github/workflows/release.yml` and the tables in install.sh/install.ps1. +/// `.gitea/workflows/release.yml` and the tables in install.sh/install.ps1. pub(crate) fn target_triple() -> Result<&'static str> { let triple = if cfg!(all(target_os = "macos", target_arch = "aarch64")) { "aarch64-apple-darwin" @@ -2653,7 +2653,7 @@ mod tests { #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[test] fn test_release_asset_name_matches_release_workflow_naming() { - // Must stay in lockstep with .github/workflows/release.yml, which + // Must stay in lockstep with .gitea/workflows/release.yml, which // publishes kigi--.{tar.gz|zip}. let triple = target_triple().unwrap(); assert!( diff --git a/crates/codegen/kigi-update/src/version.rs b/crates/codegen/kigi-update/src/version.rs index 7e8f8a8..827d429 100644 --- a/crates/codegen/kigi-update/src/version.rs +++ b/crates/codegen/kigi-update/src/version.rs @@ -10,7 +10,7 @@ const TTL_SECONDS_BEFORE_AUTO_UPDATE: Duration = Duration::from_secs(60 * 30); /// Release channel: this repo's Releases (PRD F8). The API base is resolved /// through [`kigi_env::update_base_url`] — production default -/// `https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases`, +/// `https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases`, /// overridable via `KIGI_UPDATE_BASE_URL` for mirrors and tests. pub(crate) fn update_base_url() -> String { kigi_env::update_base_url() @@ -533,7 +533,7 @@ mod tests { #[test] fn test_release_json_parses_github_wire_shape() { let json = r#"{ - "url": "https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases/1", + "url": "https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/Kigi-CLI/releases/1", "tag_name": "v0.1.0", "name": "Kigi 0.1.0", "draft": false, @@ -541,13 +541,13 @@ mod tests { "assets": [ { "name": "kigi-0.1.0-aarch64-apple-darwin.tar.gz", - "browser_download_url": "https://github.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/kigi-0.1.0-aarch64-apple-darwin.tar.gz", + "browser_download_url": "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/kigi-0.1.0-aarch64-apple-darwin.tar.gz", "size": 123, "content_type": "application/gzip" }, { "name": "SHA256SUMS", - "browser_download_url": "https://github.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/SHA256SUMS" + "browser_download_url": "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/SHA256SUMS" } ] }"#; @@ -560,7 +560,7 @@ mod tests { let asset = r.asset("kigi-0.1.0-aarch64-apple-darwin.tar.gz").unwrap(); assert_eq!( asset.browser_download_url, - "https://github.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/kigi-0.1.0-aarch64-apple-darwin.tar.gz" + "https://git.zacharyzhang.com/ZacharyZhang-NY/Kigi-CLI/releases/download/v0.1.0/kigi-0.1.0-aarch64-apple-darwin.tar.gz" ); assert_eq!( r.asset("SHA256SUMS").unwrap().name, diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 242579e..51cea41 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -1,14 +1,17 @@ # Release checklist (PRD F8) Kigi ships as a single-file binary for five targets, published on this -repo's GitHub Releases by `.github/workflows/release.yml`. Users install via +repo's Gitea Releases by `.gitea/workflows/release.yml`. Users install via `install.sh` / `install.ps1` and stay current through the in-app self-updater (`kigi-update`), which resolves the same Releases API. +`.github/workflows/release.yml` is the retired GitHub pipeline. Gitea ignores +`.github/workflows` entirely whenever `.gitea/workflows` is non-empty, so it +does not run — it is kept only as a reference for the GitHub-hosted layout. + ## Cutting a release -1. Bump `[workspace.package] version` in `Cargo.toml` (and refresh - `Cargo.lock` with `cargo update --workspace`); land the change on +1. Bump `[workspace.package] version` in `Cargo.toml`; land the change on `main` with green CI. 2. Regenerate the third-party notices (not enforced by CI — this is the step that keeps `THIRD-PARTY-NOTICES.md` fresh): @@ -31,10 +34,15 @@ self-updater (`kigi-update`), which resolves the same Releases API. `release-dist` profile, packages `kigi--.{tar.gz|zip}` archives (binary + LICENSE + NOTICE + THIRD-PARTY-NOTICES), generates `SHA256SUMS`, and - publishes the GitHub Release with every asset attached in one step, so - `releases/latest` never serves a release whose checksum manifest is - missing. Tags containing `-` (e.g. `v0.2.0-alpha.1`) publish as - pre-releases, which only the `alpha` update channel picks up. + publishes the Gitea Release. Tags containing `-` (e.g. + `v0.2.0-alpha.1`) publish as pre-releases, which only the `alpha` + update channel picks up. + + The release is created as a **draft** and only flipped public once + `SHA256SUMS` is attached, so `releases/latest` never hands the installer + a release whose checksum manifest is still missing. A failed run leaves + the draft in place; re-pushing the tag reuses it rather than erroring on + a duplicate `tag_name`. 5. Smoke-test an installed artifact: ```sh @@ -44,27 +52,127 @@ self-updater (`kigi-update`), which resolves the same Releases API. ## Build runners -All five legs run on GitHub-hosted runners — nothing self-hosted to keep -alive: +Gitea has no hosted runners, so all three `runs-on` labels below are machines +we register ourselves with `gitea-runner` (the binary formerly called +`act_runner`). Labels are declared in the runner's `config.yaml` as +`