Skip to content

feat(chat): exact-match cache for DEVIN_CONNECT path (stream + non-stream) #865

feat(chat): exact-match cache for DEVIN_CONNECT path (stream + non-stream)

feat(chat): exact-match cache for DEVIN_CONNECT path (stream + non-stream) #865

Workflow file for this run

name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
# Manual mutation sweep (Actions -> CI -> Run workflow). Deliberately NOT on the PR path:
# each mutation re-runs its spec's whole test suite, so all 44 specs is hours, and a gate
# nobody can afford to run is worse than no gate — that is the warning in the specs job's
# own comment. Until 2026-09-13 nothing in this repo ever executed `npm run mutate`.
workflow_dispatch:
inputs:
specs:
description: 'Spec files to run (space separated). Blank = every test/mutations/*.json'
required: false
default: ''
shard:
description: 'Zero-based shard i/N (0/1 = all selected specs)'
required: false
default: '0/1'
jobs:
mutations:
name: Mutation specs (manual)
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 350
steps:
# fetch-depth 0: the mutation harness materializes its workspace with
# `git clone --local` and reads history objects, so a shallow checkout fails it.
# Measured 2026-09-13: with the default depth 1 the sweep died on the FIRST spec
# with exit 2 and produced no verdict lines at all.
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
- name: Install dependencies
run: npm ci
# PR/push jobs intentionally do not execute mutation specs: this is a cost boundary.
# Optional parallel form (replace the input shard with matrix.shard in Sweep):
# strategy:
# fail-fast: false
# matrix:
# shard: ['0/4', '1/4', '2/4', '3/4']
# Put strategy at the mutations job level, not inside steps. Each job has its own
# checkout, manifest and artifact; never run two shards in one owner worktree.
- name: Sweep
env:
REQUESTED_SPECS: ${{ inputs.specs }}
REQUESTED_SHARD: ${{ inputs.shard }}
run: |
args=(--shard "${REQUESTED_SHARD:-0/1}" --report-dir "$RUNNER_TEMP/mutation-report")
if [ -n "$REQUESTED_SPECS" ]; then args+=(--specs "$REQUESTED_SPECS"); fi
node scripts/mutation-sweep.mjs "${args[@]}"
- name: Assert verdict coverage
if: always()
run: node scripts/mutation-sweep.mjs --verify "$RUNNER_TEMP/mutation-report"
- name: Publish mutation evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: mutation-report-${{ github.run_id }}-${{ strategy.job-index || 0 }}
path: ${{ runner.temp }}/mutation-report/
if-no-files-found: error
syntax:
name: Node syntax check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
- name: Check JS syntax for all src files
run: |
find src -name '*.js' -type f -print0 | xargs -0 -n1 node --check
- name: Validate package.json
run: node -e "JSON.parse(require('fs').readFileSync('package.json'))"
- name: Validate JSON configs
run: |
for f in $(find . -maxdepth 2 -name '*.json' -not -path './node_modules/*' -not -path './.git/*'); do
echo "Checking $f"
node -e "JSON.parse(require('fs').readFileSync('$f'))"
done
# Mutation-spec consistency. Three baseline/anchor drifts reached master in one week
# (#241's anchors, retry-rescue 82/87 -> 88, reasoning-continuity 289 -> 300) and the
# test suite saw none of them: it does not run specs. The anchor/shape checks read files
# only and cost ~0.3s, so they are mandatory rather than a local habit to remember.
#
# Baseline VALUE drift is measured here too, by EXECUTING the covered specs and reading
# the runner's events (scripts/spec-baseline-check.mjs) — never by scraping display text
# and never by rewriting a pin. A changed diff measures only the affected specs; a
# synchronised checkout measures all of them, which costs ~100s for 82 specs. The check
# used to run only in the local gate, so a wrong pin reached CI green.
specs:
name: Mutation spec consistency
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
with:
# Change-scoped selection diffs against origin/master, so the history has to be
# here; a shallow clone would silently widen the selection to every spec.
fetch-depth: 0
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
- name: Anchors unique, specs well-formed
run: node scripts/spec-static-check.mjs
- name: Measured baselines match, from executed evidence
run: node scripts/spec-baseline-check.mjs
- name: Secret scan (includes test/ — half of a typical diff lives there)
run: node scripts/secret-scan.mjs
wire-identity:
name: Default-path request bytes
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
with:
# The comparison base is a tag, so the tags have to be here.
fetch-depth: 0
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
- name: Select the release baseline (strict ancestor, recorded)
# `git describe --tags --abbrev=0` answers "nearest reachable tag", and at a
# tagged HEAD that is HEAD's own tag: a rerun of a release commit compared
# the tree with itself. scripts/wire-base.mjs picks the newest ANNOTATED
# release tag that is a strict ancestor of the tested commit — no
# lightweight fallback — so a rerun at a tag repeats the same comparison
# against the PREVIOUS release, the recorded SHAs never change, and a
# repository without such a tag fails closed (exit 3).
run: |
node scripts/wire-base.mjs
BASE_TAG=$(node scripts/wire-base.mjs --field tag)
BASE_SHA=$(node scripts/wire-base.mjs --field sha)
HEAD_SHA=$(git rev-parse HEAD)
if [ "$BASE_SHA" = "$HEAD_SHA" ]; then
echo "::error::refusing to compare $HEAD_SHA against itself"
exit 1
fi
git worktree add --detach "$RUNNER_TEMP/wire-base" "$BASE_SHA"
echo "comparing $HEAD_SHA against $BASE_SHA ($BASE_TAG)"
echo "WIRE_BASE_TREE=$RUNNER_TEMP/wire-base" >> "$GITHUB_ENV"
echo "WIRE_BASE_SHA=$BASE_SHA" >> "$GITHUB_ENV"
echo "WIRE_BASE_TAG=$BASE_TAG" >> "$GITHUB_ENV"
echo "WIRE_BASE_REQUIRED=1" >> "$GITHUB_ENV"
- name: Same bytes as the last release, or this fails
run: node --import ./test/setup-env.mjs --test test/wire-byte-identity.test.js
test:
name: Unit tests shard ${{ matrix.shard }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v5
- name: Setup Node 24
uses: actions/setup-node@v5
with:
node-version: '24'
cache: 'npm'
# image-resize.test.js imports jimp (the sole runtime dependency), so the
# runner needs node_modules — without this step every shard that touches
# a jimp-importing test fails with ERR_MODULE_NOT_FOUND.
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:shard -- ${{ matrix.shard }} 4