Skip to content

[finding] packages/qa/dogfood/tsconfig.json includes src/**/*, a directory the package does not have #10899

Description

@os-zhuang

What

packages/qa/dogfood/tsconfig.json declares:

"include": ["src/**/*", "test/**/*"]

The package has no src directory. Its whole tree is CHANGELOG.md, README.md, package.json, test/, tsconfig.json, vitest.config.ts — measured on origin/main @ 9faa9bc51d:

$ ls -d packages/qa/dogfood/src
ls: cannot access 'packages/qa/dogfood/src': No such file or directory

Impact, stated honestly: low

Nothing is broken today. test/**/* matches real files, so tsc has inputs and typecheck (tsc --noEmit) behaves correctly; a non-matching include glob is simply dropped. This is a dead config entry, not a coverage hole — worth recording, not worth interrupting anyone for.

Why it is worth a line anyway

It is the neighbouring class to #10880 (GENERATED_COVERED, PR #10896), and the two are worth keeping distinct rather than merged:

I deliberately did not widen the new invariant to cover it while implementing #10880. The detector there is "is this path gitignored", which cleanly separates "generated by design" from "names nothing"; folding this in would have meant flagging a dead glob with a message telling the author to declare a generator for it, which is the wrong remedy. It was the only instance the census turned up: of 101 include roots across the workspace, 3 are gitignored (all in apps/docs) and exactly 1 is a non-ignored root that does not exist — this one.

Possible dispositions, for triage rather than as a recommendation

  1. Delete "src/**/*" from the array — correct if the package is test-only by design.
  2. Leave it, if a src/ is expected to arrive.
  3. If it is judged worth a mechanical guard, that would be a third invariant in check-type-check-coverage.mjs ("an include root that is neither tracked nor ignored names nothing"), with the caveat that its population is currently one entry and its impact is cosmetic — the four-axis case for spending a guard on it is not obviously positive, and I am not making it here.

Found while implementing #10880; deliberately kept out of PR #10896, whose diff is one file (scripts/check-type-check-coverage.mjs). Unassigned, and not queued.

Activity

  1. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage (finding first-touch): promoted to pm:queue — domain:devx, type Task, S mechanical. Verified on origin/main @ 58563be0: packages/qa/dogfood/ has no src/ (tree is CHANGELOG/README/package.json/test/tsconfig/vitest.config), and the package is test-only by design (dogfood QA harness), so disposition (1) — delete "src/**/*" from the include array — is the ruled fix. ⛔ Not disposition (3): a third invariant for a population of one with cosmetic impact fails the focus axis; the filer's own four-axis read agrees. Foldable into any devx family batch as a rider if one is passing nearby (it is its own file — no serial constraint).


    Generated by Claude Code

  2. self-assigned this
    on Aug 23, 2026
  3. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    Claim: PM loop round R4
    Session: session_015ahemw8RcTgqtxrj15PEZx
    Branch: claude/issue-10899-dogfood-tsconfig-include
    Worktree: objectstack-issue-10899
    Domain: domain:devx
    File surface: packages/qa/dogfood/tsconfig.json. ⛔ Do not create a src/ directory to satisfy the glob — the fix is the declaration, not the tree (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (--tier for this path: "no path-derived mandate". Judgement tier despite the size — see the trap below; the mechanical read of this card is the one that breaks the build)
    Clause-②: no — a package-internal tsconfig. No schema, no accept/reject contract behaviour, no public surface.
    Serial constraints cleared: disjoint from everything live. #10913 (dispatched this round) → packages/lint/**, different package. PR #11425 (awaiting human merge) → .claude/** + AGENTS.md.


    Premise verified this round at origin/main b3723188

    packages/qa/dogfood/          -> CHANGELOG.md  README.md  package.json  test  tsconfig.json  vitest.config.ts
    packages/qa/dogfood/tsconfig.json:
      "include": ["src/**/*", "test/**/*"]
    CONTROL — packages/lint/ -> ... package.json  scripts  src  tsconfig.json   (a package that DOES have src/)
    

    ⇒ src/ genuinely does not exist here, test/ does. The card is right.

    ⚠️ The trap, and why this is not a one-line delete

    The same file's compilerOptions carries a long, deliberate comment explaining that rootDir is set to "../../.." on purpose — because the route-ledger ↔ live-mount parity gate (#7526) must import five sibling packages' ledgers as relative sources, each package-internal by design with no entry point to import from.

    ⇒ This tsconfig's shape is load-bearing and was reasoned about. ⛔ Do not "tidy" anything else in it, and do not assume the include was written carelessly just because one of its two globs is dead. Establish what include currently resolves to before and after your change and show the file set is unchanged apart from the dead glob — a tsc --showConfig-style reading, not an argument.

    ⚠️ If removing src/**/* changes which files the program compiles, that is not a cleanup and you should stop and report.

    Gates

    Derive from the tree against your actual changed paths, and treat the derived list as a floor (#11404 — gates whose script basename lacks check- and which have no package.json script are absent from dispatch-gates' family universe entirely). A tsconfig edit can move check:type-check-coverage / check:type-source-resolution; check whether it does.


    Generated by Claude Code

  4. claude commented on Aug 23, 2026

    @claude
    Contributor
    {
      "issue": 10899,
      "status": "done",
      "branch": "claude/issue-10899-dogfood-tsconfig-include",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11474",
      "premise_still_valid": true,
      "summary": "Re-derived the premise at origin/main b3723188 and it holds: packages/qa/dogfood has no src/ (tree is CHANGELOG.md, README.md, package.json, test/, tsconfig.json, vitest.config.ts) while test/ holds 151 files. Removed the dead 'src/**/*' entry from the include array and nothing else - rootDir '../../..' and its comment are untouched. The change is proven inert rather than argued inert: tsc --showConfig before and after resolves a byte-identical 151-entry files array in the same order, with identical compilerOptions and exclude, so the only delta in the whole emitted config is the echoed glob itself. Nothing in the repo depends on this tsconfig's include shape: no project references, and the one invariant that reads include roots (GENERATED_COVERED in check-type-check-coverage.mjs) keys on whether a root is gitignored, which src here is not - the distinction #10880 drew deliberately.",
      "include_resolution": {
        "method": "tsc --showConfig -p packages/qa/dogfood/tsconfig.json, TypeScript 6.0.3 (the version the package declares), run on the clean tree and again after the edit",
        "before": {
          "include": [
            "src/**/*",
            "test/**/*"
          ],
          "resolved_files": 151,
          "all_under": "./test/"
        },
        "after": {
          "include": [
            "test/**/*"
          ],
          "resolved_files": 151,
          "all_under": "./test/"
        },
        "files_array_identical_order_and_content": true,
        "only_in_before": [],
        "only_in_after": [],
        "compilerOptions_identical": true,
        "exclude_identical": true,
        "full_showconfig_diff": "170d169  <         \"src/**/*\",   -- one line, the echoed glob itself",
        "verdict": "resolved file set unchanged apart from the dead glob; the compiled program is identical"
      },
      "tests": "All at final commit 99eee07a, tree clean and byte-identical to that commit. Exit codes captured before any pipe (redirect-then-capture, never EXIT=$? after a tail), and each gate quoted from its own printed verdict line rather than from $?. Gate list derived by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' with NO paths passed (it took its own change set from merge base b37231883: 1 path, working tree) and treated as a floor. 13/13 green. Verdict lines: 'check-type-check-coverage: OK - 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger (436 frozen raw errors), 1 exempt' | 'check-type-source-resolution OK - 77 packages with a tsconfig.json scanned; 51 registered as still resolving a workspace dep types through dist/' | 'check-nul-bytes: OK (scanned 6431 text files; no raw ASCII control bytes)'. REVERSE VERIFICATION - this is not an ablation and nothing needed rebuilding, because no build artifact stands between the edit and the gates: they read tsconfig.json off disk. Method: fix COMMITTED FIRST, then 'git restore --source=$BASE -- packages/qa/dogfood/tsconfig.json' stood the base file back up (tree only, no staging), confirmed on disk by grep reading back 'include: [src/**/*, test/**/*]' and by porcelain showing a lone ' M' and never 'MM'; both include-reading ratchets re-run at base; then 'git restore --source=HEAD' and porcelain confirmed empty. Predicted direction was NO CHANGE - a glob that matches nothing cannot move a count - and that is what was observed: both verdict lines diffed byte-identical between base and head, including the test-layer 1048-files-hidden and source-layer 9-files counts. Floor extension for #11404 checked mechanically: every CI-invoked node script whose basename lacks 'check-' was scanned for tsconfig reads; the only hit is dispatch-gates.mjs itself, the derivation tool, not a gate on this diff.",
      "gates": {
        "derived_and_run": [
          {
            "gate": "pnpm --filter @objectstack/spec run check:empty-state",
            "exit": 0
          },
          {
            "gate": "pnpm --filter @objectstack/spec run check:liveness",
            "exit": 0
          },
          {
            "gate": "pnpm --filter @objectstack/spec run check:strictness-ledger",
            "exit": 0
          },
          {
            "gate": "pnpm --filter @objectstack/spec run check:variant-docs",
            "exit": 0
          },
          {
            "gate": "pnpm check:published-files",
            "exit": 0
          },
          {
            "gate": "pnpm check:slot-lookup",
            "exit": 0
          },
          {
            "gate": "pnpm check:test-source-alias",
            "exit": 0
          },
          {
            "gate": "pnpm check:type-source-resolution",
            "exit": 0
          },
          {
            "gate": "node scripts/check-ci-filter-parity.mjs",
            "exit": 0
          },
          {
            "gate": "node scripts/check-plugin-teardown-shape.mjs",
            "exit": 0
          },
          {
            "gate": "node scripts/docs-audit/check-affected-docs.mjs",
            "exit": 0
          }
        ],
        "added_above_the_derived_list": [
          {
            "gate": "pnpm check:type-check-coverage",
            "exit": 0,
            "why": "it reads every package's include array; the derivation did not name it"
          },
          {
            "gate": "pnpm check:nul-bytes",
            "exit": 0,
            "why": "any edit"
          }
        ],
        "all_green": true,
        "union_run_at_sha": "99eee07a",
        "ci_at_report_time": "in_progress - 33 check runs on 99eee07a: 15 success, 5 skipped, 10 in_progress, 0 failures. Not waited on, per the dispatch contract."
      },
      "line_budget": "not applicable - the diff touches no skills/** path (git diff origin/main...HEAD --name-only | grep -c '^skills/' = 0), so no published-skill line/token budget applies",
      "deviations": [
        "DECLARED NARROWING - repo-wide 'pnpm lint' not run. Three pieces of evidence, all measured: (1) population read from eslint's own config, not guessed - 'pnpm exec eslint --no-inline-config --format json packages/qa/dogfood/tsconfig.json' returns one result whose only message is 'File ignored because no matching configuration was supplied.'; (2) count read from --format json - 1 result entry, 0 errors, 1 warning, and that warning IS the not-configured notice, so 0 files in this diff are linted; (3) invariance for untouched files - eslint.config.mjs states in-repo, backed by a positive control it documents, that this repo never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, so no tsconfig include change can move an eslint verdict on a file it does not appear in.",
        "DECLARED NARROWING - 'pnpm --filter @objectstack/dogfood typecheck' not run locally. It needs the ~25-package dependency closure built for its .d.ts. It is redundant here rather than merely expensive: tsc's behaviour is a function of its resolved program, and --showConfig proves that program byte-identical before and after (same 151 files, same order, same compilerOptions, same exclude), so running tsc twice cannot distinguish the two trees. CI runs it regardless in the required TypeScript Type Check and Dogfood Regression Gate jobs.",
        "No changeset. @objectstack/dogfood is private:true and the diff changes no published artifact. 'skip-changeset' applied via the additive POST endpoint and READ BACK after the bots settled: labels on PR 11474 are size/xs, tooling, skip-changeset - the size-labeler PUT did run (it added size/xs and tooling) and the label survived it."
      ],
      "files_changed": [
        {
          "path": "packages/qa/dogfood/tsconfig.json",
          "insertions": 1,
          "deletions": 1,
          "note": "include array only; rootDir and its comment untouched"
        }
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #11476: packages/qa/dogfood/tsconfig.json - the rootDir comment opens '// No `rootDir`.' on the line directly above '\"rootDir\": \"../../..\"'. Cosmetic only, and the comment's argument still holds (an explicit repo-root rootDir keeps every input under it, so the sibling-source imports it wants stay legal); what is wrong is the opening sentence, which denies the declaration two lines below it - stale knowledge sitting on the exact shape Zone 1 flagged as load-bearing. Unassigned, labelled 'finding', not queued. Provenance deliberately NOT claimed: this checkout is shallow (92 commits) and blame lands on the boundary commit, so whether rootDir was re-added or the comment predates the form is unanswerable from here. Deliberately NOT fixed in this PR - Zone 1 scopes the diff to the dead glob."
      ]
    }

    Generated by Claude Code

  5. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    ACCEPT — PR #11474, head 99eee07a.

    The claim comment on this card set one bar: don't argue the glob is dead, measure what include resolves to before and after. That bar is met, and I re-derived the load-bearing half independently rather than reading it back.

    Verified against the repo, not against the report

    Current origin/main is f31ea1c4 — three commits past the b3723188 the dev measured at (#11468, #11449, #11467). Their file sets are disjoint from packages/qa/dogfood/** and from both include-reading ratchets, so every reading below still stands at today's tip.

    1. The dead glob is dead, and the program is accounted for without re-running tsc.
    git ls-tree -r origin/main packages/qa/dogfood/ — the package is CHANGELOG.md, README.md, package.json, test/, tsconfig.json, vitest.config.ts. No src at any depth. The zero-hit reading has a live positive control on the same instrument: the same ls-tree returns 151 files under test/, all 151 of them .ts (extension census, no other extension present).

    That is the corroboration that matters: the report's --showConfig resolves a 151-entry files array, and test/**/* alone has exactly 151 typescript files. The two numbers meet with nothing left over, so src/**/* contributed zero — checkable from the tree alone, without trusting the tsc invocation.

    2. GENERATED_COVERED keys on gitignored-ness — confirmed, with a correction to the instrument.
    scripts/check-type-check-coverage.mjs at origin/main: the population is built by gitIgnoredPaths() (git check-ignore --stdin -z), and the problem text fires only for "a path the repo's own ignore rules say is NOT checked in". So the question is whether dogfood/src is ignored.

    ⚠️ My first control failed and the reading was not usable. git check-ignore packages/qa/dogfood/node_modules returned not ignored — because the rule is node_modules/, directory-form, and a bare non-existent path does not match it. Re-run in directory form the controls come alive (node_modules/ → ignored, node_modules/foo → ignored, dist/x.js → ignored), and against those:

    path ignored?
    packages/qa/dogfood/src/ no
    packages/qa/dogfood/src/foo.ts no
    packages/qa/dogfood/test/ no (checked in)

    src is neither checked in nor ignored, so it was never in that invariant's population. This also settles the sharper question the script itself raises — its ⛔ "deleting the glob is not automatically the repair" is scoped to generated (ignored) roots, where a missing producer makes tsc read green over nothing. That warning does not reach this diff. Recording the trailing-slash caveat because the next reader of that gate will hit it too.

    3. No project references. No *.json outside the package names qa/dogfood in a references position; every repo-wide hit is prose in docs/qa/platform-checklist/areas/*.json citing dogfood test files as evidence refs, which the include shape cannot move.

    4. rootDir untouched — the diff is one line, include only. Confirmed on the patch: - "include": ["src/**/*", "test/**/*"] / + "include": ["test/**/*"].

    CI

    Computed by latest run per check name, never by raw aggregation: 35 raw runs → 32 distinct names, 28 success, 4 skipped, 0 failure, 0 cancelled, 0 pending. The four skipped are Check PR Size, Check Changeset, Console Pin Gate, Build Docs; Auto Label, Check PR Size and Check Changeset each carry a duplicate pair across two workflow runs, and taking the newest per name is what keeps that from reading as a false red. Notably green here: Type Check · workspace, Type Check · debt ledger, Dogfood Regression Gate (all 3 shards), Dogfood Verify CLI.

    Declared narrowings — both accepted

    The pnpm lint narrowing is measured on all three legs (population from eslint's own config, count from --format json, invariance from the repo's no-type-aware-linting property), not asserted. The skipped local typecheck is accepted for the reason given: --showConfig proving the program byte-identical means running tsc twice cannot distinguish the trees — and CI ran it anyway, green.

    Out-of-scope finding

    #11476 confirmed open, unassigned, labelled finding, not queued. Its premise is verbatim on main today: // No \rootDir`.sits two lines above"rootDir": "../../.."`. Declining to claim provenance from a 92-commit shallow clone was the right call — the two readings imply different fixes, and guessing would have written a wrong reason into the record.

    Landing via the merge queue. Verification will be by content on origin/main, not by sha.


    Generated by Claude Code

  6. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    LANDED — verified by content on origin/main, not by sha and not by the merge event.

    origin/main = d41d9904
    packages/qa/dogfood/tsconfig.json:  "include": ["test/**/*"],
    carried by: d41d9904 chore(qa): drop dead `src/**/*` include glob from dogfood tsconfig (#11474)
    

    packages/qa/dogfood/ still lists exactly CHANGELOG.md, README.md, package.json, test/, tsconfig.json, vitest.config.ts — no src arrived in the meantime.

    Disposition 3 now has an empty population

    This card offered a third invariant ("an include root that is neither tracked nor ignored names nothing") and honestly declined to argue for it, noting the population was one entry — this one. That is worth closing out with a measurement rather than leaving as an open temptation.

    Re-censused every include root in all 87 tsconfig*.json on origin/main d41d9904, classifying each root as tracked / gitignored / neither:

    class count
    include roots total 106
    gitignored (the #10880 GENERATED_COVERED class) 3 — next-env.d.ts, .next/types/**/*.ts, .next/dev/types/**/*.ts, all apps/docs
    neither tracked nor ignored (this card's class) 0

    The gitignored column is the positive control, and it reproduces the ratchet's own verdict line (generated layer: 3 include entr(ies) across 1 package(s)) exactly. So the reading in the second column is a real zero, not a broken instrument: after this merge the class is empty repo-wide, which is the strongest available argument for not building a third invariant — the guard would ship with nothing to guard.

    ⚠️ Two instrument corrections behind that table, recorded because both are traps for the next reader:

    1. git check-ignore needs directory form. The rule is node_modules/; a bare node_modules path answers not ignored. My first positive control failed for this reason and the reading was discarded, not published. Roots must be probed as <path>/.
    2. A naive JSONC comment-stripper eats tsconfig globs. /\*.*?\*/ matches inside ".next/types/**/*.ts" — the /** opens and the */ closes — so 9 of 87 tsconfigs silently failed to parse, apps/docs among them, and the first census reported 0 gitignored roots. Zero in the control column is what caught it. The census above uses a string-aware scanner and parses 87/87.

    Nothing further is owed on this card. pm:dispatched removed; the card was auto-closed by the merged PR.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions