@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.
Files changed (2) hide show
  1. package/bin/verity.js +119 -28
  2. 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
- var GLOBAL_SETTINGS_FILE = `${process.env.HOME || process.env.USERPROFILE || ""}/.claude/settings.json`;
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, GLOBAL_SETTINGS_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
- const external = await checkAllVerityHooks();
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): ${moments.includes("stop") ? "on" : "off"}`);
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(" UserPromptSubmit (verity intent capture): on");
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 buildGuardRequest(moment, files, iter, sessionId) {
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
- if (specs.length > 0 || plans.length > 0) {
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 allowWithNotice(moment, decision) {
15075
- resetIter(moment);
15076
- if (decision === "WARN") {
15077
- process.stderr.write(`${YELLOW}Verity ${moment}: WARN \u2014 proceeding. See findings in the report.${NC}
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
- process.exit(0);
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
- process.stderr.write(`${DIM}Verity ${moment}: service unavailable \u2014 allowing commit/push (${result.error}).${NC}
15117
- `);
15118
- process.exit(0);
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
- if (decision === "FAIL") {
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
- allowWithNotice(moment, decision);
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.1").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.23.1",
3
+ "version": "0.23.3",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {