Skip to content
Back to skills

Pipeline Audit

BSecurity

Analyze pipeline execution history for efficiency insights - compute token

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 5, 2026
developmentpythongoshellbashnodegitapiperformance

Works with

  • claude code
  • cursor
  • vscode
  • terminal
  • cli
  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro scans all 3 files and shows the line behind each finding

Scanned November 5, 2026

npx -y skills add nightgauge/nightgauge --skill pipeline-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pipeline Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pipeline Audit
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/nightgauge-pipeline-audit/badge)](https://www.skillsdirectory.com/skills/nightgauge-pipeline-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pipeline-audit
description: Analyze pipeline execution history for efficiency insights - compute token
  usage, stage performance, cost optimization, quality correlation, and trend
  metrics. Use anytime to identify improvement opportunities.
license: Apache-2.0
metadata:
  author: nightgauge
  version: "1.4.0"
  source: https://github.com/nightgauge/nightgauge
  chainable: true
allowed-tools: Read Write Edit Glob Grep Bash Task
context: fork
agent: pipeline-optimizer
model: haiku
---

# Nightgauge Pipeline Audit

## Description

Scrubs pipeline execution history, computes efficiency metrics across 8
categories, and generates actionable improvement recommendations. Uses
deterministic Bash/jq for data extraction and metric computation; AI interprets
results into human-readable findings.

**For comprehensive cross-referenced analysis with recommendation tracking, use
`/nightgauge:pipeline-health` instead.**

**Use Cases:**

- Post-sprint efficiency reviews
- Cost optimization analysis
- Identifying bottleneck stages
- Tracking pipeline improvement over time
- Creating issues for recurring problems

**When to Use:**

- After completing several pipeline runs to identify patterns
- When pipeline costs seem higher than expected
- Before planning pipeline optimization work
- Periodically (weekly/bi-weekly) for trend monitoring
- When you need a **quick snapshot** — pipeline-audit runs fast against a single
  data source. For deep multi-source analysis with historical tracking, use
  `/nightgauge:pipeline-health`.

## Invocation

| Tool        | Command                                  |
| ----------- | ---------------------------------------- |
| Claude Code | `/nightgauge:pipeline-audit [options]`   |
| Copilot     | Invoke via Agent Skills extension        |
| Cursor      | Run via Agent Skills or direct SKILL.md  |
| Headless    | `claude -p "/nightgauge:pipeline-audit"` |

## Arguments

### Core Options

| Argument           | Description                                                | Default   |
| ------------------ | ---------------------------------------------------------- | --------- |
| `--runs N`         | Analyze last N pipeline runs                               | `10`      |
| `--since DATE`     | Analyze runs since date (YYYY-MM-DD)                       | -         |
| `--issue N`        | Analyze runs for specific issue number                     | -         |
| `--adapter NAME`   | Filter the per-stage adapter breakdown to a single adapter | `-` (all) |
| `--create-issues`  | Auto-create GitHub issues for high findings                | `false`   |
| `--severity LEVEL` | Minimum severity for `--create-issues`                     | `high`    |
| `--format FORMAT`  | Output format: `summary`, `json`, `both`                   | `both`    |
| `--compare DATE`   | Compare metrics before/after date (YYYY-MM-DD)             | -         |

### Examples

```bash
# Analyze last 10 runs (default)
/nightgauge:pipeline-audit

# Analyze last 50 runs
/nightgauge:pipeline-audit --runs 50

# Analyze since a specific date
/nightgauge:pipeline-audit --since 2026-02-01

# Analyze a specific issue's pipeline runs
/nightgauge:pipeline-audit --issue 628

# Scope the per-stage adapter breakdown to a single adapter
/nightgauge:pipeline-audit --adapter codex

# Generate improvement issues for high-severity findings
/nightgauge:pipeline-audit --create-issues

# Create issues for medium+ severity findings
/nightgauge:pipeline-audit --create-issues --severity medium

# JSON output only (for programmatic consumption)
/nightgauge:pipeline-audit --format json

# Compare metrics before/after an optimization
/nightgauge:pipeline-audit --compare 2026-03-01
```

`nightgauge pipeline aggregate --json` output includes
`stage_metrics.<stage>.adapter_usage`, a per-adapter token/cost breakdown keyed
by adapter name, with a reserved `"unknown"` key for stage entries that carry
no adapter stamp. Each bucket's `cost_unstamped_count` marks how many of its
stage entries had a placeholder (unpriceable) cost — the persisted substitute
for the issue's `UsagePartial` field, which is execution-layer-only and never
reaches this schema (see `internal/state/history.go`'s `V2StageTokens.CostUnstamped`
doc comment).

---

## Prerequisites

- `python3` installed (for reliable JSONL parsing — `cat | jq` drops records)
- `nightgauge` CLI installed and authenticated (`nightgauge forge auth login`)
- `jq` installed for JSON processing (fallback data sources only)
- Git repository with `.nightgauge/` directory
- Pipeline history in `history/*.jsonl` (preferred) or pipeline context files
  (`dev-*.json`) as a minimal fallback, both in the clone's pipeline state
  directory (`nightgauge layout path pipeline`)

**CRITICAL**: This skill runs headless. Do NOT use AskUserQuestion. Make
autonomous decisions or fail with clear error.

---

## Workflow

### Phase 0: Environment Preflight

### Phase 0: Environment Preflight

**PURPOSE**: Verify the nightgauge binary is available and the environment
is healthy before executing any pipeline stage. Skills halt immediately with
actionable instructions when this check fails.

Three-layer check:

1. **Binary discovery** — shell-level, runs before invoking the binary
2. **Doctor check** — binary-level, full environment health via `doctor --json`
3. **Project environment** — the checks the project declares under
   `preflight.environment` (#101); opt-in, silent when none are declared

The binary discovery cascade below is **provider-neutral** (issue 4029): it works
identically under every adapter (Claude, Codex, Gemini, …) and references no
VSCode-extension-specific path. The host that spawns the skill (the VSCode
extension's `skillRunner`, or the Go `nightgauge` binary in auto/CLI mode)
resolves the binary authoritatively and exports `NIGHTGAUGE_BIN`, which
this cascade honors first. The remaining steps (PATH, the development-only repo
`bin/` and worktree canonical-repo `bin/`, `~/go/bin`) are fallbacks for direct
or terminal invocation.

This intentionally **diverges** from
`claude-plugins/nightgauge/hooks/lib/guard.sh` (issue 3262 → issue 4029): guard.sh is
Claude-Code-only and retains a trailing `~/.vscode/extensions/...` glob to serve
the standalone-terminal-Claude case (where no host exports `NIGHTGAUGE_BIN`).
Skills must stay portable Markdown and cannot reference that VSCode-only path —
so the shared resolution order (`NIGHTGAUGE_BIN` → PATH → gated repo → gated
canonical → `~/go/bin`) stays in sync, and only guard.sh carries the extra Claude-only
fallback. The `nightgauge preflight skill-portability` gate enforces that
no skill reintroduces a `.vscode/extensions` path, and that every cascade keeps
the `NIGHTGAUGE_DEV_REPO_BIN` gate (#2758): a foreign repository's
`bin/nightgauge` must never run.

After the cascade resolves `$BINARY`, the preflight prepends
`dirname($BINARY)` to `PATH` so subsequent bare `nightgauge ...`
invocations later in the same skill body resolve through PATH rather than
failing with "command not found" (issue 3262).

```bash
# Layer 1: provider-neutral binary discovery (NIGHTGAUGE_BIN → PATH → gated repo
# bin → gated canonical-repo bin → ~/go/bin). No VSCode-extension path — see issue 4029.
# Repo bins need NIGHTGAUGE_DEV_REPO_BIN=1 and a verified core checkout (#2758).
BINARY="${NIGHTGAUGE_BIN:-}"
[ -n "$BINARY" ] && [ ! -x "$BINARY" ] && BINARY=""
[ -z "$BINARY" ] && BINARY=$(command -v nightgauge 2>/dev/null || echo "")
if [ -z "$BINARY" ] && [ "${NIGHTGAUGE_DEV_REPO_BIN:-}" = "1" ]; then
  for NG_ROOT in "$(git rev-parse --show-toplevel 2>/dev/null)" \
    "$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." 2>/dev/null && pwd)"; do
    [ -x "$NG_ROOT/bin/nightgauge" ] || continue
    grep -qx 'module github.com/nightgauge/nightgauge' "$NG_ROOT/go.mod" 2>/dev/null || continue
    NG_ORIGIN="$(git -C "$NG_ROOT" config --get remote.origin.url 2>/dev/null)"
    case "${NG_ORIGIN%.git}" in
      git@github.com:nightgauge/nightgauge | https://github.com/nightgauge/nightgauge | \
        ssh://git@github.com/nightgauge/nightgauge) BINARY="$NG_ROOT/bin/nightgauge"; break ;;
    esac
  done
fi
[ -z "$BINARY" ] && [ -x "$HOME/go/bin/nightgauge" ] && BINARY="$HOME/go/bin/nightgauge"
if [ -z "$BINARY" ]; then
  echo "ERROR: nightgauge binary not found via NIGHTGAUGE_BIN, PATH or ~/go/bin" >&2
  echo "" >&2
  echo "Install via: go install github.com/nightgauge/nightgauge/cmd/nightgauge@latest" >&2
  echo "Or download from: https://github.com/nightgauge/nightgauge/releases" >&2
  echo "Run \`nightgauge doctor\` after installing to verify your environment." >&2
  exit 1
fi
# Make the resolved binary callable via bare `nightgauge ...` later in this skill body (issue 3262).
export PATH="$(dirname "$BINARY"):$PATH"

# Per-repo GitHub token: export GH_TOKEN so every `gh` call in this skill
# authenticates as the *current repo's* configured user instead of the machine-
# global `gh auth` active account. `forge auth token` resolves per-repo (config
# github_user/token → GITHUB_TOKEN → `gh auth token --user <github_user>`), so
# concurrent sessions/workspaces owned by different GitHub users each use their
# own token. GH_TOKEN outranks GITHUB_TOKEN and the gh keyring. Only resolve when
# unset (an upstream env value — e.g. the VSCode extension's terminal env —
# wins); never fail preflight over it. Mirrors hooks/lib/guard.sh (sync, issue 3262).
if [ -z "${GH_TOKEN:-}" ]; then
  IB_REPO_TOKEN="$("$BINARY" forge auth token 2>/dev/null || true)"
  if [ -n "$IB_REPO_TOKEN" ]; then
    export GH_TOKEN="$IB_REPO_TOKEN"
    export GITHUB_TOKEN="${GITHUB_TOKEN:-$IB_REPO_TOKEN}"
  fi
  unset IB_REPO_TOKEN
fi

# Layer 2: full environment health check via doctor --json
DOCTOR_RESULT=$("$BINARY" doctor --json 2>/dev/null)
DOCTOR_EXIT=$?
if [ "$DOCTOR_EXIT" -eq 2 ]; then
  echo "ERROR: Environment check failed — nightgauge doctor reports broken environment." >&2
  FAILED_CHECKS=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.failed_checks[]? // empty' 2>/dev/null)
  if [ -n "$FAILED_CHECKS" ]; then
    echo "Failing check(s): $(echo "$FAILED_CHECKS" | tr '\n' ',' | sed 's/,$//')" >&2
  fi
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.errors[]' >&2 2>/dev/null || true
  INSTALL_MSG=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.install_instructions // empty' 2>/dev/null)
  [ -n "$INSTALL_MSG" ] && echo "$INSTALL_MSG" >&2
  exit 1
fi
if [ "$DOCTOR_EXIT" -eq 1 ]; then
  echo "WARNING: Environment has non-critical issues:" >&2
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.warnings[]' >&2 2>/dev/null || true
  # Continue — warnings do not block skill execution
fi

# Layer 3: project-declared environment checks (preflight.environment, #101).
# Opt-in: with none declared this prints nothing and continues. The verb
# writes one `[environment-halt]` line per failed check, with its remedy.
if ! "$BINARY" preflight environment >/dev/null; then
  echo "[environment-halt] Project environment check failed — fix the environment, not the code." >&2
  exit 1
fi
```

**Exit codes from `nightgauge doctor`**:

| Code | Meaning                         | Skill behavior                          |
| ---- | ------------------------------- | --------------------------------------- |
| 0    | Healthy                         | Continue                                |
| 1    | Degraded (warnings only)        | Continue with warning printed to stderr |
| 2    | Broken (required checks failed) | Halt immediately with error details     |

**Layer 3** halts the stage when any check declared under
`preflight.environment` in the workspace's `.nightgauge/config.yaml` fails
(exit 1) or the declaration is invalid (exit 2). The failure is in the
operator's environment, not the code: do not edit code to work around it, do
not run the remedy yourself, and end the stage. The pipeline records it as
`environment_halt` and holds the issue for an operator.

Do not theorize about the cause beyond what is printed here — the failing
check name (`failed_checks`) and its `.errors[]` message are the complete
diagnosis (#277).


---

### Phase 1: Parse Arguments and Locate Data

#### Step 1.1: Parse Arguments

Extract options from invocation:

```bash
RUNS_LIMIT=10
SINCE_DATE=""
ISSUE_FILTER=""
CREATE_ISSUES=false
MIN_SEVERITY="high"
OUTPUT_FORMAT="both"
COMPARE_DATE=""

# Parse arguments from invocation
# --runs N: set RUNS_LIMIT
# --since YYYY-MM-DD: set SINCE_DATE
# --issue N: set ISSUE_FILTER
# --create-issues: set CREATE_ISSUES=true
# --severity LEVEL: set MIN_SEVERITY
# --format FORMAT: set OUTPUT_FORMAT
# --compare YYYY-MM-DD: set COMPARE_DATE
```

#### Step 1.2: Locate Data Sources

Check for execution history in priority order:

```bash
HISTORY_DIR="$(nightgauge layout path pipeline history)"
PIPELINE_DIR="$(nightgauge layout path pipeline)"

DATA_SOURCE="none"

# Priority 1: JSONL history files (from execution history persistence)
if ls "${HISTORY_DIR}"/*.jsonl 2>/dev/null | head -1 > /dev/null; then
  DATA_SOURCE="history"
  JSONL_FILES=$(ls -1 "${HISTORY_DIR}"/*.jsonl | sort -r)
  echo "Found execution history JSONL files"
fi

# Priority 2: Pipeline context files (dev-*.json, planning-*.json, etc.)
if [ "$DATA_SOURCE" = "none" ]; then
  CONTEXT_FILES=$(ls -1 "${PIPELINE_DIR}"/dev-*.json 2>/dev/null)
  if [ -n "$CONTEXT_FILES" ]; then
    DATA_SOURCE="context"
    echo "No JSONL history found. Using pipeline context files"
  fi
fi

# No data available
if [ "$DATA_SOURCE" = "none" ]; then
  echo "WARNING: No pipeline execution data found."
  echo "  Expected: ${HISTORY_DIR}/*.jsonl (from execution history)"
  echo "  Minimum:  ${PIPELINE_DIR}/dev-*.json"
  echo ""
  echo "Run some pipeline stages first, or enable execution history (#649)."
  exit 0
fi
```

#### Step 1.3: Apply Filters

Filter data based on arguments:

```bash
# Validate --since date format (YYYY-MM-DD)
if [ -n "$SINCE_DATE" ]; then
  if ! printf '%s\n' "$SINCE_DATE" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
    echo "ERROR: --since must be YYYY-MM-DD format (got: $SINCE_DATE)"
    exit 1
  fi
fi

# Filter by date (--since)
if [ -n "$SINCE_DATE" ]; then
  # For JSONL: filter files by name (YYYY-MM-DD.jsonl)
  # For state/context: filter by created_at field
  JSONL_FILES=$(echo "$JSONL_FILES" | while read f; do
    FILE_DATE=$(basename "$f" .jsonl)
    if [[ "$FILE_DATE" >= "$SINCE_DATE" ]]; then echo "$f"; fi
  done)
fi

# Filter by issue (--issue)
if [ -n "$ISSUE_FILTER" ]; then
  # For JSONL: filter records by issue_number field
  # For context: filter files by issue number in filename
  echo "Filtering for issue #${ISSUE_FILTER}"
fi

# Limit by run count (--runs)
# Applied during extraction phase
```

---

### Phase 2: Extract Raw Metrics (Deterministic)

**IMPORTANT**: This phase uses Python3 for data extraction because JSONL files
contain one JSON object per line and piping through `cat | jq` loses records.
Python's `glob.glob()` + `json.loads()` per line is the reliable approach. Do
NOT read raw JSONL into AI context — extract only aggregated metrics.

#### Actual JSONL Record Schema

Each line in `*.jsonl` is a complete pipeline run record:

```json
{
  "schema_version": "1",
  "record_type": "run",
  "issue_number": 654,
  "title": "feat: ...",
  "branch": "feat/654-...",
  "base_branch": "main",
  "execution_mode": "automatic",
  "started_at": "ISO-8601",
  "completed_at": "ISO-8601",
  "total_duration_ms": 813341,
  "outcome": "complete",
  "stages": {
    "issue-pickup": {
      "status": "complete",
      "started_at": "ISO-8601",
      "completed_at": "ISO-8601",
      "duration_ms": 55014,
      "execution_mode": "headless"
    },
    "feature-planning": { "...same shape..." },
    "feature-dev": { "...same shape..." },
    "feature-validate": { "...same shape..." },
    "pr-create": { "...same shape..." },
    "pr-merge": { "...same shape..." }
  },
  "tokens": {
    "total_input": 304,
    "total_output": 30158,
    "total_cache_read": 5350629,
    "total_cache_creation": 154560,
    "estimated_cost_usd": 3.56,
    "per_stage": {
      "issue-pickup": {
        "input": 71,
        "output": 2965,
        "cache_read": 459492,
        "cache_creation": 21950
      }
    }
  },
  "labels": ["type:feature"],
  "size": "M",
  "type": "feature",
  "priority": "high",
  "recorded_at": "ISO-8601"
}
```

**Key schema notes:**

- Stages are **nested under `stages`** (not flat `.stage` field)
- A stage with `duration_ms: 0` or missing means it was skipped
- Token data is under `tokens` with `total_*` fields and `per_stage` breakdown
- `estimated_cost_usd` is **pre-computed** — use it instead of recalculating
- `labels` contains the full GitHub labels array (Issue #844) — classification
  labels only (`type:*`, `component:*`); priority/size are board fields
- `size`, `type`, `priority` are extracted from board fields (or legacy labels)
  for convenient filtering
- Older records may have `labels: []` and missing size/type/priority fields

#### Step 2.1: Extract from JSONL History (Primary)

When `DATA_SOURCE="history"`, invoke the deterministic Go verb
`nightgauge pipeline aggregate` (audit row **B2**). The verb replaces
~230 lines of inline-Python aggregation with a single binary call that emits
a stable JSON schema (v1) — see
[docs/GO_BINARY.md](https://github.com/nightgauge/nightgauge/blob/main/docs/GO_BINARY.md#pipeline-analysis) for the schema
reference and flag list.

```bash
# Audit row B2 — deterministic Go aggregation over the daily JSONL history.
nightgauge pipeline aggregate \
  --runs "${RUNS_LIMIT}" \
  ${SINCE_DATE:+--since "${SINCE_DATE}"} \
  ${ISSUE_FILTER:+--issue "${ISSUE_FILTER}"} \
  --include analysis \
  --workdir . \
  --json > /tmp/audit_extracted.json 2>/dev/null \
  || echo '{"v":1,"runs_analyzed":0,"runs":[],"stage_metrics":{},"model_usage":{"by_stage":{},"by_source":{}},"warnings":["pipeline aggregate failed"]}' > /tmp/audit_extracted.json

echo "Extracted $(jq -r '.runs_analyzed' /tmp/audit_extracted.json) runs to /tmp/audit_extracted.json"
```

The output schema (v1) provides the same metric set the previous Python
aggregator computed, with these field renames (locked by the v1 contract):

| Old (Python) field                        | New (Go schema v1) field               |
| ----------------------------------------- | -------------------------------------- |
| `run_metrics`                             | `runs`                                 |
| `stage_durations.<stage>`                 | `stage_metrics.<stage>.duration_stats` |
| `stage_statuses.<stage>`                  | `stage_metrics.<stage>.status`         |
| `stage_tokens.<stage>`                    | `stage_metrics.<stage>.token_stats`    |
| `size_estimation_accuracy.baselines`      | `analysis.size_baselines`              |
| `size_estimation_accuracy.accuracy_rates` | `analysis.size_accuracy_rates`         |
| `size_estimation_accuracy.oversized`      | `analysis.oversized`                   |
| `size_estimation_accuracy.undersized`     | `analysis.undersized`                  |
| `size_estimation_accuracy.weekly_trend`   | `analysis.weekly_accuracy`             |

`duration_stats` and `token_stats` are pre-computed `Stats` objects with
`count`, `median`, `mean`, `p90`, `min`, `max` — the analysis phase reads them
directly instead of recomputing percentiles in Python.

#### Step 2.2: Extract from Context Files (Minimal Fallback)

When `DATA_SOURCE="context"`, extract from dev-\*.json pipeline context files:

```bash
for f in "${PIPELINE_DIR}"/dev-*.json; do
  ISSUE=$(jq -r '.issue_number' "$f")
  TESTS_PASSED=$(jq -r '.tests_status.passed // 0' "$f")
  TESTS_FAILED=$(jq -r '.tests_status.failed // 0' "$f")
  FILES_MODIFIED=$(jq -r '.files_changed.modified | length' "$f")
  echo "{\"issue\": $ISSUE, \"tests_passed\": $TESTS_PASSED, \"tests_failed\": $TESTS_FAILED, \"files_modified\": $FILES_MODIFIED}"
done | jq -s '.' > /tmp/audit_extracted.json
```

---

### Phase 3: Compute Analysis

Read `/tmp/audit_extracted.json` (produced in Phase 2) and compute aggregates.
The extraction step already parsed the JSONL and built structured data — this
phase reads that pre-aggregated JSON to compute higher-level analysis.

#### Step 3.1: Read Extracted Data

Read the file `/tmp/audit_extracted.json` to load the extracted metrics into
context. This file is small (pre-aggregated) — safe to read directly.

Use the extracted `runs`, `stage_metrics`, and `model_usage` blocks to compute
the following analyses. Per-stage stats (count/median/mean/p90/min/max) are
pre-computed under `stage_metrics.<stage>.duration_stats` and
`stage_metrics.<stage>.token_stats` — read them directly instead of
recomputing percentiles.

#### Step 3.2: Token Efficiency Analysis

From `runs`, compute:

- **Total tokens**: Sum of `total_input + total_output` across all runs
- **Avg tokens/run**: Total divided by `runs_analyzed`
- **Cache hit rate**:
  `sum(total_cache_read) / sum(total_cache_read + total_cache_creation + total_input) * 100`
- **Per-stage cache hit rate** (Issue #3804): For each stage in
  `stage_metrics`, compute
  `cache_read.mean / (cache_read.mean + cache_creation.mean + input.mean) * 100`
  from `stage_metrics.<stage>.token_stats`. This is the same canonical formula
  as the global rate above, applied per stage. A stage whose `cache_read.count`
  is `0` (or whose denominator is `0` — e.g. a skipped deterministic stage with
  no cacheable input) is reported as `null` ("no data"), **never** `0%`, so it
  does not raise a false low-reuse finding. Emit the result as the
  `per_stage_cache_hit_rate` report block (see Phase 5.1).
- **Per-stage token distribution**: From `stage_metrics.<stage>.token_stats`,
  compute what % each stage uses
- **Output token outliers**: Runs where `total_output` exceeds 2x the median

#### Step 3.3: Stage Performance Analysis

From `stage_metrics`, compute:

- **Per-stage median/avg/p90/max duration**: Read directly from
  `stage_metrics.<stage>.duration_stats` (in milliseconds)
- **Failure rate**: From `stage_metrics.<stage>.status`, count "failed"/"error"
  vs total
- **Bottleneck detection**: Stages where mean duration > 1.5x overall average
- **Stage skip rate**: Count runs where stage appears in `runs[].skipped_stages`

#### Step 3.4: Cost Analysis

From `runs`, compute:

- **Total cost**: Sum of `estimated_cost_usd` (pre-computed in records — use it
  directly, do NOT recalculate from token counts)
- **Avg/median/p90/max cost per run**
- **Top 10 most expensive runs** by `estimated_cost_usd`
- **Per-stage cost breakdown** for expensive runs (from
  `stage_metrics.<stage>.token_stats`)
- **Daily cost trends**: Group by `started_at[:10]`

#### Step 3.5: Quality Correlation

From `runs`, compute:

- **Success rate**: Count `outcome == "complete"` / total
- **Validate-to-dev ratio**: Per-run, compare feature-validate duration vs
  feature-dev duration. Ratio > 2.0 is a red flag (validate doing too much work)

#### Step 3.6: Size-Correlated Analysis (Issue #844)

From `runs`, group by `size`, `type`, and `priority`:

- **Cost by size**: Avg/median `estimated_cost_usd` for each size (XS/S/M/L/XL)
- **Duration by size**: Avg/median `total_duration_ms` for each size
- **Cost by type**: Avg cost for feature vs bug vs docs vs refactor
- **Priority vs cost**: Does priority correlate with cost?
- **Size accuracy**: Flag runs where cost deviates significantly from size
  expectations (e.g., S-sized issue with L-sized cost)

Note: Older records without size/type/priority fields should be excluded from
size-correlated analysis. Count how many records have these fields populated.

#### Step 3.7: Model Usage Analysis (Issue #1590)

From `model_usage`, compute:

- **Per-stage model distribution**: For each stage, show which model was used
  and how often (e.g., feature-dev: 80% sonnet, 20% opus). Read from
  `model_usage.by_stage.<stage>` (also mirrored at
  `stage_metrics.<stage>.models`).
- **Model source distribution**: How the stage ended up on the model it ran
  (`scheduler` / `cli-refusal-fallback` / `model-unavailable-downgrade` /
  `escalation` — the `MODEL_SELECTION_SOURCES` vocabulary; plus `unknown`
  for a record whose source is empty). Read from
  `model_usage.by_source.<stage>`. Note `scheduler` means the scheduler's
  resolved model ran unsubstituted — it covers operator pins as well as
  automatic picks, so never report it as "auto-selected".
- **Misrouting detection**: Flag runs where expensive models (opus) were used
  for deterministic stages (issue-pickup, pr-create, pr-merge)
- **Cost-per-model**: Cross-reference with `stage_metrics.<stage>.token_stats`
  to compute avg cost when each model was used per stage

Note: Older records without `model_selection` in stages will have empty
`model_usage`. Report the count of records with model data vs without.

#### Step 3.9: Size Estimate Accuracy (Issue #1591)

From the `analysis` block (populated when `--include analysis` was passed):

- **Per-size accuracy table**: For each size label (XS/S/M/L/XL), show:
  - Run count, median cost, avg cost, min/max cost — read directly from
    `analysis.size_baselines.<size>`
  - Accuracy rate — % of runs whose actual cost fell within 0.5x–2x of the
    size's median cost — read from `analysis.size_accuracy_rates.<size>`
  - Flag sizes with accuracy rate below 70% as needing recalibration
- **Oversize detection**: List issues from `analysis.oversized` — labeled
  bigger than the actual cost bracket suggests (e.g., labeled L but cost
  matched S median). These represent wasted budget.
- **Undersize detection**: List issues from `analysis.undersized` — labeled
  smaller than actual cost suggests. These represent budget-constrained runs
  that may have produced lower quality output.
- **Sizing accuracy trend**: From `analysis.weekly_accuracy`, determine if
  sizing accuracy is improving, stable, or degrading over time. Compare
  first-half accuracy to second-half accuracy across the analysis window.

Note: Runs without a `size` field are excluded. The aggregator reports
`analysis.runs_with_size` and `analysis.runs_without_size` directly.

#### Step 3.8: Trend Analysis

From `runs`, group by `started_at[:10]` and compute:

- **Daily run count, avg cost, total cost**
- **Week-over-week comparisons** if data spans multiple weeks
- **Efficiency trajectory**: Is avg cost/run increasing, stable, or decreasing?

#### Step 3.10: Retry & Backtrack Analysis

From `runs`, analyze retry patterns:

- **Per-stage retry rate**: Count stages with `retry_count > 0` / total stage
  executions
- **Avg retries per run**: Mean of `retry_count` across all runs
- **Backtrack frequency**: Count runs that triggered stage backtracks
- **Retry cost impact**: For runs with retries, compute the cost premium vs
  no-retry runs
- **Flag**: Stages with retry rate > 20% as needing investigation

#### Step 3.11: Comparison Analysis (Conditional)

Only runs when `--compare DATE` is provided. Splits runs into "before" and
"after" the comparison date, then computes deltas for:

- Avg cost per run (before vs after, % change)
- Success rate (before vs after)
- Avg duration (before vs after)
- Cache hit rate (before vs after)

Display as a comparison table in the human-readable output.

#### Step 3.12: Friction Correlation (Optional)

If the `assessments/` directory in the clone's pipeline state directory
(`nightgauge layout path pipeline assessments`) exists and contains
assessment records:

- Count friction records per skill
- Correlate skills with most friction to their cost data
- Include "Skills with Most Friction" section in findings

Gracefully skip if no assessments directory exists.

---

### Phase 4: Generate Findings (Probabilistic)

**PURPOSE**: AI interprets the computed metrics to produce human-readable
findings with severity levels and actionable recommendations.

#### Step 4.1: Read Computed Metrics

Read all `/tmp/audit_*.json` files produced in Phase 3. These are small,
pre-aggregated JSON — safe to load into AI context.

#### Step 4.2: Classify Findings

For each analysis category, generate findings:

```json
{
  "category": "token_efficiency | stage_performance | cost_optimization | quality_correlation | trends | size_estimation_accuracy | retry_analysis",
  "severity": "critical | high | medium | low | info",
  "title": "Short description of finding",
  "description": "Detailed explanation with supporting metrics",
  "estimated_savings": "$X.XX/run or X% improvement",
  "recommendation": "Specific actionable step to address this"
}
```

**Severity Classification Rules:**

| Severity   | Criteria                                                  |
| ---------- | --------------------------------------------------------- |
| `critical` | >50% waste or >30% failure rate                           |
| `high`     | >25% waste or >15% failure rate or clear cost opportunity |
| `medium`   | >10% waste or noticeable inefficiency                     |
| `low`      | Minor optimization opportunity                            |
| `info`     | Informational — positive trend or baseline observation    |

**Per-stage low-reuse cache finding (Issue #3804):**

For each stage whose `per_stage_cache_hit_rate` (from Step 3.2) is non-`null`
and **below the configured threshold**, emit a `token_efficiency` finding. The
threshold is resolved from config:

- `pipeline.cache.stage_alert_thresholds.<stage>` if present for that stage,
- else `pipeline.cache.alert_threshold` (default `40`, i.e. 40%).

Both this skill and the Token Economics health dimension read the same resolved
value, so the per-stage finding agrees across surfaces. Severity:

| Per-stage rate | Severity |
| -------------- | -------- |
| `< 10%`        | `high`   |
| `< threshold`  | `medium` |

Stages reported as `null` (no cacheable input) are skipped — never flagged.
Example finding `title`: `"Low cache reuse for feature-validate (18%)"`.

#### Step 4.3: Generate Recommendations

For each finding, produce a specific recommendation. Recommendations must be:

- **Actionable**: Describes a specific change to make
- **Measurable**: Includes expected improvement metric
- **Scoped**: References specific stages, files, or configurations

---

### Phase 5: Output Results

#### Step 5.1: Structured JSON Report

Assemble the full report:

```json
{
  "schema_version": "1.1",
  "analysis_period": {
    "from": "YYYY-MM-DD",
    "to": "YYYY-MM-DD",
    "runs_analyzed": 15,
    "data_source": "history | state | context"
  },
  "summary": {
    "total_cost_usd": 12.5,
    "avg_cost_per_run": 0.83,
    "total_tokens": 1500000,
    "success_rate": 0.87,
    "avg_duration_minutes": 45,
    "cache_hit_rate": 0.96
  },
  "per_stage_cache_hit_rate": {
    "<stage>": {
      "cache_hit_rate": "number (0-1) | null",
      "sample_count": "number",
      "threshold": "number (0-1, resolved per-stage threshold)",
      "below_threshold": "boolean"
    },
    "feature-dev": {
      "cache_hit_rate": 0.974,
      "sample_count": 41,
      "threshold": 0.4,
      "below_threshold": false
    },
    "issue-pickup": {
      "cache_hit_rate": null,
      "sample_count": 0,
      "threshold": 0.4,
      "below_threshold": false
    }
  },
  "findings": [
    {
      "category": "token_efficiency",
      "severity": "high",
      "title": "Feature-dev reads ARCHITECTURE.md on every run (15KB)",
      "description": "...",
      "estimated_savings": "$0.15/run",
      "recommendation": "Use context compaction or cache this file"
    }
  ],
  "recommendations": [
    {
      "priority": 1,
      "action": "Enable prompt caching for frequently-read docs",
      "impact": "~15% token reduction",
      "effort": "low"
    }
  ],
  "model_usage": {
    "by_stage": {
      "issue-pickup": { "haiku": 15 },
      "feature-planning": { "sonnet": 15 },
      "feature-dev": { "sonnet": 12, "opus": 3 },
      "feature-validate": { "haiku": 8, "sonnet": 7 },
      "pr-create": { "haiku": 15 },
      "pr-merge": { "haiku": 15 }
    },
    "by_source": {
      "issue-pickup": { "scheduler": 15 },
      "feature-dev": { "scheduler": 12, "cli-refusal-fallback": 3 }
    }
  },
  "size_estimation_accuracy": {
    "baselines": {
      "S": { "count": 40, "median_cost": 3.5, "avg_cost": 4.1 },
      "M": { "count": 30, "median_cost": 7.2, "avg_cost": 8.0 },
      "L": { "count": 20, "median_cost": 12.5, "avg_cost": 13.8 }
    },
    "accuracy_rates": {
      "S": { "total": 40, "within_range": 34, "accuracy_pct": 85.0 },
      "M": { "total": 30, "within_range": 22, "accuracy_pct": 73.3 },
      "L": { "total": 20, "within_range": 12, "accuracy_pct": 60.0 }
    },
    "oversized": [
      {
        "issue_number": 1234,
        "labeled_size": "L",
        "actual_bracket": "S",
        "cost_usd": 3.2
      }
    ],
    "undersized": [
      {
        "issue_number": 1235,
        "labeled_size": "S",
        "actual_bracket": "L",
        "cost_usd": 14.5
      }
    ],
    "weekly_trend": [
      { "week": "2026-W05", "total": 8, "accurate": 6, "accuracy_pct": 75.0 },
      { "week": "2026-W06", "total": 7, "accurate": 6, "accuracy_pct": 85.7 }
    ],
    "sizing_trend": "improving | stable | degrading"
  },
  "retry_analysis": {
    "per_stage_retry_rate": {
      "<stage>": "number (0-1)"
    },
    "avg_retries_per_run": "number",
    "backtrack_count": "number",
    "retry_cost_premium_pct": "number",
    "flagged_stages": ["string (stages with retry rate > 20%)"]
  },
  "trends": {
    "cost_trend": "decreasing | stable | increasing",
    "efficiency_trend": "improving | stable | degrading",
    "quality_trend": "improving | stable | degrading"
  },
  "created_at": "ISO-8601 timestamp"
}
```

If `--format json`, write it with
`nightgauge layout write pipeline audit-report-YYYY-MM-DD.json` (content on
stdin, or `--from FILE` after writing a temp file outside the git directory)
and output the path the command prints.

#### Step 5.2: Human-Readable Summary

Output a formatted summary to the console:

```
PIPELINE AUDIT REPORT
═══════════════════════════════════════════════════════════

Analysis Period: 2026-02-01 to 2026-02-12 (15 runs)
Data Source: execution history (JSONL)

SUMMARY
───────────────────────────────────────────────────────────
  Total Cost:        $12.50 (avg $0.83/run)
  Total Tokens:      1,500,000 (avg 100,000/run)
  Success Rate:      87% (13/15 runs)
  Avg Duration:      45 minutes
  Cache Hit Rate:    23%

PER-STAGE CACHE HIT RATE
───────────────────────────────────────────────────────────
  feature-planning:   96%
  feature-dev:        97%
  feature-validate:   18%  ⚠ below 40% threshold
  pr-create:          96%
  pr-merge:           95%
  issue-pickup:       — (no cacheable input)

MODEL USAGE
───────────────────────────────────────────────────────────
  issue-pickup:       haiku (100% via scheduler)
  feature-planning:   sonnet (100% via scheduler)
  feature-dev:        sonnet 80% / opus 20% (scheduler)
  feature-validate:   haiku 53% / sonnet 47% (scheduler)
  pr-create:          haiku (100% via scheduler)
  pr-merge:           haiku (100% via scheduler)

SIZE ESTIMATE ACCURACY (90 runs with size data)
───────────────────────────────────────────────────────────
  Size   Runs   Median $   Accuracy
  XS       10     $1.20      90.0%
  S        40     $3.50      85.0%
  M        30     $7.20      73.3%  ⚠ below 70% threshold
  L        20    $12.50      60.0%  ⚠ below 70% threshold

  Oversized (3 issues — labeled bigger than actual cost):
    #1234  L → S bracket  ($3.20)
    #1456  M → S bracket  ($3.80)
    #1478  XL → M bracket ($7.10)

  Undersized (2 issues — labeled smaller than actual cost):
    #1235  S → L bracket  ($14.50)
    #1467  S → M bracket  ($8.20)

  Sizing Trend: improving (75.0% → 85.7% over 2 weeks)

RETRY & BACKTRACK ANALYSIS
───────────────────────────────────────────────────────────
  Avg retries/run:     0.3
  Backtrack count:     2 runs triggered backtracks
  Retry cost premium:  +18% vs no-retry runs

  Per-Stage Retry Rates:
    feature-dev:        25%  ⚠ above 20% threshold
    feature-validate:   12%
    pr-create:           5%

FINDINGS (4 total: 1 high, 2 medium, 1 info)
───────────────────────────────────────────────────────────

  [HIGH] Token Efficiency: feature-dev reads ARCHITECTURE.md every run
    → Estimated savings: $0.15/run ($2.25 over 15 runs)
    → Recommendation: Enable prompt caching for stable docs

  [MEDIUM] Stage Performance: feature-validate is 2.3x slower than average
    → Avg duration: 12 min (vs 5.2 min average)
    → Recommendation: Review test suite for redundant test cases

  [MEDIUM] Cost Optimization: 3 runs used Opus for simple bug fixes
    → Estimated savings: $1.20/run with Sonnet
    → Recommendation: Use model routing based on issue complexity

  [INFO] Trends: Cost per run decreased 12% week-over-week
    → Positive trend — continue current optimization approach

RECOMMENDATIONS (sorted by impact)
───────────────────────────────────────────────────────────
  1. Enable prompt caching for docs/  (~15% token reduction, low effort)
  2. Route simple issues to Sonnet    (~$1.20/run savings, medium effort)
  3. Optimize feature-validate tests  (~40% stage speedup, medium effort)

───────────────────────────────────────────────────────────
Run with --create-issues to auto-create GitHub issues for findings.
Next: /nightgauge:pipeline-audit --create-issues --severity medium
```

---

### Phase 6: Create Issues (Optional)

Only executes when `--create-issues` is passed.

#### Step 6.1: Filter by Severity

```bash
# Only create issues for findings at or above MIN_SEVERITY
# Default: high (critical + high only)
# With --severity medium: critical + high + medium
```

#### Step 6.2: Check for Existing Issues

Before creating, search for duplicates across **both open and recently closed**
issues. Findings may overlap with recently completed epics.

```bash
REPO="${NIGHTGAUGE_REPO:-$(nightgauge git repo-slug)}"

# Search open issues (`issue list --search` is scoped to open issues)
nightgauge issue list --repo "$REPO" --search "${FINDING_KEYWORDS}" --json | jq '[(. // [])[] | {number, title}]'

# Search closed issues/epics (no list verb reaches closed issues: GraphQL search)
nightgauge forge graphql -f query='query($q: String!) { search(query: $q, type: ISSUE, first: 20) { nodes { ... on Issue { number title } } } }' \
  -f q="repo:$REPO is:issue is:closed ${FINDING_KEYWORDS}" | jq '.data.search.nodes'

# Also check open epics that may already cover this finding
nightgauge forge issue list --repo "$REPO" --labels "type:epic" --json | jq '[.[:30][] | {number, title}]'
```

Skip creation if a matching issue exists (open or recently closed).

#### Step 6.3: Create Issues

When creating 3+ findings as issues, create them as an **epic with sub-issues**
using the project's deterministic hooks. This follows the
`/nightgauge-issue-create` workflow:

**Step 6.3a**: Create the epic:

```bash
REPO="${NIGHTGAUGE_REPO:-$(nightgauge git repo-slug)}"
nightgauge issue create --repo "$REPO" \
  --title "epic: Pipeline audit findings — ${SUMMARY}" \
  --body "${EPIC_BODY}" \
  --labels "type:epic,priority:high,size:L"
```

**Step 6.3b**: Create each sub-issue using the Go binary:

```bash
BINARY="${NIGHTGAUGE_BIN:-}"
[ -n "$BINARY" ] && [ ! -x "$BINARY" ] && BINARY=""
[ -z "$BINARY" ] && BINARY=$(command -v nightgauge 2>/dev/null || echo "")
if [ -z "$BINARY" ] && [ "${NIGHTGAUGE_DEV_REPO_BIN:-}" = "1" ]; then
  for NG_ROOT in "$(git rev-parse --show-toplevel 2>/dev/null)" \
    "$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." 2>/dev/null && pwd)"; do
    [ -x "$NG_ROOT/bin/nightgauge" ] || continue
    grep -qx 'module github.com/nightgauge/nightgauge' "$NG_ROOT/go.mod" 2>/dev/null || continue
    NG_ORIGIN="$(git -C "$NG_ROOT" config --get remote.origin.url 2>/dev/null)"
    case "${NG_ORIGIN%.git}" in
      git@github.com:nightgauge/nightgauge | https://github.com/nightgauge/nightgauge | \
        ssh://git@github.com/nightgauge/nightgauge) BINARY="$NG_ROOT/bin/nightgauge"; break ;;
    esac
  done
fi
[ -z "$BINARY" ] && [ -x "$HOME/go/bin/nightgauge" ] && BINARY="$HOME/go/bin/nightgauge"
[ -n "$BINARY" ] && export PATH="$(dirname "$BINARY"):$PATH"

"$BINARY" issue create-sub <EPIC_NUMBER> \
  --title "<FINDING_TITLE>" --body "<FINDING_BODY>" \
  --labels "type:bug,component:go-binary"
```

`create-sub` takes the parent number positionally and everything else as
flags, and `--labels` takes label NAMES (#1214) — it resolves them to node IDs
itself and fails before creating anything if a name is unknown. The raw
`addLabelsToLabelable` mutation that used to be documented here is not needed.

**Step 6.3c**: Sync the epic to the project board:

```bash
"$BINARY" project add <EPIC_NUMBER>
```

Each sub-issue created via `issue create-sub` is automatically synced to the
project board by the binary.

**Severity to priority mapping:**

| Finding Severity | Issue Priority    |
| ---------------- | ----------------- |
| `critical`       | `priority:high`   |
| `high`           | `priority:high`   |
| `medium`         | `priority:medium` |
| `low`            | `priority:low`    |

For fewer than 3 findings, create standalone issues instead of an epic.

#### Step 6.4: Report Created Issues

Output list of created issues with numbers, titles, and URLs.

---

### Self-Assessment Epilogue

### Self-Assessment Epilogue

**PURPOSE**: Evaluate whether this skill's instructions matched reality during
this execution. This phase is **non-blocking** — skip entirely if any main phase
failed. A perfectly working skill produces **no output** from this phase.

> See [docs/SKILL_SELF_ASSESSMENT.md](https://github.com/nightgauge/nightgauge/blob/main/docs/SKILL_SELF_ASSESSMENT.md) for
> the full strategy, synthesis algorithm, and integration architecture.

#### When to Skip

- Any prior phase exited with an error before completing
- The skill was cancelled or timed out
- Running in `--dry-run` mode

#### Step 1: Evaluate Execution Friction

Review the execution that just completed. Answer these questions honestly:

1. **Command failures**: Did any command, script, or binary call in the skill
   instructions fail? (e.g., script not found, wrong flags, missing binary)
2. **Workarounds**: Did you have to deviate from the skill instructions to
   accomplish the goal? (e.g., used `gh` directly because a referenced script
   didn't exist, skipped a step that referenced a nonexistent file)
3. **Stale references**: Did any file path, function name, API endpoint, or tool
   referenced in the instructions not exist in the current codebase?
4. **Unclear instructions**: Were any instructions ambiguous enough that you had
   to guess at the intended behavior?
5. **Missing instructions**: Was there a significant step you had to figure out
   on your own that should have been documented in this skill?

**If ALL answers are "no" — write nothing and complete the skill normally.** The
goal is silence when everything works.

#### Step 2: Write Assessment Record

Only if friction was detected in Step 1. Write a single JSON file,
`assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json` in the clone's pipeline state
directory, through the binary (JSON on stdin; never a direct file write):

```bash
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
nightgauge layout write pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json" <<'JSON'
{ ... the record below ... }
JSON
```

The assessment record MUST follow this schema:

```json
{
  "schema_version": "1",
  "skill": "{STAGE_NAME}",
  "skill_file": "skills/nightgauge-{STAGE_NAME}/SKILL.md",
  "issue_number": 42,
  "timestamp": "2026-03-10T14:30:00Z",
  "friction": [
    {
      "type": "command_failure",
      "severity": "high",
      "description": "hooks/lib/add-to-project.sh not found — script was deleted",
      "skill_line_hint": "claude-plugins/nightgauge/hooks/lib/add-to-project.sh <issue-number>",
      "actual_resolution": "Used gh api graphql to add issue to project board directly",
      "suggested_fix": "Replace with: nightgauge project add <issue-number>"
    }
  ]
}
```

**Friction types**: `command_failure`, `workaround`, `stale_reference`,
`unclear_instruction`, `missing_instruction`

**Severity levels**:

- `high` — instruction is **broken**. Required manual workaround to complete.
- `medium` — instruction is **misleading**. Agent adapted without user help.
- `low` — instruction is **suboptimal**. No functional impact.

**Rules**:

- **One record per execution** — multiple friction items go in the `friction`
  array, not separate files.
- **Be specific** — quote the exact instruction text that was wrong. Not "some
  commands didn't work" but "Step 5.2 calls `hooks/lib/add-to-project.sh` but
  this script was deleted in commit 65915701."
- **Suggest the fix** — every finding MUST include `suggested_fix` with the
  concrete SKILL.md change needed. Not "update the docs" but "replace
  `hooks/lib/add-to-project.sh <N>` with `nightgauge project add <N>`."
- **Don't invent friction** — only report issues you actually encountered during
  this execution. Do not speculate about potential problems.

#### Step 3: Validate and Complete

```bash
# Validate JSON if written
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
ASSESSMENT_FILE="$(nightgauge layout path pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json")"
if [ -f "$ASSESSMENT_FILE" ]; then
  python3 -m json.tool "$ASSESSMENT_FILE" > /dev/null 2>&1 || \
    echo "WARNING: Assessment record is not valid JSON" >&2
fi
```


---

## Output Format

### JSON Schema

The full JSON report follows this structure:

```json
{
  "schema_version": "1.1",
  "analysis_period": {
    "from": "string (YYYY-MM-DD)",
    "to": "string (YYYY-MM-DD)",
    "runs_analyzed": "number",
    "data_source": "string (history | state | context)"
  },
  "summary": {
    "total_cost_usd": "number",
    "avg_cost_per_run": "number",
    "total_tokens": "number",
    "success_rate": "number (0-1)",
    "avg_duration_minutes": "number",
    "cache_hit_rate": "number (0-1, global)"
  },
  "per_stage_cache_hit_rate": {
    "<stage>": {
      "cache_hit_rate": "number (0-1) | null",
      "sample_count": "number",
      "threshold": "number (0-1)",
      "below_threshold": "boolean"
    }
  },
  "findings": [
    {
      "category": "string",
      "severity": "string (critical | high | medium | low | info)",
      "title": "string",
      "description": "string",
      "estimated_savings": "string",
      "recommendation": "string"
    }
  ],
  "recommendations": [
    {
      "priority": "number",
      "action": "string",
      "impact": "string",
      "effort": "string (low | medium | high)"
    }
  ],
  "model_usage": {
    "by_stage": {
      "<stage>": { "<model>": "number (count)" }
    },
    "by_source": {
      "<stage>": { "<source>": "number (count)" }
    }
  },
  "size_estimation_accuracy": {
    "baselines": {
      "<size>": {
        "count": "number",
        "median_cost": "number",
        "avg_cost": "number",
        "min_cost": "number",
        "max_cost": "number",
        "median_duration_ms": "number",
        "avg_duration_ms": "number"
      }
    },
    "accuracy_rates": {
      "<size>": {
        "total": "number",
        "within_range": "number",
        "accuracy_pct": "number (0-100)"
      }
    },
    "oversized": [
      {
        "issue_number": "number",
        "title": "string",
        "labeled_size": "string",
        "actual_bracket": "string",
        "cost_usd": "number"
      }
    ],
    "undersized": ["...same shape as oversized..."],
    "weekly_trend": [
      {
        "week": "string (YYYY-Www)",
        "total": "number",
        "accurate": "number",
        "accuracy_pct": "number (0-100)"
      }
    ],
    "sizing_trend": "string (improving | stable | degrading)",
    "runs_with_size": "number",
    "runs_without_size": "number"
  },
  "retry_analysis": {
    "per_stage_retry_rate": {
      "<stage>": "number (0-1)"
    },
    "avg_retries_per_run": "number",
    "backtrack_count": "number",
    "retry_cost_premium_pct": "number",
    "flagged_stages": ["string (stages with retry rate > 20%)"]
  },
  "trends": {
    "cost_trend": "string (decreasing | stable | increasing)",
    "efficiency_trend": "string (improving | stable | degrading)",
    "quality_trend": "string (improving | stable | degrading)"
  },
  "created_at": "string (ISO-8601)"
}
```

---

## Analysis Categories

### 1. Token Efficiency

Identifies wasteful token usage patterns:

- **Per-stage token averages**: Which stages consume the most tokens
- **Outlier detection**: Runs with abnormally high token usage
- **Cache hit rates**: How effectively prompt caching reduces input tokens
- **Input/output ratios**: Balance between context injection and generation
- **Redundant reads**: Same files read multiple times across stages

### 2. Stage Performance

Timing and reliability analysis per pipeline stage:

- **Duration baselines**: Average time per stage with percentiles
- **Failure rates**: Which stages fail most often
- **Retry frequency**: How often stages need re-execution
- **Bottleneck detection**: Stages that are >1.5x slower than average
- **Stage skip effectiveness**: When bypassing stages saves time

### 3. Cost Optimization

Financial analysis and savings opportunities:

- **Total cost trends**: Week-over-week cost tracking
- **Per-run cost distribution**: Identify expensive outlier runs
- **Cost-per-stage**: Where money is spent
- **Model opportunities**: Where a cheaper model would suffice
- **Cache optimization**: Potential savings from better caching

### 4. Quality Correlation

Relationship between spending and outcomes:

- **Success rate trends**: Pipeline reliability over time
- **First-attempt pass rates**: How often runs succeed without retries
- **Token-spend vs outcome**: Does spending more produce better results
- **PR quality signals**: Review iteration counts correlated with spend

### 5. Model Routing (Issue #1590)

Per-stage model selection analysis:

- **Model distribution by stage**: Which model was used for each stage and how
  often
- **Selection source breakdown**: `scheduler` vs `cli-refusal-fallback` vs
  `model-unavailable-downgrade` vs `escalation` (see
  `MODEL_SELECTION_SOURCES`), plus `unknown` for an empty source
- **Misrouting detection**: Expensive models used for deterministic stages
- **Cost-per-model-per-stage**: Average cost when each model is used per stage

### 6. Trend Analysis

Longitudinal patterns across time:

- **Week-over-week comparisons**: Cost, tokens, duration, success rate
- **Rolling averages**: Smoothed 7-day metrics
- **Efficiency trajectory**: Improving, stable, or degrading
- **Seasonal patterns**: Certain issue types consistently cost more

### 7. Size Estimate Accuracy (Issue #1591)

Validates whether issue size labels accurately predict actual cost and duration:

- **Per-size baselines**: Median and average cost/duration for each size bracket
  (XS/S/M/L/XL)
- **Accuracy rates**: Percentage of runs whose actual cost falls within 0.5x–2x
  of the size's median cost — the "within expected range" metric
- **Oversize detection**: Issues labeled with a larger size than their actual
  cost suggests (wasted budget — could have used cheaper model/budget)
- **Undersize detection**: Issues labeled smaller than actual cost suggests
  (budget-constrained runs that may have produced lower quality)
- **Sizing trend**: Weekly accuracy rate over time — is the team getting better
  or worse at estimating issue size?

### 8. Retry & Backtrack Analysis

Analyzes retry and backtrack patterns across pipeline runs:

- **Per-stage retry rates**: Which stages require the most retries
- **Average retries per run**: Mean retry count across all analyzed runs
- **Backtrack frequency**: How often runs trigger stage backtracks
- **Retry cost premium**: Additional cost incurred by runs with retries vs clean
  runs
- **Flagged stages**: Stages with retry rate above 20% that need investigation

---

## Error Handling

| Condition                          | Action                                                         |
| ---------------------------------- | -------------------------------------------------------------- |
| No JSONL history files             | Fall back to context files (dev-\*.json)                       |
| No data at all                     | Output friendly message, exit 0                                |
| Malformed JSONL records            | Skip bad records, report count at end                          |
| python3 not installed              | Error with install instructions                                |
| forge not authenticated            | Error with `nightgauge forge auth login` instructions          |
| `--create-issues` without findings | Output "No findings at severity threshold"                     |
| Insufficient data for trends       | Skip trend analysis, note in output                            |
| `issue create-sub` label error     | Use `nightgauge forge graphql` addLabelsToLabelable separately |
| Duplicate finding exists           | Skip creation, report as already tracked                       |

---

## Pipeline Position

```
UTILITIES (not part of main pipeline)

/nightgauge:pipeline-audit
       ↑
  Use anytime to analyze pipeline efficiency
  (pipeline/ = the clone's pipeline state dir: nightgauge layout path pipeline)
  Reads: pipeline/history/*.jsonl
  Reads: pipeline/dev-*.json (minimal fallback)
```

This is a standalone utility skill. It does not affect pipeline state and can be
run at any time without interfering with active pipeline runs.

---

## Configuration

This skill reads configuration from `.nightgauge/config.yaml`:

| Config Key           | Default | Description                      |
| -------------------- | ------- | -------------------------------- |
| `project.number`     | -       | GitHub Project number for issues |
| `audit.default_runs` | `10`    | Default `--runs` value           |
| `audit.cost_model`   | `opus`  | Cost model for estimation        |
| `audit.auto_create`  | `false` | Auto-create issues without flag  |

---

**Author:** nightgauge **License:** Apache-2.0

Files in this skill

  • CHANGELOG.md2 KB
  • SKILL.md54.5 KB
  • test_size_accuracy.py13.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.