Skip to content
Conduit

mcp server

lastword

Pins the verdict of a finished Claude Code session — the final answer, changed files and command outcomes — machine-checks every claim, and renders it as one pinned HTML page.

SuperMarioYL/lastwordcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 27, 2026

English | 简体中文

LastWord

The transcript overlay that pins a coding agent's final answer for developers.

Multi-hour Claude Code sessions bury the operative answer in hundreds of turns of narration. LastWord reads the finished transcript and pins the verdict — the final decision, what changed, and machine-checkable pointers to the exact turns that produced it — so a 500-turn run ends with one visible answer instead of another scroll.

CI Version Go License Local


Quickstart

Requires Go 1.24+. Everything runs locally — no API keys, no model calls, no network.

git clone https://github.com/SuperMarioYL/lastword.git
cd lastword
go build -o /tmp/lastword ./cmd/lastword

Try it on the bundled 500-turn session (a sanitized synthetic fixture shaped like a real Claude Code transcript):

/tmp/lastword verdict testdata/sessions/session-500turn.jsonl --format text
/tmp/lastword verify  testdata/sessions/session-500turn.jsonl   # machine-check it
/tmp/lastword view    testdata/sessions/session-500turn.jsonl   # pinned HTML viewer

Then run it on your own machine, where Claude Code already wrote the session for you:

/tmp/lastword latest    # the newest main session under ~/.claude/projects
/tmp/lastword verdict   # its verdict span, discovered automatically

That is the whole loop: one command in, one verdict out — checked with verify, pinned with view. These lines also live in examples/quickstart.sh.

Recorded demo

The commands and outputs below are copied verbatim from a real 0.2.0 run — full record in docs/demo-results.json. The session under test is the bundled sanitized fixture (testdata/sessions/session-500turn.jsonl, 500 counted turns), staged as a demo projects tree; on your own machine the same commands read your newest session under ~/.claude/projects through the identical code path.

The verdict panel, as printed by lastword verdict --format text:

LastWord — verdict-span/v0
session 7f3d2c8a-1e94-4b67-9c02-5a8d3f6b1e47

ANSWER
  The login flake was a stale refresh-token cache key: rotating a session
  changed the in-memory key but never invalidated the old entry, so roughly
  one in five refreshes read a token the issuer had already rejected.

CHANGES (7)
  src/auth/token.go               +6   -6
  src/auth/session.go             +5   -3
  internal/api/middleware.go      +8   -5
  tests/refresh_rotation_test.go  +13  -0
  internal/api/server.go          +1   -1
  docs/auth.md                    +5   -0
  README.md                       +5   -0

RESULTS (6)
  [ok] go test ./...
  [ok] gofmt -w .
  [ok] go vet ./...
  [FAIL (exit 2)] npm --prefix web run build
  [ok] git log --oneline -3 -- web/
  [ok] go test ./... -run Auth

POINTERS (14)
  #500  final   answer.lead
  #8    edit    answer.changes[src/auth/token.go]
  #8    edit    answer.changes[src/auth/session.go]
  #11   edit    answer.changes[internal/api/middleware.go]
  #13   edit    answer.changes[tests/refresh_rotation_test.go]
  #37   edit    answer.changes[internal/api/server.go]
  #40   edit    answer.changes[docs/auth.md]
  #40   edit    answer.changes[README.md]
  #16   result  answer.results[go test ./...]
  #39   result  answer.results[gofmt -w .]
  #44   result  answer.results[go vet ./...]
  #45   result  answer.results[npm --prefix web run build]
  #47   result  answer.results[git log --oneline -3 -- web/]
  #48   result  answer.results[go test ./... -run Auth]

checks: not evaluated — run: lastword verify

The machine check over the same span, as printed by lastword verify (exit 0, scriptable):

LastWord — verify verdict-span/v0
session 7f3d2c8a-1e94-4b67-9c02-5a8d3f6b1e47

  [pass] pointer_resolution — 14/14 pointers resolve
  [pass] answer_diff — 7 files claimed, 7 edited, 0 unclaimed

verdict: PASS

Note what the span gets right that the raw transcript hides: go test ./... failed early in the session and passed after the fix — the span claims the last outcome, the passing run at turn 16. The npm build failure is claimed as failed with its printed exit code. The same verdict as the open JSON document (head shown; the full document is in the record):

{
  "schema": "verdict-span/v0",
  "session": "7f3d2c8a-1e94-4b67-9c02-5a8d3f6b1e47",
  "answer": {
    "lead": "The login flake was a stale refresh-token cache key: rotating a session changed the in-memory key but never invalidated the old entry, so roughly one in five refreshes read a token the issuer had already rejected.",
    "changes": [
      {
        "file": "src/auth/token.go",
        "added": 6,
        "removed": 6
      },
      {
        "file": "src/auth/session.go",
        "added": 5,
        "removed": 3
      },
      {
        "file": "internal/api/middleware.go",
        "added": 8,
        "removed": 5
      },
      {
        "file": "tests/refresh_rotation_test.go",
        "added": 13,
        "removed": 0
      }
      /* … 3 more files, 6 command results, 14 turn pointers,
         "checks": null — see docs/demo-results.json */
    ]
  }
}

Why LastWord exists

You come back to a terminal after a forty-five-minute Claude Code run. Somewhere in 500 turns of tool calls, retries and narration is the thing you actually need: what did it decide, what changed, do the tests pass. The last message is usually more narration, so you scroll — and scrollback search finds mentions, not the verdict.

The ecosystem's current fixes live one layer short of the problem:

  • Behavior skills (i-have-ADHD, caveman) ask the agent to lead with the answer or talk tersely. A prompt is a request, not a contract: any given run can still bury the answer, nothing checks a finished session, and a run that ended without the skill stays unreadable.
  • Transcript renderers (claude-code-log) make the scrolling nicer. But the answer is not distinguishable from narration at the format level, and their session summaries are generated re-descriptions — nothing resolves to specific turns, nothing is checkable.

LastWord replaces the request with a derived contract. Extraction is deterministic — it never calls a model — and two elements make the result a primitive rather than another summary:

  • The pointer: every claim in the answer carries a resolvable, snippet-anchored reference to the specific turn that produced it. answer.changes[src/auth/token.go] is not a vibe, it is turn 8, with a verbatim quote.
  • The check semantics: the spec defines what makes a span valid — every pointer must resolve to a real turn, and the claimed diff must equal the transcript's actual edit events. lastword verify enforces exactly that, with a pass/fail exit code.

How it works

One Go binary, one process, no daemon, no server, no network calls.

~/.claude/projects/<project-dir>/<session-uuid>.jsonl
   (main sessions, one level deep; nested subagent transcripts excluded)
      |
      v
 transcript/claude.go    JSONL -> turns {uuid, role, text, toolUses, toolResults}
                        (sidechain entries and bookkeeping types skipped;
                         a torn trailing write is skipped, mid-file
                         corruption still fails the load)
 transcript/discover.go  newest main-session lookup
      |
      v
 verdictspan/extract.go  deterministic rules -> VerdictSpan
        answer.lead = leading paragraph of the last assistant turn
        changes     = aggregated Edit/Write tool events
        results     = last outcome per command: ok mirrors is_error,
                      exit code parsed from "Exit code N" text when present
 verdictspan/verify.go   pointer_resolution + answer_diff -> checks[]
 verdictspan/schema.go  verdict-span/v0 types + open-format reference
      |
      +---> render/terminal.go   `lastword verdict`  stdout — canonical JSON, or a
      |                          readable panel with --format text; `lastword
      |                          verify` prints the check report with exit 0/1
      +---> render/viewer.go     `lastword view`     single-file HTML (embedded template)

The extraction pipeline behind every span:

The verdict-span format

The span is a published open format — docs/verdict-span-v0.md — so other tools can emit and consume it. It is agent-generic by design; this CLI reads Claude Code session JSONL only.

{
  "schema": "verdict-span/v0",
  "session": "<session uuid>",
  "answer": {
    "lead": "leading paragraph of the final assistant turn",
    "changes": [{"file": "src/auth.go", "added": 12, "removed": 4}],
    "results": [{"kind": "command", "cmd": "go test ./...", "ok": true, "exit_code": null}]
  },
  "pointers": [
    {"turn": 412, "uuid": "<message uuid>", "kind": "final | edit | result",
     "snippet": "verbatim 1-2 lines from that turn",
     "provenance": "which claim this turn backs"}
  ],
  "checks": [
    {"name": "pointer_resolution", "pass": true},
    {"name": "answer_diff", "pass": true, "detail": "3 files claimed, 3 edited, 0 unclaimed"}
  ]
}

Result entries are derived, not read: Claude Code tool_result blocks carry no structured exit code, so cmd comes from the paired tool_use (matched via tool_use_id), ok mirrors is_error, and exit_code is parsed from Exit code N free text only when a failure prints one. The spec also fixes the deterministic rules — what counts as a turn, how lines are counted, which outcome a repeated command claims — so independent implementations produce the same span.

Command line

CommandWhat it does
lastword verdict [file]Extract and print the verdict span; with no file, the newest main session is discovered
lastword verify [file]Run the pointer-resolution and answer-diff checks over the span; exit 0/1
lastword view [file]Write the single-file HTML viewer (pinned verdict, click-to-jump provenance)
lastword latestPrint the path of the newest main-session transcript
OptionApplies toDefaultDescription
--formatverdict, verifyjson / textjson — the canonical verdict-span/v0 document (verify fills checks); text — a readable panel / the check report
--projects-dirall discovery~/.claude/projectsClaude Code projects root, used when no file is given
--outviewlastword.htmlPath of the written HTML viewer
--openviewoffOpen the viewer in a browser after writing it (best-effort)

Main sessions are the *.jsonl files directly inside a project directory; anything nested deeper (subagent transcripts) is excluded. When discovery runs, the resolved path is printed to stderr so stdout stays machine-readable.

Capabilities and integration

ComponentCurrent support
InputFinished Claude Code session JSONL (the files under ~/.claude/projects), read post-hoc
Change claimsEdit and Write tool events, aggregated per file with +/− line counts
Result claimsBash use/result pairs matched by tool_use_id; last outcome per distinct command
PointersTurn number + message uuid + verbatim snippet for the lead, every file, every command
Checkspointer_resolution and answer_diff via lastword verify, with a pass/fail exit code
ViewerSingle-file HTML (lastword view): verdict pinned, click-to-jump provenance, no CDN
Outputverdict-span/v0 JSON (default) and a terminal panel (--format text)
FixturesSanitized synthetic sessions in testdata/sessions/, including a 500-turn run

Configuration and limits

  • v0.2 reads finished Claude Code sessions only. No Codex or Antigravity transcripts, no live-run hooks during a session, no cross-session search or indexing.
  • Checks are evaluated by lastword verify. Extraction alone emits "checks": null — an unevaluated span never asserts passes; verify fills pointer_resolution and answer_diff and exits 0/1. A snippet cut at the 120-character cap carries an ellipsis, and verify matches it at a line boundary of the turn.
  • The viewer is one HTML file. lastword view embeds everything — inline CSS, no CDN, no external fetches, no JavaScript build step — and opens with the verdict pinned over the full transcript.
  • Deterministic by design. No LLM-generated summaries, no interpretation — a span claims only what the transcript objectively contains. A Write event counts added lines and claims nothing removed (the previous content is not in the transcript).
  • No config file, no telemetry, no model calls. CLI flags only; everything runs locally.

Roadmap

  • v0.1 (m1) — turn parsing, newest-session discovery, deterministic verdict-span extraction (lead, changes, results, pointers), JSON + terminal output, open-format spec, 500-turn fixture
  • v0.2 (m2 + m3) — lastword verify: pointer-resolution and answer-diff machine checks with a pass/fail exit code; lastword view: the self-contained HTML viewer with the verdict pinned and click-to-jump provenance — plus two extraction fixes (rune-safe snippet truncation, torn-final-line tolerance)

Not in yet and not rushed: live-run enforcement, Codex/Antigravity transcript support, LLM-generated summaries, an MCP server exposing spans, IDE extensions, telemetry. The format and the CLI ship first; the substrate (open JSONL transcripts) already exists.

Development and license

go build ./... && go vet ./...
go test ./...

The suite covers JSONL parsing (block shapes, sidechain skipping, tool pairing), discovery, every extraction rule and the fixtures. Presentation artwork is regenerated with python3 docs/render_presentation.py --repo ..

MIT © 2026 SuperMarioYL


↑↓ navigate openesc close