{"_id":"@alxmss/tracekit","_rev":"2-542829fbde451c06e074f4490a80c51e","name":"@alxmss/tracekit","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@alxmss/tracekit","version":"0.1.0","_id":"@alxmss/tracekit@0.1.0","maintainers":[{"name":"alxmss","email":"alexander.m.s.strandberg@gmail.com"}],"bin":{"tracekit":"dist/cli.js"},"dist":{"shasum":"e7759ea4ee3599e119bc5f8999b189b800ecfabc","tarball":"https://registry.npmjs.org/@alxmss/tracekit/-/tracekit-0.1.0.tgz","fileCount":34,"integrity":"sha512-c+x2pfvvmwHruWShtEqK8uMFKiP2XLROeqU9gMaBDQYv9YY/4I9bOQRUAHFrJl12pDrqVl2GzjACv71Q7syyxw==","signatures":[{"sig":"MEUCIHm7eVY+DTylYsyyXNfuf1d9tjHk5kfJYGx/5PqKXrO7AiEAqk5IvuCBL8eeruHxTa1hVIzCiib0PWxTZLQOuGPuh/o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":66562},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","gitHead":"1a02ebd80e5bd6eb6c5c2c1f293ebb62fdc73268","scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","typecheck":"tsc --noEmit","test:watch":"vitest","trace:distill":"tracekit distill --trace trace.json --root .","prepublishOnly":"npm run build"},"_npmUser":{"name":"alxmss","email":"alexander.m.s.strandberg@gmail.com"},"_npmVersion":"11.12.1","description":"Runtime instrumentation and source pruning for TypeScript modules","directories":{},"_nodeVersion":"24.15.0","dependencies":{"ts-morph":"^28.0.0","commander":"^14.0.3","magic-string":"^0.30.21"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.1.3","typescript":"^5.8.3","@types/node":"^22.15.3"},"_npmOperationalInternal":{"tmp":"tmp/tracekit_0.1.0_1777316816346_0.9439401055812326","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alxmss/tracekit","publishConfig":{"access":"public"},"version":"0.1.1","description":"Runtime instrumentation and source pruning for TypeScript modules","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"tracekit":"dist/cli.js"},"scripts":{"build":"tsc","dev":"tsc --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","trace:distill":"tracekit distill --trace trace.json --root ."},"devDependencies":{"@types/node":"^22.15.3","typescript":"^5.8.3","vitest":"^3.1.3"},"dependencies":{"commander":"^14.0.3","magic-string":"^0.30.21","ts-morph":"^28.0.0"},"gitHead":"a292199ef55eb5c4f50ea9bf924f8bc2245b08de","_id":"@alxmss/tracekit@0.1.1","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-sI4XE9KFxCeTFuSaG4tgAPeTdc45exCM3snMrGRS7hpZiD8ApIEU1Fz+8NzMRKIgOEd1WshNUKJi2hGbBhu5sQ==","shasum":"71ddac41216406476899207406487c2b9a87793f","tarball":"https://registry.npmjs.org/@alxmss/tracekit/-/tracekit-0.1.1.tgz","fileCount":34,"unpackedSize":69188,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIB1yZd60fopPYgO3/oU7LCJwDP2Ks8UghACFx7zTLNG3AiB8TWUq/jfRSaEOkQLbAn+GtbotJq9k+oA/CTu5WfLYtw=="}]},"_npmUser":{"name":"alxmss","email":"alexander.m.s.strandberg@gmail.com"},"directories":{},"maintainers":[{"name":"alxmss","email":"alexander.m.s.strandberg@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tracekit_0.1.1_1777317658323_0.16374090527336738"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-27T19:06:56.161Z","modified":"2026-04-27T19:20:58.591Z","0.1.0":"2026-04-27T19:06:56.504Z","0.1.1":"2026-04-27T19:20:58.492Z"},"description":"Runtime instrumentation and source pruning for TypeScript modules","maintainers":[{"name":"alxmss","email":"alexander.m.s.strandberg@gmail.com"}],"readme":"# TraceKit\n\nRuntime instrumentation and deterministic source pruning for TypeScript.\n\nTraceKit wraps your modules in a transparent Proxy, records which functions are called during an execution, and then rewrites your source files to remove every function body that was never touched — replacing them with `{ /* pruned */ }`. The result is a minimal, accurate picture of what your code actually does at runtime.\n\n> **Status: Operational** — 34/34 tests passing.  \n> Node.js 22+, TypeScript 5.x, ESM.\n\n[![Tests](https://img.shields.io/badge/tests-34%20passing-brightgreen)](https://github.com/alxmss/TraceKit)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://github.com/alxmss/TraceKit)\n[![Node](https://img.shields.io/badge/Node.js-22%2B-green)](https://github.com/alxmss/TraceKit)\n\n---\n\n## How it works\n\n```\nyour code  →  tk.slice()  →  Proxy  →  tk.track()  →  Registry\n                                                            ↓\ntracekit distill  ←  trace.json  ←  registry.drain()  ←  traceId\n        ↓\n  mathService.pruned.ts   (complexMatrixMultiply body gone, add/subtract intact)\n```\n\n1. **Slice** — wrap a module with `tk.slice(name, module)`. The return type is `T`, so IntelliSense and JSDoc are fully preserved.\n2. **Track** — run your code inside `tk.track(fn)`. Every function call on a wrapped module is tagged with a unique trace ID and stored in the in-process registry.\n3. **Drain** — export the registry to a JSON file with `registry.drain()`.\n4. **Distill** — run the CLI against the JSON file. Functions that appear in the trace keep their bodies; everything else is pruned.\n\n---\n\n## Is TraceKit right for your project?\n\nTraceKit is a precision instrument. It works best when you know *which* execution you want to understand, not just *where* a function is defined.\n\n### The sweet spot\n\n**Language: TypeScript / JavaScript (Node.js & modern web)**\n\nTraceKit's engine is built on the ECMAScript `Proxy` API and Node's `AsyncLocalStorage`, making it native to the JS/TS ecosystem. It wraps any object or function export without an OS-level agent.\n\n- Node.js backends — Express, Fastify, NestJS\n- Modern frontend build pipelines — React/Vite, Next.js (server-side)\n\n**Architecture: modular codebases with clear service boundaries**\n\nTraceKit thrives when logic is separated into services, controllers, or slices. You tell it \"watch only the `auth` and `payment` slices\" and it ignores the other 200 files in your project — so the output is 100% focused on the transaction flow you care about.\n\nGood fits: `/services`, `/controllers`, `/slices`, `/repositories`, or any Redux Toolkit-style layout.\n\n**Task type: interaction-heavy debugging with a clear start and end**\n\nTraceKit records a single execution boundary — everything between `tk.track()` start and finish.\n\n- **API request/response cycles** — trace every function from route entry to response\n- **Failing tests** — run a broken test inside `tk.track()`; the distilled output is exactly the code that ran\n- **State transitions** — pinpoint why a Redux action or database mutation didn't behave as expected\n\n**Project scale: the \"context wall\"**\n\n| Project size | Recommendation |\n|---|---|\n| < 5k LOC | TraceKit is overkill — an LLM can read the whole project |\n| 20k–200k+ LOC | TraceKit becomes a game-changer — a search returns 50 matches, TraceKit shows the 2 that actually ran |\n\n### TraceKit vs. standard RAG (vector search)\n\n| | Standard RAG | TraceKit |\n|---|---|---|\n| **Relevance** | Shows code that *looks* relevant | Shows code that *actually ran* |\n| **Async logic** | Struggles to follow callbacks | Follows perfectly via `AsyncLocalStorage` |\n| **Token usage** | Scalable but noisy | Ultra-minimal |\n| **Setup cost** | Requires a vector DB | Zero-config via `tracekit init` |\n| **Ambiguity** | High | Zero |\n\nIf you're working in a large TypeScript codebase where \"finding where the logic actually lives\" is the hardest part of the job, TraceKit turns a *needle-in-a-haystack* problem into a *here is the needle* solution.\n\n---\n\n## Installation\n\n### In a project that already has TypeScript\n\n```bash\nnpm install --save-dev @alxmss/tracekit\n```\n\nThen initialise the config (this creates `tracekit.config.ts` and adds a helper script to `package.json`):\n\n```bash\nnpx tracekit init\n```\n\n### From source (this repo)\n\n```bash\ngit clone https://github.com/alxmss/TraceKit.git\ncd TraceKit\nnpm install\nnpm run build        # compiles src/ → dist/\n```\n\n---\n\n## Quick start\n\n### 1. Initialise\n\n```bash\nnpx tracekit init\n```\n\nThis creates `tracekit.config.ts` in your project root and adds `trace:distill` to your `package.json` scripts.\n\n### 2. Edit the config\n\nOpen `tracekit.config.ts` and map your slice names to their source files:\n\n```ts\n// tracekit.config.ts\nimport { defineConfig } from '@alxmss/tracekit';\n\nexport default defineConfig({\n  slices: {\n    math:   './src/mathService.ts',\n    auth:   './src/auth.ts',\n    db:     './src/db/index.ts',\n  },\n  outputDir: '.tracekit',\n});\n```\n\n### 3. Instrument your code\n\n```ts\nimport { tk, registry, traceStorage } from '@alxmss/tracekit';\nimport { writeFile } from 'node:fs/promises';\nimport * as mathService from './src/mathService.js';\n\n// Option A — explicit (no config required)\nconst math = tk.slice('math', mathService);\n\n// Option B — config-aware (reads sliceMap from tracekit.config.ts automatically)\nimport config from './tracekit.config.js';\ntk.configure(config);\nconst math = tk.autoSlice('math', mathService);\n```\n\n### 4. Run a trace\n\n```ts\nlet traceId!: string;\n\nawait tk.track(async () => {\n  traceId = traceStorage.getStore()!.traceId;\n\n  // Only call the functions you actually need right now\n  math.add(3, 7);\n  math.subtract(10, 4);\n  // math.complexMatrixMultiply is never called → will be pruned\n});\n\n// Export the trace\nconst records = registry.drain();\nawait writeFile('trace.json', JSON.stringify(records), 'utf8');\nconsole.log('Trace ID:', traceId);\n```\n\n### 5. Distill\n\n```bash\n# Auto-resolves slice→file from tracekit.config.ts\nnpx tracekit distill <traceId> --trace trace.json --root .\n\n# Or print as markdown (useful for LLM context)\nnpx tracekit distill <traceId> --trace trace.json --root . --format md\n\n# Or write to a directory\nnpx tracekit distill <traceId> --trace trace.json --root . --output .tracekit\n```\n\n**Input** (`src/mathService.ts`):\n\n```ts\nexport function add(a: number, b: number): number {\n  return a + b;\n}\n\nexport function subtract(a: number, b: number): number {\n  return a - b;\n}\n\nexport function complexMatrixMultiply(a: number[][], b: number[][]): number[][] {\n  // ... 30 lines of O(n³) loops ...\n}\n```\n\n**Output** (distilled):\n\n```ts\nexport function add(a: number, b: number): number {\n  return a + b;\n}\n\nexport function subtract(a: number, b: number): number {\n  return a - b;\n}\n\nexport function complexMatrixMultiply(a: number[][], b: number[][]): number[][] { /* pruned */ }\n```\n\n---\n\n## API reference\n\n### `tk.slice<T>(name, module): T`\n\nWraps a module in a transparent Proxy. The return type is exactly `T` — no type information is lost. Recording only happens inside a `tk.track()` context; calls made outside are passed through without being observed.\n\n```ts\nconst auth = tk.slice('auth', authModule);\n```\n\n### `tk.configure(config)`\n\nSets the global config so that `tk.autoSlice` knows the slice→file mapping. Call once at startup before any `autoSlice` calls.\n\n```ts\nimport config from './tracekit.config.js';\ntk.configure(config);\n```\n\n### `tk.autoSlice<T>(name, module): T`\n\nLike `tk.slice`, but validates that `name` is registered in the config. The CLI reads the same config file, so you never need to pass `--map` on the command line.\n\n```ts\nconst db = tk.autoSlice('db', dbModule);\n```\n\n### `tk.track<T>(fn: () => T | Promise<T>): Promise<T>`\n\nRuns `fn` inside a new trace context backed by `AsyncLocalStorage`. Every function call on a sliced module within `fn` — including across `await` boundaries — is tagged with the same `traceId`. Concurrent `tk.track()` calls never share a context.\n\n```ts\nconst result = await tk.track(async () => {\n  const user = await userRepo.findById(42);\n  return user;\n});\n```\n\n### `tk.distill(traceId, options): Promise<Map<string, string>>`\n\nProgrammatic distillation. Reads the registry for `traceId`, looks up source files via `sliceMap`, and returns a `Map<filePath, prunedSource>`. Does not drain the registry.\n\n```ts\nconst pruned = await tk.distill(traceId, {\n  projectRoot: process.cwd(),\n  sliceMap: new Map([['auth', './src/auth.ts']]),\n});\n```\n\n### `registry`\n\nThe global in-process ring buffer (10 000 records by default, configurable via `TRACEKIT_MAX_RECORDS`).\n\n| Method | Description |\n|--------|-------------|\n| `registry.drain()` | Returns all records and clears the buffer |\n| `registry.snapshot()` | Returns all records without clearing |\n| `registry.onFlush` | Optional callback fired synchronously on every `record()` call |\n\nEach record is a tuple `[timestamp, traceId, sliceName, fnPath]`.\n\n### `defineConfig(config): TraceKitConfig`\n\nIdentity helper with TypeScript type inference — the same pattern as Vite's `defineConfig`. Use it in `tracekit.config.ts` for editor autocomplete.\n\n---\n\n## CLI reference\n\n### `tracekit init`\n\nScaffolds `tracekit.config.ts` in the project root and patches `package.json`.\n\n```\nOptions:\n  --root <path>   project root (default: current directory)\n  --force         overwrite an existing tracekit.config.ts\n```\n\n### `tracekit distill <traceId>`\n\nPrunes source files based on a recorded trace.\n\n```\nOptions:\n  --trace <file>     JSON file from registry.drain() (use \"-\" for stdin)\n  --root <path>      project root (required)\n  --map <name=path>  slice → file mapping, repeatable; overrides tracekit.config.ts\n  --output <dir>     write pruned files here instead of printing to stdout\n  --format <fmt>     text (default) or md\n```\n\nWhen `--map` is omitted, the CLI auto-resolves the mapping from `tracekit.config.ts` at `--root`.\n\nWhen `--format md` and `--output` are both set, all files are combined into a single `<traceId>.md` file — useful for pasting into an LLM prompt.\n\n---\n\n## Edge cases and known behaviour\n\n| Scenario | Behaviour |\n|----------|-----------|\n| Calls made outside `tk.track()` | Executed normally, not recorded |\n| Circular references | Safe — a `WeakMap` breaks cycles |\n| `new ClassName()` | Recorded as `new sliceName.ClassName` via `construct` trap |\n| Private class fields (`#field`) | Passed through without interception |\n| Symbol-keyed properties | Passed through; wrapping `Symbol.iterator` would break iteration |\n| Arrow functions with concise bodies | Bodies like `() => expr` are not pruned (no block to replace) |\n| Concurrent `tk.track()` calls | Each gets its own `traceId`; `AsyncLocalStorage` keeps them isolated |\n\n---\n\n## Development\n\n```bash\nnpm run build        # tsc → dist/\nnpm run dev          # tsc --watch\nnpm test             # vitest run (34 tests)\nnpm run test:watch   # vitest interactive\nnpm run typecheck    # tsc --noEmit (no emit, just type-check)\n```\n\n---\n\n## Project structure\n\n```\nsrc/\n  registry.ts       Ring-buffer record store\n  tracker.ts        AsyncLocalStorage context (traceId per tk.track call)\n  slice.ts          Proxy engine + tk namespace\n  distiller.ts      ts-morph AST parser + magic-string body replacer\n  config.ts         defineConfig helper and TraceKitConfig interface\n  config-loader.ts  CLI-side static parser for tracekit.config.ts\n  cli.ts            Commander CLI (init, distill)\n  index.ts          Public API surface\n  __tests__/\n    slice.test.ts\n    tracker.test.ts\n    distiller.test.ts\n```\n","readmeFilename":"README.md"}