Skip to content

ci: a spec-only pull request runs the tests that read a record it changes (AST-030 FR2) - #9446

Merged
cixzhang merged 6 commits into
mainfrom
ci/spec-readers-run-on-spec-changes
Nov 2, 2026
Merged

cixzhang merged 6 commits into
mainfrom
ci/spec-readers-run-on-spec-changes

Conversation

@cixzhang

@cixzhang cixzhang commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer impact

  • Before: a spec-only pull request skips every Vitest test. Such a pull request changes only records: spec and component records under docs/specs/, family and design contracts, and theme package specs. Some tests read records from disk: packages/core/src/motion/roles.test.ts reads AST-094's FR4 table, and icon-set-checks.test.mjs reads AST-084. A record change can break them, and the break shows up only on main. spec(ContextMenu): touch-opened menus lift with a tint, grow from the finger, and use named springs #9423 changed AST-094's FR4 table, and main went red until it was fixed.
  • After: on a spec-only pull request, leg 1 of test-ui and leg 1 of test-node each list the tests in their own Vitest project that read a changed record, and run them. Project membership is read from vitest.config.ts. A leg with no reader adds only the check-out and the listing (about 10 s). A renamed record counts under its old and new names. Every other lane is unchanged.
  • Which tests: a test counts as reading a record when its import closure reads files (the existing file-system guard rule from FR13, relative imports followed) and its code names a path into a record root (docs/specs/, docs/families/, docs/design/, or packages/themes/ together with a .spec.md name, so theme-source readers don't count). The path may be relative to the test (../../docs/specs/…) or from the root. Prose in comments doesn't count.
    • A test reads a changed record if the path it names is that record or a directory above it. A path that is neither a tracked file nor a directory is cut back to its last /. That covers a computed one such as docs/specs/AST-${id}/spec.md, and a record since renamed. A glob is cut at its first wildcard. So a computed prefix, a glob such as docs/specs/**/spec.md, or path.join(…, 'docs', 'specs', …) names the whole tree.
    • Today 23 tests read records: 1 UI (roles.test.ts) and 22 Node.

Authority

spec:AST-030 FR2: a surface uses the broad check set until it has an admitted, complete specialized owner route. A record that tests read is not completely owned by knowledge validation alone. The knowledge row's owners now name "the Vitest tests that read a changed record, directly or through a module they import", which completes the route; it adds no new policy. The FR1–FR2 Verification row adds the spec-reader cases. That needs an owner's /approve-spec at this head.

Change

  • .github/scripts/fs-guard-tests.mjs: specReaderTests(), uiTestRoots(), and the --spec-readers [--project ui|node] flags (the diff turns renames off in that mode), beside the existing guard lister and sharing its import walker.
  • .github/workflows/ci.yml: in test-ui and test-node, four steps on leg 1 when the scope is spec-only: check out the change, list that project's readers, set up (only when there are any), and run them with pnpm test:ui|test:node. There is no --passWithNoTests: the list holds that project's tests only, so a run that finds none fails the step.
  • .github/scripts/ci-test-routing.test.mjs: the lane-shape checks skip the new reader step, whose contract lives with the lister's.
  • docs/specs/AST-030/spec.md: FR2's knowledge owners, and the FR1–FR2 Verification row.

Evidence

  • fs-guard-tests.test.mjs:
    • Fixtures for each spelling: a relative record path, a directory read through a helper, a computed template, joined segments, family and design contracts, a theme-spec reader, a theme-source reader (not counted), a prose-only mention (not counted), and a path named as data with no file read (not counted).
    • Narrowing: a changed record, a directory above it, and a record nobody names, which still runs the whole-tree readers.
    • On this repository, a spec-only change to AST-094 lists roles.test.ts, and a change to AST-084 lists icon-set-checks.test.mjs.
    • A real git repository renames a record directory; the lister still lists the test that reads the old path. Dropping --no-renames fails it.
    • The project split is read from vitest.config.ts, and an unexpected config shape fails the lister. A real git repository runs --project ui and --project node, and each lists only its own reader; inverting the filter fails it.
    • A computed segment (docs/specs/AST-${n}/spec.md) is selected for any spec record; without the cut-back it is not.
    • Glob readers (docs/specs/**/spec.md, docs/specs/*/spec.md) are selected for any spec record. Stripping /**/ inside strings, or keeping the wildcard segment, fails them.
    • Workflow contract for both lanes, each listing its own project.
    • Mutation-checked: dropping relative-path support fails four of these.
  • spec(ContextMenu): touch-opened menus lift with a tint, grow from the finger, and use named springs #9423's actual change (AST-094 plus a component record) lists roles.test.ts among 6 tests.
  • All 23 readers pass locally without any package build (the Vitest global setup builds Core if a test needs it). The AST-094 set takes about 70 s on Node and 2 s on UI.
  • ci-test-routing, fs-guard-tests and the CI contract suites pass (461 tests).
  • CI impact: test-ui and test-node stay on standard GitHub-hosted runners, and no job is added. Only spec-only pull requests change; every other pull request and main are unchanged.
    • Job-minutes per spec-only update: test-node goes from about 1.2 to about 3.0 (leg 1: setup plus about 14 readers for a component record). test-ui goes from about 1.2 to about 1.35 (only the check-out and listing, unless a UI test reads the record).
    • Runner-pool usage: over the last 24 h, 106 of 828 pull-request CI runs were spec-only, and every one would have listed readers. That is about +205 runner-minutes a day, about +0.14 average concurrent jobs.
    • Time to green on a spec-only pull request: leg 1 of test-node takes about 2 minutes instead of about 15 s, so up to about +2 minutes.

Scope

  • One change: a spec-only pull request runs the tests that read the records it changes.
  • Public text contains no internal Meta context.

Writing rubric results

  • R1 narration: none. The FR2 owner and the Verification row are present tense.
  • R2 research: none.
  • R3 evidence: the Verification row names the state (a spec-only change to a record a test reads) and the failure (the test is skipped).
  • R4 rejected alternatives: none added; the row completes FR2's existing rule.

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 2, 2026 4:53am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 2, 2026
@astracat-bot

astracat-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 2, 2026, 5:04 AM UTC

@cixzhang cixzhang changed the title ci: a spec-only pull request runs the tests that read a record it changes ci: a spec-only pull request runs the tests that read a record it changes (AST-030 FR2) Nov 2, 2026
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 2, 2026
@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 3b8771f

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 2, 2026
@cixzhang
cixzhang marked this pull request as ready for review November 2, 2026 03:14
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 68d0d61

Storybook · Sandbox · built from this PR's head

Package size: no published package changed; no new dependencies.

API changes: none in this pull request

Committed API report behind this build: core 4 rows; run pnpm api:report.

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.

CI impact: +8.6 job-min per update · pool +13% · time to green −0.3 min · standard runners · 3 CI action or script files changed (not measured) ⚠️
Before After Change
Job-minutes per PR update 65.6 74.2 +8.6
Runner pool, runner-minutes a day 27,180 30,721 +3,542 (+13%)
Average concurrent jobs 18.9 21.3 +2.5
Time to green (median → this run) 15.7 min 15.4 min −0.3 min
Runs a day 414 414 0

Changed jobs: test-node changed: standard · test-ui changed: standard

Not measured (the jobs that use these keep their recent minutes above): .github/scripts/ci-test-routing.test.mjs, .github/scripts/fs-guard-tests.mjs, .github/scripts/fs-guard-tests.test.mjs.

Flags: runner-pool usage +13%.

Assumptions: before is each job's average over the last 20 pull-request runs; after keeps that for unchanged jobs and uses this run's minutes for added or changed ones, weighted by how often each changed job ran in those runs; runs a day averages the seven whole days before today; time to green compares this run with the median of those runs that ran the same jobs.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: the spec-reader lane is a good fit for AST-030 FR2, but it still misses some spec-only changes it claims to cover.

  • Spec-only changes to family, design and theme records list no readers, because specReaderTests keeps only docs/specs/ paths; extend it to every spec-only record path, or narrow FR2 and the description to docs/specs/.
  • git diff --name-only reports only the new name of a renamed record, so a rename of AST-094's directory drops roles.test.ts; use --no-renames in --spec-readers mode and add a rename case.
  • test-ui runs setup on every docs/specs/ change even when no UI test is listed; gate each leg on its own project's files, and add FR19's runner-pool usage to the CI impact line.

[Automated review]

Comment thread .github/scripts/fs-guard-tests.mjs Outdated
const changedSpecs =
changedFiles == null
? null
: changedFiles.filter(file => file.startsWith(SPEC_ROOT));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trusted classifier's spec-only lane also admits docs/families/*.md, docs/design/*.md and packages/themes/<t>/<t>.spec.md (change-scope.cjs:22-27). This filter drops them, so a family-only change lists no readers, even though scripts/check-knowledge.test.mjs reads those records through validateKnowledgeRoot(repoRoot). Either count every spec-only record path (and recognise those roots in SPEC_LITERAL), or narrow FR2's owner text to docs/specs/ records.

Comment thread .github/workflows/ci.yml Outdated
id: spec-readers
if: matrix.shard == 1 && steps.scope.outputs.spec_only == 'true'
run: |
node .github/scripts/fs-guard-tests.mjs --spec-readers --changed HEAD^1 > "$RUNNER_TEMP/spec-readers.txt"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git diff --name-only HEAD^1 HEAD (inside the lister) reports only the new name of a renamed record. Renaming docs/specs/AST-094-touch-presentations/ therefore lists five whole-tree readers but not roles.test.ts, which reads the old path and then fails on main. Passing --no-renames in --spec-readers mode lists both names.

Comment thread .github/workflows/ci.yml
fi

- uses: ./.github/actions/setup
if: steps.spec-readers.outputs.any == 'true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any covers both projects' files. Every docs/specs/ change lists the five whole-tree Node readers, so this leg runs setup and then pnpm test:ui finds no test, unless AST-094 changed. Gating on this project's own files (for example, the UI include roots under packages/{core,lab,charts,richtext,vega}/src/) avoids that setup.

… renamed record under both names, and run per Vitest project
@cixzhang
cixzhang marked this pull request as draft November 2, 2026 03:52
@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 0287643

@cixzhang
cixzhang marked this pull request as ready for review November 2, 2026 03:54

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: family, design, theme and renamed records now reach their readers, and the per-project split removes the idle UI setup, but two gaps remain in the lister.

  • No test runs --project: inverting the filter in fs-guard-tests.mjs keeps every case green, and both legs then find no test files and pass under --passWithNoTests, so add a CLI case per project and drop --passWithNoTests from the reader runs.
  • A computed prefix that ends inside a path segment, such as `docs/specs/AST-${id}/spec.md`, is never selected, although the description says computed prefixes count as the whole tree, so cut such prefixes back to the last / or narrow the wording.

[Automated review]

throw new Error(`--project must be ui or node, not ${project}`);
const roots = uiTestRoots(read('vitest.config.ts'));
const inUi = test => roots.some(root => test.startsWith(root));
tests = tests.filter(test => inUi(test) === (project === 'ui'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test runs this filter: the split case recomputes it inline, so flipping === to !== keeps all 32 lister cases green. With that flip, an AST-094 change sends the Node readers to test-ui and roles.test.ts to test-node, and both legs print "No test files found, exiting with code 0" under --passWithNoTests. A CLI contract case per project (as the rename case does), plus dropping --passWithNoTests from the reader runs, would catch it.

Comment on lines +321 to +322
changed === named ||
changed.startsWith(named.endsWith('/') ? named : `${named}/`),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A computed prefix that ends inside a path segment, such as `docs/specs/AST-${id}/spec.md` or 'docs/specs/AST-' + id, names docs/specs/AST-. Appending / here means it matches no record, so that reader is listed with no --changed but never selected for a change. Cutting a truncated literal back to its last / (or matching changed.startsWith(named) for computed prefixes) would keep the description's whole-tree rule.

… CLI case, and a computed prefix inside a segment names its directory
@cixzhang
cixzhang marked this pull request as draft November 2, 2026 04:22
@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 2849fa8

@cixzhang
cixzhang marked this pull request as ready for review November 2, 2026 04:22

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: the spec-reader lane now covers every spec-only record kind, renames and the per-project split with mutation-sensitive cases, but one spelling of a record reader is still never run.

  • A test that reads records through a directory glob, such as fs.globSync('docs/specs/**/spec.md') or 'docs/specs/*/spec.md', is never selected for a changed record, because code() treats the /**/ in the string as a comment and the cut-back keeps the * segment; strip only comments that start a line or follow whitespace, cut a literal at its first glob character, and add a glob fixture.

[Automated review]

Comment thread .github/scripts/fs-guard-tests.mjs Outdated

/** The source without block comments and whole-line `//` comments, where prose names records. */
function code(source) {
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also strips /**/ inside string literals, so a reader written fs.globSync('docs/specs/**/spec.md') becomes 'docs/specsspec.md' and is never listed. Stripping only block comments that start a line or follow whitespace keeps it: source.replace(/(^|\s)\/\*[\s\S]*?\*\//g, '$1').

Comment thread .github/scripts/fs-guard-tests.mjs Outdated
const namesSpecFile = /\.spec\.md['"`]/.test(body);
const counts = root => root !== 'packages/themes/' || namesSpecFile;
for (const [, , literal] of body.matchAll(RECORD_LITERAL)) {
const fixed = literal.split('${')[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A glob literal such as 'docs/specs/*/spec.md' is cut back to docs/specs/*/, which no changed path starts with. Cutting at the first glob character too (literal.split(/\$\{|[*?[{]/)[0]) names docs/specs/; a ** and a */ glob fixture would pin it.

@cixzhang
cixzhang marked this pull request as draft November 2, 2026 04:46
@cixzhang

cixzhang commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 68d0d61

@cixzhang
cixzhang marked this pull request as ready for review November 2, 2026 04:48

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval: readers that name records through a ** or * glob are now selected and pinned by fixtures, and the spec-reader steps run each project's readers of a changed record under the AST-030 FR2 amendment an engineering owner approved at this head. Two optional description fixes: #9423's change lists roles.test.ts among 16 tests rather than 6, and the reader lister shares the guard lister's resolver and patterns but not its traversal.

[Automated review]

@cixzhang
cixzhang merged commit 358c2c5 into main Nov 2, 2026
140 checks passed
@github-actions
github-actions Bot deleted the ci/spec-readers-run-on-spec-changes branch November 2, 2026 06:05

This branch was successfully deployed

1 active deployment
Preview — 68d0d61e Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant