TypeScript slice of candor-spec — the derivability proof: written from the spec alone, 20/20 on the shared conformance oracle.
- ✓Open-source license (Apache-2.0)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
git clone https://github.com/tombaldwin/candor-tsTools overview
# candor-ts
<p align="center"><img src="https://raw.githubusercontent.com/tombaldwin/candor/main/assets/beaky.svg" alt="Beaky, the candor canary" width="180"></p>
**candor for TypeScript: per-function side effects, transitively, with a deterministic policy
gate.** candor-ts resolves every call through the TypeScript compiler API and reports, for each
function in your project, which effects it can reach — `Net`, `Fs`, `Db`, `Exec`, `Env`, `Clock`,
… — **including effects inherited through any chain of calls across files**, with a disclosed
`Unknown` wherever resolution fails (a callback value, an `any`-typed callee — never silently
pure). A [candor-spec](https://github.com/tombaldwin/candor-spec) implementation, sibling of the
[Rust](https://github.com/tombaldwin/candor-rust) and
[JVM](https://github.com/tombaldwin/candor-java) engines.
**Site:** [candor.poly.io](https://candor.poly.io) — the measured case in five minutes.
```sh
npm install # typescript + @types/node
node scan.mjs <project-dir> # tsconfig.json honored; tests excluded; writes
# <dir>/.candor/report.json + .callgraph.json
node scan.mjs . --policy .candor/policy # the §6.2 gate: exit 1 on violation, 2 if unreadable
node scan.mjs . --gate-json gate.json # + the structured verdict {spec, ok, violations} (§3.3)
CANDOR_BASELINE=saved.json node scan.mjs . # AS-EFF-005 guard: exit 1 if an existing fn GAINED an
# effect vs the saved report; 2 if it can't evaluate
node scan.mjs --version # installed build + spec contract (offline), + upgrade line
node query.mjs show .candor/report db.save 1 # a function's effects (match ladder)
node query.mjs where .candor/report Net 1 # direct sources vs inheritors
node query.mjs callers .candor/report db.save 1 # the blast radius (transitive callers)
node query.mjs map .candor/report 1 # module → effects overview
node query.mjs containment .candor/report # §6.1 boundary-effect dispersion (+ baseline = ratchet)
node query.mjs blindspots .candor/report # the Unknown SOURCES, ranked by blast radius
node query.mjs whatif .candor/report db.save Net policy # pre-edit gate verdict (exit 1)
node query.mjs diff .candor/report baseline 1 # per-function effect delta (exit 1 on a gain;
# a baseline from a DIFFERENT build ⇒ disclosed ⚠ + exit 0)
node query.mjs gate --report dep/.candor/report.json --policy arch.policy --gate-json -
# ⟨0.24⟩ apply a policy to an EXISTING report, NO scan
# (exit 0/1/2) — the supply-chain verb: gate a
# DEPENDENCY's published report without its source.
# Reads that report and nothing else; a rule the wire
# cannot answer (`forbid`, `allow`, a class-scoped
# `deny` with the scoping field absent) is REFUSED
# with exit 2, never evaluated on partial evidence.
```
A checked-in **`.candor/config`** (spec §3.4) replaces the env wiring — `policy arch.policy` /
`baseline <report.json>` / `deps <report paths>` one per line, discovered by walking up from the
scan target; relative values resolve against the config's repo, so CI is "point at the repo". A
configured-but-unusable config/policy/baseline fails loud (exit 2), never silently gateless.
The scan-time **baseline guard** (AS-EFF-005, spec §7) makes effect *regressions* un-shippable:
point `CANDOR_BASELINE` (or the config's `baseline` key) at a saved report, and any existing
function that **gained** an effect fails the scan — exit 1, the records join the `--gate-json`
verdict. New functions are exempt (reviewed as new code, not a regression). The guard is
fail-closed like the policy gate: a present-but-unparseable baseline, or one produced by a
different engine build (§2.1 — an engine upgrade is baseline-invalidating), exits 2 **without
evaluating**; only a genuinely absent file is a one-line note (guard not active). Keep the two
surfaces straight: `query diff` is the read-only comparison — it *discloses* a producing-build
mismatch (⚠, exit 0) and informs; the scan-time guard is the gate-grade fail-closed surface, the
one CI should hold. Semantics mirror the reference engine (candor-java) exactly.
**Staying current:** check your installed version and upgrade — [candor/AGENTS.md §2a](https://github.com/tombaldwin/candor/blob/main/AGENTS.md#2a-staying-current--check-the-version-upgrade). `npx -y candor-ts --version` prints the build, the spec, and the upgrade one-liner (offline; candor never phones home).
Function names are module-qualified with `.` segments (`src.db.save`), so policy scopes read
naturally. A function declared inside a TS `namespace` carries the namespace segments in `fn` and
the callgraph keys (`src.util.Ns.helper`) — so layer policies on namespaces bite — while the §2
`hash` join key keeps the bare local name; builds before 0.8.7 omitted the segments, so crossing
that line invalidates saved baselines (regenerate them). A `pure <scope>` rule forbids every
*effect* but not `Unknown` — the §4 trust marker is uncertainty, not an effect (matching the
reference engine, candor-java); `deny Unknown <scope>` is the explicit knob for boundaries that
must also exclude the unverifiable case.
```text
# .candor/policy
deny Net domain # the domain layer reaches no network, even through helpers
pure parse # parsing is effect-free
allow Db in db orders audit_log # the db layer touches ONLY these tables
allow Net in billing api.stripe.com # billing talks ONLY to Stripe
forbid domain -> infra # the domain layer must not depend on infra
```
The report carries the four **literal surfaces** where a declaration makes them decidable —
`hosts` at `Net` calls, `tables` at `Db` calls (SQL table positions, mirroring the Rust/JVM
extractors exactly, **plus TypeORM's `@Entity("user")` declarations** read through the receiver's
`Repository<T>` type argument), `cmds` at `Exec`, path-shaped `paths` at `Fs` — never from a
runtime-computed value, propagated transitively, enforced by the `allow` rules above. On a real
Nest app this makes table-level policy live: `allow Db in article.service article comments` flags
the service reaching `user` and `follows`.
**The classifier** is curated (the same under-report-and-say-so posture as the other engines): the
Node builtins (`fs`, `net`/`http`/`tls`, `dns`, `child_process`, `worker_threads`, `node:sqlite`,
`node:vm`, `process.env`, the clock), the **web network globals** (`fetch`, `XMLHttpRequest`,
`navigator.sendBeacon`, and `WebSocket`/`EventSource` — construction, `send` and `close`, charged
identically whether they resolve through `lib.dom` or through `@types/node`'s `undici-types`
re-export), the HTTP/queue/mail tier (axios/got/node-fetch/undici/ws/
socket.io/nodemailer, gaxios + googleapis-common + google-auth-library, stripe, @sentry/*,
posthog-node, bull/bullmq), the database drivers (pg/mysql2/mongodb/redis/ioredis/sqlite3/
better-sqlite3/knex) **and the ORM tier** (TypeORM — with `@Entity("…")` table extraction —
Prisma, Mongoose, Sequelize, drizzle-orm), plus execa/cross-spawn/shelljs/open, fs-extra/
graceful-fs/rimraf/glob/chokidar, dotenv, winston/pino/bunyan. An unlisted npm package contributes
nothing — candor never guesses an effect — but the scan **names it**: the receipt's coverage-ledger
line (marker: `classifier doesn't cover`) lists every package the code demonstrably calls that
candor's classifier neither classifies nor has reviewed-pure, and each function carries the
`invisible` list it (transitively) reaches.
⟨0.32⟩ **Node core is the one part of the classifier that is a denylist, not a curated list.** The
builtins are a finite set, so every module's surface is reviewed and a core member that is neither
classified nor reviewed effect-free reads `Unknown[native:<module>.<member>]` — never pure. That is
why `v8.writeHeapSnapshot()` is `Fs`, `inspector.open()` is `Net`, and `repl.start()`,
`process.dlopen()` and `new worker_threads.Worker(file)` are visible holes rather than silence.
## MCP server — candor as agent ground truth
`candor-ts-mcp` exposes the read-only queries as an [MCP](https://modelcontextprotocol.io) server, so
a coding agent can ask **"if I change this, what's the runtime blast radius?"** or **"what reaches the
network?"** and get deterministic ground truth from a precomputed report — instead of burning tokens
tracing the call graph by hand (the measured ~700–2000× token win on blast-radius questions).
```jsonc
// in an MCP client config — point it at a report you've already scanned
{ "command": "npx", "args": ["-y", "candor-ts-mcp"],
"env": { "CANDOR_REPORT": ".candor/report.myPkg.scan" } }
```
Tools: `candor_impact` (backward blast radius), `candor_reachable` (what runs at runtime),
`candor_where` (effect surface), `candor_path` (how an effect is reached), `candor_callers`,
`candor_show`, `candor_map`, `candor_containment`, `candor_blindspots`, `candor_whatif` (pre-edit
gate check — a given-but-unreadable policy is a loud error, never a clean verdict), `candor_gate`
(the checked-in `.candor/config` policy verdict), `candor_diff`/`candor_gains` (baseline deltas).
Each takes an optional `report` prefix (else `$CANDOR_REPORT`); `--root <dir>` locks the server to
one workspace. The server is **query-only** — it never scans (the analyzer self-boundary, spec
§7.12: an agent or a hook produces the report; the server reads it, Fs only). The query logic is
the shared `query-core.mjs`, the same answeWhat people ask about candor-ts
What is tombaldwin/candor-ts?
+
tombaldwin/candor-ts is tools for the Claude AI ecosystem. TypeScript slice of candor-spec — the derivability proof: written from the spec alone, 20/20 on the shared conformance oracle. It has 0 GitHub stars and its last recorded update is dated 2026-09-13.
How do I install candor-ts?
+
You can install candor-ts by cloning the repository (https://github.com/tombaldwin/candor-ts) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is tombaldwin/candor-ts safe to use?
+
Our security agent has analyzed tombaldwin/candor-ts and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.
Who maintains tombaldwin/candor-ts?
+
tombaldwin/candor-ts is maintained by tombaldwin. The last recorded GitHub activity is dated 2026-09-13, with 0 open issues.
Are there alternatives to candor-ts?
+
Yes. On ClaudeWave you can browse similar tools at /categories/tools, sorted by popularity or recent activity.
Deploy candor-ts to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/tombaldwin-candor-ts)<a href="https://claudewave.com/repo/tombaldwin-candor-ts"><img src="https://claudewave.com/api/badge/tombaldwin-candor-ts" alt="Featured on ClaudeWave: tombaldwin/candor-ts" width="320" height="64" /></a>More Tools
A single CLAUDE.md file to improve Claude Code behavior, derived from Andrej Karpathy's observations on LLM coding pitfalls.
An AI skill that provides design intelligence for building professional UI/UX across multiple platforms.
🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman
CLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies
The fastest, litest AI Gateway. Rust core with Python SDK. Call 100+ LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]
Use Claude Code, Codex, Pi, and OpenCode (and 6 other harnesses) for free (1.3B+ free tokens) from your terminal, app, IDE, or phone, and now from the browser with native browser sessions (multi-harness + multi-model) like OpenClaw (voice supported + ToS friendly)