Skip to content

chore(deps-dev): bump vitest from 3.2.6 to 4.1.11 in /cli #643

chore(deps-dev): bump vitest from 3.2.6 to 4.1.11 in /cli

chore(deps-dev): bump vitest from 3.2.6 to 4.1.11 in /cli #643

Workflow file for this run

name: cli CI
# No `paths:` filter, deliberately: a filtered workflow that is not triggered still leaves
# its required checks "expected" on GitHub's side, blocking merge on a PR outside its scope.
# The `changes` job decides in bash instead, every other job is gated on its `relevant`
# output, and `gate` reports green when nothing was relevant.
#
# kanban/ has its own job and its own dependencies; no CLI job needs its node_modules.
on:
push:
branches: [main, next]
pull_request:
schedule:
# Weekly, every mutant of every scope from scratch: the incremental files PRs restore
# only replay what a change touched, so drift through a dependency is caught here.
- cron: "0 3 * * 1"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
permissions:
actions: read
contents: read
jobs:
changes:
name: cli / changes
permissions:
actions: read
contents: read
pull-requests: read
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
trusted_promotion: ${{ steps.promotion.outputs.trusted }}
mutation_scopes: ${{ steps.mutation.outputs.scopes }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Decide whether this workflow's scope changed
id: filter
run: |
set -euo pipefail
# The scope the jobs below actually exercise. The telemetry plugin's own prose is
# excluded because validate.yml already asserts it on every push and pull request.
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.sha }}"
fi
# A new branch or a force-push gives an all-zero `before`: with no base to diff
# against, `git diff` would error into a skip reading as "nothing changed".
if [[ -z "$BASE" || "$BASE" =~ ^0+$ ]]; then
echo "no usable base SHA (new branch or force-push) - treating as relevant"
echo "relevant=true" >> "$GITHUB_OUTPUT"
exit 0
fi
relevant=false
while IFS= read -r f; do
[[ -z "$f" ]] && continue
case "$f" in
cli/*|kanban/*|scripts/__tests__/*|README.md)
relevant=true
;;
# This file: a change to how the suite runs matches nothing else here, so the
# workflow deciding whether the suite passes would land unverified.
.github/workflows/cli-ci.yml)
relevant=true
;;
plugins/aidd-telemetry/*)
case "$f" in
*.md) ;; # prose only, covered by validate.yml instead
*) relevant=true ;;
esac
;;
esac
[[ "$relevant" == "true" ]] && break
done < <(git diff --name-only "$BASE" "$HEAD")
echo "relevant=$relevant" >> "$GITHUB_OUTPUT"
# A promotion branch is a snapshot of next, not the live next branch. Reuse only proves
# a tree already gated on next: every missing Git or API proof falls back to mutations.
- name: Check whether a promotion snapshot or main merge passed next
id: promotion
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
CURRENT_SHA: ${{ github.sha }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
run: |
set -euo pipefail
trusted=false
reason="not a reusable promotion snapshot"
has_successful_next_gate() {
local run_ids run_id gate
reason="no successful cli CI push run found for the snapshot SHA"
if run_ids="$(gh api --paginate "/repos/$REPO/actions/workflows/cli-ci.yml/runs?branch=next&event=push&status=completed&head_sha=$HEAD_SHA&per_page=100" \
--jq '.workflow_runs[] | select(.conclusion == "success") | .id' 2>/dev/null)"; then
while IFS= read -r run_id; do
[[ -z "$run_id" ]] && continue
if gate="$(gh api "/repos/$REPO/actions/runs/$run_id/jobs?filter=latest&per_page=100" \
--jq 'any(.jobs[]; .name == "cli / gate" and .conclusion == "success")' 2>/dev/null)"; then
if [[ "$gate" == "true" ]]; then
reason="reusing the successful cli CI gate from next run $run_id"
return 0
fi
reason="cli / gate did not pass in next run $run_id"
else
reason="could not inspect cli CI run $run_id"
fi
done <<< "$run_ids"
else
reason="could not list successful cli CI push runs"
fi
return 1
}
# GitHub tests pull requests at a synthetic merge ref. The source gate may be reused
# only if main is already in the snapshot and that merge ref has the same file tree.
if [[ "$EVENT_NAME" == "pull_request" && "$BASE_REF" == "main" && "$HEAD_REF" =~ ^promote/next-to-main-[0-9]+$ && "$HEAD_REPO" == "$REPO" ]]; then
if ! git merge-base --is-ancestor "$BASE_SHA" "$HEAD_SHA" 2>/dev/null; then
reason="main base is not an ancestor of the promotion snapshot"
elif ! snapshot_tree="$(git rev-parse "$HEAD_SHA^{tree}" 2>/dev/null)" || ! merge_tree="$(git rev-parse "$CURRENT_SHA^{tree}" 2>/dev/null)"; then
reason="could not prove the promotion merge tree"
elif [[ "$merge_tree" != "$snapshot_tree" ]]; then
reason="promotion merge tree differs from the snapshot"
elif has_successful_next_gate; then
trusted=true
fi
# A main push is reusable only for the exact merge of a numbered, same-repository
# promotion PR. Parent two is its source snapshot; neither a squash nor an unrelated
# merge can satisfy this proof.
elif [[ "$EVENT_NAME" == "push" && "$GITHUB_REF" == "refs/heads/main" ]]; then
if ! parents="$(git rev-list --parents -n 1 "$CURRENT_SHA" 2>/dev/null)" || ! read -r merge_sha first_parent snapshot_sha extra_parent <<< "$parents" || [[ "$merge_sha" != "$CURRENT_SHA" || -z "$first_parent" || -z "$snapshot_sha" || -n "${extra_parent:-}" ]]; then
reason="main commit is not a two-parent merge"
elif ! snapshot_tree="$(git rev-parse "$snapshot_sha^{tree}" 2>/dev/null)" || ! main_tree="$(git rev-parse "$CURRENT_SHA^{tree}" 2>/dev/null)"; then
reason="could not read the main merge or snapshot tree"
elif [[ "$main_tree" != "$snapshot_tree" ]]; then
reason="main merge tree differs from the promotion snapshot"
elif ! prs="$(gh api "/repos/$REPO/commits/$CURRENT_SHA/pulls" \
--jq '.[] | select(.base.ref == "main" and (.head.ref | test("^promote/next-to-main-[0-9]+$")) and .merged_at != null) | [.base.repo.full_name, .head.repo.full_name, .head.ref, .head.sha, .merged_at, .merge_commit_sha] | @tsv' 2>/dev/null)"; then
reason="could not inspect pull requests associated with the main commit"
else
matching_prs=0
while IFS=$'\t' read -r base_repo head_repo head_ref pr_head_sha merged_at merge_commit_sha; do
if [[ "$base_repo" == "$REPO" && "$head_repo" == "$REPO" && "$head_ref" =~ ^promote/next-to-main-[0-9]+$ && "$pr_head_sha" == "$snapshot_sha" && -n "$merged_at" && "$merge_commit_sha" == "$CURRENT_SHA" ]]; then
((matching_prs += 1))
fi
done <<< "$prs"
if [[ "$matching_prs" -ne 1 ]]; then
reason="no unique matching promotion pull request proved this main merge"
else
HEAD_SHA="$snapshot_sha"
if has_successful_next_gate; then
trusted=true
fi
fi
fi
fi
echo "promotion mutation reuse: $reason"
echo "trusted=$trusted" >> "$GITHUB_OUTPUT"
- name: Decide which mutation scopes a change can move
id: mutation
run: |
set -euo pipefail
if [[ "${{ steps.promotion.outputs.trusted }}" == "true" ]]; then
scopes='[]'
else
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.sha }}"
fi
if [[ -z "$BASE" || "$BASE" =~ ^0+$ ]]; then
changed=""
all=true
else
changed="$(git diff --name-only "$BASE" "$HEAD")"
all=false
fi
scopes="$(ALL="$all" CHANGED="$changed" node cli/scripts/mutation-scopes-to-run.mjs)"
fi
echo "mutation scopes: $scopes"
echo "scopes=$scopes" >> "$GITHUB_OUTPUT"
cli-typecheck:
name: cli / Typecheck
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm typecheck
cli-lint:
name: cli / Lint
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm lint
cli-architecture:
name: cli / Architecture invariants
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm test:arch
# Every tier once, instrumented: coverage is the test run, and a second uninstrumented
# pass would prove nothing this one does not.
cli-coverage:
name: cli / Test & Coverage thresholds
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# The runner image carries Google's chrome apt source, whose mirror serves a stale index a
# few times a day ("Hash Sum mismatch") and fails the whole update; expect comes from ubuntu.
- name: Install expect (TTY persona tests)
run: |
sudo rm -f /etc/apt/sources.list.d/google-chrome.list /etc/apt/sources.list.d/google-chrome.sources
sudo apt-get update && sudo apt-get install -y --no-install-recommends expect
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm test:coverage
cli-smoke:
name: cli / Smoke
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
# Hermetic: every setup uses the local framework fixture, so this needs no
# token and no network. `smoke:full` adds the remote-fetch section on demand.
- run: cd cli && pnpm smoke
cli-kilo-runtime:
name: cli / Kilo runtime smoke
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
# The test starts Kilo's local server and asks it to load the generated project plugin.
# No model or account is needed for this protocol-level smoke.
- name: Install Kilo Code CLI
run: npm install -g @kilocode/cli@7.7.5
- run: cd cli && pnpm test:e2e:kilo
cli-build:
name: cli / Build & Bundle Budget
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm build
cli-knip:
name: cli / Knip (dead code)
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm knip
identifier-join:
name: cli / Identifier join (Claude Code)
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm build
# The probe drives the real tool, so the real tool has to be here. No credentials are
# needed: it points Claude Code at a dead address, and the session id is minted first.
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code # floats on purpose, declared in scripts/__tests__/workflows-pin-global-installs.test.js
# Everything in this layer joins on the identifier a hook receives being the one the
# export carries, and a tool update can break it with nothing else turning red. Exit 2
# means the probe could not form an opinion, rather than blaming the tool.
- name: Probe the identifier join
run: node scripts/probe-identifier-join.cjs
# The golden snapshots pin what a claude build contains; only the host can say it loads.
# Its `plugin validate` exits 0 either way, so the script reads the verdict from its text.
- name: Claude Code accepts the translated marketplace
run: node scripts/check-claude-accepts-build.cjs
cli-jscpd:
name: cli / JSCPD (duplication)
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
- run: cd cli && pnpm jscpd
kanban-checks:
name: kanban / Typecheck, Lint & Test
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('kanban/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd kanban && pnpm install --frozen-lockfile
- run: cd kanban && pnpm typecheck
- run: cd kanban && pnpm lint
- run: cd kanban && pnpm test
windows:
# Runs under bash (Git Bash, bundled on windows-latest) so every command is the one the
# Linux jobs run, not a PowerShell rewrite of it. No pnpm store cache: the store path in
# the Linux jobs' cache key is POSIX, not where a native Windows pnpm resolves its store.
name: cli / Windows
needs: [changes]
if: needs.changes.outputs.relevant == 'true'
runs-on: windows-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
# The WRITE path, on the platform where a home directory, a line ending and an absolute
# path all resolve differently. It runs before the CLI is built on purpose: recording is
# supposed to need nothing installed, and this is where that claim is exercised.
- name: Chain - allow measurement, with nothing installed
run: |
# Written directly rather than through a command: the switch is a file the hooks
# read fresh at every write, so no binary has to exist for it to work.
mkdir -p .aidd
node -e "require('fs').writeFileSync('.aidd/config.json', JSON.stringify({ telemetry: { enabled: true } }, null, 2) + '\n');"
- name: Chain - journal a captured payload
run: |
# The fixture's cwd came off whatever machine captured it: rewritten so getRepoRoot
# resolves a repository that exists here.
node -e "const p=require('./scripts/__tests__/fixtures/claude-code-session-start.json'); p.cwd=process.cwd(); require('fs').writeFileSync('payload.json', JSON.stringify(p));"
node plugins/aidd-telemetry/hooks/journal.cjs session-start < payload.json
node plugins/aidd-telemetry/hooks/journal.cjs turn-end < payload.json
# `telemetry check` fails on `sessionFound: false`, and nothing in this chain places a
# transcript for the session just journalled. Copied from a local-cost fixture with its
# session id rewritten, under `$HOME` — which is what the read path itself resolves
# first, and which Git Bash sets on this runner.
- name: Chain - place the transcript where claude keeps it
run: |
node -e "const fs=require('fs'),os=require('os'),path=require('path');const homeDir=process.env.HOME||os.homedir();const sessionId='ffde6fda-14a8-4b32-8110-be1f1d13eebf';const src='cli/tests/fixtures/local-cost/.claude/projects/fake-project/22222222-2222-4222-8222-222222222222.jsonl';const dir=path.join(homeDir,'.claude','projects','fake-project');fs.mkdirSync(dir,{recursive:true});const content=fs.readFileSync(src,'utf8').split('22222222-2222-4222-8222-222222222222').join(sessionId);fs.writeFileSync(path.join(dir,sessionId+'.jsonl'),content);"
- name: Chain - the journal stays private and git add -A still works
run: |
git add -A
git status --porcelain >/dev/null
# Wrapped: a suite writing into this repository's own .git/hooks passes every assertion
# and destroys an install nothing can restore. The suite reads YAML through the root
# devDependencies, so the install has to precede it — and the chain steps above stay
# ahead of any install on purpose.
- run: pnpm install --frozen-lockfile
- name: Plugin suite
run: node scripts/check-tests-leave-git-alone.js -- node --test 'scripts/__tests__/**/*.test.js'
- run: cd cli && pnpm install --frozen-lockfile
# A real global install, not `node dist/cli.js` by path: nothing else proves `aidd`
# resolves on this platform's PATH. Built here, ahead of the suites below, so this is
# the only build the job needs.
#
# Inlined rather than `pnpm run install:local`: a package.json script runs through
# pnpm's configured shell, `cmd.exe` on Windows, where that script's `$(node -p ...)`
# is not valid syntax. This block is bash by the job's `defaults.run.shell`.
- name: Install the built CLI globally, the way a person actually would
run: |
cd cli
pnpm build
pnpm pack --pack-destination ./dist
npm install -g ./dist/ai-driven-dev-cli-*.tgz --force
# The exact command `02-check`'s own markdown names, not a script beside it. Failing
# `aidd --version` means the CLI could not be resolved on the PATH.
- name: Chain - diagnose, through the command every skill's own markdown names
run: |
aidd --version
# A gated run judges nothing and exits 0, so the one row the chain exists to make
# true is asserted separately: the transcript placed for the session was read.
aidd telemetry check | tee telemetry-check.out
grep -E "tool files readable +ok" telemetry-check.out
- run: cd cli && pnpm test:unit
- run: cd cli && pnpm test:integration
# Every e2e but the two named below. `telemetry-commit-trailer.e2e.test.ts` is why the
# step matters most: it is the only thing proving the `prepare-commit-msg` hook is
# reachable by the shell Git for Windows ships, which a backslash path is not.
- name: cli e2e
# `--max-workers=2`, and only here. Vitest transforms modules on its own main thread
# while every worker calls back into it, and that call has a fixed 60 s timeout no
# configuration exposes: on this runner the default pool queued it past 60 s and
# failed with `Timeout calling "onTaskUpdate"` while every test passed. A fixed pool
# of two caps the competing requests whatever the runner's core count.
run: |
cd cli
pnpm exec vitest run --project=e2e --max-workers=2 \
--exclude "tests/e2e/persona.e2e.test.ts" \
--exclude "tests/e2e/telemetry-multi-tool.e2e.test.ts"
# persona.e2e.test.ts needs /usr/bin/expect, which windows-latest does not carry;
# telemetry-multi-tool.e2e.test.ts relies on a shebang, and Windows resolves an
# executable by PATHEXT.
# Fan-in: every job above either ran and passed, or was skipped because `changes` found
# nothing relevant. The one check the branch rulesets require — without it a pull request
# merges with the real jobs red, because nothing names them.
cli-mutation:
name: cli / Mutation (${{ matrix.scope }})
needs: [changes]
if: needs.changes.outputs.mutation_scopes != '[]'
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
scope: ${{ fromJSON(needs.changes.outputs.mutation_scopes) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install pnpm
run: corepack enable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.local/share/pnpm/store
key: pnpm-${{ runner.os }}-${{ hashFiles('cli/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-
- run: cd cli && pnpm install --frozen-lockfile
# The incremental file is what keeps a run to the mutants a change can move; the newest
# one this branch or its base saved is restored, and this run's is saved whatever the score.
- name: Restore this scope's incremental file
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: cli/reports/mutation/${{ matrix.scope }}/incremental.json
key: mutation-${{ matrix.scope }}-${{ github.run_id }}
restore-keys: mutation-${{ matrix.scope }}-
- run: cd cli && node scripts/run-mutation.mjs "${{ matrix.scope }}" ${{ github.event_name == 'schedule' && '--force' || '' }}
- name: Save this scope's incremental file
if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: cli/reports/mutation/${{ matrix.scope }}/incremental.json
key: mutation-${{ matrix.scope }}-${{ github.run_id }}
gate:
name: cli / gate
runs-on: ubuntu-latest
if: always()
needs:
- changes
- cli-typecheck
- cli-lint
- cli-architecture
- cli-coverage
- cli-smoke
- cli-kilo-runtime
- cli-build
- cli-knip
- identifier-join
- cli-jscpd
- cli-mutation
- kanban-checks
- windows
steps:
- name: Fail unless changes decided cleanly and every gated job passed or was skipped
run: |
if [[ "${{ needs.changes.result }}" != "success" ]]; then
echo "::error::the changes job did not succeed (${{ needs.changes.result }}) — whether this workflow had anything to do could not be decided, so every job below it reads as skipped without proving it should have been"
exit 1
fi
for result in \
"${{ needs.cli-typecheck.result }}" \
"${{ needs.cli-lint.result }}" \
"${{ needs.cli-architecture.result }}" \
"${{ needs.cli-coverage.result }}" \
"${{ needs.cli-smoke.result }}" \
"${{ needs.cli-kilo-runtime.result }}" \
"${{ needs.cli-build.result }}" \
"${{ needs.cli-knip.result }}" \
"${{ needs.identifier-join.result }}" \
"${{ needs.cli-jscpd.result }}" \
"${{ needs.cli-mutation.result }}" \
"${{ needs.kanban-checks.result }}" \
"${{ needs.windows.result }}"; do
if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
echo "::error::a required job did not pass (result: $result)"
exit 1
fi
done