/* Process-lifetime singleton cache for ActualState instances (ADR-0022). Avoids rebuilding the actual state from scratch when executeKernelPipeline or executeKernelCommand is called multiple times within the same Node.js process. Do not persist cache across processes — each process starts with an empty cache. Do not cache single-module registries built by buildRegistryForModule — only full registries are cached. ActualState is cached per process — rebuilding the registry per command is avoided. RFC-1026: add clearModule for incremental single-module invalidation without clearing the entire cache. RFC-1038: cache ActualState instead of KernelRegistry; remove clearModule (no module unload in desired-state model). RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1147: getOrBuildSiteModuleStates — process-lifetime cache for materialized site-module ActualStates keyed by workspaceRoot; cleared alongside the registry cache. ADR-0022 */ import type { KernelAppConfig } from "@warpgogol/werkstatt-shared/kernel"; import type { ActualState, ModuleExport } from "@warpgogol/werkstatt-shared/kernel"; import { buildRegistry, type RegistryLoadOptions } from "./registry.ts"; import { buildActualState } from "../../runtime/reconciler.ts"; import { loadWorkspaceConfig } from "../discovery.ts"; const registryCache = new Map(); const siteModuleStatesCache = new Map>(); let cacheEnabled = true; /** * Returns a cached ActualState for the given cache key, or builds and caches * a new one. The cache key must uniquely identify the config source within the * process (e.g. `workspace:` or `site:`). * * When the cache is disabled (via `setRegistryCacheEnabled(false)`), always * builds a fresh actual state without reading from or writing to the cache. */ export async function getOrBuildRegistry( cacheKey: string, config: KernelAppConfig, options?: RegistryLoadOptions, ): Promise { if (cacheEnabled) { const cached = registryCache.get(cacheKey); if (cached) { if (!options?.quiet) process.stderr.write(` [registry] cache hit for ${cacheKey}\n`); return cached; } } const registry = await buildRegistry(config, options); if (cacheEnabled) { registryCache.set(cacheKey, registry); } return registry; } /** * Load workspace config and return a cached actual state for it. * Returns undefined when no workspace config exists. */ export async function getOrBuildWorkspaceRegistry( workspaceRoot: string, options?: RegistryLoadOptions, ): Promise { const config = await loadWorkspaceConfig(workspaceRoot, options); if (!config) return undefined; const cacheKey = `workspace:${workspaceRoot}`; return getOrBuildRegistry(cacheKey, config, options); } /** * RFC-1147: materialize the canonical site module set once per workspace root. * The loaders resolve @warpgogol/werkstatt-site specifiers from the consumer's * node_modules — keyed by workspaceRoot because different workshops may pin * different site-plugin versions. Returns module-name → ActualState pairs so * callers keep the registering-module attribution. */ export async function getOrBuildSiteModuleStates( workspaceRoot: string, loaders: Record Promise>, ): Promise> { const cacheKey = `site-modules:${workspaceRoot}`; if (cacheEnabled) { const cached = siteModuleStatesCache.get(cacheKey); if (cached) return cached; } const states = new Map(); for (const [moduleName, loader] of Object.entries(loaders)) { const mod = await loader(); states.set(moduleName, buildActualState([mod])); } if (cacheEnabled) { siteModuleStatesCache.set(cacheKey, states); } return states; } /** * Clear all cached actual states. Called by the `--no-registry-cache` CLI flag * and by tests that need a fresh actual state between runs. */ export function clearRegistryCache(): void { registryCache.clear(); siteModuleStatesCache.clear(); } /** * Enable or disable the registry cache. When disabled, `getOrBuildRegistry` * always builds a fresh actual state and does not read from or write to the cache. * Calling with `false` also clears any existing cached entries. */ export function setRegistryCacheEnabled(enabled: boolean): void { cacheEnabled = enabled; if (!enabled) { registryCache.clear(); siteModuleStatesCache.clear(); } } /** * Returns true if the registry cache is currently enabled. */ export function isRegistryCacheEnabled(): boolean { return cacheEnabled; }