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.3.0-py3-none-win_amd64.whl (2.2 MB view details)

Uploaded Python 3Windows x86-64

open_harness_dupelens-0.3.0-py3-none-macosx_11_0_arm64.whl (988.4 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

open_harness_dupelens-0.3.0-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.3.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 c44fa43dedaa70f99cdcac0e362bba0cb4f65e7fa030cb8f4bfa64a3abe0f7e4
MD5 9cdc8656d98cbeab9dfc4af88e45c181
BLAKE2b-256 14067364d247c301cf2278dc557c17be9f374cd09f033b15f27451a71c6c5bec

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.0-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 cb49a37df7e525cf1c550a41069762ae4cbfdbf91ffdb72d87441763da84f2c9
MD5 fcaa38946032a05a6d4af4f96983b8a4
BLAKE2b-256 429a1c390f11187c0f82fc1734a4e69b1b5cfe226317683b6e65384250ef6b8e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e8c2868e2bc5dbaa5472094f60e3386d44189b354b4398996b1e9d22947c1a03
MD5 333d3477fda5ea463870f3a365cc9b80
BLAKE2b-256 1d22e6b9353cdb165decae59d20393d253234199132df8d90c298de6017e8c36

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.0-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 7b334430f58fb8d23cca2e61d42d114061cc0cbb0f9b98d92d77963767ca359c
MD5 9fd5fa62fd7eb5ce64e0db0941a61d30
BLAKE2b-256 97419b89facb67e04fc2a4fdee5e113937f0def5d6cbc54d076e9c0368d7b51e

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.1

4 files

0.4.0

4 files

0.3.2

4 files

0.3.1

4 files

This release

0.3.0 This release

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