@codacy/verity-cli 0.23.1 → 0.23.3
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/bin/verity.js +119 -28
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10902,7 +10902,9 @@ function isLegacyHook(entry) {
|
|
|
10902
10902
|
return LEGACY_STOP_RE.test(c) || LEGACY_INTENT_RE.test(c) || c.includes(".gate/hooks/analyze.sh") || c.includes(".gate/hooks/capture-intent.sh");
|
|
10903
10903
|
}
|
|
10904
10904
|
var SETTINGS_LOCAL_FILE = ".claude/settings.local.json";
|
|
10905
|
-
|
|
10905
|
+
function globalSettingsFile() {
|
|
10906
|
+
return `${process.env.HOME || process.env.USERPROFILE || ""}/.claude/settings.json`;
|
|
10907
|
+
}
|
|
10906
10908
|
async function readSettings() {
|
|
10907
10909
|
try {
|
|
10908
10910
|
const content = await (0, import_promises4.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
|
|
@@ -10912,7 +10914,7 @@ async function readSettings() {
|
|
|
10912
10914
|
}
|
|
10913
10915
|
}
|
|
10914
10916
|
async function readAllSettings() {
|
|
10915
|
-
const files = [CLAUDE_SETTINGS_FILE, SETTINGS_LOCAL_FILE,
|
|
10917
|
+
const files = [CLAUDE_SETTINGS_FILE, SETTINGS_LOCAL_FILE, globalSettingsFile()];
|
|
10916
10918
|
const out = [];
|
|
10917
10919
|
for (const f of files) {
|
|
10918
10920
|
try {
|
|
@@ -10936,6 +10938,24 @@ async function checkAllVerityHooks() {
|
|
|
10936
10938
|
}
|
|
10937
10939
|
return { stop, intent, guard, guardOn };
|
|
10938
10940
|
}
|
|
10941
|
+
async function checkExternalVerityHooks() {
|
|
10942
|
+
let stop = false;
|
|
10943
|
+
let intent = false;
|
|
10944
|
+
let guardOn = [];
|
|
10945
|
+
for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
|
|
10946
|
+
let settings;
|
|
10947
|
+
try {
|
|
10948
|
+
settings = JSON.parse(await (0, import_promises4.readFile)(f, "utf-8"));
|
|
10949
|
+
} catch {
|
|
10950
|
+
continue;
|
|
10951
|
+
}
|
|
10952
|
+
const r = checkVerityHooks(settings);
|
|
10953
|
+
stop = stop || r.stop;
|
|
10954
|
+
intent = intent || r.intent;
|
|
10955
|
+
if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
|
|
10956
|
+
}
|
|
10957
|
+
return { stop, intent, guardOn };
|
|
10958
|
+
}
|
|
10939
10959
|
async function checkAllVerityHooksDetailed() {
|
|
10940
10960
|
let stop = false;
|
|
10941
10961
|
let intent = false;
|
|
@@ -11102,6 +11122,12 @@ function reconcileMomentHooks(settings, moments, externalPresent = {
|
|
|
11102
11122
|
if (Object.keys(s.hooks).length === 0) delete s.hooks;
|
|
11103
11123
|
return s;
|
|
11104
11124
|
}
|
|
11125
|
+
async function applyMomentSelection(moments) {
|
|
11126
|
+
const external = await checkExternalVerityHooks();
|
|
11127
|
+
const settings = reconcileMomentHooks(await readSettings(), moments, external);
|
|
11128
|
+
await writeSettings(settings);
|
|
11129
|
+
return settings;
|
|
11130
|
+
}
|
|
11105
11131
|
|
|
11106
11132
|
// src/commands/hooks.ts
|
|
11107
11133
|
var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
|
|
@@ -11118,15 +11144,13 @@ function registerHooksCommands(program2) {
|
|
|
11118
11144
|
const force = opts.force ?? false;
|
|
11119
11145
|
if (opts.moments != null) {
|
|
11120
11146
|
const moments = parseMoments(opts.moments);
|
|
11121
|
-
|
|
11122
|
-
const settings2 = reconcileMomentHooks(await readSettings(), moments, external);
|
|
11123
|
-
await writeSettings(settings2);
|
|
11147
|
+
await applyMomentSelection(moments);
|
|
11124
11148
|
const status = await checkAllVerityHooks();
|
|
11125
11149
|
printInfo("Verity hooks reconciled in .claude/settings.json:");
|
|
11126
|
-
printInfo(` Stop (verity analyze): ${
|
|
11150
|
+
printInfo(` Stop (verity analyze): ${status.stop ? "on" : "off"}`);
|
|
11127
11151
|
printInfo(` Pre-commit gate: ${status.guardOn.includes("commit") ? "on" : "off"}`);
|
|
11128
11152
|
printInfo(` Pre-push/PR gate: ${status.guardOn.includes("push") ? "on" : "off"}`);
|
|
11129
|
-
printInfo(
|
|
11153
|
+
printInfo(` UserPromptSubmit (verity intent capture): ${status.intent ? "on" : "off"}`);
|
|
11130
11154
|
return;
|
|
11131
11155
|
}
|
|
11132
11156
|
const detail = await checkAllVerityHooksDetailed();
|
|
@@ -12809,6 +12833,11 @@ function getPushRangeFiles() {
|
|
|
12809
12833
|
const last = diff("HEAD~1..HEAD");
|
|
12810
12834
|
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12811
12835
|
}
|
|
12836
|
+
function getPushRangeMessages() {
|
|
12837
|
+
const { range } = getPushRangeFiles();
|
|
12838
|
+
if (!range) return "";
|
|
12839
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12840
|
+
}
|
|
12812
12841
|
function getWorktreeFiles() {
|
|
12813
12842
|
const result = [];
|
|
12814
12843
|
const worktreeDir = ".claude/worktrees";
|
|
@@ -15033,7 +15062,48 @@ function registerGuardCommand(program2) {
|
|
|
15033
15062
|
function getMomentFiles(moment) {
|
|
15034
15063
|
return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
|
|
15035
15064
|
}
|
|
15036
|
-
function
|
|
15065
|
+
function matchFlagValue(command, flags) {
|
|
15066
|
+
const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
|
|
15067
|
+
const m = re.exec(command);
|
|
15068
|
+
if (!m) return null;
|
|
15069
|
+
const v = m[2] ?? m[3] ?? m[4];
|
|
15070
|
+
return v != null ? v.replace(/\\(["'])/g, "$1") : null;
|
|
15071
|
+
}
|
|
15072
|
+
function parseCommitMessage(command) {
|
|
15073
|
+
const parts = [];
|
|
15074
|
+
const re = /(?<![\w-])(?:--message|-am|-m)(?:=|\s+)('((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)"|([^\s'"-][^\s]*))/g;
|
|
15075
|
+
let m;
|
|
15076
|
+
while ((m = re.exec(command)) !== null) {
|
|
15077
|
+
const v = m[2] ?? m[3] ?? m[4];
|
|
15078
|
+
if (v != null && v.length) parts.push(v.replace(/\\(["'])/g, "$1"));
|
|
15079
|
+
}
|
|
15080
|
+
return parts.length ? parts.join("\n\n") : null;
|
|
15081
|
+
}
|
|
15082
|
+
function parsePrIntent(command) {
|
|
15083
|
+
const title = matchFlagValue(command, "--title|-t");
|
|
15084
|
+
const body = matchFlagValue(command, "--body|-b");
|
|
15085
|
+
const parts = [title, body].filter((x) => !!x);
|
|
15086
|
+
return parts.length ? parts.join("\n\n") : null;
|
|
15087
|
+
}
|
|
15088
|
+
var TRIVIAL_INTENT = /^(wip|tmp|temp|test|fix|chore|amend|\.+|fixup!.*|squash!.*|merge\b.*)$/i;
|
|
15089
|
+
var SHELL_PLUMBING = /\$\(|`|<<-?\s*['"]?[A-Za-z_]/;
|
|
15090
|
+
function isSubstantiveIntent(text) {
|
|
15091
|
+
if (!text) return false;
|
|
15092
|
+
const t = text.trim();
|
|
15093
|
+
if (t.length < 12) return false;
|
|
15094
|
+
if (TRIVIAL_INTENT.test(t)) return false;
|
|
15095
|
+
if (SHELL_PLUMBING.test(t)) return false;
|
|
15096
|
+
return true;
|
|
15097
|
+
}
|
|
15098
|
+
function extractStatedIntent(moment, command) {
|
|
15099
|
+
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? (getPushRangeMessages() || null);
|
|
15100
|
+
return isSubstantiveIntent(text) ? text : null;
|
|
15101
|
+
}
|
|
15102
|
+
function hasBlockingFinding(response) {
|
|
15103
|
+
const findings = response.findings ?? [];
|
|
15104
|
+
return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
|
|
15105
|
+
}
|
|
15106
|
+
function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
15037
15107
|
const analyzable = filterAnalyzable(files);
|
|
15038
15108
|
const securityFiles = filterSecurity(files);
|
|
15039
15109
|
let staticResults;
|
|
@@ -15063,20 +15133,21 @@ function buildGuardRequest(moment, files, iter, sessionId) {
|
|
|
15063
15133
|
};
|
|
15064
15134
|
const specs = discoverSpecs();
|
|
15065
15135
|
const plans = discoverPlans();
|
|
15066
|
-
|
|
15136
|
+
const statedIntent = extractStatedIntent(moment, command);
|
|
15137
|
+
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
15067
15138
|
const intentContext = {};
|
|
15139
|
+
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
15068
15140
|
if (specs.length > 0) intentContext.specs = specs;
|
|
15069
15141
|
if (plans.length > 0) intentContext.plans = plans;
|
|
15070
15142
|
requestBody.intent_context = intentContext;
|
|
15071
15143
|
}
|
|
15072
15144
|
return requestBody;
|
|
15073
15145
|
}
|
|
15074
|
-
function
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
}
|
|
15146
|
+
function emitAllowNotice(userMsg, agentMsg) {
|
|
15147
|
+
process.stdout.write(JSON.stringify({
|
|
15148
|
+
systemMessage: userMsg,
|
|
15149
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: agentMsg }
|
|
15150
|
+
}) + "\n");
|
|
15080
15151
|
process.exit(0);
|
|
15081
15152
|
}
|
|
15082
15153
|
async function runGuard(opts, globals) {
|
|
@@ -15090,19 +15161,21 @@ async function runGuard(opts, globals) {
|
|
|
15090
15161
|
}
|
|
15091
15162
|
const moment = classifyCommand(command, on);
|
|
15092
15163
|
if (!moment) process.exit(0);
|
|
15164
|
+
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
15093
15165
|
const iter = readIter(moment);
|
|
15094
15166
|
if (iter >= GUARD_BLOCK_CAP) {
|
|
15095
|
-
process.stderr.write(`${DIM}Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing through.${NC}
|
|
15096
|
-
`);
|
|
15097
15167
|
resetIter(moment);
|
|
15098
|
-
|
|
15168
|
+
emitAllowNotice(
|
|
15169
|
+
`Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing the ${verb} through`,
|
|
15170
|
+
`Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
|
|
15171
|
+
);
|
|
15099
15172
|
}
|
|
15100
15173
|
const files = getMomentFiles(moment);
|
|
15101
15174
|
if (files.length === 0) process.exit(0);
|
|
15102
15175
|
const tokenResult = await resolveToken(globals.token);
|
|
15103
15176
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15104
15177
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
15105
|
-
const requestBody = buildGuardRequest(moment, files, iter, sessionId);
|
|
15178
|
+
const requestBody = buildGuardRequest(moment, files, iter, sessionId, command);
|
|
15106
15179
|
if (!requestBody) process.exit(0);
|
|
15107
15180
|
const result = await analyzeRequest({
|
|
15108
15181
|
serviceUrl: urlResult.data,
|
|
@@ -15113,21 +15186,39 @@ async function runGuard(opts, globals) {
|
|
|
15113
15186
|
cmd: "guard"
|
|
15114
15187
|
});
|
|
15115
15188
|
if (!result.ok) {
|
|
15116
|
-
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
if (opts.json) {
|
|
15121
|
-
process.stdout.write(JSON.stringify(result.data) + "\n");
|
|
15189
|
+
emitAllowNotice(
|
|
15190
|
+
`\u26A0 Verity ${moment}: service offline \u2014 ${verb}ed WITHOUT review`,
|
|
15191
|
+
`Verity ${moment}: service unavailable (${result.error}); the ${verb} was allowed WITHOUT a Verity review.`
|
|
15192
|
+
);
|
|
15122
15193
|
}
|
|
15194
|
+
if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
|
|
15123
15195
|
const response = result.data;
|
|
15124
15196
|
const decision = response.gate_decision ?? "PASS";
|
|
15125
|
-
|
|
15197
|
+
const viewUrl = response.view_url ?? "";
|
|
15198
|
+
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
15199
|
+
if (decision === "FAIL" && hasBlockingFinding(response)) {
|
|
15126
15200
|
writeIter(moment, iter + 1);
|
|
15127
15201
|
writeBlockMessage(moment, response);
|
|
15128
15202
|
process.exit(2);
|
|
15129
15203
|
}
|
|
15130
|
-
|
|
15204
|
+
resetIter(moment);
|
|
15205
|
+
if (decision === "FAIL") {
|
|
15206
|
+
const narrative = response.assessment?.narrative ?? "";
|
|
15207
|
+
emitAllowNotice(
|
|
15208
|
+
`\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding${link}`,
|
|
15209
|
+
`Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}${viewUrl ? ` Report: ${viewUrl}` : ""}`
|
|
15210
|
+
);
|
|
15211
|
+
}
|
|
15212
|
+
if (decision === "WARN") {
|
|
15213
|
+
emitAllowNotice(
|
|
15214
|
+
`\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
|
|
15215
|
+
`Verity ${moment} review: WARN (proceeding).${viewUrl ? ` Report: ${viewUrl}` : ""}`
|
|
15216
|
+
);
|
|
15217
|
+
}
|
|
15218
|
+
emitAllowNotice(
|
|
15219
|
+
`\u2713 Verity ${moment}: PASS${link}`,
|
|
15220
|
+
`Verity ${moment} review: PASS.${viewUrl ? ` Report: ${viewUrl}` : ""}`
|
|
15221
|
+
);
|
|
15131
15222
|
}
|
|
15132
15223
|
function writeBlockMessage(moment, response) {
|
|
15133
15224
|
const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
|
|
@@ -16317,7 +16408,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16317
16408
|
}
|
|
16318
16409
|
|
|
16319
16410
|
// src/cli.ts
|
|
16320
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.23.
|
|
16411
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.23.3").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16321
16412
|
registerAuthCommands(program);
|
|
16322
16413
|
registerHooksCommands(program);
|
|
16323
16414
|
registerIntentCommands(program);
|