ci(actions): full pipeline — build / test / scan / publish #154
No files matched your search
@@ -0,0 +1,3 @@
|
||||
# CODEOWNERS — review hints for the sole maintainer.
|
||||
# Wildcard catches everything; expand later as the team grows.
|
||||
* @KofTwentyTwo
|
||||
@@ -0,0 +1,291 @@
|
||||
# Main CI: build + test + boundary gates for every PR and push to long-lived branches.
|
||||
#
|
||||
# Branch strategy (defaults — override by editing the branches: lists):
|
||||
# - feature/*, fix/* PR-only checks, no tagging
|
||||
# - develop full CI + pre-release tag v0.X.Y-dev.<run_number>
|
||||
# - rc/*, release/* full CI + tag v0.X.Y-rc.N (N auto-increments)
|
||||
# - main full CI + tag v0.X.Y + GitHub Release (release.yml)
|
||||
#
|
||||
# Versioning: read commits since previous tag, bump per Conventional Commits
|
||||
# (feat -> minor, fix -> patch, feat! / BREAKING CHANGE -> major). Defaults to
|
||||
# v0.1.0 when no tags exist.
|
||||
#
|
||||
# Untrusted input handling: this workflow does NOT consume PR titles, bodies,
|
||||
# issue text, or commit-author fields directly in run: blocks. The only
|
||||
# external inputs are github.ref (controlled) and the constrained
|
||||
# workflow_dispatch choice. github.run_number is GitHub-provided.
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop, main, "rc/*", "release/*"]
|
||||
push:
|
||||
branches: [develop, main, "rc/*", "release/*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_real_hardware:
|
||||
description: "Run JARVIS_REAL_MODELS / JARVIS_REAL_CAMERA suites (self-hosted only)"
|
||||
required: false
|
||||
default: "false"
|
||||
type: choice
|
||||
options:
|
||||
- "false"
|
||||
- "true"
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
boundary-gates:
|
||||
name: Boundary gates
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run all scripts/check-*.sh (excluding app-builds)
|
||||
run: |
|
||||
fail=0
|
||||
for s in scripts/check-*.sh; do
|
||||
name="$(basename "$s")"
|
||||
if [ "$name" = "check-app-builds.sh" ]; then
|
||||
echo "::group::SKIP $name (needs xcodebuild)"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
echo "::group::$name"
|
||||
if bash "$s"; then
|
||||
echo "PASS: $name"
|
||||
else
|
||||
echo "::error::FAIL: $name"
|
||||
fail=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
exit "$fail"
|
||||
|
||||
webview:
|
||||
name: Webview (pnpm test + build)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.30.2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: webview/pnpm-lock.yaml
|
||||
- name: pnpm install
|
||||
working-directory: webview
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Typecheck
|
||||
working-directory: webview
|
||||
run: pnpm -r typecheck
|
||||
- name: Test (vitest)
|
||||
working-directory: webview
|
||||
run: pnpm -r test
|
||||
- name: Build HUD bundle
|
||||
working-directory: webview
|
||||
run: pnpm -r build
|
||||
- name: Upload HUD dist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: webview-dist
|
||||
path: webview/packages/hud/dist/
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
swift-packages:
|
||||
name: SPM ${{ matrix.package }}
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- AgentCore
|
||||
- Bus
|
||||
- Config
|
||||
- DevOverlay
|
||||
- Harness
|
||||
- Keychain
|
||||
- Logging
|
||||
- MCP
|
||||
- Memory
|
||||
- Replay
|
||||
- Shell
|
||||
- Vision
|
||||
- Voice
|
||||
- VoiceLog
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
- name: Show Swift toolchain
|
||||
run: |
|
||||
swift --version
|
||||
xcodebuild -version
|
||||
- name: Cache SPM build artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
packages/${{ matrix.package }}/.build
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: spm-${{ matrix.package }}-${{ runner.os }}-${{ hashFiles(format('packages/{0}/Package.resolved', matrix.package), format('packages/{0}/Package.swift', matrix.package)) }}
|
||||
restore-keys: |
|
||||
spm-${{ matrix.package }}-${{ runner.os }}-
|
||||
- name: swift test
|
||||
run: swift test --package-path packages/${{ matrix.package }}
|
||||
|
||||
app-build:
|
||||
name: App target (xcodebuild)
|
||||
runs-on: macos-15
|
||||
needs: [boundary-gates, webview]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.30.2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: webview/pnpm-lock.yaml
|
||||
- name: Install xcodegen
|
||||
run: brew install xcodegen
|
||||
- name: Build webview (produces App/Resources/webview/)
|
||||
run: bash scripts/build-webview.sh
|
||||
- name: check-app-builds.sh
|
||||
run: bash scripts/check-app-builds.sh
|
||||
- name: verify-codesign-settings.sh
|
||||
run: bash scripts/verify-codesign-settings.sh
|
||||
- name: Upload Jarvis.app artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Jarvis-app-debug
|
||||
path: build/Build/Products/Debug/Jarvis.app
|
||||
retention-days: 7
|
||||
if-no-files-found: warn
|
||||
|
||||
real-hardware:
|
||||
name: Real-hardware tests (opt-in)
|
||||
runs-on: [self-hosted, macOS]
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.run_real_hardware == 'true'
|
||||
needs: [swift-packages]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: JARVIS_REAL_MODELS Memory regression corpus
|
||||
env:
|
||||
JARVIS_REAL_MODELS: "1"
|
||||
run: swift test --package-path packages/Memory --filter MemoryRegressionCorpusTests
|
||||
- name: JARVIS_REAL_CAMERA capture suite
|
||||
env:
|
||||
JARVIS_REAL_CAMERA: "1"
|
||||
run: swift test --package-path packages/Vision --filter CameraCaptureRealHardwareTests
|
||||
- name: JARVIS_REAL_MODELS Orpheus TTFA
|
||||
env:
|
||||
JARVIS_REAL_MODELS: "1"
|
||||
run: swift test --package-path packages/Voice --filter OrpheusTTFATests
|
||||
|
||||
publish-tag:
|
||||
name: Publish tag
|
||||
runs-on: ubuntu-latest
|
||||
needs: [boundary-gates, webview, swift-packages, app-build]
|
||||
if: |
|
||||
github.event_name == 'push' && (
|
||||
github.ref == 'refs/heads/develop' ||
|
||||
github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/heads/rc/') ||
|
||||
startsWith(github.ref, 'refs/heads/release/')
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Compute next version
|
||||
id: version
|
||||
env:
|
||||
GH_REF: ${{ github.ref }}
|
||||
RUN_NUMBER: ${{ github.run_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --tags --force --quiet
|
||||
last_tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' \
|
||||
| grep -Ev -- '-(dev|rc|alpha|beta)' \
|
||||
| sort -V | tail -n 1 || true)
|
||||
if [ -z "$last_tag" ]; then
|
||||
base="v0.1.0"
|
||||
from="$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
echo "No prior tag; defaulting base=$base, scanning all history."
|
||||
else
|
||||
base="$last_tag"
|
||||
from="$last_tag"
|
||||
echo "Last final tag: $last_tag"
|
||||
fi
|
||||
version="${base#v}"
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
bump="patch"
|
||||
if git log "$from"..HEAD --pretty=%B 2>/dev/null | grep -qE '(^|\n)BREAKING CHANGE:|^[a-zA-Z]+(\([^)]+\))?!:'; then
|
||||
bump="major"
|
||||
elif git log "$from"..HEAD --pretty=%B 2>/dev/null | grep -qE '^feat(\([^)]+\))?:'; then
|
||||
bump="minor"
|
||||
elif git log "$from"..HEAD --pretty=%B 2>/dev/null | grep -qE '^fix(\([^)]+\))?:'; then
|
||||
bump="patch"
|
||||
fi
|
||||
if [ -n "$last_tag" ]; then
|
||||
case "$bump" in
|
||||
major) major=$((major+1)); minor=0; patch=0 ;;
|
||||
minor) minor=$((minor+1)); patch=0 ;;
|
||||
patch) patch=$((patch+1)) ;;
|
||||
esac
|
||||
fi
|
||||
next="${major}.${minor}.${patch}"
|
||||
case "$GH_REF" in
|
||||
refs/heads/develop)
|
||||
tag="v${next}-dev.${RUN_NUMBER}"
|
||||
prerelease=true
|
||||
;;
|
||||
refs/heads/rc/*|refs/heads/release/*)
|
||||
existing_rc=$(git tag --list "v${next}-rc.*" | sed -E "s/.*-rc\.([0-9]+)/\1/" | sort -n | tail -n 1)
|
||||
n=$(( ${existing_rc:-0} + 1 ))
|
||||
tag="v${next}-rc.${n}"
|
||||
prerelease=true
|
||||
;;
|
||||
refs/heads/main)
|
||||
tag="v${next}"
|
||||
prerelease=false
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected ref: $GH_REF" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
|
||||
echo "Next tag: $tag (prerelease=$prerelease)"
|
||||
- name: Create + push tag
|
||||
env:
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
GH_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then
|
||||
echo "Tag $TAG already exists; skipping."
|
||||
exit 0
|
||||
fi
|
||||
git tag -a "$TAG" -m "Automated tag from CI for $GH_REF"
|
||||
git push origin "$TAG"
|
||||
@@ -0,0 +1,61 @@
|
||||
# CodeQL static analysis for Swift + JavaScript/TypeScript.
|
||||
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop, main, "rc/*", "release/*"]
|
||||
pull_request:
|
||||
branches: [develop, main]
|
||||
schedule:
|
||||
- cron: "0 9 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze ${{ matrix.language }}
|
||||
runs-on: ${{ matrix.language == 'swift' && 'macos-15' || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: swift
|
||||
build-mode: manual
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Select Xcode (swift)
|
||||
if: matrix.language == 'swift'
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer || sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
queries: security-extended
|
||||
|
||||
# CodeQL needs a manual Swift build. We build a representative subset
|
||||
# of packages (Bus, AgentCore, MCP) so the security lane stays cheap;
|
||||
# extending to all 14 packages would dominate the CI budget.
|
||||
- name: Build Swift (CodeQL extraction)
|
||||
if: matrix.language == 'swift'
|
||||
run: |
|
||||
swift build --package-path packages/Bus
|
||||
swift build --package-path packages/AgentCore
|
||||
swift build --package-path packages/MCP
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
@@ -0,0 +1,35 @@
|
||||
# Dependency review on PRs that touch manifest/lockfiles for npm or SPM.
|
||||
# Fails the PR if a new dependency introduces a known high-severity vuln
|
||||
# or a forbidden license.
|
||||
|
||||
name: Dependency Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop, main]
|
||||
paths:
|
||||
- "webview/package.json"
|
||||
- "webview/pnpm-lock.yaml"
|
||||
- "webview/packages/**/package.json"
|
||||
- "**/Package.swift"
|
||||
- "**/Package.resolved"
|
||||
- ".github/workflows/dependency-review.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: high
|
||||
# Default license policy: warn on copyleft, fail on nothing automatically.
|
||||
# Tighten via `deny-licenses: GPL-2.0, GPL-3.0, AGPL-3.0` after the
|
||||
# user reviews the existing SPM + npm graph.
|
||||
comment-summary-in-pr: on-failure
|
||||
@@ -0,0 +1,79 @@
|
||||
# Release workflow: creates a GitHub Release whenever a v* tag is pushed.
|
||||
# Pre-release tags (vX.Y.Z-dev.*, vX.Y.Z-rc.*) are marked as pre-releases.
|
||||
# Final tags (vX.Y.Z) become production releases.
|
||||
#
|
||||
# The release body is auto-generated from commits since the previous tag.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+-*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Publish GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Classify tag
|
||||
id: classify
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Anything containing a dash after vX.Y.Z is a pre-release.
|
||||
if [[ "$TAG" == *-* ]]; then
|
||||
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
# Find the previous tag (any kind) for the changelog scope.
|
||||
prev=$(git tag --list 'v*' --sort=-v:refname | grep -v "^${TAG}$" | head -n 1 || true)
|
||||
echo "previous_tag=${prev}" >> "$GITHUB_OUTPUT"
|
||||
echo "Classified $TAG: prerelease=$(grep prerelease "$GITHUB_OUTPUT" | tail -n 1), previous=$prev"
|
||||
|
||||
- name: Generate release notes
|
||||
id: notes
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
PREV: ${{ steps.classify.outputs.previous_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
{
|
||||
echo "## Changes"
|
||||
echo
|
||||
if [ -n "$PREV" ]; then
|
||||
git log --pretty='format:- %s (%h)' "$PREV..$TAG"
|
||||
else
|
||||
git log --pretty='format:- %s (%h)'
|
||||
fi
|
||||
echo
|
||||
echo
|
||||
echo "## Verification"
|
||||
echo
|
||||
echo "- CI build: see https://github.com/${GITHUB_REPOSITORY}/actions"
|
||||
echo "- Boundary gates: 18/18 expected green on this tag"
|
||||
} > release-notes.md
|
||||
{
|
||||
echo "body<<NOTES_EOF"
|
||||
cat release-notes.md
|
||||
echo "NOTES_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: ${{ github.ref_name }}
|
||||
prerelease: ${{ steps.classify.outputs.prerelease == 'true' }}
|
||||
body: ${{ steps.notes.outputs.body }}
|
||||
generate_release_notes: false
|
||||
@@ -0,0 +1,53 @@
|
||||
# Semgrep static analysis covering default + Swift + TypeScript rule packs.
|
||||
# Runs on PRs + pushes to develop/main + weekly schedule.
|
||||
#
|
||||
# SEMGREP_APP_TOKEN is optional; without it, scans run with public rules
|
||||
# and findings appear only in the workflow log (not Semgrep Cloud).
|
||||
|
||||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop, main]
|
||||
push:
|
||||
branches: [develop, main]
|
||||
schedule:
|
||||
- cron: "0 10 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: semgrep-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Semgrep scan
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
if: (github.actor != 'dependabot[bot]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Semgrep CI
|
||||
env:
|
||||
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||
SEMGREP_RULES: "p/default p/swift p/typescript p/secrets"
|
||||
run: |
|
||||
# If SEMGREP_APP_TOKEN is set, `semgrep ci` posts to Semgrep Cloud.
|
||||
# Otherwise, we fall back to a local scan over SEMGREP_RULES.
|
||||
if [ -n "${SEMGREP_APP_TOKEN:-}" ]; then
|
||||
semgrep ci
|
||||
else
|
||||
semgrep scan --config "$SEMGREP_RULES" --error --sarif --output=semgrep.sarif || true
|
||||
fi
|
||||
|
||||
- name: Upload SARIF (when present)
|
||||
if: hashFiles('semgrep.sarif') != ''
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
category: semgrep
|
||||
+68
@@ -160,6 +160,74 @@ The eval harness — Anthropic + local-model matrix, corpus in
|
||||
`.planning/evals/` — is pinned to `qwen2.5-coder:32b` for the local-model
|
||||
lane.
|
||||
|
||||
## CI/CD
|
||||
|
||||
GitHub Actions is the canonical CI surface. Five workflows live in
|
||||
`.github/workflows/`:
|
||||
|
||||
| Workflow | Trigger | What it does |
|
||||
|----------|---------|--------------|
|
||||
| `ci.yml` | PR + push to `develop`/`main`/`rc/*`/`release/*` | Boundary gates, webview (pnpm), 14 SPM packages (matrix), app target (xcodebuild), tag publish |
|
||||
| `codeql.yml` | PR + push + weekly cron | Static analysis for Swift + JavaScript/TypeScript |
|
||||
| `semgrep.yml` | PR + push + weekly cron | Rule-based scan (`p/default`, `p/swift`, `p/typescript`, `p/secrets`) |
|
||||
| `dependency-review.yml` | PR touching `package.json` / `pnpm-lock.yaml` / `Package.swift` / `Package.resolved` | Blocks high-severity vulns + license violations |
|
||||
| `release.yml` | Push of `v*` tag | Creates a GitHub Release with auto-generated changelog |
|
||||
|
||||
### Branch + tag conventions
|
||||
|
||||
- **`feature/*`, `fix/*`** — open a PR against `develop`. CI runs full
|
||||
build + test + scan; no tagging.
|
||||
- **`develop`** — integration trunk. Every push runs full CI and publishes a
|
||||
pre-release tag `v0.X.Y-dev.<run_number>`.
|
||||
- **`rc/*` / `release/*`** — release candidates. Tag `v0.X.Y-rc.N` (N auto-
|
||||
increments from existing tags).
|
||||
- **`main`** — production. Only updated via PR from `rc/*` or `release/*`.
|
||||
Push to `main` produces a final tag `v0.X.Y` and a GitHub Release.
|
||||
|
||||
Versioning bumps follow Conventional Commits since the previous final tag:
|
||||
|
||||
- `feat:` → minor bump
|
||||
- `fix:` → patch bump
|
||||
- `feat!:` or `BREAKING CHANGE:` footer → major bump
|
||||
|
||||
When no tags exist yet, the seed version is `v0.1.0` (current roadmap
|
||||
target).
|
||||
|
||||
### Failed boundary gate?
|
||||
|
||||
Each script in `scripts/check-*.sh` encodes an architectural invariant. The
|
||||
table in [Boundary-gate linters](#boundary-gate-linters) above explains
|
||||
what each one enforces. To reproduce locally:
|
||||
|
||||
```bash
|
||||
bash scripts/check-<name>.sh
|
||||
```
|
||||
|
||||
The script's header comments explain the "why" behind the check and point
|
||||
at the relevant audit finding or bug.
|
||||
|
||||
### Opting into real-hardware tests
|
||||
|
||||
The real-hardware suites (`JARVIS_REAL_MODELS=1`, `JARVIS_REAL_CAMERA=1`)
|
||||
need local Ollama and a real camera, so they don't run on GitHub-hosted
|
||||
runners. To run them in CI:
|
||||
|
||||
1. Register a self-hosted macOS runner with `Ollama` + the necessary
|
||||
models + camera TCC grants.
|
||||
2. Trigger `ci.yml` via the Actions tab → "Run workflow" → set
|
||||
`run_real_hardware` to `true`.
|
||||
|
||||
The `real-hardware` job is gated on `runs-on: [self-hosted, macOS]` and
|
||||
only fires for `workflow_dispatch` events with the input set.
|
||||
|
||||
### Required repo secrets (optional)
|
||||
|
||||
- `SEMGREP_APP_TOKEN` — only needed if you want findings posted to
|
||||
Semgrep Cloud. Without it, scans still run with public rules and the
|
||||
workflow log shows results inline.
|
||||
|
||||
`GITHUB_TOKEN` is provided automatically by Actions; no setup needed.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [README](README.md) — project overview and quick-start.
|
||||
|
||||
Reference in new issue
Block a user