| name | get-status |
| description | Render the state of the ODD loop from the repository's committed .odd/ history and git alone - per-service loop state, findings ledger, trends, open telemetry gaps, next recommended action - read-only, no backend queries, no report written. Use when answering where the loop is, when /odd-status runs, or when a status must be computed offline from the clone. |
Get the ODD Loop Status
Answer "where is the loop?" for this repository, from its committed
memory alone. Every input is already in the clone — the stored reports,
the decisions ledger, and git — so the status costs no backend query, no
running stack, and no network: it is what the loop wrote down about
itself.
Sources — and nothing else
.odd/observe-run-reports/ and .odd/otel-instrumentation-reports/:
frontmatters first, bodies only where a step below needs their
structured tables or a verification's rulings;
- git metadata about the repository and those files: report commit
dates, and each report's
revision and tree_anchor fields against
the commits that came after it;
.odd/decisions.md, the findings decision ledger — read through the
ledger contract the record-finding-decision skill owns: that skill
is the format's authority, this one only reads what it wrote. A
missing file means no decision has been recorded yet, which is a
fact, not an error.
The caller may restrict the status to service name(s), a stack (local,
grafana, ...) and/or a deployment environment (prod, uat, ...).
When no filter is named, every stored report qualifies — no arguments is
the whole picture, not an empty scope.
Never query a backend, never start the stack, never write or edit a
report, a ledger, or any other file — this skill reads the loop, it does
not advance it. The one write in the status surface belongs to the
record-finding-decision skill. The status renders in the conversation,
as tables — never a committed artifact.
Build the status in this order
- Inventory — frontmatters only. List both directories and read
every frontmatter, no bodies yet. No
.odd/ directory or no reports
at all: say the loop has not started here, point at /odd-instrument
or /odd-observe, and stop — that IS the status, not a failure.
- Per-service loop state. One row per service (
services for
observation reports; an instrumentation report contributes to the
services its plan covers, project names its scope): last
observation (date, stack, environment, mode, workload when
present), last verification (mode: verify reports — their
verifies value names what they replayed) with its verdict from the
report body, and the chain as the files tell it:
observed -> fixed -> verified. A mode: re-measure report is an
observation event, never a verification: it replayed the protocol of
the report its verifies names without ruling on a fix — count it as
the last observation when newest, and never let it satisfy "verified"
in the chain.
"Fixed" means commits landed after the report's revision,
excluding commits that only touch .odd/ or documentation — the
loop's own memory is not a fix. Scope the commit test to the
service's path when a report names one (an instrumentation report's
project); otherwise say the count is repo-wide, not service-scoped.
A verification covers the commits its own revision has as ancestors
— but in a squash-merge workflow that revision never becomes an
ancestor of the merged history, so ancestry alone cannot prove
coverage: a commit whose squash introduced the verification report
itself is covered by that verification, and when ancestry is
otherwise inconclusive, say coverage is uncertain rather than ruling
a verification due. When a report carries a tree_anchor, that is
the preferred boundary: compare its entry hashes against
git ls-tree of the candidate commit, ignoring .odd and every
entry that cannot change the observed service's runtime behavior —
documentation is the canonical case, but so are CI configuration,
generated/packaging artifacts, and release-metadata files. Equal
hashes mean no code change, and the comparison resolves in any
clone whatever the merge strategy; when the only differing entries
are ones you cannot classify, the boundary is uncertain — say so,
never rule "code changed". When a report carries neither an anchor
nor a revision, its commit date — already a source — is the
substitute boundary.
Pre-convention reports (no field) leave the chain
"unknown (pre-convention)" — state it, never reconstruct it from
prose.
A filter that matches nothing is still a status
When the caller restricted the status — service, stack, environment —
and no stored report matches, say exactly two things and stop:
- what was searched: each filter and its value, kept distinct (a
stack scope is not an environment scope);
- what exists instead: the distinct services, stacks, and
environments present across the stored frontmatters — the inventory
of step 1, which is already read.
Example: "no report with environment prod — all 4 stored reports are
environment: local." That IS the status, not a failure: it names the
miss and hands back the values that let the caller correct the scope in
one turn.
Service names match exactly against the frontmatter services
values (and an instrumentation report's plan scope): a partial name
misses, and falls into the statement above rather than being guessed
into a match. Never render the unfiltered picture under a scope that
matched nothing, never render an empty table silently, and never error.
Degrade gracefully
Degrade gracefully everywhere: a single report, reports predating newer
frontmatter fields, a body missing a structured section — render what
exists, mark what cannot be known ("no verification yet", "chain
unknown"), and never fail the whole status over one unreadable report.
The ledger degrades the same way: a malformed row, or one naming a
report or finding ID that does not exist, is reported and skipped —
say which row and why, then render the rest of the status. A broken
ledger row is never fatal, and never silently dropped either.