Repository navigation
Description
[loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests that reach the names it touched
Tier: 1
Project: cli
Type: perf
Objectives
O1. A diff hunk that falls inside a named export statement (export { a, b } from '…' or export { a, b }) is attributed to the exported names written on the hunk's lines, by their exported name (the as alias when one is given), instead of making the whole file affected; a hunk on the statement's export {, } from '…' or module-specifier lines, or on an export * statement, still makes the whole file affected; tests in the changed-name attribution (O3) block of apps/cli/tests/lib/test-selector.test.ts assert each case.
O2. A diff that only adds one name to a named export list of a package's barrel file selects no test file through that barrel, apart from tests the diff itself adds and prePush.alwaysRun, since no test imports a name that did not exist; a test builds a fixture package whose barrel gains one name and asserts the selection.
O3. The self-hosting specification's paragraph on name-level selection states the named-export rule and the cases that still affect the whole file.
Documentation
apps/cli/src/lib/changed-names.ts—namesForRangesand its whole-file fallbacks (O1)apps/cli/src/lib/test-selector.ts—selectAffectedTestFilesand itsaffectedNamesoption (O2)apps/cli/specs/self-hosting.md— "Selection follows the changed exported NAMES, not the changed file" (O3)- https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API — the compiler API's
ExportDeclarationandNamedExportsnodes (O1)
Planner's rationale
Boundary — The pre-push selector follows changed exported names, but namesForRanges gives up on any hunk inside an export statement and makes the whole file affected. On a package barrel that is most of the suite: the one-line change that added DEFAULT_READY_PUSH_TIMEOUT_MINUTES to the aeg-core package barrel's export list, run alone through affectedNames and selectAffectedTestFiles against the default branch, attributes all to the file and selects 100 of 537 test files. This task attributes a hunk inside a named export list to the specifiers on its lines. Pinned files: apps/cli/src/lib/changed-names.ts, apps/cli/tests/lib/test-selector.test.ts, apps/cli/specs/self-hosting.md, .changeset/selector-named-exports.md. Out: the selector's graph and its depth rule; export * and export default, which keep the whole-file answer; the other changed files of a large diff, whose own selection is unchanged; every importer of a pinned file, which calls it unchanged and is in the Surface only because the importer gate requires it.
Sizing — One verification story: a named-export hunk narrows to its names and nothing else narrows. Four pinned files; one failure mode. Size S–M.
Project(s) + blast radius — Project: cli. changed-names.ts feeds the pre-push hook's selection and the pull request's CI selection: re-verification only, since only hunks inside named export lists change from the whole file to a set of names. blast-radius-ack: packages/aeg-core/src is in the Surface only because its tests read the pinned specification; no aeg-core file is edited.
Dependency rationale — No Depends-on; no Conflicts-with: the Issue write's collision check reports no open task Issue or pull request sharing three of these files.
Traps to avoid — Do NOT narrow when a hunk touches the braces, the from clause or the specifier string: a changed module path re-points every name. A removed specifier is attributed on the diff's old side, as deletions already are. Never narrow an export *. Do NOT add a text or regular-expression reading of the source; use the compiler nodes the module already parses. Test fixtures use fixtureDir from apps/cli/tests/lib/test-temp-fixture.ts. Keep new comments free of Issue and pull-request numbers and tranche slugs. A new test file under apps/cli/tests must be listed in one apps/cli/tests/ci-shards/shard-N.txt in the same commit.
Suggested agent-class — mid: one attribution rule over the TypeScript syntax tree, with fixture tests for each edge.
Stop-and-escalate — Stop if an existing expectation in changed-name attribution (O3) or selection follows the changed names (in apps/cli/tests/lib/test-selector.test.ts) must change for any shape other than a named export list; name the test.
Docs to keep coherent — apps/cli/specs/self-hosting.md: the name-level selection paragraph and its list of whole-file cases.
Check decisions
changeset-coverage: add the pinned changeset.doc-claims: every AEG:CLAIM line in a changed file is kept true in the same commit.reader-resolvable-prose: new comments and spec text carry no Issue or pull-request numbers and no tranche slugs.
Premises
apps/cli/src/lib/changed-names.tscontainsts.isFunctionDeclaration(statement) ||apps/cli/src/lib/changed-names.tscontainsif (d.name === null) return nullapps/cli/src/lib/test-selector.tscontainsaffectedNames?: ReadonlyMap<string, ReadonlySet<string> | 'all'>
Surface
in: apps/cli/src/lib, apps/cli/src, apps/cli/tests, apps/cli/specs, packages/aeg-core/src, .changeset
out: harness, processes, aeg-root, .github, apps/log-server, apps/vinaya-portal
Parts
Part 1 (O1) — namesForRanges attributes a hunk inside a named export list to the specifiers on its lines.
Part 2 (O2) — A barrel that gains one export name selects no test through that barrel.
Part 3 (O3) — The self-hosting specification states the named-export rule.
Test plan
cd apps/cli && bun test --timeout=30000 tests/lib/test-selector.test.ts → 0 fail; named-export hunks narrow to their names, braces and export * stay whole-file, a one-name barrel addition selects nothing through the barrel
Stop conditions
- An existing expectation in the selector's changed-name attribution or changed-name selection tests must change for a shape other than a named export list.
Origin
Principal-directed, 2026-10-29: two approved version-2 tasks timed out at the ready push after selecting 129 and 140 of 537 test files; one of their changes was a single added name in a package barrel's export list.
Activity
daniboomerang commentedon Oct 29, 2026
Brief hash: 4b867c9a7e86ae0f90ac53043dc4681c3dfb0f7de78b3d92c22ab9b34514b61c
For: [model] (coding-agent CLI on a dev machine, dispatched locally, unattended)
Reason: Suggested agent-class — mid: one attribution rule over the TypeScript syntax tree, with fixture tests for each edge.
Owner: the Principal
Goal: A change to a named export list selects only the tests reaching the touched names
Project: cli
Tier: 1
Closes #1741
You are the AEG Developer. Run
bun apps/cli/src/index.ts doctrine --role developer --printand read its output first. Mandatory.Objectives
O1. A diff hunk that falls inside a named export statement (
export { a, b } from '…'orexport { a, b }) is attributed to the exported names written on the hunk's lines, by their exported name (theasalias when one is given), instead of making the whole file affected; a hunk on the statement'sexport {,} from '…'or module-specifier lines, or on anexport *statement, still makes the whole file affected; tests in thechanged-name attribution (O3)block ofapps/cli/tests/lib/test-selector.test.tsassert each case.O2. A diff that only adds one name to a named export list of a package's barrel file selects no test file through that barrel, apart from tests the diff itself adds and
prePush.alwaysRun, since no test imports a name that did not exist; a test builds a fixture package whose barrel gains one name and asserts the selection.O3. The self-hosting specification's paragraph on name-level selection states the named-export rule and the cases that still affect the whole file.
Documentation
apps/cli/src/lib/changed-names.ts—namesForRangesand its whole-file fallbacks (O1)apps/cli/src/lib/test-selector.ts—selectAffectedTestFilesand itsaffectedNamesoption (O2)apps/cli/specs/self-hosting.md— "Selection follows the changed exported NAMES, not the changed file" (O3)ExportDeclarationandNamedExportsnodes (O1)Premises
Checked against the code when this task Issue was cut, and again against the default branch at dispatch — facts, not claims.
apps/cli/src/lib/changed-names.tscontainsts.isFunctionDeclaration(statement) ||apps/cli/src/lib/changed-names.tscontainsif (d.name === null) return nullapps/cli/src/lib/test-selector.tscontainsaffectedNames?: ReadonlyMap<string, ReadonlySet<string> | 'all'>2. Context — read before doing anything
loop-v2-hardening-v1, task 5, Issue [loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests reaching the touched names #1741. Branchtask/loop-v2-hardening-v1/5.Depends-on: —,Conflicts-with: —.cbb621d0e59f2934e08de95039cb94e782d68379— the checkout's HEAD equaled the remote default branch, and no pinned file below carried an uncommitted change, when these facts were read.namesForRangesgives up on any hunk inside anexportstatement and makes the whole file affected. On a package barrel that is most of the suite: the one-line change that addedDEFAULT_READY_PUSH_TIMEOUT_MINUTESto the aeg-core package barrel's export list, run alone throughaffectedNamesandselectAffectedTestFilesagainst the default branch, attributesallto the file and selects 100 of 537 test files. This task attributes a hunk inside a named export list to the specifiers on its lines. Pinned files:apps/cli/src/lib/changed-names.ts,apps/cli/tests/lib/test-selector.test.ts,apps/cli/specs/self-hosting.md,.changeset/selector-named-exports.md. Out: the selector's graph and its depth rule;export *andexport default, which keep the whole-file answer; the other changed files of a large diff, whose own selection is unchanged; every importer of a pinned file, which calls it unchanged and is in the Surface only because the importer gate requires it.fromclause or the specifier string: a changed module path re-points every name. A removed specifier is attributed on the diff's old side, as deletions already are. Never narrow anexport *. Do NOT add a text or regular-expression reading of the source; use the compiler nodes the module already parses. Test fixtures usefixtureDirfromapps/cli/tests/lib/test-temp-fixture.ts. Keep new comments free of Issue and pull-request numbers and tranche slugs. A new test file underapps/cli/testsmust be listed in oneapps/cli/tests/ci-shards/shard-N.txtin the same commit.3. Technical dependencies
Dependency rationale — No Depends-on; no Conflicts-with: the Issue write's collision check reports no open task Issue or pull request sharing three of these files.
4. Technical surface map
Create:
Modify:
apps/cli/src/lib
apps/cli/src
apps/cli/tests
apps/cli/specs
packages/aeg-core/src
.changeset
consumer-tests: none — no consumer test path named yet for @attalabs/aeg-core (apps/log-server), @attalabs/aeg-core (packages/sources), @attalabs/aeg-core (processes/vinaya); name one before dispatch, or confirm no consumer-facing behavior changed.
consumer-tests: apps/cli/tests (covers @attalabs/aeg-core)
Out of surface: harness, processes, aeg-root, .github, apps/log-server, apps/vinaya-portal — a change there, or to any path no Surface
in:glob covers, is still published: the review-loop driver records each such path in the pull request body's beyond-Surface block, and the reviewers judge it like any change.Premise pins
Premise:
5. Pre-flight checks
Step 0 (mandatory, verbatim):
origin/main; branch suffix literal-matches the task id.bun apps/cli/src/index.ts task run), the driver has already run the dispatch-readiness gate plus, on this repository toolchain, its own fuller derivation from its own unsandboxed process before dispatching you (re-derived at render time: it was ready), and dispatches you only when that verdict is READY — a NOT READY verdict refuses the turn before it starts, so being dispatched is the verdict: look for no file and run neither script, whose ownghcall fails inside your sandbox. Working manually, with no driver behind you, fall back toroles/developer.md's own items 3/5/7 prose — each a complete, hand-run forge check in its own right.On any failure: STOP and report.
6. Numbered parts — publish once with
publish_changeswhen every Part is done, thenopen_pull_requestPart 1 (O1):
namesForRangesattributes a hunk inside a named export list to the specifiers on its lines.Files (touches @attalabs/vinaya):
The pre-push hook runs the affected suite on your one push and refuses it on failure — do not run it yourself per Part.
Part 2 (O2): A barrel that gains one export name selects no test through that barrel.
Part 3 (O3): The self-hosting specification states the named-export rule.
7. Documentation-update list
.vinaya/doc-ownersderivation matched zero bindings against this task's surface — no mechanically-derived doc updates. Confirm no additional doc artifact applies before treating this list as final.8. Verification before claiming done
run_checksand CI provide the authoritative results. Do not hand-run or paste static-gate commands. Working manually, run this repository's declared typecheck, lint/format, build, and documentation-gate commands yourself.bun apps/cli/src/index.ts doctrine --role developer --printgenuinely satisfied; use the controller'srun_checksresult, hooks, and CI as gate evidence.9. Test Plan
10. Stop conditions
Stop-and-escalate — Stop if an existing expectation in
changed-name attribution (O3)orselection follows the changed names(inapps/cli/tests/lib/test-selector.test.ts) must change for any shape other than a named export list; name the test.11. Constraints
12. Deliverable
[loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests reaching the touched namesopen_pull_requesttool (title and body); publish your commits withpublish_changesand update the body withupdate_pull_request_body/refresh_evidence— you hold nogh/git pushcredential.publish_changes, when every Part is done; then open the pull request withopen_pull_request. Publish again only to answer review findings or a red gate — a refused hook, a failing check, a merge conflict.bun apps/cli/src/index.ts doctrine --template pr-report --print), withCloses #1741at the top of the header block. This brief stays frozen on the task Issue and never rides in the body.run_checksand CI run it.git diff origin/main...HEAD --statas context only; Surface is judged by the driver'ssurface-scopecheck, never by this hand-run diff. Token use needs no report — the Vinaya log records it.