Skip to content

Commit 8a24d63

Browse files
authored
Merge pull request #85 from QoderAI/feat/inspector-trace-linked-timeline
feat(inspector): add linked replay and public demo
2 parents 0bb0530 + 6d68cb5 commit 8a24d63

23 files changed

Lines changed: 2666 additions & 42 deletions

.github/workflows/pages.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
paths:
77
- "docs/**"
88
- "assets/**"
9+
- "scripts/commit-session-link/**"
10+
- "scripts/harness-inspector/**"
11+
- "scripts/session-analysis/**"
912
- ".github/workflows/pages.yml"
1013
workflow_dispatch:
1114

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<p align="center">
2+
<img src="assets/logo.svg" alt="Better Harness logo" width="56" height="56">
3+
</p>
4+
15
<h1 align="center">Better Harness</h1>
26

37
<p align="center">

assets/logo.svg

Lines changed: 13 additions & 0 deletions
Loading

docs/docusaurus.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ const config = {
109109
position: "left",
110110
label: "Docs",
111111
},
112+
{
113+
to: "/inspector/",
114+
label: "Inspector",
115+
position: "left",
116+
className: "navbar__link--inspector-new",
117+
"aria-label": "Inspector",
118+
},
112119
{
113120
to: "/blog",
114121
label: "Blog",

docs/i18n/zh-Hans/code.json

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,124 @@
11
{
2+
"inspector.meta.description": {
3+
"message": "探索 Better Harness 如何以明确的证据边界连接产品意图、Coding Agent 活动、会话、文件与提交。"
4+
},
5+
"inspector.hero.lead": {
6+
"message": "从产品意图追踪到 Agent 活动、会话、文件与提交,同时让证据强度及其限制始终可见。"
7+
},
8+
"inspector.boundaries.label": {
9+
"message": "Demo 边界"
10+
},
11+
"inspector.boundaries.interactive": {
12+
"message": "可交互样例"
13+
},
14+
"inspector.boundaries.readOnly": {
15+
"message": "只读"
16+
},
17+
"inspector.actions.fullscreen": {
18+
"message": "全屏打开"
19+
},
20+
"inspector.demo.title": {
21+
"message": "可交互 Harness Inspector 样例"
22+
},
23+
"inspector.demo.frameTitle": {
24+
"message": "可交互 Harness Inspector 样例"
25+
},
26+
"inspector.demo.loading": {
27+
"message": "正在加载可交互样例…"
28+
},
29+
"inspector.demo.note": {
30+
"message": "此确定性样例使用虚构的英文数据,不会读取你的工作区、Git 历史或 Coding Agent 会话。"
31+
},
32+
"inspector.features.eyebrow": {
33+
"message": "它展示什么"
34+
},
35+
"inspector.features.title": {
36+
"message": "沿着交付链路追踪,同时不越过证据边界"
37+
},
38+
"inspector.features.intro": {
39+
"message": "Inspector 把产品结构、Agent 活动与 Git 结果放进同一个只读工作区,并明确区分强关联与有用但有限的上下文。"
40+
},
41+
"inspector.features.intent.title": {
42+
"message": "从意图到交付"
43+
},
44+
"inspector.features.intent.body": {
45+
"message": "沿 Delivery Tree 从能力和 Story 意图,查看与之关联的会话、文件与提交。"
46+
},
47+
"inspector.features.session.title": {
48+
"message": "把会话放回上下文"
49+
},
50+
"inspector.features.session.body": {
51+
"message": "检查保留的提示词、归一化工具活动、文件路径、提交事件,以及只读会话回放。"
52+
},
53+
"inspector.features.evidence.title": {
54+
"message": "可解释的证据"
55+
},
56+
"inspector.features.evidence.body": {
57+
"message": "同步选择与 Evidence Drawer 会说明关联为何存在、可信度如何,以及它不能证明什么。"
58+
},
59+
"inspector.how.eyebrow": {
60+
"message": "如何使用"
61+
},
62+
"inspector.how.title": {
63+
"message": "先看全局,再沿着证据深入"
64+
},
65+
"inspector.how.scope.title": {
66+
"message": "选择范围"
67+
},
68+
"inspector.how.scope.body": {
69+
"message": "按产品意图查看时使用 Delivery Tree;按时间查看会话与提交时使用 Date。"
70+
},
71+
"inspector.how.lanes.title": {
72+
"message": "阅读三条信息轨"
73+
},
74+
"inspector.how.lanes.body": {
75+
"message": "在不离开当前 Story 或日期的情况下,对照用户提示、检查点活动以及提交或文件。"
76+
},
77+
"inspector.how.evidence.title": {
78+
"message": "检查关联关系"
79+
},
80+
"inspector.how.evidence.body": {
81+
"message": "选择提示、操作、提交或路径。相关项目会保持高亮,Evidence Drawer 则解释关联及其限制。"
82+
},
83+
"inspector.how.session.title": {
84+
"message": "打开 Session View 或 Replay"
85+
},
86+
"inspector.how.session.body": {
87+
"message": "深入查看保留的轮次和归一化工具调用,或回放已观察到的序列。Replay 不会重新运行工具,也不会恢复 Coding Agent 会话。"
88+
},
89+
"inspector.evidence.eyebrow": {
90+
"message": "理解证据标签"
91+
},
92+
"inspector.evidence.title": {
93+
"message": "关联会明确展示,但不会擅自推断作者身份"
94+
},
95+
"inspector.evidence.explicit": {
96+
"message": "保留的引用直接连接 Story、会话或提交。"
97+
},
98+
"inspector.evidence.observed": {
99+
"message": "会话与提交共享完全相同的仓库路径。这支持相关性判断,但不能证明作者身份。"
100+
},
101+
"inspector.evidence.candidate": {
102+
"message": "结构或时间信息提示了有用的关联,但仍需人工确认。"
103+
},
104+
"inspector.evidence.contextual": {
105+
"message": "附近的历史有助于解释交付过程,但不会声称存在直接关联。"
106+
},
107+
"inspector.local.eyebrow": {
108+
"message": "使用你自己的证据"
109+
},
110+
"inspector.local.title": {
111+
"message": "生成私有的自包含 Inspector"
112+
},
113+
"inspector.local.body": {
114+
"message": "安装 Better Harness 后,在仓库中运行高级 Inspector 命令。采集留在本地,生成的报告保持只读。"
115+
},
116+
"inspector.local.installation": {
117+
"message": "查看安装方式"
118+
},
119+
"inspector.local.output": {
120+
"message": "默认输出:.qoder/better-harness-runs/harness-inspector/inspector.html"
121+
},
2122
"homepage.dimensions.taskUnderstanding.title": {
3123
"message": "任务理解"
4124
},

docs/i18n/zh-Hans/docusaurus-theme-classic/navbar.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"message": "文档",
88
"description": "Navbar item with label Docs"
99
},
10+
"item.label.Inspector": {
11+
"message": "Inspector",
12+
"description": "Navbar item with label Inspector"
13+
},
1014
"item.label.Blog": {
1115
"message": "博客",
1216
"description": "Navbar item with label Blog"

docs/scripts/sync-assets.mjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Sync published assets from the repository root into docs/static/.
22
// assets/ stays the single source of truth; synced targets are gitignored.
3-
import { cpSync, mkdirSync, rmSync } from "node:fs";
3+
import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
44
import { dirname, join, resolve } from "node:path";
55
import { fileURLToPath } from "node:url";
66

7+
import { renderHarnessInspectorDemoHtml } from "../../scripts/harness-inspector/demo-report.mjs";
8+
79
export function syncAssets({ repoRoot, siteRoot }) {
810
const generatedDemoRoot = join(siteRoot, "static", "demo");
911

@@ -42,7 +44,11 @@ export function syncAssets({ repoRoot, siteRoot }) {
4244
mkdirSync(dirname(to), { recursive: true });
4345
cpSync(from, to, { recursive: true });
4446
}
45-
return copies.length;
47+
const inspectorDemo = join(generatedDemoRoot, "harness-inspector", "index.html");
48+
mkdirSync(dirname(inspectorDemo), { recursive: true });
49+
writeFileSync(inspectorDemo, renderHarnessInspectorDemoHtml(), "utf8");
50+
51+
return copies.length + 1;
4652
}
4753

4854
const currentFile = fileURLToPath(import.meta.url);
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Replay retained session evidence
2+
3+
## Traceability
4+
5+
- Spec ID: inspector-session-replay
6+
- Status: Draft
7+
8+
## Intent
9+
10+
Let a reviewer play through one retained coding-agent session without leaving
11+
the Inspector's existing evidence model. Session View should keep its current
12+
chronological Trace and add a Replay mode that synchronizes a current-event
13+
stage, an event/file index, a compact timeline, and playback controls.
14+
15+
Replay is a read-only presentation of the sanitized report projection. It does
16+
not rerun tools, restore a worktree, resume a native host session, or invent
17+
timestamps for content whose time was not observed.
18+
19+
## Acceptance Scenarios
20+
21+
- AC-1: `Open session` exposes semantic `Trace` and `Replay` tabs. Trace remains
22+
the default, and a URL with `view=session&session-mode=replay` restores Replay
23+
for the named session.
24+
- AC-2: Each projected session owns a `SessionReplay` model, without a version
25+
suffix in its name. It deterministically projects retained prompts,
26+
intermediate responses, tool calls, final responses, and directly linked
27+
commits from the already-sanitized report model.
28+
- AC-3: Every replay event declares its timing basis. Observed prompt, tool, and
29+
commit timestamps may appear on the wall-clock rail; response boundaries may
30+
be labelled as Turn-bound; intermediate responses and other untimed content
31+
remain `sequence-only` and are never assigned an invented clock time.
32+
- AC-4: Selecting an event updates the current-event stage, the event index, the
33+
compact timeline cursor when timing exists, and the Inspector selection when
34+
the event has a Story, Session, Turn, Tool Call, File, or Commit descriptor.
35+
Existing Evidence Drawer relationships and limitations remain authoritative.
36+
- AC-5: Replay provides previous, play/pause, and next controls plus 1x, 2x, 4x,
37+
and 8x event pacing. Playback advances by retained event order, compresses
38+
long unobserved gaps instead of making multi-hour sessions wait in real time,
39+
stops at the end, and never invokes a host tool.
40+
- AC-6: Replay exposes `Events` and `Files` index tabs. Choosing a file selects
41+
that repository-relative path and moves to the first retained replay event
42+
that names it, without claiming the event authored a commit.
43+
- AC-7: The mode tabs, index tabs, event rows, file rows, playback buttons, and
44+
speed controls are keyboard operable; the current event uses non-color state
45+
cues, playback respects reduced-motion preference, and controls do not steal
46+
shortcuts from form inputs.
47+
- AC-8: The self-contained HTML retains no raw tool input/output, hidden
48+
reasoning, absolute home path, or credential. Missing content and unavailable
49+
timing stay explicit in the model and UI.
50+
- AC-9: Focused behavior tests cover `SessionReplay` ordering and timing bases,
51+
direct-commit inclusion, untimed fallbacks, mode restoration, playback
52+
controls, and final HTML privacy. A real multi-hour local session is verified
53+
in desktop and narrow browser layouts with no page or console errors.
54+
55+
## Non-goals
56+
57+
- Adding Replay as a top-level Inspector scope beside Delivery Tree and Date.
58+
- Executing, retrying, resuming, or mutating a tool call, Git state, worktree,
59+
feature-tree mapping, checkpoint, or native host session.
60+
- Human annotations, pinned notes, author identity, or report persistence.
61+
- Fabricating cost, token, test-result, response, or timing evidence that the
62+
source projection did not retain.
63+
- Replacing Trace, the Evidence Drawer, or the existing wall-clock activity
64+
chart with a second relationship model.
65+
66+
## Plan and Tasks
67+
68+
1. Extend `scripts/harness-inspector/report-model.mjs` with a bounded
69+
`SessionReplay` projection built only after Session/Commit links exist.
70+
Preserve explicit timing bases and direct-commit limitations.
71+
2. Extend `scripts/harness-inspector/ui/workbench.js` with a session-local
72+
playback owner separate from evidence selection and chart zoom. Render
73+
Trace/Replay tabs, the event stage, Events/Files index, compact rail, and
74+
playback controls from the projected model.
75+
3. Extend `scripts/harness-inspector/ui/workbench.css` with responsive Replay
76+
layout and accessible selected/focus treatments that reuse current Inspector
77+
tokens and typography.
78+
4. Extend focused Inspector tests with behavior assertions over the model and
79+
rendered interactions. Preserve unrelated working-tree changes in the same
80+
files and keep the report self-contained.
81+
5. Render a real report, exercise mode switching, event/file selection,
82+
playback, speed, URL restoration, Trace return, keyboard behavior, desktop
83+
and narrow layouts, then inspect console/page errors.
84+
85+
## Test and Review Evidence
86+
87+
- AC-2/AC-3/AC-8: `npx vitest run test/reporting/harness-inspector.test.mjs`
88+
asserts the parsed model shape, event ordering, timing bases, direct commits,
89+
and sanitized projection rather than matching implementation source text.
90+
- AC-1/AC-4..AC-7: browser behavior against a real self-contained report,
91+
including a copied Replay deep link and an event with no observed timestamp.
92+
- AC-9 regression: `npm test`, `npm run pack:verify`, documentation link graph,
93+
and a Review Readiness Check over the final local/staged split.
94+
- Privacy risk: Replay consumes only projected safe text and repository-relative
95+
paths; final HTML is checked for private-path and credential leakage.
96+
- Correlation risk: only explicit or observed-commit relationships enter the
97+
replay stream. Same-path and contextual commits stay in Trace/Evidence.
98+
- Scale risk: the index is event-driven and the compact rail renders timed
99+
events only; playback advances by event pace so long idle windows do not block
100+
review.
101+
- Interaction risk: Replay state is session-local and URL-addressable, while
102+
Evidence selection and activity zoom remain separate owners.
103+
104+
## Replay Legibility Follow-up
105+
106+
Deep-linking to a mid-session event (`replay-event=call:A179`) exposed six
107+
legibility defects, since fixed and verified in-browser against a real
108+
multi-hour local session with no console or page errors:
109+
110+
- AC-4 follow-up: `updateReplayPresentation` now keeps the current event row in
111+
view inside the index's own scroller only, so following playback never scrolls
112+
Session View or the Workbench underneath it, and a tab return re-reveals it.
113+
- AC-7 follow-up: the current row uses a stronger non-color cue (heavier bar,
114+
bold title) in addition to background, and the mode tabs (filled) read
115+
distinctly from the index tabs (underlined).
116+
- Layout: the index column tracks layout width instead of the viewport and keeps
117+
its own readable height, so a narrow window or open Drawer narrows the column
118+
rather than stacking it where the sticky transport would cover the list. The
119+
event card no longer reserves a tall fixed height that dwarfed short evidence.
120+
- AC-8 follow-up: a bounded (clipped) projection body is flagged `bodyExcerpt`
121+
and shown as an `Excerpt` badge instead of a silently truncated command.
122+
- Orientation: the timeline rail gains a type legend, and duplicated position and
123+
timing text collapses to one `Event N / total` label plus the card's Turn line.
124+
- Overlay: the root scroller locks while Session View is open so wheel gestures
125+
cannot scroll the hidden Workbench.

docs/specs/2026-08-13-inspector-trace-view.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,60 @@ claim the evidence vocabulary forbids.
123123
inactivity, and the outside-the-window commit track states its own limit.
124124
- Scale: binning is bounded by plot width, not by call count, and the removal of
125125
per-session SVG templates reduced report size at the same evidence coverage.
126+
127+
## Amendment (2026-08-13): the timeline is inside Session View and linked
128+
129+
The first slice kept the wall-clock activity chart in the workbench lane and left
130+
Session View as a vertical Turn list. In use that split the one time-axis
131+
visualization from the one place calls can be read line by line: they never
132+
shared a screen, so a reviewer could not go from a busy stretch on the chart to
133+
the calls under it, and a session whose calls carried no dialogue Turn showed
134+
"0 tool calls" on every Turn while its whole trace sat in a page-tail bucket.
135+
136+
This amendment moves the same chart (`activityChartMarkup`, re-rendered by
137+
`renderActivityChart` through the existing `[data-activity-chart]` wiring, so it
138+
shares the per-session zoom state and needs no second model) into a sticky,
139+
collapsible strip at the top of Session View, and links it to the list. No new
140+
data is projected; it reuses `startedAt` and Turn `startMs/endMs` already
141+
present.
142+
143+
- AC-10: Session View opens with the wall-clock strip visible above the list.
144+
A session with no dialogue Turn still renders the strip from observed call
145+
times, and its untied calls are held in one bucket ordered by observed time
146+
rather than as an unordered pile.
147+
- AC-11: The strip is a minimap. Clicking a bar scrolls the list to the calls
148+
under it (expanding whatever disclosure hides them); a multi-call bar also
149+
zooms. Zooming in the strip and in the workbench chart share one state.
150+
- AC-12: A short session opens every Turn's tool calls; a long session (> 12
151+
Turns) stays collapsed and is navigated from the strip. Identical consecutive
152+
rows stay collapsed as a run so the default view is concise, not a wall of
153+
duplicates. Row virtualization and a node-link canvas remain non-goals.
154+
- AC-13: Filtering a tool type hides both its rows and the run bands that stand
155+
in for it, and the sidebar tool total recomputes to the count that survives
156+
the current filters (a run counted once per grouped call), independent of
157+
which disclosures are open.
158+
- AC-14: The idle legend states the shading is a window with no observed call,
159+
not a user wait; a session with no observed timing keeps the sequence-axis
160+
label rather than implying a time.
161+
- AC-15: Above the action lanes the chart carries a ribbon that fills the whole
162+
domain: observed calls are painted over a continuous band, so the time not
163+
spent inside a tool is a visible share of the trace rather than blank canvas.
164+
The bare band is labelled unattributed — model work or waiting — and never as
165+
a model turn. The ribbon is omitted on the call-order fallback, where spacing
166+
would be an artefact of ordinal position rather than elapsed time.
167+
168+
**Why the ribbon.** The lanes plot the instant a call ran, so a reader saw only
169+
the moments a tool was executing. Measured on a real local report, 1 178 gaps
170+
between consecutive calls had a median of 6.6 s and a p90 of 29.7 s, while the
171+
idle threshold that produced any shading was 45 s: 1 113 of those gaps (94.5 %)
172+
had no visual representation at all. On one 2.2 h session the observed tool time
173+
totals 14 m — the chart was drawing 21 % of the trace and leaving 79 % blank.
174+
The ribbon makes that residue a first-class part of the picture without claiming
175+
to know what happened inside it: `projectDialogue` keeps note text but no note
176+
timestamp, so the host observed when tools ran, not when the model worked.
177+
178+
Verified on a real local report: on a dialogue-less 235-call session the strip
179+
renders on open and clicking a bar zooms to ~18 calls in view and scrolls the
180+
list to the matching rows (timestamps ascending); toggling the Bash tool moves
181+
the sidebar total 235 → 150 → 235 while its run bands hide and restore; a
182+
10-Turn session opens all five tool blocks by default; no console errors.

0 commit comments

Comments
 (0)