Skip to main content

List the specs a pull request affects

tiden impact lists the specs a diff affects. It finds them through the source anchors of the changed files: a spec is affected when one of the files the diff changes is anchored to it. It needs no intent session, so a reviewer, a CI job, or an agent can run it on any branch.

Requires tiden 0.45.0 or newer; run tiden upgrade to update.

Anchors are read from the product's main branch. A file the diff adds has no anchor there yet, so it is listed as new until the change merges. The same holds for specs that exist only on an unmerged intent branch.

Run it locally​

Compare your branch with its merge base on origin/main, the same files GitHub shows under "Files changed":

tiden impact --base origin/main

Other sources of changed files:

CommandFiles
tiden impactAll uncommitted changes: staged, unstaged and untracked
tiden impact --stagedStaged changes only
tiden impact --base <ref> --head <ref>The range from merge-base(<base>, <head>) to <head>
tiden impact <path>...These paths, relative to the current directory

A renamed file is matched through its old path as well, so a spec anchored to the old name is still found. In a repository with submodules, a changed file inside a submodule belongs to the submodule's repository.

Read the result​

SectionMeaning
Affected specsSpecs anchored to a changed file, ranked by how many changed files hit them, with no covering test when no test is linked
Reached only through hub filesSpecs reached only through a file anchored to more than 10 specs, such as CHANGELOG.md. They are listed apart so they do not flood the ranking
New filesAdded files. They cannot have an anchor on main until they merge
Files with no anchorExisting files no spec is anchored to
Hub filesFiles anchored to more specs than --hub-threshold (default 10; 0 turns this off). The count is per repository
Repositories left outSubmodules whose own .tiden/config.json names another product. Run tiden impact in that checkout instead

Matching is per file: any change to an anchored file affects the spec, whatever lines it touches.

--format takes text, json, markdown or github. json is the full, never-truncated result (schemaVersion 1). --limit (default 20) caps the specs in the other formats, and --no-titles prints spec references without titles, for comments on public repositories.

Gate a pull request​

--fail-on makes the command exit 3 when its policy matches:

PolicyExits 3 when
never (default)Never
affectedAny spec is affected, including specs reached through hub files
uncoveredAn affected spec has no covering test, including specs reached through hub files
unanchoredAn existing changed file has no anchor. New files never count

If the product has no anchors on main at all, every list is empty and says nothing about the change. With --fail-on set, the command then exits 1 instead of passing.

GitHub Actions​

With no --base, --staged or paths, tiden impact uses GITHUB_BASE_REF (set on pull_request runs) as --base origin/<branch>. The merge base needs history: the default fetch-depth: 1 checkout has none, and the command exits 1 with the fix. Use fetch-depth: 0, with filter: blob:none to skip file contents you do not need.

This workflow writes the annotations and the job summary, updates one PR comment, and fails when an affected spec has no covering test:

name: Specs affected
on: pull_request

permissions:
contents: read
pull-requests: write

jobs:
impact:
# Pull requests from forks get no secrets, so tiden cannot authenticate.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
env:
TIDEN_API_TOKEN: ${{ secrets.TIDEN_API_TOKEN }}
TIDEN_PRODUCT_ID: ${{ vars.TIDEN_PRODUCT_ID }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
filter: blob:none

- name: Install tiden
run: |
curl -fsSL https://tiden.ai/install.sh | TIDEN_PREFIX="$HOME/.local/bin" bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Comment the affected specs
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
run: |
tiden impact --format markdown > impact.md
id=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR/comments" --paginate \
--jq '.[] | select(.body | startswith("<!-- tiden-impact -->")) | .id' | head -n1)
if [ -n "$id" ]; then
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -F body=@impact.md
else
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR/comments" -F body=@impact.md
fi

- name: Fail on uncovered specs
run: tiden impact --format github --fail-on uncovered

The markdown output starts with a hidden <!-- tiden-impact --> line, which is how the step finds its own comment again, and stays under GitHub's comment size. The github format prints at most 10 annotations of each kind, the most GitHub shows per step, and writes the full list to the job summary.

To run on pull requests from forks, you would need pull_request_target, which runs with secrets next to untrusted code. Read GitHub's guidance on that event before you use it.

GitLab CI​

In a merge request pipeline, CI_MERGE_REQUEST_TARGET_BRANCH_NAME selects the base the same way, as origin/<target branch>. Set GIT_DEPTH: 0 so the merge base is in the clone, and fetch the target branch so that ref exists:

impact:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
GIT_DEPTH: 0
script:
- git fetch origin "+refs/heads/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:refs/remotes/origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
- curl -fsSL https://tiden.ai/install.sh | TIDEN_PREFIX=/usr/local/bin bash
- tiden impact --format text --fail-on uncovered

Exit codes​

CodeMeaning
0Listed; no --fail-on policy matched
1Error: the base is not reachable in this clone, a flag is invalid, or --fail-on is set while the product has no anchors on main
2Authentication error
3The --fail-on policy matched