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
3 changes: 3 additions & 0 deletions .Manual/jenkins-ci-pipeline.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Jenkins CI Pipeline

> **The merge gate is GitHub Actions**, not this job. See [`docs/ci.md`](../docs/ci.md).
> This page is the old local Poll SCM monitor ([ADR-0095](../docs/adr/0095-jenkins-ci-with-poll-scm.md)).

## Feature Name

Automated build verification for the MBA repo — every push to `main` is typechecked, tested, and built by Jenkins.
Expand Down
67 changes: 67 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Publish `@mba-ai/core` to npm on a version tag (`v0.1.16`).
# Does not run on pull requests or on push to main.
# Auth is npm Trusted Publisher (OIDC). No NPM_TOKEN.
#
# On npmjs.com → @mba-ai/core → Trusted Publisher → GitHub Actions:
# Organization or user: SoryAK
# Repository: MBA
# Workflow filename: publish.yml
# Environment: (leave empty)
# Allowed actions: npm publish

name: Publish

on:
push:
tags:
- "v*"

permissions:
contents: read
id-token: write

concurrency:
group: npm-publish
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 22
package-manager-cache: false

- name: npm CLI for trusted publishing
run: |
npm install -g npm@latest
npm --version

- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build

- name: Tag matches core version and changelog
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
ver="$(node -p "require('./packages/core/package.json').version")"
if [ "$tag" != "$ver" ]; then
echo "tag v$tag does not match @mba-ai/core $ver"
exit 1
fi
if ! grep -qE "^## \\[${ver}\\] " packages/core/CHANGELOG.md; then
echo "packages/core/CHANGELOG.md has no ## [$ver] section"
exit 1
fi
if grep -qE "^## \\[Unreleased\\]" packages/core/CHANGELOG.md; then
echo "changelog still has an Unreleased heading"
exit 1
fi

- name: Publish @mba-ai/core
run: npm publish -w @mba-ai/core
3 changes: 2 additions & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// MBA CI pipeline — runs on every push to main (see docs/ci.md for job setup).
// MBA CI pipeline — local reference only. The merge gate is GitHub Actions
// (.github/workflows/ci.yml) plus the Protect main ruleset; see docs/ci.md.
// Requires a NodeJS tool named 'node-22' configured in Jenkins (Manage Jenkins → Tools).
pipeline {
agent any
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ After CLI changes, rebuild `@mba-ai/core` so a linked `mba` picks them up (`npm
## Docs

- [`.Manual/model-behavioral-adapters.md`](.Manual/model-behavioral-adapters.md) — system manual
- [`docs/ci.md`](docs/ci.md) — merge gate (GitHub Actions)
- [`docs/adr/`](docs/adr/) — architecture decision records

## License
Expand Down
5 changes: 4 additions & 1 deletion docs/adr/0095-jenkins-ci-with-poll-scm.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# ADR-0095: Jenkins CI with Poll SCM (local, no internet exposure)

**Status:** Accepted
**Status:** Accepted for the local Jenkins trigger. The **merge gate** on the
public repo is GitHub Actions plus the Protect main ruleset; see
[docs/ci.md](../ci.md). This ADR remains the record of why Poll SCM was
chosen when the repo could not use Actions.

## Context

Expand Down
101 changes: 49 additions & 52 deletions docs/ci.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,72 @@
# CI — Jenkins Pipeline
# CI — GitHub Actions merge gate

MBA builds on every push to `main` via a Jenkins declarative pipeline defined in
[`Jenkinsfile`](../Jenkinsfile) at the repo root. The job polls GitHub every 5
minutes (Poll SCM) and builds automatically when a new commit lands on `main`.
MBA’s quality gate is [`.github/workflows/ci.yml`](../.github/workflows/ci.yml).
It runs on every **pull request** and on every **push to `main`**. The
**Protect main** ruleset requires that check to be green before a PR can
merge. A red typecheck, test, or build cannot land on `main` unless someone
explicitly bypasses the ruleset.

## What a build does
## What a run does

1. **Checkout** — clones the latest `main` from GitHub (`SoryAK/MBA`, public — no credential needed)
2. **Install** — `npm ci` (exact deps from `package-lock.json`)
1. **Install** — `npm ci` (exact deps from `package-lock.json`)
2. **Audit** — `npm audit --omit=dev --audit-level=high`
3. **Typecheck** — `npm run typecheck` (`tsc --noEmit`)
4. **Test** — `npm test` (`vitest run`)
5. **Build** — `npm run build` (all workspaces with a build script)

Any stage failing marks the build red.
The check name GitHub requires is **`test`** (the job in that workflow).
CodeQL also runs on PRs; it is not part of the merge requirement.

## One-time Jenkins setup

### 1. Node 22 tool
## Local parity

The pipeline references a NodeJS tool named **`node-22`**.
A green local `npm run typecheck && npm test && npm run build` should mean a
green Actions run (audit is extra on CI).

- Manage Jenkins → **Tools** → **NodeJS** → *Add NodeJS*
- **Name:** `node-22` (must match exactly)
- Either tick *Install automatically* (Jenkins downloads Node 22.x) or point
**Tool home** at an existing install, e.g. `/usr/lib/node_modules` is NOT
correct — use the directory *containing* the `bin/` folder, e.g.
`/opt/node-22` or wherever `node` lives (`dirname $(dirname $(readlink -f $(command -v node)))`).
After CLI UI changes, rebuild `@mba-ai/core` so a linked `mba` matches CI’s
`dist`. The daemon still runs from source (`tsx`); restart the user unit
after service-path changes.

### 2. Create the job
## Ruleset

1. **New Item** → name it `MBA` → type **Pipeline** → OK
2. **Pipeline** section:
- **Definition:** *Pipeline script from SCM*
- **SCM:** Git
- **Repository URL:** `https://github.com/SoryAK/MBA.git`
- **Branch:** `main`
- **Script Path:** `Jenkinsfile`
3. **Save** → **Build Now** to verify the first run is green.
Repository **Settings → Rules → Protect main** (branch `main`):

### 3. Trigger on push — Poll SCM (active)
- Must go through a pull request
- Required status check: `test` (GitHub Actions)
- No force-push, no deleting `main`

The job uses **Poll SCM**: Jenkins checks GitHub every 5 minutes and builds
when a new commit lands on `main`.
The repo owner can still bypass in an emergency. Treat that as exceptional.

- Job **Configure** → **Triggers** → tick **Poll SCM**
- **Schedule:** `H/5 * * * *` (every 5 min; `H` staggers the exact minute)
- **Save**
## npm publish (Trusted Publisher)

Verify it's working: the job page gains a **Git Polling Log** link
(`/job/MBA/scmPollLog`). Each poll should end with `No changes` until you push
a new commit, at which point the next poll triggers a build.
`@mba-ai/core` publishes from [`.github/workflows/publish.yml`](../.github/workflows/publish.yml)
when you push a tag that matches the package version (`v0.1.16` → `0.1.16`).
It does not run on PRs or on `main`. `@mba-ai/mcp-server` is not in this
workflow.

> **Why polling and not a webhook?** Jenkins runs on `localhost`, which GitHub
> can't reach. A webhook would need a public tunnel (ngrok/cloudflared) kept
> running. Polling needs no internet exposure and is the robust choice for a
> local Jenkins. If Jenkins ever moves to a server, switch to the webhook below
> for near-instant triggers.
GitHub Actions authenticates to npm with OIDC. There is no `NPM_TOKEN`.

### 4. (Future) GitHub webhook — only if Jenkins is publicly reachable
One-time on [npmjs.com](https://www.npmjs.com/package/@mba-ai/core) → package
settings → **Trusted Publisher** → **GitHub Actions**:

GitHub → repo **Settings → Webhooks → Add webhook**:
| Field | Value |
| --- | --- |
| Organization or user | `SoryAK` |
| Repository | `MBA` |
| Workflow filename | `publish.yml` |
| Environment | leave empty |
| Allowed actions | `npm publish` |

- **Payload URL:** `http://<jenkins-host>:8080/github-webhook/`
- **Content type:** `application/json`
- **Events:** *Only selected events* → **Pushes**
Keep two-factor authentication required on the account.

Requires the **GitHub plugin** (Manage Jenkins → Plugins) and a Jenkins
instance reachable from the internet.
Release path: bump `packages/core/package.json` (and the lockfile), add a
`## [x.y.z]` section to `packages/core/CHANGELOG.md`, merge to `main`, then
`git tag vX.Y.Z && git push origin vX.Y.Z`.

## Local parity
## Jenkins (reference only)

The pipeline runs exactly the scripts in `package.json`, so a green local
`npm run typecheck && npm test && npm run build` should always mean a green
build.
[`Jenkinsfile`](../Jenkinsfile) is the old local Poll SCM monitor
([ADR-0095](adr/0095-jenkins-ci-with-poll-scm.md)). It is not the merge gate.
Keep the file as a map of the same stages. If a local Jenkins job is still
polling `main`, turn that poll off so you are not running two monitors.
A self-hosted Actions runner is the replacement if a check ever needs this
machine’s GPU or LAN — not a second CI product.