Skip to content

Metrics & Codes Reference ​

Dense cheat sheet for everything harness-score reports: scores, scopes, levels, dimensions, check IDs, configuration keys, CLI flags, Action inputs, and JSON fields. Remediation recipes live in chapter 8 — Measure & Improve.

Scores: maturity vs effective ​

CodeWhat it includesUsed for
maturityRepository files only (scopes: repo)Default CI gate, badge, --min-level, official team maturity
effectiveRepo ∪ configured global/extra scopesLocal “what the agent sees on this machine” when user/system harness is enabled

When no extra scopes are configured, effective equals maturity (same level, score, and checks). The report always includes both blocks for stable JSON.

Set which score gates CI with gate in config, --gate, or the Action gate input (maturity default).

Scopes ​

ScopeMeaningWhat is scanned
repoAlways onThe directory you pass to harness-score (default .). When the scan root is a Claude Code or Cursor plugin repository (.claude-plugin/plugin.json or .cursor-plugin/plugin.json at the root), default plugin skills/, commands/, agents/, and hooks/hooks.json count like .claude/... / .cursor/....
userOpt-inAllowlisted user-level paths mapped to repo-relative shapes: ~/.cursor/*, ~/.claude/*, user-scoped Claude Code plugins from ~/.claude/plugins/installed_plugins.json, ~/.codeium/windsurf/* (Windsurf global rules alias), ~/Documents/Cline/Rules → .clinerules/, ~/.continue/{rules,prompts}, ~/.agents/*, ~/.zed/commands, ~/.config/opencode/agents, global MCP/hooks configs, etc. See multi-harness — user-scope by tool. Not included: Copilot global (repo-only), Continue inline rules in config.yaml, IDE-only Cursor User Rules, Cursor user plugin cache (v1).
systemOpt-inReserved for validated system-wide installs (minimal in v1)
extraRootsOpt-inAdditional directories (relative or absolute) whose tree mirrors harness layout — e.g. a shared team harness checkout

Project files win over overlay paths on conflict (same relative path).

Not scanned: Cursor User Rules stored only in the IDE UI (not on disk), arbitrary home-directory walks, or secrets content in evidence strings.

Levels (L0–L4) ​

Official level names apply to maturity unless you set gate: effective.

LevelNameRequirements (all previous levels +)
L0Unharnessed—
L1Documentedcontext ≥ 40%
L2Guidedcontext ≥ 60%; skills ≥ 30% or hooks ≥ 30%; hygiene ≥ 50%
L3Sensingsensors ≥ 60%; ci ≥ 50%
L4Self-correctinghooks ≥ 70%; total ≥ 80%

Full narrative: The Maturity Model.

Dimensions ​

IDTitleMax ptsMeasures
contextContext & Guides20AGENTS.md, scoped rules, README
skillsSkills & Commands17Skills, commands/workflows, subagents
hooksHooks & Guardrails14Cursor, Claude Code, and Devin hook configurations
sensorsSensors & Feedback20Tests, linter, types, formatter
ciCI Feedback14Pipeline, pre-commit
hygieneHygiene & Safety23.gitignore, secrets, lockfile, license, MCP hygiene

Total: 108 points when every check applies. A scan's score.max is lower when a check is not applicable (checks[].applicable is false) or set to "off".

Check catalog ​

Stable IDs — linked to remediation in Measure & Improve.

Context & Guides ​

IDPtsAnalyzes exactlyRemediation
CTX-014Root AGENTS.md, CLAUDE.md, or GEMINI.md, or .claude/CLAUDE.md / .claude/AGENTS.mdctx-01
CTX-023Context file has ≥20 meaningful lines and ≥2 headingsctx-02
CTX-034At least one scoped rule file (any supported tool), .claude/rules/**/*.md, or nested context file in a code subdirectoryctx-03
CTX-043Every rule declares activation metadata in frontmatterctx-04
CTX-052Not every rule is blanket always-onctx-05
CTX-062No single rule file exceeds 500 linesctx-06
CTX-071README.md at repository rootctx-07
CTX-081No legacy .cursorrules without modern scoped rulesctx-08

Skills & Commands ​

IDPtsAnalyzes exactlyRemediation
SKL-014At least one SKILL.md under a recognized skills directoryskl-01
SKL-023Every skill has name: and description: in frontmatterskl-02
SKL-033Command/workflow files exist for any supported toolskl-03
SKL-042Skill descriptions are ≥40 charactersskl-04
AGT-013At least one subagent markdown fileagt-01
AGT-022Every subagent has name: and description: frontmatteragt-02

Hooks & Guardrails ​

IDPtsAnalyzes exactlyRemediation
HKS-014A Cursor, Claude Code, or Devin hooks config exists and parses as JSONhks-01
HKS-022Hook events and typed handlers are structurally valid; unknown valid events warnhks-02
HKS-034A gate-class hook is registered (shell/MCP/read/tool gate)hks-03
HKS-042A feedback-class hook is registered (post-edit/tool)hks-04
HKS-052Every local path in command executables and args exists; non-command handlers are N/Ahks-05

Sensors & Feedback ​

IDPtsAnalyzes exactlyRemediation
SNS-016Test runner configured (package.json script, pytest, phpunit.xml, pest, go test, node --test, …)sns-01
SNS-025Linter configured (eslint, biome, ruff, phpcs, rector, golangci-lint, …)sns-02
SNS-034Type checking configured (tsconfig, mypy, pyright, ty, phpstan, psalm, …)sns-03
SNS-043Formatter configured (prettier, black, pint, php-cs-fixer, gofmt, …)sns-04
SNS-052At least one test file exists in the treesns-05

CI Feedback ​

IDPtsAnalyzes exactlyRemediation
CI-014CI pipeline file present (GitHub Actions, GitLab CI, …)ci-01
CI-024CI executes test commands (phpunit, pest, …; resolves referenced Composer script bodies; ignores step labels)ci-02
CI-033CI executes lint or typecheck commands (phpstan, pint, phpcs, …; resolves referenced Composer script bodies; ignores step labels)ci-03
CI-043Pre-commit or git hook tooling installedci-04

Hygiene & Safety ​

IDPtsAnalyzes exactlyRemediation
HYG-012.gitignore presenthyg-01
HYG-023.gitignore covers environment fileshyg-02
HYG-034No unprotected .env files (without .env.example pattern)hyg-03
HYG-044MCP JSON configs contain no inline credential patternshyg-04
HYG-052LICENSE file presenthyg-05
HYG-062No credential-like signatures in harness markdown/JSONhyg-06
HYG-073Dependency lockfile committedhyg-07
HYG-083MCP configs use env interpolation for secretshyg-08

Configuration file (.harness-score.json) ​

Optional JSON at the scan root (strict schema — unknown keys error):

json
{
  "scan": {
    "includePaths": ["build/ci"]
  },
  "scopes": {
    "user": false,
    "system": false
  },
  "extraRoots": [
    { "id": "team-shared", "path": "../shared-harness" }
  ],
  "gate": "maturity",
  "extends": ["no-hooks"],
  "rules": {
    "HYG-05": "off"
  }
}
KeyTypeDefaultMeaning
scopes.userbooleanfalseInclude user-level harness overlay
scopes.systembooleanfalseInclude system-level overlay
extraRoots{ id, path }[][]Extra harness trees merged into effective
scan.includePathsstring[][]Exact repo-relative files/directories to scan inside normally skipped directories; no globs
gate"maturity" | "effective""maturity"Which score --min-level uses
extendsstring[][]Named presets to apply (see below)
rulesRecord<checkId, severity>{}Per-check severity override, applied after every preset in extends

Precedence: CLI flags → Action inputs → config file → defaults. scan.includePaths, extends, and rules are configured in the config file; use --config <path> to point at a .harness-score.json that sets them. The GitHub Action reads the same config file.

scan.includePaths accepts exact paths relative to the repository root, using / separators. A path can name a file or directory; selected directories include descendants recursively. Paths cannot be absolute, contain .., or use globs. Missing paths are ignored. The default exclusions still apply everywhere outside the selected paths, and the effective list is shown in reports and compared with baselines.

Team customization: extends and rules ​

Borrowed straight from ESLint's own vocabulary, because it's a vocabulary most teams already know:

  • rules overrides a single check's severity by ID: "HYG-05": "off". Severity is "off" or "error" in this release — "error" is the implicit default for every check, and "off" removes the check from both the numerator and the denominator of its dimension's score (it's structurally excluded, never scored as a failure). "warn" is a recognized value that's deliberately rejected today with a clear "not supported yet" error — it's reserved for a future advisory, non-blocking mode.
  • extends applies a named, maintainer-curated preset — a versioned, PR-reviewed bundle of rules overrides, not a free-form per-repo waiver. This preserves the same governance that already protects the check catalog: proposing a new preset goes through review (see CONTRIBUTING.md), not a silent local opt-out. Presets in extends are applied in array order, and any explicit rules entry always wins over a preset.

Every check excluded by extends/rules is always disclosed — in the terminal output (Preset: ... line), in the Markdown report (**Preset:** line and a ➖ status in the checks table), and in --json's preset field — never silently hidden behind a flag.

One exception, on purpose: HYG-03, HYG-04, and HYG-06 — the checks that detect actively leaked or exposed credentials — can never be set to "off", from either rules or a preset. Everything else in this config format leans on disclosure and PR review for integrity; these three are the one place that isn't negotiable.

Built-in presets ​

PresetEffectWhy
no-hooksSets HKS-01–HKS-05 (the whole Hooks & Guardrails dimension, 14 of 108 points) to "off"For environments where local hook script execution is disallowed by policy — locked-down dev containers, regulated orgs, shared runners without permission to install hooks. Guardrails there are enforced in CI only.

Excluding an entire dimension has one honest consequence worth knowing up front: because L4 · Self-correcting is defined by runtime guardrail hooks (see the Maturity Model), a repository under no-hooks can never reach L4 — the level becomes capped, not "failed." report.level.capped is true and report.level.capReason explains why; every other dimension's score is completely unaffected. This is the scanner staying honest about what "self-correcting" means, not a penalty for excluding hooks.

CLI flags (scan configuration) ​

Repository maturity discovery has no production depth cap. It includes tracked, untracked, and ignored files after skipping known dependency and generated directories. file-count-limit represents an emergency 1,000,000-file fuse for the repository scan; bounded user and extra-root overlays can still report depth-limit. A discovered path that cannot be inspected when a check requests it reports unreadable-path; intentionally skipped bodies above 512 KiB do not. Internal symlinks are followed and deduplicated, while a target outside the scan root reports outside-root-symlink and is not traversed or read.

FlagMeaning
--config <file>Load config from a specific path
--scope userEnable user scope (comma-separated: user, system)
--gate maturity|effectiveScore used for --min-level
--min-level <0-4>Exit 1 when a complete gated score is below level; an incomplete gated snapshot exits 2
--jsonFull report including scopes, gate, effective, and verdicts

GitHub Action inputs ​

InputDefaultMeaning
include-user-harnessfalsePass --scope user
include-system-harnessfalsePass --scope system
gatematurityPass --gate
config''Pass --config when set
min-level0Fail when gated score is below level

Outputs: level, level-name, percent (maturity); effective-level, effective-percent. The Action publishes maturity outputs, badges, and reports only when maturity is complete, and effective outputs only when effective is complete. If only effective is incomplete, gate: maturity can pass with a warning; gate: effective exits 2. An incomplete maturity snapshot publishes no maturity outputs, badge, report, or PR comment.

Report JSON fields (stable) ​

FieldDescription
rootAbsolute scan root
scopes.maturityAlways ["repo"]
scopes.effectivee.g. ["repo"], ["repo","user"]
gate"maturity" or "effective"
resolvedRootsOptional list of { scope, absPath } for overlays
level, score, dimensions, checksMaturity snapshot
effectiveSame shape: { level, score, dimensions, checks, detectedHarnesses }
detectedHarnessesTools seen in repo (informational)
verdicts.maturity, verdicts.effectiveCompleteness status and deterministic reasons for each snapshot: complete or incomplete
verdicts.*.reasons[]file-count-limit, depth-limit, unreadable-directory, unreadable-path, or outside-root-symlink, with optional path and limit
truncatedCompatibility alias; true when the maturity or effective snapshot is incomplete for any reason
preset{ extends, rules, resolved } — team customization actually applied; resolved lists only checks whose severity differs from the default
level.capped, level.capReasoncapped is true when a blocking requirement for the next level can never be met under the current config (e.g. its dimension was excluded by a preset); capReason explains why
dimensions[].applicablefalse when no check in that dimension is scored: every check is "off" and/or checks[].applicable is false
checks[].applicablefalse when the check does not apply to this repository (HYG-05 with a closed-source license, HYG-08 with no MCP config). Excluded from the numerator and denominator. Defaults to true. Omitted on older reports, which means applicable
checks[].severity"off" | "warn" | "error" — the resolved severity this scan used for that check
checks[].warningsOptional non-fatal diagnostics { code, message, source? }; terminal and Markdown render them without changing points

level, score, dimensions, and checks remain present for diagnosis, but are provisional when their verdict is incomplete. Terminal and Markdown identify the unavailable snapshot. The badge always represents maturity and uses incomplete, never L0-L4, when maturity is incomplete. Old reports without verdicts are treated as complete when truncated is false, and incomplete when it is true.

--diff compares maturity fields by default (top-level level / score / checks) and rejects an incomplete maturity baseline or current result. An incomplete effective snapshot does not block a maturity diff. A check moving between applicable and not applicable is its own delta (became-applicable / became-not-applicable), separate from newly passing or newly failing.