Medir y mejorar
Todo en esta guía condensa en un comando:
npx harness-scoreEl escáner recorre tu repositorio (solo filesystem — sin LLM, sin red, sin telemetría), corre 36 checks deterministas en cualquier herramienta de IA y reporta un nivel de madurez con los gaps exactos al siguiente:
harness-score v1.0.0 /work/my-app
Maturity: L2 · Guided Score: 66/108 (61%)
Detected: Cursor, Claude Code
Context & Guides ████████████████░░░░ 80%
Skills & Commands █████████████░░░░░░░ 67%
Hooks & Guardrails ░░░░░░░░░░░░░░░░░░░░ 0%
...
To reach L3: sensors ≥ 60%; ci ≥ 50%Multi-herramienta: El escáner reconoce artefactos de harness de Cursor, Claude Code, Windsurf, Cline, Continue y otras herramientas vía semántica OR — si configuras cualquiera, Harness Score lo cuenta. Más en Soporte multi-harness.
Instalación
npx harness-score # no install
npm install -g harness-score # global binary
npm install --save-dev harness-score # pinned devDependencyTambién espejado en GitHub Packages (@paladini/harness-score) y JSR para proyectos Deno/Bun.
Usándolo como biblioteca
La CLI es un wrapper fino sobre API programática totalmente tipada — útil para dashboard custom, bot o herramienta que quiere el Report crudo en lugar de parsear salida de terminal:
import { score } from 'harness-score';
const report = score('/path/to/repo');
console.log(report.level.name, report.score.percent, report.dimensions);
// With global scopes: score(path, { scopeFlags: ['user'] })
console.log(report.effective.level.index);Report, Check, CheckResult, DimensionScore, LevelInfo, ScoreSnapshot y toda forma shipan como declaraciones TypeScript — resueltas vía campo "types" explícito, para editores y tsc sin config extra. Bloques de nivel inferior también se exportan, para lo que score() no cubre directamente:
import { score, computeDiff, renderMarkdown } from 'harness-score';
const report = score('/path/to/repo');
const markdown = renderMarkdown(report); // same renderer the CLI's --md usesConfiguración del scan
Por defecto el escáner mide solo la madurez del repositorio — el harness que viaja con el código y se reproduce en CI. Opcionalmente incluye árboles de harness a nivel usuario o compartidos para un puntaje effective (lo que el agente probablemente ve en el laptop de un desarrollador).
{
"scopes": { "user": false, "system": false },
"extraRoots": [{ "id": "team-shared", "path": "../shared-harness" }],
"gate": "maturity"
}Guárdalo como .harness-score.json en la raíz del scan, o pasa --config <file>. Referencia completa: Métricas y códigos — configuración.
harness-score --scope user # repo + ~/.cursor, ~/.claude, …
harness-score --scope user,system
harness-score --gate effective --min-level 2 # gate on effective scoreEl reporte de terminal muestra Maturity (repo) y Effective (cuando difieren). CI debe mantener gate: maturity salvo que corras intencionalmente en runners self-hosted con harness de usuario poblado.
Referencia CLI
harness-score [path] # human report (default: current directory)
harness-score --json # full report as JSON
harness-score --md report.md # markdown report (use "-" for stdout)
harness-score --badge badge.svg # SVG pill: harness + detected level (L0–L4)
harness-score --min-level 3 # exit 1 if below L3 — the CI gate (uses gate mode)
harness-score --diff base.json # compare maturity against a previous --json report
harness-score --config .harness-score.json
harness-score --scope user # include user-level harness in effective score
harness-score --gate maturity # or effective — which score --min-level usesSeguir puntuación en el tiempo
--diff <file> compara el scan actual contra reporte baseline guardado de ejecución --json anterior — deltas de nivel y puntuación, movimiento por dimensión y exactamente qué checks cambiaron:
harness-score --json > baseline.json # save today's report
# ...later, after changes...
harness-score --diff baseline.json # see what moved Compared to baseline:
Level: L2 · Guided → L3 · Sensing (+1)
Score: 61/108 (56%) → 84/108 (78%) (+22pp)
Sensors & Feedback 20% → 90% (+70pp)
Newly passing: SNS-01, SNS-02, SNS-04, CI-01, CI-02--diff funciona con --json (agrega current/baseline/diff al payload) y --md (agrega sección "Compared to baseline") — es lo que la GitHub Action usa para comentar "puntuación subió de L2 a L3" en PRs.
El plugin Cursor
Instala Harness Score desde su directorio de plugin en este repo (listado Cursor Marketplace enviado y pendiente de revisión — este enlace moverá allí cuando esté live) y obtienes:
/harness-audit— corre el escáner en el workspace abierto y hace que el agente presente el reporte con las principales remediaciones.- La skill
harness-engineering— cuando dices "arréglalo" o "mejora mi harness", el agente sabe escribir los artefactos faltantes siguiendo las recetas de esta guía.
El análisis en sí es siempre la CLI determinista; el modelo solo presenta resultados y aplica correcciones que pidas.
El gate de CI
Los harnesses regresan en silencio — alguien borra hooks.json en una limpieza, una rule se pudre. Traba tu nivel en CI:
- name: Harness gate
run: npx -y harness-score --min-level 3O usa la action empaquetada, que también emite el badge:
- uses: paladini/harness-score@v1
with:
min-level: '3'
badge: 'harness-badge.svg'Muestra tu madurez
Harness Score entrega dos formatos SVG con marca en el mismo lenguaje visual de las barras de progreso del escáner — sin shields.io, sin servicio pago, sin red al renderizar:
| Formato | Archivos | Muestra | Mejor para |
|---|---|---|---|
| Badge | harness-badge.svg o badge-l0.svg … badge-l4.svg | harness · L4 | Fila README (píldora 112×20) |
| Share card | card-l0.svg … card-l4.svg | Banner completo con nombre de nivel | Posts sociales, hero del repo (860×240) |
El badge muestra solo el nivel (L0–L4). Nombres de nivel (Unharnessed, Guided, …) viven en share cards y en la salida del escáner.
La píldora se ve idéntica si CI regenera o fijas archivo estático — solo cambia el cableado.
Badge — auto-actualizando (recomendado)
harness-score --badge escribe SVG para el nivel que detecta el escáner. Configúralo en CI una vez; la imagen en README se actualiza conforme mejora el harness.
# .github/workflows/harness.yml
name: Harness Score
on: { push: { branches: [main] } }
permissions: { contents: write }
jobs:
harness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: paladini/harness-score@v1
with: { badge: 'harness-badge.svg' }
- uses: JamesIves/github-pages-deploy-action@v4
with: { branch: badges, folder: ., clean: false }Referencia el archivo publicado desde tu README — recetas copy-paste completas en Snippets de embed:
<img alt="Harness Score" src="https://raw.githubusercontent.com/<you>/<repo>/badges/harness-badge.svg" height="20">Set height="20" on the <img> so the pill lines up with npm/CI shields in the same row (112×20 SVG; level only — score percent stays in the CLI report).
Dogfood example (this guide's live badge on GitHub Pages):
Badge (this repo)
The matching share card for the detected level is published as harness-card.svg (currently L4 for this repository):
Badge — pin a level
Same pill, static file — pick badge-l0.svg … badge-l4.svg if you do not want CI to regenerate the image. See Embed snippets for Markdown, HTML, iframe, JSX, and more.
Share card
For a hero image or social post, use the banner card — it includes the level name (Unharnessed, Guided, …):
| Level | Badge | Share card |
|---|---|---|
| L0 · Unharnessed | badge-l0.svg | card-l0.svg |
| L1 · Documented | badge-l1.svg | card-l1.svg |
| L2 · Guided | badge-l2.svg | card-l2.svg |
| L3 · Sensing | badge-l3.svg | card-l3.svg |
| L4 · Self-correcting | badge-l4.svg | card-l4.svg |
All badge levels (112×20)
Share card example (860×240)
Descarga cualquier nivel de la tabla — las cards incluyen el nombre del nivel.
Snippets de embed
Recetas copy-paste para compartir. Reemplaza placeholders:
| Placeholder | Badge auto-actualizando | Badge fijo (nivel {N}) | Share card |
|---|---|---|---|
{BADGE_URL} | https://raw.githubusercontent.com/{owner}/{repo}/badges/harness-badge.svg | https://paladini.github.io/harness-score/maturity/badge-l{N}.svg | — |
{CARD_URL} | — | — | https://paladini.github.io/harness-score/maturity/card-l{N}.svg |
{LINK} | Tu repo o https://paladini.github.io/harness-score/ | Igual | Igual |
{N} es 0–4. Badge live de este repo (sin CI en tu fork): https://raw.githubusercontent.com/paladini/harness-score/main/docs/public/harness-badge.svg
Tamaño del badge: 112×20 — siempre define height="20" (o height={20}) para que la píldora alinee con badges shields.io en la misma fila.
Badge — Markdown
Image only (GitHub, GitLab, dev.to — use HTML if plain ![]() stretches):
<img alt="Harness Score L4" src="{BADGE_URL}" height="20">Linked (clickable):
[]({LINK})Reference-style:
[![Harness Score][hs-badge]][hs-link]
[hs-badge]: {BADGE_URL}
[hs-link]: {LINK}Badge — HTML
<img alt="Harness Score L4" src="{BADGE_URL}" height="20" width="112">Linked:
<a href="{LINK}">
<img alt="Harness Score L4" src="{BADGE_URL}" height="20" width="112">
</a>Badge — iframe
For CMS or wikis that only allow iframes (not <img>):
<iframe
src="{BADGE_URL}"
title="Harness Score L4"
width="112"
height="20"
style="border:0;overflow:hidden"
></iframe>Badge — SVG object / embed
<object data="{BADGE_URL}" type="image/svg+xml" width="112" height="20">
<a href="{BADGE_URL}">Harness Score L4</a>
</object><embed src="{BADGE_URL}" type="image/svg+xml" width="112" height="20" />Badge — JSX / React
<a href="{LINK}">
<img
alt="Harness Score L4"
src="{BADGE_URL}"
height={20}
width={112}
style={{ verticalAlign: 'middle' }}
/>
</a>Badge — AsciiDoc
image:{BADGE_URL}[Harness Score L4,link={LINK},height=20]Badge — BBCode (forums)
[url={LINK}][img]{BADGE_URL}[/img][/url]Badge — direct URL
Paste in chat, Notion image block, Slack, Discord, or any tool that accepts a raw image URL:
{BADGE_URL}Share card — Markdown / HTML
Banner for README hero, blog posts, or social previews ({N} = 0–4):
[]({LINK})<a href="{LINK}">
<img
alt="Harness Score L4 · Self-correcting"
src="{CARD_URL}"
width="560"
style="max-width:100%;height:auto;border-radius:8px"
/>
</a>Share card — iframe
<iframe
src="{CARD_URL}"
title="Harness Score L4 · Self-correcting"
width="560"
height="157"
style="border:0;max-width:100%"
></iframe>Share card — direct URL
{CARD_URL}Worked example (pinned L3 badge)
<a href="https://paladini.github.io/harness-score/">
<img alt="Harness Score L3" src="https://paladini.github.io/harness-score/maturity/badge-l3.svg" height="20">
</a><iframe
src="https://paladini.github.io/harness-score/maturity/badge-l3.svg"
title="Harness Score L3"
width="112"
height="20"
style="border:0"
></iframe>shields.io fan? Your Action can also write a small JSON file and point a shields endpoint at it (
{ "schemaVersion": 1, "label": "harness", "message": "L3", "color": "brightgreen" }). The brand SVGs above are self-contained and need no third party.
Catálogo de checks
Cada check que corre el escáner, con receta de remediación. Los IDs de check son estables; la CLI enlaza cada fallo a su entrada aquí.
Context & Guides (20 pt)
CTX-01 · Agent context file present — 4 pts
An AGENTS.md (or CLAUDE.md / GEMINI.md) exists at the repository root. Corrección: create AGENTS.md answering: what is this project, how do I build and test it, what conventions hold, what must I never touch. Recipe in capítulo 3.
CTX-02 · Context file is substantive — 3 pts
≥20 meaningful lines and ≥2 headings — a stub scores nothing. Corrección: cover layout, build & test commands, conventions, and no-go zones. Commands over descriptions; point to rules instead of pasting them.
CTX-03 · Scoped rules in use — 4 pts
At least one scoped rule file for any supported tool (e.g. .cursor/rules/*.mdc, .windsurf/rules/*.md, .clinerules/*.md, .continue/rules/*.md, .github/instructions/*.instructions.md, .agents/rules/*). Nested context files in subdirectories (AGENTS.md, CLAUDE.md, GEMINI.md anywhere below the root) also count — they are directory-scoped rules in tools like Claude Code and Codex. Corrección: start with one short always-on rule holding your non-negotiables, then add path-scoped rules per area (or nested context files per subtree).
CTX-04 · Rules have valid frontmatter — 3 pts
Every rule declares activation metadata (description, globs/trigger/paths/applyTo, or alwaysApply). Rules a tool auto-loads without metadata — .continue/rules/* and nested context files — pass by construction. Corrección: add the frontmatter block; without it the agent can't decide when the rule applies.
CTX-05 · Rules are scoped — 2 pts
Not every rule is blanket always-on. Nested context files count as scoped — they apply only to their subtree. Corrección: scope rules to paths (globs:, trigger: glob, paths:, applyTo:) so they load only when relevant — every always-on rule taxes every request's context.
CTX-06 · No bloated rules — 2 pts
No single rule exceeds 500 lines. Corrección: split by concern, or move procedural content into a skill.
CTX-07 · README present — 1 pt
Corrección: add a README.md; it's the first orientation document for humans and a fallback for agents.
CTX-08 · No legacy .cursorrules — 1 pt
The deprecated single-file format is absent (or modern scoped rules also exist). Corrección: migrate .cursorrules content into scoped rules for your tool.
Skills & Commands (17 pts)
SKL-01 · At least one skill — 4 pts
A SKILL.md under .cursor/skills/<name>/, .claude/skills/<name>/, or .agents/skills/<name>/. Corrección: package your most repeated procedure (deploy, release, migration) as a skill — capítulo 3.
SKL-02 · Skills declare name and description — 3 pts
Frontmatter with name: and description: on every skill. Corrección: the agent decides whether to load a skill from these two fields alone; without them the skill is invisible.
SKL-03 · Explicit workflows/commands defined — 3 pts
Command or workflow files (.cursor/commands/, .windsurf/workflows/, .claude/commands/, .continue/prompts/, .zed/commands/, .agents/workflows/). Corrección: encode workflows you trigger deliberately (/review, /release) as command/workflow files.
SKL-04 · Skill descriptions are trigger-worthy — 2 pts
Descriptions ≥40 characters. Corrección: write descriptions as trigger conditions — "Use when the user asks to deploy or release; covers tagging, pipeline, rollback, smoke tests."
AGT-01 · Custom subagent defined — 3 pts
A subagent file under .cursor/agents/, .claude/agents/, or .opencode/agents/. Corrección: package a purpose-built subagent for a job the primary agent should delegate (planning, review, release) — see Subagents en el capítulo 2.
AGT-02 · Subagents declare name and description — 2 pts
Frontmatter with name: and description: on every subagent definition. Corrección: the parent agent decides whether to delegate from these two fields alone; without them the subagent is never invoked.
Hooks & Guardrails (14 pts)
Los artefactos del harness se reconocen tanto desde la raíz del repositorio por su ruta canónica como cuando el propio directorio de la herramienta es la raíz del scan. Por ejemplo, al escanear .claude, las rutas físicas settings.json, hooks/, skills/ y agents/ se reconocen como sus equivalentes .claude/.... Las raíces genéricas con otro nombre no reciben este tratamiento. La evidencia siempre usa la ruta física que se leyó.
HKS-01 · Hooks configuration present and valid JSON — 4 pts
.cursor/hooks.json o .claude/settings.json (clave hooks) existe, se parsea como JSON y contiene un objeto de hooks. Cuando existen varias configuraciones, gana una válida y no vacía; la profundidad de la raíz nativa, la cantidad de eventos y la ruta léxica son los desempates deterministas. Corrección: crea la configuración de hooks y amplíala con las recetas del capítulo 5.
HKS-02 · Hook events and handlers are structurally valid — 2 pts
El mapa de eventos no está vacío, cada evento contiene handlers válidos y están presentes los metadatos obligatorios del proveedor, como version en Cursor. Los handlers de Claude Code pueden usar command, http, mcp_tool, prompt o agent; cada tipo debe incluir sus campos obligatorios. El catálogo incluye los 31 eventos documentados de Claude Code: SessionStart, Setup, InstructionsLoaded, UserPromptSubmit, UserPromptExpansion, MessageDisplay, PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionDenied, Notification, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Stop, StopFailure, TeammateIdle, ConfigChange, CwdChanged, DirectoryAdded, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, SessionEnd, Elicitation y ElicitationResult. Compatibilidad futura: un evento desconocido pero estructuralmente válido conserva los puntos y emite el warning unknown-hook-event. Los eventos vacíos o mal formados siguen reprobando.
HKS-03 · Gate hook guards risky operations — 4 pts
A gate hook registered (Cursor: beforeShellExecution, beforeMCPExecution, preToolUse, or beforeReadFile; Claude Code: PreToolUse). Corrección: gate de deny de comandos destructivos del capítulo 5 — rules en prosa son pedidos; gates son hechos.
HKS-04 · Feedback hook observes output — 2 pts
A feedback hook registered (Cursor: afterFileEdit, postToolUse, …; Claude Code: PostToolUse). Corrección: format-and-lint on edit gives the agent instant feedback inside the session.
HKS-05 · Hook scripts committed — 2 pts
Cada ruta local del repositorio encontrada en el ejecutable, el comando de shell o una entrada de args resuelve a un archivo confirmado en el repositorio. Las rutas canónicas con prefijo del proyecto, como ${CLAUDE_PROJECT_DIR}/.claude/hooks/check.js, también resuelven a la ruta física hooks/check.js cuando .claude es la raíz del scan. Los handlers válidos que no ejecutan comandos pasan sin scripts locales aplicables. Corrección: confirma cada script local referenciado; una sola ruta ausente reprueba HKS-05.
Sensors & Feedback (20 pts)
SNS-01 · Test runner configured — 6 pts
A real test script/config (vitest, jest, pytest, go test, cargo test…). Corrección: wire up the runner with one obvious entry point and document it in AGENTS.md — tests are how the agent verifies its own work.
SNS-02 · Linter configured — 5 pts
eslint/biome, ruff, golangci-lint, rubocop, or equivalent. Corrección: every convention expressible as a lint rule stops needing prose.
SNS-03 · Type checking in place — 4 pts
tsconfig (ideally strict: true), mypy/pyright, or a statically typed language. Corrección: the type checker is the only sensor that reviews every agent edit for free — capítulo 4.
SNS-04 · Formatter configured — 3 pts
prettier/biome, black/ruff-format, gofmt/rustfmt. Corrección: formatting noise in diffs hides real mistakes from review.
SNS-05 · Test files exist — 2 pts
At least one actual test file in the tree. Corrección: a configured runner with zero tests is a green light nobody earned.
CI Feedback (14 pts)
CI-01 · CI pipeline configured — 4 pts
Flujo de trabajo de GitHub Actions (o equivalente de GitLab/CircleCI/Jenkins), incluida una configuración de Google Cloud Build en cloudbuild/. La detección se basa en el filesystem y funciona a cualquier profundidad por debajo de la raíz del scan. Por lo tanto, también cuentan los archivos de CI en proyectos anidados de un workspace. No verifica si el proveedor realmente ejecuta el archivo. Corrección: add .github/workflows/ci.yml running your sensors on every push.
CI-02 · CI runs the tests — 4 pts
Corrección: no agent-authored change should be mergeable without the suite firing.
CI-03 · CI runs lint/typecheck — 3 pts
Corrección: cheap computational sensors belong on every push — keep quality left.
CI-04 · Pre-commit checks installed — 3 pts
husky/lint-staged, pre-commit, or lefthook. Corrección: the earliest feedback a commit can get; catches what on-edit hooks missed before it enters history.
Hygiene & Safety (23 pts)
HYG-01 · .gitignore present — 2 pts
Corrección: agents commit what they see; make build output and local state invisible.
HYG-02 · .gitignore covers env files — 3 pts
A .env pattern in .gitignore. Corrección: add .env and .env.* (allow !.env.example) — the cheapest guardrail in existence.
HYG-03 · No unprotected .env files — 4 pts
No real env files in the tree unless gitignored (templates are fine). Corrección: move secrets out; keep .env.example documenting required variables.
HYG-04 · MCP config free of credentials — 4 pts
No credential signatures in MCP config (.cursor/mcp.json, .mcp.json, .agents/mcp_config.json). Corrección: use ${ENV_VAR} interpolation — an inlined key in MCP config is a secret published to every clone.
HYG-05 · License present — 2 pts
Corrección: add a LICENSE; required for open-source use and plugin marketplaces.
HYG-06 · No secrets in harness files — 2 pts
AGENTS.md, rules, and hooks config are clean of token signatures. Corrección: these files are loaded into model context every session — a key there is exfiltrated by design.
HYG-07 · Lockfile committed — 3 pts
package-lock.json, uv.lock, Cargo.lock, go.sum, or equivalent. Corrección: reproducible installs mean your sensors test the same dependency tree everywhere.
HYG-08 · MCP config uses env interpolation for credentials — 3 pts
An MCP config file is valid, and any credential-shaped field (token, key, secret, password…) uses ${ENV_VAR} interpolation rather than a literal. The positive complement to HYG-04 — a repo with no MCP setup earns nothing here, same as any other bonus check. Corrección: reference secrets as "${VAR_NAME}" and document required variables in .env.example.
Plan de mejora práctico
Partiendo de un repo de producto típico en L0, una sesión enfocada por nivel:
- → L1 (una tarde). Escribe
AGENTS.md(CTX-01/02). Incluye comandos build/test aunque los sensores sean débiles — el agente los usará. - → L2 (un día). Tres rules con alcance + una skill para el procedimiento más repetido (CTX-03…06, SKL-01/02). Corrige higiene: gitignore, env files, licencia (HYG-01…05).
- → L3 (el trabajo real, si faltan sensores). Test runner + linter + tipos strict +
ci.ymlcorriendo los tres (SNS-*, CI-01…03). Si ya los tienes, este nivel es gratis. - → L4 (una mañana). Los dos hooks del capítulo 5 — un gate, un formatter — commiteados con scripts (HKS-*), pre-commit (CI-04), luego
--min-level 4en CI para que nunca regrese.