# Reference graph

> Append-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions.

- Repository: superdesigndev/loopany
- GitHub: https://github.com/superdesigndev/loopany
- Human docs: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8
- Complete Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/llms-full.txt

## Source Files

- `src/core/references.ts`
- `src/commands/refs.ts`
- `src/commands/trace.ts`
- `skills/loopany-core/conventions/relations.md`
- `src/core/link-parser.ts`
- `test/references.test.ts`

---

---
title: "Reference graph"
description: "Append-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions."
---

The loopany reference graph lives at `$LOOPANY_HOME/references.jsonl` as an append-only edge log. At index build time, `ArtifactIndex` loads those rows, synthesizes `mentions` edges from frontmatter and body `[[slug]]` links, and exposes the merged graph through `loopany refs` (BFS neighborhood) and `loopany trace` (signed causal walk). Reverse adjacency is computed in memory; only forward rows are persisted.

## Storage and index model

```text
$LOOPANY_HOME/
  references.jsonl     # explicit edges only (append-only)
  artifacts/...        # implicit mention sources (frontmatter + body)

Index build (each CLI command that calls engine.index()):
  1. ReferenceGraph.load() → forward + reverse maps from JSONL
  2. For each artifact:
       - frontmatter mentions: [...]  → implicit mentions edge
       - body [[slug]] via extractLinks → implicit mentions edge
  3. ArtifactIndex.refsOut / refsIn query the merged maps
```

<Note>
`relation` is an open registry in code — any string is accepted on `refs add`. Searchability depends on using the six convention verbs documented in `skills/loopany-core/conventions/relations.md`; synonyms fragment `refs` and `trace` results.
</Note>

### Explicit edge row shape

Each `refs add` or runtime append writes one JSON object per line:

| Field | Type | Meaning |
|-------|------|---------|
| `ts` | ISO-8601 string | Set automatically on append |
| `from` | artifact id (slug) | Source node |
| `to` | artifact id (slug) | Target node |
| `relation` | string | Verb (convention: see table below) |
| `actor` | string | Who wrote the edge (`cli`, `agent`, …) |

`ReferenceGraph.load()` skips blank lines and malformed JSON; rows missing `from`, `to`, or `relation` are ignored. Reverse edges are **not** stored — `ArtifactIndex` mirrors each row into both `forwardRefs` and `reverseRefs`.

### Implicit `mentions` edges

Implicit edges are **not** written to `references.jsonl`. They are rebuilt on every index from artifact content:

| Source | `actor` | `relation` | `ts` |
|--------|---------|------------|------|
| `mentions: [id, …]` in frontmatter | `frontmatter` | `mentions` | source file mtime |
| `[[slug]]` in body (outside code) | `body` | `mentions` | source file mtime |

All implicit edges carry `"implicit": true`. Editing frontmatter or body updates the graph on the next index without a `refs add` call. Query APIs return implicit and explicit edges together; filter by `implicit` or `actor` in JSON output when you need to distinguish them.

## Canonical relation verbs

Direction is always **A → verb → B** (active voice from `from`). To traverse the inverse, use `loopany refs <B> --direction in --relation <verb>` instead of writing a second edge.

| Verb | `from` → `to` reads as | Use when |
|------|------------------------|----------|
| `led-to` | cause → effect | Downstream artifact produced from upstream (default causal link) |
| `addresses` | action → concern | Task/signal pattern: handler resolves the observation |
| `mentions` | source → entity | Soft reference (frontmatter, `[[id]]`, or explicit add) |
| `supersedes` | new → old | Replacement artifact after mission/architecture shift |
| `follows-up` | continuation → original | Thread picked up later (often paired with `checkAt` tasks) |
| `cites` | summary → evidence | Brief or learning drawing on source artifacts |

<Warning>
Do not store both directions for the same fact (for example `led-to` plus `caused-by`). `trace` defaults exclude `caused-by`; use `refs --direction in` on the effect node instead.
</Warning>

### `addresses` via signal status

Closing a signal with responsibility emits a persisted `addresses` edge in one step:

```bash
loopany artifact status <signal-slug> addressed --addressed-by <task-slug>
```

The edge is `from: <task-slug>, to: <signal-slug>, relation: addresses` (action handles observation). `--addressed-by` is required for `addressed` and rejected for other statuses.

## Wiki-link `[[id]]` conventions

Body wiki links are parsed by `extractLinks()`:

- Pattern: `[[<slug>]]` where `<slug>` matches `^[\p{L}\p{M}\p{N}\-_]+$` (Unicode letters, marks, numbers, hyphen, underscore).
- No internal whitespace; characters like `.`, `/`, `:` are rejected.
- Fenced code blocks and inline `` `...` `` spans are stripped before matching so examples do not create edges.
- Every resolved link becomes an implicit `mentions` edge; **`[[id]]` cannot express `led-to`, `addresses`, or other verbs** — use `loopany refs add` for those.

Validation is deferred to index time: slug-shaped links to missing artifacts still appear as edges whose `to` id is unknown. `loopany doctor` reports them as dangling (`from → to (relation)`).

<Info>
Skill cross-references use `[[path/to/SKILL.md]]` in skill bodies. Those paths are **not** ingested into `references.jsonl` or the artifact graph; agents follow them manually. Artifact wiki links always target artifact slugs/ids, not skill file paths.
</Info>

## CLI: `refs`

### Add an explicit edge

```bash
loopany refs add --from <id> --to <id> --relation <verb>
```

<ResponseExample>

```json
{
  "ts": "2026-06-05T12:00:00.000Z",
  "from": "morning-signal",
  "to": "fix-cache-task",
  "relation": "led-to",
  "actor": "cli"
}
```

</ResponseExample>

`refs add` is recorded in `audit.jsonl` as `refs.add`.

### Query neighborhood (BFS)

```bash
loopany refs <id> [--direction in|out|both] [--relation <verb>] [--depth <N>] [--domain <name>]
```

<ParamField body="direction" type="string" default="out">
`out` — edges where `<id>` is `from`. `in` — edges where `<id>` is `to`. `both` — union of outgoing and incoming at each hop.
</ParamField>

<ParamField body="depth" type="positive integer" default="1">
Maximum hop count. Depth 1 is one-hop neighborhood; larger values walk chains but dedupe edges by `from|to|relation|ts`.
</ParamField>

<ParamField body="domain" type="string">
When set, keeps only edges where **both** endpoints have `frontmatter.domain` equal to the flag value.
</ParamField>

Returns a JSON array of edge objects (implicit and explicit). Visited-node deduplication prevents re-expanding a node, but all unique edges encountered within the depth budget are included.

## CLI: `trace`

`trace` walks **lineage predicates** to a fixed point, separate from the shallow `refs` neighborhood:

```bash
loopany trace <id> [--direction forward|backward|both] [--relations <csv>] [--max-depth <N>]
```

Default `--relations`: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites`. **`mentions` is excluded** (soft pointer, not causal lineage). Override with `--relations mentions` when you need mention chains.

<ResponseExample>

```json
{
  "root": "fix-cache-task",
  "nodes": [
    { "id": "morning-signal", "kind": "signal", "distance": -1, "...": "..." },
    { "id": "fix-cache-task", "kind": "task", "distance": 0, "...": "..." },
    { "id": "cache-learning", "kind": "learning", "distance": 1, "...": "..." }
  ],
  "edges": [ "...Edge objects..." ]
}
```

</ResponseExample>

| Field | Meaning |
|-------|---------|
| `distance` | Signed hop count from root: negative = backward (causes), `0` = root, positive = forward (effects) |
| `nodes` | Sorted by `distance`, then `id`; dangling edge targets omitted |
| `edges` | All edges traversed, deduped by `from|to|relation|ts` |

`--direction forward` follows `refsOut`; `backward` follows `refsIn`. Default `both` walks both sides. Unknown root id fails with `No artifact with id: <id>`. Cycles terminate without duplicating nodes (first-seen distance wins).

## Explicit vs implicit: when to use which

```mermaid
flowchart LR
  subgraph persist["references.jsonl"]
    R1["refs add"]
    R2["artifact status --addressed-by"]
  end
  subgraph artifact["Artifact files"]
    F1["mentions: [...]"]
    F2["body [[slug]]"]
  end
  subgraph index["ArtifactIndex.build"]
    M["Merged forward/reverse maps"]
  end
  R1 --> persist
  R2 --> persist
  F1 --> index
  F2 --> index
  persist --> index
  index --> M
  M --> Q1["loopany refs"]
  M --> Q2["loopany trace"]
```

| Mechanism | Persisted | Best for |
|-----------|-----------|----------|
| `refs add --relation <verb>` | Yes | `led-to`, `addresses`, `supersedes`, `follows-up`, `cites` |
| `mentions:` frontmatter | No (implicit) | Structural attribution (mission, stakeholders) |
| `[[slug]]` in body | No (implicit) | In-prose name-drops |
| `artifact status … addressed --addressed-by` | Yes (`addresses`) | Signal-close workflow |

Explicit and implicit edges can coexist on the same pair with different relations (for example implicit `mentions` plus explicit `led-to` from the same task).

## Integrity and operations

`loopany doctor` checks reference integrity: every edge’s `to` (and orphan `from`) must resolve to an existing artifact in the index. Unresolved `[[typo-slug]]` targets count as dangling. Mission coverage warns when tasks lack a mission in `mentions`, but that is separate from the references check.

v0.1 → v0.2 migrations can rewrite `references.jsonl` ids to slug form (`skills/migrations/v0.1.0-to-v0.2.0/scripts/03-rebuild-references.ts` backs up the prior file). After migration, rebuild the index by running any command that loads the workspace.

The read-only **factory** UI (`loopany factory`) visualizes the same merged graph, rendering implicit edges with lower opacity than explicit `references.jsonl` rows.

## Anti-patterns

- **Duplicate direction edges** — store one canonical direction; query the inverse with `--direction in`.
- **Synonym verbs** (`triggered`, `resolves`, `references`, …) — use the six canonical verbs or extend `relations.md` deliberately.
- **`led-to` for everything** — prefer `addresses` for responsibility, `mentions` for referential links.
- **`[[id]]` for causal links** — always `refs add` with the intended verb.

## Related pages

<CardGroup>
  <Card title="Artifacts and workspace" href="/artifacts-and-workspace">
    On-disk layout for artifacts, references.jsonl, audit.jsonl, and v0.2 slug-as-id rules.
  </Card>
  <Card title="Graph, search, and scheduling" href="/graph-search-commands">
    Full refs/trace/followups command flags, factory UI, and audit side effects.
  </Card>
  <Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
    End-to-end signal → task with led-to/addresses edges and status transitions.
  </Card>
  <Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
    Dangling-edge detection, reference integrity checks, and recovery steps.
  </Card>
</CardGroup>
