Skip to content

Route generate and lint through the shared project resolver - #180

Open
KayleeWilliams wants to merge 3 commits into
dx/sync-resolved-graphfrom
dx/generate-lint-resolve-project
Open

Route generate and lint through the shared project resolver#180
KayleeWilliams wants to merge 3 commits into
dx/sync-resolved-graphfrom
dx/generate-lint-resolve-project

Conversation

@KayleeWilliams

@KayleeWilliams KayleeWilliams commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #178#167. Closes the last two hand-assembled resolution pipelines #167's review found.

The evidence

generate duplicated the subtlest invariant in the codebase. After syncing, executeGenerate hand-assembled inherit → re-normalize: inheritCollectionSourceConfigs, then normalizeDocsConfig, then a hand-written "keep the first pass's sources and deprecations" splice — a near-verbatim copy of the canonical block in resolveProject (config/project.ts), comment and all. The invariant is subtle because it's invisible when violated: normalization expands sources into collections, so only the load-time pass ever sees authored source names, and it folds deprecated aliases onto canonical names, so a second pass over the canonical config correctly finds no deprecations — and reporting that "nothing" tells a legacy config it has nothing to migrate. Two hand-synced copies of that rule is exactly the drift pattern that gave doctor and nav the same two bugs before #167.

lint skipped inheritance entirely — the same bug class, still live. lint/cli.ts loaded the config and went straight to resolveAllCollections, so a collection whose frontmatterSchema, mounts, or flatteners come from its source repository via inheritConfig: true was linted against the defaults. Confirmed by a failing test first: a synced fixture whose source-owned schema requires an owner field lints clean on this PR's base — the page violates the contract its own source repo declares. Worse, an unsynced source resolved to a cache path that doesn't exist, so lint scanned an empty tree and passed with zero files: exit 0 on a project whose build would fail.

The design

The post-load resolution — per-collection source-owned inheritance, the re-normalization that preserves the first pass's graph and deprecations, provenance stamping, and content-dir resolution through the sync cache — now lives once, as resolveProjectFromLoaded(loaded, { rootDir, … }) in config/project.ts. The three callers differ only in what they do around it:

  • resolveProject: discover config → shared resolver. Unchanged behavior, cache-only, never clones.
  • generate: load → syncSources (generate still owns acquisition; the resolver deliberately never clones) → shared resolver → convert blocking diagnostics to throws, the same pattern createDocsProject established. Since generate just synced, the resolver's cache checks are satisfied by construction; what remains fatal is what was fatal before — an unsatisfiable inheritConfig, a missing collection dir.
  • lint: load → shared resolver, cache-only like the runtime. A missing or stale checkout is now a clear error naming leadtype sync (diagnostic message + fix, in the existing diagnostic style) instead of a silent empty pass.

generate.ts no longer imports inheritCollectionSourceConfigs or normalizeDocsConfig; lint/cli.ts no longer imports resolveAllCollections. Both pieces keep their module exports only because resolveProjectFromLoaded itself consumes them; neither is public API.

Behavior

  • The c15t example (gitSource + inheritConfig + sparse — the exact changed path) regenerates byte-identical output modulo generatedAt timestamps, verified by generating with the base commit and this branch and diffing the trees. The fumadocs/astro examples use the single-source branch this PR doesn't touch.
  • lint behavior changes only where it was wrong: inherited schemas/mounts/flatteners now apply, and unsynced/stale caches fail with the sync pointer instead of passing on zero files.

Tests

  • Regression: lint against an inherited frontmatterSchema (fails on base, passes here), and the unsynced-source hard error.
  • Cross-subsystem agreement, the pattern Make sync consume the resolved source graph #178 introduced for sync: for a shared fixture (named gitSource with inherited navigation + local collection authored with a deprecated alias), generate's exact resolution path and resolveProject produce identical sources, deprecations, collection content dirs, source ids, and navigation origins — plus an end-to-end check that generate --json's acquisition graph deep-equals resolveProject's, authored source names included.

Verification

887 tests pass (bun run test), lint clean, tsgo --noEmit clean for the package. bun run check-types at the workspace level still trips the pre-existing parallel-build race on this stack; that fix is #166, off main.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4caf8566-31c9-4e7e-94e3-58fd99eae7eb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

lint now enforces the project-level gate the published docs assign exclusively to doctor. The code change is right; the docs that describe the split are now wrong, and there is no changeset for a user-visible CLI behavior change.

Reviewed changes — the full diff at 5002598: the extracted resolver, both new callers, and the two new test surfaces.

  • Shared post-load resolver extractedresolveProjectFromLoaded(loaded, { rootDir, fallbackContentDir?, inherit?, infer? }) in config/project.ts now owns per-collection source-owned inheritance, the re-normalization that carries the first pass's sources/deprecations/sourceId through, provenance stamping, and content-dir resolution through the sync cache.
  • resolveProject reduced to discovery — it loads (or accepts) a config and delegates; contentDir/docsDirs[0] become fallbackContentDir. Behaviorally unchanged.
  • generate routed through itexecuteGenerate drops inheritCollectionSourceConfigs, normalizeDocsConfig, and the hand-written sources/deprecations splice, syncs first, then converts the first error-level diagnostic into a throw.
  • lint routed through it — collections-based projects now get source-owned frontmatterSchema/mounts/flatteners, exit 1 on blocking diagnostics, and read contentDir/routePrefix/frontmatterSchema off ResolvedProjectCollection instead of resolveAllCollections.
  • Tests — new cli/generate.test.ts (cross-subsystem agreement plus --json graph parity) and a lint.test.ts block covering the inherited frontmatterSchema and the unsynced-source hard error.

I traced the equivalence claims and they hold: ResolvedDocsCollection.routePrefix is byte-identical to resolveCollection's urlPrefix (both normalizeUrlPrefix(routePrefix ?? "/"+key)); the second normalizeDocsConfig pass preserves every field resolveDocsSourcesFromCollections reads; and generate's remote cache diagnostics really are satisfied by construction, since the default syncMode is "missing" and syncOne already throws on any repository/ref/sparse manifest mismatch. The contentDir as string cast is sound today — all five early returns in resolveContentDir push level: "error".

⚠️ lint now fails on project problems the docs promise only doctor reports

The new blocking-diagnostic gate covers every error from resolveContentDir, which includes the unconditional existsSync on a local collection's dir — not just the unsynced/stale remote cases the PR description enumerates. Meanwhile docs/pipeline/validate-in-ci.mdx:130 still tells readers that lint checks content while doctor is the one that checks "that every collection's directory exists, that remote sources are synced to the revision the config asks for", and the recipe at :140-150 runs doctor → lint → generate with no leadtype sync. Anyone who gated only on lint for a collections-based project now gets exit 1 on a fresh CI runner with a doc that says they shouldn't.

Technical details
# `lint`'s new project gate is undocumented and unversioned

## Affected sites
- `packages/leadtype/src/lint/cli.ts:276-284` — any `level: "error"` diagnostic returns 1 before a single file is read.
- `packages/leadtype/src/config/project.ts:231-240``source.dir-missing` fires for local collections too; the `remote` branch at 182-229 is layered on top, not a precondition. Previously lint globbed the missing directory and exited 0 on zero files.
- `docs/pipeline/validate-in-ci.mdx:130` — "lint checks your content. `leadtype doctor` checks the *project* — that the config resolves, that every collection's directory exists, that remote sources are synced…". No longer the split.
- `docs/pipeline/validate-in-ci.mdx:138` — the "either run `leadtype sync` first, or gate only on the repo whose docs you own" caveat is scoped to doctor; it now applies to lint identically.
- `docs/pipeline/validate-in-ci.mdx:140-150` — the "Run before generate" recipe has no `sync` step.
- `.changeset/` — this PR adds none. `.changeset/resolve-project.md` enumerates `generate`, `doctor`, `nav`, and `createDocsProject`; `lint` is not mentioned, and its new failure mode is the most user-visible change in this PR.

## Required outcome
- The CI-gating docs describe lint's actual contract for collections-based projects: it is now sync-sensitive and directory-existence-sensitive in exactly the way doctor is.
- A changeset entry (new, or an addition to `resolve-project.md`) records that `leadtype lint` gained source-owned inheritance and now exits 1 on unresolvable collections.

## Open questions for the human
- Is failing on a *local* collection whose `dir` does not yet exist the intended contract? It is consistent with the fix and arguably correct, but it is a strictly new failure for anyone whose build populates a collection directory before lint runs. If it is intended, the docs should say so; if not, `source.dir-missing` for non-remote collections would need to stay non-blocking for lint.

ℹ️ No test fails if generate stops using the shared resolver

The PR's central claim is that generate no longer hand-assembles the inherit → re-normalize invariant, but neither new test is sensitive to that. generate.test.ts test 1 hand-rolls generate's pipeline (loadLeadtypeConfigsyncSourcesresolveProjectFromLoaded) rather than driving executeGenerate, so it exercises the resolver and not generate. Test 2 does drive runGenerateCommand, but asserts only result.sources — and the old hand-assembled path preserved sources from the first-pass graph identically, so that assertion passes on either implementation.

Technical details
# Generate's use of the shared resolver is unpinned

## Affected sites
- `packages/leadtype/src/cli/generate.test.ts:128-170` — reconstructs generate's steps by hand; a revert of `generate.ts` to `inheritCollectionSourceConfigs` + `normalizeDocsConfig` + splice leaves this test green.
- `packages/leadtype/src/cli/generate.test.ts:172-190` — the only end-to-end assertion is `result.sources` vs `project.sources`; both implementations source that from `loadedConfig.resolved.sources`.
- `packages/leadtype/src/cli/generate.ts:1707-1714` — the new blocking-diagnostic → throw branch has no test at all. The nearest existing coverage (`cli.test.ts:2391`) is the `--docs-dir` single-source path, which does not reach it.

## Required outcome
- At least one assertion that fails if generate's resolution stops flowing through `resolveProjectFromLoaded` — e.g. drive `runGenerateCommand` against the shared fixture and assert something only the resolved view carries (the inherited navigation reaching the emitted output, or `resolved.collections[].sourceId` in `--json`).
- One test for the blocking-diagnostic throw, so the new fatal path is a contract rather than an accident.

## Suggested approach (optional)
`resolveProjectFromLoaded` rewrites `resolved.collections[].sourceId` from the first pass specifically because the second normalize pass would otherwise report `repo#ref` instead of the authored source name — that is a value the old generate path did not preserve, so asserting it through `generate --json` would pin the new wiring precisely.

ℹ️ Nitpicks

  • packages/leadtype/src/lint/cli.ts:269 — this resolveProjectFromLoaded call sits just outside the try/catch at 229-256 whose comment states "A config that fails to load or validate is a lint failure in its own right — report it instead of crashing"; generate wraps the identical call inside its own catch. I could not construct a reachable throw (inherited fields are validated in validateSourceOwnedConfigFields before the second normalize pass, and the top-level CLI catch still yields exit 1), so this is consistency rather than a bug — but the fix is moving one brace.
  • packages/leadtype/src/lint/cli.ts:376-377collection.contentDir as string is correct today only because every resolveContentDir early return happens to be level: "error". A future warn-level content-dir diagnostic would silently hand undefined to collectRouteSet/lintDocs rather than fail; the invariant is load-bearing and only a comment enforces it.
  • packages/leadtype/src/lint/lint.test.ts:1590-1591 — asserting stderr contains "leadtype sync" matches the generic fix suffix shared by several diagnostics, so a message naming the wrong collection (or a different source.* id) still passes. Asserting on the collection name or the source.not-synced message would pin the behavior the test is named for.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

@KayleeWilliams
KayleeWilliams force-pushed the dx/generate-lint-resolve-project branch from 5002598 to 6524df4 Compare August 15, 2026 18:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6524df4f4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/leadtype/src/lint/cli.ts
@KayleeWilliams
KayleeWilliams force-pushed the dx/generate-lint-resolve-project branch from 6524df4 to 8c7cdb2 Compare August 15, 2026 18:59
@KayleeWilliams

Copy link
Copy Markdown
Collaborator Author

Rebased onto dx/sync-resolved-graph after that stack was rebased onto origin/main (#167). Generate and lint still share resolveProjectFromLoaded for post-load inheritance and cache checks. 8c7cdb23 now verifies each remote cache before importing an inherited docs.config.*.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta. The stale-cache fix is correct, complete, and genuinely covered — one carry-over from the previous review is still open, and this commit widened it.

Reviewed changes — the delta since the prior pullfrog review at 5002598: a pre-inheritance remote-cache gate, two regression tests, and the rebase onto the new base tip 8ba12b6a. lint/cli.ts and cli/generate.test.ts are byte-identical to the previously reviewed revision.

  • Verified the remote cache before inheritance runsremoteCacheDiagnostic (config/project.ts:198-261) is lifted out of resolveContentDir and now also called at the top of resolveProjectFromLoaded's inheritance loop (:902-912), so a stale, unverifiable, or missing checkout is reported and skipped before inheritCollectionSourceConfigs imports and executes that checkout's docs.config.*.
  • Suppressed the resulting duplicate diagnosticpushUniqueDiagnostic (:263-276) keeps resolveContentDir's now-redundant second check from reporting the same id + collection twice.
  • Added two regression testsdoes not import inherited config from a stale cache (config/project.test.ts:457-485) and rejects a stale cache before importing its inherited config (lint/lint.test.ts:1594-1626), the latter using a throw new Error('SHOULD_NOT_IMPORT') source config to prove the module never evaluates.
  • Absorbed the base branch's --docs-dir work — the multi-dir synthesis block moved back out of resolveProjectFromLoaded into resolveProject; the extracted function now forwards warn, accepts a loaded config with no path, and takes fallbackContentDir rather than re-deriving it.

Both new tests can actually fail: disabling the continue at project.ts:909-912 flips navigationOrigin back to "inherited" in the project test and surfaces SHOULD_NOT_IMPORT through source.inherit-failed in the lint test. vitest run src/config/project.test.ts src/lint/lint.test.ts src/cli/generate.test.ts is 83/83 green.

Two things I chased specifically and found clean. The gate is complete: inheritCollectionSourceConfigs (config/inherit.ts:405) is the only code in the repo that imports a source-owned config, its only caller is project.ts:914, generate/lint/doctor/nav/createDocsProject all reach it through resolveProjectFromLoaded, and watch mode re-enters via a fresh executeGenerate per rebuild — no unverified cacheDir import path remains. The loop's gate and resolveContentDir's gate also cannot disagree, since loaded.config is already normalizeDocsConfig output (so sources: { name: gitSource(...) } and deprecated aliases are flattened before the loop) and mergeInheritedSourceConfig never touches repository/ref/cacheDir/sparse. The --docs-dir relocation is faithful: diagnostic ordering is preserved ([load warnings] → [inherit/content-dir] → [extras]), configOrigin is optional on ResolvedProject and read by neither new caller, and the pre-existing project.test.ts:720-803 multi-dir tests still cover the moved block. source.inherit-failed also isn't lost — it still fires for a verified-good checkout whose source config is unreadable (doctor.test.ts:690-758).

ℹ️ This commit adds a second undocumented behavior change to the one that already has no changeset

The previous review asked for a changeset covering lint's new project gate. That is still open, and 8c7cdb23 adds a second user-visible change on top of it: resolveProject now skips source-owned inheritance for a stale or unverifiable cache rather than inheriting from it, so doctor, nav, and createDocsProject stop reporting the source repo's navigation, schema, mounts, and flatteners in that state. Those three commands are named in .changeset/resolve-project.md, so unlike the lint gap this isn't an omission of scope — the existing entry's text is simply now incomplete for commands it already claims to describe.

Technical details
# Changeset and CI-gating docs don't cover either behavior change this PR ships

## Affected sites
- `.changeset/resolve-project.md` — names `generate`, `doctor`, `nav`, `createDocsProject`. Does not mention `lint` at all, and does not mention that a stale/unverifiable cache now suppresses inheritance rather than feeding it.
- `packages/leadtype/src/config/project.ts:902-912` — the new `continue` means a collection with `inheritConfig: true` behind a stale cache resolves with no inherited `navigation`/`groups`/`frontmatterSchema`/`flatteners`/`mounts`. Always accompanied by an error diagnostic, so `doctor` and `nav` still exit 1 — but the *reported* project changed.
- `packages/leadtype/src/lint/cli.ts:276-284` — unchanged in this delta; still the first-error-exits-1 gate flagged in the prior review.
- `docs/pipeline/validate-in-ci.mdx:130` — still assigns "every collection's directory exists, remote sources are synced" exclusively to `doctor`.
- `docs/pipeline/validate-in-ci.mdx:140-150` — the "Run before generate" recipe is `doctor → lint → generate` with no `leadtype sync` step, which now fails at `lint` on a fresh CI runner for a collections-based project.

## Required outcome
- A changeset entry records both changes: `leadtype lint` gained source-owned inheritance and now exits 1 on unresolvable collections, and a stale/unverifiable cache is no longer a source of inherited config for any consumer.
- The CI-gating docs describe `lint`'s actual contract for collections-based projects — sync-sensitive and directory-existence-sensitive in the same way `doctor` is.

## Open questions for the human
- Is failing on a *local* collection whose `dir` does not yet exist the intended contract for `lint`? It follows from routing through `resolveContentDir` and is arguably correct, but it is a strictly new failure for anyone whose build populates a collection directory before lint runs.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

@KayleeWilliams

Copy link
Copy Markdown
Collaborator Author

Windows CI failed at 8c7cdb23 because fixture configs interpolated filesystem paths into JS strings. D:\a\leadtype\... became D:aleadtype... (\a / \t treated as escapes), so generate could not load gitSource and lint's inherited valibot import never ran — empty JSON stdout, not a cache-check regression.

b774e685 writes those specifiers with JSON.stringify so the backslashes stay literal. Assertions unchanged.

generate hand-assembled the full resolution pipeline after syncing —
including a near-verbatim copy of the subtlest invariant in the
codebase: re-normalize after inheritance, but keep the FIRST pass's
sources and deprecations, because normalization expands `sources` into
`collections` and folds aliases, so only the load-time pass ever sees
authored source names or deprecated fields. Two hand-synced copies of
that rule is the bug pattern resolveProject was written to kill.

lint was worse: it loaded the config and skipped inheritance entirely,
so a collection whose frontmatterSchema, mounts, or flatteners come
from its source repository via `inheritConfig: true` was linted against
the defaults — and an unsynced source linted an empty tree and passed
with zero files scanned.

The post-load resolution now lives once, in resolveProjectFromLoaded:
resolveProject calls it after discovery (cache-only, never cloning),
generate calls it after running its own syncSources and converts
blocking diagnostics into hard failures, and lint calls it cache-only
with a clear error naming `leadtype sync` when a checkout is missing
or stale.

New tests: a lint regression fixture where the inherited schema changes
the result, a cache-only failure for unsynced sources, and a
cross-subsystem agreement test pinning that generate's resolution path
and resolveProject see identical sources, deprecations, content dirs,
and navigation origins — plus generate's --json graph matching
resolveProject's. The c15t example regenerates byte-identical output
modulo generatedAt timestamps.
Windows CI interpolates D:\a\leadtype\... into generated configs; raw
backslashes become JS escapes. JSON.stringify the filesystem paths.
@KayleeWilliams
KayleeWilliams force-pushed the dx/generate-lint-resolve-project branch from b774e68 to d725444 Compare August 15, 2026 19:55

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the code. The changeset closes most of the carry-over finding; two of the behavior changes it was asked to record are still missing from it.

Reviewed changes — the delta since the prior pullfrog review at 8c7cdb23: one new changeset, the Windows fixture-escaping commit, and a rebase onto the new base tip.

  • Added a changeset for the routing change.changeset/generate-lint-resolve-project.md, patch on leadtype, recording that generate and lint now resolve through resolveProjectFromLoaded, that lint applies source-owned inheritance, and that a missing or wrong-revision cache is now a diagnostic naming leadtype sync instead of a silently empty lint. patch matches how the sibling sync-resolved-graph.md entry versions a comparable behavior change at 0.4.3.
  • Escaped fixture import paths so Windows configs load — the two new test files build their fixture entry import with JSON.stringify(nativePath) rather than splicing a raw native path into a string literal. Verified and closed in the previous run; unchanged here.
  • Rebased onto 99fb9a4 — the base branch gained its own changeset commit. The range-diff against b774e685 shows no branch-owned content moved; config/project.ts, lint/cli.ts, cli/generate.ts and all three test files are byte-identical to the previously reviewed revision.

I re-verified the two claims behind the inline note against the tree rather than the diff: resolveContentDir still pushes an unconditional source.dir-missing for a local collection at config/project.ts:326-333, and the pre-inheritance cache gate at config/project.ts:904-912 sits inside resolveProjectFromLoaded, which resolveProject — and therefore doctor, nav, and createDocsProject — calls.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment on lines +5 to +7
Route `leadtype generate` and `leadtype lint` through the same resolved project as doctor and the runtime.

Generate used to assemble a project of its own after sync, and lint read collections without running source-owned inheritance — so a remote collection with `inheritConfig: true` was checked against the defaults, and a stale checkout's `docs.config` could still be imported before the cache was verified. Both commands now call `resolveProjectFromLoaded`. Inheritance is applied; a missing or wrong-revision cache is a diagnostic naming `leadtype sync` rather than a silently empty lint or a module that should never have run.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This closes most of the gap, but two of the user-visible changes the previous review asked for are still absent. lint now also exits 1 for a local collection whose dir does not exist — source.dir-missing at config/project.ts:326-333 is unconditional, has no remote or cache involved, and carries no fix, so the entry's leadtype sync pointer doesn't apply to it. And the stale-cache inheritance suppression lives in resolveProjectFromLoaded (config/project.ts:904-912), so it changes what doctor, nav, and createDocsProject report too — not just the two commands this entry names.

Technical details
# Changeset omits lint's local-`dir` failure and the cross-command scope of the inheritance change

## Affected sites
- `.changeset/generate-lint-resolve-project.md:7` — attributes the whole change to `generate` and `lint`, and describes the new lint failure exclusively as "a missing or wrong-revision cache".
- `packages/leadtype/src/config/project.ts:326-333``source.dir-missing` fires for any collection whose resolved `absoluteDir` is absent, local or remote, at `level: "error"` and with no `fix` field. Combined with the gate at `packages/leadtype/src/lint/cli.ts:276-284`, a collections-based project whose build populates a directory before lint runs now gets exit 1 where it previously globbed an empty tree and exited 0.
- `packages/leadtype/src/config/project.ts:904-912` — the pre-inheritance `remoteCacheDiagnostic` + `continue` is inside `resolveProjectFromLoaded`, which `resolveProject` calls, so `doctor`, `nav`, and `createDocsProject` also stop reporting a stale source's inherited `navigation` / `groups` / `frontmatterSchema` / `flatteners` / `mounts`.
- `docs/pipeline/validate-in-ci.mdx:130` and `:140-150` — unchanged in this PR; still assign "every collection's directory exists, remote sources are synced" exclusively to `doctor`, and still document a `doctor → lint → generate` recipe with no `sync` step. This was the second bullet of the previous review's required outcome and is the remaining half of that finding.

## Required outcome
- The changeset states that `leadtype lint` now exits 1 on any collection whose content directory cannot be resolved, including a local `dir` that does not exist — not only on an unsynced or stale remote cache.
- The changeset scopes the stale-cache inheritance change to every consumer of `resolveProject`, so a reader of the release notes learns that `doctor` / `nav` / `createDocsProject` output changed in that state as well.
- The CI-gating docs describe lint's actual contract for collections-based projects.

## Open questions for the human
- Still unanswered from both prior reviews: is failing on a local collection whose `dir` does not yet exist the intended contract for `lint`? If yes, it belongs in the changeset and the docs as written. If not, `source.dir-missing` would need to stay non-blocking for lint on non-remote collections, and the changeset text is already correct as-is.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant