Skip to content

[loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests reaching the touched names #1741

Closed

Description

@daniboomerang

[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 — namesForRanges and its whole-file fallbacks (O1)
  • apps/cli/src/lib/test-selector.ts — selectAffectedTestFiles and its affectedNames option (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 ExportDeclaration and NamedExports nodes (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.ts contains ts.isFunctionDeclaration(statement) ||
  • apps/cli/src/lib/changed-names.ts contains if (d.name === null) return null
  • apps/cli/src/lib/test-selector.ts contains affectedNames?: 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

  1. daniboomerang commented on Oct 29, 2026

    @daniboomerang
    MemberAuthor

    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 --print and read its output first. Mandatory.

    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 — namesForRanges and its whole-file fallbacks (O1)
    • apps/cli/src/lib/test-selector.ts — selectAffectedTestFiles and its affectedNames option (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 ExportDeclaration and NamedExports nodes (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.ts contains ts.isFunctionDeclaration(statement) ||
    • apps/cli/src/lib/changed-names.ts contains if (d.name === null) return null
    • apps/cli/src/lib/test-selector.ts contains affectedNames?: ReadonlyMap<string, ReadonlySet<string> | 'all'>

    2. Context — read before doing anything

    • Tranche: 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. Branch task/loop-v2-hardening-v1/5. Depends-on: —, Conflicts-with: —.
    • Read Issue [loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests reaching the touched names #1741 in full for the complete rationale — do not re-derive it.
    • Revision: rendered at cbb621d0e59f2934e08de95039cb94e782d68379 — the checkout's HEAD equaled the remote default branch, and no pinned file below carried an uncommitted change, when these facts were read.
    • 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.
    • 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.

    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:

    • (none — every surface file already exists)

    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:

    • apps/cli/specs/self-hosting.md sha256: 261677bfc53dc0a99b2a4ce60f6236865f2e359fd5175a92f20be516c740df3b
    • apps/cli/src/lib/changed-names.ts sha256: 6680ddfa37a8219fde1c48aa8c107fb5020a63b44747fc687c6ea3d980f7c2bb
    • apps/cli/tests/lib/test-selector.test.ts sha256: d1e907589c560680d3d808867f88c9bf63dc5ce886c8ab01682e3a59f30e2d8b

    5. Pre-flight checks

    Step 0 (mandatory, verbatim):

    bun install --frozen-lockfile --silent
    
    1. Clean status; parent origin/main; branch suffix literal-matches the task id.
    2. Under the review loop (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 own gh call fails inside your sandbox. Working manually, with no driver behind you, fall back to roles/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_changes when every Part is done, then open_pull_request

    1. Part 1 (O1): namesForRanges attributes a hunk inside a named export list to the specifiers on its lines.

      Files (touches @attalabs/vinaya):

      • apps/cli/specs/self-hosting.md
      • apps/cli/src/lib/changed-names.ts
      • apps/cli/tests/lib/test-selector.test.ts

      The pre-push hook runs the affected suite on your one push and refuses it on failure — do not run it yourself per Part.

    2. Part 2 (O2): A barrel that gains one export name selects no test through that barrel.

    3. Part 3 (O3): The self-hosting specification states the named-export rule.

    7. Documentation-update list

    .vinaya/doc-owners derivation 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

    • Under the review loop, the controller runs the static gates: the commit hook applies safe format and lint fixes to staged files, typechecks affected packages, and runs the Vinaya checks; the pre-push hook runs the affected suite; run_checks and 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.
    • The pre-push hook runs the affected suite once, on your one push, and refuses it on failure; CI runs the full suite.
    • Every blast-radius consumer named in §4, re-verified by name.
    • The tier checklist in bun apps/cli/src/index.ts doctrine --role developer --print genuinely satisfied; use the controller's run_checks result, hooks, and CI as gate evidence.

    9. 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
    

    10. 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.

    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.

    11. Constraints

    Autonomy: Do not stop to ask clarifying questions. For any ambiguity not covered by a Section 10 stop condition, choose the most reasonable option consistent with this brief, record the choice in the PR body at open, or in a PR comment after open, and continue. Halt only for the explicit Section 10 stop conditions — and when you halt, record the blocker in a PR comment or an Issue comment rather than waiting interactively for input.

    12. Deliverable

    • PR title (exact): [loop-v2-hardening-v1] 5 — A change to a named export list selects only the tests reaching the touched names
    • Open the PR only via the open_pull_request tool (title and body); publish your commits with publish_changes and update the body with update_pull_request_body/refresh_evidence — you hold no gh/git push credential.
    • Publish once, with publish_changes, when every Part is done; then open the pull request with open_pull_request. Publish again only to answer review findings or a red gate — a refused hook, a failing check, a merge conflict.
    • PR body = the Developer's PR report (print it with bun apps/cli/src/index.ts doctrine --template pr-report --print), with Closes #1741 at the top of the header block. This brief stays frozen on the task Issue and never rides in the body.
    • Pre-open: tier checklist satisfied. The documentation gate is the controller's — run_checks and CI run it.
    • Include git diff origin/main...HEAD --stat as context only; Surface is judged by the driver's surface-scope check, never by this hand-run diff. Token use needs no report — the Vinaya log records it.
    • Then STOP. Review and Verification are separate invocations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions