Skip to content

scripts(checks): check-turbo-typecheck-inputs misses dynamic import(...), leaving packages/loopover-miner/package.json unhashed #10046

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

scripts/check-turbo-typecheck-inputs.ts recomputes what //#typecheck must hash, so turbo's inputs list
can never go quietly stale — the exact class of bug PR #5082 burned this repo on (a stale cache HIT on a
typecheck a real tsc --noEmit would fail). It finds cross-workspace reach with this regex:

// scripts/check-turbo-typecheck-inputs.ts:79-81
    // Both `from "..."` and bare `import "..."`, since a side-effect import is type-checked too.
    for (const match of source.matchAll(/(?:from|import)\s+"((?:\.\.\/)+[^"]+)"/g)) {

import\s+" requires whitespace between the keyword and the quote, so a dynamic import("...") — where
the next character is ( — never matches. Its sibling checker gets this right:
scripts/check-dead-source-files.ts:167 uses /(?:from|import)\s*\(?\s*"..."/, which accepts both forms. The
two checkers disagree about what an import is.

That is not theoretical. test/unit/miner-cli.test.ts reaches into the miner package by dynamic import:

// test/unit/miner-cli.test.ts:102-106
  it("keeps the CLI version source aligned with package metadata", async () => {
    const packageJson = await import(
      "../../packages/loopover-miner/package.json",
      { with: { type: "json" } }
    );

The root tsconfig sets "resolveJsonModule": true, so packages/loopover-miner/package.json is genuinely on
tsc's surface (the test reads packageJson.default.version and it type-checks). But
turbo.json's //#typecheck inputs lists packages/loopover-mcp/package.json and not
packages/loopover-miner/package.json, and dependsOn is only
["@loopover/engine#build", "@loopover/contract#build"], so the miner workspace is not covered that way
either. Editing packages/loopover-miner/package.json — a version bump, a dependency change — can therefore
leave a stale cache HIT on //#typecheck.

That the mcp one IS listed is the proof of the mechanism: src/services/mcp-compatibility.ts:6 and several
tests reach it with a static import ... from "../../packages/loopover-mcp/package.json", which the regex
sees. The only difference between the two is the import syntax.

Confirm the gap by running the checker's own collectCrossBoundaryReach with the sibling's wider regex: the
result gains exactly one entry, packages/loopover-miner/package.json, imported by
test/unit/miner-cli.test.ts.

Requirements

  • The specifier regex in collectCrossBoundaryReach must match dynamic import("...") in addition to
    from "..." and bare import "...", including the multi-line form used at
    test/unit/miner-cli.test.ts:103-106 (whitespace and newlines between import, ( and the opening quote).
  • turbo.json's //#typecheck inputs must gain "packages/loopover-miner/package.json", so the check is
    green after the regex is widened. Both changes must land together; widening the regex alone makes
    npm run turbo-inputs:check red inside test:ci.
  • The existing filter that requires the resolved path to exist on disk
    (scripts/check-turbo-typecheck-inputs.ts:88-92) must be preserved — it is what keeps the checker-testing
    files' fixture-string imports out of the result, and the widened regex will see more of those strings, not
    fewer.
  • findUnhashedReach and coveredWorkspacesFromDependsOn must not change.
  • No existing inputs entry may be removed, and no dependsOn entry may be added — adding
    @loopover/miner#build would change turbo's scheduling graph, which is a different decision from hashing a
    manifest file.
  • npm run turbo-inputs:check must exit 0 on the resulting tree.

⚠️ Required pattern: mirror scripts/check-dead-source-files.ts:167's specifier pattern
(/(?:from|import)\s*\(?\s*"((?:\.{1,2}\/)[^"]+?)..."/), which already handles all three forms, rather than
inventing a fourth spelling. What does NOT satisfy this issue: (a) adding
"packages/loopover-miner/package.json" to turbo.json without fixing the regex — the checker still cannot
see the next dynamic cross-boundary import and the "compute the reach instead of remembering it" guarantee
stays broken; (b) replacing the regex with a full TypeScript program / AST parse, a repo-wide blast-radius
change this file's own header explicitly rejects; (c) broadening the pattern so far that it matches
import( inside a comment or a import("...") type reference (e.g.
src/types.ts:880's import("../services/ai-review").CombineStrategy) and reports paths outside
packages//apps/ — the existing packages|apps segment match and on-disk existence check must still
filter those; (d) a test-only PR.

Deliverables

  • collectCrossBoundaryReach in scripts/check-turbo-typecheck-inputs.ts returns an entry for
    packages/loopover-miner/package.json when run against the real tree, with importedBy
    test/unit/miner-cli.test.ts.
  • turbo.json's //#typecheck inputs array contains "packages/loopover-miner/package.json".
  • npm run turbo-inputs:check exits 0.
  • A regression test in the existing test/unit/check-turbo-typecheck-inputs.test.ts named for this bug
    that feeds a synthetic file whose only cross-boundary reference is
    await import(\n "../../packages/loopover-miner/package.json",\n { with: { type: "json" } }\n) and
    asserts the resolved reach contains packages/loopover-miner/package.json — plus a companion assertion
    that a file whose only match is a static from "../../packages/loopover-miner/lib/version" still
    resolves, so the widening did not break the original form.

All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example one
that adds the turbo.json entry and calls it done, leaving the detector blind — does not resolve this issue.

Test Coverage Requirements

This repo enforces 99%+ Codecov patch coverage, branch-counted. vitest.config.ts's coverage.include
covers src/**/*.ts, packages/loopover-engine/src/**/*.ts, packages/loopover-{miner,mcp}/{lib,bin}/**/*.ts,
packages/loopover-contract/src/**/*.ts and packages/discovery-index/src/**/*.ts. It does not cover
scripts/**, and codecov.yml's ignore: list names scripts/** explicitly; turbo.json is not source at
all. Codecov does not gate the patch on this change.

The test is still mandatory and still gating: the root vitest suite (include: ["test/**/*.test.ts"]) runs
test/unit/check-turbo-typecheck-inputs.test.ts on every PR and a failure there is a red required check.

Both arms of every branch the change touches must be asserted in that file: the widened alternation must be
exercised for the dynamic form AND for each pre-existing form (from "...", bare import "..."); the
packages|apps segment match must be exercised for a specifier that hits (../../packages/x/y) and one that
misses (../../some/other/path); and the existsSync filter must be exercised for a path that exists and one
that does not (a fixture-string import naming a package that was renamed away), since the widened regex sees
more of those strings.

Expected Outcome

npm run turbo-inputs:check sees dynamic imports, so it computes tsc's real cross-workspace surface rather
than only the statically-spelled part of it — and the one path that gap was already hiding,
packages/loopover-miner/package.json, is hashed by //#typecheck so editing it can no longer leave a stale
cache HIT.

Links & Resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions