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

Uploaded Python 3Windows x86-64

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

Uploaded Python 3macOS 11.0+ ARM64

open_harness_dupelens-0.3.2-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.2-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 71d522d0a4bb659b356647085e00cf62c8bbbbfc88aa4afaa50cbb2710255cd2
MD5 86d2e3b83bc1c3786ba9dd22874b5822
BLAKE2b-256 52cef75b5bdf1830e7d4be806ae8fc10d15fe6f037303332f272bc587c25e493

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.2-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ca65ecb72b5bc648078cf702f3a45c6a437d6bb6d0fd2e19053c06ef0630b12b
MD5 9add1c52db44f85786c3e000404b0431
BLAKE2b-256 295c6d87a2d7a22860c060b0c26de66abb2cf459129e03f8b63188cd284529c7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 43c9c857a3b1661c8d8acfef626eef42c086fdbde933f10eb36133546900bc3d
MD5 9901a4148bf27972e0e903312ca629d6
BLAKE2b-256 81e508b45cbefdc5069c8ece8e6f6ceebf63f48aa3dc622f3e3bd5bd9f6c6e4e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for open_harness_dupelens-0.3.2-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 d369df4e102dd4fdb861ec77698417a7199e4d01d1e9a6401d31287d0b4ade4a
MD5 a56147112e025d53c783558128ac1482
BLAKE2b-256 ed8a46ddd4c168687cbfc61977827245dba5ce4f8fb030153a00784239405d5e

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.1

4 files

0.4.0

4 files

This release

0.3.2 This release

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