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:
| Command | Files |
|---|---|
tiden impact | All uncommitted changes: staged, unstaged and untracked |
tiden impact --staged | Staged 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
| Section | Meaning |
|---|---|
| Affected specs | Specs 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 files | Specs 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 files | Added files. They cannot have an anchor on main until they merge |
| Files with no anchor | Existing files no spec is anchored to |
| Hub files | Files anchored to more specs than --hub-threshold (default 10; 0 turns this off). The count is per repository |
| Repositories left out | Submodules 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:
| Policy | Exits 3 when |
|---|---|
never (default) | Never |
affected | Any spec is affected, including specs reached through hub files |
uncovered | An affected spec has no covering test, including specs reached through hub files |
unanchored | An 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
| Code | Meaning |
|---|---|
0 | Listed; no --fail-on policy matched |
1 | Error: 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 |
2 | Authentication error |
3 | The --fail-on policy matched |