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
152 changes: 152 additions & 0 deletions .automaker/context/agent-push-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Agent Push Protocol — ZERO TOLERANCE

Every agent push MUST follow this exact sequence. No shortcuts. No exceptions.
Pushing code that fails CI is a wasted cycle and an unacceptable failure.

**CODE DOES NOT LEAVE THIS MACHINE UNTIL IT PASSES ALL QUALITY GATES.**

---

## The Push Sequence

### Step 1: Format

```bash
pnpm run format
git add -u
```

### Step 2: Commit

```bash
git commit -m "type(scope): lowercase message"
```

Do NOT use `HUSKY=0`. The pre-commit hook runs gitleaks (secret scanning)
and lint-staged. These are safety checks, not obstacles.

### Step 3: Push

```bash
git push origin <branch>
```

The pre-push hook runs `pnpm run preflight` automatically. This executes:
1. Lint (ESLint)
2. Format check (Prettier)
3. Type check (TypeScript strict)
4. Build (tsc compilation)
5. Test (Vitest)
6. Changeset check (if source changed)
7. Docker CI (act-ci, if Docker is available)
8. Full test suite (all tests, not just changed files)

If ANY gate fails, the push is blocked. Fix the errors and push again.

**Do NOT use `--no-verify` to bypass the pre-push hook.**
**Do NOT use `HUSKY=0` to skip hooks.**

### Step 4: Create PR + Auto-Merge

```bash
PR_URL=$(gh pr create \
--repo bookedsolidtech/helixir \
--base dev \
--title "type(scope): lowercase description" \
--body "Description of changes")

PR_NUMBER=$(echo $PR_URL | grep -oE '[0-9]+$')

gh pr merge $PR_NUMBER --auto --merge --repo bookedsolidtech/helixir
```

---

## Enforcement Layers

| Layer | What | Bypassable? |
|-------|------|-------------|
| pre-commit | gitleaks + lint-staged | Only with `--no-verify` |
| commit-msg | commitlint | Only with `--no-verify` |
| **pre-push** | **`pnpm run preflight` (8 gates including changeset, Docker CI, and full test suite)** | **Only with `--no-verify`** |

The pre-push hook calls `pnpm run preflight` which runs all 8 gates.
Gate 7 (Docker CI) is the act-ci gate — runs if Docker is available, hard fail if it fails.
Gate 8 (Full test suite) runs all tests when source files changed.
Skip Docker only: `SKIP_ACT=1 git push`
Skip full tests only: `SKIP_FULL_TESTS=1 git push`

---

## What Happens If You Skip Steps

- **Skip format** → pre-push format check fails → push blocked
- **Skip lint** → pre-push lint fails → push blocked
- **Type errors** → pre-push type-check fails → push blocked
- **Build broken** → pre-push build fails → push blocked
- **Tests fail** → pre-push test fails → push blocked
- **Use --no-verify** → EMERGENCY ONLY — document why in the commit message

---

## Changeset Requirement

If your changes modify published source code, create a changeset:

```bash
pnpm exec changeset
```

Select the package, bump type, and write a description.
Commit the `.changeset/*.md` file WITH your code changes (same commit).

---

## CI Matrix Parity — Node 20/22/24

helixir must pass tests on Node 20, 22, and 24. This mirrors the CI matrix
defined in `.github/workflows/ci-matrix.yml`.

### When to run `--matrix`

Run `./scripts/act-ci.sh --matrix` when:
- Modifying `package.json` engines or dependencies
- Adding/changing Node.js-version-specific code paths
- Preparing a release to main
- CI matrix failures are reported on a PR

### How it works

The `--matrix` flag sets `ACT_MATRIX_TESTS=true` and `ACT_FULL_TESTS=true`,
which activates the `test-full` job in `act-ci.yml`. That job uses nvm to
install and test against Node 20, 22, and 24 in parallel (fail-fast: false).

```bash
# Run full matrix locally
./scripts/act-ci.sh --matrix

# Matrix on ARM64 (no Rosetta emulation, faster on Apple Silicon)
./scripts/act-ci.sh --native --matrix

# Run full suite on current Node only (no matrix)
./scripts/act-ci.sh --full
```

### Enforcement

Gate 7 of preflight runs `act-ci.sh --native` (standard quality gates).
For matrix parity verification before a release, run manually:

```bash
./scripts/act-ci.sh --native --matrix
```

This is required before merging any PR that touches `src/`, `package.json`,
or Node version configuration.

---

## The One Rule

If `git push` fails due to the pre-push hook, you do NOT bypass it. Period.
Fix the errors first. Then push. This is non-negotiable.
132 changes: 132 additions & 0 deletions .github/workflows/act-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# ============================================================================
# Local CI — Quality gates for nektos/act
# ============================================================================
# Mirrors the core quality gates from ci.yml but avoids GitHub-specific actions
# (pnpm/action-setup, actions/setup-node) that break in act due to PATH issues
# and missing API context.
#
# Single-job design: helixir is a single-package Node project. Running all
# gates in one job avoids bind-mount contention (parallel jobs sharing
# node_modules via --bind corrupt each other's pnpm install).
#
# Usage: ./scripts/act-ci.sh [--job <job-name>] [--list] [--full] [--matrix]
# Jobs: quality-gates (default), test-full (activated by --full or --matrix)
# ============================================================================

name: Local CI

on:
pull_request:

jobs:
quality-gates:
name: Quality Gates
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
run: |
corepack enable
corepack prepare pnpm@9.15.9 --activate
pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Format check
run: pnpm run format:check
- name: Type check
run: pnpm run type-check
- name: Build
run: pnpm run build
- name: Test
run: pnpm run test

# ── Full suite test with Node version matrix (mirrors ci-matrix.yml) ───
test-full:
name: Test Full (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ env.ACT_MATRIX_TESTS == 'true' || env.ACT_FULL_TESTS == 'true' }}
strategy:
fail-fast: false
matrix:
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- name: Setup Node ${{ matrix.node-version }} via nvm
run: |
if [ "${{ matrix.node-version }}" != "" ]; then
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
fi
. "$NVM_DIR/nvm.sh"
nvm install ${{ matrix.node-version }}
nvm use ${{ matrix.node-version }}
echo "Node version: $(node --version)"
fi
- name: Setup pnpm
run: |
corepack enable
corepack prepare pnpm@9.15.9 --activate
pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Run full test suite with hang watchdog
run: |
# Vitest hang watchdog — kills stale processes after 15s of no output
LOGFILE=$(mktemp /tmp/helixir-test-full.XXXXXX)
STALE_TIMEOUT=15
POLL_INTERVAL=3
START_TIME=$(date +%s)

pnpm exec vitest run --reporter=verbose > "$LOGFILE" 2>&1 &
VITEST_PID=$!

echo "[test-full] vitest PID=$VITEST_PID, Node ${{ matrix.node-version }}"

LAST_SIZE=0
STALE_SECONDS=0
FORCE_KILLED=false

while kill -0 "$VITEST_PID" 2>/dev/null; do
sleep "$POLL_INTERVAL"
CURRENT_SIZE=$(stat -c "%s" "$LOGFILE" 2>/dev/null || stat -f "%z" "$LOGFILE" 2>/dev/null || echo 0)
ELAPSED=$(( $(date +%s) - START_TIME ))

if [ "$CURRENT_SIZE" -eq "$LAST_SIZE" ] && [ "$CURRENT_SIZE" -gt 0 ]; then
STALE_SECONDS=$((STALE_SECONDS + POLL_INTERVAL))
if [ "$STALE_SECONDS" -ge "$STALE_TIMEOUT" ] && [ "$ELAPSED" -ge 30 ]; then
echo "[test-full] Output stale for ${STALE_SECONDS}s at ${ELAPSED}s — force killing vitest"
kill "$VITEST_PID" 2>/dev/null || true
sleep 1
kill -9 "$VITEST_PID" 2>/dev/null || true
FORCE_KILLED=true
break
fi
else
STALE_SECONDS=0
fi
LAST_SIZE=$CURRENT_SIZE
done

wait "$VITEST_PID" 2>/dev/null || true

# Determine pass/fail from output
FAILED_TESTS=$(grep -c "^[[:space:]]*[×x]" "$LOGFILE" 2>/dev/null || echo 0)
PASSED_TESTS=$(grep -c "^[[:space:]]*[✓✔]" "$LOGFILE" 2>/dev/null || echo 0)

echo ""
cat "$LOGFILE" | tail -30
echo ""
echo "[test-full] Node ${{ matrix.node-version }}: ${PASSED_TESTS} passed, ${FAILED_TESTS} failed"

if [ "$FORCE_KILLED" = true ]; then
echo "[test-full] vitest was force-killed after teardown hang"
fi

rm -f "$LOGFILE"

if [ "$FAILED_TESTS" -gt 0 ]; then
exit 1
fi
106 changes: 106 additions & 0 deletions .github/workflows/ci-matrix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ============================================================================
# Matrix CI Pipeline — HELiXiR MCP Server
# ============================================================================
# Tests across Node.js 20, 22, and 24 to ensure forward-compatibility
# and catch version-specific ESM / native module regressions.
# ============================================================================

name: CI Matrix

on:
push:
branches: [main, dev, staging, 'changeset-release/**']
paths-ignore:
- '**/*.md'
- '.automaker/**'
- '.claude/**'
- 'LICENSE'
- '.editorconfig'
pull_request:
branches: [main, dev]
paths-ignore:
- '**/*.md'
- '.automaker/**'
- '.claude/**'
- 'LICENSE'
- '.editorconfig'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
matrix-test:
name: Test (Node ${{ matrix.node-version }} on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15

strategy:
fail-fast: false
matrix:
node-version: [20, 22, 24]
os: [ubuntu-latest]

steps:
# ---- Setup ----

- name: Checkout
uses: actions/checkout@v4

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

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

# ---- Quality Checks ----

- name: Type check
run: pnpm run type-check

- name: Lint
run: pnpm run lint

- name: Format check
run: pnpm run format:check

# ---- Build ----

- name: Build
run: pnpm run build

# ---- Tests ----

- name: Test
run: |
# Promotion PRs (staging→main): tests already verified on staging — skip
if [[ "${{ github.head_ref }}" == "staging" && "${{ github.base_ref }}" == "main" ]]; then
echo "Promotion PR (staging → main) — skipping matrix tests"
exit 0
fi
pnpm run test

matrix-summary:
name: Matrix Test Summary
runs-on: ubuntu-latest
needs: matrix-test
if: always()

steps:
- name: Check matrix results
run: |
if [ "${{ needs.matrix-test.result }}" == "failure" ]; then
echo "❌ Matrix tests failed"
exit 1
else
echo "✅ All matrix tests passed"
fi
Loading
Loading