dsh-session-eval 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1 — 2026-09-26
4
+
5
+ - **Fix: ship the bundle overlay.** v0.2.0 declared `dsh.bundle.patch: ./cordis.patch.yml`
6
+ in `package.json` but the file was never committed, so the published tarball did not
7
+ contain it and `dsh web` failed at startup with
8
+ `failed to read overlay .../dsh-session-eval/cordis.patch.yml: ENOENT`.
9
+ The overlay (mounting `/eval`, `/eval-diff`, `/eval-history`) is now in the tarball;
10
+ verified with `npm pack --dry-run`.
11
+ - **Fix: peer ranges match the current harness.** `@deepseek-ai/*` peers and dev
12
+ dependencies moved from `^0.1.1-rc.2` to `^0.1.5-rc.3` (semver prerelease ranges do not
13
+ cross `0.1.1-rc.x` → `0.1.5-rc.3`, which let stale `0.1.1-rc.2` copies install into
14
+ profiles and shadow the running harness). `@deepseek-ai/cordis` pinned to `4.0.2`,
15
+ matching the official plugins at this harness level.
16
+ - Added this changelog; `files` always listed it but it was never written.
17
+
18
+ ## 0.2.0 — 2026-09-25
19
+
20
+ - New `/eval-history [N] [--json]` command: lists the current workspace's recent sessions
21
+ (same `sessionQuery` seam as `/archive`), grades the last N, and diffs first-vs-last for
22
+ a trend verdict. Caps the manual id-picking that `/eval-diff` required.
23
+ - Version string single-sourced from `src/version.ts` (replaces four hardcoded `v0.1.0`
24
+ strings).
25
+ - 8 new tests (35 total) and `typecheck` script.
26
+
27
+ ## 0.1.0 — 2026-09-24
28
+
29
+ - Initial release: `/eval` grade cards from persisted session logs (tool-error rate,
30
+ follow-up signals, tool load), `/eval-diff` regression comparison, no LLM judge.
package/README.md CHANGED
@@ -45,7 +45,7 @@ Then in DeepSeek Harness:
45
45
  ```
46
46
 
47
47
  ```text
48
- Session eval — a1b2c3d4… (dsh-session-eval v0.1.0)
48
+ Session eval — a1b2c3d4… (dsh-session-eval v0.2.0)
49
49
  Overall: A
50
50
  Reliability A 2/87 tool results errored (2.3%)
51
51
  Re-ask A 1 re-ask signal(s) over 23 turns (0.4/10 turns)
@@ -67,7 +67,7 @@ Compare two sessions — the regression workflow:
67
67
  ```
68
68
 
69
69
  ```text
70
- Session diff — a1b2c3d4… → e5f6a7b8… (dsh-session-eval v0.1.0)
70
+ Session diff — a1b2c3d4… → e5f6a7b8… (dsh-session-eval v0.2.0)
71
71
  Overall: regressed — 0 improved, 2 regressed, 1 flat.
72
72
  Reliability 2.1% → 11.8% regressed
73
73
  Re-ask 0.5 → 3.2 regressed
@@ -76,6 +76,23 @@ Overall: regressed — 0 improved, 2 regressed, 1 flat.
76
76
 
77
77
  Typical use: grade the ten sessions before and after a plugin or prompt change; a persistent `Reliability` regression is an early warning that the change hurt real work, not just benchmarks.
78
78
 
79
+ See the whole trend — no ids to hunt for:
80
+
81
+ ```text
82
+ /eval-history # last 5 sessions in this workspace
83
+ /eval-history 12 # last 12
84
+ ```
85
+
86
+ ```text
87
+ Session history — last 5 in this workspace
88
+ 2026-09-20 14:02 a1b2c3d4… B 4 turns, 9 tools
89
+ 2026-09-21 09:15 c3d4e5f6… A 5 turns, 11 tools
90
+ 2026-09-22 17:44 e5f6a7b8… A 3 turns, 7 tools
91
+ Trend (first → last): improved — 2 improved, 0 regressed, 1 flat.
92
+ ```
93
+
94
+ `/eval-history` finds the sessions for you via the same `sessionQuery` seam `/archive` uses (scoped to the current workspace's `cwd`), grades each, and diffs the first against the last. `/eval-diff` remains the tool when you already know the two ids that matter.
95
+
79
96
  ## Dimensions & thresholds
80
97
 
81
98
  All grades derive from three deterministic metrics. `n/a` when the session lacks the relevant activity.
@@ -105,6 +122,8 @@ Same session logs in → same grade card out, every time, forever. There is no m
105
122
  | `/eval --json` | Machine-readable metrics + card |
106
123
  | `/eval-diff <beforeId> <afterId>` | Regression comparison, noise-tolerant |
107
124
  | `/eval-diff … --json` | Machine-readable trend report |
125
+ | `/eval-history [N]` | Grade the last N sessions in this workspace and show the trend |
126
+ | `/eval-history … --json` | Machine-readable history report |
108
127
 
109
128
  ## Development
110
129
 
package/README.zh.md CHANGED
@@ -37,8 +37,12 @@ npm install -g dsh-session-eval
37
37
  /eval # 当前会话等级卡
38
38
  /eval --id <会话ID> # 指定会话
39
39
  /eval-diff <前> <后> # 回归对比(换插件/prompt 前后各评一次)
40
+ /eval-history # 本工作区最近 5 个会话的成绩单 + 首末趋势
41
+ /eval-history 12 # 最近 12 个
40
42
  ```
41
43
 
44
+ `/eval-history` 通过与 `/archive` 相同的 `sessionQuery` 接缝自动找到会话(按当前工作区 `cwd` 过滤),逐个评级,并把首尾两个会话做趋势对比——不用再手动挑 id。
45
+
42
46
  ## 维度与阈值(确定性,无裁判)
43
47
 
44
48
  | 维度 | 指标 | A | B | C | D | E |
@@ -0,0 +1,6 @@
1
+ # The dsh-session-eval bundle patch: mounts /eval, /eval-diff, and /eval-history.
2
+ # Applied as one insert; requires `commands` and `sessionQuery` services,
3
+ # which the shipped dsh-base / web bundles already provide.
4
+ - insert:
5
+ - id: session-eval
6
+ name: 'dsh-session-eval'
package/lib/index.d.ts CHANGED
@@ -132,10 +132,57 @@ declare function executeEval(ctx: Context, invocation: CommandInvocation, seam?:
132
132
  /** Execute /eval-diff against the session-query seam. */
133
133
  declare function executeEvalDiff(ctx: Context, invocation: CommandInvocation, seam?: QuerySeam): Promise<CommandResult>;
134
134
  //#endregion
135
+ //#region src/historyCommand.d.ts
136
+ declare const EVAL_HISTORY_USAGE = "Usage: /eval-history [N] [--json] (N = last N sessions, 1-20, default 5)";
137
+ /** Args for /eval-history. */
138
+ interface EvalHistoryArgs {
139
+ readonly count: number;
140
+ readonly json: boolean;
141
+ }
142
+ /** Parse /eval-history input; returns args or a usage-error string. */
143
+ declare function parseEvalHistoryArgs(rawInput: string): EvalHistoryArgs | string;
144
+ /** One graded session in a history report. */
145
+ interface HistoryEntry {
146
+ readonly sessionId: string;
147
+ readonly createdAt: number | null;
148
+ readonly metrics: SessionMetrics;
149
+ readonly card: GradeCard;
150
+ }
151
+ /** The full /eval-history report. */
152
+ interface HistoryReport {
153
+ readonly entries: readonly HistoryEntry[];
154
+ readonly trend: TrendReport | null;
155
+ }
156
+ /** The slice of sessionQuery this command consumes (structural, for testability). */
157
+ interface HistorySeam {
158
+ listSessions(): Promise<ReadonlyArray<{
159
+ header: {
160
+ id: unknown;
161
+ createdAt: unknown;
162
+ cwd?: unknown;
163
+ };
164
+ }>>;
165
+ readSession(id: ReturnType<typeof SessionId>): Promise<{
166
+ events: readonly unknown[];
167
+ }>;
168
+ }
169
+ /** Render a history report as terminal text. */
170
+ declare function renderHistory(report: HistoryReport): string;
171
+ /** Execute /eval-history against the session-query seam. */
172
+ declare function executeEvalHistory(ctx: Context, invocation: CommandInvocation, seam?: HistorySeam): Promise<CommandResult>;
173
+ //#endregion
174
+ //#region src/version.d.ts
175
+ /**
176
+ * Single source of truth for the plugin version string.
177
+ *
178
+ * @module dsh-session-eval/version
179
+ */
180
+ declare const VERSION = "0.2.1";
181
+ //#endregion
135
182
  //#region src/index.d.ts
136
183
  declare const name = "session-eval";
137
184
  declare const inject: string[];
138
- /** Plugin entry: mount the /eval and /eval-diff commands. */
185
+ /** Plugin entry: mount the /eval, /eval-diff and /eval-history commands. */
139
186
  declare function apply(ctx: Context): void;
140
187
  //#endregion
141
- export { type DimensionDelta, type DimensionGrade, EVAL_DIFF_USAGE, EVAL_USAGE, type EvalArgs, type EvalDiffArgs, type EvalEvent, type EvalEventKind, type Grade, type GradeCard, type SessionMetrics, type TrendReport, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, renderCard, renderTrend };
188
+ export { type DimensionDelta, type DimensionGrade, EVAL_DIFF_USAGE, EVAL_HISTORY_USAGE, EVAL_USAGE, type EvalArgs, type EvalDiffArgs, type EvalEvent, type EvalEventKind, type EvalHistoryArgs, type Grade, type GradeCard, type HistoryEntry, type HistoryReport, type HistorySeam, type SessionMetrics, type TrendReport, VERSION, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, executeEvalHistory, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, parseEvalHistoryArgs, renderCard, renderHistory, renderTrend };
package/lib/index.js CHANGED
@@ -221,6 +221,14 @@ function compareSessions(before, after) {
221
221
  };
222
222
  }
223
223
  //#endregion
224
+ //#region src/version.ts
225
+ /**
226
+ * Single source of truth for the plugin version string.
227
+ *
228
+ * @module dsh-session-eval/version
229
+ */
230
+ const VERSION = "0.2.1";
231
+ //#endregion
224
232
  //#region src/evalCommand.ts
225
233
  const EVAL_USAGE = "Usage: /eval [--id <sessionId>] [--json]";
226
234
  const EVAL_DIFF_USAGE = "Usage: /eval-diff <beforeSessionId> <afterSessionId> [--json]";
@@ -319,14 +327,14 @@ async function executeEval(ctx, invocation, seam = ctx.sessionQuery) {
319
327
  if (parsed.json === true) return {
320
328
  kind: "success",
321
329
  text: JSON.stringify({
322
- generator: "dsh-session-eval v0.1.0",
330
+ generator: "dsh-session-eval v0.2.1",
323
331
  metrics,
324
332
  card
325
333
  }, null, 2)
326
334
  };
327
335
  return {
328
336
  kind: "success",
329
- text: renderCard(card, "dsh-session-eval v0.1.0")
337
+ text: renderCard(card, "dsh-session-eval v0.2.1")
330
338
  };
331
339
  }
332
340
  /** Execute /eval-diff against the session-query seam. */
@@ -350,20 +358,133 @@ async function executeEvalDiff(ctx, invocation, seam = ctx.sessionQuery) {
350
358
  if (parsed.json === true) return {
351
359
  kind: "success",
352
360
  text: JSON.stringify({
353
- generator: "dsh-session-eval v0.1.0",
361
+ generator: "dsh-session-eval v0.2.1",
362
+ report
363
+ }, null, 2)
364
+ };
365
+ return {
366
+ kind: "success",
367
+ text: renderTrend(report, "dsh-session-eval v0.2.1")
368
+ };
369
+ }
370
+ //#endregion
371
+ //#region src/historyCommand.ts
372
+ const EVAL_HISTORY_USAGE = "Usage: /eval-history [N] [--json] (N = last N sessions, 1-20, default 5)";
373
+ /** Parse /eval-history input; returns args or a usage-error string. */
374
+ function parseEvalHistoryArgs(rawInput) {
375
+ const tokens = rawInput.trim().split(/\s+/).filter((t) => t.length > 0);
376
+ let count = 5;
377
+ let json = false;
378
+ let sawCount = false;
379
+ for (const token of tokens) {
380
+ if (token === "--json") {
381
+ if (json) return `--json may be given only once.\n${EVAL_HISTORY_USAGE}`;
382
+ json = true;
383
+ continue;
384
+ }
385
+ if (token.startsWith("--")) return `Unknown argument: ${token}\n${EVAL_HISTORY_USAGE}`;
386
+ if (sawCount) return `Expected at most one count argument.\n${EVAL_HISTORY_USAGE}`;
387
+ if (!/^\d+$/.test(token)) return `N must be a positive integer.\n${EVAL_HISTORY_USAGE}`;
388
+ const value = Number(token);
389
+ if (value < 1 || value > 20) return `N must be between 1 and 20.\n${EVAL_HISTORY_USAGE}`;
390
+ count = value;
391
+ sawCount = true;
392
+ }
393
+ return {
394
+ count,
395
+ json
396
+ };
397
+ }
398
+ function toMillis(createdAt) {
399
+ if (typeof createdAt === "number" && Number.isFinite(createdAt)) return createdAt;
400
+ if (typeof createdAt === "string") {
401
+ const parsed = Date.parse(createdAt);
402
+ if (Number.isFinite(parsed)) return parsed;
403
+ }
404
+ return null;
405
+ }
406
+ function stamp(ms) {
407
+ if (ms === null) return "?";
408
+ return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
409
+ }
410
+ /** Render a history report as terminal text. */
411
+ function renderHistory(report) {
412
+ const lines = [];
413
+ if (report.entries.length === 0) return "No sessions found in this workspace; nothing to grade.";
414
+ lines.push(`Session history — last ${report.entries.length} in this workspace`);
415
+ for (const entry of report.entries) {
416
+ const m = entry.metrics;
417
+ lines.push(` ${stamp(entry.createdAt)} ${id8(entry.sessionId)} ${String(entry.card.overall).padEnd(2)} ${m.userTurns} turns, ${m.toolCalls} tools`);
418
+ }
419
+ const trend = report.trend;
420
+ if (trend !== null) lines.push(`Trend (first → last): ${trend.overall} — ${trend.summary}`);
421
+ return lines.join("\n");
422
+ }
423
+ /** Execute /eval-history against the session-query seam. */
424
+ async function executeEvalHistory(ctx, invocation, seam = ctx.sessionQuery) {
425
+ const parsed = parseEvalHistoryArgs(invocation.rawInput);
426
+ if (typeof parsed === "string") return {
427
+ kind: "error",
428
+ text: parsed
429
+ };
430
+ const ownCwd = invocation.agent.session.header?.cwd;
431
+ let records;
432
+ try {
433
+ records = await seam.listSessions();
434
+ } catch (error) {
435
+ return {
436
+ kind: "error",
437
+ text: `Could not list sessions: ${error instanceof Error ? error.message : String(error)}`
438
+ };
439
+ }
440
+ const recent = records.map((record) => record.header).filter((header) => header != null).filter((header) => ownCwd === void 0 || header.cwd === ownCwd).map((header) => ({
441
+ id: String(header.id),
442
+ createdAt: toMillis(header.createdAt)
443
+ })).sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)).slice(-parsed.count);
444
+ if (recent.length === 0) return {
445
+ kind: "success",
446
+ text: "No sessions found in this workspace; nothing to grade."
447
+ };
448
+ const entries = [];
449
+ for (const header of recent) {
450
+ let events;
451
+ try {
452
+ events = (await seam.readSession(SessionId(header.id))).events;
453
+ } catch (error) {
454
+ return {
455
+ kind: "error",
456
+ text: `Could not read session ${id8(header.id)}: ${error instanceof Error ? error.message : String(error)}`
457
+ };
458
+ }
459
+ const metrics = computeMetrics(header.id, adaptEvents(events));
460
+ entries.push({
461
+ sessionId: header.id,
462
+ createdAt: header.createdAt,
463
+ metrics,
464
+ card: gradeSession(metrics)
465
+ });
466
+ }
467
+ const report = {
468
+ entries,
469
+ trend: entries.length >= 2 ? compareSessions(entries[0].metrics, entries[entries.length - 1].metrics) : null
470
+ };
471
+ if (parsed.json === true) return {
472
+ kind: "success",
473
+ text: JSON.stringify({
474
+ generator: `dsh-session-eval v${VERSION}`,
354
475
  report
355
476
  }, null, 2)
356
477
  };
357
478
  return {
358
479
  kind: "success",
359
- text: renderTrend(report, "dsh-session-eval v0.1.0")
480
+ text: renderHistory(report)
360
481
  };
361
482
  }
362
483
  //#endregion
363
484
  //#region src/index.ts
364
485
  const name = "session-eval";
365
486
  const inject = ["commands", "sessionQuery"];
366
- /** Plugin entry: mount the /eval and /eval-diff commands. */
487
+ /** Plugin entry: mount the /eval, /eval-diff and /eval-history commands. */
367
488
  function apply(ctx) {
368
489
  ctx.effect(function* () {
369
490
  yield ctx.commands.register({
@@ -376,7 +497,12 @@ function apply(ctx) {
376
497
  description: "Compare two sessions (before, after) and report which dimensions improved or regressed",
377
498
  handler: (invocation) => executeEvalDiff(ctx, invocation)
378
499
  });
500
+ yield ctx.commands.register({
501
+ name: "eval-history",
502
+ description: "Grade the last N sessions in this workspace (default 5) and show whether the trend is improving",
503
+ handler: (invocation) => executeEvalHistory(ctx, invocation)
504
+ });
379
505
  }, "session-eval lifecycle");
380
506
  }
381
507
  //#endregion
382
- export { EVAL_DIFF_USAGE, EVAL_USAGE, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, renderCard, renderTrend };
508
+ export { EVAL_DIFF_USAGE, EVAL_HISTORY_USAGE, EVAL_USAGE, VERSION, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, executeEvalHistory, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, parseEvalHistoryArgs, renderCard, renderHistory, renderTrend };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-session-eval",
3
3
  "description": "Retrospective agent-session evaluation for DeepSeek Harness: deterministic grade cards from persisted session logs, cross-session regression diffs, era comparison — no benchmark runs, no LLM judges.",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,21 +38,21 @@
38
38
  "regression"
39
39
  ],
40
40
  "peerDependencies": {
41
- "@deepseek-ai/cordis": "^4.0.1",
42
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
43
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
44
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
46
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
41
+ "@deepseek-ai/cordis": "4.0.2",
42
+ "@deepseek-ai/dsh-commands": "^0.1.5-rc.3",
43
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.3",
44
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.3",
45
+ "@deepseek-ai/dsh-session-query": "^0.1.5-rc.3",
46
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.2.0",
50
50
  "tsdown": "^0.22.14",
51
51
  "typescript": "^7.0.2",
52
52
  "vitest": "^4.1.11",
53
- "@deepseek-ai/cordis": "^4.0.1",
54
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2"
53
+ "@deepseek-ai/cordis": "4.0.2",
54
+ "@deepseek-ai/dsh-commands": "^0.1.5-rc.3",
55
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.3"
56
56
  },
57
57
  "scripts": {
58
58
  "bundle": "tsdown",