chore(deps-dev): bump vitest from 3.2.6 to 4.1.11 in /cli #643
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |