feat: restore CI/release, prompt contracts, faster install (0.9.33)

Bring back Gitea CI + auto-publish; share release helpers; strengthen multi-turn
tool/job prompts with a unit contract; ccache/make help and --release bootstrap.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-29 19:56:42 +08:00
co-authored by Cursor
parent f00aad9e3e
commit 814a31c9c8
14 changed files with 588 additions and 94 deletions
+140 -69
View File
@@ -2,13 +2,20 @@
# Install or upgrade CST-Agent on Linux, WSL or macOS. Safe to rerun: an
# existing checkout is fast-forwarded, rebuilt and reinstalled.
#
# bootstrap_linux.sh [DIR] [--no-pull]
# bootstrap_linux.sh [DIR] [--no-pull] [--release] [--source]
# cst-agent-update (installed by this script; same as rerunning it)
#
# --release downloads the static musl tarball for this host (fast; no compile).
# --source (default) builds from the checkout. CST_INSTALL_MODE=release|source
# overrides the flag.
#
# Env: CST_AGENT_PREFIX (install prefix, default ~/.local), CST_AGENT_DIR
# (checkout, default: current checkout or ~/CST-agent), CST_AGENT_REPO.
# (checkout, default: current checkout or ~/CST-agent), CST_AGENT_REPO,
# CST_AGENT_VERSION (pin a release; default: cst_version.h or latest tag).
set -euo pipefail
ROOT_HINT="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd || true)"
deps_hint() {
echo "==> deps: gcc/clang make pkg-config libcurl ncursesw"
if [[ "$(uname -s)" == Darwin ]]; then
@@ -34,74 +41,15 @@ run_make() {
fi
}
main() {
local repo_url="${CST_AGENT_REPO:-https://git.whcself.xyz/WHC/CST-agent.git}"
local prefix="${CST_AGENT_PREFIX:-$HOME/.local}"
local dir="" pull=1 changed=0 arg
for arg in "$@"; do
case "$arg" in
--no-pull) pull=0 ;;
--rebuilt) changed=1 ;; # internal: re-exec after a pull
-h | --help)
sed -n '2,9p' "$0"
return 0
;;
*) dir="$arg" ;;
esac
done
if [[ -z "$dir" ]]; then
if [[ -n "${CST_AGENT_DIR:-}" ]]; then
dir="$CST_AGENT_DIR"
elif [[ -f Makefile && -f src/main.c && -d .git ]]; then
dir="$(pwd)"
else
dir="$HOME/CST-agent"
fi
fi
local old="" new=""
if [[ ! -f "$dir/Makefile" ]]; then
echo "==> git clone $repo_url -> $dir"
git clone "$repo_url" "$dir"
changed=1
elif ((pull)); then
old="$(git -C "$dir" rev-parse HEAD)"
if git -C "$dir" diff --quiet && git -C "$dir" diff --cached --quiet; then
echo "==> git pull ($dir)"
git -C "$dir" pull --ff-only
else
echo "warning: $dir has local changes; skipping git pull" >&2
fi
new="$(git -C "$dir" rev-parse HEAD)"
if [[ "$old" != "$new" ]]; then
echo " $(git -C "$dir" log --oneline "$old..$new" | wc -l) new commit(s)"
# The script itself may have changed: continue with the new version.
exec bash "$dir/scripts/bootstrap_linux.sh" "$dir" --no-pull --rebuilt
fi
fi
cd "$dir"
dir="$(pwd)"
local jobs
jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
echo "==> build (incremental, -j$jobs)"
# Prefer incremental rebuilds. Set CST_AGENT_CLEAN=1 to force a clean tree.
if [[ "${CST_AGENT_CLEAN:-0}" == "1" ]]; then
run_make clean >/dev/null 2>&1 || true
fi
# `changed` is only used so re-exec after pull still builds; keep for clarity.
: "${changed:=0}"
if ! run_make -j"$jobs"; then
deps_hint
return 1
fi
if [[ "${CST_AGENT_SKIP_CHECK:-0}" != "1" ]]; then
run_make -j"$jobs" check >/dev/null 2>&1 || echo "warning: some tests failed (run 'make check')"
fi
echo "==> install to $prefix/bin"
install_bin() {
local prefix="$1" bin="$2"
mkdir -p "$prefix/bin"
install -m 0755 "$bin" "$prefix/bin/cst-agent"
}
install_helpers() {
local prefix="$1" dir="$2"
mkdir -p "$prefix/bin"
run_make install PREFIX="$prefix" >/dev/null
cat >"$prefix/bin/cst-agent-update" <<EOF
#!$(command -v bash)
export CST_AGENT_PREFIX="\${CST_AGENT_PREFIX:-$prefix}"
@@ -137,6 +85,128 @@ restart) systemctl --user restart cst-agent-parliament.service ;;
esac
EOF
chmod 0755 "$prefix/bin/cst-agent-service"
}
# Fast path: fetch static release tarball for this machine.
install_from_release() {
local prefix="$1" dir="$2"
# shellcheck source=lib_release.sh
. "$dir/scripts/lib_release.sh"
ROOT="$dir"
local arch
arch="$(cst_host_release_arch)" || {
echo "no prebuilt release for $(uname -m); use --source" >&2
return 1
}
local ver="${CST_AGENT_VERSION:-}"
if [[ -z "$ver" ]]; then
ver="$(cst_version)"
fi
local name="cst-agent-$ver-linux-$arch.tar.gz"
local tmp
tmp="$(mktemp -d /tmp/cst-rel.XXXXXX)"
# shellcheck disable=SC2046
if ! cst_fetch "$tmp/$name" $(cst_release_asset_urls "$ver" "$name"); then
echo "release download failed for $name; falling back to source build" >&2
rm -rf "$tmp"
return 1
fi
tar -C "$tmp" -xzf "$tmp/$name"
local bin
bin="$(find "$tmp" -type f -name cst-agent | head -1)"
[[ -x "$bin" ]] || { echo "tarball missing cst-agent" >&2; rm -rf "$tmp"; return 1; }
install_bin "$prefix" "$bin"
rm -rf "$tmp"
echo "==> installed release $ver ($arch) -> $prefix/bin/cst-agent"
}
main() {
local repo_url="${CST_AGENT_REPO:-https://git.whcself.xyz/WHC/CST-agent.git}"
local prefix="${CST_AGENT_PREFIX:-$HOME/.local}"
local dir="" pull=1 changed=0 mode="${CST_INSTALL_MODE:-source}" arg
for arg in "$@"; do
case "$arg" in
--no-pull) pull=0 ;;
--release) mode=release ;;
--source) mode=source ;;
--rebuilt) changed=1 ;; # internal: re-exec after a pull
-h | --help)
sed -n '2,16p' "$0"
return 0
;;
*) dir="$arg" ;;
esac
done
if [[ -z "$dir" ]]; then
if [[ -n "${CST_AGENT_DIR:-}" ]]; then
dir="$CST_AGENT_DIR"
elif [[ -f Makefile && -f src/main.c && -d .git ]]; then
dir="$(pwd)"
elif [[ -n "$ROOT_HINT" && -f "$ROOT_HINT/Makefile" ]]; then
dir="$ROOT_HINT"
else
dir="$HOME/CST-agent"
fi
fi
local old="" new=""
if [[ ! -f "$dir/Makefile" ]]; then
echo "==> git clone $repo_url -> $dir"
git clone "$repo_url" "$dir"
changed=1
elif ((pull)); then
old="$(git -C "$dir" rev-parse HEAD)"
if git -C "$dir" diff --quiet && git -C "$dir" diff --cached --quiet; then
echo "==> git pull ($dir)"
git -C "$dir" pull --ff-only
else
echo "warning: $dir has local changes; skipping git pull" >&2
fi
new="$(git -C "$dir" rev-parse HEAD)"
if [[ "$old" != "$new" ]]; then
echo " $(git -C "$dir" log --oneline "$old..$new" | wc -l) new commit(s)"
# The script itself may have changed: continue with the new version.
exec bash "$dir/scripts/bootstrap_linux.sh" "$dir" --no-pull --rebuilt \
${mode:+--$mode}
fi
fi
cd "$dir"
dir="$(pwd)"
if [[ "$mode" == release ]]; then
if install_from_release "$prefix" "$dir"; then
install_helpers "$prefix" "$dir"
echo
echo "OK: $("$prefix/bin/cst-agent" --version) (release install)"
case ":$PATH:" in
*":$prefix/bin:"*) ;;
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
esac
echo "Upgrade: cst-agent-update --release Rebuild from source: --source"
return 0
fi
echo "==> continuing with source build"
fi
local jobs
jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
echo "==> build (incremental, -j$jobs)"
# Prefer incremental rebuilds. Set CST_AGENT_CLEAN=1 to force a clean tree.
if [[ "${CST_AGENT_CLEAN:-0}" == "1" ]]; then
run_make clean >/dev/null 2>&1 || true
fi
: "${changed:=0}"
if ! run_make -j"$jobs"; then
deps_hint
return 1
fi
if [[ "${CST_AGENT_SKIP_CHECK:-0}" != "1" ]]; then
run_make -j"$jobs" check >/dev/null 2>&1 || echo "warning: some tests failed (run 'make check')"
fi
echo "==> install to $prefix/bin"
install_bin "$prefix" "$dir/cst-agent"
install_helpers "$prefix" "$dir"
echo
echo "OK: $("$prefix/bin/cst-agent" --version)"
@@ -145,6 +215,7 @@ EOF
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
esac
echo "Run 'cst-agent' inside a project directory; upgrade with 'cst-agent-update'."
echo "Fast upgrade from Release: 'cst-agent-update --release'."
echo "Parliament seat service helper: 'cst-agent-service status|install-unit'."
echo "The first start asks for the provider and its API key and saves them."
}
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# CI helper: publish release assets with RELEASE_TOKEN falling back to
# GITHUB_TOKEN. Skips cleanly when neither is set.
# scripts/ci_publish.sh FILE...
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export GITEA_TOKEN="${RELEASE_TOKEN:-${GITEA_TOKEN:-${GITHUB_TOKEN:-}}}"
if [[ -z "${GITEA_TOKEN}" ]]; then
echo "::warning::no RELEASE_TOKEN / GITHUB_TOKEN; skip auto-release"
exit 0
fi
[[ $# -gt 0 ]] || { echo "usage: $0 FILE..." >&2; exit 1; }
export CI="${CI:-true}"
exec bash "$ROOT/scripts/release.sh" "$@"
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Shared helpers for packaging and publishing. Source from other scripts:
# ROOT=...; . "$ROOT/scripts/lib_release.sh"
# Safe to source multiple times.
[[ -n "${_CST_LIB_RELEASE:-}" ]] && return 0
_CST_LIB_RELEASE=1
: "${ROOT:=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
# Print MAJOR.MINOR.PATCH from include/cst_version.h
cst_version() {
sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' \
"$ROOT/include/cst_version.h" | paste -sd.
}
# Write FILE.sha256 next to FILE (relative to cwd of FILE's directory).
cst_sha256_write() {
local f="$1" dir base
dir="$(cd "$(dirname "$f")" && pwd)"
base="$(basename "$f")"
(cd "$dir" && sha256sum "$base" >"$base.sha256")
}
# Pack DIR (relative to PARENT) as PARENT/NAME.tar.gz + .sha256
# cst_pack_tgz dist cst-agent-0.9.33-linux-x86_64
cst_pack_tgz() {
local parent="$1" name="$2"
mkdir -p "$parent"
tar -C "$parent" -czf "$parent/$name.tar.gz" "$name"
cst_sha256_write "$parent/$name.tar.gz"
echo "$parent/$name.tar.gz"
}
# Host arch for release asset names (x86_64|x86|aarch64) or empty.
cst_host_release_arch() {
case "$(uname -m)" in
x86_64 | amd64) echo x86_64 ;;
aarch64 | arm64) echo aarch64 ;;
i686 | i386 | x86) echo x86 ;;
*) return 1 ;;
esac
}
# Download URL candidates for a Gitea/GitHub release asset.
# cst_release_asset_urls VER NAME -> prints urls
cst_release_asset_urls() {
local ver="$1" name="$2"
local server="${CST_GITEA:-${GITHUB_SERVER_URL:-https://git.whcself.xyz}}"
local repo="${CST_REPO:-${GITHUB_REPOSITORY:-WHC/CST-agent}}"
printf '%s\n' \
"$server/$repo/releases/download/v$ver/$name" \
"https://ghfast.top/$server/$repo/releases/download/v$ver/$name"
}
# Fetch URL list to OUT; first success wins. Returns 0 on success.
cst_fetch() {
local out="$1"
shift
local u
for u in "$@"; do
echo " try $u" >&2
if curl -fsSL --retry 2 --connect-timeout 15 --max-time 600 -o "$out.part" "$u"; then
mv "$out.part" "$out"
return 0
fi
done
rm -f "$out.part"
return 1
}
# Fingerprint for incremental release rebuilds (compiler + version).
cst_build_stamp() {
local cc="${1:?}"
local machine
machine="$("$cc" -dumpmachine 2>/dev/null || echo unknown)"
printf '%s %s\n' "$machine" "$(cst_version)"
}
+19 -7
View File
@@ -3,9 +3,12 @@
# dist/cst-agent-VERSION-linux-ARCH.tar.gz (+ .sha256).
# scripts/package_linux.sh x86_64|x86|aarch64
# CST_LINUX_CHECK=1 also runs `make check` (only where the binaries can run).
# Builds in the source tree, so the tree is cleaned first.
# CST_FORCE_REBUILD=1 forces a clean tree; otherwise same compiler+version
# reuses object files (much faster CI/local repacks).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck source=lib_release.sh
. "$ROOT/scripts/lib_release.sh"
ARCH="${1:?usage: $0 x86_64|x86|aarch64}"
PREFIX="$ROOT/third_party/prefix-linux-$ARCH"
cd "$ROOT"
@@ -24,9 +27,20 @@ unset PKG_CONFIG_PATH CC CFLAGS LDFLAGS
mk=(make CC="${T}gcc" AR="${T}ar" STATIC=1
CFLAGS="-std=c11 -Wall -Wextra -O2 -g -Werror")
make clean >/dev/null
make -C vendor/sashidown clean >/dev/null 2>&1 || true
stamp=".gen/release-linux-$ARCH.stamp"
want="$(cst_build_stamp "${T}gcc")"
if [[ "${CST_FORCE_REBUILD:-0}" == 1 || ! -f cst-agent || ! -f "$stamp" ||
"$(cat "$stamp" 2>/dev/null || true)" != "$want" ]]; then
echo "==> clean rebuild ($ARCH)"
make clean >/dev/null
make -C vendor/sashidown clean >/dev/null 2>&1 || true
else
echo "==> incremental rebuild ($ARCH, stamp match)"
fi
"${mk[@]}" -j"$(nproc)" cst-agent
mkdir -p .gen
printf '%s\n' "$want" >"$stamp"
if "${T}readelf" -l cst-agent | grep -q 'Requesting program interpreter'; then
echo "cst-agent is dynamically linked" >&2
exit 1
@@ -35,7 +49,7 @@ if [[ "${CST_LINUX_CHECK:-0}" == 1 ]]; then
"${mk[@]}" check
fi
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' include/cst_version.h | paste -sd.)"
ver="$(cst_version)"
name="cst-agent-$ver-linux-$ARCH"
rel="dist/$name"
rm -rf "$rel"
@@ -44,8 +58,6 @@ cp -f cst-agent "$rel/cst-agent"
"${T}strip" "$rel/cst-agent"
cp -f README.md "$rel/README.md"
echo "$ver" >"$rel/VERSION"
tgz="dist/$name.tar.gz"
tar -C dist -czf "$tgz" "$name"
(cd dist && sha256sum "$name.tar.gz" >"$name.tar.gz.sha256")
tgz="$(cst_pack_tgz dist "$name")"
ls -la "$rel"
echo "$ROOT/$tgz"
+5 -3
View File
@@ -4,6 +4,8 @@
# picks another, e.g. build-msys2) and busybox-w32.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck source=lib_release.sh
. "$ROOT/scripts/lib_release.sh"
REL="$ROOT/dist/windows-x86_64"
if [[ -n "${CST_WIN_BUILD:-}" ]]; then
EXE="$ROOT/$CST_WIN_BUILD/cst-agent.exe"
@@ -15,7 +17,7 @@ BB="$ROOT/third_party/busybox/busybox.exe"
[[ -f "$EXE" ]] || { echo "no cst-agent.exe; build first" >&2; exit 1; }
[[ -f "$BB" ]] || { echo "no busybox.exe; scripts/build_third_party_mingw.sh busybox-w32" >&2; exit 1; }
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' "$ROOT/include/cst_version.h" | paste -sd.)"
ver="$(cst_version)"
strip="${MINGW_HOST-x86_64-w64-mingw32}"
strip="${strip:+$strip-}strip"
command -v "$strip" >/dev/null || strip=strip # MSYS2: unprefixed
@@ -33,7 +35,7 @@ echo "$ver" >"$REL/VERSION"
zip="$ROOT/dist/cst-agent-$ver-windows-x86_64.zip"
rm -f "$zip"
(cd "$ROOT/dist" && zip -qr "$zip" windows-x86_64)
(cd "$ROOT/dist" && sha256sum "$(basename "$zip")" >"$(basename "$zip").sha256")
cst_sha256_write "$zip"
ls -la "$REL"
echo "$zip"
@@ -43,7 +45,7 @@ if command -v makensis >/dev/null; then
rm -f "$setup"
makensis -V2 -WX -INPUTCHARSET UTF8 -DVERSION="$ver" -DSRCDIR="$REL" \
-DOUTFILE="$setup" "$ROOT/scripts/windows/cst-agent.nsi"
(cd "$ROOT/dist" && sha256sum "$(basename "$setup")" >"$(basename "$setup").sha256")
cst_sha256_write "$setup"
echo "$setup"
elif [[ "${CST_REQUIRE_NSIS:-0}" == 1 ]]; then
echo "makensis not found" >&2
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Offline multi-round prompt smoke: builds the agent and runs the prompt
# contract unit test (no API key). Used by local loops and CI-adjacent checks.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
jobs="$(nproc 2>/dev/null || echo 4)"
make -j"$jobs" tests/test_prompt_contract cst-agent
./tests/test_prompt_contract
echo "prompt_smoke: OK"
+3 -1
View File
@@ -12,6 +12,8 @@
# check is skipped under GITEA_ACTIONS / GITHUB_ACTIONS / CI.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck source=lib_release.sh
. "$ROOT/scripts/lib_release.sh"
SERVER="${CST_GITEA:-${GITHUB_SERVER_URL:-https://git.whcself.xyz}}"
REPO="${CST_REPO:-${GITHUB_REPOSITORY:-WHC/CST-agent}}"
API="$SERVER/api/v1/repos/$REPO"
@@ -33,7 +35,7 @@ for f in "$@"; do
done
cd "$ROOT"
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' include/cst_version.h | paste -sd.)"
ver="$(cst_version)"
tag="v$ver"
# If this job was triggered by a version tag, it must match cst_version.h.