Skip to main content

open-harness-dupelens

Code duplication detector. Uses Rabin-Karp rolling-hash fingerprinting over tokenized source — strings and comments are stripped before hashing to reduce false positives. Language-agnostic (Go, TS, JS, Python, Rust, Java, etc.). Single native binary, zero runtime dependencies.

Part of the open-harness monorepo. Español abajo.

Same tool, other ecosystems: also available on npm (@open_harness/dupelens) and on Packagist (open-harness/dupelens). Identical binary, identical config; pick the registry that matches your stack.

Install

pip install open-harness-dupelens

pip picks the right native wheel for your platform automatically (Linux x86_64, macOS arm64, macOS x86_64, Windows x86_64). Each wheel embeds the Go binary — no runtime deps.

Usage

dupelens check                  # scan current directory with defaults
dupelens check --fail           # exit 1 if duplicates found (CI / git hooks)
dupelens check --min-tokens 30  # override the rolling window size
dupelens check --format=json    # JSON output for tooling integrations
dupelens check --dir ./src      # scan a specific directory
dupelens check --verbose        # print timings to stderr
dupelens check --no-color       # plain console output
dupelens init                   # generate a default dupelens.json
dupelens version                # print version

Configuration

Place a dupelens.json at the repo root:

{
  "default": {
    "minTokens": 50,
    "minLines": 5
  },
  "rules": [
    { "pattern": "**/*_test.go",     "skip": true },
    { "pattern": "**/migrations/**", "skip": true }
  ],
  "exclude": ["node_modules", "vendor", ".git", "dist", "build"]
}
  • minTokens — window size of the rolling hash. Higher values catch only larger duplications.
  • minLines — filters short matches (e.g. back-to-back identical imports).
  • rules — per-pattern skip. The first matching entry wins.

Alternative: configure inside pyproject.toml or the dedicated dupelens.json

If you prefer not to keep a separate dupelens.json, add a dupelens key in your package.json with the same shape:

{
  "name": "my-project",
  "dupelens": {
    "default": { "minTokens": 50, "minLines": 5 },
    "rules": [{ "pattern": "**/*_test.go", "skip": true }],
    "exclude": ["node_modules", "dist"]
  }
}

Precedence: --config <path> > dupelens.json > package.json key > built-in defaults. CLI flags (--min-tokens, --format, etc.) always win.

Output (console)

DUPLICATES (2 match(es) found in 87 files):

  src/auth.go:42-58  <->  src/users.go:12-28  (35 tokens)
  | func validate(input string) error {
  | ...
  src/db.go:1-10  <->  src/cache.go:1-10  (15 tokens)

SUMMARY: 2 match(es) across 87 files
Top duplicated files:
  - src/auth.go  (1 match(es))

Output (JSON)

{
  "scannedFiles": 87,
  "matchCount": 2,
  "matches": [
    {
      "fileA": "src/auth.go", "startLineA": 42, "endLineA": 58,
      "fileB": "src/users.go", "startLineB": 12, "endLineB": 28,
      "tokens": 35
    }
  ],
  "summary": {
    "topDuplicatedFiles": [{ "file": "src/auth.go", "count": 1 }]
  }
}

Integrations

# Husky pre-commit
dupelens check --fail
# GitHub Actions
- name: Run dupelens
  run: npx @open_harness/dupelens check --fail

Why Rabin-Karp over AST?

  • Zero dependencies: no language-specific parsers to ship per language.
  • Language-agnostic: the same binary scans Go, TypeScript, Python, Rust, Java, etc.
  • Fast: rolling hash detects matches in O(n) over the token stream.

The trade-off is documented in ADR-012.

Limitations (v0.2.0)

  • Detects only literal or near-literal duplication (token-by-token). Refactors with renamed variables are not flagged — that requires AST analysis.
  • The algorithm is binary (match or no match); there is no similarity threshold flag.
  • Per-rule minTokens override does not work cross-file because window sizes must be uniform. Use rules.skip to exclude patterns entirely.

Exit codes

Code Meaning
0 No duplicates (or --fail not passed)
1 Duplicates found and --fail was passed, or config error

Español

Detector de duplicación de código. Usa fingerprinting Rabin-Karp (hash rodante) sobre el código tokenizado — los strings y comentarios se eliminan antes del hashing para reducir falsos positivos. Agnóstico al lenguaje (Go, TS, JS, Python, Rust, Java, etc.). Un solo binario nativo, cero dependencias.

Parte del monorepo open-harness.

Instalación

pip install open-harness-dupelens

pip descarga automáticamente la wheel nativa correcta para tu plataforma.

Uso

dupelens check                  # escanea con defaults
dupelens check --fail           # exit 1 si hay duplicados (CI / git hooks)
dupelens check --min-tokens 30  # cambia el tamaño de ventana del hash rodante
dupelens check --format=json    # salida JSON para integraciones
dupelens check --dir ./src      # escanea un directorio específico
dupelens check --verbose        # imprime timings en stderr
dupelens check --no-color       # consola sin colores
dupelens init                   # genera un dupelens.json por defecto
dupelens version                # imprime la versión

Configuración

Colocá un dupelens.json en la raíz del repo (ver ejemplo arriba).

  • minTokens — tamaño de la ventana del hash rodante. Valores más altos detectan solo duplicaciones más grandes.
  • minLines — filtra matches cortos (ej. imports idénticos consecutivos).
  • rulesskip por patrón. Gana la primera regla coincidente.

Alternativa: configurar dentro de pyproject.toml o dupelens.json

Si preferís no tener un dupelens.json separado, agregá una key dupelens en tu package.json con la misma forma del archivo dedicado. Precedencia: --config <path> > dupelens.json > key en package.json > defaults. Los flags CLI (--min-tokens, --format, etc.) siempre ganan.

Salida

Soporta consola coloreada y JSON estructurado. Ver ejemplos arriba.

Integraciones

Sirve con Husky, lefthook o GitHub Actions usando los mismos snippets de la sección en inglés.

Por qué Rabin-Karp en vez de AST

  • Cero dependencias: no hay que enviar parsers por lenguaje.
  • Agnóstico: el mismo binario escanea Go, TypeScript, Python, Rust, Java, etc.
  • Rápido: el hash rodante detecta matches en O(n) sobre el stream de tokens.

El trade-off está documentado en ADR-012.

Limitaciones (v0.2.0)

  • Solo detecta duplicación literal o cuasi-literal (token a token). Refactors con variables renombradas no se detectan — eso requiere análisis AST.
  • El algoritmo es binario (hay match o no hay); no existe un flag de umbral de similitud.
  • El override de minTokens por regla no funciona entre archivos porque la ventana debe ser uniforme. Usá rules.skip para excluir patrones por completo.

Códigos de salida

Código Significado
0 Sin duplicados (o no se pasó --fail)
1 Hay duplicados con --fail, o error de configuración

License

MIT — see the main repository.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

open_harness_dupelens-0.4.1-py3-none-win_amd64.whl (2.2 MB view details)

Uploaded Python 3Windows x86-64

open_harness_dupelens-0.4.1-py3-none-macosx_11_0_arm64.whl (1.0 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

open_harness_dupelens-0.4.1-py3-none-macosx_10_9_x86_64.whl (1.1 MB view details)

Uploaded Python 3macOS 10.9+ x86-64

File details

Details for the file open_harness_dupelens-0.4.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.4.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 0716a498691846eac890369774631a33f6a1ac4b677f55179e72c42b04b4c7ed
MD5 f8e9e89204ce9e0416a4e6c2357ad351
BLAKE2b-256 32311f52b6c6642b910f7031769f62bdfb03a53399aa426a693f4e02adfe896e

See more details on using hashes here.

File details

Details for the file open_harness_dupelens-0.4.1-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.4.1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 87975ad1f52d8631c850646d6684f9dee474d620167c7a4661949631f1b76a05
MD5 ca1dc472d69c4800eee4132aa5aeae35
BLAKE2b-256 247f30597d2593678f25218658fc977ec61df7667748cc5a7ff0c4a26f127cf0

See more details on using hashes here.

File details

Details for the file open_harness_dupelens-0.4.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.4.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a53669cccf26c40ac1fb2be66e31e718929e081934eae61d8cb9cdb0d476e05e
MD5 f139663f10f69d709df1b384351068a3
BLAKE2b-256 2ea67c66769c97fe16847f053166af86e9491fc979eff26a361604a73e1a9b13

See more details on using hashes here.

File details

Details for the file open_harness_dupelens-0.4.1-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.4.1-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 c44702db7225db638c9b0b88bdd232dfa28422a7a7aec3a570b638dc2858fb16
MD5 39ff84d9acbf6fd32dc0ce7b8337474c
BLAKE2b-256 460b6585af501963784161a8a06a9d3a98b7d45231cfc48f64829248b9c3adc4

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.4.1 This release

4 files

0.4.0

4 files

0.3.2

4 files

0.3.1

4 files

0.3.0

4 files

0.2.1

4 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page