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
128 changes: 128 additions & 0 deletions .github/workflows/kc-check-imports.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: kc-check-imports

on:
pull_request:
branches:
- "ga/**"
- "lts/**"
workflow_dispatch:
inputs:
baseline:
description: "Baseline ref (tag or SHA) to validate against"
required: false
default: "v2026.4.20"
type: string
range:
description: "Range or candidates (e.g. origin/ga/1.0..HEAD)"
required: false
default: ""
type: string

permissions:
contents: read
pull-requests: write

jobs:
check-imports:
runs-on: ubuntu-latest
steps:
- name: Checkout repo (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Fetch baseline tag
run: |
set -euo pipefail
baseline="${{ inputs.baseline || 'v2026.4.20' }}"
if ! git rev-parse "$baseline" >/dev/null 2>&1; then
echo "Baseline ref '$baseline' not found locally; fetching tags from origin."
git fetch origin --tags --force
fi
if ! git rev-parse "$baseline" >/dev/null 2>&1; then
echo "ERROR: baseline ref '$baseline' not found after fetch. Push the tag to origin or pass --baseline=<full-sha>."
exit 1
fi

- name: Checkout kissclaw-tools
uses: actions/checkout@v4
with:
repository: MachineWisdomAI/kissclaw-tools
path: .kissclaw-tools
token: ${{ secrets.KISSCLAW_TOOLS_TOKEN || secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Install kissclaw-tools dependencies
working-directory: .kissclaw-tools
run: |
if [ -f pnpm-lock.yaml ]; then
pnpm install --frozen-lockfile
elif [ -f package.json ]; then
pnpm install
else
echo "kissclaw-tools has no package.json yet; installing typescript locally for module resolution."
npm install --no-save typescript
fi

- name: Install repo dependencies (for TS module resolution)
run: pnpm install --frozen-lockfile

- name: Resolve range
id: range
run: |
set -euo pipefail
if [ -n "${{ inputs.range }}" ]; then
range="${{ inputs.range }}"
elif [ "${{ github.event_name }}" = "pull_request" ]; then
base_ref="origin/${{ github.base_ref }}"
git fetch origin "${{ github.base_ref }}" --depth=200
range="${base_ref}..HEAD"
else
range="origin/ga/1.0..HEAD"
fi
echo "range=$range" >> "$GITHUB_OUTPUT"

- name: kc-check-imports — per-candidate
id: candidates
run: |
set -euo pipefail
baseline="${{ inputs.baseline || 'v2026.4.20' }}"
range="${{ steps.range.outputs.range }}"
echo "Running kc-check-imports --candidates $range against baseline $baseline"
node .kissclaw-tools/kc-check-imports.mjs \
--baseline "$baseline" \
--candidates "$range" \
--repo "$PWD" \
> kc-check-imports-candidates.json
cat kc-check-imports-candidates.json
continue-on-error: false

- name: kc-check-imports — final-tree
if: always()
run: |
set -euo pipefail
baseline="${{ inputs.baseline || 'v2026.4.20' }}"
range="${{ steps.range.outputs.range }}"
echo "Running kc-check-imports --final-tree $range against baseline $baseline"
node .kissclaw-tools/kc-check-imports.mjs \
--baseline "$baseline" \
--final-tree "$range" \
--repo "$PWD" \
> kc-check-imports-final-tree.json
cat kc-check-imports-final-tree.json

- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: kc-check-imports-reports
path: |
kc-check-imports-candidates.json
kc-check-imports-final-tree.json
77 changes: 61 additions & 16 deletions .github/workflows/kissclaw-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,50 @@ on:
required: true
type: string

# `contents: write` is required for `gh release upload`. The default-branch
# defense-in-depth check below prevents this write capability from being abused
# to publish builds from `main` (the upstream mirror).
permissions:
contents: read
contents: write

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Resolve build ref
id: ref
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "release" ]; then
ref="${{ github.event.release.tag_name }}"
elif [ -n "${{ inputs.ref }}" ]; then
ref="${{ inputs.ref }}"
else
ref="${{ github.ref }}"
fi
echo "ref=$ref" >> "$GITHUB_OUTPUT"

- name: Checkout release ref
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0

- name: Verify checkout integrity
run: |
set -euo pipefail
intended_ref="${{ inputs.ref || github.ref }}"
# Annotated tags need dereferencing to get the commit SHA.
intended_ref="${{ steps.ref.outputs.ref }}"
# Annotated tags need ^{commit} dereferencing; rev-list -n 1 handles both
# annotated tags and branches consistently.
intended_sha=$(git rev-list -n 1 "$intended_ref")
actual_sha=$(git rev-parse HEAD)
if [ "$intended_sha" != "$actual_sha" ]; then
echo "ERROR: actual HEAD ($actual_sha) does not match intended ref ($intended_ref -> $intended_sha)"
exit 1
fi
default_branch=$(gh api "repos/${{ github.repository }}" --jq .default_branch)
# actions/checkout@v4 only fetches the requested ref by default, so origin/$default_branch
# may not exist locally. Ask GitHub for the SHA directly.
default_sha=$(gh api "repos/${{ github.repository }}/git/refs/heads/$default_branch" --jq .object.sha)
if [ "$actual_sha" = "$default_sha" ]; then
echo "ERROR: HEAD equals default-branch HEAD ($default_sha). Release builds must be from a tag or release branch, not the upstream mirror."
Expand All @@ -58,38 +78,63 @@ jobs:
run: pnpm build

- name: Package tarball
id: pack
run: |
set -euo pipefail
version=$(node -p "require('./package.json').version")
name=$(node -p "require('./package.json').name")
# pnpm pack writes <name>-<version>.tgz. We renormalize defensively to
# "kissclaw-<version>.tgz" so the release asset name is deterministic
# regardless of the package.json name field. Guarded so we don't mv onto
# ourselves when the names already agree.
pnpm pack
tarball="${name}-${version}.tgz"
mv *.tgz "kissclaw-${version}.tgz"
tarball="kissclaw-${version}.tgz"
sha256sum "$tarball" > "${tarball}.sha256"
echo "TARBALL=$tarball" >> "$GITHUB_ENV"
echo "VERSION=$version" >> "$GITHUB_ENV"
src="${name}-${version}.tgz"
dst="kissclaw-${version}.tgz"
if [ ! -f "$src" ]; then
src=$(ls *.tgz | head -n 1)
fi
if [ "$src" != "$dst" ]; then
mv "$src" "$dst"
fi
sha256sum "$dst" > "${dst}.sha256"
{
echo "tarball=$dst"
echo "version=$version"
} >> "$GITHUB_OUTPUT"

- name: Generate release metadata
run: |
set -euo pipefail
built_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
sha=$(git rev-parse HEAD)
cat > "kissclaw-release-metadata.json" <<METADATA
{
"name": "kissclaw",
"version": "$VERSION",
"ref": "${{ inputs.ref || github.ref }}",
"sha": "$(git rev-parse HEAD)",
"built_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
"version": "${{ steps.pack.outputs.version }}",
"ref": "${{ steps.ref.outputs.ref }}",
"sha": "$sha",
"built_at": "$built_at"
}
METADATA

- name: Upload release assets
if: github.event_name == 'release'
run: |
set -euo pipefail
gh release upload "${{ github.event.release.tag_name }}" \
"$TARBALL" \
"${TARBALL}.sha256" \
"${{ steps.pack.outputs.tarball }}" \
"${{ steps.pack.outputs.tarball }}.sha256" \
"kissclaw-release-metadata.json" \
--clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload workflow artifacts (manual dispatch)
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: kissclaw-release-${{ steps.pack.outputs.version }}
path: |
${{ steps.pack.outputs.tarball }}
${{ steps.pack.outputs.tarball }}.sha256
kissclaw-release-metadata.json
Loading