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

Uploaded Python 3Windows x86-64

open_harness_dupelens-0.3.1-py3-none-macosx_11_0_arm64.whl (989.3 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

open_harness_dupelens-0.3.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.3.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 b5fa62c0b608d8f2ce1e9e3684257ff8cdba4c65462cbb42912ee4c109cb0ca2
MD5 e3cf1e24897b2b163526fe11cd6d29d5
BLAKE2b-256 e29e637ec798e7e0fa839f3106296654b8eecaa9691f3b7a4f072df9c61c6aa1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0eb0d3a0975c610a10a40a0e3f728ffbd8d3d624c54a1078fb192e714ebc129f
MD5 8cf46e40592f12424967564abc42e5c9
BLAKE2b-256 1a94affaa1f493cc2224988831b1c9c08b9aab980c88ebfd05b187d56ed764e6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2c0e234059ce31a75b22e1e6104877ce775e35ac8e3038d70133b9827f6ef9aa
MD5 29439fe982a5f1c9943085e47e0f648c
BLAKE2b-256 6c772fb7f610438da09b4ba110ef31f394c360411c167a2c8932cf999a722621

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.1-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 bc2446325d905dbb52e7bf2921d510373e2bbd1ecef3a4e95752b1fc79e80b64
MD5 cb63b16a08dc786a7d9746ecb3f1d2b2
BLAKE2b-256 b4b45bbdea056816d30f17402679252978e67f1ebac35a440890114960563487

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

This release

0.3.1 This release

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