Master reference for porting the Golden Suite (GoldenCheck, GoldenFlow, GoldenMatch) from Python to TypeScript. Written so a fresh Claude Code session can pick up any repo and port it with full context.
| Repo | Python LOC | TS LOC | TS Files | Status |
|---|---|---|---|---|
| GoldenCheck | ~11,700 (86 files) | ~10,000 (72 files) | packages/goldencheck-js/ |
Done |
| GoldenFlow | ~5,350 (61 files) | ~5,234 (54 files) | packages/goldenflow-js/ |
Done |
| GoldenMatch | ~59,500 (282 files) | — | packages/goldenmatch-js/ |
Next |
repo-root/
├── goldenmatch/ # Python package (existing, untouched)
├── packages/goldenmatch-js/ # TypeScript port (new)
│ ├── package.json # npm: "goldenmatch"
│ ├── tsconfig.json # Strict TypeScript
│ ├── tsup.config.ts # Build: 4 entry points, dual ESM+CJS
│ ├── vitest.config.ts # Test runner
│ ├── src/
│ │ ├── index.ts # Re-exports core
│ │ ├── cli.ts # Commander.js CLI
│ │ ├── core/ # Edge-safe (browsers, Workers, Edge Runtime)
│ │ │ ├── index.ts # Public API surface
│ │ │ ├── types.ts # All interfaces, enums, factory functions
│ │ │ ├── data.ts # TabularData (Polars replacement)
│ │ │ └── ... # Module-per-module port
│ │ └── node/ # Node 20+ only
│ │ ├── index.ts # Re-exports core + Node-only
│ │ ├── reader.ts # CSV/Parquet file reading
│ │ └── ... # MCP, TUI, DB, etc.
│ └── tests/
│ ├── unit/ # Per-module tests
│ ├── parity/ # Python-TS equivalence tests
│ └── smoke.test.ts # Basic import sanity
├── package.json # Root orchestrator (NOT a workspace)
└── scripts/
└── gen_parity_goldens.py # Python generates golden outputs for TS parity tests
{
"name": "goldenmatch",
"type": "module",
"exports": {
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" },
"./core": { "types": "./dist/core/index.d.ts", "import": "./dist/core/index.js", "require": "./dist/core/index.cjs" },
"./node": { "types": "./dist/node/index.d.ts", "import": "./dist/node/index.js", "require": "./dist/node/index.cjs" }
},
"bin": { "goldenmatch-js": "./dist/cli.cjs" },
"engines": { "node": ">=20" },
"dependencies": { "commander": "^13.0.0" },
"peerDependencies": { "yaml": "*" },
"peerDependenciesMeta": { "yaml": { "optional": true } },
"devDependencies": { "@types/node": "^20.0.0", "rimraf": "^5.0.0", "tsup": "^8.5.1", "typescript": "^5.4.0", "vitest": "^4.1.0", "yaml": "^2.7.0" }
}{
"compilerOptions": {
"target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler",
"lib": ["ES2022"], "strict": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true, "noImplicitOverride": true,
"noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true,
"esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true,
"skipLibCheck": true, "declaration": true, "declarationMap": true,
"sourceMap": true, "outDir": "dist", "types": ["node"]
},
"include": ["src/**/*.ts", "tests/**/*.ts", "*.config.ts"],
"exclude": ["dist", "node_modules"]
}import { defineConfig } from "tsup";
export default defineConfig({
entry: { index: "src/index.ts", "core/index": "src/core/index.ts", "node/index": "src/node/index.ts", cli: "src/cli.ts" },
format: ["esm", "cjs"], dts: true, sourcemap: true, clean: true, target: "node20", splitting: false, treeshake: true,
});{ "private": true, "scripts": { "install:js": "npm --prefix packages/goldenmatch-js install", "build:js": "npm --prefix packages/goldenmatch-js run build", "test:js": "npm --prefix packages/goldenmatch-js run test", "typecheck:js": "npm --prefix packages/goldenmatch-js run typecheck" } }- NEVER import
node:fs,node:path,node:http, or useprocess.on() - NEVER use
require()— onlyimport(tsup handles CJS conversion) - If a module needs Node APIs, put it in
src/node/— this was a bug in the GoldenFlow port that had to be fixed during review (history.ts was in core withrequire()) - LLM providers: use raw
fetch()— edge-safe, no SDK needed
- NEVER use bare
catch {}— always log:catch (e) { console.warn("...", e instanceof Error ? e.message : String(e)); } - MCP server
handleToolmust wrap ALL tool cases in try-catch that returns JSON error — this was a critical bug in GoldenFlow's first draft - Config loader must throw on invalid YAML (non-object), not silently return empty config
- LLM corrector must log HTTP status on
!resp.okand validate JSON response structure - REST API server must validate paths to prevent traversal (resolve + check startsWith cwd)
- NEVER coerce leading-zero strings to numbers:
"01234"(zip code) must stay a string - Guard:
if (raw.length > 1 && raw[0] === "0" && raw[1] !== ".") return raw; - Handle quoted fields with commas, escaped quotes (
""inside quoted field) - Return
nullfor empty CSV fields
- Always use UTC methods (
getUTCFullYear,getUTCMonth,getUTCDate, etc.) new Date(val)parses into local timezone — extract with UTC methods for consistent behavior across environments- Use millisecond arithmetic for date_shift (
d.getTime() + days * 86_400_000), notsetDate()
TabularData.column()usesisNullish()which converts "N/A"→null — this inflates null counts- Add
rawColumn()that preserves original values for profiling - Transforms must receive raw values (not pre-filtered), so the transform engine should NOT use
toColumnValue()when extracting column values for transforms - Row comparison after transform: compare
row[column] ?? nullvsnewValues[i], nottoColumnValue(row[column])which masks changes
- NEVER
Math.min(...array)orMath.max(...array)— crashes on >65K elements. Use a for-loop. - PRNG is Mulberry32 (NOT Python's Mersenne Twister) — sampling results will differ
Row = Readonly<Record<string, unknown>>— acceptunknownvalues, coerce in getters- All interfaces are
readonly— use spread{ ...obj, ...overrides }for updates exactOptionalPropertyTypes: truemeansfield?: Trequires explicitundefined- Export types with
export type { ... }for tree-shaking
- REST API and MCP server must sanitize file paths:
resolve(path)thenstartsWith(cwd) - Never expose internal filesystem error messages to API clients
npm run typecheck— 0 errorsnpm run test— all passnpm run build— clean ESM + CJS + .d.tsgit tag goldenmatch-js-v0.1.0 && git push origin goldenmatch-js-v0.1.0- Requires
NPM_TOKENGitHub secret
GoldenMatch is ~10x larger than GoldenFlow (~59,500 LOC vs ~5,350). A full port is NOT practical in one session. Recommended: phased approach.
Target: dedupe_df() and match_df() working with exact + fuzzy matching.
| Python module | TS location | Priority | Notes |
|---|---|---|---|
config/schemas.py |
src/core/types.ts |
P0 | GoldenMatchConfig, MatchkeyConfig, BlockingConfig |
core/scorer.py |
src/core/scorer.ts |
P0 | Field scorers: jaro_winkler, levenshtein, token_sort, exact |
core/matchkey.py |
src/core/matchkey.ts |
P0 | Matchkey builder + transforms |
core/blocker.py |
src/core/blocker.ts |
P0 | Static blocking (hash-based grouping) |
core/cluster.py |
src/core/cluster.ts |
P0 | Union-Find + MST splitting |
core/pipeline.py |
src/core/pipeline.ts |
P0 | Orchestrator: block → score → cluster |
core/golden.py |
src/core/golden.ts |
P0 | Golden record builder (merge strategies) |
_api.py |
src/core/api.ts |
P0 | High-level dedupe(), match() functions |
Key algorithm to implement:
Input rows
→ build blocking keys (hash on transformed fields)
→ group into blocks (Map<string, Row[]>)
→ for each block: score all pairs (NxN with field-level scoring)
→ filter pairs above threshold
→ cluster matched pairs (Union-Find)
→ optionally split weak clusters (MST bottleneck)
→ build golden records (most_complete / majority_vote / recency)
→ return DedupeResult
Fuzzy matching (edge-safe): Implement Jaro-Winkler and Levenshtein in pure TS (no native deps). RapidFuzz is Python-only. Reference implementations:
- Jaro-Winkler: ~40 lines, well-documented algorithm
- Levenshtein ratio:
1 - (distance / max(len_a, len_b))— GoldenFlow already has this inauto-correct.ts
| Python module | TS location | Notes |
|---|---|---|
config/loader.py |
src/core/config/loader.ts |
YAML → GoldenMatchConfig |
config/wizard.py |
src/node/init-wizard.ts |
Interactive config generation |
core/autoconfig.py |
src/core/autoconfig.ts |
Auto-generate config from data |
cli/main.py + subcommands |
src/cli.ts |
Commander.js (dedupe, match, demo, profile, etc.) |
connectors/ |
src/node/connectors/ |
CSV/JSON file I/O (reuse GoldenFlow pattern) |
| Python module | TS location | Notes |
|---|---|---|
core/probabilistic.py |
src/core/probabilistic.ts |
Fellegi-Sunter EM training |
core/llm_scorer.py |
src/core/llm/scorer.ts |
Edge-safe via fetch() |
core/llm_cluster.py |
src/core/llm/cluster.ts |
In-context clustering |
core/llm_budget.py |
src/core/llm/budget.ts |
Cost tracking |
core/explain.py |
src/core/explain.ts |
NL pair/cluster explanations |
| Python module | TS location | Notes |
|---|---|---|
core/ann_blocker.py |
src/core/ann-blocker.ts |
Needs FAISS.js or similar |
core/learned_blocking.py |
src/core/learned-blocking.ts |
Decision tree predicates |
core/cross_encoder.py |
src/core/cross-encoder.ts |
Needs ONNX Runtime or Transformers.js |
core/embedder.py |
src/core/embedder.ts |
Sentence embeddings |
pprl/ |
src/core/pprl/ |
Privacy-preserving record linkage |
core/graph_er.py |
src/core/graph-er.ts |
Multi-table ER |
core/memory/ |
src/core/memory/ |
Learning memory |
core/review_queue.py |
src/core/review-queue.ts |
Human-in-the-loop |
| Python module | TS location | Notes |
|---|---|---|
mcp/server.py |
src/node/mcp/server.ts |
30 MCP tools |
a2a/server.py |
src/node/a2a/server.ts |
Agent-to-Agent |
api/server.py |
src/node/api/server.ts |
REST API |
tui/ |
src/node/tui/ |
Terminal UI (ink or blessed) |
db/ |
src/node/db/ |
Postgres, DuckDB connectors |
output/ |
src/core/output/ |
Report generation |
backends/ |
src/node/backends/ |
Ray → Worker threads, DuckDB → WASM |
| Python module | TS location | Notes |
|---|---|---|
connectors/snowflake.py |
src/node/connectors/snowflake.ts |
Snowflake SDK |
connectors/bigquery.py |
src/node/connectors/bigquery.ts |
BigQuery client |
connectors/databricks.py |
src/node/connectors/databricks.ts |
Databricks SQL |
connectors/salesforce.py |
src/node/connectors/salesforce.ts |
Simple Salesforce |
// src/core/types.ts — key interfaces
interface DedupeResult {
readonly goldenRecords: readonly Row[];
readonly clusters: ReadonlyMap<number, Cluster>;
readonly dupes: readonly Row[];
readonly unique: readonly Row[];
readonly stats: DedupeStats;
readonly scoredPairs: readonly ScoredPair[];
readonly config: GoldenMatchConfig;
}
interface MatchResult {
readonly matched: readonly Row[];
readonly unmatched: readonly Row[];
readonly stats: MatchStats;
}
interface Cluster {
readonly members: readonly number[];
readonly size: number;
readonly pairScores: ReadonlyMap<string, number>; // "id_a:id_b" → score
readonly confidence: number;
readonly quality: "strong" | "weak" | "split";
}
interface ScoredPair {
readonly idA: number;
readonly idB: number;
readonly score: number;
readonly fieldScores: Readonly<Record<string, number>>;
}
interface GoldenMatchConfig {
readonly matchkeys: readonly MatchkeyConfig[];
readonly blocking: BlockingConfig;
readonly threshold: number;
readonly goldenRules: GoldenRulesConfig | null;
readonly llmScorer: LLMScorerConfig | null;
}
interface MatchkeyConfig {
readonly fields: readonly MatchkeyField[];
readonly type: "exact" | "weighted" | "probabilistic";
}
interface MatchkeyField {
readonly field: string;
readonly transforms: readonly string[];
readonly scorer: string; // "jaro_winkler", "levenshtein", "exact", etc.
readonly weight: number;
}
interface BlockingConfig {
readonly strategy: "static" | "adaptive" | "sorted_neighborhood" | "multi_pass";
readonly keys: readonly string[];
readonly maxBlockSize: number;
}Given the size (~59K LOC), use aggressive parallelization:
Wave 1 (foundation — 3 parallel agents):
- Types + TabularData + config schema/loader
- Fuzzy scorers (Jaro-Winkler, Levenshtein, token_sort, exact, ensemble)
- Matchkey builder + transforms
Wave 2 (core engine — 3 parallel agents):
- Blocker (static + adaptive)
- Cluster (Union-Find + MST)
- Golden record builder (merge strategies)
Wave 3 (orchestration — 2 parallel agents):
- Pipeline orchestrator + high-level API (
dedupe(),match()) - Config autoconfig + explain
Wave 4 (integrations — 3 parallel agents):
- CLI (Commander.js, 15+ commands)
- MCP server (30 tools) + REST API
- LLM scorer + budget + cluster
Wave 5 (advanced — deferred):
- Probabilistic, ANN, embeddings, cross-encoder, PPRL, graph ER, memory, TUI, DB, cloud connectors
| Phase | Python LOC | Expected TS LOC | Priority |
|---|---|---|---|
| Phase 1: Core engine | ~5,000 | ~4,000 | Must-have |
| Phase 2: Config & CLI | ~3,000 | ~2,500 | Must-have |
| Phase 3: Probabilistic + LLM | ~4,000 | ~3,500 | Should-have |
| Phase 4: Advanced features | ~15,000 | ~12,000 | Nice-to-have |
| Phase 5: Infrastructure | ~10,000 | ~8,000 | Nice-to-have |
| Phase 6: Cloud connectors | ~5,000 | ~4,000 | Defer |
| Total (all phases) | ~42,000 | ~34,000 |
Recommended MVP (Phases 1-2): ~6,500 TS LOC, achievable in one session. This gives you dedupe(), match(), config, CLI, and file I/O — enough to be a usable npm package.
When in doubt, look at the completed ports:
- GoldenCheck JS:
D:\show_case\goldencheck\packages\goldencheck-js\ - GoldenFlow JS:
D:\show_case\goldenflow\packages\goldenflow-js\
Key files to reference:
types.ts— how Python dataclasses/Pydantic → TS interfaces + factory functionsdata.ts— TabularData (Polars replacement)transforms/registry.ts— decorator-based registration patternengine/transformer.ts— the orchestrator patternnode/connectors/file.ts— CSV parser with type coercion (leading-zero safe)node/mcp/server.ts— MCP tool definitions + handler with try-catch + path sanitizationcli.ts— Commander.js CLI with async command handlers
# After porting, same pattern as GoldenFlow:
gh auth switch --user benzsevern
# Set NPM_TOKEN (reuse the same token from goldenflow repo)
# gh secret set NPM_TOKEN --repo benzsevern/goldenmatch --body "<your-npm-token>"
# Branch, commit, PR, merge
git checkout -b ts-port
# ... do the work ...
git push -u origin ts-port
gh pr create --title "feat: TypeScript port of GoldenMatch" ...
gh pr merge N --squash
# Tag and release
git tag goldenmatch-js-v0.1.0
git push origin goldenmatch-js-v0.1.0
gh release create v2.x.x --title "vX.Y.Z — TypeScript Port" ...
# Update repo metadata
gh repo edit benzsevern/goldenmatch --description "Entity resolution toolkit — deduplicate, match, and create golden records. Python & TypeScript."
gh repo edit benzsevern/goldenmatch --add-topic typescript --add-topic nodejs --add-topic npm
# Switch back
gh auth switch --user benzsevern-mjh