Architecture

How Pretense works

A local proxy that scans, mutates, transmits, and reverses -- in under one millisecond. Your code never leaves your machine in its original form.

1

Token Scanning

Pretense parses source code to extract every identifier -- functions, classes, and variables -- without touching comments or string literals.

The scanner uses a regex-based AST walker that understands TypeScript, JavaScript, Python, Go, Java, C#, Ruby, and Rust syntax. It identifies token boundaries and classifies each token by type (function, class, variable, constant). Comments are preserved verbatim because they carry developer intent that LLMs need. String literals are preserved because multiline strings cannot be safely mutated without breaking semantics.

Input source
typescript
// Fetch the user's payment token from the vault
async function getUserPaymentToken(userId: string): Promise<string> {
  const vaultClient = new PaymentVaultClient(process.env.VAULT_URL);
  return vaultClient.getToken(userId);
}
Token classification
getUserPaymentTokenfunctionmutate
userIdvariablemutate
vaultClientvariablemutate
PaymentVaultClientclassmutate
// Fetch the user's payment token...commentpreserve
process.env.VAULT_URLenv-refpreserve
2

Mutation Algorithm

Each identifier is hashed with SHA-256 and the first 4 hex characters become the mutation suffix. The result is deterministic, reversible, and meaningless to external observers.

Determinism is critical: the same identifier always produces the same synthetic, so the LLM sees a stable codebase across sessions. The 4-char suffix gives 65,536 possible values -- enough uniqueness for any real codebase. Prefixes (_fn, _v, _cls) preserve the token type so LLMs can still reason about the code structure without knowing the real names.

Mutation transform
pseudocode
mutate("getUserPaymentToken", type="function"):
  hash  = SHA256("getUserPaymentToken")   // full 64-char hex
  short = hash.slice(0, 4)               // "4a2b"
  return "_fn" + short                   // "_fn4a2b"

mutate("userId", type="variable"):
  hash  = SHA256("userId")
  short = hash.slice(0, 4)               // "9x2c"
  return "_v" + short                    // "_v9x2c"

mutate("PaymentVaultClient", type="class"):
  hash  = SHA256("PaymentVaultClient")
  short = hash.slice(0, 4)               // "8d3f"
  return "_cls" + short                  // "_cls8d3f"
Token classification
getUserPaymentTokenfunction_fn4a2b
userIdvariable_v9x2c
vaultClientvariable_v7e1a
PaymentVaultClientclass_cls8d3f
3

Proxy Transmission

The mutated source is sent to the LLM API. Secrets detected in transit are blocked entirely -- the request never leaves your machine if a credential is found.

Pretense runs a parallel secrets scan on every outbound request using 30+ regex patterns covering API keys, JWT tokens, AWS credentials, database connection strings, and PII. If a secret is detected, the entire request is rejected with a clear error. Mutated identifiers are syntactically valid code -- the LLM can read, reason about, and improve it as if it were real code.

What the LLM receives
typescript
// Fetch the user's payment token from the vault
async function _fn4a2b(_v9x2c: string): Promise<string> {
  const _v7e1a = new _cls8d3f(process.env.VAULT_URL);
  return _v7e1a.getToken(_v9x2c);
}
4

Reversal

When the LLM responds, Pretense applies the mutation map in reverse. Every synthetic identifier is swapped back to the original. The developer never sees mutated code.

The reversal pass is a simple string substitution driven by the in-memory MutationMap built during the scan phase. Because mutation is deterministic, there is no ambiguity: _fn4a2b can only ever come from one source token. The reversal is byte-exact -- indentation, whitespace, and comments are untouched. The round-trip is lossless.

LLM response after reversal
typescript
// Fetch the user's payment token from the vault
async function getUserPaymentToken(userId: string): Promise<string> {
  const vaultClient = new PaymentVaultClient(process.env.VAULT_URL);
  return vaultClient.getToken(userId);
}

// Pretense restored 4 identifiers from MutationMap

Why LLM output quality is preserved

Variable names are noise to LLMs. Models infer meaning from structure, types, comments, and relationships -- none of which Pretense touches.

Structural semantics preserved

Function signatures, class hierarchies, and call graphs remain intact. The LLM can still reason about architecture, interfaces, and control flow.

Comments never touched

Developer intent, TODOs, and business-logic annotations are passed through verbatim. LLMs use these heavily for context.

Types and interfaces intact

TypeScript type annotations, generics, and return types are preserved. The LLM understands the data model even with mutated names.

String literals unchanged

Error messages, SQL queries, and template strings pass through unmodified. No broken references or malformed queries.

What you write
function getUserPaymentToken(
  userId: string
): Promise<string> {
  // real function name
  // real variable names
}
What the LLM sees
function _fn4a2b(
  _v9x2c: string
): Promise<string> {
  // real function name
  // real variable names
}

The LLM can still improve logic, fix bugs, and add features. It just never learns your naming conventions or domain vocabulary.

Rust hot-path

The scanner and SHA-256 hashing are implemented in Rust via a native Node.js addon. The Rust layer uses rayon for parallel work-stealing across all CPU cores, making it 27x faster than a pure JavaScript implementation on large codebases.

27x
Speedup vs pure JS
on 1M token scan
rayon
Parallelism
work-stealing thread pool
480 MB/s
Scanner throughput
on M-series Mac
<1 ms
Latency added
p99 on 10K-line file
scanner-rs/src/lib.rs (simplified)
rust
use rayon::prelude::*;
use sha2::{Digest, Sha256};

pub fn scan_tokens(source: &str) -> Vec<Token> {
    // Split into lines, scan in parallel across cores
    source.par_lines()
          .enumerate()
          .flat_map(|(line_no, line)| scan_line(line_no, line))
          .collect()
}

pub fn mutate(identifier: &str, kind: TokenKind) -> String {
    let hash = Sha256::digest(identifier.as_bytes());
    let short = &format!("{:x}", hash)[..4];   // first 4 hex chars
    let prefix = match kind {
        TokenKind::Function => "_fn",
        TokenKind::Class    => "_cls",
        TokenKind::Variable => "_v",
    };
    format!("{}{}", prefix, short)
}

// getUserPaymentToken (function) -> _fn4a2b
// PaymentVaultClient  (class)    -> _cls8d3f
// userId              (variable) -> _v9x2c

The Rust addon ships as a prebuilt binary inside the npm package. No build toolchain required on the developer machine.

Security model

Pretense is local-first. Nothing about your source code is ever sent to Pretense servers. The mutation map lives in memory and on disk in your project directory.

DataWhere it livesSent externally
Source identifiers (real names)In-memory MutationMap + .pretense/Never
Mutated identifiersOutbound LLM requestYes -- to LLM API only
Comments and stringsOutbound LLM requestYes -- verbatim
Detected secretsBlocked before transmissionNever
Audit log.pretense/audit.db (SQLite)Never (Pro: cloud export optional)
Mutation map.pretense/mutations.jsonNever
Was this page helpful?
How It Works — Pretense Docs | Pretense