Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
paths:
- "docs/**"
- "assets/**"
- "scripts/commit-session-link/**"
- "scripts/harness-inspector/**"
- "scripts/session-analysis/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<p align="center">
<img src="assets/logo.svg" alt="Better Harness logo" width="56" height="56">
</p>

<h1 align="center">Better Harness</h1>

<p align="center">
Expand Down
13 changes: 13 additions & 0 deletions assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ const config = {
position: "left",
label: "Docs",
},
{
to: "/inspector/",
label: "Inspector",
position: "left",
className: "navbar__link--inspector-new",
"aria-label": "Inspector",
},
{
to: "/blog",
label: "Blog",
Expand Down
120 changes: 120 additions & 0 deletions docs/i18n/zh-Hans/code.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,124 @@
{
"inspector.meta.description": {
"message": "探索 Better Harness 如何以明确的证据边界连接产品意图、Coding Agent 活动、会话、文件与提交。"
},
"inspector.hero.lead": {
"message": "从产品意图追踪到 Agent 活动、会话、文件与提交,同时让证据强度及其限制始终可见。"
},
"inspector.boundaries.label": {
"message": "Demo 边界"
},
"inspector.boundaries.interactive": {
"message": "可交互样例"
},
"inspector.boundaries.readOnly": {
"message": "只读"
},
"inspector.actions.fullscreen": {
"message": "全屏打开"
},
"inspector.demo.title": {
"message": "可交互 Harness Inspector 样例"
},
"inspector.demo.frameTitle": {
"message": "可交互 Harness Inspector 样例"
},
"inspector.demo.loading": {
"message": "正在加载可交互样例…"
},
"inspector.demo.note": {
"message": "此确定性样例使用虚构的英文数据,不会读取你的工作区、Git 历史或 Coding Agent 会话。"
},
"inspector.features.eyebrow": {
"message": "它展示什么"
},
"inspector.features.title": {
"message": "沿着交付链路追踪,同时不越过证据边界"
},
"inspector.features.intro": {
"message": "Inspector 把产品结构、Agent 活动与 Git 结果放进同一个只读工作区,并明确区分强关联与有用但有限的上下文。"
},
"inspector.features.intent.title": {
"message": "从意图到交付"
},
"inspector.features.intent.body": {
"message": "沿 Delivery Tree 从能力和 Story 意图,查看与之关联的会话、文件与提交。"
},
"inspector.features.session.title": {
"message": "把会话放回上下文"
},
"inspector.features.session.body": {
"message": "检查保留的提示词、归一化工具活动、文件路径、提交事件,以及只读会话回放。"
},
"inspector.features.evidence.title": {
"message": "可解释的证据"
},
"inspector.features.evidence.body": {
"message": "同步选择与 Evidence Drawer 会说明关联为何存在、可信度如何,以及它不能证明什么。"
},
"inspector.how.eyebrow": {
"message": "如何使用"
},
"inspector.how.title": {
"message": "先看全局,再沿着证据深入"
},
"inspector.how.scope.title": {
"message": "选择范围"
},
"inspector.how.scope.body": {
"message": "按产品意图查看时使用 Delivery Tree;按时间查看会话与提交时使用 Date。"
},
"inspector.how.lanes.title": {
"message": "阅读三条信息轨"
},
"inspector.how.lanes.body": {
"message": "在不离开当前 Story 或日期的情况下,对照用户提示、检查点活动以及提交或文件。"
},
"inspector.how.evidence.title": {
"message": "检查关联关系"
},
"inspector.how.evidence.body": {
"message": "选择提示、操作、提交或路径。相关项目会保持高亮,Evidence Drawer 则解释关联及其限制。"
},
"inspector.how.session.title": {
"message": "打开 Session View 或 Replay"
},
"inspector.how.session.body": {
"message": "深入查看保留的轮次和归一化工具调用,或回放已观察到的序列。Replay 不会重新运行工具,也不会恢复 Coding Agent 会话。"
},
"inspector.evidence.eyebrow": {
"message": "理解证据标签"
},
"inspector.evidence.title": {
"message": "关联会明确展示,但不会擅自推断作者身份"
},
"inspector.evidence.explicit": {
"message": "保留的引用直接连接 Story、会话或提交。"
},
"inspector.evidence.observed": {
"message": "会话与提交共享完全相同的仓库路径。这支持相关性判断,但不能证明作者身份。"
},
"inspector.evidence.candidate": {
"message": "结构或时间信息提示了有用的关联,但仍需人工确认。"
},
"inspector.evidence.contextual": {
"message": "附近的历史有助于解释交付过程,但不会声称存在直接关联。"
},
"inspector.local.eyebrow": {
"message": "使用你自己的证据"
},
"inspector.local.title": {
"message": "生成私有的自包含 Inspector"
},
"inspector.local.body": {
"message": "安装 Better Harness 后,在仓库中运行高级 Inspector 命令。采集留在本地,生成的报告保持只读。"
},
"inspector.local.installation": {
"message": "查看安装方式"
},
"inspector.local.output": {
"message": "默认输出:.qoder/better-harness-runs/harness-inspector/inspector.html"
},
"homepage.dimensions.taskUnderstanding.title": {
"message": "任务理解"
},
Expand Down
4 changes: 4 additions & 0 deletions docs/i18n/zh-Hans/docusaurus-theme-classic/navbar.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"message": "文档",
"description": "Navbar item with label Docs"
},
"item.label.Inspector": {
"message": "Inspector",
"description": "Navbar item with label Inspector"
},
"item.label.Blog": {
"message": "博客",
"description": "Navbar item with label Blog"
Expand Down
10 changes: 8 additions & 2 deletions docs/scripts/sync-assets.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Sync published assets from the repository root into docs/static/.
// assets/ stays the single source of truth; synced targets are gitignored.
import { cpSync, mkdirSync, rmSync } from "node:fs";
import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import { renderHarnessInspectorDemoHtml } from "../../scripts/harness-inspector/demo-report.mjs";

export function syncAssets({ repoRoot, siteRoot }) {
const generatedDemoRoot = join(siteRoot, "static", "demo");

Expand Down Expand Up @@ -42,7 +44,11 @@ export function syncAssets({ repoRoot, siteRoot }) {
mkdirSync(dirname(to), { recursive: true });
cpSync(from, to, { recursive: true });
}
return copies.length;
const inspectorDemo = join(generatedDemoRoot, "harness-inspector", "index.html");
mkdirSync(dirname(inspectorDemo), { recursive: true });
writeFileSync(inspectorDemo, renderHarnessInspectorDemoHtml(), "utf8");

return copies.length + 1;
}

const currentFile = fileURLToPath(import.meta.url);
Expand Down
125 changes: 125 additions & 0 deletions docs/specs/2026-08-13-inspector-session-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Replay retained session evidence

## Traceability

- Spec ID: inspector-session-replay
- Status: Draft

## Intent

Let a reviewer play through one retained coding-agent session without leaving
the Inspector's existing evidence model. Session View should keep its current
chronological Trace and add a Replay mode that synchronizes a current-event
stage, an event/file index, a compact timeline, and playback controls.

Replay is a read-only presentation of the sanitized report projection. It does
not rerun tools, restore a worktree, resume a native host session, or invent
timestamps for content whose time was not observed.

## Acceptance Scenarios

- AC-1: `Open session` exposes semantic `Trace` and `Replay` tabs. Trace remains
the default, and a URL with `view=session&session-mode=replay` restores Replay
for the named session.
- AC-2: Each projected session owns a `SessionReplay` model, without a version
suffix in its name. It deterministically projects retained prompts,
intermediate responses, tool calls, final responses, and directly linked
commits from the already-sanitized report model.
- AC-3: Every replay event declares its timing basis. Observed prompt, tool, and
commit timestamps may appear on the wall-clock rail; response boundaries may
be labelled as Turn-bound; intermediate responses and other untimed content
remain `sequence-only` and are never assigned an invented clock time.
- AC-4: Selecting an event updates the current-event stage, the event index, the
compact timeline cursor when timing exists, and the Inspector selection when
the event has a Story, Session, Turn, Tool Call, File, or Commit descriptor.
Existing Evidence Drawer relationships and limitations remain authoritative.
- AC-5: Replay provides previous, play/pause, and next controls plus 1x, 2x, 4x,
and 8x event pacing. Playback advances by retained event order, compresses
long unobserved gaps instead of making multi-hour sessions wait in real time,
stops at the end, and never invokes a host tool.
- AC-6: Replay exposes `Events` and `Files` index tabs. Choosing a file selects
that repository-relative path and moves to the first retained replay event
that names it, without claiming the event authored a commit.
- AC-7: The mode tabs, index tabs, event rows, file rows, playback buttons, and
speed controls are keyboard operable; the current event uses non-color state
cues, playback respects reduced-motion preference, and controls do not steal
shortcuts from form inputs.
- AC-8: The self-contained HTML retains no raw tool input/output, hidden
reasoning, absolute home path, or credential. Missing content and unavailable
timing stay explicit in the model and UI.
- AC-9: Focused behavior tests cover `SessionReplay` ordering and timing bases,
direct-commit inclusion, untimed fallbacks, mode restoration, playback
controls, and final HTML privacy. A real multi-hour local session is verified
in desktop and narrow browser layouts with no page or console errors.

## Non-goals

- Adding Replay as a top-level Inspector scope beside Delivery Tree and Date.
- Executing, retrying, resuming, or mutating a tool call, Git state, worktree,
feature-tree mapping, checkpoint, or native host session.
- Human annotations, pinned notes, author identity, or report persistence.
- Fabricating cost, token, test-result, response, or timing evidence that the
source projection did not retain.
- Replacing Trace, the Evidence Drawer, or the existing wall-clock activity
chart with a second relationship model.

## Plan and Tasks

1. Extend `scripts/harness-inspector/report-model.mjs` with a bounded
`SessionReplay` projection built only after Session/Commit links exist.
Preserve explicit timing bases and direct-commit limitations.
2. Extend `scripts/harness-inspector/ui/workbench.js` with a session-local
playback owner separate from evidence selection and chart zoom. Render
Trace/Replay tabs, the event stage, Events/Files index, compact rail, and
playback controls from the projected model.
3. Extend `scripts/harness-inspector/ui/workbench.css` with responsive Replay
layout and accessible selected/focus treatments that reuse current Inspector
tokens and typography.
4. Extend focused Inspector tests with behavior assertions over the model and
rendered interactions. Preserve unrelated working-tree changes in the same
files and keep the report self-contained.
5. Render a real report, exercise mode switching, event/file selection,
playback, speed, URL restoration, Trace return, keyboard behavior, desktop
and narrow layouts, then inspect console/page errors.

## Test and Review Evidence

- AC-2/AC-3/AC-8: `npx vitest run test/reporting/harness-inspector.test.mjs`
asserts the parsed model shape, event ordering, timing bases, direct commits,
and sanitized projection rather than matching implementation source text.
- AC-1/AC-4..AC-7: browser behavior against a real self-contained report,
including a copied Replay deep link and an event with no observed timestamp.
- AC-9 regression: `npm test`, `npm run pack:verify`, documentation link graph,
and a Review Readiness Check over the final local/staged split.
- Privacy risk: Replay consumes only projected safe text and repository-relative
paths; final HTML is checked for private-path and credential leakage.
- Correlation risk: only explicit or observed-commit relationships enter the
replay stream. Same-path and contextual commits stay in Trace/Evidence.
- Scale risk: the index is event-driven and the compact rail renders timed
events only; playback advances by event pace so long idle windows do not block
review.
- Interaction risk: Replay state is session-local and URL-addressable, while
Evidence selection and activity zoom remain separate owners.

## Replay Legibility Follow-up

Deep-linking to a mid-session event (`replay-event=call:A179`) exposed six
legibility defects, since fixed and verified in-browser against a real
multi-hour local session with no console or page errors:

- AC-4 follow-up: `updateReplayPresentation` now keeps the current event row in
view inside the index's own scroller only, so following playback never scrolls
Session View or the Workbench underneath it, and a tab return re-reveals it.
- AC-7 follow-up: the current row uses a stronger non-color cue (heavier bar,
bold title) in addition to background, and the mode tabs (filled) read
distinctly from the index tabs (underlined).
- Layout: the index column tracks layout width instead of the viewport and keeps
its own readable height, so a narrow window or open Drawer narrows the column
rather than stacking it where the sticky transport would cover the list. The
event card no longer reserves a tall fixed height that dwarfed short evidence.
- AC-8 follow-up: a bounded (clipped) projection body is flagged `bodyExcerpt`
and shown as an `Excerpt` badge instead of a silently truncated command.
- Orientation: the timeline rail gains a type legend, and duplicated position and
timing text collapses to one `Event N / total` label plus the card's Turn line.
- Overlay: the root scroller locks while Session View is open so wheel gestures
cannot scroll the hidden Workbench.
57 changes: 57 additions & 0 deletions docs/specs/2026-08-13-inspector-trace-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,60 @@ claim the evidence vocabulary forbids.
inactivity, and the outside-the-window commit track states its own limit.
- Scale: binning is bounded by plot width, not by call count, and the removal of
per-session SVG templates reduced report size at the same evidence coverage.

## Amendment (2026-08-13): the timeline is inside Session View and linked

The first slice kept the wall-clock activity chart in the workbench lane and left
Session View as a vertical Turn list. In use that split the one time-axis
visualization from the one place calls can be read line by line: they never
shared a screen, so a reviewer could not go from a busy stretch on the chart to
the calls under it, and a session whose calls carried no dialogue Turn showed
"0 tool calls" on every Turn while its whole trace sat in a page-tail bucket.

This amendment moves the same chart (`activityChartMarkup`, re-rendered by
`renderActivityChart` through the existing `[data-activity-chart]` wiring, so it
shares the per-session zoom state and needs no second model) into a sticky,
collapsible strip at the top of Session View, and links it to the list. No new
data is projected; it reuses `startedAt` and Turn `startMs/endMs` already
present.

- AC-10: Session View opens with the wall-clock strip visible above the list.
A session with no dialogue Turn still renders the strip from observed call
times, and its untied calls are held in one bucket ordered by observed time
rather than as an unordered pile.
- AC-11: The strip is a minimap. Clicking a bar scrolls the list to the calls
under it (expanding whatever disclosure hides them); a multi-call bar also
zooms. Zooming in the strip and in the workbench chart share one state.
- AC-12: A short session opens every Turn's tool calls; a long session (> 12
Turns) stays collapsed and is navigated from the strip. Identical consecutive
rows stay collapsed as a run so the default view is concise, not a wall of
duplicates. Row virtualization and a node-link canvas remain non-goals.
- AC-13: Filtering a tool type hides both its rows and the run bands that stand
in for it, and the sidebar tool total recomputes to the count that survives
the current filters (a run counted once per grouped call), independent of
which disclosures are open.
- AC-14: The idle legend states the shading is a window with no observed call,
not a user wait; a session with no observed timing keeps the sequence-axis
label rather than implying a time.
- AC-15: Above the action lanes the chart carries a ribbon that fills the whole
domain: observed calls are painted over a continuous band, so the time not
spent inside a tool is a visible share of the trace rather than blank canvas.
The bare band is labelled unattributed — model work or waiting — and never as
a model turn. The ribbon is omitted on the call-order fallback, where spacing
would be an artefact of ordinal position rather than elapsed time.

**Why the ribbon.** The lanes plot the instant a call ran, so a reader saw only
the moments a tool was executing. Measured on a real local report, 1 178 gaps
between consecutive calls had a median of 6.6 s and a p90 of 29.7 s, while the
idle threshold that produced any shading was 45 s: 1 113 of those gaps (94.5 %)
had no visual representation at all. On one 2.2 h session the observed tool time
totals 14 m — the chart was drawing 21 % of the trace and leaving 79 % blank.
The ribbon makes that residue a first-class part of the picture without claiming
to know what happened inside it: `projectDialogue` keeps note text but no note
timestamp, so the host observed when tools ran, not when the model worked.

Verified on a real local report: on a dialogue-less 235-call session the strip
renders on open and clicking a bar zooms to ~18 calls in view and scrolls the
list to the matching rows (timestamps ascending); toggling the Bash tool moves
the sidebar total 235 → 150 → 235 while its run bands hide and restore; a
10-Turn session opens all five tool blocks by default; no console errors.
Loading
Loading