Skip to content
Back to skills

Loop Station

ASecurity

Observe the harness itself — (WS1) measure context budget as a deterministic, trendable inventory (tokens per CLAUDE.md / skill / agent, lexical redundancy, per-CI cold-write totals; committed history.jsonl), (WS2) reconstruct the real invocation trace (which skills/agents/tools fired and how they nested) from session transcripts, and (WS-A) roll the per-run CI traces up into a committed weekly usage record (per-skill invocation counts; usage.jsonl) — the usage side of the dead-weight join. U...

  • 10 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 12, 2026
ai-agentsbashnodecode-reviewgitapifrontendsecurity

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

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

Scanned September 12, 2026

npx -y skills add FriendlyInternet/nuxt-crouton --skill loop-station --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Loop Station?

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

Security grade badge for Loop Station
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/friendlyinternet-loop-station/badge)](https://www.skillsdirectory.com/skills/friendlyinternet-loop-station)

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: loop-station
layer: method
description: Observe the harness itself — (WS1) measure context budget as a deterministic, trendable inventory (tokens per CLAUDE.md / skill / agent, lexical redundancy, per-CI cold-write totals; committed history.jsonl), (WS2) reconstruct the real invocation trace (which skills/agents/tools fired and how they nested) from session transcripts, and (WS-A) roll the per-run CI traces up into a committed weekly usage record (per-skill invocation counts; usage.jsonl) — the usage side of the dead-weight join. Use for "context budget", "how big is CLAUDE.md / our skills", "is the harness bloating", "how do our agent loops actually run", "which skills actually get used", "trace this session", "loop station".
---

# Loop Station — harness observatory (WS1 inventory + WS2 trace)

> WS1 (context-budget inventory) is below; the WS2 invocation trace is the second
> section. Both are producers — WS3 (`pocs/loop-station`) renders them.

# WS1 — context-budget inventory

The first, cheapest layer of the **Loop Station observatory** (epic #926): a
**deterministic** snapshot of how big our always-on harness context is and how
much of it is restated, captured at every merge that touches it. No LLM in this
path — `count_tokens` is free tokenization, not generation — so the trend is
clean and reproducible.

> Boundary: this is the **producer**. The daily one-line readout is WS4
> (housekeeping digest reads the latest record); the rendered view is WS3
> (`pocs/loop-station`); the runtime invocation trace is WS2. Compute stays
> here in the scripts — the view only renders.

## What it measures

Walking root `CLAUDE.md` + every skill (`.claude/skills/**`) + every agent
(`.claude/agents/**`) + each LLM CI workflow's `prompt:` block:

- **tokens per artifact** — via Anthropic `count_tokens` when `ANTHROPIC_API_KEY`
  is set (free, deterministic), else a deterministic lexical heuristic. The
  record carries `tokenizer` so a trend is never silently compared across rulers.
- **lexical redundancy** — shingle-containment (8-word shingles). Corpus % of
  duplicated shingle occurrences (intra- *and* cross-file restatement), each
  file's self-redundancy, and the top restated artifact pairs (e.g. the
  near-duplicate `a11y` / `frontend-review` skills). Lexical, deterministic,
  trendable — escalate to embeddings only if real paraphrase slips through.
- **threshold scorecard** — `length` / `redundancy` / `drift-risk` graded
  green/amber/red by fixed **formulas** in `lib/scorecard.mjs` (not LLM
  judgments). Tune the bands in one place there.
- **cold-write totals** — for each CI workflow that runs `claude-code-action`,
  what it cold-writes per run (always-on `CLAUDE.md` + its own `prompt:` block).
- **budget by harness layer** — `totals.byLayer` rolls each skill/agent's tokens up
  by its `layer:` tag (`method` / `stage` / `stack`; epic #952), with untagged
  artifacts (CLAUDE.md, an un-tagged new skill) bucketed as `unlayered` so the
  per-layer tokens reconcile against `totals.tokens`. Quantifies how much of the
  always-on surface is the portable method vs. the swappable crouton stack.

## Files

| file | role |
|------|------|
| `gather.mjs` | walk + count + score → one JSON record on stdout (`--pretty` adds a summary on stderr) |
| `append-history.mjs` | append the record to `writeups/loop-station/history.jsonl`, idempotent per commit |
| `lib/tokens.mjs` | `count_tokens` with deterministic heuristic fallback |
| `lib/redundancy.mjs` | shingle-containment redundancy + self-redundancy |
| `lib/scorecard.mjs` | threshold bands (the one place to tune) |
| `writeups/loop-station/history.jsonl` | the **committed** trend data (see its README) |

## Run by hand

```bash
node .claude/skills/loop-station/gather.mjs --pretty                       # inspect
node .claude/skills/loop-station/gather.mjs | node .claude/skills/loop-station/append-history.mjs   # record a point
LOOP_STATION_FORCE_HEURISTIC=1 node .claude/skills/loop-station/gather.mjs  # force offline tokenizer (deterministic tests)
```

## In CI

`.github/workflows/loop-station-inventory.yml` runs on **merge to `main`**
path-filtered to `CLAUDE.md` / `.claude/skills/**` / `.claude/agents/**`. It
gathers (with `ANTHROPIC_API_KEY` → `count_tokens`), appends, and commits the new
`history.jsonl` line back with `[skip ci]` and the causing PR recorded — one data
point per relevant merge, with the cause attached, no filler.

---

# WS2 — invocation trace

Reconstructs the **real call tree** of a session — which skills/agents/tools
fired, how they nested, how long sub-agents ran — from Claude Code transcripts.

> **Hard privacy rule:** the trace carries **names + correlation ids + durations
> only**, never a tool's `input` or a `tool_result`'s content. It is runtime
> exhaust → **gitignored**, shipped from CI as an artifact, never committed
> (unlike WS1's `history.jsonl`).

## What it reads

`~/.claude/projects/<slug>/<session>.jsonl` (+ its `subagents/agent-*.jsonl`).
Two transcript layouts are auto-detected — the parser handles both because
Claude Code has shipped both:

- **inline** (current local schema) — sub-agents are `isSidechain` records in the
  main transcript; skills tag their calls with `attributionSkill`. Nesting comes
  from the `parentUuid` tree + those markers.
- **files** (the proven prototype layout) — each sub-agent is its own
  `subagents/agent-*.jsonl`, linked to its spawning Agent call by global
  start-time order (robust for sync *and* async agents), carrying durations.

Both reconstruct **2+-level recursion** (agent→agent→…) to the correct depth.

## Files

| file | role |
|------|------|
| `parse-transcripts.mjs` | **claude harness:** parse one Claude Code session → `trace.jsonl` of `{ts,kind,name,parent,depth,agentId?,durMs?}` (also importable: `parseSession()`) |
| `pi-telemetry.mjs` | **pi harness (#944):** adapt pi.dev telemetry (native session JSONL + subagent meta, per `writeups/architecture/pi-telemetry-schema.md`) → the SAME WS2 event shape **and** the #883 ledger slice. `buildPiTrace(dir)` / `runOutcomeToLedgerRecord(outcome)`; CLI: `node pi-telemetry.mjs <dir> [--ledger]` |
| `pi-telemetry.test.mjs` | runs the adapter against the real captured fixtures (`pocs/loop-station/data/pi-telemetry-sample/`); asserts the ledger slice passes the real `eval-ledger/schema.mjs` validator + payload-freeness |
| `collect-traces.mjs` | CI collector — discover the run's session across all project dirs, tag events with the run id, write one NDJSON file (meta header + tagged events) for artifact upload |
| `lib/parse-transcripts.test.mjs` | fixtures for both layouts, 2-level recursion, payload-freeness, defensiveness |

### Harness parity (#944)

WS2 reads whichever harness is active. The data seam (`pocs/loop-station/scripts/prepare-data.mjs`)
branches on `AGENT_HARNESS`: `pi` → `pi-telemetry.mjs` (live `PI_TELEMETRY_DIR` of subagent metas,
else the committed real sample); anything else → the claude transcript parser. **One feed, two
consumers:** the same pi telemetry yields the WS2 trace **and** the `{model,cost,turns,wall}` slice
for the #883 run-outcome ledger — so the cost ledger *consumes* pi telemetry, it doesn't re-derive
it. Note: pi's WS2 trace is **agent-granularity** today (one node per subagent); tool-level nesting
arrives when pi-otel spans are actually collected (schema doc §3, not yet wired).

## Run by hand

```bash
node .claude/skills/loop-station/parse-transcripts.mjs --out trace.jsonl   # latest session
node .claude/skills/loop-station/parse-transcripts.mjs <session> --json     # inspect
node .claude/skills/loop-station/collect-traces.mjs --out loop-station-trace.jsonl
```

## In CI

Every Claude-agent workflow ships its trace via the composite action
**`./.github/actions/loop-station-trace`** (one `uses:` step after the agent,
`if: always()`, tolerant — never fails the job it rides along with). Each
ephemeral runner ships its trace tagged by `run_id`; the weekly WS-A rollup
(below) aggregates the artifacts. The pi workflows are excluded — their trace
comes from pi telemetry (#944), not Claude transcripts.

---

# WS-A — cross-run usage rollup (#1064)

Turns the per-run trace artifacts (which otherwise expire unread) into a
**committed, trendable usage fact**: per-skill/agent invocation counts across
all CI runs in a window. This is the usage side of the dead-weight join — the
observatory panel (#1065) and the advisor's dead-weight rule (#1066) read it.

> Privacy line: raw traces stay gitignored/artifact-only (WS2 rule). What gets
> committed is the **aggregate** — names + counts, the same granularity
> `history.jsonl` commits for size. The record carries `source: 'ci-rollup'`
> and `scope: 'pipeline'` so a consumer can't over-read it: **0 means "never
> fired in CI"**, not "never used" (interactive coverage is #1067).

## Files

| file | role |
|------|------|
| `aggregate-usage.mjs` | N trace NDJSON files → ONE usage record (counts per name, runs, workflows, first/lastSeen). Importable `aggregate(texts, opts)`. Zero input ⇒ a visible meta-only record, never silence |
| `append-usage.mjs` | append the record to `writeups/loop-station/usage.jsonl`, **idempotent per ISO week** (exports `isoWeek()`) |
| `lib/aggregate-usage.test.mjs` | counting, attribution, meta-only-on-empty, scope fields, week-key idempotence |
| `writeups/loop-station/usage.jsonl` | the **committed** usage trend (one record per week) |

## Run by hand

```bash
node .claude/skills/loop-station/aggregate-usage.mjs --window 7 trace1.jsonl trace2.jsonl \
  | node .claude/skills/loop-station/append-usage.mjs
```

## In CI

`.github/workflows/loop-station-usage.yml` runs **weekly** (Mondays 06:41 UTC,
before the advisor): list the window's `loop-station-trace-*` artifacts →
download → aggregate → append → commit back `[skip ci]`. Fail-loud contract
(#1037): a listing failure **fails the job** (an outage must not read as a
quiet week); zero artifacts appends a meta-only record (a gap you can see).

---

# WS5 — advisor (state → actionable ticket)

Turns the observatory from numbers-to-look-at into decisions. An agent reviews the
inventory state and, **only when something's actionable**, files a single GitHub
issue assigned to the maintainer with concrete recommendations.

> Two layers, kept separate: deterministic **numbers → the WS4 digest** (no LLM);
> qualitative **remarks → a ticket** (this). The LLM never touches the trend and
> only *recommends* — epic #926's observatory-not-builder boundary holds.

## Files

| file | role |
|------|------|
| `advisor.mjs` | **deterministic gate** — reads `history.jsonl` + `usage.jsonl`, surfaces candidate findings (scorecard reds, sharp always-on growth, redundancy jumps, dead-weight skills), decides `actionable`. No LLM, no issue. Importable `analyze()` / `usageCoverage()`. |
| `lib/advisor.test.mjs` | findings logic (reds flag, growth flags, cross-tokenizer deltas are NOT compared, dead-weight coverage gates, deterministic) |
| `.github/workflows/loop-station-advisor.yml` | weekly: run the gate → **only if actionable** invoke `claude-code-action` to open/update ONE `loop-station-advisor` issue assigned to `pmcp` |

### Dead-weight rule (#1066)

Joins WS1 size × WS-A usage: an **oversized skill** (≥ the scorecard's artifact
amber band) that **provably never fired** becomes one `dead-weight` finding
listing the candidates. "Provably" is gated — the rule judges ONLY when the
usage rollup covers **≥ 60 cumulative days AND ≥ 5 observed runs** (tune in
`ADVISOR`); anything thinner stays silent with `usage.why` naming the reason,
because "no data" must never read as "dead". Evidence carries
`scope: 'pipeline'` — 0 means *never fired in CI*, not never used (#1067).

## Why the gate is deterministic

The cheap deterministic pass decides *whether to bother the human* (and whether to
spend an LLM call) — so the model runs only on a real signal, never on a quiet
week, and the trend numbers stay LLM-free. Deltas are compared **only within the
same tokenizer** (a heuristic→anthropic switch isn't real growth).

## Run by hand

```bash
node .claude/skills/loop-station/advisor.mjs --pretty   # see findings + actionable verdict
```

---

# WS6 — accountability scoreboard (#1570)

The reviewer-vs-author lens: every **confirmed defect** is a severity-weighted,
zero-sum-ish transaction — **−w to the author flow, +w to the gate that caught it**
— and a clean merge earns its author +1. Turns review gates and coding agents into
opposing teams so we can see who ships clean and which gates actually earn their keep.

> Boundary holds: a new *lens* over existing data, not a new pipeline. It joins the
> **findings** ledger (`writeups/loop-station/findings.jsonl`) × the run-outcome
> **eval-ledger** (`writeups/reports/eval-ledger.jsonl`, #883). The tally is
> deterministic arithmetic — no LLM. Severity comes from the gate's own rating.

## The asymmetric model (decided on #1570)

| Event | Author | Catcher | Gate that missed it |
|---|--:|--:|--:|
| Defect caught in review | −w | +w | — |
| Clean merge, stays clean | +1 | 0 | — |
| Defect escaped, caught later | −w·2 | +w | −w |
| Flag rejected (false-positive) | 0 | −w (noise) | — |

`w` is severity-weighted (`critical 5 / high 3 / medium 2 / low 1`). A find scores
**only once confirmed** (fix merged / reverted / `lgtm`) — unconfirmed = `pending`.

**Two lanes (`class`).** `defect` (default) = a correctness/security/a11y/convention failure —
the main board. `quality` = a preference on *correct* code (a `/simplify` cleanup) — a
**separate low-weight lane** that never touches an author's defect Net/Rate (so it can't
punish verbose-but-correct code or drown the defect signal). A quality gate captures on any
change (`capture-finding.mjs --gate simplify --class quality`), not just 🔴 critical.

## Files (live in `scripts/eval-ledger/` with the ledger they join)

| file | role |
|------|------|
| `findings-schema.mjs` | finding record shape + `validate()` + `transactionsFor()` (the scoring rules — tune weights here) + `dedupKey()` |
| `append-finding.mjs` | validate + append ONE finding to `findings.jsonl` (idempotent via `dedupKey`; `--check` / `--no-dedup`) |
| `accountability.mjs` | join findings × ledger → two leaderboards (markdown / `--json` / `--html`); importable `tally()` |
| `capture-finding.mjs` | gate-side: a `<gate>-verdict.json` → a pending finding **candidate** (only for 🔴 critical) |
| `ingest-findings.mjs` | rollup-side: resolve candidates by PR-merge fact (merged → confirmed · closed → drop · open → skip); importable `resolveCandidates()` |
| `reconcile-findings.mjs` | rollup-side: reverted eval-ledger rows → escaped-defect findings; importable `reconcile()` |
| `accountability.test.mjs` | pins the arithmetic + reconcile + candidate resolution (19 tests) |

## Capture in CI (#1570)

Two deterministic sources feed `findings.jsonl` — **no LLM, no CI state machine**:

1. **PR gates** (`frontend-review` / `a11y` / `red-team`) upload a finding **candidate**
   artifact (`loop-station-finding-*`) whenever they flag a 🔴 critical. Confirmation is
   the **durable PR-merge fact**: merged ⇒ the blocking defect was fixed ⇒ a *confirmed
   caught* defect; closed ⇒ dropped; open ⇒ left for a later run.
2. **Reverts** — a `reverted` run in the eval-ledger ⇒ a *confirmed escaped* defect.

`.github/workflows/loop-station-findings.yml` (merge-to-`main` + weekly, mirrors
`loop-station-usage.yml`) downloads the candidates, resolves them, runs `reconcile`, and
commits new findings to `main`. **Only this main-context job writes the committed ledger** —
a PR-branch gate run never does. Rendered in `pocs/loop-station` (the `AccountabilityBoard`
panel, staged by its `prepare-data.mjs`).

## Run by hand

```bash
node scripts/eval-ledger/accountability.mjs            # the two leaderboards
node scripts/eval-ledger/append-finding.mjs --gate code-review --severity high \
  --status confirmed --confirmed_via fix-merged --author_flow task-worker --author_ref <PR-url>
```

Files in this skill

  • SKILL.md15.9 KB
  • advisor.mjs9.7 KB
  • aggregate-usage.mjs5.4 KB
  • append-history.mjs2.3 KB
  • append-usage.mjs2.9 KB
  • collect-traces.mjs4.7 KB
  • gather.mjs10.6 KB
  • lib/advisor.test.mjs6 KB
  • lib/aggregate-usage.test.mjs3.3 KB
  • lib/loop-station.test.mjs2.9 KB
  • lib/parse-transcripts.test.mjs6.4 KB
  • lib/redundancy.mjs5.2 KB
  • lib/scorecard.mjs2.8 KB
  • lib/tokens.mjs3.2 KB
  • parse-transcripts.mjs11.7 KB
  • pi-telemetry.mjs9.7 KB
  • pi-telemetry.test.mjs4.7 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…