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@main
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@main
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)
HKS-01 · Hooks configuration present and valid — 4 pts
.cursor/hooks.json or .claude/settings.json (hooks key) exists and parses as JSON. Corrección: create hooks config and grow from the recetas en el capítulo 5.
HKS-02 · Known events, version declared — 2 pts
Version/metadata present; every registered event is documented for your tool (Cursor lifecycle events, or Claude Code PreToolUse/PostToolUse). Corrección: typo'd event names fail silently — check against the event list in capítulo 2.
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
Scripts referenced by the hooks config exist in the repository. Corrección: commit them; a hook pointing at a missing script fails open on every machine but the author's.
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
GitHub Actions workflow (or GitLab/CircleCI/Jenkins equivalent). 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.