Free

Evidence Board

Your project's notes as an offline corkboard: every claim pinned to its source line, anything unsourced stamped unverified.

Install

Install the "Evidence Board" skill into ~/.claude/skills/evidence-board.
Download each file and save it under that folder:
https://skilllift.pages.dev/packs/evidence-board/SKILL.md → SKILL.md
https://skilllift.pages.dev/packs/evidence-board/references/style.md → references/style.md
https://skilllift.pages.dev/packs/evidence-board/assets/template.html → assets/template.html
https://skilllift.pages.dev/packs/evidence-board/assets/strings.json → assets/strings.json
https://skilllift.pages.dev/packs/evidence-board/scripts/lib.mjs → scripts/lib.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/index.mjs → scripts/index.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/verify.mjs → scripts/verify.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/hints.mjs → scripts/hints.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/layout.mjs → scripts/layout.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/build.mjs → scripts/build.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/check.mjs → scripts/check.mjs
https://skilllift.pages.dev/packs/evidence-board/scripts/drive.mjs → scripts/drive.mjs
Then read SKILL.md, tell me what it does, and remind me to start a new session.

Unpack it into ~/.claude/skills

Same skill, different brief

The left is the preview. The right was made by a separate agent from a different brief, using only the installed skill and without seeing the preview.

The preview
The preview
A new brief, made by a separate agent
A new brief, made by a separate agent

One test run on a fictional brief. Letterforms can differ on systems without the same fonts.

Turns a project's notes into an offline knowledge graph in one HTML file, drawn as a detective's corkboard. Reads a folder of Markdown, JSON or CSV records, or a directory listing: the agent reads the notes and writes every claim with its source line, and a script verifies each citation against the file. The page has search, type filters, a case-file inspector with source lines, dated lifecycle state, neighbours and backlinks, path tracing, at-a-glance answers to who owns what, what blocks what and what is still unknown, and a visible graph-size policy. Anything without a source is stamped unverified. Also delivers the dataset as plain JSON. Use when someone wants to see how a project's people, tasks, decisions and risks connect, and where each claim came from.

What it must pass before delivery

  • I read the whole index and wrote hand.json myself; reviewed says truthfully what I read.
  • I reread the lines verify.mjs listed as uncited.
  • Owners, Blockers and Unknowns each answer their question from the page.
  • Later notes that change a state are added as new states, and finished blockers are marked resolved.
  • node scripts/check.mjs exits 0 with the render checks run, or I said they did not.
  • node scripts/drive.mjs exits 0.
  • I looked at the screenshots for board, inspector, Owners, Blockers, Unknowns, trace, path and policy at 1280 and 390.
  • Every fact is stated on its cited lines; anything weaker is marked inferred with a note, or unverified.
  • Nothing was invented. Fictional content carries the notice.
  • The page's interface, title and notice are in one language.
  • graph.json sits beside index.html.
Read the full SKILL.md
---
name: evidence-board
description: Turns a project's notes into an offline knowledge graph in one HTML file, drawn as a detective's corkboard. Reads a folder of Markdown, JSON or CSV records, or a directory listing: the agent reads the notes and writes every claim with its source line, and a script verifies each citation against the file. The page has search, type filters, a case-file inspector with source lines, dated lifecycle state, neighbours and backlinks, path tracing, at-a-glance answers to who owns what, what blocks what and what is still unknown, and a visible graph-size policy. Anything without a source is stamped unverified. Also delivers the dataset as plain JSON. Use when someone wants to see how a project's people, tasks, decisions and risks connect, and where each claim came from.
version: 1.0.0
---

# Evidence Board

**You are the extractor.** You read the notes and write every entity, relationship and state, each with its citation. A script cannot do this: on a small real project the pattern matcher found 4 of 22 entities and 2 of 26 relations, and missed the two obvious owners. The look and behaviour live in `assets/template.html`; layout comes from the data, so never position anything by hand. Read `references/style.md` for the tokens and rules.

What each step does, honestly:

| Step | Does | Does not |
|---|---|---|
| `index.mjs` | Prints every line and CSV row with its citation | Decide anything |
| You | Read it and write `hand.json`: every claim | |
| `verify.mjs` | Checks each citation is a real line that supports the claim as marked; lists the lines where each name appears; reports lines nobody cited | Add claims, or know whether your reading is right |
| `hints.mjs` (optional) | Guesses names and links by pattern, to compare with your reading | Reach the board. `build.mjs` refuses its output |
| `build.mjs` | Lays out and draws the board | Change claims |
| `check.mjs`, `drive.mjs` | Test what a viewer sees and does | Judge whether the claims are complete |

## Inputs to ask for

Ask once, in one message, for whatever the brief does not give you.

Required:
1. The notes: a folder path (Markdown, `.json`, `.csv`), or a directory listing saved as a `.txt` file (`find`, `ls -R` or `tree` output).
2. The project's name.

Optional:
3. One line on what the notes are.
4. Page language, English or Chinese. Default: the language most of the notes are written in.
5. Whether the content is fictional or a sample.

## Steps

Commands assume you are in the skill folder; put `notes.index.txt`, `hand.json` and `work.json` in a working folder outside the skill, never inside it. Each script takes a second or so, check a few seconds, drive up to a minute. Writing `hand.json` is the real work and takes as long as reading the notes.

1. Index the evidence:
   `node scripts/index.mjs <notes> --out notes.index.txt`
2. **Read all of it, and write `hand.json`** (below): every person, organisation, task, decision and risk that matters, who owns what, what blocks what, states, and what the notes say is not known. Cite the line for each.
3. Optional, after your first draft: `node scripts/hints.mjs <notes> --out hints.json`, then look through it for anything you missed. Expect it to be wrong often. For a long directory listing or large records file, `--draft hand.draft.json` writes its guesses in `hand.json` form for you to correct, since that structure is mechanical. Read it line by line before you adopt any of it.
4. Verify:
   `node scripts/verify.mjs <notes> --hand hand.json --out work.json`
   It reports every problem at once and writes nothing until all pass. Fix the citation or weaken the claim; do not delete a claim to quiet it unless it really has no support. It then lists uncited lines: **read those again**, and add what you missed.
5. Build:
   `node scripts/build.mjs work.json --out <folder>/index.html --root <notes> --title "..." [--subtitle "..."] [--notice "..."] [--fictional] [--lang en|zh]`
   This writes `index.html` and `graph.json` side by side.
6. Check, and fix everything it reports:
   `node scripts/check.mjs <folder>/index.html`
   Pass `--root <notes>` if the page has moved away from its notes. It fails when labels are under 11 px at Fit on 1280x800, cards overlap, a label is covered, any title, notice, subtitle, button or chip is clipped, a string runs under an unrelated pin or behind an unrelated card, a claim with no source is shown as fact, a state is stale against its own evidence, a cited line does not match the real file, or a phone touch target is under 44 px.
7. Drive every interaction and look:
   `node scripts/drive.mjs <folder>/index.html --shots <temp folder>`
   This runs at 1280 and 390, motion on and reduced, with touch emulated. **Open and look at the screenshots: board, inspector, Owners, Blockers, Unknowns, trace, path and policy, at both sizes.** Passing scripts do not replace looking.

## Fields you fill, with limits and what to do on a thin brief

| Field | Meaning | Limit | Thin brief |
|---|---|---|---|
| `--title` | The project's own name, in the page's language | 60 characters; wraps, never clips | Use the name in the brief. If none, take it from the notes' own words, and list it as an assumption. Use a folder name only if it reads as a name in the page's language; the checker refuses names like `repro-notes`. |
| `--subtitle` | What these notes are, in the user's words | 120 characters | Leave it out. Never invent a description. |
| `--notice` | Says the content is fictional or a sample | 80 characters, wraps | Leave it out unless fictional. With `--fictional` it is required and must contain "fictional", "invented" or "虚构". |
| `reviewed` | What you actually read and checked | 120 characters, at least 12 | Say what you read. If you truly could not read the notes, write exactly `Not reviewed: first machine read, claims may be wrong` or `未复核:机器首次读取,可能有错`. These pass, and the page then shows a warning banner. Never claim a review you did not do. |
| `note` | Your reading: why this line supports a claim it does not state outright | 120 characters. Required for an inferred state, or a claim whose line does not name it | Say what the line says and what you concluded. No note you can write means no claim. |
| `dates` | A scheduled date on the item it belongs to | `value` 16 characters of free text, `label` 8 | See below. Leave out if the notes give none. |
| `date` | When a state or resolution became true | `YYYY`, `YYYY-MM` or `YYYY-MM-DD` | Leave it out if the notes give none. List states oldest first: order then follows your list, and the page says so. |
| `unverified_reason` | Why a claim has no source | 120 characters, required when there is no `source` | State where it came from, for example `Said in conversation; no note records it.` If you do not know where it came from, leave the claim out. |
| `label` | Words on the string, when `kind` is `related_to` | 24 characters, in the page's language | Leave it out; the default word is used. Avoid repeating the end names. |

### Hand file

This is your reading of the notes. Only what is here reaches the board.

```json
{
  "reviewed": "Read all 8 notes line by line and wrote these claims.",
  "aliases": { "Heritage Fund": "Maritime Heritage Fund" },
  "entities": [
    { "name": "Concert pages", "type": "task", "source": { "file": "design.md", "line": 2 },
      "states": [
        { "state": "blocked", "date": "2026-03", "source": { "file": "design.md", "line": 2 } },
        { "state": "active", "date": "2026-04", "source": { "file": "april.md", "line": 1 },
          "note": "Blocked until photos arrive, and this line says they were delivered." }
      ] },
    { "name": "Domain login", "type": "risk", "source": { "file": "risks.md", "line": 2 },
      "states": [ { "state": "unknown", "source": { "file": "risks.md", "line": 2 } } ] },
    { "name": "Something only said aloud", "type": "org", "unverified_reason": "Said in conversation; no note records it." }
  ],
  "relations": [
    { "from": "Maya", "to": "Website rebuild", "kind": "owns", "basis": "fact", "source": { "file": "kickoff.md", "line": 2 } },
    { "from": "A", "to": "C", "kind": "owns", "basis": "fact", "source": { "file": "notes.md", "line": 14 }, "context": 12 },
    { "from": "Photos", "to": "Concert pages", "kind": "blocks", "basis": "inferred", "source": { "file": "design.md", "line": 2 },
      "resolved": { "source": { "file": "april.md", "line": 1 }, "note": "Photos delivered.", "date": "2026-04" } },
    { "from": "A", "to": "E", "kind": "related_to", "label": "must approve", "unverified_reason": "Said in conversation; no note records it." }
  ]
}
```

Types: `person org place asset task event decision risk document other`. Kinds: `depends_on blocks owns approved rejected replaces supplies funds inspected reviewed reported located_at part_of contains works_for affects operates related_to`. `owns` feeds Owners; `blocks` and `depends_on` feed Blockers, so use those kinds rather than a label when that is what the notes mean.

States: `proposed active blocked done rejected superseded unknown`.
- The last state wins, so **later evidence supersedes earlier**: add a new entry, do not edit the old one. Both stay visible with their citations. With dates on every entry they sort by date; otherwise they keep your order.
- **`unknown` means the notes raise a question and do not answer it.** Cite the line that raises it. Leave `states` out when the notes simply do not say; that shows as no state.
- An open question gets its own entity with state `unknown` citing that line. Do not invent a placeholder person; if you add a card for a person nobody names, it must be unverified with a reason.
- State is lifecycle only. "Scheduled for 12 October" is not `active`; leave the state out and use `dates`.
- **State words that count as stated:** proposed: proposed, draft, planned, todo, pending, will, next, `[ ]`, 待定, 计划, 未开始, 还没开始, 待办, 提议, 草案, 待确认. active: active, in progress, ongoing, underway, started, resumed, handling, open, 进行中, 在做, 正在, 推进中, 施工中, 开始了. blocked: blocked, on hold, paused, stalled, stuck, delayed, waiting, moved, 受阻, 卡住, 卡着, 卡在, 暂停, 搁置, 停滞, 延期, 阻塞, 等待. done: done, complete, completed, closed, finished, resolved, approved, delivered, shipped, picked, `[x]`, 已完成, 完成, 修好, 好了, 到货, 搞定, 做完, 结束, 已解决, 关闭, 已交付, 已上线, 通过. rejected: rejected, cancelled, dropped, abandoned, declined, 否决, 驳回, 取消, 放弃, 砍掉, 不做了. superseded: superseded, retired, obsolete, replaced, 作废, 废弃, 被取代. unknown: unknown, not sure, unsure, unclear, nobody knows, tbc, tbd, `?`, 不清楚, 不知道, 未知, 没人知道, 不确定, 待查. Anything else is your reading: add a `note`.
- **Types with no obvious home:** a project is `task`, money is `asset`, a meeting or dated occasion is `event`, an open question is `risk`.
- When a blocker is over, add `resolved` to the relation. It moves to "No longer blocking" and its string goes pale.

### A date that changes

Do not make one card per date. Put `dates` on the item, oldest first. The last one is current and shows on its card; older ones stay in the case file, marked replaced, each with its citation. `value` is free text as the notes write it, so a date with no year is fine. If the line does not contain the value, add a `note`.

```json
{ "name": "Autumn sowing", "type": "task", "source": { "file": "schedule.csv", "line": 3 },
  "dates": [
    { "value": "2026-10-05", "source": { "file": "schedule.csv", "line": 3 } },
    { "value": "12 October", "source": { "file": "october.md", "line": 1 } }
  ] }
```

`date` inside `states` and `resolved` is different: it says when that became true, in `YYYY`, `YYYY-MM` or `YYYY-MM-DD`, and it only orders evidence. With no year in the notes, leave it out and list entries oldest first.

### Choosing fact, inferred or unverified

- **fact**: someone reading only the cited line, plus at most one `context` line (usually the heading above it), would state this claim without adding anything. A relation must name both ends there. A state must name the entity and use a word for that state (`blocked`, `delivered`, `not sure`, `[x]` and so on).
- **inferred**: the line supports the claim, but a reader has to connect something: an elided subject, a pronoun, an implied cause or a sequence of events. A state needs a `note`. A relation needs a `note` if the line names neither end.
- **unverified**: no line supports it. It needs `unverified_reason`, and it is drawn with a hatched card or dotted string and a warning.
- Marking something weaker does not hide it. Path tracing goes by fewest links and lists other routes, whatever their basis, each labelled.
- Unsure between two? Pick the weaker one. A correct claim marked inferred costs little; a wrong one marked fact misleads. `verify.mjs` catches a fact whose line lacks the words; it cannot catch a wrong reading, so your care is the only guard there.

## Why hints are only hints

`hints.mjs` was measured on two folders of deliberately messy Chinese notes, written by a separate agent that never saw it, against that agent's answer key. Folder `library` was used for tuning; `tea` was run once after freezing it, and the bold numbers are that run. On a further small English project, a fresh agent's key gave 4 of 22 entities and 2 of 26 relations. Scoring is strict: a real thing the key did not list counts as wrong.

| Scored against the key | `library`, first blind run | `library`, shipped | `tea`, frozen blind run | `tea`, shipped |
|---|---|---|---|---|
| Entity precision | 0.22 | 0.50 | **0.42** | 0.46 |
| Entity recall | 0.54 | 0.88 | **0.54** | 0.54 |
| Fact relation precision | 2 of 10 | 6 of 13 | **2 of 7** | 2 of 6 |
| Relation recall | 0.14 | 0.29 | **0.08** | 0.08 |
| Cited lines that mismatch their file | 0 of 201 | 0 of 195 | **0 of 167** | 0 of 164 |

Its line references hold up; its reading does not. `example/messy-zh/` holds the notes, keys and scorer. No score exists for your accuracy as the reader: `verify.mjs` confirms citations, not judgement.

**Patterns cannot infer**, so you must: elided subjects ("Reviewed by Maya"), names with no recognisable shape, relationships across sentences or by pronoun, verbs outside its list, that two names are one thing, corrections in the text, negation and tentative statements, and whether a later note ends an earlier state.

## When things are not straightforward

- **Thin brief:** restate the user's facts, never invent, and list every inferred line in your report. The board can only say what the notes say.
- **Nobody can answer:** proceed with the rules in the table above and list your assumptions in your report.
- **Fewer or more cards than expected:** a board holds 60 cards, a fixed limit that keeps labels at 11 px or more. Past that, cards leave by rank and are listed by name in the size policy, and stay searchable. To change what is on the board, change which entities you write. Do not edit the limit.
- **No real link for a source:** sources are file paths and line numbers, not links, because the page has to work offline. Do not add URLs.
- **The notes contain secrets or personal details:** quotes are embedded in the page. Tell the user before sharing it.
- **Moved page:** the check needs `--root <notes>`. The page itself carries its quotes and works anywhere.

## Deliverables

- `index.html`: the board, one file, no external requests.
- `graph.json`: the entity-relation dataset, with sources and the size policy.
- `hand.json`: your claims, kept so the board can be rebuilt.
- In your report: counts, every inferred or unverified claim you added, what the policy left off, and whether the render checks ran.

## Before delivering

- [ ] I read the whole index and wrote `hand.json` myself; `reviewed` says truthfully what I read.
- [ ] I reread the lines `verify.mjs` listed as uncited.
- [ ] Owners, Blockers and Unknowns each answer their question from the page.
- [ ] Later notes that change a state are added as new states, and finished blockers are marked `resolved`.
- [ ] `node scripts/check.mjs` exits 0 with the render checks run, or I said they did not.
- [ ] `node scripts/drive.mjs` exits 0.
- [ ] I looked at the screenshots for board, inspector, Owners, Blockers, Unknowns, trace, path and policy at 1280 and 390.
- [ ] Every fact is stated on its cited lines; anything weaker is marked inferred with a note, or unverified.
- [ ] Nothing was invented. Fictional content carries the notice.
- [ ] The page's interface, title and notice are in one language.
- [ ] `graph.json` sits beside `index.html`.

## When not to use it

- For a polished diagram of relationships someone has already decided on; this board shows evidence, not a chosen story.
- For more than a few hundred entities, or a live database. It is a snapshot.
- For one document, where a summary serves better.
- When the user wants exact placement by hand: layout is fixed by the data.

## Requirements

- Node 18 or later. The scripts use no packages.
- For the render checks and `drive.mjs`: `npm install playwright` and `npx playwright install chromium`. If Playwright is installed elsewhere, set `NODE_PATH` to its `node_modules`. Without it, `check.mjs` exits 3. `--static` skips the render checks; if you use it, say so in your report.

Used in these workflows

Similar skills