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/README.md +27 -0
- package/assets/viewer/assets/index-C7hwGkuU.js +3 -0
- package/assets/viewer/assets/index-Dhc-7q5o.css +1 -0
- package/assets/viewer/index.html +14 -0
- package/assets/viewer-single.html +16 -0
- package/dist/index.js +289 -47
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
|
4
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5
5
|
import { resolve as resolve2 } from "path";
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
|
-
import
|
|
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
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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
|
|
514
|
-
|
|
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: ${
|
|
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
|
|
1217
|
-
mkdirSync as
|
|
1218
|
-
readFileSync as
|
|
1219
|
-
readdirSync as
|
|
1430
|
+
existsSync as existsSync5,
|
|
1431
|
+
mkdirSync as mkdirSync3,
|
|
1432
|
+
readFileSync as readFileSync3,
|
|
1433
|
+
readdirSync as readdirSync3,
|
|
1220
1434
|
rmSync as rmSync2,
|
|
1221
|
-
statSync as
|
|
1222
|
-
writeFileSync as
|
|
1435
|
+
statSync as statSync3,
|
|
1436
|
+
writeFileSync as writeFileSync4
|
|
1223
1437
|
} from "fs";
|
|
1224
1438
|
import { homedir as homedir2 } from "os";
|
|
1225
|
-
import { join as
|
|
1226
|
-
import
|
|
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 ?
|
|
1443
|
+
return scope.global ? join8(homedir2(), ".claude", "skills") : join8(scope.cwd, ".claude", "skills");
|
|
1230
1444
|
}
|
|
1231
1445
|
function sourceSkillsDir() {
|
|
1232
|
-
return
|
|
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 (!
|
|
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
|
-
|
|
1243
|
-
const available =
|
|
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 =
|
|
1247
|
-
if (
|
|
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(
|
|
1253
|
-
const skillFile =
|
|
1254
|
-
const rewritten =
|
|
1255
|
-
|
|
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 ${
|
|
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 =
|
|
1274
|
-
if (
|
|
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 =
|
|
1291
|
-
return
|
|
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(` ${
|
|
1508
|
+
if (present.length) for (const n of present) process.stderr.write(` ${pc8.green("\u2713")} /${n}
|
|
1295
1509
|
`);
|
|
1296
|
-
else process.stderr.write(` ${
|
|
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(
|
|
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
|
-
() =>
|
|
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
|
-
${
|
|
1396
|
-
${
|
|
1397
|
-
${
|
|
1398
|
-
${
|
|
1399
|
-
${
|
|
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
|
`
|