Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ name: CI

# The fork's value is being able to cut a reliable release when it matters, so
# gate every PR (and main) on the pieces a release depends on: a clean install,
# the TypeScript build, and a real native Linux kernel build validated by the
# kernel walker-parity suites.
# the TypeScript build, a real native Linux kernel build validated by the
# kernel walker-parity suites, and the whole test suite. The Release
# workflow calls this one too, on the commit it is about to publish.
on:
pull_request:
push:
branches: [main]
workflow_call:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
# Called from Release, github.workflow is "Release", so a release's test run
# and a push to main never cancel each other.
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -38,3 +42,13 @@ jobs:
# Fail if the native kernel is missing or diverges from the reference
# walker — CODEGRAPH_KERNEL_EXPECT=1 turns a missing binary into an error.
run: CODEGRAPH_KERNEL_EXPECT=1 npx vitest run __tests__/kernel-*.test.ts

- name: Test suite
# Three daemon-older-version cases fail on any fork build by design:
# they compare plain X.Y.Z releases, and ours carry -fmagent.N
# (FORK.md). They are skipped by name because each workspace project's
# own exclude list overrides --exclude. The kernel suites run again
# here, which takes seconds.
run: >-
npx vitest run -t
"^(?!a launcher meeting a daemon of another version \(#2335\) (finds an older daemon|keeps the older daemon|stops a daemon from an older install))"
63 changes: 50 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ name: Release
# FM-Agent maintenance fork — trimmed from upstream v1.5.0's release workflow.
#
# Manually triggered ("Run workflow") from the `main` branch only. On trigger it:
# 1. builds the native Rust extraction kernel per platform (the `kernel`
# matrix, unchanged from upstream — required as of 1.5.0),
# 2. bundles a self-contained per-OS archive (mac/linux/windows, arm64/x64),
# 3. creates the GitHub Release (tag v<version>) pinned to the built commit,
# with all archives + SHA256SUMS.
# 1. refuses a version already released from another commit (`preflight`),
# 2. runs the CI workflow — build, kernel gate and the test suite — on the
# commit being released (`test`), alongside
# 3. the native Rust extraction kernel per platform (the `kernel` matrix,
# unchanged from upstream — required as of 1.5.0),
# 4. bundles a self-contained per-OS archive (mac/linux/windows, arm64/x64),
# 5. creates the GitHub Release (tag v<version>) pinned to the built commit,
# with all archives + SHA256SUMS, and notes listing the pull requests
# merged since the previous release.
#
# Before triggering: bump package.json's version to the target
# (X.Y.Z-fmagent.N) and make sure the intended patches are on this branch.
# A version is published once: `preflight` stops a release whose tag already
# points at another commit, so a forgotten bump cannot replace its bundles.
#
# Removed vs upstream: npm publish, npmmirror sync, build-provenance attestation,
# and the CHANGELOG / package-lock auto-commit-and-push steps — this fork
Expand All @@ -34,6 +40,29 @@ permissions:
contents: write # create the GitHub Release + tag

jobs:
# Runs first and gates every other job. The version comes from package.json;
# its tag either does not exist yet, or points at this very commit (a re-run
# after a failed upload). Anything else means the bump was forgotten.
preflight:
# Same rule as the kernel job below.
if: github.ref == 'refs/heads/main' || inputs.prerelease
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Resolve version
id: ver
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- name: Refuse a version already released from another commit
run: bash scripts/check-release-tag.sh "v${{ steps.ver.outputs.version }}" "$GITHUB_SHA"

# The gate every pull request passes, run again on the commit being released:
# a prerelease is often cut from a branch no pull request has tested yet.
test:
needs: preflight
uses: ./.github/workflows/ci.yml

# Native extraction-kernel prebuilds — REQUIRED as of 1.5.0 (the kernel is the
# release's headline, not an optional extra). Each leg builds its platform's
# codegraph-kernel.node natively; the vendored-grammar-C languages
Expand All @@ -46,6 +75,7 @@ jobs:
# anyone's upgrade target, and being able to build one from the branch under
# review is the point — main stays untouched while the build is tried.
if: github.ref == 'refs/heads/main' || inputs.prerelease
needs: preflight
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -78,7 +108,7 @@ jobs:
# Same rule as the kernel job above.
if: github.ref == 'refs/heads/main' || inputs.prerelease
runs-on: ubuntu-latest
needs: kernel
needs: [preflight, test, kernel]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
Expand All @@ -88,10 +118,6 @@ jobs:
- name: Ensure zip/unzip
run: sudo apt-get update -qq && sudo apt-get install -y -qq zip unzip

- name: Resolve version
id: ver
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"

- name: Download kernel prebuilds
# All legs are required (see the kernel job), so every target's
# <target>/codegraph-kernel.node must be present in release/kernel/.
Expand Down Expand Up @@ -134,13 +160,22 @@ jobs:
# The base is NOT named here. It moves every sync, a literal goes stale
# silently, and one did: every release from v1.6.0 on said "v1.5.0".
# FORK.md carries the pinned base, and it is read from this same commit.
# What changed comes from GitHub's generated notes: the pull requests
# merged since the previous full release, with their authors and a
# compare link.
env:
GH_TOKEN: ${{ github.token }}
run: |
{
echo 'FM-Agent maintenance fork of [codegraph](https://github.com/colbymchenry/codegraph).'
echo
echo 'Self-contained per-OS bundles (mac/linux/windows, arm64/x64) carrying the'
echo 'native Rust extraction kernel. FORK.md at this tag records the pinned'
echo 'upstream base and every patch carried on top of it.'
echo
gh api "repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="v${{ needs.preflight.outputs.version }}" \
-f target_commitish="$GITHUB_SHA" --jq .body
} > notes.md
if [ "${{ inputs.prerelease }}" = "true" ]; then
{
Expand All @@ -152,7 +187,7 @@ jobs:
echo 'build on its own. Install it by exact version:'
echo
echo '```'
echo "CODEGRAPH_VERSION=v${{ steps.ver.outputs.version }} bash <(curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/install.sh)"
echo "CODEGRAPH_VERSION=v${{ needs.preflight.outputs.version }} bash <(curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/install.sh)"
echo '```'
echo
echo "Built from \`${{ github.ref_name }}\` at \`${{ github.sha }}\`."
Expand All @@ -164,8 +199,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="v${{ steps.ver.outputs.version }}"
# Idempotent: create once, otherwise (re-run) refresh assets.
TAG="v${{ needs.preflight.outputs.version }}"
# Idempotent: create once, otherwise (re-run) refresh assets. Only a
# re-run of the same commit gets here with the release in place:
# preflight has refused a tag on any other commit.
# --target pins the tag to the commit these artifacts were built from,
# so a main that advanced during the matrix build can't leave the tag
# and the bundles sourced from different commits.
Expand Down
5 changes: 3 additions & 2 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ daemon left over from an *older* install, but `isOlderRelease` (`src/mcp/version
compares plain `X.Y.Z` releases only, so a fork build neither stops an older daemon
nor is stopped by a newer one — it reports a version mismatch instead. Three
end-to-end cases in `__tests__/daemon-older-version.test.ts` fail on a fork build for
that reason (they pass on upstream); CI does not run them. FM-Agent runs `init` and
`index` from the command line and starts no daemon, so it is unaffected.
that reason (they pass on upstream); CI runs the whole suite but skips these three
by name. FM-Agent runs `init` and `index` from the command line and starts no
daemon, so it is unaffected.

**All install/upgrade entry points point at this fork,** so a fork install never
silently escapes back to upstream: `install.sh` / `install.ps1` (`REPO` / `$repo`)
Expand Down
32 changes: 32 additions & 0 deletions scripts/check-release-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Refuse to release a version that is already released from another commit.
#
# usage: scripts/check-release-tag.sh <tag> <commit>
#
# A published version must never change: whoever installs v1.6.2-fmagent.1
# gets the same bundles every time. So a tag that already points at another
# commit means package.json's version was not bumped, and the release stops
# before anything is built. The same tag on the same commit is a re-run (an
# upload that failed part way) and may go ahead.
set -euo pipefail

tag=$1
commit=$2

refs=$(git ls-remote --tags origin "refs/tags/$tag" "refs/tags/$tag^{}")
if [ -z "$refs" ]; then
echo "$tag is not released yet."
exit 0
fi

# An annotated tag also lists "<tag>^{}", the commit it points at.
existing=$(grep -F "refs/tags/$tag^{}" <<<"$refs" | cut -f1 || true)
[ -n "$existing" ] || existing=$(cut -f1 <<<"$refs")

if [ "$existing" = "$commit" ]; then
echo "$tag already points at $commit: re-running the release of the same commit."
exit 0
fi

echo "::error::$tag is already released from $existing, not $commit. Bump the version in package.json, ui/package.json and package-lock.json before releasing."
exit 1