agent-gauntlet 0.13.1 → 0.14.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/LICENSE +21 -201
- package/README.md +1 -1
- package/dist/index.js +610 -293
- package/dist/index.js.map +22 -19
- package/dist/skill-templates/help-ref-stop-hook-troubleshooting.md +1 -1
- package/dist/skill-templates/status.md +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -7,9 +7,9 @@ import { Command } from "commander";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "agent-gauntlet",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.14.0",
|
|
11
11
|
description: "A CLI tool for testing AI coding agents",
|
|
12
|
-
license: "
|
|
12
|
+
license: "MIT",
|
|
13
13
|
author: "Paul Caplan",
|
|
14
14
|
repository: {
|
|
15
15
|
type: "git",
|
|
@@ -70,6 +70,7 @@ var package_default = {
|
|
|
70
70
|
typescript: "^5"
|
|
71
71
|
},
|
|
72
72
|
dependencies: {
|
|
73
|
+
"@inquirer/prompts": "^8.2.1",
|
|
73
74
|
"@logtape/logtape": "^2.0.2",
|
|
74
75
|
chalk: "^5.6.2",
|
|
75
76
|
commander: "^14.0.2",
|
|
@@ -946,11 +947,14 @@ class ClaudeStopHookAdapter {
|
|
|
946
947
|
const stopReason = result.shouldBlock && blockReason ? blockReason : result.message;
|
|
947
948
|
const response = {
|
|
948
949
|
decision: result.shouldBlock ? "block" : "approve",
|
|
949
|
-
|
|
950
|
-
systemMessage: result.message,
|
|
951
|
-
status: result.status,
|
|
952
|
-
message: result.message
|
|
950
|
+
status: result.status
|
|
953
951
|
};
|
|
952
|
+
if (stopReason)
|
|
953
|
+
response.stopReason = stopReason;
|
|
954
|
+
if (result.message) {
|
|
955
|
+
response.systemMessage = result.message;
|
|
956
|
+
response.message = result.message;
|
|
957
|
+
}
|
|
954
958
|
if (result.shouldBlock && blockReason) {
|
|
955
959
|
response.reason = blockReason;
|
|
956
960
|
}
|
|
@@ -995,7 +999,7 @@ class CursorStopHookAdapter {
|
|
|
995
999
|
return JSON.stringify(response2);
|
|
996
1000
|
}
|
|
997
1001
|
const response = {
|
|
998
|
-
systemMessage: result.message
|
|
1002
|
+
...result.message ? { systemMessage: result.message } : {}
|
|
999
1003
|
};
|
|
1000
1004
|
return JSON.stringify(response);
|
|
1001
1005
|
}
|
|
@@ -1305,6 +1309,9 @@ class DebugLogger {
|
|
|
1305
1309
|
async logStopHook(decision, reason) {
|
|
1306
1310
|
await this.write(`STOP_HOOK decision=${decision} reason=${reason}`);
|
|
1307
1311
|
}
|
|
1312
|
+
async logStartHook(adapter) {
|
|
1313
|
+
await this.write(`START_HOOK adapter=${adapter}`);
|
|
1314
|
+
}
|
|
1308
1315
|
async logStopHookDiagnostics(diagnostics) {
|
|
1309
1316
|
const parts = [
|
|
1310
1317
|
"STOP_HOOK_DIAG",
|
|
@@ -1567,6 +1574,27 @@ async function isCommitInBranch(commit, branch) {
|
|
|
1567
1574
|
function getExecutionStateFilename() {
|
|
1568
1575
|
return EXECUTION_STATE_FILENAME;
|
|
1569
1576
|
}
|
|
1577
|
+
async function hasWorkingTreeChanges() {
|
|
1578
|
+
return new Promise((resolve) => {
|
|
1579
|
+
const child = spawn("git", ["status", "--porcelain"], {
|
|
1580
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1581
|
+
});
|
|
1582
|
+
let stdout = "";
|
|
1583
|
+
child.stdout.on("data", (data) => {
|
|
1584
|
+
stdout += data.toString();
|
|
1585
|
+
});
|
|
1586
|
+
child.on("close", (code) => {
|
|
1587
|
+
if (code === 0) {
|
|
1588
|
+
resolve(stdout.trim().length > 0);
|
|
1589
|
+
} else {
|
|
1590
|
+
resolve(true);
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
child.on("error", () => {
|
|
1594
|
+
resolve(true);
|
|
1595
|
+
});
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1570
1598
|
async function gitObjectExists(sha) {
|
|
1571
1599
|
return new Promise((resolve) => {
|
|
1572
1600
|
const child = spawn("git", ["cat-file", "-t", sha], {
|
|
@@ -1818,7 +1846,7 @@ var STATUS_MESSAGES = {
|
|
|
1818
1846
|
no_config: "○ Not a gauntlet project — no .gauntlet/config.yml found.",
|
|
1819
1847
|
stop_hook_active: "↺ Stop hook cycle detected — allowing stop to prevent infinite loop.",
|
|
1820
1848
|
loop_detected: "↺ Loop detected — stop hook blocked 3 times within 60s. Allowing stop to prevent infinite loop.",
|
|
1821
|
-
stop_hook_disabled: "
|
|
1849
|
+
stop_hook_disabled: "",
|
|
1822
1850
|
invalid_input: "⚠ Invalid hook input — could not parse JSON, allowing stop."
|
|
1823
1851
|
};
|
|
1824
1852
|
function getStatusMessage(status, context) {
|
|
@@ -2528,6 +2556,9 @@ class ClaudeAdapter {
|
|
|
2528
2556
|
transformCommand(markdownContent) {
|
|
2529
2557
|
return markdownContent;
|
|
2530
2558
|
}
|
|
2559
|
+
supportsHooks() {
|
|
2560
|
+
return true;
|
|
2561
|
+
}
|
|
2531
2562
|
async execute(opts) {
|
|
2532
2563
|
const fullContent = `${opts.prompt}
|
|
2533
2564
|
|
|
@@ -2727,6 +2758,9 @@ class CodexAdapter {
|
|
|
2727
2758
|
transformCommand(markdownContent) {
|
|
2728
2759
|
return markdownContent;
|
|
2729
2760
|
}
|
|
2761
|
+
supportsHooks() {
|
|
2762
|
+
return false;
|
|
2763
|
+
}
|
|
2730
2764
|
buildArgs(allowToolUse, thinkingBudget) {
|
|
2731
2765
|
const args = [
|
|
2732
2766
|
"exec",
|
|
@@ -2837,6 +2871,9 @@ class CursorAdapter {
|
|
|
2837
2871
|
transformCommand(markdownContent) {
|
|
2838
2872
|
return markdownContent;
|
|
2839
2873
|
}
|
|
2874
|
+
supportsHooks() {
|
|
2875
|
+
return true;
|
|
2876
|
+
}
|
|
2840
2877
|
async execute(opts) {
|
|
2841
2878
|
const fullContent = `${opts.prompt}
|
|
2842
2879
|
|
|
@@ -3080,6 +3117,9 @@ class GeminiAdapter {
|
|
|
3080
3117
|
canUseSymlink() {
|
|
3081
3118
|
return false;
|
|
3082
3119
|
}
|
|
3120
|
+
supportsHooks() {
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3083
3123
|
transformCommand(markdownContent) {
|
|
3084
3124
|
const fmMatch = markdownContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
3085
3125
|
let description = "Run the gauntlet verification suite";
|
|
@@ -3279,6 +3319,9 @@ class GitHubCopilotAdapter {
|
|
|
3279
3319
|
transformCommand(markdownContent) {
|
|
3280
3320
|
return markdownContent;
|
|
3281
3321
|
}
|
|
3322
|
+
supportsHooks() {
|
|
3323
|
+
return false;
|
|
3324
|
+
}
|
|
3282
3325
|
async execute(opts) {
|
|
3283
3326
|
const fullContent = `${opts.prompt}
|
|
3284
3327
|
|
|
@@ -5468,7 +5511,8 @@ async function shouldAutoClean(logDir, baseBranch) {
|
|
|
5468
5511
|
} catch {
|
|
5469
5512
|
return { clean: false };
|
|
5470
5513
|
}
|
|
5471
|
-
|
|
5514
|
+
const hasChanges = await hasWorkingTreeChanges();
|
|
5515
|
+
if (!hasChanges) {
|
|
5472
5516
|
try {
|
|
5473
5517
|
const isMerged = await isCommitInBranch(state.commit, baseBranch);
|
|
5474
5518
|
if (isMerged) {
|
|
@@ -6720,13 +6764,141 @@ function registerHelpCommand(program) {
|
|
|
6720
6764
|
}
|
|
6721
6765
|
// src/commands/init.ts
|
|
6722
6766
|
import { readFileSync } from "node:fs";
|
|
6767
|
+
import fs26 from "node:fs/promises";
|
|
6768
|
+
import path25 from "node:path";
|
|
6769
|
+
import { fileURLToPath } from "node:url";
|
|
6770
|
+
import chalk10 from "chalk";
|
|
6771
|
+
|
|
6772
|
+
// src/commands/init-checksums.ts
|
|
6773
|
+
import { createHash } from "node:crypto";
|
|
6723
6774
|
import fs25 from "node:fs/promises";
|
|
6724
6775
|
import path24 from "node:path";
|
|
6725
|
-
|
|
6776
|
+
async function computeSkillChecksum(skillDir) {
|
|
6777
|
+
const files = await collectFiles(skillDir);
|
|
6778
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
6779
|
+
const hash = createHash("sha256");
|
|
6780
|
+
for (const file of files) {
|
|
6781
|
+
hash.update(file.relativePath);
|
|
6782
|
+
hash.update(file.content);
|
|
6783
|
+
}
|
|
6784
|
+
return hash.digest("hex");
|
|
6785
|
+
}
|
|
6786
|
+
function computeExpectedSkillChecksum(content, references) {
|
|
6787
|
+
const entries = [
|
|
6788
|
+
{ relativePath: "SKILL.md", content }
|
|
6789
|
+
];
|
|
6790
|
+
if (references) {
|
|
6791
|
+
for (const [name, refContent] of Object.entries(references)) {
|
|
6792
|
+
entries.push({
|
|
6793
|
+
relativePath: path24.join("references", name),
|
|
6794
|
+
content: refContent
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
6799
|
+
const hash = createHash("sha256");
|
|
6800
|
+
for (const entry of entries) {
|
|
6801
|
+
hash.update(entry.relativePath);
|
|
6802
|
+
hash.update(entry.content);
|
|
6803
|
+
}
|
|
6804
|
+
return hash.digest("hex");
|
|
6805
|
+
}
|
|
6806
|
+
function computeHookChecksum(entries) {
|
|
6807
|
+
const gauntletEntries = entries.filter((entry) => isGauntletHookEntry(entry));
|
|
6808
|
+
const hash = createHash("sha256");
|
|
6809
|
+
hash.update(JSON.stringify(gauntletEntries));
|
|
6810
|
+
return hash.digest("hex");
|
|
6811
|
+
}
|
|
6812
|
+
function computeExpectedHookChecksum(hookEntries) {
|
|
6813
|
+
return computeHookChecksum(hookEntries);
|
|
6814
|
+
}
|
|
6815
|
+
function isGauntletHookEntry(entry) {
|
|
6816
|
+
if (typeof entry.command === "string" && entry.command.startsWith("agent-gauntlet")) {
|
|
6817
|
+
return true;
|
|
6818
|
+
}
|
|
6819
|
+
const nested = entry.hooks;
|
|
6820
|
+
if (Array.isArray(nested)) {
|
|
6821
|
+
return nested.some((h) => typeof h.command === "string" && h.command.startsWith("agent-gauntlet"));
|
|
6822
|
+
}
|
|
6823
|
+
return false;
|
|
6824
|
+
}
|
|
6825
|
+
async function collectFiles(dir, baseDir) {
|
|
6826
|
+
const base = baseDir ?? dir;
|
|
6827
|
+
const results = [];
|
|
6828
|
+
const entries = await fs25.readdir(dir, { withFileTypes: true });
|
|
6829
|
+
for (const entry of entries) {
|
|
6830
|
+
const fullPath = path24.join(dir, entry.name);
|
|
6831
|
+
if (entry.isDirectory()) {
|
|
6832
|
+
results.push(...await collectFiles(fullPath, base));
|
|
6833
|
+
} else if (entry.isFile()) {
|
|
6834
|
+
const content = await fs25.readFile(fullPath, "utf-8");
|
|
6835
|
+
results.push({ relativePath: path24.relative(base, fullPath), content });
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
return results;
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
// src/commands/init-prompts.ts
|
|
6842
|
+
import { checkbox, confirm, number } from "@inquirer/prompts";
|
|
6726
6843
|
import chalk9 from "chalk";
|
|
6727
|
-
|
|
6844
|
+
async function promptDevCLIs(detectedNames, skipPrompts) {
|
|
6845
|
+
if (skipPrompts)
|
|
6846
|
+
return detectedNames;
|
|
6847
|
+
console.log();
|
|
6848
|
+
console.log(chalk9.bold("Select your development CLI(s). These are the main tools you work in."));
|
|
6849
|
+
const selected = await checkbox({
|
|
6850
|
+
message: "Development CLIs:",
|
|
6851
|
+
choices: detectedNames.map((name) => ({ name, value: name })),
|
|
6852
|
+
required: true
|
|
6853
|
+
});
|
|
6854
|
+
return selected;
|
|
6855
|
+
}
|
|
6856
|
+
async function promptReviewCLIs(detectedNames, skipPrompts) {
|
|
6857
|
+
if (skipPrompts)
|
|
6858
|
+
return detectedNames;
|
|
6859
|
+
console.log();
|
|
6860
|
+
console.log(chalk9.bold("Select your reviewer CLI(s). These are the CLIs that will be used for AI code reviews."));
|
|
6861
|
+
const selected = await checkbox({
|
|
6862
|
+
message: "Review CLIs:",
|
|
6863
|
+
choices: detectedNames.map((name) => ({ name, value: name })),
|
|
6864
|
+
required: true
|
|
6865
|
+
});
|
|
6866
|
+
return selected;
|
|
6867
|
+
}
|
|
6868
|
+
async function promptNumReviews(reviewCliCount, skipPrompts) {
|
|
6869
|
+
if (reviewCliCount === 1)
|
|
6870
|
+
return 1;
|
|
6871
|
+
if (skipPrompts)
|
|
6872
|
+
return reviewCliCount;
|
|
6873
|
+
const result = await number({
|
|
6874
|
+
message: "How many of these CLIs would you like to run on every review?",
|
|
6875
|
+
min: 1,
|
|
6876
|
+
max: reviewCliCount,
|
|
6877
|
+
default: 1
|
|
6878
|
+
});
|
|
6879
|
+
return result ?? 1;
|
|
6880
|
+
}
|
|
6881
|
+
async function promptFileOverwrite(name, skipPrompts) {
|
|
6882
|
+
if (skipPrompts)
|
|
6883
|
+
return true;
|
|
6884
|
+
return confirm({
|
|
6885
|
+
message: `Skill \`${name}\` has changed, update it?`,
|
|
6886
|
+
default: true
|
|
6887
|
+
});
|
|
6888
|
+
}
|
|
6889
|
+
async function promptHookOverwrite(hookFile, skipPrompts) {
|
|
6890
|
+
if (skipPrompts)
|
|
6891
|
+
return true;
|
|
6892
|
+
return confirm({
|
|
6893
|
+
message: `Hook configuration in ${hookFile} has changed, update it?`,
|
|
6894
|
+
default: true
|
|
6895
|
+
});
|
|
6896
|
+
}
|
|
6897
|
+
|
|
6898
|
+
// src/commands/init.ts
|
|
6899
|
+
var __dirname2 = path25.dirname(fileURLToPath(import.meta.url));
|
|
6728
6900
|
function readSkillTemplate(filename) {
|
|
6729
|
-
const templatePath =
|
|
6901
|
+
const templatePath = path25.join(__dirname2, "skill-templates", filename);
|
|
6730
6902
|
return readFileSync(templatePath, "utf-8");
|
|
6731
6903
|
}
|
|
6732
6904
|
var CLI_PREFERENCE_ORDER = [
|
|
@@ -6744,13 +6916,15 @@ var ADAPTER_CONFIG = {
|
|
|
6744
6916
|
function buildGauntletSkillContent(mode) {
|
|
6745
6917
|
const isRun = mode === "run";
|
|
6746
6918
|
const name = isRun ? "run" : "check";
|
|
6747
|
-
const description = isRun ? "Run the full verification gauntlet" : "Run checks only (no reviews)";
|
|
6919
|
+
const description = isRun ? "Run the full verification gauntlet. Use this as the final step after completing a coding task — verifies quality, runs checks, and ensures all gates pass. Must be run before committing, pushing, or creating PRs." : "Run checks only (no reviews)";
|
|
6748
6920
|
const command = isRun ? "agent-gauntlet run" : "agent-gauntlet check";
|
|
6749
6921
|
const heading = isRun ? "Execute the autonomous verification suite." : "Run the gauntlet checks only — no AI reviews.";
|
|
6922
|
+
const disableModelInvocation = isRun ? "false" : "true";
|
|
6750
6923
|
const frontmatter = `---
|
|
6751
6924
|
name: gauntlet-${name}
|
|
6752
|
-
description:
|
|
6753
|
-
|
|
6925
|
+
description: >-
|
|
6926
|
+
${description}
|
|
6927
|
+
disable-model-invocation: ${disableModelInvocation}
|
|
6754
6928
|
allowed-tools: Bash
|
|
6755
6929
|
---`;
|
|
6756
6930
|
const steps = [
|
|
@@ -6839,16 +7013,37 @@ var SETUP_SKILL_CONTENT = readSkillTemplate("setup-skill.md");
|
|
|
6839
7013
|
var CHECK_CATALOG_REFERENCE = readSkillTemplate("check-catalog.md");
|
|
6840
7014
|
var PROJECT_STRUCTURE_REFERENCE = readSkillTemplate("setup-ref-project-structure.md");
|
|
6841
7015
|
var SKILL_DEFINITIONS = [
|
|
6842
|
-
{
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
7016
|
+
{
|
|
7017
|
+
action: "run",
|
|
7018
|
+
content: GAUNTLET_RUN_SKILL_CONTENT,
|
|
7019
|
+
description: "Run the verification suite"
|
|
7020
|
+
},
|
|
7021
|
+
{
|
|
7022
|
+
action: "check",
|
|
7023
|
+
content: GAUNTLET_CHECK_SKILL_CONTENT,
|
|
7024
|
+
description: "Run a single check gate"
|
|
7025
|
+
},
|
|
7026
|
+
{
|
|
7027
|
+
action: "push-pr",
|
|
7028
|
+
content: PUSH_PR_SKILL_CONTENT,
|
|
7029
|
+
description: "Commit, push, and create a PR"
|
|
7030
|
+
},
|
|
7031
|
+
{
|
|
7032
|
+
action: "fix-pr",
|
|
7033
|
+
content: FIX_PR_SKILL_CONTENT,
|
|
7034
|
+
description: "Fix PR review comments and CI failures"
|
|
7035
|
+
},
|
|
7036
|
+
{
|
|
7037
|
+
action: "status",
|
|
7038
|
+
content: GAUNTLET_STATUS_SKILL_CONTENT,
|
|
7039
|
+
description: "Show gauntlet status"
|
|
7040
|
+
},
|
|
6847
7041
|
{
|
|
6848
7042
|
action: "help",
|
|
6849
7043
|
content: HELP_SKILL_BUNDLE.content,
|
|
6850
7044
|
references: HELP_SKILL_BUNDLE.references,
|
|
6851
|
-
skillsOnly: true
|
|
7045
|
+
skillsOnly: true,
|
|
7046
|
+
description: "Diagnose and explain gauntlet behavior"
|
|
6852
7047
|
},
|
|
6853
7048
|
{
|
|
6854
7049
|
action: "setup",
|
|
@@ -6857,82 +7052,192 @@ var SKILL_DEFINITIONS = [
|
|
|
6857
7052
|
"check-catalog.md": CHECK_CATALOG_REFERENCE,
|
|
6858
7053
|
"project-structure.md": PROJECT_STRUCTURE_REFERENCE
|
|
6859
7054
|
},
|
|
6860
|
-
skillsOnly: true
|
|
7055
|
+
skillsOnly: true,
|
|
7056
|
+
description: "Configure checks and reviews interactively"
|
|
6861
7057
|
}
|
|
6862
7058
|
];
|
|
7059
|
+
var NATIVE_CLIS = new Set(["claude", "cursor"]);
|
|
6863
7060
|
function registerInitCommand(program) {
|
|
6864
7061
|
program.command("init").description("Initialize .gauntlet configuration").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
|
|
6865
7062
|
const projectRoot = process.cwd();
|
|
6866
|
-
const targetDir =
|
|
6867
|
-
|
|
6868
|
-
console.log(chalk9.yellow(".gauntlet directory already exists."));
|
|
6869
|
-
return;
|
|
6870
|
-
}
|
|
7063
|
+
const targetDir = path25.join(projectRoot, ".gauntlet");
|
|
7064
|
+
const skipPrompts = options.yes ?? false;
|
|
6871
7065
|
console.log("Detecting available CLI agents...");
|
|
6872
7066
|
const availableAdapters = await detectAvailableCLIs();
|
|
6873
7067
|
if (availableAdapters.length === 0) {
|
|
6874
7068
|
printNoCLIsMessage();
|
|
6875
7069
|
return;
|
|
6876
7070
|
}
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
}
|
|
6886
|
-
|
|
6887
|
-
|
|
7071
|
+
const detectedNames = availableAdapters.map((a) => a.name);
|
|
7072
|
+
const gauntletExists = await exists(targetDir);
|
|
7073
|
+
let hookAdapters;
|
|
7074
|
+
let instructionCLINames;
|
|
7075
|
+
if (gauntletExists) {
|
|
7076
|
+
console.log(chalk10.dim(".gauntlet/ already exists, skipping scaffolding"));
|
|
7077
|
+
hookAdapters = availableAdapters;
|
|
7078
|
+
instructionCLINames = detectedNames;
|
|
7079
|
+
} else {
|
|
7080
|
+
const devCLINames = await promptDevCLIs(detectedNames, skipPrompts);
|
|
7081
|
+
hookAdapters = availableAdapters.filter((a) => devCLINames.includes(a.name));
|
|
7082
|
+
for (const adapter of hookAdapters) {
|
|
7083
|
+
if (!adapter.supportsHooks()) {
|
|
7084
|
+
console.log(chalk10.yellow(` ${adapter.name} doesn't support hooks yet, skipping hook installation`));
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
const reviewCLINames = await promptReviewCLIs(detectedNames, skipPrompts);
|
|
7088
|
+
const numReviews = await promptNumReviews(reviewCLINames.length, skipPrompts);
|
|
7089
|
+
console.log(chalk10.cyan("Agent Gauntlet's built-in code quality reviewer will be installed."));
|
|
7090
|
+
await scaffoldGauntletDir(projectRoot, targetDir, reviewCLINames, numReviews);
|
|
7091
|
+
instructionCLINames = devCLINames;
|
|
6888
7092
|
}
|
|
7093
|
+
await installExternalFiles(projectRoot, hookAdapters, skipPrompts);
|
|
6889
7094
|
await addToGitignore(projectRoot, "gauntlet_logs");
|
|
6890
|
-
|
|
6891
|
-
console.log(chalk9.bold("Run /gauntlet-setup to configure your checks and reviews"));
|
|
7095
|
+
printPostInitInstructions(instructionCLINames);
|
|
6892
7096
|
});
|
|
6893
7097
|
}
|
|
6894
7098
|
function printNoCLIsMessage() {
|
|
6895
7099
|
console.log();
|
|
6896
|
-
console.log(
|
|
7100
|
+
console.log(chalk10.red("Error: No CLI agents found. Install at least one:"));
|
|
6897
7101
|
console.log(" - Claude: https://docs.anthropic.com/en/docs/claude-code");
|
|
6898
7102
|
console.log(" - Gemini: https://github.com/google-gemini/gemini-cli");
|
|
6899
7103
|
console.log(" - Codex: https://github.com/openai/codex");
|
|
6900
7104
|
console.log();
|
|
6901
7105
|
}
|
|
6902
|
-
async function
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
await
|
|
7106
|
+
async function scaffoldGauntletDir(_projectRoot, targetDir, reviewCLINames, numReviews) {
|
|
7107
|
+
if (await exists(targetDir)) {
|
|
7108
|
+
console.log(chalk10.dim(".gauntlet/ already exists, skipping scaffolding"));
|
|
7109
|
+
return;
|
|
7110
|
+
}
|
|
7111
|
+
await fs26.mkdir(targetDir);
|
|
7112
|
+
await fs26.mkdir(path25.join(targetDir, "checks"));
|
|
7113
|
+
await fs26.mkdir(path25.join(targetDir, "reviews"));
|
|
7114
|
+
await writeConfigYml(targetDir, reviewCLINames);
|
|
7115
|
+
await fs26.writeFile(path25.join(targetDir, "reviews", "code-quality.yml"), `builtin: code-quality
|
|
7116
|
+
num_reviews: ${numReviews}
|
|
7117
|
+
`);
|
|
7118
|
+
console.log(chalk10.green("Created .gauntlet/reviews/code-quality.yml"));
|
|
7119
|
+
}
|
|
7120
|
+
async function writeSkillFiles(actionDir, content, references) {
|
|
7121
|
+
await fs26.mkdir(actionDir, { recursive: true });
|
|
7122
|
+
await fs26.writeFile(path25.join(actionDir, "SKILL.md"), content);
|
|
7123
|
+
if (references) {
|
|
7124
|
+
const refsDir = path25.join(actionDir, "references");
|
|
7125
|
+
await fs26.mkdir(refsDir, { recursive: true });
|
|
7126
|
+
for (const [fileName, fileContent] of Object.entries(references)) {
|
|
7127
|
+
await fs26.writeFile(path25.join(refsDir, fileName), fileContent);
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
async function installSkillsWithChecksums(projectRoot, skipPrompts) {
|
|
7132
|
+
const skillsDir = path25.join(projectRoot, ".claude", "skills");
|
|
7133
|
+
for (const skill of SKILL_DEFINITIONS) {
|
|
7134
|
+
const actionDir = path25.join(skillsDir, `gauntlet-${skill.action}`);
|
|
7135
|
+
const skillPath = path25.join(actionDir, "SKILL.md");
|
|
7136
|
+
const references = "references" in skill ? skill.references : undefined;
|
|
7137
|
+
if (!await exists(actionDir)) {
|
|
7138
|
+
await writeSkillFiles(actionDir, skill.content, references);
|
|
7139
|
+
console.log(chalk10.green(`Created ${path25.relative(projectRoot, skillPath)}`));
|
|
7140
|
+
continue;
|
|
7141
|
+
}
|
|
7142
|
+
const expectedChecksum = computeExpectedSkillChecksum(skill.content, references);
|
|
7143
|
+
const actualChecksum = await computeSkillChecksum(actionDir);
|
|
7144
|
+
if (expectedChecksum === actualChecksum)
|
|
7145
|
+
continue;
|
|
7146
|
+
const shouldOverwrite = await promptFileOverwrite(`gauntlet-${skill.action}`, skipPrompts);
|
|
7147
|
+
if (!shouldOverwrite)
|
|
7148
|
+
continue;
|
|
7149
|
+
await fs26.rm(actionDir, { recursive: true, force: true });
|
|
7150
|
+
await writeSkillFiles(actionDir, skill.content, references);
|
|
7151
|
+
console.log(chalk10.green(`Updated ${path25.relative(projectRoot, skillPath)}`));
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
async function installHookWithChecksums(target, skipPrompts) {
|
|
7155
|
+
const spec = buildHookSpec(target);
|
|
7156
|
+
let existingConfig = {};
|
|
7157
|
+
if (await exists(spec.config.filePath)) {
|
|
7158
|
+
try {
|
|
7159
|
+
existingConfig = JSON.parse(await fs26.readFile(spec.config.filePath, "utf-8"));
|
|
7160
|
+
} catch {
|
|
7161
|
+
existingConfig = {};
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
const existingHooks = existingConfig.hooks || {};
|
|
7165
|
+
const existingEntries = Array.isArray(existingHooks[spec.config.hookKey]) ? existingHooks[spec.config.hookKey] : [];
|
|
7166
|
+
const gauntletEntries = existingEntries.filter((e) => isGauntletHookEntry(e));
|
|
7167
|
+
if (gauntletEntries.length === 0) {
|
|
7168
|
+
await installHookWithLog(spec.config, spec.installedMsg, spec.existsMsg);
|
|
7169
|
+
return;
|
|
6922
7170
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
7171
|
+
const expectedEntry = spec.config.wrapInHooksArray ? { hooks: [spec.config.hookEntry] } : spec.config.hookEntry;
|
|
7172
|
+
const expectedChecksum = computeExpectedHookChecksum([
|
|
7173
|
+
expectedEntry
|
|
7174
|
+
]);
|
|
7175
|
+
const actualChecksum = computeHookChecksum(existingEntries);
|
|
7176
|
+
if (expectedChecksum === actualChecksum) {
|
|
7177
|
+
console.log(chalk10.dim(spec.existsMsg));
|
|
7178
|
+
return;
|
|
7179
|
+
}
|
|
7180
|
+
const shouldOverwrite = await promptHookOverwrite(spec.config.filePath, skipPrompts);
|
|
7181
|
+
if (!shouldOverwrite) {
|
|
7182
|
+
console.log(chalk10.dim(spec.existsMsg));
|
|
7183
|
+
return;
|
|
7184
|
+
}
|
|
7185
|
+
const nonGauntletEntries = existingEntries.filter((e) => !isGauntletHookEntry(e));
|
|
7186
|
+
const entryToAdd = spec.config.wrapInHooksArray ? { hooks: [spec.config.hookEntry] } : spec.config.hookEntry;
|
|
7187
|
+
const newEntries = [...nonGauntletEntries, entryToAdd];
|
|
7188
|
+
const merged = {
|
|
7189
|
+
...spec.config.baseConfig ?? {},
|
|
7190
|
+
...existingConfig,
|
|
7191
|
+
hooks: {
|
|
7192
|
+
...existingHooks,
|
|
7193
|
+
[spec.config.hookKey]: newEntries
|
|
7194
|
+
}
|
|
7195
|
+
};
|
|
7196
|
+
await fs26.mkdir(path25.dirname(spec.config.filePath), { recursive: true });
|
|
7197
|
+
await fs26.writeFile(spec.config.filePath, `${JSON.stringify(merged, null, 2)}
|
|
6926
7198
|
`);
|
|
6927
|
-
console.log(
|
|
6928
|
-
|
|
7199
|
+
console.log(chalk10.green(spec.installedMsg));
|
|
7200
|
+
}
|
|
7201
|
+
async function installExternalFiles(projectRoot, devAdapters, skipPrompts) {
|
|
7202
|
+
await installSkillsWithChecksums(projectRoot, skipPrompts);
|
|
7203
|
+
await copyStatusScript(path25.join(projectRoot, ".gauntlet"));
|
|
7204
|
+
for (const adapter of devAdapters) {
|
|
7205
|
+
if (!adapter.supportsHooks())
|
|
7206
|
+
continue;
|
|
7207
|
+
if (adapter.name !== "claude" && adapter.name !== "cursor")
|
|
7208
|
+
continue;
|
|
7209
|
+
for (const kind of ["stop", "start"]) {
|
|
7210
|
+
const target = {
|
|
7211
|
+
projectRoot,
|
|
7212
|
+
variant: adapter.name,
|
|
7213
|
+
kind
|
|
7214
|
+
};
|
|
7215
|
+
await installHookWithChecksums(target, skipPrompts);
|
|
7216
|
+
}
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
function printPostInitInstructions(devCLINames) {
|
|
7220
|
+
const hasNative = devCLINames.some((name) => NATIVE_CLIS.has(name));
|
|
7221
|
+
const nonNativeNames = devCLINames.filter((name) => !NATIVE_CLIS.has(name));
|
|
7222
|
+
const hasNonNative = nonNativeNames.length > 0;
|
|
7223
|
+
console.log();
|
|
7224
|
+
if (hasNative) {
|
|
7225
|
+
console.log(chalk10.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."));
|
|
7226
|
+
}
|
|
7227
|
+
if (hasNonNative) {
|
|
7228
|
+
console.log(chalk10.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."));
|
|
7229
|
+
console.log();
|
|
7230
|
+
console.log("Available skills:");
|
|
7231
|
+
for (const s of SKILL_DEFINITIONS) {
|
|
7232
|
+
console.log(` @.claude/skills/gauntlet-${s.action}/SKILL.md — ${s.description}`);
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
6929
7235
|
}
|
|
6930
|
-
async function writeConfigYml(targetDir,
|
|
7236
|
+
async function writeConfigYml(targetDir, reviewCLINames) {
|
|
6931
7237
|
const baseBranch = await detectBaseBranch();
|
|
6932
|
-
const
|
|
6933
|
-
const cliList = sortedAdapters.map((a) => ` - ${a.name}`).join(`
|
|
7238
|
+
const cliList = reviewCLINames.map((name) => ` - ${name}`).join(`
|
|
6934
7239
|
`);
|
|
6935
|
-
const adapterSettings = buildAdapterSettingsBlock(
|
|
7240
|
+
const adapterSettings = buildAdapterSettingsBlock(reviewCLINames);
|
|
6936
7241
|
const content = `# Ordered list of CLI agents to try for reviews
|
|
6937
7242
|
cli:
|
|
6938
7243
|
default_preference:
|
|
@@ -6989,14 +7294,14 @@ entry_points: []
|
|
|
6989
7294
|
# enabled: true
|
|
6990
7295
|
# format: text # Options: text, json
|
|
6991
7296
|
`;
|
|
6992
|
-
await
|
|
6993
|
-
console.log(
|
|
7297
|
+
await fs26.writeFile(path25.join(targetDir, "config.yml"), content);
|
|
7298
|
+
console.log(chalk10.green("Created .gauntlet/config.yml"));
|
|
6994
7299
|
}
|
|
6995
7300
|
async function addToGitignore(projectRoot, entry) {
|
|
6996
|
-
const gitignorePath =
|
|
7301
|
+
const gitignorePath = path25.join(projectRoot, ".gitignore");
|
|
6997
7302
|
let content = "";
|
|
6998
7303
|
if (await exists(gitignorePath)) {
|
|
6999
|
-
content = await
|
|
7304
|
+
content = await fs26.readFile(gitignorePath, "utf-8");
|
|
7000
7305
|
const lines = content.split(`
|
|
7001
7306
|
`).map((l) => l.trim());
|
|
7002
7307
|
if (lines.includes(entry)) {
|
|
@@ -7006,9 +7311,9 @@ async function addToGitignore(projectRoot, entry) {
|
|
|
7006
7311
|
const suffix = content.length > 0 && !content.endsWith(`
|
|
7007
7312
|
`) ? `
|
|
7008
7313
|
` : "";
|
|
7009
|
-
await
|
|
7314
|
+
await fs26.appendFile(gitignorePath, `${suffix}${entry}
|
|
7010
7315
|
`);
|
|
7011
|
-
console.log(
|
|
7316
|
+
console.log(chalk10.green(`Added ${entry} to .gitignore`));
|
|
7012
7317
|
}
|
|
7013
7318
|
function gitSilent(args, opts) {
|
|
7014
7319
|
const { execFileSync } = __require("node:child_process");
|
|
@@ -7035,13 +7340,13 @@ async function detectBaseBranch() {
|
|
|
7035
7340
|
}
|
|
7036
7341
|
return "origin/main";
|
|
7037
7342
|
}
|
|
7038
|
-
function buildAdapterSettingsBlock(
|
|
7039
|
-
const items =
|
|
7343
|
+
function buildAdapterSettingsBlock(adapterNames) {
|
|
7344
|
+
const items = adapterNames.filter((name) => ADAPTER_CONFIG[name]);
|
|
7040
7345
|
if (items.length === 0)
|
|
7041
7346
|
return "";
|
|
7042
|
-
const lines = items.map((
|
|
7043
|
-
const c = ADAPTER_CONFIG[
|
|
7044
|
-
return ` ${
|
|
7347
|
+
const lines = items.map((name) => {
|
|
7348
|
+
const c = ADAPTER_CONFIG[name];
|
|
7349
|
+
return ` ${name}:
|
|
7045
7350
|
allow_tool_use: ${c?.allow_tool_use}
|
|
7046
7351
|
thinking_budget: ${c?.thinking_budget}`;
|
|
7047
7352
|
});
|
|
@@ -7057,233 +7362,171 @@ async function detectAvailableCLIs() {
|
|
|
7057
7362
|
for (const adapter of allAdapters) {
|
|
7058
7363
|
const isAvailable = await adapter.isAvailable();
|
|
7059
7364
|
if (isAvailable) {
|
|
7060
|
-
console.log(
|
|
7365
|
+
console.log(chalk10.green(` ✓ ${adapter.name}`));
|
|
7061
7366
|
available.push(adapter);
|
|
7062
7367
|
} else {
|
|
7063
|
-
console.log(
|
|
7368
|
+
console.log(chalk10.dim(` ✗ ${adapter.name} (not installed)`));
|
|
7064
7369
|
}
|
|
7065
7370
|
}
|
|
7066
7371
|
return available;
|
|
7067
7372
|
}
|
|
7068
7373
|
async function copyStatusScript(targetDir) {
|
|
7069
|
-
const statusScriptDir =
|
|
7070
|
-
const statusScriptPath =
|
|
7071
|
-
await
|
|
7374
|
+
const statusScriptDir = path25.join(targetDir, "scripts");
|
|
7375
|
+
const statusScriptPath = path25.join(statusScriptDir, "status.ts");
|
|
7376
|
+
await fs26.mkdir(statusScriptDir, { recursive: true });
|
|
7072
7377
|
if (await exists(statusScriptPath))
|
|
7073
7378
|
return;
|
|
7074
|
-
const bundledScript =
|
|
7379
|
+
const bundledScript = path25.join(path25.dirname(new URL(import.meta.url).pathname), "..", "scripts", "status.ts");
|
|
7075
7380
|
if (await exists(bundledScript)) {
|
|
7076
|
-
await
|
|
7077
|
-
console.log(
|
|
7381
|
+
await fs26.copyFile(bundledScript, statusScriptPath);
|
|
7382
|
+
console.log(chalk10.green("Created .gauntlet/scripts/status.ts"));
|
|
7078
7383
|
} else {
|
|
7079
|
-
console.log(
|
|
7384
|
+
console.log(chalk10.yellow("Warning: bundled status script not found; /gauntlet-status may fail."));
|
|
7080
7385
|
}
|
|
7081
7386
|
}
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
commands
|
|
7387
|
+
function hookHasCommand(entries, cmd) {
|
|
7388
|
+
return entries.some((hook) => {
|
|
7389
|
+
if (hook.command === cmd)
|
|
7390
|
+
return true;
|
|
7391
|
+
const nested = hook.hooks;
|
|
7392
|
+
return Array.isArray(nested) && nested.some((h) => h.command === cmd);
|
|
7089
7393
|
});
|
|
7090
7394
|
}
|
|
7091
|
-
async function
|
|
7092
|
-
const
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
}
|
|
7100
|
-
await
|
|
7101
|
-
|
|
7102
|
-
console.log(chalk9.green(`Created ${relPath}`));
|
|
7103
|
-
if (command.references) {
|
|
7104
|
-
const refsDir = path24.join(actionDir, "references");
|
|
7105
|
-
await fs25.mkdir(refsDir, { recursive: true });
|
|
7106
|
-
for (const [fileName, fileContent] of Object.entries(command.references)) {
|
|
7107
|
-
const refPath = path24.join(refsDir, fileName);
|
|
7108
|
-
if (await exists(refPath))
|
|
7109
|
-
continue;
|
|
7110
|
-
await fs25.writeFile(refPath, fileContent);
|
|
7111
|
-
const refRelPath = ctx.isUserLevel ? refPath : path24.relative(ctx.projectRoot, refPath);
|
|
7112
|
-
console.log(chalk9.green(`Created ${refRelPath}`));
|
|
7113
|
-
}
|
|
7114
|
-
}
|
|
7115
|
-
}
|
|
7116
|
-
async function installFlatCommand(adapter, commandDir, ctx, command) {
|
|
7117
|
-
const name = command.action === "run" ? "gauntlet" : command.action;
|
|
7118
|
-
const fileName = `${name}${adapter.getCommandExtension()}`;
|
|
7119
|
-
const filePath = path24.join(commandDir, fileName);
|
|
7395
|
+
async function mergeHookConfig(opts) {
|
|
7396
|
+
const {
|
|
7397
|
+
filePath,
|
|
7398
|
+
hookKey,
|
|
7399
|
+
hookEntry,
|
|
7400
|
+
deduplicateCmd,
|
|
7401
|
+
wrapInHooksArray,
|
|
7402
|
+
baseConfig
|
|
7403
|
+
} = opts;
|
|
7404
|
+
await fs26.mkdir(path25.dirname(filePath), { recursive: true });
|
|
7405
|
+
let existing = {};
|
|
7120
7406
|
if (await exists(filePath)) {
|
|
7121
|
-
const relPath2 = ctx.isUserLevel ? filePath : path24.relative(ctx.projectRoot, filePath);
|
|
7122
|
-
console.log(chalk9.dim(` ${adapter.name}: ${relPath2} already exists, skipping`));
|
|
7123
|
-
return;
|
|
7124
|
-
}
|
|
7125
|
-
const transformedContent = adapter.transformCommand(command.content);
|
|
7126
|
-
await fs25.writeFile(filePath, transformedContent);
|
|
7127
|
-
const relPath = ctx.isUserLevel ? filePath : path24.relative(ctx.projectRoot, filePath);
|
|
7128
|
-
console.log(chalk9.green(`Created ${relPath}`));
|
|
7129
|
-
}
|
|
7130
|
-
async function installSkillsForAdapter(adapter, skillDir, ctx, commands) {
|
|
7131
|
-
const resolvedSkillDir = ctx.isUserLevel ? skillDir : path24.join(ctx.projectRoot, skillDir);
|
|
7132
|
-
try {
|
|
7133
|
-
for (const command of commands) {
|
|
7134
|
-
await installSkill(resolvedSkillDir, ctx, command);
|
|
7135
|
-
}
|
|
7136
|
-
} catch (error) {
|
|
7137
|
-
const err = error;
|
|
7138
|
-
console.log(chalk9.yellow(` ${adapter.name}: Could not create skill - ${err.message}`));
|
|
7139
|
-
}
|
|
7140
|
-
}
|
|
7141
|
-
async function installFlatCommandsForAdapter(adapter, commandDir, ctx, commands) {
|
|
7142
|
-
const resolvedCommandDir = ctx.isUserLevel ? commandDir : path24.join(ctx.projectRoot, commandDir);
|
|
7143
|
-
try {
|
|
7144
|
-
await fs25.mkdir(resolvedCommandDir, { recursive: true });
|
|
7145
|
-
const flatCommands = commands.filter((c) => c.action !== "check" && c.action !== "status" && !c.skillsOnly);
|
|
7146
|
-
for (const command of flatCommands) {
|
|
7147
|
-
await installFlatCommand(adapter, resolvedCommandDir, ctx, command);
|
|
7148
|
-
}
|
|
7149
|
-
} catch (error) {
|
|
7150
|
-
const err = error;
|
|
7151
|
-
console.log(chalk9.yellow(` ${adapter.name}: Could not create command - ${err.message}`));
|
|
7152
|
-
}
|
|
7153
|
-
}
|
|
7154
|
-
async function installCommands(options) {
|
|
7155
|
-
const { level, agentNames, projectRoot, commands } = options;
|
|
7156
|
-
if (level === "none" || agentNames.length === 0)
|
|
7157
|
-
return;
|
|
7158
|
-
console.log();
|
|
7159
|
-
const allAdapters = getAllAdapters();
|
|
7160
|
-
const isUserLevel = level === "user";
|
|
7161
|
-
const ctx = { isUserLevel, projectRoot };
|
|
7162
|
-
for (const agentName of agentNames) {
|
|
7163
|
-
const adapter = allAdapters.find((a) => a.name === agentName);
|
|
7164
|
-
if (!adapter)
|
|
7165
|
-
continue;
|
|
7166
|
-
const skillDir = isUserLevel ? adapter.getUserSkillDir() : adapter.getProjectSkillDir();
|
|
7167
|
-
if (skillDir) {
|
|
7168
|
-
await installSkillsForAdapter(adapter, skillDir, ctx, commands);
|
|
7169
|
-
continue;
|
|
7170
|
-
}
|
|
7171
|
-
const commandDir = isUserLevel ? adapter.getUserCommandDir() : adapter.getProjectCommandDir();
|
|
7172
|
-
if (!commandDir)
|
|
7173
|
-
continue;
|
|
7174
|
-
await installFlatCommandsForAdapter(adapter, commandDir, ctx, commands);
|
|
7175
|
-
}
|
|
7176
|
-
}
|
|
7177
|
-
var STOP_HOOK_CONFIG = {
|
|
7178
|
-
hooks: {
|
|
7179
|
-
Stop: [
|
|
7180
|
-
{
|
|
7181
|
-
hooks: [
|
|
7182
|
-
{
|
|
7183
|
-
type: "command",
|
|
7184
|
-
command: "agent-gauntlet stop-hook",
|
|
7185
|
-
timeout: 300
|
|
7186
|
-
}
|
|
7187
|
-
]
|
|
7188
|
-
}
|
|
7189
|
-
]
|
|
7190
|
-
}
|
|
7191
|
-
};
|
|
7192
|
-
var CURSOR_STOP_HOOK_CONFIG = {
|
|
7193
|
-
version: 1,
|
|
7194
|
-
hooks: {
|
|
7195
|
-
stop: [
|
|
7196
|
-
{
|
|
7197
|
-
command: "agent-gauntlet stop-hook",
|
|
7198
|
-
loop_limit: 10
|
|
7199
|
-
}
|
|
7200
|
-
]
|
|
7201
|
-
}
|
|
7202
|
-
};
|
|
7203
|
-
async function installStopHook(projectRoot) {
|
|
7204
|
-
const claudeDir = path24.join(projectRoot, ".claude");
|
|
7205
|
-
const settingsPath = path24.join(claudeDir, "settings.local.json");
|
|
7206
|
-
await fs25.mkdir(claudeDir, { recursive: true });
|
|
7207
|
-
let existingSettings = {};
|
|
7208
|
-
if (await exists(settingsPath)) {
|
|
7209
7407
|
try {
|
|
7210
|
-
|
|
7211
|
-
existingSettings = JSON.parse(content);
|
|
7408
|
+
existing = JSON.parse(await fs26.readFile(filePath, "utf-8"));
|
|
7212
7409
|
} catch {
|
|
7213
|
-
|
|
7410
|
+
existing = {};
|
|
7214
7411
|
}
|
|
7215
7412
|
}
|
|
7216
|
-
const existingHooks =
|
|
7217
|
-
const
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
console.log(chalk9.dim("Stop hook already installed"));
|
|
7221
|
-
return;
|
|
7413
|
+
const existingHooks = existing.hooks || {};
|
|
7414
|
+
const existingEntries = Array.isArray(existingHooks[hookKey]) ? existingHooks[hookKey] : [];
|
|
7415
|
+
if (hookHasCommand(existingEntries, deduplicateCmd)) {
|
|
7416
|
+
return false;
|
|
7222
7417
|
}
|
|
7223
|
-
const
|
|
7224
|
-
const
|
|
7225
|
-
|
|
7418
|
+
const entryToAdd = wrapInHooksArray ? { hooks: [hookEntry] } : hookEntry;
|
|
7419
|
+
const newEntries = [...existingEntries, entryToAdd];
|
|
7420
|
+
const merged = {
|
|
7421
|
+
...baseConfig ?? {},
|
|
7422
|
+
...existing,
|
|
7226
7423
|
hooks: {
|
|
7227
7424
|
...existingHooks,
|
|
7228
|
-
|
|
7425
|
+
[hookKey]: newEntries
|
|
7229
7426
|
}
|
|
7230
7427
|
};
|
|
7231
|
-
await
|
|
7428
|
+
await fs26.writeFile(filePath, `${JSON.stringify(merged, null, 2)}
|
|
7232
7429
|
`);
|
|
7233
|
-
|
|
7430
|
+
return true;
|
|
7234
7431
|
}
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
const content = await fs25.readFile(hooksPath, "utf-8");
|
|
7243
|
-
existingConfig = JSON.parse(content);
|
|
7244
|
-
} catch {
|
|
7245
|
-
existingConfig = {};
|
|
7432
|
+
var START_HOOK_ENTRY = {
|
|
7433
|
+
matcher: "startup|resume|clear|compact",
|
|
7434
|
+
hooks: [
|
|
7435
|
+
{
|
|
7436
|
+
type: "command",
|
|
7437
|
+
command: "agent-gauntlet start-hook",
|
|
7438
|
+
async: false
|
|
7246
7439
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7440
|
+
]
|
|
7441
|
+
};
|
|
7442
|
+
var CURSOR_START_HOOK_ENTRY = {
|
|
7443
|
+
command: "agent-gauntlet start-hook --adapter cursor"
|
|
7444
|
+
};
|
|
7445
|
+
var STOP_HOOK_ENTRY = {
|
|
7446
|
+
type: "command",
|
|
7447
|
+
command: "agent-gauntlet stop-hook",
|
|
7448
|
+
timeout: 300
|
|
7449
|
+
};
|
|
7450
|
+
var CURSOR_STOP_HOOK_ENTRY = {
|
|
7451
|
+
command: "agent-gauntlet stop-hook",
|
|
7452
|
+
loop_limit: 10
|
|
7453
|
+
};
|
|
7454
|
+
async function installHookWithLog(config, installedMsg, existsMsg) {
|
|
7455
|
+
const added = await mergeHookConfig(config);
|
|
7456
|
+
console.log(added ? chalk10.green(installedMsg) : chalk10.dim(existsMsg));
|
|
7457
|
+
}
|
|
7458
|
+
function buildHookSpec(target) {
|
|
7459
|
+
const { projectRoot, variant, kind } = target;
|
|
7460
|
+
const isCursor = variant === "cursor";
|
|
7461
|
+
const isStop = kind === "stop";
|
|
7462
|
+
const hookConfigs = {
|
|
7463
|
+
"claude-stop": {
|
|
7464
|
+
dir: ".claude",
|
|
7465
|
+
file: "settings.local.json",
|
|
7466
|
+
hookKey: "Stop",
|
|
7467
|
+
entry: STOP_HOOK_ENTRY,
|
|
7468
|
+
cmd: "agent-gauntlet stop-hook",
|
|
7469
|
+
wrap: true
|
|
7470
|
+
},
|
|
7471
|
+
"cursor-stop": {
|
|
7472
|
+
dir: ".cursor",
|
|
7473
|
+
file: "hooks.json",
|
|
7474
|
+
hookKey: "stop",
|
|
7475
|
+
entry: CURSOR_STOP_HOOK_ENTRY,
|
|
7476
|
+
cmd: "agent-gauntlet stop-hook",
|
|
7477
|
+
wrap: false
|
|
7478
|
+
},
|
|
7479
|
+
"claude-start": {
|
|
7480
|
+
dir: ".claude",
|
|
7481
|
+
file: "settings.local.json",
|
|
7482
|
+
hookKey: "SessionStart",
|
|
7483
|
+
entry: START_HOOK_ENTRY,
|
|
7484
|
+
cmd: "agent-gauntlet start-hook",
|
|
7485
|
+
wrap: false
|
|
7486
|
+
},
|
|
7487
|
+
"cursor-start": {
|
|
7488
|
+
dir: ".cursor",
|
|
7489
|
+
file: "hooks.json",
|
|
7490
|
+
hookKey: "sessionStart",
|
|
7491
|
+
entry: CURSOR_START_HOOK_ENTRY,
|
|
7492
|
+
cmd: "agent-gauntlet start-hook --adapter cursor",
|
|
7493
|
+
wrap: false
|
|
7265
7494
|
}
|
|
7266
7495
|
};
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7496
|
+
const key = `${variant}-${kind}`;
|
|
7497
|
+
const cfg = hookConfigs[key];
|
|
7498
|
+
const prefix = isCursor ? "Cursor " : "";
|
|
7499
|
+
const kindLabel = isCursor ? kind : isStop ? "Stop" : "Start";
|
|
7500
|
+
const purpose = isStop ? "gauntlet will run automatically when agent stops" : "agent will be primed with gauntlet instructions at session start";
|
|
7501
|
+
return {
|
|
7502
|
+
config: {
|
|
7503
|
+
filePath: path25.join(projectRoot, cfg.dir, cfg.file),
|
|
7504
|
+
hookKey: cfg.hookKey,
|
|
7505
|
+
hookEntry: cfg.entry,
|
|
7506
|
+
deduplicateCmd: cfg.cmd,
|
|
7507
|
+
wrapInHooksArray: cfg.wrap,
|
|
7508
|
+
...isCursor ? { baseConfig: { version: 1 } } : {}
|
|
7509
|
+
},
|
|
7510
|
+
installedMsg: `${prefix}${kindLabel} hook installed - ${purpose}`,
|
|
7511
|
+
existsMsg: `${prefix}${kindLabel} hook already installed`
|
|
7512
|
+
};
|
|
7270
7513
|
}
|
|
7271
7514
|
// src/commands/list.ts
|
|
7272
|
-
import
|
|
7515
|
+
import chalk11 from "chalk";
|
|
7273
7516
|
function registerListCommand(program) {
|
|
7274
7517
|
program.command("list").description("List configured gates").action(async () => {
|
|
7275
7518
|
try {
|
|
7276
7519
|
const config = await loadConfig();
|
|
7277
|
-
console.log(
|
|
7520
|
+
console.log(chalk11.bold("Check Gates:"));
|
|
7278
7521
|
Object.values(config.checks).forEach((c) => {
|
|
7279
7522
|
console.log(` - ${c.name}`);
|
|
7280
7523
|
});
|
|
7281
|
-
console.log(
|
|
7524
|
+
console.log(chalk11.bold(`
|
|
7282
7525
|
Review Gates:`));
|
|
7283
7526
|
Object.values(config.reviews).forEach((r) => {
|
|
7284
7527
|
console.log(` - ${r.name} (Tools: ${r.cli_preference?.join(", ")})`);
|
|
7285
7528
|
});
|
|
7286
|
-
console.log(
|
|
7529
|
+
console.log(chalk11.bold(`
|
|
7287
7530
|
Entry Points:`));
|
|
7288
7531
|
config.project.entry_points.forEach((ep) => {
|
|
7289
7532
|
console.log(` - ${ep.path}`);
|
|
@@ -7294,12 +7537,12 @@ Entry Points:`));
|
|
|
7294
7537
|
});
|
|
7295
7538
|
} catch (error) {
|
|
7296
7539
|
const err = error;
|
|
7297
|
-
console.error(
|
|
7540
|
+
console.error(chalk11.red("Error:"), err.message);
|
|
7298
7541
|
}
|
|
7299
7542
|
});
|
|
7300
7543
|
}
|
|
7301
7544
|
// src/commands/review.ts
|
|
7302
|
-
import
|
|
7545
|
+
import chalk12 from "chalk";
|
|
7303
7546
|
function registerReviewCommand(program) {
|
|
7304
7547
|
program.command("review").description("Run only applicable reviews for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific review gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").action(async (options) => {
|
|
7305
7548
|
let config;
|
|
@@ -7321,7 +7564,7 @@ function registerReviewCommand(program) {
|
|
|
7321
7564
|
const effectiveBaseBranch = options.baseBranch || (process.env.GITHUB_BASE_REF && (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true") ? process.env.GITHUB_BASE_REF : null) || config.project.base_branch;
|
|
7322
7565
|
const autoCleanResult = await shouldAutoClean(config.project.log_dir, effectiveBaseBranch);
|
|
7323
7566
|
if (autoCleanResult.clean) {
|
|
7324
|
-
console.log(
|
|
7567
|
+
console.log(chalk12.dim(`Auto-cleaning logs (${autoCleanResult.reason})...`));
|
|
7325
7568
|
await debugLogger?.logClean("auto", autoCleanResult.reason || "unknown");
|
|
7326
7569
|
await performAutoClean(config.project.log_dir, autoCleanResult);
|
|
7327
7570
|
}
|
|
@@ -7337,7 +7580,7 @@ function registerReviewCommand(program) {
|
|
|
7337
7580
|
let changeOptions;
|
|
7338
7581
|
let passedSlotsMap;
|
|
7339
7582
|
if (isRerun) {
|
|
7340
|
-
console.log(
|
|
7583
|
+
console.log(chalk12.dim("Existing logs detected — running in verification mode..."));
|
|
7341
7584
|
const { failures: previousFailures, passedSlots } = await findPreviousFailures(config.project.log_dir, options.gate, true);
|
|
7342
7585
|
failuresMap = new Map;
|
|
7343
7586
|
for (const gateFailure of previousFailures) {
|
|
@@ -7351,7 +7594,7 @@ function registerReviewCommand(program) {
|
|
|
7351
7594
|
passedSlotsMap = passedSlots;
|
|
7352
7595
|
if (previousFailures.length > 0) {
|
|
7353
7596
|
const totalViolations = previousFailures.reduce((sum, gf) => sum + gf.adapterFailures.reduce((s, af) => s + af.violations.length, 0), 0);
|
|
7354
|
-
console.log(
|
|
7597
|
+
console.log(chalk12.yellow(`Found ${previousFailures.length} gate(s) with ${totalViolations} previous violation(s)`));
|
|
7355
7598
|
}
|
|
7356
7599
|
changeOptions = { uncommitted: true };
|
|
7357
7600
|
const executionState = await readExecutionState(config.project.log_dir);
|
|
@@ -7363,7 +7606,7 @@ function registerReviewCommand(program) {
|
|
|
7363
7606
|
if (executionState) {
|
|
7364
7607
|
const resolved = await resolveFixBase(executionState, effectiveBaseBranch);
|
|
7365
7608
|
if (resolved.warning) {
|
|
7366
|
-
console.log(
|
|
7609
|
+
console.log(chalk12.yellow(`Warning: ${resolved.warning}`));
|
|
7367
7610
|
}
|
|
7368
7611
|
if (resolved.fixBase) {
|
|
7369
7612
|
changeOptions = { fixBase: resolved.fixBase };
|
|
@@ -7383,16 +7626,16 @@ function registerReviewCommand(program) {
|
|
|
7383
7626
|
});
|
|
7384
7627
|
const expander = new EntryPointExpander;
|
|
7385
7628
|
const jobGen = new JobGenerator(config);
|
|
7386
|
-
console.log(
|
|
7629
|
+
console.log(chalk12.dim("Detecting changes..."));
|
|
7387
7630
|
const changes = await changeDetector.getChangedFiles();
|
|
7388
7631
|
if (changes.length === 0) {
|
|
7389
|
-
console.log(
|
|
7632
|
+
console.log(chalk12.green("No changes detected."));
|
|
7390
7633
|
await writeExecutionState(config.project.log_dir);
|
|
7391
7634
|
await releaseLock(config.project.log_dir);
|
|
7392
7635
|
restoreConsole?.restore();
|
|
7393
7636
|
process.exit(0);
|
|
7394
7637
|
}
|
|
7395
|
-
console.log(
|
|
7638
|
+
console.log(chalk12.dim(`Found ${changes.length} changed files.`));
|
|
7396
7639
|
const entryPoints = await expander.expand(config.project.entry_points, changes);
|
|
7397
7640
|
let jobs = jobGen.generateJobs(entryPoints);
|
|
7398
7641
|
jobs = jobs.filter((j) => j.type === "review");
|
|
@@ -7400,13 +7643,13 @@ function registerReviewCommand(program) {
|
|
|
7400
7643
|
jobs = jobs.filter((j) => j.name === options.gate);
|
|
7401
7644
|
}
|
|
7402
7645
|
if (jobs.length === 0) {
|
|
7403
|
-
console.log(
|
|
7646
|
+
console.log(chalk12.yellow("No applicable reviews for these changes."));
|
|
7404
7647
|
await writeExecutionState(config.project.log_dir);
|
|
7405
7648
|
await releaseLock(config.project.log_dir);
|
|
7406
7649
|
restoreConsole?.restore();
|
|
7407
7650
|
process.exit(0);
|
|
7408
7651
|
}
|
|
7409
|
-
console.log(
|
|
7652
|
+
console.log(chalk12.dim(`Running ${jobs.length} review(s)...`));
|
|
7410
7653
|
const runMode = isRerun ? "verification" : "full";
|
|
7411
7654
|
await debugLogger?.logRunStart(runMode, changes.length, jobs.length);
|
|
7412
7655
|
const reporter = new ConsoleReporter;
|
|
@@ -7429,15 +7672,15 @@ function registerReviewCommand(program) {
|
|
|
7429
7672
|
await releaseLock(config.project.log_dir);
|
|
7430
7673
|
}
|
|
7431
7674
|
const err = error;
|
|
7432
|
-
console.error(
|
|
7675
|
+
console.error(chalk12.red("Error:"), err.message);
|
|
7433
7676
|
restoreConsole?.restore();
|
|
7434
7677
|
process.exit(1);
|
|
7435
7678
|
}
|
|
7436
7679
|
});
|
|
7437
7680
|
}
|
|
7438
7681
|
// src/core/run-executor.ts
|
|
7439
|
-
import
|
|
7440
|
-
import
|
|
7682
|
+
import fs27 from "node:fs/promises";
|
|
7683
|
+
import path26 from "node:path";
|
|
7441
7684
|
|
|
7442
7685
|
// src/core/diff-stats.ts
|
|
7443
7686
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -7746,25 +7989,25 @@ function isProcessAlive(pid) {
|
|
|
7746
7989
|
}
|
|
7747
7990
|
}
|
|
7748
7991
|
async function tryAcquireLock(logDir) {
|
|
7749
|
-
await
|
|
7750
|
-
const lockPath =
|
|
7992
|
+
await fs27.mkdir(logDir, { recursive: true });
|
|
7993
|
+
const lockPath = path26.resolve(logDir, LOCK_FILENAME2);
|
|
7751
7994
|
try {
|
|
7752
|
-
await
|
|
7995
|
+
await fs27.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
7753
7996
|
return true;
|
|
7754
7997
|
} catch (err) {
|
|
7755
7998
|
if (typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST") {
|
|
7756
7999
|
try {
|
|
7757
|
-
const lockContent = await
|
|
8000
|
+
const lockContent = await fs27.readFile(lockPath, "utf-8");
|
|
7758
8001
|
const lockPid = parseInt(lockContent.trim(), 10);
|
|
7759
|
-
const lockStat = await
|
|
8002
|
+
const lockStat = await fs27.stat(lockPath);
|
|
7760
8003
|
const lockAgeMs = Date.now() - lockStat.mtimeMs;
|
|
7761
8004
|
const pidValid = !Number.isNaN(lockPid);
|
|
7762
8005
|
const pidDead = pidValid && !isProcessAlive(lockPid);
|
|
7763
8006
|
const lockStale = !pidValid && lockAgeMs > STALE_LOCK_MS;
|
|
7764
8007
|
if (pidDead || lockStale) {
|
|
7765
|
-
await
|
|
8008
|
+
await fs27.rm(lockPath, { force: true });
|
|
7766
8009
|
try {
|
|
7767
|
-
await
|
|
8010
|
+
await fs27.writeFile(lockPath, String(process.pid), {
|
|
7768
8011
|
flag: "wx"
|
|
7769
8012
|
});
|
|
7770
8013
|
return true;
|
|
@@ -7780,7 +8023,7 @@ async function tryAcquireLock(logDir) {
|
|
|
7780
8023
|
}
|
|
7781
8024
|
async function findLatestConsoleLog(logDir) {
|
|
7782
8025
|
try {
|
|
7783
|
-
const files = await
|
|
8026
|
+
const files = await fs27.readdir(logDir);
|
|
7784
8027
|
let maxNum = -1;
|
|
7785
8028
|
let latestFile = null;
|
|
7786
8029
|
for (const file of files) {
|
|
@@ -7796,7 +8039,7 @@ async function findLatestConsoleLog(logDir) {
|
|
|
7796
8039
|
}
|
|
7797
8040
|
}
|
|
7798
8041
|
}
|
|
7799
|
-
return latestFile ?
|
|
8042
|
+
return latestFile ? path26.join(logDir, latestFile) : null;
|
|
7800
8043
|
} catch {
|
|
7801
8044
|
return null;
|
|
7802
8045
|
}
|
|
@@ -7828,7 +8071,7 @@ var statusMessages = {
|
|
|
7828
8071
|
loop_detected: "Loop detected — rapid blocks overridden.",
|
|
7829
8072
|
interval_not_elapsed: "Run interval not elapsed.",
|
|
7830
8073
|
invalid_input: "Invalid input.",
|
|
7831
|
-
stop_hook_disabled: "
|
|
8074
|
+
stop_hook_disabled: "",
|
|
7832
8075
|
pr_push_required: "Gates passed — PR needs to be created/updated.",
|
|
7833
8076
|
ci_pending: "CI checks still running.",
|
|
7834
8077
|
ci_failed: "CI checks failed or review changes requested.",
|
|
@@ -8076,17 +8319,90 @@ function registerRunCommand(program) {
|
|
|
8076
8319
|
process.exit(isSuccessStatus(result.status) ? 0 : 1);
|
|
8077
8320
|
});
|
|
8078
8321
|
}
|
|
8322
|
+
// src/commands/start-hook.ts
|
|
8323
|
+
import fs28 from "node:fs/promises";
|
|
8324
|
+
import path27 from "node:path";
|
|
8325
|
+
import YAML7 from "yaml";
|
|
8326
|
+
var START_HOOK_MESSAGE = `<IMPORTANT>
|
|
8327
|
+
This project uses Agent Gauntlet for automated quality verification.
|
|
8328
|
+
|
|
8329
|
+
BEFORE reporting any coding task as complete, you MUST run /gauntlet-run
|
|
8330
|
+
to verify your changes pass all quality gates.
|
|
8331
|
+
|
|
8332
|
+
Run /gauntlet-run when ALL of these are true:
|
|
8333
|
+
1. You completed a coding task (implemented a feature, fixed a bug,
|
|
8334
|
+
refactored code, or made other substantive code changes)
|
|
8335
|
+
2. You are about to report the work as done, complete, or fixed
|
|
8336
|
+
|
|
8337
|
+
Do NOT run /gauntlet-run when:
|
|
8338
|
+
- You only answered questions, explored code, or ran read-only commands
|
|
8339
|
+
- You are in the middle of a multi-step task (run it at the end, not
|
|
8340
|
+
after every individual change)
|
|
8341
|
+
- The user explicitly asked to skip verification
|
|
8342
|
+
|
|
8343
|
+
If you are unsure whether to run it, run it. False positives (running
|
|
8344
|
+
unnecessarily) are far less costly than false negatives (skipping
|
|
8345
|
+
verification on code that needed it).
|
|
8346
|
+
</IMPORTANT>`;
|
|
8347
|
+
function formatClaudeOutput(message) {
|
|
8348
|
+
return JSON.stringify({
|
|
8349
|
+
hookSpecificOutput: {
|
|
8350
|
+
hookEventName: "SessionStart",
|
|
8351
|
+
additionalContext: message
|
|
8352
|
+
}
|
|
8353
|
+
});
|
|
8354
|
+
}
|
|
8355
|
+
function formatCursorOutput(message) {
|
|
8356
|
+
return message;
|
|
8357
|
+
}
|
|
8358
|
+
function isValidConfig(content) {
|
|
8359
|
+
const trimmed = content.trim();
|
|
8360
|
+
if (!trimmed) {
|
|
8361
|
+
return false;
|
|
8362
|
+
}
|
|
8363
|
+
try {
|
|
8364
|
+
const parsed = YAML7.parse(trimmed);
|
|
8365
|
+
return parsed != null && typeof parsed === "object";
|
|
8366
|
+
} catch {
|
|
8367
|
+
return false;
|
|
8368
|
+
}
|
|
8369
|
+
}
|
|
8370
|
+
function registerStartHookCommand(program) {
|
|
8371
|
+
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) => {
|
|
8372
|
+
const configPath = path27.join(process.cwd(), ".gauntlet", "config.yml");
|
|
8373
|
+
try {
|
|
8374
|
+
const content = await fs28.readFile(configPath, "utf-8");
|
|
8375
|
+
if (!isValidConfig(content)) {
|
|
8376
|
+
return;
|
|
8377
|
+
}
|
|
8378
|
+
} catch {
|
|
8379
|
+
return;
|
|
8380
|
+
}
|
|
8381
|
+
const adapter = options.adapter;
|
|
8382
|
+
try {
|
|
8383
|
+
const cwd = process.cwd();
|
|
8384
|
+
const logDir = path27.join(cwd, await getLogDir(cwd));
|
|
8385
|
+
const globalConfig = await loadGlobalConfig();
|
|
8386
|
+
const projectDebugLogConfig = await getDebugLogConfig(cwd);
|
|
8387
|
+
const debugLogConfig = mergeDebugLogConfig(projectDebugLogConfig, globalConfig.debug_log);
|
|
8388
|
+
const debugLogger = new DebugLogger(logDir, debugLogConfig);
|
|
8389
|
+
await debugLogger.logStartHook(adapter);
|
|
8390
|
+
} catch {}
|
|
8391
|
+
const output = adapter === "cursor" ? formatCursorOutput(START_HOOK_MESSAGE) : formatClaudeOutput(START_HOOK_MESSAGE);
|
|
8392
|
+
console.log(output);
|
|
8393
|
+
});
|
|
8394
|
+
}
|
|
8079
8395
|
// src/commands/validate.ts
|
|
8080
|
-
import
|
|
8396
|
+
import chalk13 from "chalk";
|
|
8081
8397
|
function registerValidateCommand(program) {
|
|
8082
8398
|
program.command("validate").description("Validate .gauntlet/ config files against schemas").action(async () => {
|
|
8083
8399
|
try {
|
|
8084
8400
|
await loadConfig();
|
|
8085
|
-
console.log(
|
|
8401
|
+
console.log(chalk13.green("All config files are valid."));
|
|
8086
8402
|
process.exitCode = 0;
|
|
8087
8403
|
} catch (error) {
|
|
8088
8404
|
const message = error instanceof Error ? error.message : String(error);
|
|
8089
|
-
console.error(
|
|
8405
|
+
console.error(chalk13.red("Validation failed:"), message);
|
|
8090
8406
|
process.exitCode = 1;
|
|
8091
8407
|
}
|
|
8092
8408
|
});
|
|
@@ -8369,6 +8685,7 @@ registerListCommand(program);
|
|
|
8369
8685
|
registerHealthCommand(program);
|
|
8370
8686
|
registerInitCommand(program);
|
|
8371
8687
|
registerValidateCommand(program);
|
|
8688
|
+
registerStartHookCommand(program);
|
|
8372
8689
|
registerStopHookCommand(program);
|
|
8373
8690
|
registerWaitCICommand(program);
|
|
8374
8691
|
registerHelpCommand(program);
|
|
@@ -8377,4 +8694,4 @@ if (process.argv.length < 3) {
|
|
|
8377
8694
|
}
|
|
8378
8695
|
program.parse(process.argv);
|
|
8379
8696
|
|
|
8380
|
-
//# debugId=
|
|
8697
|
+
//# debugId=27272999504866CC64756E2164756E21
|