{"_id":"@adhd/apigen-plugin-ir-cache","_rev":"5-b538aaafe25407b8a777fd27b20dc124","name":"@adhd/apigen-plugin-ir-cache","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@adhd/apigen-plugin-ir-cache","version":"0.1.0","_id":"@adhd/apigen-plugin-ir-cache@0.1.0","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"dist":{"shasum":"95e35fb4a3cdd79ae7392301cea1df3240263fe9","tarball":"https://registry.npmjs.org/@adhd/apigen-plugin-ir-cache/-/apigen-plugin-ir-cache-0.1.0.tgz","fileCount":11,"integrity":"sha512-JsbywrztEVXyQhi62XFPjcDq81XSEeK943Ibi9eJ2I5zxcjVyGDHhF0NLSXATCoELr520GW2D0xYIaHHWFHLYA==","signatures":[{"sig":"MEQCIByCtG2vHhZ8ebnb8CAodUX2XH8k7WySfgywGGSH9a0fAiA7RGlEJaSBo/zYTQ9kqdpfN5/ZM0EwcYHLZ1B2BzVF2Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33836},"main":"./index.js","module":"./index.mjs","gitHead":"7edc5d58575a24fb9695bb3474d3e169f20eac58","typings":"./index.d.ts","_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"_npmVersion":"11.6.2","description":"Extract-stage IR cache plugin (FEAT-002, Revision 2): caches the derived IR (`Operation[]`) of one `ExtractCall` so repeat extraction of unchanged source is answered from disk instead of re-running the extractor (~3.4s of the backlog CLI's cold-start cost","directories":{},"_nodeVersion":"24.11.1","dependencies":{"@adhd/apigen-core-client":"^0.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/apigen-plugin-ir-cache_0.1.0_1786144627984_0.0696089733787284","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@adhd/apigen-plugin-ir-cache","version":"0.1.1","keywords":["cache","ir","apigen","typescript"],"license":"MIT","_id":"@adhd/apigen-plugin-ir-cache@0.1.1","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"homepage":"https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-plugin-ir-cache#readme","bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"dist":{"shasum":"e61ca0432989f41bf4a84b6c4a211d1cc2fc8d61","tarball":"https://registry.npmjs.org/@adhd/apigen-plugin-ir-cache/-/apigen-plugin-ir-cache-0.1.1.tgz","fileCount":13,"integrity":"sha512-Cucp5iefFk7jCDnRm1Ru0ZSHovbcev+rLPmw7o5qEBkxnHJIEegefRtPiQKcsqOOLk3fsSDAKQIyualK+aoaHw==","signatures":[{"sig":"MEYCIQDzQXA4x0S39s4Ac0bjGfqc8ncVtw3hEAyV6FcocOS4SQIhAImq/aQTpCcbAiCzb1uDITwPF0HzZslEHmqU0vvXWfCJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCgVH45qmLNCotnUawtcbm2wMgi1ZTR37UNZP1A2CfPrgIgJhis4q5jM9x0CiGterM2Kfmj5dWVY5hs2ub9nDq/euI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":551851},"main":"./index.js","module":"./index.mjs","typings":"./index.d.ts","_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"repository":{"url":"git+https://github.com/PseudoSky/adhd.git","type":"git","directory":"packages/apigen/apigen-plugin-ir-cache"},"_npmVersion":"11.6.2","description":"Intermediate-representation cache for apigen","directories":{},"_nodeVersion":"24.11.1","dependencies":{"@adhd/environment":"^0.1.6","@adhd/apigen-core-client":"^0.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/apigen-plugin-ir-cache_0.1.1_1790266805902_0.7125916525599625","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@adhd/apigen-plugin-ir-cache","version":"0.1.2","keywords":["cache","ir","apigen","typescript"],"license":"MIT","_id":"@adhd/apigen-plugin-ir-cache@0.1.2","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"homepage":"https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-plugin-ir-cache#readme","bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"dist":{"shasum":"39432899806880053dd7c3170d5e10cef7227c7b","tarball":"https://registry.npmjs.org/@adhd/apigen-plugin-ir-cache/-/apigen-plugin-ir-cache-0.1.2.tgz","fileCount":13,"integrity":"sha512-/Fd9I8bAPRWkIsHfwA/bvs2MnVxOIlKjklWVSm3uPokjQeSmdoP2lSsBtQyCX8X4iPOpdu0E4QuO+4iPMQpBCQ==","signatures":[{"sig":"MEUCIGzcBnDhCr46qP9F6SctxaWcmMMKKVK13y27bMPND3JGAiEAiFMXOkFDJFLQTGzFWUn1G2W7Wlhjo8pPEILlDacypx4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIAmyLhgOInE3bWPbUBjUoR62F+CWWX83BHCNjtn5yw3QAiEA37SiDRaDTEgF2UpZTRYhMZxGJVeYqPBlq2MYTKXeh8w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94216},"main":"./index.js","module":"./index.mjs","gitHead":"d473447c27714c6a7ab057efee178d1af8707e05","typings":"./index.d.ts","_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"repository":{"url":"git+https://github.com/PseudoSky/adhd.git","type":"git","directory":"packages/apigen/apigen-plugin-ir-cache"},"_npmVersion":"11.6.2","description":"Intermediate-representation cache for apigen","directories":{},"_nodeVersion":"24.11.1","dependencies":{"@adhd/environment":"^0.1.7","@adhd/apigen-core-client":"^0.3.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/apigen-plugin-ir-cache_0.1.2_1790449180531_0.8327160723238389","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@adhd/apigen-plugin-ir-cache","version":"0.1.3","keywords":["cache","ir","apigen","typescript"],"license":"MIT","_id":"@adhd/apigen-plugin-ir-cache@0.1.3","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"homepage":"https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-plugin-ir-cache#readme","bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"dist":{"shasum":"3ea64c0bbf5e203b0be5aa8f6159d68ea1d0ff08","tarball":"https://registry.npmjs.org/@adhd/apigen-plugin-ir-cache/-/apigen-plugin-ir-cache-0.1.3.tgz","fileCount":13,"integrity":"sha512-urqyg5cB74sl6gyYmJWuCSy6iT9wgA7i5AGRcFtRLKDq6qQWrF11chLiuzsDFn4Dzb7+aMZu8u+ifw2wtOM2XA==","signatures":[{"sig":"MEQCIDKKBkYtySxgPmXiNPyf1V1tzBVuPnBjbBI36vjchIglAiAB603R4mxZGpYlTN7hBRhTyIuELz+9XwgegkRDaz9Y5w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQCAnhw3NEPpbBZjgSjvutdze1zsgbCC9KiF0p0J8Lp+SAIhAJwZ6VaIKNMGTfRXdkJ0NbY6Sng/R0ZJgzF8bu73+Dc1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94216},"main":"./index.js","module":"./index.mjs","gitHead":"a1a6ba024c5526b930f97c5420e920639d14e110","typings":"./index.d.ts","_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"repository":{"url":"git+https://github.com/PseudoSky/adhd.git","type":"git","directory":"packages/apigen/apigen-plugin-ir-cache"},"_npmVersion":"11.6.2","description":"Intermediate-representation cache for apigen","directories":{},"_nodeVersion":"24.11.1","dependencies":{"@adhd/environment":"^0.1.8","@adhd/apigen-core-client":"^0.3.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/apigen-plugin-ir-cache_0.1.3_1790455747681_0.6718265925164213","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"_id":"@adhd/apigen-plugin-ir-cache@0.1.4","bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"dist":{"shasum":"a586abaa1f25cfec9fc77b1c1b0da57dc503acd7","tarball":"https://registry.npmjs.org/@adhd/apigen-plugin-ir-cache/-/apigen-plugin-ir-cache-0.1.4.tgz","fileCount":13,"integrity":"sha512-oRRla3SWBDMPR5WoWPDxwT/ElF13a7RV1bEN9xIDxLwTvGbeQsKRjH4fDR47LsvEPT014zE13gqWA0KCXZsbUA==","signatures":[{"sig":"MEUCICFhXK8g10zf4XCoIAZNXurhNX3iitKlp20K52QLeOuTAiEApstsXE9Ydc1WBH3YDk5Ok12YO5UBYPVx7B0dN17U40g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHZ2WiD48Rd9izinLYWOPKl54d0rxcuHTZsbC87JOkkjAiEA9RWxPAkgYwmYtLBXWjCNtxhbO15+u2PxYqCNWHINDq4="}],"unpackedSize":94411},"main":"./index.js","name":"@adhd/apigen-plugin-ir-cache","module":"./index.mjs","license":"MIT","typings":"./index.d.ts","version":"0.1.4","_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"homepage":"https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-plugin-ir-cache#readme","keywords":["cache","ir","apigen","typescript"],"repository":{"url":"git+https://github.com/PseudoSky/adhd.git","type":"git","directory":"packages/apigen/apigen-plugin-ir-cache"},"_npmVersion":"11.6.2","description":"Intermediate-representation cache for apigen","directories":{},"maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"_nodeVersion":"24.11.1","dependencies":{"@adhd/environment":"^0.1.8","@adhd/apigen-core-client":"^0.3.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/apigen-plugin-ir-cache_0.1.4_1790970509508_0.5141627842151719"}}},"time":{"created":"2026-08-07T23:17:07.725Z","modified":"2026-10-02T19:48:29.769Z","0.1.0":"2026-08-07T23:17:08.129Z","0.1.1":"2026-09-24T16:20:05.990Z","0.1.2":"2026-09-26T18:59:40.616Z","0.1.3":"2026-09-26T20:49:07.756Z","0.1.4":"2026-10-02T19:48:29.593Z"},"bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"license":"MIT","homepage":"https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-plugin-ir-cache#readme","keywords":["cache","ir","apigen","typescript"],"repository":{"url":"git+https://github.com/PseudoSky/adhd.git","type":"git","directory":"packages/apigen/apigen-plugin-ir-cache"},"description":"Intermediate-representation cache for apigen","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"readme":"# @adhd/apigen-plugin-ir-cache\n\nExtract-stage IR cache plugin (FEAT-002, Revision 2): caches the derived IR\n(`Operation[]`) of one `ExtractCall` so repeat extraction of unchanged source\nis answered from disk instead of re-running the extractor (~3.4s of the\nbacklog CLI's cold-start cost, BUG-019).\n\nOne `IrCacheOptions.cache` option selects one of two modes over the **same**\n`CachedExtractEntry` shape — a consumer can switch modes later with zero\nformat migration:\n\n```mermaid\nflowchart TD\n    A[\"IrCacheOptions.cache: string\"] --> B{\"value?\"}\n    B -->|\"literal file path\"| C[\"RUNTIME CACHE mode<br/>(extractLayer capability)<br/>--use ir-cache, every invocation\"]\n    B -->|\"'artifact'\"| D[\"ARTIFACT mode<br/>(target capability)<br/>apigen generate --type ir-cache, once\"]\n    C -.same CachedExtractEntry shape.-> D\n```\n\n## RUNTIME CACHE mode (`cache: <file-path>`) — the `extractLayer` capability\n\nA single, literal, pre-agreed file **is** the entire cache — not a\ncontent-addressed directory of many keyed files. The caller already knows\nexactly which file to open; the only question on a read is \"is what's in it\nstill fresh,\" answered by a two-tier staleness check so a clean, repeated,\nunchanged-source call costs a handful of `stat()` calls, not a full content\nrehash:\n\n```mermaid\nflowchart TD\n    Start([\"extract-stage call\"]) --> Stat[\"stat(cache file)\"]\n    Stat -->|missing| Miss1[\"MISS\"]\n    Stat -->|exists, version matches| HasStale{\"staleness<br/>snapshot present?\"}\n    HasStale -->|no| Slow\n    HasStale -->|yes| Fast[\"FAST GATE: stat() source +<br/>every dep path (no hashing)\"]\n    Fast -->|all mtimes match| Hit1([\"HIT — N stat() calls, zero hashing\"])\n    Fast -->|something touched| Slow[\"SLOW GATE: full content<br/>rehash (sha256 of source + deps)\"]\n    Slow -->|contentKey matches| Hit2([\"HIT — rewrite mtimes<br/>so next read is fast\"])\n    Slow -->|real content change| Miss2[\"MISS\"]\n    Miss1 --> Run[\"next() — real extractor\"]\n    Miss2 --> Run\n    Run --> Write[\"durable write: temp file + fsync + rename()\"]\n```\n\n1. **Missing file**, or a mismatched `formatVersion`/`extractorVersion` → MISS.\n2. **Fast gate** — `stat()` the recorded source path and every recorded\n   transitive-import dep path directly (no `collectLocalImportPaths` call, no\n   hashing). All current `mtimeMs` match the recorded snapshot → **HIT**,\n   `O(deps)` cheap `stat()` calls only.\n3. **Slow gate** (a `staleness` snapshot exists but at least one mtime\n   disagrees) — recompute the full content/version-addressed key\n   (`computeCacheKey`: sha256 of the source and every transitive local\n   import, plus `extractorVersion`/`formatVersion`). Match → HIT (a `touch`\n   with no real edit, e.g. a fresh git checkout that didn't preserve mtimes)\n   — the entry's `staleness` mtimes are then rewritten fire-and-forget so the\n   NEXT read takes the fast path again. Mismatch → MISS.\n4. **No `staleness` snapshot at all** (an ARTIFACT-mode-written entry, or a\n   legacy pre-Revision-2 entry) — `formatVersion`/`extractorVersion` alone\n   cannot prove the source content hasn't drifted (there is no stored content\n   key to compare against), so trusting the entry would permanently launder\n   stale data as \"fresh\". This is a **MISS**: the real extractor runs once to\n   revalidate, and the fresh result is written through WITH a `staleness`\n   snapshot so every subsequent read gets the fast/slow gate.\n5. **MISS** in any case → `next()` runs the real extractor; the result is\n   written through **atomically AND durably** (temp file → `fsync` → `rename()`\n   → best-effort parent-dir `fsync`) and the layer **awaits** that write before\n   resolving. A failing backend is still non-fatal (the failure is swallowed and\n   the fresh operations are returned), but a SUCCESSFUL write is complete and its\n   bytes `fsync(2)`'d by the time the caller sees the result — so a process\n   killed, or the OS crashing, immediately afterwards cannot lose it. This is a\n   **crash-of-process / crash-of-OS** guarantee, **not** a power-loss one: Node\n   exposes no `fdatasync` (the write is an `fsync`), and on some platforms\n   (e.g. macOS/APFS) `fsync(2)` does not force the drive's own cache to media.\n   The slow-gate mtime-refresh in step 3 remains fire-and-forget: it is a pure\n   optimization of a HIT, not a correctness path.\n\n```ts\nimport { createExtractInvoker } from '@adhd/apigen-core-client';\nimport { createIrCacheLayer } from '@adhd/apigen-plugin-ir-cache';\n\nconst extractInvoke = createExtractInvoker(\n  [createIrCacheLayer({ cache: '/path/to/ir-cache.json', extractorVersion })],\n  (call) => extract({ sourceFile: call.source, namespace: call.namespace })\n);\n\nconst operations = await extractInvoke({\n  source: '/path/to/client.d.ts',\n  host: 'ts',\n  namespace: 'backlog',\n});\n```\n\n## ARTIFACT mode (`cache: 'artifact'`) — the `target` capability\n\nA one-shot, build-time JSON artifact produced once, via\n`apigen generate --type ir-cache --opt cache=artifact`, that ships alongside\na consumer's own build with **zero** extraction machinery in the runtime\npath at all — not even a cache lookup. The emitted entry has no `staleness`\nfield: an artifact is never staleness-checked at read time; its freshness\ncomes from *when* the generating command was run.\n\nReuses the existing `target`/`generate` capability and CLI surface rather\nthan a build-hook (e.g. rollup `writeBundle`) specifically to avoid a\nchicken-and-egg ordering hazard: rollup resolves static imports **before**\n`writeBundle`-style hooks fire, so a chunk containing\n`import data from './client.ir.json'` would be unresolvable the first time\ngeneration happened inside the same build pass that imports it. Artifact\nproduction must run as a **separate, prior** step — see\n[the design doc's R2.4](../../../docs/apigen/design-notes/extract-stage-onion-and-ir-cache.md#r24--artifact-mode-cache-artifact--the-target-capability)\nfor the full ordering-constraint rationale and the recommended Nx\n`generate-ir-cache` → `build` `dependsOn` wiring.\n\n```ts\nimport { buildIrCacheArtifact } from '@adhd/apigen-plugin-ir-cache';\n// invoked by the CLI's `--type ir-cache` target dispatch — see generate.ts\n```\n\n## Generic `--use`/`--type` composition\n\n`src/index.ts` exports a single `irCachePlugin: Plugin<IrCacheOptions>`\ncarrying **both** capabilities — `extractLayer` (RUNTIME CACHE, `--use\nir-cache`) and `target` (ARTIFACT, `--type ir-cache --opt cache=artifact`).\nEvery apigen-cli command that extracts (`generate`/`run`/`generate-registry`/\n`run-registry`) funnels through `orchestrator.ts`'s single `extractSource()`\ncall site, which wraps the real extractor with\n`createExtractInvokerFromPlugins(usePluginObjects, runExtractor)` — so\n`--use ir-cache` transparently wraps every command's extraction, not just a\nhand-wired call site. `entrypoint/backlog/src/server.ts` loads the same\nexported `irCachePlugin` object rather than hand-constructing a layer, and\ngates its inclusion behind `APIGEN_IR_CACHE_ENABLED` (default `'1'`; `'0'`\nomits the plugin entirely) — its three transports (HTTP/MCP/CLI) are a live\nmount, not a `--use`-flag-parsed invocation, so an env var is that host's\nonly available opt-out surface.\n\n**`--opt cache=<path>` is honored.** `irCachePlugin`'s `extractLayer`\ncapability declares `createLayer(opts)` (`ExtractLayerCapability.createLayer`,\n`@adhd/apigen-core-client`'s `plugin.ts`) alongside its static `layer`\nfallback: `createExtractInvokerFromPlugins` calls `createLayer(opts)` once\nper plugin at invoker-construction time with this invocation's flat `--opt`\nbag (the same bag already passed to `target.generate`'s `opts` — apigen-cli\nhas one flat bag per invocation, not a per-plugin-id namespaced one), and\nprefers it over `layer` whenever present. So:\n\n```sh\napigen run --source client.ts --type api-fastify --use ir-cache --opt cache=./tmp/ir-cache.json\n```\n\nactually caches at `./tmp/ir-cache.json` — not the `APIGEN_IR_CACHE_FILE`\nenv var or the machine-global default. `--opt extractorVersion=<v>` is\nhonored the same way. Only a bare `--use ir-cache` with no `--opt cache=`\nfalls back to the default middleware (`layer`): `APIGEN_IR_CACHE_FILE` if\nset, otherwise a per-source file under the `@adhd/environment`-namespaced\nglobal cache root — `~/.adhd/apigen/default/cache/ir-<hash>.json`, keyed by\nhost/namespace/source so distinct uses never overwrite each other, and\n`ADHD_ROOT` (the same override backlog's CLI honors) redirects the root.\nNever the invocation cwd (BUG-APIGEN-058).\n\nA host with no `--use`/`--opt` CLI surface at all (e.g.\n`entrypoint/backlog/src/server.ts`'s live MCP/HTTP mount) still builds its\nown `Plugin` object directly via the exported `createIrCacheLayer(opts)`\nfactory, exactly as before — `createLayer`/`opts` only matters for a\n`--use`-flag-parsed invocation.\n\n## Backends\n\n- `createSingleFileBackend(path)` — the RUNTIME CACHE mode backend: one\n  literal file is the entire cache, `get`/`put` ignore the `key` parameter\n  entirely. Writes atomically and durably (temp file → `fsync` → `rename()` →\n  best-effort parent-dir `fsync`; a crash-of-process/OS guarantee, not\n  power-loss), and `put` resolves only once the entry is published and its\n  bytes `fsync`'d.\n- `createLocalFsBackend(dir)` — content-addressed directory\n  (`<dir>/<key>.json`), one file per key. Kept for a possible future\n  multi-key/shared backend (e.g. an Nx-remote-cache-style HTTP store keyed by\n  content hash); not used by either of this plugin's two default modes today.\n  Also writes atomically and durably (via the same `atomicWriteJson` helper).\n- **Shared/remote backend (not built):** `IrCacheBackend` (`get`/`put` by\n  key) is the seam; an HTTP content-addressed store is a same-shape\n  implementation swap. Note the inherited trust model — a shared cache\n  accepts entries written by other machines/CI (the same model Nx's own\n  remote cache accepts), so a shared backend should only be pointed at\n  writers you trust.\n\n## Cache-key guarantees and known gaps\n\n- The full content/version-addressed key (`computeCacheKey`, used on the\n  slow gate and by ARTIFACT-mode-compatible reads) covers: entry file\n  content, all transitive local (non-`node_modules`) imports,\n  `extractorVersion`, `formatVersion`, `host`, `namespace`.\n- The fast gate trusts mtime as a *sufficient* condition for freshness\n  (matching make/Nx/tsc's own incremental-build convention) — a genuinely\n  reverted-but-touched file is caught by the slow gate's real rehash, which\n  the fast gate falls back to on any mtime mismatch; a fast-gate false HIT\n  is not possible without also passing the slow gate's content check, since\n  the slow gate is what re-validates whenever an mtime disagrees.\n- `extractorVersion` is what busts the cache when the extractor's output\n  changes for the same input — including a future DEBT-003 fix (Path 2\n  morph-walk correctness), which must bump it.\n- `versionHint` on `ExtractCall` is deliberately ignored — a shared cache\n  must never key on an unverified caller-supplied identity.\n- **Known gap:** a dependency (`node_modules`) version bump that changes a\n  named type's shape without touching any locally-tracked file is a false\n  HIT. `extractorVersion` covers extractor-side changes only, not\n  third-party dependency changes.\n- **Known gap:** concurrent writers on a MISS (RUNTIME CACHE mode) are not\n  coordinated — `rename()` makes each individual write atomic, but two\n  processes racing the same MISS both compute and write independently; the\n  last `rename()` wins. Accepted as \"both computed the same answer, one\n  redundant write is lost\" — not independently verified for a\n  non-deterministic extractor output (an open question, not a fix).\n- **Known gap:** ARTIFACT mode's default `filename` (`ir-cache.json`)\n  collides across multiple sources if a future multi-source\n  `generate-registry`-driven artifact build runs the `ir-cache` target once\n  per source into the same `--out-dir` — every source would silently\n  overwrite the last. Not exercised by the single-source `generate` path\n  this slice targets.\n- The directory-shaped `createLocalFsBackend` retains its original\n  known gap from v1: nothing evicts an orphaned entry for a\n  no-longer-current content version. Not used by either default mode today,\n  so this is dormant unless a future caller opts into it directly.\n\n## Building / testing\n\n```sh\nnpx nx build apigen-plugin-ir-cache\nnpx nx test apigen-plugin-ir-cache\nnpx nx lint apigen-plugin-ir-cache\n```\n","readmeFilename":"README.md"}