@moldig/core
v0.1.2
Published
The moldig engine: the index, adapters, detectors and graph for the files AI coding harnesses leave across your projects.
Readme
@moldig/core
The engine behind moldig: the read-only scan that turns what six AI coding harnesses left on a machine into one index, the detectors that read findings out of it, and the actions engine that plans what a removal would do. No terminal dependency of any kind, so a CLI, an app or a CI job can all embed it.
npm install @moldig/coreESM only, Node.js 22.18 or newer. The API is not stable before 1.0; the version moves in
lockstep with moldig.
What is in it
| | |
|---|---|
| The index | index v0 (schemaVersion: 0): harnesses, projects, breadcrumbs, entities, edges, warnings and totals. The contract shared by the CLI and anything else that reads a scan |
| The adapters | one per harness — Claude Code, Codex, Cursor, Gemini CLI, Copilot, OpenCode — plus one for the stores several harnesses share. Each knows where its harness keeps its files and turns them into entities. Read-only, always |
| The detectors | the eight categories (duplicate, orphan, bloat, drift, shadow memory, autogenerated, harness cache, exposure) and the headline number |
| The actions engine | pure planning: what a clean, delete or update would touch, the disposition of each target, the backups and the shape of the run manifest. The executors that actually move anything are injected by the caller (Executors), so this package never touches the trash, spawns a process or writes a file |
| The graph | the typed edges between entities: names, names a tool, references, loaded by, duplicates, originates from, shadows, imports, provided by, lists |
Index v0 is the contract. It is frozen at schemaVersion: 0 for v1: field order and enum values
are deterministic, unknown is null and never false, and ids are opaque — never parse one.
Using it
import { scan, audit } from "@moldig/core";
import { homedir } from "node:os";
const index = await scan({
home: homedir(),
roots: [], // or the directories to limit the scan to
cwd: process.cwd(),
platform: process.platform as "darwin" | "linux" | "win32",
env: process.env,
});
const audited = await audit(index);
console.log(audited.totals.bytes, audited.findings.length);
for (const warning of audited.warnings) console.error(warning.code, warning.message);Nothing is read from the process: home, roots, cwd, platform and env are all injected,
so the same tree yields the same index on every machine. scan also takes harnesses (restrict
the adapters that run), git (false never spawns git), now (a deterministic clock) and
isProcessAlive (the live guard). audit takes focus and readSignal.
The rest of the surface: HARNESSES, SCAN_PLATFORMS with isScanPlatform and
assertScanPlatform; CATEGORY_ORDER, PINNED_FLAGS, compareForDisplay and
compareSerialised for the one display order the CLI and the app share; isCleanable,
isPreselected, isTickable, isProtected, isLive and selectionFrom for what may be acted
on; plan and apply with Executors; dataDirFor, manifestPathFor and backupDirFor;
updatePreview; MULTIPLIERS and modelFamilyOf for the token ranges. Every index v0 type is
exported alongside them.
Runtime notes
- Tokens are counted with
gpt-tokenizer(o200k), loaded lazily so nothing pays for it until a scan counts. When it cannot be loaded, counts fall back tobytes / 4and the scan reports atokenizer-fallbackwarning rather than pretending. The index records which tokenizer and encoding ran, and whether it fell back, undertokenizer. - Parsing uses
jsonc-parserandsmol-toml. A file that will not parse becomes aparse-errorwarning, never a throw. - Databases are read through
node:sqlite, imported lazily and opened read-only (?immutable=1, falling back to?mode=ro) so no-walor-shmsidecar is ever created. On Node 22 the first import prints oneExperimentalWarningabout SQLite; the host decides whether to filter it — this package installs no process listener and prints nothing itself. - Credential stores are stat'ed and never opened.
@moldig/core/testing
loadFixture and normaliseSnapshot are exported so every package in the moldig monorepo shares
one fixture helper, and they are monorepo-internal on purpose. loadFixture walks up from
its own module looking for the repository's fixtures/ directory; the fixture cases are not in
this package's files, so outside a checkout of the monorepo it throws an error saying exactly
that. Do not build on it.
MIT © Guillermo López
