⚠️ 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
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
Context
scripts/check-turbo-typecheck-inputs.tsrecomputes what//#typecheckmust hash, so turbo'sinputslistcan 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 --noEmitwould fail). It finds cross-workspace reach with this regex:import\s+"requires whitespace between the keyword and the quote, so a dynamicimport("...")— wherethe next character is
(— never matches. Its sibling checker gets this right:scripts/check-dead-source-files.ts:167uses/(?:from|import)\s*\(?\s*"..."/, which accepts both forms. Thetwo checkers disagree about what an import is.
That is not theoretical.
test/unit/miner-cli.test.tsreaches into the miner package by dynamic import:The root tsconfig sets
"resolveJsonModule": true, sopackages/loopover-miner/package.jsonis genuinely ontsc's surface (the test readspackageJson.default.versionand it type-checks). Butturbo.json's//#typecheckinputslistspackages/loopover-mcp/package.jsonand notpackages/loopover-miner/package.json, anddependsOnis only["@loopover/engine#build", "@loopover/contract#build"], so the miner workspace is not covered that wayeither. Editing
packages/loopover-miner/package.json— a version bump, a dependency change — can thereforeleave a stale cache HIT on
//#typecheck.That the mcp one IS listed is the proof of the mechanism:
src/services/mcp-compatibility.ts:6and severaltests reach it with a static
import ... from "../../packages/loopover-mcp/package.json", which the regexsees. The only difference between the two is the import syntax.
Confirm the gap by running the checker's own
collectCrossBoundaryReachwith the sibling's wider regex: theresult gains exactly one entry,
packages/loopover-miner/package.json, imported bytest/unit/miner-cli.test.ts.Requirements
collectCrossBoundaryReachmust match dynamicimport("...")in addition tofrom "..."and bareimport "...", including the multi-line form used attest/unit/miner-cli.test.ts:103-106(whitespace and newlines betweenimport,(and the opening quote).turbo.json's//#typecheckinputsmust gain"packages/loopover-miner/package.json", so the check isgreen after the regex is widened. Both changes must land together; widening the regex alone makes
npm run turbo-inputs:checkred insidetest:ci.(
scripts/check-turbo-typecheck-inputs.ts:88-92) must be preserved — it is what keeps the checker-testingfiles' fixture-string imports out of the result, and the widened regex will see more of those strings, not
fewer.
findUnhashedReachandcoveredWorkspacesFromDependsOnmust not change.inputsentry may be removed, and nodependsOnentry may be added — adding@loopover/miner#buildwould change turbo's scheduling graph, which is a different decision from hashing amanifest file.
npm run turbo-inputs:checkmust exit 0 on the resulting tree.Deliverables
collectCrossBoundaryReachinscripts/check-turbo-typecheck-inputs.tsreturns an entry forpackages/loopover-miner/package.jsonwhen run against the real tree, withimportedBytest/unit/miner-cli.test.ts.turbo.json's//#typecheckinputsarray contains"packages/loopover-miner/package.json".npm run turbo-inputs:checkexits 0.test/unit/check-turbo-typecheck-inputs.test.tsnamed for this bugthat feeds a synthetic file whose only cross-boundary reference is
await import(\n "../../packages/loopover-miner/package.json",\n { with: { type: "json" } }\n)andasserts the resolved reach contains
packages/loopover-miner/package.json— plus a companion assertionthat a file whose only match is a static
from "../../packages/loopover-miner/lib/version"stillresolves, 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.jsonentry 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'scoverage.includecovers
src/**/*.ts,packages/loopover-engine/src/**/*.ts,packages/loopover-{miner,mcp}/{lib,bin}/**/*.ts,packages/loopover-contract/src/**/*.tsandpackages/discovery-index/src/**/*.ts. It does not coverscripts/**, andcodecov.yml'signore:list namesscripts/**explicitly;turbo.jsonis not source atall. 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"]) runstest/unit/check-turbo-typecheck-inputs.test.tson 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 "...", bareimport "..."); thepackages|appssegment match must be exercised for a specifier that hits (../../packages/x/y) and one thatmisses (
../../some/other/path); and theexistsSyncfilter must be exercised for a path that exists and onethat 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:checksees dynamic imports, so it computestsc's real cross-workspace surface ratherthan only the statically-spelled part of it — and the one path that gap was already hiding,
packages/loopover-miner/package.json, is hashed by//#typecheckso editing it can no longer leave a stalecache HIT.
Links & Resources
scripts/check-turbo-typecheck-inputs.ts:79-81— the regex that misses dynamic importsscripts/check-turbo-typecheck-inputs.ts:88-92— the on-disk existence filter that must be preservedscripts/check-dead-source-files.ts:167— the sibling pattern to mirrortest/unit/miner-cli.test.ts:102-106— the live dynamic cross-boundary importsrc/services/mcp-compatibility.ts:6— the static equivalent that IS detected and IS listed inturbo.jsonturbo.json//#typecheck— theinputs/dependsOnthis checker verifies