agent-gauntlet 1.2.1 → 1.3.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/.claude-plugin/marketplace.json +25 -0
- package/.claude-plugin/plugin.json +6 -0
- package/.cursor-plugin/plugin.json +6 -0
- package/README.md +2 -1
- package/dist/index.js +905 -368
- package/dist/index.js.map +23 -17
- package/hooks/cursor-hooks.json +16 -0
- package/hooks/hooks.json +27 -0
- package/package.json +5 -2
- package/skills/gauntlet-commit/SKILL.md +7 -3
package/dist/index.js
CHANGED
|
@@ -299,7 +299,7 @@ import { Command } from "commander";
|
|
|
299
299
|
// package.json
|
|
300
300
|
var package_default = {
|
|
301
301
|
name: "agent-gauntlet",
|
|
302
|
-
version: "1.
|
|
302
|
+
version: "1.3.0",
|
|
303
303
|
description: "A CLI tool for testing AI coding agents",
|
|
304
304
|
license: "MIT",
|
|
305
305
|
author: "Paul Caplan",
|
|
@@ -321,6 +321,9 @@ var package_default = {
|
|
|
321
321
|
files: [
|
|
322
322
|
"dist",
|
|
323
323
|
"skills",
|
|
324
|
+
".claude-plugin",
|
|
325
|
+
".cursor-plugin",
|
|
326
|
+
"hooks",
|
|
324
327
|
"README.md",
|
|
325
328
|
"LICENSE"
|
|
326
329
|
],
|
|
@@ -1233,62 +1236,61 @@ ${stderr}`);
|
|
|
1233
1236
|
jobId,
|
|
1234
1237
|
status: "pass",
|
|
1235
1238
|
duration: Date.now() - startTime,
|
|
1236
|
-
message: "Command exited with code 0"
|
|
1239
|
+
message: "Command exited with code 0",
|
|
1240
|
+
command,
|
|
1241
|
+
workingDirectory
|
|
1237
1242
|
};
|
|
1238
1243
|
await logger(`Result: ${result.status} - ${result.message}
|
|
1239
1244
|
`);
|
|
1240
1245
|
return result;
|
|
1241
1246
|
} catch (error) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
+
return this.handleExecutionError(error, jobId, command, workingDirectory, config, startTime, logger);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async handleExecutionError(error, jobId, command, workingDirectory, config, startTime, logger) {
|
|
1251
|
+
const err = error;
|
|
1252
|
+
if (err.stdout)
|
|
1253
|
+
await logger(err.stdout);
|
|
1254
|
+
if (err.stderr)
|
|
1255
|
+
await logger(`
|
|
1247
1256
|
STDERR:
|
|
1248
1257
|
${err.stderr}`);
|
|
1249
|
-
|
|
1258
|
+
await logger(`
|
|
1250
1259
|
Command failed: ${err.message}`);
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
jobId,
|
|
1254
|
-
status: "fail",
|
|
1255
|
-
duration: Date.now() - startTime,
|
|
1256
|
-
message: `Timed out after ${config.timeout}s`,
|
|
1257
|
-
fixInstructions: config.fixInstructionsContent,
|
|
1258
|
-
fixWithSkill: config.fixWithSkill
|
|
1259
|
-
};
|
|
1260
|
-
await logger(`Result: ${result2.status} - ${result2.message}
|
|
1260
|
+
const result = this.buildErrorResult(err, jobId, command, workingDirectory, config, startTime);
|
|
1261
|
+
await logger(`Result: ${result.status} - ${result.message}
|
|
1261
1262
|
`);
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1263
|
+
await this.logFixInfo(config, logger);
|
|
1264
|
+
return result;
|
|
1265
|
+
}
|
|
1266
|
+
buildErrorResult(err, jobId, command, workingDirectory, config, startTime) {
|
|
1267
|
+
const base = {
|
|
1268
|
+
jobId,
|
|
1269
|
+
duration: Date.now() - startTime,
|
|
1270
|
+
command,
|
|
1271
|
+
workingDirectory,
|
|
1272
|
+
fixInstructions: config.fixInstructionsContent,
|
|
1273
|
+
fixWithSkill: config.fixWithSkill
|
|
1274
|
+
};
|
|
1275
|
+
if (err.signal === "SIGTERM" && config.timeout) {
|
|
1276
|
+
return {
|
|
1277
|
+
...base,
|
|
1278
|
+
status: "fail",
|
|
1279
|
+
message: `Timed out after ${config.timeout}s`
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof err.code === "number") {
|
|
1283
|
+
return {
|
|
1284
|
+
...base,
|
|
1285
|
+
status: "fail",
|
|
1286
|
+
message: `Exited with code ${err.code}`
|
|
1286
1287
|
};
|
|
1287
|
-
await logger(`Result: ${result.status} - ${result.message}
|
|
1288
|
-
`);
|
|
1289
|
-
await this.logFixInfo(config, logger);
|
|
1290
|
-
return result;
|
|
1291
1288
|
}
|
|
1289
|
+
return {
|
|
1290
|
+
...base,
|
|
1291
|
+
status: "error",
|
|
1292
|
+
message: err.message || "Unknown error"
|
|
1293
|
+
};
|
|
1292
1294
|
}
|
|
1293
1295
|
async logFixInfo(config, logger) {
|
|
1294
1296
|
if (config.fixInstructionsContent) {
|
|
@@ -2694,28 +2696,83 @@ async function handleStopHookAction(ctx) {
|
|
|
2694
2696
|
await executeGauntlet(ctx, projectCwd, logDir, diagnostics, parsed);
|
|
2695
2697
|
}
|
|
2696
2698
|
|
|
2697
|
-
// src/
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
};
|
|
2699
|
+
// src/plugin/claude-cli.ts
|
|
2700
|
+
import { execFileSync } from "node:child_process";
|
|
2701
|
+
function runClaudeCommand(args) {
|
|
2702
|
+
try {
|
|
2703
|
+
const stdout = execFileSync("claude", args, {
|
|
2704
|
+
encoding: "utf-8",
|
|
2705
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2706
|
+
timeout: 60000
|
|
2707
|
+
});
|
|
2708
|
+
return { success: true, stdout };
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
const err = error;
|
|
2711
|
+
let stderr = err.message;
|
|
2712
|
+
if (typeof err.stderr === "string") {
|
|
2713
|
+
stderr = err.stderr;
|
|
2714
|
+
} else if (err.stderr instanceof Buffer) {
|
|
2715
|
+
stderr = err.stderr.toString("utf-8");
|
|
2716
|
+
}
|
|
2717
|
+
return { success: false, stderr: stderr.trim() };
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
function runClaudePluginCommand(args) {
|
|
2721
|
+
const result = runClaudeCommand(args);
|
|
2722
|
+
return {
|
|
2723
|
+
success: result.success,
|
|
2724
|
+
stderr: result.stderr
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
async function addMarketplace() {
|
|
2728
|
+
return runClaudePluginCommand([
|
|
2729
|
+
"plugin",
|
|
2730
|
+
"marketplace",
|
|
2731
|
+
"add",
|
|
2732
|
+
"pcaplan/agent-gauntlet"
|
|
2733
|
+
]);
|
|
2734
|
+
}
|
|
2735
|
+
async function installPlugin(scope) {
|
|
2736
|
+
return runClaudePluginCommand([
|
|
2737
|
+
"plugin",
|
|
2738
|
+
"install",
|
|
2739
|
+
"agent-gauntlet",
|
|
2740
|
+
"--scope",
|
|
2741
|
+
scope
|
|
2742
|
+
]);
|
|
2743
|
+
}
|
|
2744
|
+
async function listPlugins() {
|
|
2745
|
+
const result = runClaudeCommand(["plugin", "list", "--json"]);
|
|
2746
|
+
if (!result.success) {
|
|
2747
|
+
throw new Error(result.stderr ?? "Failed to list Claude plugins");
|
|
2748
|
+
}
|
|
2749
|
+
try {
|
|
2750
|
+
const parsed = JSON.parse(result.stdout ?? "");
|
|
2751
|
+
if (Array.isArray(parsed)) {
|
|
2752
|
+
return parsed;
|
|
2753
|
+
}
|
|
2754
|
+
return parsed?.plugins ?? [];
|
|
2755
|
+
} catch {
|
|
2756
|
+
throw new Error("Failed to parse `claude plugin list --json` output");
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
async function updateMarketplace() {
|
|
2760
|
+
return runClaudePluginCommand([
|
|
2761
|
+
"plugin",
|
|
2762
|
+
"marketplace",
|
|
2763
|
+
"update",
|
|
2764
|
+
"agent-gauntlet"
|
|
2765
|
+
]);
|
|
2766
|
+
}
|
|
2767
|
+
async function updatePlugin() {
|
|
2768
|
+
return runClaudePluginCommand([
|
|
2769
|
+
"plugin",
|
|
2770
|
+
"update",
|
|
2771
|
+
"agent-gauntlet@pcaplan/agent-gauntlet"
|
|
2772
|
+
]);
|
|
2773
|
+
}
|
|
2716
2774
|
|
|
2717
|
-
// src/cli-adapters/claude.ts
|
|
2718
|
-
var execAsync3 = promisify3(exec3);
|
|
2775
|
+
// src/cli-adapters/claude-otel.ts
|
|
2719
2776
|
function countBraceChange(line) {
|
|
2720
2777
|
const stripped = line.replace(/\\./g, "").replace(/"[^"]*"/g, "").replace(/'[^']*'/g, "");
|
|
2721
2778
|
let depth = 0;
|
|
@@ -2914,6 +2971,29 @@ function buildOtelEnv() {
|
|
|
2914
2971
|
}
|
|
2915
2972
|
return env;
|
|
2916
2973
|
}
|
|
2974
|
+
|
|
2975
|
+
// src/cli-adapters/thinking-budget.ts
|
|
2976
|
+
var CLAUDE_THINKING_TOKENS = {
|
|
2977
|
+
off: 0,
|
|
2978
|
+
low: 8000,
|
|
2979
|
+
medium: 16000,
|
|
2980
|
+
high: 31999
|
|
2981
|
+
};
|
|
2982
|
+
var CODEX_REASONING_EFFORT = {
|
|
2983
|
+
off: "minimal",
|
|
2984
|
+
low: "low",
|
|
2985
|
+
medium: "medium",
|
|
2986
|
+
high: "high"
|
|
2987
|
+
};
|
|
2988
|
+
var GEMINI_THINKING_BUDGET = {
|
|
2989
|
+
off: 0,
|
|
2990
|
+
low: 4096,
|
|
2991
|
+
medium: 8192,
|
|
2992
|
+
high: 24576
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
// src/cli-adapters/claude.ts
|
|
2996
|
+
var execAsync3 = promisify3(exec3);
|
|
2917
2997
|
var POST_PROCESS_BUFFER_MS = 30000;
|
|
2918
2998
|
|
|
2919
2999
|
class ClaudeAdapter {
|
|
@@ -2961,6 +3041,44 @@ class ClaudeAdapter {
|
|
|
2961
3041
|
supportsHooks() {
|
|
2962
3042
|
return true;
|
|
2963
3043
|
}
|
|
3044
|
+
async detectPlugin(projectRoot) {
|
|
3045
|
+
try {
|
|
3046
|
+
const entries = await listPlugins();
|
|
3047
|
+
const pluginEntries = entries.filter((entry) => {
|
|
3048
|
+
const e = entry;
|
|
3049
|
+
const name = e.name ?? e.id;
|
|
3050
|
+
return name === "agent-gauntlet" || typeof name === "string" && name.startsWith("agent-gauntlet@");
|
|
3051
|
+
});
|
|
3052
|
+
const resolved = path11.resolve(projectRoot);
|
|
3053
|
+
if (pluginEntries.some((entry) => {
|
|
3054
|
+
const e = entry;
|
|
3055
|
+
return e.scope === "project" && typeof e.projectPath === "string" && path11.resolve(e.projectPath) === resolved;
|
|
3056
|
+
})) {
|
|
3057
|
+
return "project";
|
|
3058
|
+
}
|
|
3059
|
+
if (pluginEntries.some((entry) => entry.scope === "user")) {
|
|
3060
|
+
return "user";
|
|
3061
|
+
}
|
|
3062
|
+
} catch {}
|
|
3063
|
+
return null;
|
|
3064
|
+
}
|
|
3065
|
+
async installPlugin(scope, _projectRoot) {
|
|
3066
|
+
const addResult = await addMarketplace();
|
|
3067
|
+
if (!addResult.success) {
|
|
3068
|
+
return { success: false, error: addResult.stderr };
|
|
3069
|
+
}
|
|
3070
|
+
const installResult = await installPlugin(scope);
|
|
3071
|
+
if (!installResult.success) {
|
|
3072
|
+
return { success: false, error: installResult.stderr };
|
|
3073
|
+
}
|
|
3074
|
+
return { success: true };
|
|
3075
|
+
}
|
|
3076
|
+
getManualInstallInstructions(scope) {
|
|
3077
|
+
return [
|
|
3078
|
+
"claude plugin marketplace add pcaplan/agent-gauntlet",
|
|
3079
|
+
`claude plugin install agent-gauntlet --scope ${scope}`
|
|
3080
|
+
];
|
|
3081
|
+
}
|
|
2964
3082
|
async execute(opts) {
|
|
2965
3083
|
const totalTimeout = (opts.timeoutMs ?? 300000) + POST_PROCESS_BUFFER_MS;
|
|
2966
3084
|
let timer;
|
|
@@ -3152,7 +3270,7 @@ class CodexAdapter {
|
|
|
3152
3270
|
return path12.join(os3.homedir(), ".codex", "prompts");
|
|
3153
3271
|
}
|
|
3154
3272
|
getProjectSkillDir() {
|
|
3155
|
-
return
|
|
3273
|
+
return ".agents/skills";
|
|
3156
3274
|
}
|
|
3157
3275
|
getUserSkillDir() {
|
|
3158
3276
|
return null;
|
|
@@ -3230,9 +3348,11 @@ ${opts.diff}`;
|
|
|
3230
3348
|
|
|
3231
3349
|
// src/cli-adapters/cursor.ts
|
|
3232
3350
|
import { exec as exec5 } from "node:child_process";
|
|
3351
|
+
import { statSync } from "node:fs";
|
|
3233
3352
|
import fs14 from "node:fs/promises";
|
|
3234
3353
|
import os4 from "node:os";
|
|
3235
3354
|
import path13 from "node:path";
|
|
3355
|
+
import { fileURLToPath } from "node:url";
|
|
3236
3356
|
import { promisify as promisify5 } from "node:util";
|
|
3237
3357
|
|
|
3238
3358
|
// src/cli-adapters/model-resolution.ts
|
|
@@ -3311,10 +3431,10 @@ class CursorAdapter {
|
|
|
3311
3431
|
return null;
|
|
3312
3432
|
}
|
|
3313
3433
|
getProjectSkillDir() {
|
|
3314
|
-
return
|
|
3434
|
+
return ".cursor/skills";
|
|
3315
3435
|
}
|
|
3316
3436
|
getUserSkillDir() {
|
|
3317
|
-
return
|
|
3437
|
+
return path13.join(os4.homedir(), ".cursor", "skills");
|
|
3318
3438
|
}
|
|
3319
3439
|
getCommandExtension() {
|
|
3320
3440
|
return ".md";
|
|
@@ -3397,6 +3517,79 @@ ${opts.diff}`;
|
|
|
3397
3517
|
await cleanup();
|
|
3398
3518
|
}
|
|
3399
3519
|
}
|
|
3520
|
+
async detectPlugin(projectRoot) {
|
|
3521
|
+
const projectPluginPath = path13.join(projectRoot, ".cursor", "plugins", "agent-gauntlet", ".cursor-plugin", "plugin.json");
|
|
3522
|
+
try {
|
|
3523
|
+
await fs14.access(projectPluginPath);
|
|
3524
|
+
return "project";
|
|
3525
|
+
} catch {}
|
|
3526
|
+
const userPluginPath = path13.join(os4.homedir(), ".cursor", "plugins", "agent-gauntlet", ".cursor-plugin", "plugin.json");
|
|
3527
|
+
try {
|
|
3528
|
+
await fs14.access(userPluginPath);
|
|
3529
|
+
return "user";
|
|
3530
|
+
} catch {}
|
|
3531
|
+
return null;
|
|
3532
|
+
}
|
|
3533
|
+
async installPlugin(scope, projectRoot) {
|
|
3534
|
+
try {
|
|
3535
|
+
const baseDir = scope === "user" ? path13.join(os4.homedir(), ".cursor", "plugins", "agent-gauntlet") : path13.join(projectRoot ?? ".", ".cursor", "plugins", "agent-gauntlet");
|
|
3536
|
+
const packageRoot = this.findPackageRoot();
|
|
3537
|
+
await this.copyPluginAssets(packageRoot, baseDir);
|
|
3538
|
+
return { success: true };
|
|
3539
|
+
} catch (err) {
|
|
3540
|
+
return {
|
|
3541
|
+
success: false,
|
|
3542
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
async updatePlugin(scope, projectRoot) {
|
|
3547
|
+
return this.installPlugin(scope, projectRoot);
|
|
3548
|
+
}
|
|
3549
|
+
getManualInstallInstructions(scope) {
|
|
3550
|
+
const targetDir = scope === "user" ? "~/.cursor/plugins/agent-gauntlet/" : ".cursor/plugins/agent-gauntlet/";
|
|
3551
|
+
return [
|
|
3552
|
+
`Copy plugin files to ${targetDir}`,
|
|
3553
|
+
"Or install via /add-plugin in Cursor or at the Cursor marketplace"
|
|
3554
|
+
];
|
|
3555
|
+
}
|
|
3556
|
+
findPackageRoot() {
|
|
3557
|
+
const moduleDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
3558
|
+
const bundled = path13.join(moduleDir, "..");
|
|
3559
|
+
const dev = path13.join(moduleDir, "..", "..");
|
|
3560
|
+
try {
|
|
3561
|
+
statSync(path13.join(bundled, ".cursor-plugin", "plugin.json"));
|
|
3562
|
+
return bundled;
|
|
3563
|
+
} catch {
|
|
3564
|
+
return dev;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
async copyPluginAssets(packageRoot, targetDir) {
|
|
3568
|
+
await fs14.mkdir(targetDir, { recursive: true });
|
|
3569
|
+
const pluginSrc = path13.join(packageRoot, ".cursor-plugin");
|
|
3570
|
+
const pluginDest = path13.join(targetDir, ".cursor-plugin");
|
|
3571
|
+
await this.copyDirRecursive(pluginSrc, pluginDest);
|
|
3572
|
+
const skillsSrc = path13.join(packageRoot, "skills");
|
|
3573
|
+
const skillsDest = path13.join(targetDir, "skills");
|
|
3574
|
+
await this.copyDirRecursive(skillsSrc, skillsDest);
|
|
3575
|
+
const hooksSrc = path13.join(packageRoot, "hooks", "cursor-hooks.json");
|
|
3576
|
+
const hooksDest = path13.join(targetDir, "hooks");
|
|
3577
|
+
await fs14.mkdir(hooksDest, { recursive: true });
|
|
3578
|
+
await fs14.copyFile(hooksSrc, path13.join(hooksDest, "hooks.json"));
|
|
3579
|
+
}
|
|
3580
|
+
async copyDirRecursive(src, dest) {
|
|
3581
|
+
await fs14.mkdir(dest, { recursive: true });
|
|
3582
|
+
const entries = await fs14.readdir(src, { withFileTypes: true });
|
|
3583
|
+
for (const entry of entries) {
|
|
3584
|
+
const srcPath = path13.join(src, entry.name);
|
|
3585
|
+
const destPath = path13.join(dest, entry.name);
|
|
3586
|
+
if (entry.isDirectory()) {
|
|
3587
|
+
await this.copyDirRecursive(srcPath, destPath);
|
|
3588
|
+
} else {
|
|
3589
|
+
await fs14.copyFile(srcPath, destPath);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3400
3593
|
}
|
|
3401
3594
|
|
|
3402
3595
|
// src/cli-adapters/gemini.ts
|
|
@@ -7465,7 +7658,7 @@ function registerDetectCommand(program) {
|
|
|
7465
7658
|
const changes = await changeDetector.getChangedFiles();
|
|
7466
7659
|
if (changes.length === 0) {
|
|
7467
7660
|
console.log(chalk6.green("No changes detected."));
|
|
7468
|
-
|
|
7661
|
+
process.exit(2);
|
|
7469
7662
|
}
|
|
7470
7663
|
console.log(chalk6.dim(`Found ${changes.length} changed files:`));
|
|
7471
7664
|
for (const file of changes) {
|
|
@@ -7476,7 +7669,7 @@ function registerDetectCommand(program) {
|
|
|
7476
7669
|
const jobs = jobGen.generateJobs(entryPoints);
|
|
7477
7670
|
if (jobs.length === 0) {
|
|
7478
7671
|
console.log(chalk6.yellow("No applicable gates for these changes."));
|
|
7479
|
-
|
|
7672
|
+
process.exit(2);
|
|
7480
7673
|
}
|
|
7481
7674
|
console.log(chalk6.bold(`Would run ${jobs.length} gate(s):
|
|
7482
7675
|
`));
|
|
@@ -8201,12 +8394,12 @@ function registerHelpCommand(program) {
|
|
|
8201
8394
|
});
|
|
8202
8395
|
}
|
|
8203
8396
|
// src/commands/init.ts
|
|
8204
|
-
import { execFileSync } from "node:child_process";
|
|
8205
|
-
import { statSync } from "node:fs";
|
|
8397
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
8398
|
+
import { statSync as statSync3 } from "node:fs";
|
|
8206
8399
|
import fs32 from "node:fs/promises";
|
|
8207
|
-
import
|
|
8208
|
-
import { fileURLToPath } from "node:url";
|
|
8209
|
-
import
|
|
8400
|
+
import path31 from "node:path";
|
|
8401
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8402
|
+
import chalk12 from "chalk";
|
|
8210
8403
|
|
|
8211
8404
|
// src/commands/init-checksums.ts
|
|
8212
8405
|
import { createHash } from "node:crypto";
|
|
@@ -8222,25 +8415,6 @@ async function computeSkillChecksum(skillDir) {
|
|
|
8222
8415
|
}
|
|
8223
8416
|
return hash.digest("hex");
|
|
8224
8417
|
}
|
|
8225
|
-
function computeHookChecksum(entries) {
|
|
8226
|
-
const gauntletEntries = entries.filter((entry) => isGauntletHookEntry(entry));
|
|
8227
|
-
const hash = createHash("sha256");
|
|
8228
|
-
hash.update(JSON.stringify(gauntletEntries));
|
|
8229
|
-
return hash.digest("hex");
|
|
8230
|
-
}
|
|
8231
|
-
function computeExpectedHookChecksum(hookEntries) {
|
|
8232
|
-
return computeHookChecksum(hookEntries);
|
|
8233
|
-
}
|
|
8234
|
-
function isGauntletHookEntry(entry) {
|
|
8235
|
-
if (typeof entry.command === "string" && entry.command.startsWith("agent-gauntlet")) {
|
|
8236
|
-
return true;
|
|
8237
|
-
}
|
|
8238
|
-
const nested = entry.hooks;
|
|
8239
|
-
if (Array.isArray(nested)) {
|
|
8240
|
-
return nested.some((h) => typeof h.command === "string" && h.command.startsWith("agent-gauntlet"));
|
|
8241
|
-
}
|
|
8242
|
-
return false;
|
|
8243
|
-
}
|
|
8244
8418
|
async function collectFiles(dir, baseDir) {
|
|
8245
8419
|
const base = baseDir ?? dir;
|
|
8246
8420
|
const results = [];
|
|
@@ -8257,19 +8431,44 @@ async function collectFiles(dir, baseDir) {
|
|
|
8257
8431
|
return results;
|
|
8258
8432
|
}
|
|
8259
8433
|
|
|
8260
|
-
// src/commands/init-
|
|
8261
|
-
import
|
|
8434
|
+
// src/commands/init-plugin.ts
|
|
8435
|
+
import os8 from "node:os";
|
|
8262
8436
|
import path29 from "node:path";
|
|
8263
|
-
import
|
|
8437
|
+
import chalk9 from "chalk";
|
|
8438
|
+
function getCodexSkillsBaseDir(scope) {
|
|
8439
|
+
if (scope === "project") {
|
|
8440
|
+
return path29.join(".agents", "skills");
|
|
8441
|
+
}
|
|
8442
|
+
const homeDir = process.env.HOME?.trim() || os8.homedir();
|
|
8443
|
+
return path29.join(homeDir, ".agents", "skills");
|
|
8444
|
+
}
|
|
8445
|
+
async function installAdapterPlugin(adapter, projectRoot, installScope) {
|
|
8446
|
+
if (!adapter.installPlugin)
|
|
8447
|
+
return;
|
|
8448
|
+
const result = await adapter.installPlugin(installScope, projectRoot);
|
|
8449
|
+
if (!result.success) {
|
|
8450
|
+
console.warn(chalk9.yellow(`${adapter.name} plugin installation failed. Continuing init.`));
|
|
8451
|
+
if (result.error) {
|
|
8452
|
+
console.warn(chalk9.yellow(result.error.trim()));
|
|
8453
|
+
}
|
|
8454
|
+
const instructions = adapter.getManualInstallInstructions?.(installScope);
|
|
8455
|
+
if (instructions) {
|
|
8456
|
+
console.warn("Run these steps manually:");
|
|
8457
|
+
for (const step of instructions) {
|
|
8458
|
+
console.warn(` ${step}`);
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8264
8463
|
|
|
8265
8464
|
// src/commands/init-prompts.ts
|
|
8266
|
-
import { checkbox, confirm, number } from "@inquirer/prompts";
|
|
8267
|
-
import
|
|
8465
|
+
import { checkbox, confirm, number, select } from "@inquirer/prompts";
|
|
8466
|
+
import chalk10 from "chalk";
|
|
8268
8467
|
async function promptDevCLIs(detectedNames, skipPrompts) {
|
|
8269
8468
|
if (skipPrompts)
|
|
8270
8469
|
return detectedNames;
|
|
8271
8470
|
console.log();
|
|
8272
|
-
console.log(
|
|
8471
|
+
console.log(chalk10.bold("Select your development CLI(s). These are the main tools you work in."));
|
|
8273
8472
|
const selected = await checkbox({
|
|
8274
8473
|
message: "Development CLIs:",
|
|
8275
8474
|
choices: detectedNames.map((name) => ({ name, value: name })),
|
|
@@ -8281,7 +8480,7 @@ async function promptReviewCLIs(detectedNames, skipPrompts) {
|
|
|
8281
8480
|
if (skipPrompts)
|
|
8282
8481
|
return detectedNames;
|
|
8283
8482
|
console.log();
|
|
8284
|
-
console.log(
|
|
8483
|
+
console.log(chalk10.bold("Select your reviewer CLI(s). These are the CLIs that will be used for AI code reviews."));
|
|
8285
8484
|
const selected = await checkbox({
|
|
8286
8485
|
message: "Review CLIs:",
|
|
8287
8486
|
choices: detectedNames.map((name) => ({ name, value: name })),
|
|
@@ -8289,6 +8488,18 @@ async function promptReviewCLIs(detectedNames, skipPrompts) {
|
|
|
8289
8488
|
});
|
|
8290
8489
|
return selected;
|
|
8291
8490
|
}
|
|
8491
|
+
async function promptInstallScope(skipPrompts) {
|
|
8492
|
+
if (skipPrompts)
|
|
8493
|
+
return "project";
|
|
8494
|
+
console.log();
|
|
8495
|
+
return select({
|
|
8496
|
+
message: "Install scope for Claude plugin and Codex skills:",
|
|
8497
|
+
choices: [
|
|
8498
|
+
{ name: "Local (project)", value: "project" },
|
|
8499
|
+
{ name: "Global (user)", value: "user" }
|
|
8500
|
+
]
|
|
8501
|
+
});
|
|
8502
|
+
}
|
|
8292
8503
|
async function promptNumReviews(reviewCliCount, skipPrompts) {
|
|
8293
8504
|
if (reviewCliCount === 1)
|
|
8294
8505
|
return 1;
|
|
@@ -8304,212 +8515,200 @@ async function promptNumReviews(reviewCliCount, skipPrompts) {
|
|
|
8304
8515
|
}
|
|
8305
8516
|
async function promptFileOverwrite(name, skipPrompts) {
|
|
8306
8517
|
if (skipPrompts)
|
|
8307
|
-
return
|
|
8308
|
-
return
|
|
8518
|
+
return "yes";
|
|
8519
|
+
return select({
|
|
8309
8520
|
message: `Skill \`${name}\` has changed, update it?`,
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
}
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
return true;
|
|
8316
|
-
return confirm({
|
|
8317
|
-
message: `Hook configuration in ${hookFile} has changed, update it?`,
|
|
8318
|
-
default: true
|
|
8521
|
+
choices: [
|
|
8522
|
+
{ name: "Yes", value: "yes" },
|
|
8523
|
+
{ name: "No", value: "no" },
|
|
8524
|
+
{ name: "Yes to all remaining", value: "all" }
|
|
8525
|
+
]
|
|
8319
8526
|
});
|
|
8320
8527
|
}
|
|
8321
8528
|
|
|
8322
|
-
// src/commands/
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8529
|
+
// src/commands/plugin-update.ts
|
|
8530
|
+
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
8531
|
+
import fs31 from "node:fs/promises";
|
|
8532
|
+
import os9 from "node:os";
|
|
8533
|
+
import path30 from "node:path";
|
|
8534
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8535
|
+
import chalk11 from "chalk";
|
|
8536
|
+
var __dirname2 = path30.dirname(fileURLToPath2(import.meta.url));
|
|
8537
|
+
var SKILLS_SOURCE_DIR = (() => {
|
|
8538
|
+
const bundled = path30.join(__dirname2, "..", "skills");
|
|
8539
|
+
const dev = path30.join(__dirname2, "..", "..", "skills");
|
|
8540
|
+
try {
|
|
8541
|
+
statSync2(bundled);
|
|
8542
|
+
return bundled;
|
|
8543
|
+
} catch (err) {
|
|
8544
|
+
const code = err.code;
|
|
8545
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
8546
|
+
return dev;
|
|
8547
|
+
throw err;
|
|
8548
|
+
}
|
|
8549
|
+
})();
|
|
8550
|
+
async function getSkillDirNames() {
|
|
8551
|
+
const entries = await fs31.readdir(SKILLS_SOURCE_DIR, { withFileTypes: true });
|
|
8552
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
8330
8553
|
}
|
|
8331
|
-
async function
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
let existing = {};
|
|
8342
|
-
if (await exists(filePath)) {
|
|
8343
|
-
try {
|
|
8344
|
-
existing = JSON.parse(await fs31.readFile(filePath, "utf-8"));
|
|
8345
|
-
} catch {
|
|
8346
|
-
existing = {};
|
|
8554
|
+
async function copyDirRecursive(opts) {
|
|
8555
|
+
await fs31.mkdir(opts.dest, { recursive: true });
|
|
8556
|
+
const entries = await fs31.readdir(opts.src, { withFileTypes: true });
|
|
8557
|
+
for (const entry of entries) {
|
|
8558
|
+
const srcPath = path30.join(opts.src, entry.name);
|
|
8559
|
+
const destPath = path30.join(opts.dest, entry.name);
|
|
8560
|
+
if (entry.isDirectory()) {
|
|
8561
|
+
await copyDirRecursive({ src: srcPath, dest: destPath });
|
|
8562
|
+
} else {
|
|
8563
|
+
await fs31.copyFile(srcPath, destPath);
|
|
8347
8564
|
}
|
|
8348
8565
|
}
|
|
8349
|
-
const existingHooks = existing.hooks || {};
|
|
8350
|
-
const existingEntries = Array.isArray(existingHooks[hookKey]) ? existingHooks[hookKey] : [];
|
|
8351
|
-
if (hookHasCommand(existingEntries, deduplicateCmd)) {
|
|
8352
|
-
return false;
|
|
8353
|
-
}
|
|
8354
|
-
const entryToAdd = wrapInHooksArray ? { hooks: [hookEntry] } : hookEntry;
|
|
8355
|
-
const newEntries = [...existingEntries, entryToAdd];
|
|
8356
|
-
const merged = {
|
|
8357
|
-
...baseConfig ?? {},
|
|
8358
|
-
...existing,
|
|
8359
|
-
hooks: { ...existingHooks, [hookKey]: newEntries }
|
|
8360
|
-
};
|
|
8361
|
-
await fs31.writeFile(filePath, `${JSON.stringify(merged, null, 2)}
|
|
8362
|
-
`);
|
|
8363
|
-
return true;
|
|
8364
8566
|
}
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
};
|
|
8371
|
-
var CURSOR_START_HOOK_ENTRY = {
|
|
8372
|
-
command: "agent-gauntlet start-hook --adapter cursor"
|
|
8373
|
-
};
|
|
8374
|
-
var STOP_HOOK_ENTRY = {
|
|
8375
|
-
type: "command",
|
|
8376
|
-
command: "agent-gauntlet stop-hook",
|
|
8377
|
-
timeout: 300
|
|
8378
|
-
};
|
|
8379
|
-
var CURSOR_STOP_HOOK_ENTRY = {
|
|
8380
|
-
command: "agent-gauntlet stop-hook",
|
|
8381
|
-
loop_limit: 10
|
|
8382
|
-
};
|
|
8383
|
-
async function installHookWithLog(config, installedMsg, existsMsg) {
|
|
8384
|
-
const added = await mergeHookConfig(config);
|
|
8385
|
-
console.log(added ? chalk10.green(installedMsg) : chalk10.dim(existsMsg));
|
|
8386
|
-
}
|
|
8387
|
-
function buildHookSpec(target) {
|
|
8388
|
-
const { projectRoot, variant, kind } = target;
|
|
8389
|
-
const isCursor = variant === "cursor";
|
|
8390
|
-
const isStop = kind === "stop";
|
|
8391
|
-
const hookConfigs = {
|
|
8392
|
-
"claude-stop": {
|
|
8393
|
-
dir: ".claude",
|
|
8394
|
-
file: "settings.local.json",
|
|
8395
|
-
hookKey: "Stop",
|
|
8396
|
-
entry: STOP_HOOK_ENTRY,
|
|
8397
|
-
cmd: "agent-gauntlet stop-hook",
|
|
8398
|
-
wrap: true
|
|
8399
|
-
},
|
|
8400
|
-
"cursor-stop": {
|
|
8401
|
-
dir: ".cursor",
|
|
8402
|
-
file: "hooks.json",
|
|
8403
|
-
hookKey: "stop",
|
|
8404
|
-
entry: CURSOR_STOP_HOOK_ENTRY,
|
|
8405
|
-
cmd: "agent-gauntlet stop-hook",
|
|
8406
|
-
wrap: false
|
|
8407
|
-
},
|
|
8408
|
-
"claude-start": {
|
|
8409
|
-
dir: ".claude",
|
|
8410
|
-
file: "settings.local.json",
|
|
8411
|
-
hookKey: "SessionStart",
|
|
8412
|
-
entry: START_HOOK_ENTRY,
|
|
8413
|
-
cmd: "agent-gauntlet start-hook",
|
|
8414
|
-
wrap: false
|
|
8415
|
-
},
|
|
8416
|
-
"cursor-start": {
|
|
8417
|
-
dir: ".cursor",
|
|
8418
|
-
file: "hooks.json",
|
|
8419
|
-
hookKey: "sessionStart",
|
|
8420
|
-
entry: CURSOR_START_HOOK_ENTRY,
|
|
8421
|
-
cmd: "agent-gauntlet start-hook --adapter cursor",
|
|
8422
|
-
wrap: false
|
|
8423
|
-
}
|
|
8424
|
-
};
|
|
8425
|
-
const key = `${variant}-${kind}`;
|
|
8426
|
-
const cfg = hookConfigs[key];
|
|
8427
|
-
const prefix = isCursor ? "Cursor " : "";
|
|
8428
|
-
let kindLabel;
|
|
8429
|
-
if (isCursor)
|
|
8430
|
-
kindLabel = kind;
|
|
8431
|
-
else if (isStop)
|
|
8432
|
-
kindLabel = "Stop";
|
|
8433
|
-
else
|
|
8434
|
-
kindLabel = "Start";
|
|
8435
|
-
const purpose = isStop ? "gauntlet will run automatically when agent stops" : "agent will be primed with gauntlet instructions at session start";
|
|
8436
|
-
return {
|
|
8437
|
-
config: {
|
|
8438
|
-
filePath: path29.join(projectRoot, cfg.dir, cfg.file),
|
|
8439
|
-
hookKey: cfg.hookKey,
|
|
8440
|
-
hookEntry: cfg.entry,
|
|
8441
|
-
deduplicateCmd: cfg.cmd,
|
|
8442
|
-
wrapInHooksArray: cfg.wrap,
|
|
8443
|
-
...isCursor ? { baseConfig: { version: 1 } } : {}
|
|
8444
|
-
},
|
|
8445
|
-
installedMsg: `${prefix}${kindLabel} hook installed - ${purpose}`,
|
|
8446
|
-
existsMsg: `${prefix}${kindLabel} hook already installed`
|
|
8447
|
-
};
|
|
8567
|
+
function isInProjectScope(cwd, projectPath) {
|
|
8568
|
+
const absoluteCwd = path30.resolve(cwd);
|
|
8569
|
+
const absoluteProjectPath = path30.resolve(projectPath);
|
|
8570
|
+
const normalizedCwd = normalizePathForMatch(absoluteCwd);
|
|
8571
|
+
const normalizedProjectPath = normalizePathForMatch(absoluteProjectPath);
|
|
8572
|
+
return normalizedCwd === normalizedProjectPath || normalizedCwd.startsWith(`${normalizedProjectPath}${path30.sep}`);
|
|
8448
8573
|
}
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
existingConfig = JSON.parse(await fs31.readFile(spec.config.filePath, "utf-8"));
|
|
8455
|
-
} catch {
|
|
8456
|
-
existingConfig = {};
|
|
8457
|
-
}
|
|
8574
|
+
function normalizePathForMatch(inputPath) {
|
|
8575
|
+
try {
|
|
8576
|
+
return realpathSync.native(inputPath);
|
|
8577
|
+
} catch {
|
|
8578
|
+
return inputPath;
|
|
8458
8579
|
}
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
const
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8580
|
+
}
|
|
8581
|
+
function detectInstalledScope(entries, cwd) {
|
|
8582
|
+
const pluginEntries = entries.filter((entry) => {
|
|
8583
|
+
const name = entry.name ?? entry.id;
|
|
8584
|
+
return name === "agent-gauntlet" || typeof name === "string" && name.startsWith("agent-gauntlet@");
|
|
8585
|
+
});
|
|
8586
|
+
const projectEntries = pluginEntries.filter((entry) => entry.scope === "project" && typeof entry.projectPath === "string" && isInProjectScope(cwd, entry.projectPath));
|
|
8587
|
+
if (projectEntries.length > 0) {
|
|
8588
|
+
return "project";
|
|
8465
8589
|
}
|
|
8466
|
-
const
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
]);
|
|
8470
|
-
const actualChecksum = computeHookChecksum(existingEntries);
|
|
8471
|
-
if (expectedChecksum === actualChecksum) {
|
|
8472
|
-
console.log(chalk10.dim(spec.existsMsg));
|
|
8473
|
-
return;
|
|
8590
|
+
const hasUserInstall = pluginEntries.some((entry) => entry.scope === "user");
|
|
8591
|
+
if (hasUserInstall) {
|
|
8592
|
+
return "user";
|
|
8474
8593
|
}
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8594
|
+
return null;
|
|
8595
|
+
}
|
|
8596
|
+
function printManualUpdateInstructions() {
|
|
8597
|
+
console.error("Run these commands manually:");
|
|
8598
|
+
console.error(" claude plugin marketplace update agent-gauntlet");
|
|
8599
|
+
console.error(" claude plugin update agent-gauntlet@pcaplan/agent-gauntlet");
|
|
8600
|
+
}
|
|
8601
|
+
async function refreshCodexSkills(cwd) {
|
|
8602
|
+
const localBase = path30.join(cwd, ".agents", "skills");
|
|
8603
|
+
const localMarker = path30.join(localBase, "gauntlet-run");
|
|
8604
|
+
const homeDir = process.env.HOME?.trim() || os9.homedir();
|
|
8605
|
+
const globalBase = path30.join(homeDir, ".agents", "skills");
|
|
8606
|
+
const globalMarker = path30.join(globalBase, "gauntlet-run");
|
|
8607
|
+
let targetBase = null;
|
|
8608
|
+
if (await exists(localMarker)) {
|
|
8609
|
+
targetBase = localBase;
|
|
8610
|
+
} else if (await exists(globalMarker)) {
|
|
8611
|
+
targetBase = globalBase;
|
|
8612
|
+
}
|
|
8613
|
+
if (!targetBase) {
|
|
8478
8614
|
return;
|
|
8479
8615
|
}
|
|
8480
|
-
const
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
...existingConfig,
|
|
8486
|
-
hooks: { ...existingHooks, [spec.config.hookKey]: newEntries }
|
|
8487
|
-
};
|
|
8488
|
-
await fs31.mkdir(path29.dirname(spec.config.filePath), { recursive: true });
|
|
8489
|
-
await fs31.writeFile(spec.config.filePath, `${JSON.stringify(merged, null, 2)}
|
|
8490
|
-
`);
|
|
8491
|
-
console.log(chalk10.green(spec.installedMsg));
|
|
8492
|
-
}
|
|
8493
|
-
async function installHooksForAdapters(projectRoot, devAdapters, skipPrompts) {
|
|
8494
|
-
for (const adapter of devAdapters) {
|
|
8495
|
-
if (!adapter.supportsHooks())
|
|
8616
|
+
for (const dirName of await getSkillDirNames()) {
|
|
8617
|
+
const sourceDir = path30.join(SKILLS_SOURCE_DIR, dirName);
|
|
8618
|
+
const targetDir = path30.join(targetBase, dirName);
|
|
8619
|
+
if (!await exists(targetDir)) {
|
|
8620
|
+
await copyDirRecursive({ src: sourceDir, dest: targetDir });
|
|
8496
8621
|
continue;
|
|
8497
|
-
|
|
8622
|
+
}
|
|
8623
|
+
const sourceChecksum = await computeSkillChecksum(sourceDir);
|
|
8624
|
+
const targetChecksum = await computeSkillChecksum(targetDir);
|
|
8625
|
+
if (sourceChecksum === targetChecksum) {
|
|
8498
8626
|
continue;
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8627
|
+
}
|
|
8628
|
+
await fs31.rm(targetDir, { recursive: true, force: true });
|
|
8629
|
+
await copyDirRecursive({ src: sourceDir, dest: targetDir });
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
function isCliUnavailableError(err) {
|
|
8633
|
+
if (err != null && typeof err === "object") {
|
|
8634
|
+
const code = err.code;
|
|
8635
|
+
if (code === "ENOENT") {
|
|
8636
|
+
return true;
|
|
8502
8637
|
}
|
|
8503
8638
|
}
|
|
8639
|
+
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
8640
|
+
return msg.includes("command not found") || msg.includes("not installed");
|
|
8641
|
+
}
|
|
8642
|
+
async function detectClaudeScope(cwd) {
|
|
8643
|
+
try {
|
|
8644
|
+
const parsedPlugins = await listPlugins();
|
|
8645
|
+
return detectInstalledScope(parsedPlugins, cwd);
|
|
8646
|
+
} catch (error) {
|
|
8647
|
+
if (isCliUnavailableError(error)) {
|
|
8648
|
+
return null;
|
|
8649
|
+
}
|
|
8650
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8651
|
+
throw new Error(`Failed to detect Claude plugin installation: ${message}`);
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8654
|
+
async function updateClaudePlugin(scope) {
|
|
8655
|
+
console.log(chalk11.cyan(`Updating agent-gauntlet Claude plugin (${scope} scope)...`));
|
|
8656
|
+
const marketplaceResult = await updateMarketplace();
|
|
8657
|
+
if (!marketplaceResult.success) {
|
|
8658
|
+
console.error(chalk11.red("Plugin update failed."));
|
|
8659
|
+
if (marketplaceResult.stderr) {
|
|
8660
|
+
console.error(chalk11.red(marketplaceResult.stderr.trim()));
|
|
8661
|
+
}
|
|
8662
|
+
printManualUpdateInstructions();
|
|
8663
|
+
throw new Error(marketplaceResult.stderr ?? "Failed to update plugin marketplace entry");
|
|
8664
|
+
}
|
|
8665
|
+
const pluginResult = await updatePlugin();
|
|
8666
|
+
if (!pluginResult.success) {
|
|
8667
|
+
console.error(chalk11.red("Plugin update failed."));
|
|
8668
|
+
if (pluginResult.stderr) {
|
|
8669
|
+
console.error(chalk11.red(pluginResult.stderr.trim()));
|
|
8670
|
+
}
|
|
8671
|
+
printManualUpdateInstructions();
|
|
8672
|
+
throw new Error(pluginResult.stderr ?? "Failed to update plugin");
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
async function updateCursorPlugin(adapter, scope, cwd) {
|
|
8676
|
+
console.log(chalk11.cyan(`Updating agent-gauntlet Cursor plugin (${scope} scope)...`));
|
|
8677
|
+
const cursorResult = await adapter.updatePlugin(scope, cwd);
|
|
8678
|
+
if (cursorResult.success) {
|
|
8679
|
+
console.log(chalk11.green("Cursor plugin updated successfully."));
|
|
8680
|
+
console.log(chalk11.yellow("Restart any open Cursor sessions to use the updated plugin."));
|
|
8681
|
+
} else {
|
|
8682
|
+
console.error(chalk11.yellow(`Cursor plugin update failed: ${cursorResult.error ?? "unknown error"}`));
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
async function runPluginUpdate(options) {
|
|
8686
|
+
options?.skipPrompts;
|
|
8687
|
+
const cwd = process.cwd();
|
|
8688
|
+
const claudeScope = await detectClaudeScope(cwd);
|
|
8689
|
+
const cursorAdapter = new CursorAdapter;
|
|
8690
|
+
const cursorScope = await cursorAdapter.detectPlugin(cwd);
|
|
8691
|
+
if (!(claudeScope || cursorScope)) {
|
|
8692
|
+
throw new Error("No agent-gauntlet plugin is installed for this project. Please run `agent-gauntlet init` first.");
|
|
8693
|
+
}
|
|
8694
|
+
if (claudeScope) {
|
|
8695
|
+
await updateClaudePlugin(claudeScope);
|
|
8696
|
+
}
|
|
8697
|
+
if (cursorScope) {
|
|
8698
|
+
await updateCursorPlugin(cursorAdapter, cursorScope, cwd);
|
|
8699
|
+
}
|
|
8700
|
+
await refreshCodexSkills(cwd);
|
|
8701
|
+
console.log(chalk11.green("Plugin update completed successfully."));
|
|
8702
|
+
console.log(chalk11.yellow("Restart any open agent sessions to use the updated plugin."));
|
|
8504
8703
|
}
|
|
8505
8704
|
|
|
8506
8705
|
// src/commands/init.ts
|
|
8507
|
-
var
|
|
8508
|
-
var
|
|
8509
|
-
const bundled =
|
|
8510
|
-
const dev =
|
|
8706
|
+
var __dirname3 = path31.dirname(fileURLToPath3(import.meta.url));
|
|
8707
|
+
var SKILLS_SOURCE_DIR2 = (() => {
|
|
8708
|
+
const bundled = path31.join(__dirname3, "..", "skills");
|
|
8709
|
+
const dev = path31.join(__dirname3, "..", "..", "skills");
|
|
8511
8710
|
try {
|
|
8512
|
-
|
|
8711
|
+
statSync3(bundled);
|
|
8513
8712
|
return bundled;
|
|
8514
8713
|
} catch (err) {
|
|
8515
8714
|
const code = err.code;
|
|
@@ -8518,8 +8717,8 @@ var SKILLS_SOURCE_DIR = (() => {
|
|
|
8518
8717
|
throw err;
|
|
8519
8718
|
}
|
|
8520
8719
|
})();
|
|
8521
|
-
async function
|
|
8522
|
-
const entries = await fs32.readdir(
|
|
8720
|
+
async function getSkillDirNames2() {
|
|
8721
|
+
const entries = await fs32.readdir(SKILLS_SOURCE_DIR2, { withFileTypes: true });
|
|
8523
8722
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
8524
8723
|
}
|
|
8525
8724
|
var CLI_PREFERENCE_ORDER = [
|
|
@@ -8546,9 +8745,20 @@ function registerInitCommand(program) {
|
|
|
8546
8745
|
await runInit(options);
|
|
8547
8746
|
});
|
|
8548
8747
|
}
|
|
8748
|
+
async function handleRerun(projectRoot, availableAdapters, skipPrompts) {
|
|
8749
|
+
try {
|
|
8750
|
+
await runPluginUpdate({ skipPrompts });
|
|
8751
|
+
} catch (error) {
|
|
8752
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8753
|
+
if (!message.includes("Claude plugin is not installed"))
|
|
8754
|
+
throw error;
|
|
8755
|
+
console.log(chalk12.yellow("Plugin not installed yet, running fresh install..."));
|
|
8756
|
+
await installExternalFiles(projectRoot, availableAdapters, skipPrompts);
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8549
8759
|
async function runInit(options) {
|
|
8550
8760
|
const projectRoot = process.cwd();
|
|
8551
|
-
const targetDir =
|
|
8761
|
+
const targetDir = path31.join(projectRoot, ".gauntlet");
|
|
8552
8762
|
const skipPrompts = options.yes ?? false;
|
|
8553
8763
|
console.log("Detecting available CLI agents...");
|
|
8554
8764
|
const availableAdapters = await detectAvailableCLIs();
|
|
@@ -8558,33 +8768,33 @@ async function runInit(options) {
|
|
|
8558
8768
|
}
|
|
8559
8769
|
const detectedNames = availableAdapters.map((a) => a.name);
|
|
8560
8770
|
const gauntletExists = await exists(targetDir);
|
|
8561
|
-
let
|
|
8771
|
+
let devAdapters;
|
|
8562
8772
|
let instructionCLINames;
|
|
8563
8773
|
if (gauntletExists) {
|
|
8564
|
-
console.log(
|
|
8565
|
-
hookAdapters = availableAdapters;
|
|
8774
|
+
console.log(chalk12.dim(".gauntlet/ already exists, skipping scaffolding"));
|
|
8566
8775
|
instructionCLINames = detectedNames;
|
|
8776
|
+
await handleRerun(projectRoot, availableAdapters, skipPrompts);
|
|
8567
8777
|
} else {
|
|
8568
8778
|
const devCLINames = await promptDevCLIs(detectedNames, skipPrompts);
|
|
8569
|
-
|
|
8570
|
-
for (const adapter of
|
|
8779
|
+
devAdapters = availableAdapters.filter((a) => devCLINames.includes(a.name));
|
|
8780
|
+
for (const adapter of devAdapters) {
|
|
8571
8781
|
if (!adapter.supportsHooks()) {
|
|
8572
|
-
console.log(
|
|
8782
|
+
console.log(chalk12.yellow(` ${adapter.name} doesn't support hooks yet, skipping hook installation`));
|
|
8573
8783
|
}
|
|
8574
8784
|
}
|
|
8575
8785
|
const reviewCLINames = await promptReviewCLIs(detectedNames, skipPrompts);
|
|
8576
8786
|
const numReviews = await promptNumReviews(reviewCLINames.length, skipPrompts);
|
|
8577
|
-
console.log(
|
|
8787
|
+
console.log(chalk12.cyan("Agent Gauntlet's built-in code quality reviewer will be installed."));
|
|
8578
8788
|
await scaffoldGauntletDir(projectRoot, targetDir, reviewCLINames, numReviews);
|
|
8579
8789
|
instructionCLINames = devCLINames;
|
|
8790
|
+
await installExternalFiles(projectRoot, devAdapters, skipPrompts);
|
|
8580
8791
|
}
|
|
8581
|
-
await installExternalFiles(projectRoot, hookAdapters, skipPrompts);
|
|
8582
8792
|
await addToGitignore(projectRoot, "gauntlet_logs");
|
|
8583
8793
|
await printPostInitInstructions(instructionCLINames);
|
|
8584
8794
|
}
|
|
8585
8795
|
function printNoCLIsMessage() {
|
|
8586
8796
|
console.log();
|
|
8587
|
-
console.log(
|
|
8797
|
+
console.log(chalk12.red("Error: No CLI agents found. Install at least one:"));
|
|
8588
8798
|
console.log(" - Claude: https://docs.anthropic.com/en/docs/claude-code");
|
|
8589
8799
|
console.log(" - Gemini: https://github.com/google-gemini/gemini-cli");
|
|
8590
8800
|
console.log(" - Codex: https://github.com/openai/codex");
|
|
@@ -8592,71 +8802,127 @@ function printNoCLIsMessage() {
|
|
|
8592
8802
|
}
|
|
8593
8803
|
async function scaffoldGauntletDir(_projectRoot, targetDir, reviewCLINames, numReviews) {
|
|
8594
8804
|
if (await exists(targetDir)) {
|
|
8595
|
-
console.log(
|
|
8805
|
+
console.log(chalk12.dim(".gauntlet/ already exists, skipping scaffolding"));
|
|
8596
8806
|
return;
|
|
8597
8807
|
}
|
|
8598
8808
|
await fs32.mkdir(targetDir);
|
|
8599
|
-
await fs32.mkdir(
|
|
8600
|
-
await fs32.mkdir(
|
|
8809
|
+
await fs32.mkdir(path31.join(targetDir, "checks"));
|
|
8810
|
+
await fs32.mkdir(path31.join(targetDir, "reviews"));
|
|
8601
8811
|
await writeConfigYml(targetDir, reviewCLINames);
|
|
8602
|
-
await fs32.writeFile(
|
|
8812
|
+
await fs32.writeFile(path31.join(targetDir, "reviews", "code-quality.yml"), `builtin: code-quality
|
|
8603
8813
|
num_reviews: ${numReviews}
|
|
8604
8814
|
`);
|
|
8605
|
-
console.log(
|
|
8815
|
+
console.log(chalk12.green("Created .gauntlet/reviews/code-quality.yml"));
|
|
8606
8816
|
}
|
|
8607
|
-
async function
|
|
8817
|
+
async function copyDirRecursive2(opts) {
|
|
8608
8818
|
await fs32.mkdir(opts.dest, { recursive: true });
|
|
8609
8819
|
const entries = await fs32.readdir(opts.src, { withFileTypes: true });
|
|
8610
8820
|
for (const entry of entries) {
|
|
8611
|
-
const srcPath =
|
|
8612
|
-
const destPath =
|
|
8821
|
+
const srcPath = path31.join(opts.src, entry.name);
|
|
8822
|
+
const destPath = path31.join(opts.dest, entry.name);
|
|
8613
8823
|
if (entry.isDirectory()) {
|
|
8614
|
-
await
|
|
8824
|
+
await copyDirRecursive2({ src: srcPath, dest: destPath });
|
|
8615
8825
|
} else {
|
|
8616
8826
|
await fs32.copyFile(srcPath, destPath);
|
|
8617
8827
|
}
|
|
8618
8828
|
}
|
|
8619
8829
|
}
|
|
8620
|
-
async function installSkillsWithChecksums(projectRoot, skipPrompts) {
|
|
8621
|
-
const skillsDir =
|
|
8622
|
-
for (const dirName of await
|
|
8623
|
-
const sourceDir =
|
|
8624
|
-
const targetDir =
|
|
8625
|
-
const relativeDir = `${
|
|
8830
|
+
async function installSkillsWithChecksums(projectRoot, targetBaseDir, skipPrompts, updateAllState) {
|
|
8831
|
+
const skillsDir = path31.isAbsolute(targetBaseDir) ? targetBaseDir : path31.join(projectRoot, targetBaseDir);
|
|
8832
|
+
for (const dirName of await getSkillDirNames2()) {
|
|
8833
|
+
const sourceDir = path31.join(SKILLS_SOURCE_DIR2, dirName);
|
|
8834
|
+
const targetDir = path31.join(skillsDir, dirName);
|
|
8835
|
+
const relativeDir = `${path31.relative(projectRoot, targetDir)}/`;
|
|
8626
8836
|
if (!await exists(targetDir)) {
|
|
8627
|
-
await
|
|
8628
|
-
console.log(
|
|
8837
|
+
await copyDirRecursive2({ src: sourceDir, dest: targetDir });
|
|
8838
|
+
console.log(chalk12.green(`Created ${relativeDir}`));
|
|
8629
8839
|
continue;
|
|
8630
8840
|
}
|
|
8631
8841
|
const sourceChecksum = await computeSkillChecksum(sourceDir);
|
|
8632
8842
|
const targetChecksum = await computeSkillChecksum(targetDir);
|
|
8633
8843
|
if (sourceChecksum === targetChecksum)
|
|
8634
8844
|
continue;
|
|
8635
|
-
|
|
8636
|
-
if (
|
|
8845
|
+
let choice;
|
|
8846
|
+
if (skipPrompts || updateAllState.updateAll) {
|
|
8847
|
+
choice = "yes";
|
|
8848
|
+
} else {
|
|
8849
|
+
choice = await promptFileOverwrite(dirName, skipPrompts);
|
|
8850
|
+
if (choice === "all") {
|
|
8851
|
+
updateAllState.updateAll = true;
|
|
8852
|
+
}
|
|
8853
|
+
}
|
|
8854
|
+
if (choice === "no")
|
|
8637
8855
|
continue;
|
|
8638
8856
|
await fs32.rm(targetDir, { recursive: true, force: true });
|
|
8639
|
-
await
|
|
8640
|
-
console.log(
|
|
8857
|
+
await copyDirRecursive2({ src: sourceDir, dest: targetDir });
|
|
8858
|
+
console.log(chalk12.green(`Updated ${relativeDir}`));
|
|
8859
|
+
}
|
|
8860
|
+
}
|
|
8861
|
+
async function detectAdaptersNeedingInstall(devAdapters, projectRoot) {
|
|
8862
|
+
const result = [];
|
|
8863
|
+
for (const adapter of devAdapters) {
|
|
8864
|
+
if (!adapter.installPlugin)
|
|
8865
|
+
continue;
|
|
8866
|
+
if (adapter.detectPlugin) {
|
|
8867
|
+
const existingScope = await adapter.detectPlugin(projectRoot);
|
|
8868
|
+
if (existingScope) {
|
|
8869
|
+
console.log(chalk12.dim(`${adapter.name} plugin already installed at ${existingScope} scope, skipping install`));
|
|
8870
|
+
continue;
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
result.push(adapter);
|
|
8874
|
+
}
|
|
8875
|
+
return result;
|
|
8876
|
+
}
|
|
8877
|
+
async function installOtherAdapterSkills(projectRoot, devAdapters, skipPrompts, updateAllState) {
|
|
8878
|
+
const seen = new Set;
|
|
8879
|
+
for (const adapter of devAdapters) {
|
|
8880
|
+
if (adapter.name === "claude" || adapter.name === "codex")
|
|
8881
|
+
continue;
|
|
8882
|
+
const dir = adapter.getProjectSkillDir();
|
|
8883
|
+
if (dir && !seen.has(dir)) {
|
|
8884
|
+
seen.add(dir);
|
|
8885
|
+
await installSkillsWithChecksums(projectRoot, dir, skipPrompts, updateAllState);
|
|
8886
|
+
}
|
|
8641
8887
|
}
|
|
8642
8888
|
}
|
|
8643
8889
|
async function installExternalFiles(projectRoot, devAdapters, skipPrompts) {
|
|
8644
|
-
|
|
8645
|
-
|
|
8890
|
+
const updateAllState = { updateAll: false };
|
|
8891
|
+
const devAdapterNames = new Set(devAdapters.map((adapter) => adapter.name));
|
|
8892
|
+
const adaptersNeedingInstall = await detectAdaptersNeedingInstall(devAdapters, projectRoot);
|
|
8893
|
+
const needsScope = adaptersNeedingInstall.length > 0 || devAdapterNames.has("codex");
|
|
8894
|
+
const installScope = needsScope ? await promptInstallScope(skipPrompts) : "project";
|
|
8895
|
+
for (const adapter of adaptersNeedingInstall) {
|
|
8896
|
+
await installAdapterPlugin(adapter, projectRoot, installScope);
|
|
8897
|
+
}
|
|
8898
|
+
if (devAdapterNames.has("codex")) {
|
|
8899
|
+
const codexBaseDir = getCodexSkillsBaseDir(installScope);
|
|
8900
|
+
await installSkillsWithChecksums(projectRoot, codexBaseDir, skipPrompts, updateAllState);
|
|
8901
|
+
}
|
|
8902
|
+
await installOtherAdapterSkills(projectRoot, devAdapters, skipPrompts, updateAllState);
|
|
8646
8903
|
}
|
|
8647
8904
|
async function printPostInitInstructions(devCLINames) {
|
|
8648
8905
|
const hasNative = devCLINames.some((name) => NATIVE_CLIS.has(name));
|
|
8649
|
-
const
|
|
8650
|
-
const
|
|
8906
|
+
const hasCodex = devCLINames.includes("codex");
|
|
8907
|
+
const otherNonNativeNames = devCLINames.filter((name) => !NATIVE_CLIS.has(name) && name !== "codex");
|
|
8908
|
+
const hasOtherNonNative = otherNonNativeNames.length > 0;
|
|
8651
8909
|
console.log();
|
|
8652
8910
|
if (hasNative) {
|
|
8653
|
-
console.log(
|
|
8911
|
+
console.log(chalk12.bold("To complete setup, run /gauntlet-setup in your CLI. This will guide you through configuring the static checks (unit tests, linters, etc.) that Agent Gauntlet will run."));
|
|
8654
8912
|
}
|
|
8655
|
-
if (
|
|
8656
|
-
console.log(
|
|
8913
|
+
if (hasCodex) {
|
|
8914
|
+
console.log(chalk12.bold("To complete setup in Codex, reference the setup skill: .agents/skills/gauntlet-setup/SKILL.md. This will guide you through configuring the static checks (unit tests, linters, etc.) that Agent Gauntlet will run."));
|
|
8915
|
+
console.log();
|
|
8916
|
+
console.log("Available Codex skills:");
|
|
8917
|
+
for (const dirName of await getSkillDirNames2()) {
|
|
8918
|
+
console.log(` .agents/skills/${dirName}/SKILL.md`);
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
if (hasOtherNonNative) {
|
|
8922
|
+
console.log(chalk12.bold("To complete setup, reference the setup skill in your CLI: @.claude/skills/gauntlet-setup/SKILL.md. This will guide you through configuring the static checks (unit tests, linters, etc.) that Agent Gauntlet will run."));
|
|
8657
8923
|
console.log();
|
|
8658
8924
|
console.log("Available skills:");
|
|
8659
|
-
for (const dirName of await
|
|
8925
|
+
for (const dirName of await getSkillDirNames2()) {
|
|
8660
8926
|
console.log(` @.claude/skills/${dirName}/SKILL.md`);
|
|
8661
8927
|
}
|
|
8662
8928
|
}
|
|
@@ -8719,11 +8985,11 @@ entry_points: []
|
|
|
8719
8985
|
# enabled: true
|
|
8720
8986
|
# format: text # Options: text, json
|
|
8721
8987
|
`;
|
|
8722
|
-
await fs32.writeFile(
|
|
8723
|
-
console.log(
|
|
8988
|
+
await fs32.writeFile(path31.join(targetDir, "config.yml"), content);
|
|
8989
|
+
console.log(chalk12.green("Created .gauntlet/config.yml"));
|
|
8724
8990
|
}
|
|
8725
8991
|
async function addToGitignore(projectRoot, entry) {
|
|
8726
|
-
const gitignorePath =
|
|
8992
|
+
const gitignorePath = path31.join(projectRoot, ".gitignore");
|
|
8727
8993
|
let content = "";
|
|
8728
8994
|
if (await exists(gitignorePath)) {
|
|
8729
8995
|
content = await fs32.readFile(gitignorePath, "utf-8");
|
|
@@ -8737,11 +9003,11 @@ async function addToGitignore(projectRoot, entry) {
|
|
|
8737
9003
|
` : "";
|
|
8738
9004
|
await fs32.appendFile(gitignorePath, `${suffix}${entry}
|
|
8739
9005
|
`);
|
|
8740
|
-
console.log(
|
|
9006
|
+
console.log(chalk12.green(`Added ${entry} to .gitignore`));
|
|
8741
9007
|
}
|
|
8742
9008
|
function gitSilent(args, opts) {
|
|
8743
9009
|
try {
|
|
8744
|
-
return
|
|
9010
|
+
return execFileSync2("git", args, {
|
|
8745
9011
|
encoding: "utf-8",
|
|
8746
9012
|
timeout: opts?.timeout,
|
|
8747
9013
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -8789,30 +9055,30 @@ async function detectAvailableCLIs() {
|
|
|
8789
9055
|
for (const adapter of allAdapters) {
|
|
8790
9056
|
const isAvailable = await adapter.isAvailable();
|
|
8791
9057
|
if (isAvailable) {
|
|
8792
|
-
console.log(
|
|
9058
|
+
console.log(chalk12.green(` ✓ ${adapter.name}`));
|
|
8793
9059
|
available.push(adapter);
|
|
8794
9060
|
} else {
|
|
8795
|
-
console.log(
|
|
9061
|
+
console.log(chalk12.dim(` ✗ ${adapter.name} (not installed)`));
|
|
8796
9062
|
}
|
|
8797
9063
|
}
|
|
8798
9064
|
return available;
|
|
8799
9065
|
}
|
|
8800
9066
|
// src/commands/list.ts
|
|
8801
|
-
import
|
|
9067
|
+
import chalk13 from "chalk";
|
|
8802
9068
|
function registerListCommand(program) {
|
|
8803
9069
|
program.command("list").description("List configured gates").action(async () => {
|
|
8804
9070
|
try {
|
|
8805
9071
|
const config = await loadConfig();
|
|
8806
|
-
console.log(
|
|
9072
|
+
console.log(chalk13.bold("Check Gates:"));
|
|
8807
9073
|
for (const c of Object.values(config.checks)) {
|
|
8808
9074
|
console.log(` - ${c.name}`);
|
|
8809
9075
|
}
|
|
8810
|
-
console.log(
|
|
9076
|
+
console.log(chalk13.bold(`
|
|
8811
9077
|
Review Gates:`));
|
|
8812
9078
|
for (const r of Object.values(config.reviews)) {
|
|
8813
9079
|
console.log(` - ${r.name} (Tools: ${r.cli_preference?.join(", ")})`);
|
|
8814
9080
|
}
|
|
8815
|
-
console.log(
|
|
9081
|
+
console.log(chalk13.bold(`
|
|
8816
9082
|
Entry Points:`));
|
|
8817
9083
|
for (const ep of config.project.entry_points) {
|
|
8818
9084
|
console.log(` - ${ep.path}`);
|
|
@@ -8823,7 +9089,7 @@ Entry Points:`));
|
|
|
8823
9089
|
}
|
|
8824
9090
|
} catch (error) {
|
|
8825
9091
|
const err = error;
|
|
8826
|
-
console.error(
|
|
9092
|
+
console.error(chalk13.red("Error:"), err.message);
|
|
8827
9093
|
}
|
|
8828
9094
|
});
|
|
8829
9095
|
}
|
|
@@ -8836,7 +9102,7 @@ function registerReviewCommand(program) {
|
|
|
8836
9102
|
}
|
|
8837
9103
|
// src/scripts/review-audit.ts
|
|
8838
9104
|
import fs33 from "node:fs";
|
|
8839
|
-
import
|
|
9105
|
+
import path32 from "node:path";
|
|
8840
9106
|
import readline from "node:readline";
|
|
8841
9107
|
function parseKeyValue2(text) {
|
|
8842
9108
|
const result = {};
|
|
@@ -8863,7 +9129,7 @@ var parseDuration = (d) => {
|
|
|
8863
9129
|
};
|
|
8864
9130
|
function getLogDir3(cwd) {
|
|
8865
9131
|
try {
|
|
8866
|
-
const cfg =
|
|
9132
|
+
const cfg = path32.join(cwd, ".gauntlet", "config.yml");
|
|
8867
9133
|
const m = fs33.readFileSync(cfg, "utf-8").match(/^log_dir:\s*(.+)$/m);
|
|
8868
9134
|
if (m?.[1])
|
|
8869
9135
|
return m[1].trim().replace(/^["']|["']$/g, "");
|
|
@@ -9170,7 +9436,7 @@ async function main2(date, since) {
|
|
|
9170
9436
|
process.exit(1);
|
|
9171
9437
|
}
|
|
9172
9438
|
const logDir = getLogDir3(cwd);
|
|
9173
|
-
const debugLogPath =
|
|
9439
|
+
const debugLogPath = path32.join(cwd, logDir, ".debug.log");
|
|
9174
9440
|
if (!fs33.existsSync(debugLogPath)) {
|
|
9175
9441
|
console.log(`No debug log found. (looked in ${logDir}/.debug.log)`);
|
|
9176
9442
|
process.exit(0);
|
|
@@ -9195,9 +9461,149 @@ function registerReviewAuditCommand(program) {
|
|
|
9195
9461
|
await main2(opts.date, opts.since);
|
|
9196
9462
|
});
|
|
9197
9463
|
}
|
|
9198
|
-
// src/core/run-executor
|
|
9464
|
+
// src/core/run-executor.ts
|
|
9465
|
+
import fs37 from "node:fs/promises";
|
|
9466
|
+
import path36 from "node:path";
|
|
9467
|
+
|
|
9468
|
+
// src/output/report.ts
|
|
9199
9469
|
import fs34 from "node:fs/promises";
|
|
9200
|
-
import
|
|
9470
|
+
import path33 from "node:path";
|
|
9471
|
+
function parseReviewJsonFilename(filename) {
|
|
9472
|
+
const m = filename.match(/^(.+)_([^@]+)@(\d+)\.\d+\.json$/);
|
|
9473
|
+
if (!(m?.[1] && m[2] && m[3]))
|
|
9474
|
+
return null;
|
|
9475
|
+
return {
|
|
9476
|
+
jobId: m[1],
|
|
9477
|
+
adapter: m[2],
|
|
9478
|
+
reviewIndex: parseInt(m[3], 10)
|
|
9479
|
+
};
|
|
9480
|
+
}
|
|
9481
|
+
async function collectViolationsFromFile(jsonPath, filename, startId) {
|
|
9482
|
+
const content = await fs34.readFile(jsonPath, "utf-8");
|
|
9483
|
+
const data = JSON.parse(content);
|
|
9484
|
+
if (!(data.violations && Array.isArray(data.violations)))
|
|
9485
|
+
return [];
|
|
9486
|
+
const parsed = parseReviewJsonFilename(filename);
|
|
9487
|
+
if (!parsed)
|
|
9488
|
+
return [];
|
|
9489
|
+
const violations = [];
|
|
9490
|
+
let nextId = startId;
|
|
9491
|
+
for (let i = 0;i < data.violations.length; i++) {
|
|
9492
|
+
const v = data.violations[i];
|
|
9493
|
+
if (!v)
|
|
9494
|
+
continue;
|
|
9495
|
+
const status = v.status || "new";
|
|
9496
|
+
if (status !== "new")
|
|
9497
|
+
continue;
|
|
9498
|
+
violations.push({
|
|
9499
|
+
id: nextId++,
|
|
9500
|
+
gateLabel: parsed.jobId,
|
|
9501
|
+
adapterSuffix: `${data.adapter}@${parsed.reviewIndex}`,
|
|
9502
|
+
file: v.file,
|
|
9503
|
+
line: v.line,
|
|
9504
|
+
issue: v.issue,
|
|
9505
|
+
fix: v.fix,
|
|
9506
|
+
priority: v.priority,
|
|
9507
|
+
jsonPath,
|
|
9508
|
+
violationIndex: i
|
|
9509
|
+
});
|
|
9510
|
+
}
|
|
9511
|
+
return violations;
|
|
9512
|
+
}
|
|
9513
|
+
async function enumerateNewViolations(logDir) {
|
|
9514
|
+
let files;
|
|
9515
|
+
try {
|
|
9516
|
+
files = await fs34.readdir(logDir);
|
|
9517
|
+
} catch (err) {
|
|
9518
|
+
const code = err.code;
|
|
9519
|
+
if (code === "ENOENT")
|
|
9520
|
+
return [];
|
|
9521
|
+
throw new Error(`Failed to read log directory ${logDir}: ${err}`);
|
|
9522
|
+
}
|
|
9523
|
+
const reviewFiles = files.filter((f) => f.endsWith(".json") && parseReviewJsonFilename(f) !== null).sort();
|
|
9524
|
+
const allViolations = [];
|
|
9525
|
+
for (const file of reviewFiles) {
|
|
9526
|
+
try {
|
|
9527
|
+
const fileViolations = await collectViolationsFromFile(path33.join(logDir, file), file, allViolations.length + 1);
|
|
9528
|
+
allViolations.push(...fileViolations);
|
|
9529
|
+
} catch (err) {
|
|
9530
|
+
throw new Error(`Failed to read review JSON ${file}: ${err}`);
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
return allViolations;
|
|
9534
|
+
}
|
|
9535
|
+
function statusLineText(status) {
|
|
9536
|
+
switch (status) {
|
|
9537
|
+
case "passed":
|
|
9538
|
+
return "Status: Passed";
|
|
9539
|
+
case "passed_with_warnings":
|
|
9540
|
+
return "Status: Passed with warnings";
|
|
9541
|
+
case "failed":
|
|
9542
|
+
return "Status: Failed";
|
|
9543
|
+
case "retry_limit_exceeded":
|
|
9544
|
+
return "Status: Retry limit exceeded";
|
|
9545
|
+
default:
|
|
9546
|
+
return `Status: ${status}`;
|
|
9547
|
+
}
|
|
9548
|
+
}
|
|
9549
|
+
function formatCheckFailure(result) {
|
|
9550
|
+
const lines = [];
|
|
9551
|
+
lines.push(`### ${result.jobId}`);
|
|
9552
|
+
if (result.command)
|
|
9553
|
+
lines.push(`Command: ${result.command}`);
|
|
9554
|
+
if (result.workingDirectory)
|
|
9555
|
+
lines.push(`Directory: ${result.workingDirectory}`);
|
|
9556
|
+
if (result.fixInstructions)
|
|
9557
|
+
lines.push(`Fix instructions: ${result.fixInstructions}`);
|
|
9558
|
+
if (result.fixWithSkill)
|
|
9559
|
+
lines.push(`Fix skill: ${result.fixWithSkill}`);
|
|
9560
|
+
if (result.logPath)
|
|
9561
|
+
lines.push(`Log: ${result.logPath}`);
|
|
9562
|
+
lines.push("");
|
|
9563
|
+
return lines;
|
|
9564
|
+
}
|
|
9565
|
+
function formatReviewViolation(v) {
|
|
9566
|
+
const lines = [];
|
|
9567
|
+
const priorityStr = v.priority ? ` [${v.priority}]` : "";
|
|
9568
|
+
lines.push(`#${v.id}${priorityStr} ${v.gateLabel} (${v.adapterSuffix})`);
|
|
9569
|
+
lines.push(` ${v.file}:${v.line} - ${v.issue}`);
|
|
9570
|
+
if (v.fix)
|
|
9571
|
+
lines.push(` Fix: ${v.fix}`);
|
|
9572
|
+
lines.push(` JSON: ${v.jsonPath}`);
|
|
9573
|
+
lines.push("");
|
|
9574
|
+
return lines;
|
|
9575
|
+
}
|
|
9576
|
+
async function generateReport(status, gateResults, logDir) {
|
|
9577
|
+
const lines = [statusLineText(status)];
|
|
9578
|
+
if (!gateResults || gateResults.length === 0) {
|
|
9579
|
+
return lines.join(`
|
|
9580
|
+
`);
|
|
9581
|
+
}
|
|
9582
|
+
const checkFailures = gateResults.filter((r) => (r.status === "fail" || r.status === "error") && r.jobId.startsWith("check:"));
|
|
9583
|
+
if (checkFailures.length > 0) {
|
|
9584
|
+
lines.push("", "## CHECK FAILURES", "");
|
|
9585
|
+
for (const result of checkFailures) {
|
|
9586
|
+
lines.push(...formatCheckFailure(result));
|
|
9587
|
+
}
|
|
9588
|
+
}
|
|
9589
|
+
const reviewViolations = await enumerateNewViolations(logDir);
|
|
9590
|
+
if (reviewViolations.length > 0) {
|
|
9591
|
+
lines.push("## REVIEW VIOLATIONS", "");
|
|
9592
|
+
for (const v of reviewViolations) {
|
|
9593
|
+
lines.push(...formatReviewViolation(v));
|
|
9594
|
+
}
|
|
9595
|
+
}
|
|
9596
|
+
return lines.join(`
|
|
9597
|
+
`);
|
|
9598
|
+
}
|
|
9599
|
+
|
|
9600
|
+
// src/core/run-executor-helpers.ts
|
|
9601
|
+
import fs36 from "node:fs/promises";
|
|
9602
|
+
import path35 from "node:path";
|
|
9603
|
+
|
|
9604
|
+
// src/core/run-executor-lock.ts
|
|
9605
|
+
import fs35 from "node:fs/promises";
|
|
9606
|
+
import path34 from "node:path";
|
|
9201
9607
|
var LOCK_FILENAME2 = ".gauntlet-run.lock";
|
|
9202
9608
|
var STALE_LOCK_MS = 10 * 60 * 1000;
|
|
9203
9609
|
function isProcessAlive(pid) {
|
|
@@ -9213,9 +9619,9 @@ function isProcessAlive(pid) {
|
|
|
9213
9619
|
}
|
|
9214
9620
|
async function isLockStale(lockPath) {
|
|
9215
9621
|
try {
|
|
9216
|
-
const lockContent = await
|
|
9622
|
+
const lockContent = await fs35.readFile(lockPath, "utf-8");
|
|
9217
9623
|
const lockPid = Number.parseInt(lockContent.trim(), 10);
|
|
9218
|
-
const lockStat = await
|
|
9624
|
+
const lockStat = await fs35.stat(lockPath);
|
|
9219
9625
|
const lockAgeMs = Date.now() - lockStat.mtimeMs;
|
|
9220
9626
|
const pidValid = !Number.isNaN(lockPid);
|
|
9221
9627
|
if (pidValid && !isProcessAlive(lockPid)) {
|
|
@@ -9230,10 +9636,10 @@ async function isLockStale(lockPath) {
|
|
|
9230
9636
|
}
|
|
9231
9637
|
}
|
|
9232
9638
|
async function tryAcquireLock(logDir) {
|
|
9233
|
-
await
|
|
9234
|
-
const lockPath =
|
|
9639
|
+
await fs35.mkdir(logDir, { recursive: true });
|
|
9640
|
+
const lockPath = path34.resolve(logDir, LOCK_FILENAME2);
|
|
9235
9641
|
try {
|
|
9236
|
-
await
|
|
9642
|
+
await fs35.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
9237
9643
|
return true;
|
|
9238
9644
|
} catch (err) {
|
|
9239
9645
|
const isExist = typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST";
|
|
@@ -9244,9 +9650,9 @@ async function tryAcquireLock(logDir) {
|
|
|
9244
9650
|
if (!stale) {
|
|
9245
9651
|
return false;
|
|
9246
9652
|
}
|
|
9247
|
-
await
|
|
9653
|
+
await fs35.rm(lockPath, { force: true });
|
|
9248
9654
|
try {
|
|
9249
|
-
await
|
|
9655
|
+
await fs35.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
9250
9656
|
return true;
|
|
9251
9657
|
} catch {
|
|
9252
9658
|
return false;
|
|
@@ -9255,7 +9661,7 @@ async function tryAcquireLock(logDir) {
|
|
|
9255
9661
|
}
|
|
9256
9662
|
async function findLatestConsoleLog(logDir) {
|
|
9257
9663
|
try {
|
|
9258
|
-
const files = await
|
|
9664
|
+
const files = await fs35.readdir(logDir);
|
|
9259
9665
|
let maxNum = -1;
|
|
9260
9666
|
let latestFile = null;
|
|
9261
9667
|
for (const file of files) {
|
|
@@ -9271,7 +9677,7 @@ async function findLatestConsoleLog(logDir) {
|
|
|
9271
9677
|
}
|
|
9272
9678
|
}
|
|
9273
9679
|
}
|
|
9274
|
-
return latestFile ?
|
|
9680
|
+
return latestFile ? path34.join(logDir, latestFile) : null;
|
|
9275
9681
|
} catch {
|
|
9276
9682
|
return null;
|
|
9277
9683
|
}
|
|
@@ -9494,6 +9900,16 @@ function determineStatus(outcome) {
|
|
|
9494
9900
|
async function buildRunResult(ctx, outcome, jobs) {
|
|
9495
9901
|
const consoleLogPath = await findLatestConsoleLog(ctx.config.project.log_dir);
|
|
9496
9902
|
const status = determineStatus(outcome);
|
|
9903
|
+
let reportText;
|
|
9904
|
+
if (ctx.options.report) {
|
|
9905
|
+
reportText = await generateReport(status, outcome.gateResults, ctx.config.project.log_dir);
|
|
9906
|
+
const reportPath = path35.join(ctx.config.project.log_dir, "report.txt");
|
|
9907
|
+
try {
|
|
9908
|
+
await fs36.writeFile(reportPath, reportText, "utf-8");
|
|
9909
|
+
} catch (err) {
|
|
9910
|
+
console.debug(`Failed to write report file ${reportPath}: ${err}`);
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9497
9913
|
if (status === "passed" || status === "retry_limit_exceeded") {
|
|
9498
9914
|
const reason = status === "passed" ? "all_passed" : "retry_limit_exceeded";
|
|
9499
9915
|
const debugLogger = getDebugLogger();
|
|
@@ -9506,6 +9922,7 @@ async function buildRunResult(ctx, outcome, jobs) {
|
|
|
9506
9922
|
gatesRun: jobs.length,
|
|
9507
9923
|
gatesFailed: outcome.allPassed ? 0 : jobs.length,
|
|
9508
9924
|
consoleLogPath: consoleLogPath ?? undefined,
|
|
9925
|
+
reportText,
|
|
9509
9926
|
gateResults: outcome.gateResults
|
|
9510
9927
|
};
|
|
9511
9928
|
}
|
|
@@ -9578,6 +9995,15 @@ async function runWithLock(ctx, isRerun, logsExist) {
|
|
|
9578
9995
|
const { failuresMap, passedSlotsMap, changeOptions } = await processRerunMode2(ctx, isRerun, logsExist);
|
|
9579
9996
|
const prepared = await detectAndPrepareChanges(ctx, isRerun, failuresMap, changeOptions);
|
|
9580
9997
|
if ("earlyResult" in prepared) {
|
|
9998
|
+
if (ctx.options.report) {
|
|
9999
|
+
const reportText = await generateReport(prepared.earlyResult.status, prepared.earlyResult.gateResults, ctx.config.project.log_dir);
|
|
10000
|
+
prepared.earlyResult.reportText = reportText;
|
|
10001
|
+
try {
|
|
10002
|
+
const reportPath = path36.join(ctx.config.project.log_dir, "report.txt");
|
|
10003
|
+
await fs37.mkdir(ctx.config.project.log_dir, { recursive: true });
|
|
10004
|
+
await fs37.writeFile(reportPath, reportText, "utf-8");
|
|
10005
|
+
} catch {}
|
|
10006
|
+
}
|
|
9581
10007
|
return finalizeAndReturn(ctx.loggerInitializedHere, prepared.earlyResult, consoleLogHandle);
|
|
9582
10008
|
}
|
|
9583
10009
|
const result = await executeAndReport(ctx, logger, isRerun, failuresMap, passedSlotsMap, changeOptions, prepared.jobs);
|
|
@@ -9629,20 +10055,27 @@ async function executeRun(options = {}) {
|
|
|
9629
10055
|
|
|
9630
10056
|
// src/commands/run.ts
|
|
9631
10057
|
function registerRunCommand(program) {
|
|
9632
|
-
program.command("run").description("Run gates for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").option("-e, --enable-review <name>", "Activate a disabled review for this run (repeatable)", (value, prev = []) => [...prev, value], []).action(async (options) => {
|
|
10058
|
+
program.command("run").description("Run gates for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").option("-e, --enable-review <name>", "Activate a disabled review for this run (repeatable)", (value, prev = []) => [...prev, value], []).option("--report", "Write a structured failure report to stdout").action(async (options) => {
|
|
10059
|
+
const reportEnabled = options.report ?? false;
|
|
9633
10060
|
const result = await executeRun({
|
|
9634
10061
|
baseBranch: options.baseBranch,
|
|
9635
10062
|
gate: options.gate,
|
|
9636
10063
|
commit: options.commit,
|
|
9637
10064
|
uncommitted: options.uncommitted,
|
|
9638
|
-
enableReviews: new Set(options.enableReview ?? [])
|
|
10065
|
+
enableReviews: new Set(options.enableReview ?? []),
|
|
10066
|
+
report: reportEnabled
|
|
9639
10067
|
});
|
|
10068
|
+
if (reportEnabled) {
|
|
10069
|
+
const text = result.reportText ?? statusLineText(result.status);
|
|
10070
|
+
process.stdout.write(`${text}
|
|
10071
|
+
`);
|
|
10072
|
+
}
|
|
9640
10073
|
const code = isSuccessStatus(result.status) ? 0 : 1;
|
|
9641
10074
|
process.exit(code);
|
|
9642
10075
|
});
|
|
9643
10076
|
}
|
|
9644
10077
|
// src/commands/skip.ts
|
|
9645
|
-
import
|
|
10078
|
+
import chalk14 from "chalk";
|
|
9646
10079
|
function registerSkipCommand(program) {
|
|
9647
10080
|
program.command("skip").description("Advance execution state baseline without running gates").action(async () => {
|
|
9648
10081
|
let config;
|
|
@@ -9661,20 +10094,20 @@ function registerSkipCommand(program) {
|
|
|
9661
10094
|
const commit = await getCurrentCommit();
|
|
9662
10095
|
const shortSha = commit.slice(0, 7);
|
|
9663
10096
|
await releaseLock(config.project.log_dir);
|
|
9664
|
-
console.log(
|
|
10097
|
+
console.log(chalk14.green(`Baseline advanced to ${shortSha}. Next run will diff from here.`));
|
|
9665
10098
|
} catch (error) {
|
|
9666
10099
|
if (config && lockAcquired) {
|
|
9667
10100
|
await releaseLock(config.project.log_dir);
|
|
9668
10101
|
}
|
|
9669
10102
|
const err = error;
|
|
9670
|
-
console.error(
|
|
10103
|
+
console.error(chalk14.red("Error:"), err.message);
|
|
9671
10104
|
process.exit(1);
|
|
9672
10105
|
}
|
|
9673
10106
|
});
|
|
9674
10107
|
}
|
|
9675
10108
|
// src/commands/start-hook.ts
|
|
9676
|
-
import
|
|
9677
|
-
import
|
|
10109
|
+
import fs38 from "node:fs/promises";
|
|
10110
|
+
import path37 from "node:path";
|
|
9678
10111
|
import YAML8 from "yaml";
|
|
9679
10112
|
var START_HOOK_MESSAGE = `<IMPORTANT>
|
|
9680
10113
|
This project uses Agent Gauntlet for automated quality verification.
|
|
@@ -9722,9 +10155,9 @@ function isValidConfig(content) {
|
|
|
9722
10155
|
}
|
|
9723
10156
|
function registerStartHookCommand(program) {
|
|
9724
10157
|
program.command("start-hook").description("Session start hook - primes agent with gauntlet verification instructions").option("--adapter <adapter>", "Output format: claude or cursor", "claude").action(async (options) => {
|
|
9725
|
-
const configPath =
|
|
10158
|
+
const configPath = path37.join(process.cwd(), ".gauntlet", "config.yml");
|
|
9726
10159
|
try {
|
|
9727
|
-
const content = await
|
|
10160
|
+
const content = await fs38.readFile(configPath, "utf-8");
|
|
9728
10161
|
if (!isValidConfig(content)) {
|
|
9729
10162
|
return;
|
|
9730
10163
|
}
|
|
@@ -9734,7 +10167,7 @@ function registerStartHookCommand(program) {
|
|
|
9734
10167
|
const adapter = options.adapter;
|
|
9735
10168
|
try {
|
|
9736
10169
|
const cwd = process.cwd();
|
|
9737
|
-
const logDir =
|
|
10170
|
+
const logDir = path37.join(cwd, await getLogDir2(cwd));
|
|
9738
10171
|
const globalConfig = await loadGlobalConfig();
|
|
9739
10172
|
const projectDebugLogConfig = await getDebugLogConfig(cwd);
|
|
9740
10173
|
const debugLogConfig = mergeDebugLogConfig(projectDebugLogConfig, globalConfig.debug_log);
|
|
@@ -9751,17 +10184,119 @@ function registerStatusCommand(program) {
|
|
|
9751
10184
|
main();
|
|
9752
10185
|
});
|
|
9753
10186
|
}
|
|
10187
|
+
// src/commands/update.ts
|
|
10188
|
+
function registerUpdateCommand(program) {
|
|
10189
|
+
program.command("update").description("Update the agent-gauntlet Claude plugin and refresh skills").action(async () => {
|
|
10190
|
+
await runPluginUpdate();
|
|
10191
|
+
});
|
|
10192
|
+
}
|
|
10193
|
+
// src/commands/update-review.ts
|
|
10194
|
+
import fs39 from "node:fs/promises";
|
|
10195
|
+
async function ensureLogDir(logDir) {
|
|
10196
|
+
try {
|
|
10197
|
+
await fs39.stat(logDir);
|
|
10198
|
+
} catch (error) {
|
|
10199
|
+
const code = error.code;
|
|
10200
|
+
if (code === "ENOENT") {
|
|
10201
|
+
console.error(`Error: Log directory does not exist: ${logDir}`);
|
|
10202
|
+
process.exit(1);
|
|
10203
|
+
}
|
|
10204
|
+
throw new Error(`Failed to access log directory ${logDir}: ${String(error)}`);
|
|
10205
|
+
}
|
|
10206
|
+
}
|
|
10207
|
+
function printViolation(v) {
|
|
10208
|
+
const priorityStr = v.priority ? ` [${v.priority}]` : "";
|
|
10209
|
+
console.log(`#${v.id}${priorityStr} ${v.gateLabel} (${v.adapterSuffix})`);
|
|
10210
|
+
console.log(` ${v.file}:${v.line} - ${v.issue}`);
|
|
10211
|
+
if (v.fix) {
|
|
10212
|
+
console.log(` Fix: ${v.fix}`);
|
|
10213
|
+
}
|
|
10214
|
+
console.log(` JSON: ${v.jsonPath}`);
|
|
10215
|
+
console.log("");
|
|
10216
|
+
}
|
|
10217
|
+
function registerUpdateReviewCommand(program) {
|
|
10218
|
+
const cmd = program.command("update-review").description("Manage review violations");
|
|
10219
|
+
cmd.command("list").description("List pending review violations with numeric IDs").action(async () => {
|
|
10220
|
+
try {
|
|
10221
|
+
const config = await loadConfig();
|
|
10222
|
+
const logDir = config.project.log_dir;
|
|
10223
|
+
await ensureLogDir(logDir);
|
|
10224
|
+
const violations = await enumerateNewViolations(logDir);
|
|
10225
|
+
if (violations.length === 0) {
|
|
10226
|
+
console.log("No pending violations.");
|
|
10227
|
+
process.exit(0);
|
|
10228
|
+
}
|
|
10229
|
+
for (const v of violations) {
|
|
10230
|
+
printViolation(v);
|
|
10231
|
+
}
|
|
10232
|
+
process.exit(0);
|
|
10233
|
+
} catch (error) {
|
|
10234
|
+
const err = error;
|
|
10235
|
+
console.error(`Error: ${err.message || "Unknown error"}`);
|
|
10236
|
+
process.exit(1);
|
|
10237
|
+
}
|
|
10238
|
+
});
|
|
10239
|
+
cmd.command("fix <id> <reason>").description("Mark a violation as fixed").action(async (idStr, reason) => {
|
|
10240
|
+
await updateViolation(idStr, reason, "fixed");
|
|
10241
|
+
});
|
|
10242
|
+
cmd.command("skip <id> <reason>").description("Mark a violation as skipped").action(async (idStr, reason) => {
|
|
10243
|
+
await updateViolation(idStr, reason, "skipped");
|
|
10244
|
+
});
|
|
10245
|
+
}
|
|
10246
|
+
async function updateViolation(idStr, reason, newStatus) {
|
|
10247
|
+
try {
|
|
10248
|
+
if (!reason) {
|
|
10249
|
+
console.error(`Error: Missing reason. Usage: agent-gauntlet update-review ${newStatus} <id> "<reason>"`);
|
|
10250
|
+
process.exit(1);
|
|
10251
|
+
}
|
|
10252
|
+
const id = parseInt(idStr, 10);
|
|
10253
|
+
if (Number.isNaN(id) || id < 1) {
|
|
10254
|
+
console.error(`Error: Invalid ID: ${idStr}`);
|
|
10255
|
+
process.exit(1);
|
|
10256
|
+
}
|
|
10257
|
+
const config = await loadConfig();
|
|
10258
|
+
const logDir = config.project.log_dir;
|
|
10259
|
+
const violations = await enumerateNewViolations(logDir);
|
|
10260
|
+
const target = violations.find((v) => v.id === id);
|
|
10261
|
+
if (!target) {
|
|
10262
|
+
console.error(`Error: Violation #${id} not found. Use 'update-review list' to see available violations.`);
|
|
10263
|
+
process.exit(1);
|
|
10264
|
+
}
|
|
10265
|
+
const content = await fs39.readFile(target.jsonPath, "utf-8");
|
|
10266
|
+
const data = JSON.parse(content);
|
|
10267
|
+
const violation = data.violations[target.violationIndex];
|
|
10268
|
+
if (!violation) {
|
|
10269
|
+
console.error(`Error: Violation at index ${target.violationIndex} not found in ${target.jsonPath}`);
|
|
10270
|
+
process.exit(1);
|
|
10271
|
+
}
|
|
10272
|
+
const currentStatus = violation.status || "new";
|
|
10273
|
+
if (currentStatus !== "new") {
|
|
10274
|
+
console.error(`Error: Violation #${id} is already ${currentStatus} and cannot be updated.`);
|
|
10275
|
+
process.exit(1);
|
|
10276
|
+
}
|
|
10277
|
+
violation.status = newStatus;
|
|
10278
|
+
violation.result = reason;
|
|
10279
|
+
await fs39.writeFile(target.jsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
10280
|
+
const action = newStatus === "fixed" ? "Fixed" : "Skipped";
|
|
10281
|
+
console.log(`${action} violation #${id}: ${target.file}:${target.line} - ${target.issue}`);
|
|
10282
|
+
process.exit(0);
|
|
10283
|
+
} catch (error) {
|
|
10284
|
+
const err = error;
|
|
10285
|
+
console.error(`Error: ${err.message || "Unknown error"}`);
|
|
10286
|
+
process.exit(1);
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
9754
10289
|
// src/commands/validate.ts
|
|
9755
|
-
import
|
|
10290
|
+
import chalk15 from "chalk";
|
|
9756
10291
|
function registerValidateCommand(program) {
|
|
9757
10292
|
program.command("validate").description("Validate .gauntlet/ config files against schemas").action(async () => {
|
|
9758
10293
|
try {
|
|
9759
10294
|
await loadConfig();
|
|
9760
|
-
console.log(
|
|
10295
|
+
console.log(chalk15.green("All config files are valid."));
|
|
9761
10296
|
process.exitCode = 0;
|
|
9762
10297
|
} catch (error) {
|
|
9763
10298
|
const message = error instanceof Error ? error.message : String(error);
|
|
9764
|
-
console.error(
|
|
10299
|
+
console.error(chalk15.red("Validation failed:"), message);
|
|
9765
10300
|
process.exitCode = 1;
|
|
9766
10301
|
}
|
|
9767
10302
|
});
|
|
@@ -9779,6 +10314,8 @@ registerDetectCommand(program);
|
|
|
9779
10314
|
registerListCommand(program);
|
|
9780
10315
|
registerHealthCommand(program);
|
|
9781
10316
|
registerInitCommand(program);
|
|
10317
|
+
registerUpdateCommand(program);
|
|
10318
|
+
registerUpdateReviewCommand(program);
|
|
9782
10319
|
registerValidateCommand(program);
|
|
9783
10320
|
registerSkipCommand(program);
|
|
9784
10321
|
registerStartHookCommand(program);
|
|
@@ -9790,4 +10327,4 @@ if (process.argv.length < 3) {
|
|
|
9790
10327
|
}
|
|
9791
10328
|
program.parse(process.argv);
|
|
9792
10329
|
|
|
9793
|
-
//# debugId=
|
|
10330
|
+
//# debugId=9B95D114BC2749E764756E2164756E21
|