Core concepts

Configuration

SpecTracer follows the ESLint convention: the CLI takes no flags, and every knob lives in one JSON file you commit to the repo. This page documents every key.

CLI surface

the entire command-line interface
# auto-discover spectracer.config.json in the current directory
spec-tracer

# or point at a differently named / located config
spec-tracer path/to/other-config.json

One command, one optional positional argument. No sub-commands, no flags, no environment variables.

Shell note Use forward slashes (./spectracer.config.json) or a bare filename. A leading .\ in PowerShell style is mangled by POSIX-style shells such as Git Bash and WSL, where backslash is the escape character.

Exit codes

CodeMeaningWhen
0SuccessReport generated, nothing gating.
1FailureConfig error, parse error, error_on_failure: true with at least one failing test, or a fail_on health check in a red state.

Config discovery

Key reference

spectracer.config.json — every key
{
  "features": ["./features"],

  "unit": {
    "":        ["./reports/unit.xml"],
    "billing": ["./reports/billing-unit.xml"]
  },
  "integration": {
    "": ["./reports/integration.xml"]
  },
  "e2e": {
    "":         ["./reports/e2e.json"],
    "checkout": ["./reports/checkout-e2e.json"]
  },

  "output":      "./report.html",
  "output_json": "./report.json",

  "error_on_failure": false,
  "fail_on": ["pyramid", "e2e_runtime"],

  "health_checks": {
    "progress_threshold_green":   80,
    "progress_threshold_amber":   50,
    "e2e_duration_amber_seconds": 600,
    "e2e_duration_red_seconds":   1800
  }
}
KeyRequiredDescription
featuresYes Array of Gherkin .feature file or directory paths. Directories are searched recursively.
unitNo Object keyed by module name; each value an array of JUnit XML and/or Cucumber JSON file or directory paths — format is auto-detected per file. Use "" for results not tied to a module. Matched against @require-unit / @require-unit:<module>.
integrationNo Same shape as unit (JUnit XML and/or Cucumber JSON, auto-detected), matched against @require-integration.
e2eNo Same shape again, but the paths are Cucumber JSON. Matched against @require-e2e.
outputYes Path for the generated HTML report. Parent directories are created if missing; an existing file is overwritten.
output_jsonNo Path for the machine-readable JSON report. Omit to skip JSON output entirely (the default). Same create/overwrite semantics as output.
error_on_failureNo If true, exit 1 when any collected test result is a failure. Default false.
fail_onNo Array of health checks that gate CI. Accepted values: "progress", "pyramid", "e2e_runtime". Any other value is a config error. Default: none.
health_checksNo Threshold overrides. See Health checks.

Layer objects, in detail

The three layer keys share one shape, and it's the part of the config worth understanding properly:

the shape
"<layer>": {
  "<module name>": [ "<file or directory>", ... ],
  ...
}
Malformed input is fatal, missing input is not

A JUnit XML file that fails to parse aborts the run with an error naming the offending file.

A Cucumber JSON file that fails to parse aborts on the JSON decode error — note that this message is not contextualised with the file path, so if you have several Cucumber JSON files (E2E, or BDD-style unit/integration) you may have to bisect.

Health checks

Four checks run on every report. By default they're purely visual — they colour the dashboard and nothing else. Listing a check under fail_on promotes it to a CI gate.

CheckMeasuresPassWarnFail
Progress
progress
% of declared @require-* layer/module pairs that have a linked result ≥ green threshold ≥ amber threshold < amber threshold
Pyramid ratio
pyramid
Unit test count vs. integration + E2E count Unit > I+E Unit = I+E Unit < I+E (inverted)
E2E runtime
e2e_runtime
Total E2E duration in seconds ≤ amber seconds ≤ red seconds > red seconds
Unlinked tests
visual only
Number of orphaned test results 0 1–3 4+

Default thresholds

SettingDefaultMeaning
progress_threshold_green80At or above this percentage, Progress is green.
progress_threshold_amber50At or above this, amber; below it, red.
e2e_duration_amber_seconds600Ten minutes. Past this, E2E runtime goes amber.
e2e_duration_red_seconds1800Thirty minutes. Past this, red.

Override only what you need — unspecified keys keep their defaults.

CI gating

Two independent mechanisms, and either one exiting non-zero fails the build.

error_on_failure

Answers one question: did any test fail? Set it to true and a single failing result exits 1. Nothing to do with coverage.

fail_on

Answers a different question: are we living up to our own standards? List progress, pyramid, and/or e2e_runtime; a red status on any listed check exits 1.

Amber never gates Only a red status triggers the exit. This is deliberate: warnings stay warnings, so the amber band is a place to notice a trend before it becomes a blocker.
Turn gates on second, not first Generate the report for a sprint or two before you gate on it. Enforcing a bar you haven't cleared yet just trains people to bypass the check.

Need a threshold the built-in checks don't cover — say, "completion must not drop below last week's" — script it against output_json rather than parsing HTML. See CI/CD recipes.

The JSON report

Setting output_json writes a second file alongside the HTML, built from the exact same internal data model — so the two outputs cannot drift apart. It conforms to spectracer-report.schema.json (JSON Schema Draft 7), which is the authoritative contract.

PathContents
summary.completion The headline stats. Note percent is the declared tests matched percentage (satisfied / required) — tested and total are scenario counts, so percent is not tested / total.
summary.pyramidPer-layer test counts, durations, and pass rates.
summary.healthA single worst-of rollup: green / amber / red, plus a reasons[] array carrying each non-passing check's message.
features[].scenarios[].results[]Every linked result, with module, and duration (milliseconds) and failureMessage omitted rather than null when unavailable. Requirement satisfaction sits under requirements[].
unlinkedTests[]The same orphaned results the HTML report lists.
configA verbatim echo of the resolved config, for provenance if the JSON is archived away from the repo.
Durations are milliseconds in the JSON The internal model stores seconds; the schema specifies milliseconds, and the conversion happens on the way out. Feature file paths are emitted relative to the config file's directory, never as absolute filesystem paths — important when comparing reports across machines.

Worked examples

Minimal — E2E only

The smallest thing that produces a useful report. Every scenario without an explicit @require-* defaults to @require-e2e, so this needs no tagging beyond @id: / @scenario:.

spectracer.config.json
{
  "features": ["./features"],
  "e2e": { "": ["./reports/e2e.json"] },
  "output": "./reports/spectracer-report.html"
}

Full stack — three layers, JSON twin, gated

spectracer.config.json
{
  "features": ["./features"],
  "unit":        { "": ["./reports/unit"] },
  "integration": { "": ["./reports/integration"] },
  "e2e":         { "": ["./reports/e2e"] },
  "output":      "./reports/spectracer-report.html",
  "output_json": "./reports/spectracer-report.json",
  "error_on_failure": true,
  "fail_on": ["pyramid", "e2e_runtime"]
}

The layer paths here are directories — every XML under ./reports/unit is picked up, so CI jobs can drop files in without the config changing.

Multi-module monorepo with custom thresholds

spectracer.config.json
{
  "features": ["./services/auth/features", "./services/billing/features"],

  "unit": {
    "auth":    ["./services/auth/reports/unit.xml"],
    "billing": ["./services/billing/reports/unit.xml"]
  },
  "integration": {
    "auth":    ["./services/auth/reports/int.xml"],
    "billing": ["./services/billing/reports/int.xml"]
  },
  "e2e": {
    "checkout": ["./reports/e2e-checkout.json"],
    "signup":   ["./reports/e2e-signup.json"]
  },

  "output":      "./reports/spectracer-report.html",
  "output_json": "./reports/spectracer-report.json",
  "fail_on": ["progress", "pyramid"],

  "health_checks": {
    "progress_threshold_green":   90,
    "progress_threshold_amber":   70,
    "e2e_duration_amber_seconds": 300,
    "e2e_duration_red_seconds":   900
  }
}

Scenarios in this repo would tag @require-unit:auth, @require-e2e:checkout, and so on. Note there is no "" key on any layer here — every result is module-scoped, so a bare @require-unit would be satisfied by any of them, while @require-unit:auth is satisfied only by the auth file.