edsger 0.81.0 → 0.82.0

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/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync3 } from "fs";
4
+ import { readFileSync as readFileSync4 } from "fs";
5
5
  import { resolve as resolve2 } from "path";
6
6
  import { Command, Option } from "commander";
7
- import pc7 from "picocolors";
7
+ import pc9 from "picocolors";
8
8
 
9
9
  // src/commands/benchmark.ts
10
- import { mkdirSync, writeFileSync } from "fs";
10
+ import { appendFileSync, mkdirSync, writeFileSync } from "fs";
11
11
  import { basename, join as join3 } from "path";
12
12
  import pc3 from "picocolors";
13
13
 
@@ -490,28 +490,55 @@ function summarizeTool(name = "tool", input = {}) {
490
490
  }
491
491
  }
492
492
 
493
+ // src/core/git.ts
494
+ import { execFileSync as execFileSync2 } from "child_process";
495
+ function tryGit(args, cwd) {
496
+ try {
497
+ return execFileSync2("git", args, {
498
+ cwd,
499
+ encoding: "utf8",
500
+ stdio: ["ignore", "pipe", "ignore"]
501
+ }).trim();
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+ function gitInfo(cwd) {
507
+ const commit = tryGit(["rev-parse", "--short", "HEAD"], cwd);
508
+ const branch = tryGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
509
+ const status = tryGit(["status", "--porcelain"], cwd);
510
+ return {
511
+ commit,
512
+ branch: branch && branch !== "HEAD" ? branch : null,
513
+ dirty: Boolean(status && status.length > 0)
514
+ };
515
+ }
516
+
493
517
  // src/core/report.ts
494
518
  function scoreBenchmark(standard, result) {
495
519
  const scale = standard.scoreScale || 4;
496
520
  const totalWeight = standard.categories.reduce((s, c) => s + c.weight, 0) || 1;
497
- const scoreLookup = /* @__PURE__ */ new Map();
498
- for (const cat of result.categories) {
499
- for (const cr of cat.criteria) scoreLookup.set(`${cat.id}/${cr.id}`, cr.score);
500
- }
501
- const justifyLookup = /* @__PURE__ */ new Map();
521
+ const tail = (id) => id.slice(id.lastIndexOf("/") + 1);
522
+ const byKey = /* @__PURE__ */ new Map();
523
+ const byCrit = /* @__PURE__ */ new Map();
502
524
  for (const cat of result.categories) {
503
525
  for (const cr of cat.criteria) {
504
- justifyLookup.set(`${cat.id}/${cr.id}`, {
526
+ const meta = {
527
+ score: cr.score,
505
528
  justification: cr.justification,
506
529
  evidence: cr.evidence ?? []
507
- });
530
+ };
531
+ byKey.set(`${tail(cat.id)}/${tail(cr.id)}`, meta);
532
+ byCrit.set(tail(cr.id), meta);
508
533
  }
509
534
  }
535
+ let matched = 0;
510
536
  const categories = standard.categories.map((cat) => {
511
537
  const critWeightTotal = cat.criteria.reduce((s, c) => s + (c.weight ?? 1), 0) || 1;
512
538
  const scored = cat.criteria.map((cr) => {
513
- const raw = clamp(scoreLookup.get(`${cat.id}/${cr.id}`) ?? 0, 0, scale);
514
- const meta = justifyLookup.get(`${cat.id}/${cr.id}`);
539
+ const meta = byKey.get(`${tail(cat.id)}/${tail(cr.id)}`) ?? byCrit.get(tail(cr.id));
540
+ if (meta) matched += 1;
541
+ const raw = clamp(meta?.score ?? 0, 0, scale);
515
542
  return {
516
543
  id: cr.id,
517
544
  title: cr.title,
@@ -534,6 +561,14 @@ function scoreBenchmark(standard, result) {
534
561
  criteria: scored
535
562
  };
536
563
  });
564
+ const totalCriteria = standard.categories.reduce((s, c) => s + c.criteria.length, 0);
565
+ if (result.categories.length > 0 && matched === 0) {
566
+ log.warn(
567
+ "The agent returned scores but none matched the rubric's criterion ids \u2014 reporting 0. This is a scoring bug, not a real result."
568
+ );
569
+ } else if (matched < totalCriteria) {
570
+ log.warn(`Only ${matched}/${totalCriteria} criteria were scored; the rest default to 0.`);
571
+ }
537
572
  const overall = round(
538
573
  categories.reduce((s, c) => s + c.points, 0),
539
574
  1
@@ -704,6 +739,7 @@ async function runBenchmark(opts) {
704
739
  const scored = scoreBenchmark(standard, result);
705
740
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
706
741
  const runId = generatedAt.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
742
+ const git = gitInfo(opts.cwd);
707
743
  const md = renderBenchmarkMarkdown(scored, {
708
744
  repo: basename(opts.cwd),
709
745
  languages: resolved.languages,
@@ -717,6 +753,7 @@ async function runBenchmark(opts) {
717
753
  generatedAt,
718
754
  model: opts.model,
719
755
  repo: basename(opts.cwd),
756
+ git,
720
757
  languages: resolved.languages,
721
758
  standard: { name: standard.name, version: standard.version, layers: resolved.layers },
722
759
  overall: scored.overall,
@@ -728,6 +765,19 @@ async function runBenchmark(opts) {
728
765
  recommendations: scored.recommendations,
729
766
  costUsd
730
767
  };
768
+ const historyRecord = {
769
+ runId,
770
+ generatedAt,
771
+ commit: git.commit,
772
+ branch: git.branch,
773
+ dirty: git.dirty,
774
+ model: opts.model,
775
+ standardVersion: standard.version,
776
+ overall: scored.overall,
777
+ grade: scored.grade,
778
+ categories: Object.fromEntries(scored.categories.map((c) => [c.id, c.fraction])),
779
+ costUsd
780
+ };
731
781
  if (!opts.noWrite && !opts.dryRun) {
732
782
  const baseDir = join3(opts.cwd, opts.out);
733
783
  const mdOut = md + "\n";
@@ -738,6 +788,7 @@ async function runBenchmark(opts) {
738
788
  writeFileSync(join3(runDir, "report.json"), jsonOut, "utf8");
739
789
  writeFileSync(join3(baseDir, "report.md"), mdOut, "utf8");
740
790
  writeFileSync(join3(baseDir, "report.json"), jsonOut, "utf8");
791
+ appendFileSync(join3(baseDir, "history.jsonl"), JSON.stringify(historyRecord) + "\n", "utf8");
741
792
  log.success(
742
793
  `Report written to ${pc3.bold(join3(opts.out, "report.md"))} ${pc3.dim(`(run archived at ${join3(opts.out, "runs", runId)})`)}`
743
794
  );
@@ -765,7 +816,7 @@ function buildPrompt(standard, cwd) {
765
816
  const rubric = standard.categories.map((cat) => {
766
817
  const crits = cat.criteria.map((cr) => {
767
818
  const anchors = cr.rubric ? Object.entries(cr.rubric).map(([k, v]) => ` - ${k}: ${v}`).join("\n") : "";
768
- return ` - id: ${cat.id}/${cr.id}
819
+ return ` - id: ${cr.id}
769
820
  title: ${cr.title}
770
821
  guidance: ${cr.guidance.trim()}` + (anchors ? `
771
822
  anchors:
@@ -1210,49 +1261,212 @@ function buildPrompt3(ctx, feedback, instruction) {
1210
1261
  ].join("\n");
1211
1262
  }
1212
1263
 
1264
+ // src/commands/serve.ts
1265
+ import { createReadStream, existsSync as existsSync4, statSync as statSync2 } from "fs";
1266
+ import { createServer } from "http";
1267
+ import { extname, join as join6, normalize } from "path";
1268
+ import pc6 from "picocolors";
1269
+
1270
+ // src/core/dashboard.ts
1271
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1272
+ import { join as join5 } from "path";
1273
+ function assembleData(benchmarkDir) {
1274
+ const latest = readJson(join5(benchmarkDir, "report.json"));
1275
+ const history = readJsonl(join5(benchmarkDir, "history.jsonl"));
1276
+ const runs = {};
1277
+ const runsDir = join5(benchmarkDir, "runs");
1278
+ if (existsSync3(runsDir)) {
1279
+ for (const entry of readdirSync2(runsDir)) {
1280
+ const report = readJson(join5(runsDir, entry, "report.json"));
1281
+ if (report) runs[entry] = report;
1282
+ }
1283
+ }
1284
+ const repo = latest && typeof latest === "object" && "repo" in latest ? latest.repo : void 0;
1285
+ return { repo, latest, history, runs };
1286
+ }
1287
+ function hasData(data) {
1288
+ return Boolean(data.latest) || data.history.length > 0;
1289
+ }
1290
+ function findViewerDir() {
1291
+ const dir = join5(findBundledAssetsDir(), "viewer");
1292
+ if (!existsSync3(join5(dir, "index.html"))) {
1293
+ throw new Error(
1294
+ "Dashboard viewer assets not found. Reinstall edsger, or run `npm run sync-viewer` in a dev checkout."
1295
+ );
1296
+ }
1297
+ return dir;
1298
+ }
1299
+ function readViewerSingleTemplate() {
1300
+ const file = join5(findBundledAssetsDir(), "viewer-single.html");
1301
+ if (!existsSync3(file)) {
1302
+ throw new Error(
1303
+ "Single-file viewer template not found. Reinstall edsger, or run `npm run sync-viewer`."
1304
+ );
1305
+ }
1306
+ return readFileSync2(file, "utf8");
1307
+ }
1308
+ function renderSingleFile(template, data) {
1309
+ const json = JSON.stringify(data).replace(/<\//g, "<\\/");
1310
+ const tag = `<script>window.__EDSGER_DATA__=${json}</script>`;
1311
+ if (template.includes("</head>")) return template.replace("</head>", `${tag}</head>`);
1312
+ return template.replace("<body>", `<body>${tag}`);
1313
+ }
1314
+ function readJson(path) {
1315
+ try {
1316
+ return JSON.parse(readFileSync2(path, "utf8"));
1317
+ } catch {
1318
+ return null;
1319
+ }
1320
+ }
1321
+ function readJsonl(path) {
1322
+ if (!existsSync3(path)) return [];
1323
+ const out = [];
1324
+ for (const line of readFileSync2(path, "utf8").split("\n")) {
1325
+ const t = line.trim();
1326
+ if (!t) continue;
1327
+ try {
1328
+ out.push(JSON.parse(t));
1329
+ } catch {
1330
+ }
1331
+ }
1332
+ return out;
1333
+ }
1334
+
1335
+ // src/core/open.ts
1336
+ import { spawn } from "child_process";
1337
+ function openInBrowser(target) {
1338
+ const platform = process.platform;
1339
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
1340
+ const args = platform === "win32" ? ["/c", "start", "", target] : [target];
1341
+ try {
1342
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
1343
+ } catch {
1344
+ }
1345
+ }
1346
+
1347
+ // src/commands/serve.ts
1348
+ var MIME = {
1349
+ ".html": "text/html; charset=utf-8",
1350
+ ".js": "text/javascript; charset=utf-8",
1351
+ ".css": "text/css; charset=utf-8",
1352
+ ".json": "application/json; charset=utf-8",
1353
+ ".svg": "image/svg+xml",
1354
+ ".map": "application/json",
1355
+ ".ico": "image/x-icon",
1356
+ ".woff2": "font/woff2",
1357
+ ".woff": "font/woff"
1358
+ };
1359
+ async function runServe(opts) {
1360
+ const benchmarkDir = join6(opts.cwd, opts.dir);
1361
+ const viewerDir = findViewerDir();
1362
+ const server = createServer((req, res) => {
1363
+ const url = (req.url ?? "/").split("?")[0] ?? "/";
1364
+ if (url === "/edsger-data.json") {
1365
+ res.writeHead(200, {
1366
+ "content-type": "application/json; charset=utf-8",
1367
+ "cache-control": "no-store"
1368
+ });
1369
+ res.end(JSON.stringify(assembleData(benchmarkDir)));
1370
+ return;
1371
+ }
1372
+ const rel = url === "/" ? "index.html" : decodeURIComponent(url.replace(/^\/+/, ""));
1373
+ const safe = normalize(rel).replace(/^(\.\.(?:[/\\]|$))+/, "");
1374
+ let file = join6(viewerDir, safe);
1375
+ if (!file.startsWith(viewerDir)) {
1376
+ res.writeHead(403);
1377
+ res.end("forbidden");
1378
+ return;
1379
+ }
1380
+ if (!existsSync4(file) || statSync2(file).isDirectory()) file = join6(viewerDir, "index.html");
1381
+ res.writeHead(200, { "content-type": MIME[extname(file)] ?? "application/octet-stream" });
1382
+ createReadStream(file).pipe(res);
1383
+ });
1384
+ return new Promise((resolve3, reject) => {
1385
+ server.on("error", reject);
1386
+ server.listen(opts.port, opts.host, () => {
1387
+ const addr = server.address();
1388
+ const port = typeof addr === "object" && addr ? addr.port : opts.port;
1389
+ const shownHost = opts.host === "0.0.0.0" ? "localhost" : opts.host;
1390
+ const url = `http://${shownHost}:${port}/`;
1391
+ if (!hasData(assembleData(benchmarkDir))) {
1392
+ log.warn(`No benchmark data in ${opts.dir} yet \u2014 run \`edsger benchmark\` first.`);
1393
+ }
1394
+ log.success(`Dashboard at ${pc6.bold(pc6.cyan(url))} ${pc6.dim("(Ctrl-C to stop)")}`);
1395
+ if (opts.open) openInBrowser(url);
1396
+ });
1397
+ const shutdown = () => {
1398
+ server.close(() => resolve3(0));
1399
+ };
1400
+ process.on("SIGINT", shutdown);
1401
+ process.on("SIGTERM", shutdown);
1402
+ });
1403
+ }
1404
+
1405
+ // src/commands/report.ts
1406
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
1407
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join7 } from "path";
1408
+ import pc7 from "picocolors";
1409
+ async function runReport(opts) {
1410
+ const benchmarkDir = join7(opts.cwd, opts.dir);
1411
+ const data = assembleData(benchmarkDir);
1412
+ if (!hasData(data)) {
1413
+ log.error(`No benchmark data in ${opts.dir}. Run \`edsger benchmark\` first.`);
1414
+ return 1;
1415
+ }
1416
+ const html = renderSingleFile(readViewerSingleTemplate(), data);
1417
+ const out = isAbsolute2(opts.html) ? opts.html : join7(opts.cwd, opts.html);
1418
+ mkdirSync2(dirname2(out), { recursive: true });
1419
+ writeFileSync3(out, html, "utf8");
1420
+ log.success(
1421
+ `Wrote self-contained report to ${pc7.bold(opts.html)} ${pc7.dim(`(${Math.round(html.length / 1024)} kB, open in any browser)`)}`
1422
+ );
1423
+ if (opts.open) openInBrowser(out);
1424
+ return 0;
1425
+ }
1426
+
1213
1427
  // src/commands/skills.ts
1214
1428
  import {
1215
1429
  cpSync,
1216
- existsSync as existsSync3,
1217
- mkdirSync as mkdirSync2,
1218
- readFileSync as readFileSync2,
1219
- readdirSync as readdirSync2,
1430
+ existsSync as existsSync5,
1431
+ mkdirSync as mkdirSync3,
1432
+ readFileSync as readFileSync3,
1433
+ readdirSync as readdirSync3,
1220
1434
  rmSync as rmSync2,
1221
- statSync as statSync2,
1222
- writeFileSync as writeFileSync3
1435
+ statSync as statSync3,
1436
+ writeFileSync as writeFileSync4
1223
1437
  } from "fs";
1224
1438
  import { homedir as homedir2 } from "os";
1225
- import { join as join5 } from "path";
1226
- import pc6 from "picocolors";
1439
+ import { join as join8 } from "path";
1440
+ import pc8 from "picocolors";
1227
1441
  var MANAGED = ["benchmark", "pr-review", "pr-resolve"];
1228
1442
  function targetSkillsDir(scope) {
1229
- return scope.global ? join5(homedir2(), ".claude", "skills") : join5(scope.cwd, ".claude", "skills");
1443
+ return scope.global ? join8(homedir2(), ".claude", "skills") : join8(scope.cwd, ".claude", "skills");
1230
1444
  }
1231
1445
  function sourceSkillsDir() {
1232
- return join5(findBundledAssetsDir(), "skills");
1446
+ return join8(findBundledAssetsDir(), "skills");
1233
1447
  }
1234
1448
  async function runSkillsInstall(opts) {
1235
1449
  const assetsDir = findBundledAssetsDir();
1236
1450
  const src = sourceSkillsDir();
1237
- if (!existsSync3(src)) {
1451
+ if (!existsSync5(src)) {
1238
1452
  log.error(`Bundled skills not found at ${src}. Reinstall edsger.`);
1239
1453
  return 1;
1240
1454
  }
1241
1455
  const dest = targetSkillsDir(opts);
1242
- mkdirSync2(dest, { recursive: true });
1243
- const available = readdirSync2(src).filter((n) => existsSync3(join5(src, n, "SKILL.md")));
1456
+ mkdirSync3(dest, { recursive: true });
1457
+ const available = readdirSync3(src).filter((n) => existsSync5(join8(src, n, "SKILL.md")));
1244
1458
  let installed = 0;
1245
1459
  for (const name of available) {
1246
- const outDir = join5(dest, name);
1247
- if (existsSync3(outDir) && !opts.force) {
1460
+ const outDir = join8(dest, name);
1461
+ if (existsSync5(outDir) && !opts.force) {
1248
1462
  log.warn(`skill "${name}" already exists \u2014 use --force to overwrite`);
1249
1463
  continue;
1250
1464
  }
1251
1465
  rmSync2(outDir, { recursive: true, force: true });
1252
- cpSync(join5(src, name), outDir, { recursive: true });
1253
- const skillFile = join5(outDir, "SKILL.md");
1254
- const rewritten = readFileSync2(skillFile, "utf8").split("${CLAUDE_PLUGIN_ROOT}").join(assetsDir);
1255
- writeFileSync3(skillFile, rewritten, "utf8");
1466
+ cpSync(join8(src, name), outDir, { recursive: true });
1467
+ const skillFile = join8(outDir, "SKILL.md");
1468
+ const rewritten = readFileSync3(skillFile, "utf8").split("${CLAUDE_PLUGIN_ROOT}").join(assetsDir);
1469
+ writeFileSync4(skillFile, rewritten, "utf8");
1256
1470
  installed++;
1257
1471
  log.success(`installed /${name}`);
1258
1472
  }
@@ -1260,7 +1474,7 @@ async function runSkillsInstall(opts) {
1260
1474
  log.info("Nothing installed.");
1261
1475
  return 0;
1262
1476
  }
1263
- log.heading(`Installed ${installed} skill(s) into ${pc6.bold(dest)}`);
1477
+ log.heading(`Installed ${installed} skill(s) into ${pc8.bold(dest)}`);
1264
1478
  log.step(`scope: ${opts.global ? "user (all projects)" : "this project"}`);
1265
1479
  log.step(`standards: ${assetsDir}`);
1266
1480
  log.step("Open Claude Code and run /benchmark, /pr-review, or /pr-resolve.");
@@ -1270,8 +1484,8 @@ async function runSkillsUninstall(opts) {
1270
1484
  const dest = targetSkillsDir(opts);
1271
1485
  let removed = 0;
1272
1486
  for (const name of MANAGED) {
1273
- const dir = join5(dest, name);
1274
- if (existsSync3(dir)) {
1487
+ const dir = join8(dest, name);
1488
+ if (existsSync5(dir)) {
1275
1489
  rmSync2(dir, { recursive: true, force: true });
1276
1490
  removed++;
1277
1491
  log.success(`removed /${name}`);
@@ -1287,20 +1501,20 @@ async function runSkillsList(opts) {
1287
1501
  ]) {
1288
1502
  const dir = targetSkillsDir(scope);
1289
1503
  const present = MANAGED.filter((n) => {
1290
- const p = join5(dir, n, "SKILL.md");
1291
- return existsSync3(p) && statSync2(p).isFile();
1504
+ const p = join8(dir, n, "SKILL.md");
1505
+ return existsSync5(p) && statSync3(p).isFile();
1292
1506
  });
1293
1507
  log.heading(`${label}: ${dir}`);
1294
- if (present.length) for (const n of present) process.stderr.write(` ${pc6.green("\u2713")} /${n}
1508
+ if (present.length) for (const n of present) process.stderr.write(` ${pc8.green("\u2713")} /${n}
1295
1509
  `);
1296
- else process.stderr.write(` ${pc6.dim("(none installed)")}
1510
+ else process.stderr.write(` ${pc8.dim("(none installed)")}
1297
1511
  `);
1298
1512
  }
1299
1513
  return 0;
1300
1514
  }
1301
1515
 
1302
1516
  // src/index.ts
1303
- var pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1517
+ var pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
1304
1518
  var VERSION = pkg.version;
1305
1519
  var DEFAULT_MODEL = "claude-sonnet-4-6";
1306
1520
  function toGlobal(raw) {
@@ -1338,10 +1552,38 @@ program.name("edsger").description(
1338
1552
  "Agentic CLI to benchmark repositories and review/resolve pull requests\nagainst industrial-grade standards (from edsger-assets, fully overridable)."
1339
1553
  ).version(VERSION, "-V, --version");
1340
1554
  addGlobalOptions(
1341
- program.command("benchmark").description("Score a repository against a benchmark rubric and write a report").option("--out <dir>", "report output directory (relative to repo)", ".edsger/benchmark").option("--no-write", "do not write report files into the repo")
1555
+ program.command("benchmark").description("Score a repository against a benchmark rubric and write a report").option("--out <dir>", "report output directory (relative to repo)", ".edsger/benchmark").option("--no-write", "do not write report files into the repo").option("--serve", "open the dashboard after benchmarking")
1342
1556
  ).action(async (opts) => {
1557
+ await guard(async () => {
1558
+ const g = toGlobal(opts);
1559
+ const code = await runBenchmark({ ...g, out: opts.out, noWrite: opts.write === false });
1560
+ if (code === 0 && opts.serve) {
1561
+ return runServe({ cwd: g.cwd, dir: opts.out, port: 4577, host: "127.0.0.1", open: true });
1562
+ }
1563
+ return code;
1564
+ });
1565
+ });
1566
+ program.command("serve").description("Serve the benchmark dashboard (current, history, trends) locally").option("-C, --cwd <dir>", "repository directory").option("--dir <dir>", "benchmark output directory (relative to repo)", ".edsger/benchmark").option("--port <n>", "port to listen on", "4577").option("--host <host>", "host to bind", "127.0.0.1").option("--no-open", "do not open the browser automatically").action(
1567
+ async (opts) => {
1568
+ await guard(
1569
+ () => runServe({
1570
+ cwd: resolve2(opts.cwd ?? process.cwd()),
1571
+ dir: opts.dir,
1572
+ port: Number.parseInt(opts.port, 10) || 4577,
1573
+ host: opts.host,
1574
+ open: opts.open !== false
1575
+ })
1576
+ );
1577
+ }
1578
+ );
1579
+ program.command("report").description("Export a self-contained HTML dashboard (data inlined) for sharing").option("-C, --cwd <dir>", "repository directory").option("--dir <dir>", "benchmark output directory (relative to repo)", ".edsger/benchmark").option("--html <path>", "output HTML file path", "edsger-report.html").option("--open", "open the exported file after writing").action(async (opts) => {
1343
1580
  await guard(
1344
- () => runBenchmark({ ...toGlobal(opts), out: opts.out, noWrite: opts.write === false })
1581
+ () => runReport({
1582
+ cwd: resolve2(opts.cwd ?? process.cwd()),
1583
+ dir: opts.dir,
1584
+ html: opts.html,
1585
+ open: Boolean(opts.open)
1586
+ })
1345
1587
  );
1346
1588
  });
1347
1589
  addGlobalOptions(
@@ -1392,11 +1634,11 @@ program.addHelpText(
1392
1634
  "after",
1393
1635
  `
1394
1636
  Examples:
1395
- ${pc7.dim("$")} edsger benchmark
1396
- ${pc7.dim("$")} edsger benchmark -C ./my-repo --json
1397
- ${pc7.dim("$")} edsger pr-review 123 --post
1398
- ${pc7.dim("$")} edsger pr-resolve 123 --instruction "fix the failing test" --push
1399
- ${pc7.dim("$")} edsger skills install ${pc7.dim("# enable /benchmark etc. in Claude Code")}
1637
+ ${pc9.dim("$")} edsger benchmark
1638
+ ${pc9.dim("$")} edsger benchmark -C ./my-repo --json
1639
+ ${pc9.dim("$")} edsger pr-review 123 --post
1640
+ ${pc9.dim("$")} edsger pr-resolve 123 --instruction "fix the failing test" --push
1641
+ ${pc9.dim("$")} edsger skills install ${pc9.dim("# enable /benchmark etc. in Claude Code")}
1400
1642
 
1401
1643
  Auth: the agent uses your Claude Code login (run \`claude\` once to sign in); run \`gh auth login\` for PR commands.
1402
1644
  `