@codacy/verity-cli 0.31.2 → 0.31.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 (3) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin/verity.js +340 -307
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -107,6 +107,26 @@ with one entry point.
107
107
  terminal (Ctrl+D, a pty that ends) falls back to the default instead of
108
108
  crashing init between copying the skills and wiring the hooks.
109
109
 
110
+ ## [0.31.3] — 2026-09-03
111
+
112
+ **After a rebase, Verity reviewed the wrong code.** When a branch was rebased
113
+ onto an updated main, the gate could treat all of the upstream changes as the
114
+ session's own work and block the push on findings in code the session never
115
+ touched. The cause was that the review baseline was trusted by whether its commit
116
+ still existed rather than whether it was still part of the current history, so a
117
+ rewritten baseline kept anchoring every diff.
118
+
119
+ - **The push gate reviews exactly what the push publishes** — the commits the
120
+ remote does not yet have — so a rebase, a force-push, a first push, or
121
+ re-pushing an already-published commit each review only their real new work.
122
+ - **The stop-hook baseline is trusted only while it is an ancestor of HEAD.** A
123
+ rewritten baseline is discarded and re-derived from the commits actually made
124
+ since, so upstream changes are never attributed to the session.
125
+ - **A commit made during a merge reviews only the files you resolved**, not the
126
+ whole incoming branch.
127
+ - **A slash command such as `/login` is no longer treated as a task or a goal**,
128
+ so the review is graded against what you were actually working on.
129
+
110
130
  ## [0.31.1] — 2026-08-27
111
131
 
112
132
  **Verity stops repeating itself.** A finding it had already closed could keep
package/bin/verity.js CHANGED
@@ -10833,16 +10833,9 @@ function splitLines(s) {
10833
10833
  }
10834
10834
  var SHA_RE = /^[0-9a-f]{40}$/;
10835
10835
  function readBaselineSha() {
10836
- if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10837
- let sha;
10838
- try {
10839
- sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10840
- } catch {
10841
- return null;
10842
- }
10843
- if (!SHA_RE.test(sha)) return null;
10844
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10845
- if (!reachable) {
10836
+ const sha = readRawBaselineSha();
10837
+ if (sha === null) return null;
10838
+ if (!commitResolves(sha)) {
10846
10839
  try {
10847
10840
  (0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
10848
10841
  } catch {
@@ -10851,6 +10844,16 @@ function readBaselineSha() {
10851
10844
  }
10852
10845
  return sha;
10853
10846
  }
10847
+ function readRawBaselineSha() {
10848
+ if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10849
+ let sha;
10850
+ try {
10851
+ sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10852
+ } catch {
10853
+ return null;
10854
+ }
10855
+ return SHA_RE.test(sha) ? sha : null;
10856
+ }
10854
10857
  function writeBaselineSha(sha) {
10855
10858
  if (!SHA_RE.test(sha)) return;
10856
10859
  try {
@@ -10859,6 +10862,25 @@ function writeBaselineSha(sha) {
10859
10862
  } catch {
10860
10863
  }
10861
10864
  }
10865
+ function commitObjectExists(sha) {
10866
+ if (!SHA_RE.test(sha)) return false;
10867
+ return execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10868
+ }
10869
+ function committedSinceRewrite(oldSha) {
10870
+ if (!SHA_RE.test(oldSha)) return [];
10871
+ const newLocal = new Set(splitLines(execGit(`git rev-list HEAD --not ${oldSha} --remotes`)));
10872
+ if (newLocal.size === 0) return [];
10873
+ const files = /* @__PURE__ */ new Set();
10874
+ for (const line of splitLines(execGit(`git cherry ${oldSha} HEAD`))) {
10875
+ const sp = line.indexOf(" ");
10876
+ if (sp < 0) continue;
10877
+ const mark = line.slice(0, sp);
10878
+ const sha = line.slice(sp + 1).trim();
10879
+ if (mark !== "+" || !newLocal.has(sha)) continue;
10880
+ for (const f of splitLines(execGit(`git diff-tree --no-commit-id --name-only -r ${sha}`))) files.add(f);
10881
+ }
10882
+ return [...files].filter((f) => !isVerityOwnedPath(f));
10883
+ }
10862
10884
  var VERITY_OWNED_PREFIXES = [".verity/", ".gate/", ".codacy/"];
10863
10885
  var VERITY_OWNED_FILES = ["VERITY.md"];
10864
10886
  function isVerityOwnedPath(file) {
@@ -10871,6 +10893,7 @@ function getChangedFiles() {
10871
10893
  for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
10872
10894
  for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
10873
10895
  for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
10896
+ const rawBaseline = readRawBaselineSha();
10874
10897
  const baseline = readBaselineSha();
10875
10898
  if (baseline) {
10876
10899
  const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
@@ -10878,6 +10901,13 @@ function getChangedFiles() {
10878
10901
  hasRecentCommitFiles = true;
10879
10902
  for (const f of committed) sets.add(f);
10880
10903
  }
10904
+ } else if (rawBaseline && commitObjectExists(rawBaseline)) {
10905
+ const committed = committedSinceRewrite(rawBaseline);
10906
+ logEvent("baseline_rewritten", { recovered: committed.length });
10907
+ if (committed.length > 0) {
10908
+ hasRecentCommitFiles = true;
10909
+ for (const f of committed) sets.add(f);
10910
+ }
10881
10911
  } else {
10882
10912
  const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
10883
10913
  const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
@@ -13083,6 +13113,242 @@ function getRecentCommitMessages() {
13083
13113
  }
13084
13114
  }
13085
13115
 
13116
+ // src/lib/analysis-mode.ts
13117
+ var DEBUG_PHRASES = [
13118
+ "not working",
13119
+ "doesn't work",
13120
+ "doesn't work",
13121
+ "does not work",
13122
+ "isn't working",
13123
+ "is not working",
13124
+ "can't figure out",
13125
+ "stack trace"
13126
+ ];
13127
+ var DEBUG_WORDS = [
13128
+ "fix",
13129
+ "bug",
13130
+ "broken",
13131
+ "crash",
13132
+ "crashing",
13133
+ "failing",
13134
+ "debug",
13135
+ "debugging",
13136
+ "investigate",
13137
+ "troubleshoot",
13138
+ "regression",
13139
+ "wrong"
13140
+ ];
13141
+ var DEBUG_PATTERN = new RegExp(
13142
+ [
13143
+ ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
13144
+ ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
13145
+ ].join("|"),
13146
+ "i"
13147
+ );
13148
+ var FALSE_POSITIVE_PATTERNS = [
13149
+ /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
13150
+ /\berror\s+handling\b/i,
13151
+ /\berror\s+boundar(?:y|ies)\b/i,
13152
+ /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
13153
+ /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
13154
+ ];
13155
+ function hasDebugIntent(prompt) {
13156
+ if (!DEBUG_PATTERN.test(prompt)) return false;
13157
+ for (const fp of FALSE_POSITIVE_PATTERNS) {
13158
+ if (fp.test(prompt)) return false;
13159
+ }
13160
+ return true;
13161
+ }
13162
+ var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
13163
+ var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
13164
+ function isGitOnlyPrompt(prompt) {
13165
+ if (!GIT_ONLY_PATTERN.test(prompt)) return false;
13166
+ if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
13167
+ return true;
13168
+ }
13169
+ function reconcileAnalysisMode(predictedMode, signals) {
13170
+ const mode2 = resolveAnalysisMode(predictedMode, signals);
13171
+ if (mode2 !== "skip") return mode2;
13172
+ const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
13173
+ if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
13174
+ return mode2;
13175
+ }
13176
+ function resolveAnalysisMode(predictedMode, signals) {
13177
+ if (!predictedMode || !isValidMode(predictedMode)) {
13178
+ return detectAnalysisMode(
13179
+ signals.noFilesChanged,
13180
+ signals.assistantResponse,
13181
+ signals.conversationPrompts,
13182
+ signals.actionSummary,
13183
+ signals.sessionAuthoredCode
13184
+ );
13185
+ }
13186
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13187
+ const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13188
+ switch (predictedMode) {
13189
+ case "skip":
13190
+ if (agentAuthoredCode) return "standard";
13191
+ return "skip";
13192
+ case "plan":
13193
+ if (agentAuthoredCode) return "standard";
13194
+ return "plan";
13195
+ case "debug":
13196
+ return "debug";
13197
+ case "standard":
13198
+ if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
13199
+ return agentInvestigated ? "plan" : "skip";
13200
+ }
13201
+ return "standard";
13202
+ }
13203
+ }
13204
+ function didAgentInvestigate(summary) {
13205
+ if (!summary) return false;
13206
+ return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
13207
+ }
13208
+ function isValidMode(mode2) {
13209
+ return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
13210
+ }
13211
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
13212
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13213
+ if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13214
+ if (!agentAuthoredCode) return "skip";
13215
+ }
13216
+ if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
13217
+ return "plan";
13218
+ }
13219
+ if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
13220
+ return didAgentInvestigate(actionSummary) ? "plan" : "skip";
13221
+ }
13222
+ for (const prompt of conversationPrompts) {
13223
+ if (hasDebugIntent(prompt)) {
13224
+ return "debug";
13225
+ }
13226
+ }
13227
+ return "standard";
13228
+ }
13229
+ var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
13230
+ var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
13231
+ var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
13232
+ var CHAIN_RE = /&&|\||;|\$\(|\x60/;
13233
+ function isNonAuthoringCommand(cmd) {
13234
+ if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
13235
+ if (FILE_MUTATE_RE.test(cmd)) return false;
13236
+ if (CHAIN_RE.test(cmd)) return false;
13237
+ return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
13238
+ }
13239
+ function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
13240
+ if (!actionSummary) return sessionAuthoredCode;
13241
+ if ((actionSummary.subagents ?? 0) > 0) return true;
13242
+ if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
13243
+ const commands = actionSummary.commands ?? [];
13244
+ if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
13245
+ if (sessionAuthoredCode) {
13246
+ const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
13247
+ if (!allSafe) return true;
13248
+ }
13249
+ return false;
13250
+ }
13251
+ function scopeToAuthored(files, actionSummary) {
13252
+ if (!actionSummary) return { files, signal: "no-transcript" };
13253
+ const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
13254
+ if (touched.length === 0) return { files: [], signal: "none-authored" };
13255
+ return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
13256
+ }
13257
+ function chooseReviewScope(input) {
13258
+ if (input.narrowingIsTrustworthy) return input.scopedFiles;
13259
+ if (input.recoveredScope.length > 0) return input.recoveredScope;
13260
+ return input.authorshipWasObservable ? [] : input.allForReview;
13261
+ }
13262
+ function narrowToAgentAuthored(files, actionSummary) {
13263
+ if (!actionSummary) return files;
13264
+ const touched = [
13265
+ ...actionSummary.files_edited,
13266
+ ...actionSummary.files_created
13267
+ ];
13268
+ if (touched.length === 0) return files;
13269
+ return files.filter((f) => {
13270
+ const suffix = "/" + f;
13271
+ return touched.some((t) => t === f || t.endsWith(suffix));
13272
+ });
13273
+ }
13274
+
13275
+ // src/lib/skip-detection.ts
13276
+ function isBareAckPrompt(prompt) {
13277
+ if (typeof prompt !== "string") return false;
13278
+ const trimmed = prompt.trim();
13279
+ if (trimmed.length === 0) return false;
13280
+ if (trimmed.length > 20) return false;
13281
+ const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
13282
+ return bareAckPattern.test(trimmed);
13283
+ }
13284
+ function isContinuationPrompt(prompt) {
13285
+ if (typeof prompt !== "string") return false;
13286
+ const trimmed = prompt.trim();
13287
+ if (trimmed.length === 0) return false;
13288
+ if (trimmed.length > 24) return false;
13289
+ const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
13290
+ return continuation.test(trimmed) || isBareAckPrompt(trimmed);
13291
+ }
13292
+ function isSlashCommand(prompt) {
13293
+ if (typeof prompt !== "string") return false;
13294
+ return /^\s*\/[A-Za-z][\w-]*(\s|$)/.test(prompt);
13295
+ }
13296
+ function resolveGoalPrompt(prompts) {
13297
+ if (prompts.length === 0) return null;
13298
+ const latest = prompts[prompts.length - 1];
13299
+ if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
13300
+ for (let i = prompts.length - 2; i >= 0; i--) {
13301
+ if (!isContinuationPrompt(prompts[i].prompt)) {
13302
+ return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
13303
+ }
13304
+ }
13305
+ return { entry: latest, turnsBack: 0 };
13306
+ }
13307
+ function isReflectionQuestion(response) {
13308
+ if (!response || typeof response !== "string") return false;
13309
+ const markers = [
13310
+ /reflection\s+for\s+future\s+agents/i,
13311
+ /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
13312
+ /say\s+['"]?skip['"]?\s+to\s+skip/i,
13313
+ /quick\s+reflection\s+question/i,
13314
+ // Post-flip (VRT-21): the agent drafts the reflection itself and, when
13315
+ // interactive, asks the user to confirm/correct before recording. That
13316
+ // turn authors no code either, so it's still a reflection turn.
13317
+ /reflection\s+draft/i,
13318
+ /confirm,?\s+correct,?\s+or\s+add/i
13319
+ ];
13320
+ return markers.some((m) => m.test(response));
13321
+ }
13322
+ function isMetaTaskLabel(label2) {
13323
+ if (label2 === null || label2 === void 0) return false;
13324
+ if (typeof label2 !== "string") return false;
13325
+ const trimmed = label2.trim();
13326
+ if (trimmed.length === 0) return true;
13327
+ const metaPatterns = [
13328
+ /^verity\s+[\w-]+\s+response$/i,
13329
+ // "Verity reflect response"
13330
+ /^simple user response$/i,
13331
+ /^verity\s+command$/i,
13332
+ // "Verity command"
13333
+ /^user\s+(question|reply|response|ack)$/i
13334
+ ];
13335
+ return metaPatterns.some((p) => p.test(trimmed));
13336
+ }
13337
+ function shouldSkipForBareAck(input) {
13338
+ if (!isBareAckPrompt(input.prompt)) return false;
13339
+ if (input.turnAuthoredCode) return false;
13340
+ return input.canSeeTurnAuthorship;
13341
+ }
13342
+ function isCommandOnlyTurn(input) {
13343
+ if (!input.authorshipIsObservable) return false;
13344
+ if (input.userCommandsTruncated) return false;
13345
+ const commands = input.userCommands ?? [];
13346
+ if (commands.length === 0) return false;
13347
+ if (input.agentAuthoredFiles > 0) return false;
13348
+ if (input.agentToolCalls > 0) return false;
13349
+ return commands.every(isNonAuthoringCommand);
13350
+ }
13351
+
13086
13352
  // src/lib/context-identity.ts
13087
13353
  var import_node_crypto2 = require("node:crypto");
13088
13354
  var import_node_fs8 = require("node:fs");
@@ -14177,233 +14443,6 @@ var import_node_fs17 = require("node:fs");
14177
14443
  var import_node_crypto7 = require("node:crypto");
14178
14444
  var import_node_path13 = require("node:path");
14179
14445
 
14180
- // src/lib/analysis-mode.ts
14181
- var DEBUG_PHRASES = [
14182
- "not working",
14183
- "doesn't work",
14184
- "doesn't work",
14185
- "does not work",
14186
- "isn't working",
14187
- "is not working",
14188
- "can't figure out",
14189
- "stack trace"
14190
- ];
14191
- var DEBUG_WORDS = [
14192
- "fix",
14193
- "bug",
14194
- "broken",
14195
- "crash",
14196
- "crashing",
14197
- "failing",
14198
- "debug",
14199
- "debugging",
14200
- "investigate",
14201
- "troubleshoot",
14202
- "regression",
14203
- "wrong"
14204
- ];
14205
- var DEBUG_PATTERN = new RegExp(
14206
- [
14207
- ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
14208
- ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
14209
- ].join("|"),
14210
- "i"
14211
- );
14212
- var FALSE_POSITIVE_PATTERNS = [
14213
- /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
14214
- /\berror\s+handling\b/i,
14215
- /\berror\s+boundar(?:y|ies)\b/i,
14216
- /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
14217
- /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
14218
- ];
14219
- function hasDebugIntent(prompt) {
14220
- if (!DEBUG_PATTERN.test(prompt)) return false;
14221
- for (const fp of FALSE_POSITIVE_PATTERNS) {
14222
- if (fp.test(prompt)) return false;
14223
- }
14224
- return true;
14225
- }
14226
- var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
14227
- var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
14228
- function isGitOnlyPrompt(prompt) {
14229
- if (!GIT_ONLY_PATTERN.test(prompt)) return false;
14230
- if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
14231
- return true;
14232
- }
14233
- function reconcileAnalysisMode(predictedMode, signals) {
14234
- const mode2 = resolveAnalysisMode(predictedMode, signals);
14235
- if (mode2 !== "skip") return mode2;
14236
- const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
14237
- if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
14238
- return mode2;
14239
- }
14240
- function resolveAnalysisMode(predictedMode, signals) {
14241
- if (!predictedMode || !isValidMode(predictedMode)) {
14242
- return detectAnalysisMode(
14243
- signals.noFilesChanged,
14244
- signals.assistantResponse,
14245
- signals.conversationPrompts,
14246
- signals.actionSummary,
14247
- signals.sessionAuthoredCode
14248
- );
14249
- }
14250
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
14251
- const agentInvestigated = didAgentInvestigate(signals.actionSummary);
14252
- switch (predictedMode) {
14253
- case "skip":
14254
- if (agentAuthoredCode) return "standard";
14255
- return "skip";
14256
- case "plan":
14257
- if (agentAuthoredCode) return "standard";
14258
- return "plan";
14259
- case "debug":
14260
- return "debug";
14261
- case "standard":
14262
- if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
14263
- return agentInvestigated ? "plan" : "skip";
14264
- }
14265
- return "standard";
14266
- }
14267
- }
14268
- function didAgentInvestigate(summary) {
14269
- if (!summary) return false;
14270
- return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
14271
- }
14272
- function isValidMode(mode2) {
14273
- return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
14274
- }
14275
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
14276
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
14277
- if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
14278
- if (!agentAuthoredCode) return "skip";
14279
- }
14280
- if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
14281
- return "plan";
14282
- }
14283
- if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
14284
- return didAgentInvestigate(actionSummary) ? "plan" : "skip";
14285
- }
14286
- for (const prompt of conversationPrompts) {
14287
- if (hasDebugIntent(prompt)) {
14288
- return "debug";
14289
- }
14290
- }
14291
- return "standard";
14292
- }
14293
- var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
14294
- var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
14295
- var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
14296
- var CHAIN_RE = /&&|\||;|\$\(|\x60/;
14297
- function isNonAuthoringCommand(cmd) {
14298
- if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
14299
- if (FILE_MUTATE_RE.test(cmd)) return false;
14300
- if (CHAIN_RE.test(cmd)) return false;
14301
- return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
14302
- }
14303
- function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
14304
- if (!actionSummary) return sessionAuthoredCode;
14305
- if ((actionSummary.subagents ?? 0) > 0) return true;
14306
- if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
14307
- const commands = actionSummary.commands ?? [];
14308
- if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
14309
- if (sessionAuthoredCode) {
14310
- const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
14311
- if (!allSafe) return true;
14312
- }
14313
- return false;
14314
- }
14315
- function scopeToAuthored(files, actionSummary) {
14316
- if (!actionSummary) return { files, signal: "no-transcript" };
14317
- const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
14318
- if (touched.length === 0) return { files: [], signal: "none-authored" };
14319
- return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
14320
- }
14321
- function narrowToAgentAuthored(files, actionSummary) {
14322
- if (!actionSummary) return files;
14323
- const touched = [
14324
- ...actionSummary.files_edited,
14325
- ...actionSummary.files_created
14326
- ];
14327
- if (touched.length === 0) return files;
14328
- return files.filter((f) => {
14329
- const suffix = "/" + f;
14330
- return touched.some((t) => t === f || t.endsWith(suffix));
14331
- });
14332
- }
14333
-
14334
- // src/lib/skip-detection.ts
14335
- function isBareAckPrompt(prompt) {
14336
- if (typeof prompt !== "string") return false;
14337
- const trimmed = prompt.trim();
14338
- if (trimmed.length === 0) return false;
14339
- if (trimmed.length > 20) return false;
14340
- const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
14341
- return bareAckPattern.test(trimmed);
14342
- }
14343
- function isContinuationPrompt(prompt) {
14344
- if (typeof prompt !== "string") return false;
14345
- const trimmed = prompt.trim();
14346
- if (trimmed.length === 0) return false;
14347
- if (trimmed.length > 24) return false;
14348
- const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
14349
- return continuation.test(trimmed) || isBareAckPrompt(trimmed);
14350
- }
14351
- function resolveGoalPrompt(prompts) {
14352
- if (prompts.length === 0) return null;
14353
- const latest = prompts[prompts.length - 1];
14354
- if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
14355
- for (let i = prompts.length - 2; i >= 0; i--) {
14356
- if (!isContinuationPrompt(prompts[i].prompt)) {
14357
- return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
14358
- }
14359
- }
14360
- return { entry: latest, turnsBack: 0 };
14361
- }
14362
- function isReflectionQuestion(response) {
14363
- if (!response || typeof response !== "string") return false;
14364
- const markers = [
14365
- /reflection\s+for\s+future\s+agents/i,
14366
- /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
14367
- /say\s+['"]?skip['"]?\s+to\s+skip/i,
14368
- /quick\s+reflection\s+question/i,
14369
- // Post-flip (VRT-21): the agent drafts the reflection itself and, when
14370
- // interactive, asks the user to confirm/correct before recording. That
14371
- // turn authors no code either, so it's still a reflection turn.
14372
- /reflection\s+draft/i,
14373
- /confirm,?\s+correct,?\s+or\s+add/i
14374
- ];
14375
- return markers.some((m) => m.test(response));
14376
- }
14377
- function isMetaTaskLabel(label2) {
14378
- if (label2 === null || label2 === void 0) return false;
14379
- if (typeof label2 !== "string") return false;
14380
- const trimmed = label2.trim();
14381
- if (trimmed.length === 0) return true;
14382
- const metaPatterns = [
14383
- /^verity\s+[\w-]+\s+response$/i,
14384
- // "Verity reflect response"
14385
- /^simple user response$/i,
14386
- /^verity\s+command$/i,
14387
- // "Verity command"
14388
- /^user\s+(question|reply|response|ack)$/i
14389
- ];
14390
- return metaPatterns.some((p) => p.test(trimmed));
14391
- }
14392
- function shouldSkipForBareAck(input) {
14393
- if (!isBareAckPrompt(input.prompt)) return false;
14394
- if (input.turnAuthoredCode) return false;
14395
- return input.canSeeTurnAuthorship;
14396
- }
14397
- function isCommandOnlyTurn(input) {
14398
- if (!input.authorshipIsObservable) return false;
14399
- if (input.userCommandsTruncated) return false;
14400
- const commands = input.userCommands ?? [];
14401
- if (commands.length === 0) return false;
14402
- if (input.agentAuthoredFiles > 0) return false;
14403
- if (input.agentToolCalls > 0) return false;
14404
- return commands.every(isNonAuthoringCommand);
14405
- }
14406
-
14407
14446
  // src/lib/pending-repeat.ts
14408
14447
  var STOP = /* @__PURE__ */ new Set([
14409
14448
  "the",
@@ -15863,15 +15902,18 @@ function registerIntentCommands(program2) {
15863
15902
  if (deferredToPlugin("intent capture", event.session_id ?? process.env.CLAUDE_SESSION_ID ?? null)) {
15864
15903
  process.exit(0);
15865
15904
  }
15905
+ const isPrimitive = isSlashCommand(prompt);
15866
15906
  const authForScope = await resolveToken(program2.opts().token);
15867
15907
  const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
15868
15908
  const scopeSession = event.session_id || process.env.CLAUDE_SESSION_ID || "";
15869
- await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
15870
- try {
15871
- const tok = await resolveToken(globals.token);
15872
- const session = sessionDossier(tok.ok ? tok.data.token : null, event.session_id ?? null);
15873
- if (session) recordGoal(session.d, prompt);
15874
- } catch {
15909
+ if (!isPrimitive) await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
15910
+ if (!isPrimitive) {
15911
+ try {
15912
+ const tok = await resolveToken(globals.token);
15913
+ const session = sessionDossier(tok.ok ? tok.data.token : null, event.session_id ?? null);
15914
+ if (session) recordGoal(session.d, prompt);
15915
+ } catch {
15916
+ }
15875
15917
  }
15876
15918
  try {
15877
15919
  await ensureMemoryDir();
@@ -18831,21 +18873,19 @@ function refResolves(frame, ref) {
18831
18873
  return frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]) !== "";
18832
18874
  }
18833
18875
  var SHA_RE2 = /^[0-9a-f]{40}$/;
18834
- function baselineShaAt(frame) {
18835
- if (!frame.worktreeRoot) return null;
18836
- try {
18837
- const sha = (0, import_node_fs26.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
18838
- if (!SHA_RE2.test(sha)) return null;
18839
- return refResolves(frame, sha) ? sha : null;
18840
- } catch {
18841
- return null;
18876
+ function stagedRange(frame) {
18877
+ if (!frame.worktreeRoot) return { kind: "nothing", base: null, head: "INDEX", via: "refused" };
18878
+ const mergeHead = frame.gitDir ? (0, import_node_path19.join)(frame.gitDir, "MERGE_HEAD") : null;
18879
+ if (mergeHead && (0, import_node_fs26.existsSync)(mergeHead)) {
18880
+ const vsHead = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "HEAD"]).split("\n").filter(Boolean));
18881
+ const vsMerge = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "MERGE_HEAD"]).split("\n").filter(Boolean));
18882
+ const resolutions = [...vsHead].filter((f) => vsMerge.has(f) && !isVerityOwnedPath(f));
18883
+ return { kind: "merge", base: "HEAD", head: "INDEX", via: "merge-resolutions", files: resolutions };
18842
18884
  }
18843
- }
18844
- function stagedRange() {
18845
18885
  return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
18846
18886
  }
18847
18887
  function resolvePushRange(frame, command, on) {
18848
- const nothing = (via) => ({ kind: "nothing", base: null, head: "HEAD", via });
18888
+ const nothing = (via2) => ({ kind: "nothing", base: null, head: "HEAD", via: via2 });
18849
18889
  if (!frame.worktreeRoot) return nothing("refused");
18850
18890
  const found = findMomentSegment(command, on);
18851
18891
  const segment = found ? splitSegments(command)[found.segmentIndex] : "";
@@ -18853,54 +18893,32 @@ function resolvePushRange(frame, command, on) {
18853
18893
  if (target.isDelete) return nothing("deletion");
18854
18894
  const head = target.srcRef ?? "HEAD";
18855
18895
  if (!refResolves(frame, head)) return nothing(`src-unresolvable:${head}`);
18856
- const srcName = target.srcRef;
18857
- const branchForRemote = srcName ?? frame.branch;
18858
- const candidates = [];
18859
- if (target.remote && (target.dstRef ?? srcName)) {
18860
- const dstName = (target.dstRef ?? srcName).replace(/^refs\/heads\//, "");
18861
- candidates.push({ ref: `refs/remotes/${target.remote}/${dstName}`, via: `refspec:${target.remote}/${dstName}` });
18862
- }
18863
- if (target.remote && !srcName && !target.dstRef && frame.branch) {
18864
- candidates.push({ ref: `refs/remotes/${target.remote}/${frame.branch}`, via: `remote:${target.remote}/${frame.branch}` });
18865
- }
18866
- candidates.push({ ref: srcName ? `${srcName}@{push}` : "@{push}", via: "@{push}" });
18867
- candidates.push({ ref: srcName ? `${srcName}@{upstream}` : "@{upstream}", via: "@{upstream}" });
18868
- if (branchForRemote) {
18869
- candidates.push({
18870
- ref: `refs/remotes/origin/${branchForRemote.replace(/^refs\/heads\//, "")}`,
18871
- via: `origin/${branchForRemote.replace(/^refs\/heads\//, "")}`
18872
- });
18873
- }
18874
- for (const c of candidates) {
18875
- if (!refResolves(frame, c.ref)) continue;
18876
- const mergeBase = frameGit(frame, ["merge-base", c.ref, head]);
18877
- if (SHA_RE2.test(mergeBase)) return { kind: "push", base: mergeBase, head, via: c.via };
18896
+ const remotePattern = target.remote ? `--remotes=${target.remote}` : "--remotes";
18897
+ const via = target.remote ? `publication:${target.remote}` : "publication";
18898
+ const commits = frameGit(frame, ["rev-list", head, "--not", remotePattern]).split("\n").filter(Boolean);
18899
+ if (commits.length === 0) return { kind: "nothing", base: null, head, via: "already-published" };
18900
+ const boundary = frameGit(frame, ["rev-list", head, "--not", remotePattern, "--boundary"]).split("\n").filter((l) => l.startsWith("-")).map((l) => l.slice(1));
18901
+ const base = boundary.find((s) => SHA_RE2.test(s)) ?? null;
18902
+ const files = /* @__PURE__ */ new Set();
18903
+ for (const sha of commits) {
18904
+ for (const f of frameGit(frame, ["diff-tree", "--no-commit-id", "--name-only", "-r", sha]).split("\n")) {
18905
+ if (f && !isVerityOwnedPath(f)) files.add(f);
18906
+ }
18878
18907
  }
18879
- const baseline = baselineShaAt(frame);
18880
- if (baseline) return { kind: "baseline", base: baseline, head, via: "review-baseline" };
18881
- if (refResolves(frame, `${head}~1`)) return { kind: "last-commit", base: `${head}~1`, head, via: `${head}~1` };
18882
- return nothing("no-parent");
18908
+ return { kind: "push", base, head, via, files: [...files], commits };
18883
18909
  }
18884
18910
  function rangeFiles(frame, range) {
18885
- let out;
18886
- switch (range.kind) {
18887
- case "staged":
18888
- out = frameGit(frame, ["diff", "--cached", "--name-only"]);
18889
- break;
18890
- case "push":
18891
- case "baseline":
18892
- case "last-commit":
18893
- out = frameGit(frame, ["diff", "--name-only", range.base, range.head === "INDEX" ? "HEAD" : range.head]);
18894
- break;
18895
- case "nothing":
18896
- return [];
18911
+ if (range.files) return range.files.filter((f) => !isVerityOwnedPath(f));
18912
+ if (range.kind === "staged") {
18913
+ return frameGit(frame, ["diff", "--cached", "--name-only"]).split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
18897
18914
  }
18898
- return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
18915
+ return [];
18899
18916
  }
18900
18917
  function rangeChangeSignals(frame, range, paths) {
18901
18918
  const out = /* @__PURE__ */ new Map();
18902
18919
  if (range.kind === "nothing" || paths.length === 0) return out;
18903
- const args = range.kind === "staged" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
18920
+ if (range.kind === "push" && !range.base) return out;
18921
+ const args = range.kind === "staged" || range.kind === "merge" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
18904
18922
  const diff = frameGit(frame, [...args, "--", ...paths]);
18905
18923
  let current = null;
18906
18924
  let oldSide = null;
@@ -18932,8 +18950,9 @@ function rangeChangeSignals(frame, range, paths) {
18932
18950
  return out;
18933
18951
  }
18934
18952
  function rangeMessages(frame, range) {
18935
- if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
18936
- return frameGit(frame, ["log", `${range.base}..${range.head === "INDEX" ? "HEAD" : range.head}`, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
18953
+ if (range.kind !== "push" || !range.commits || range.commits.length === 0) return "";
18954
+ const commits = range.commits.slice(0, 100);
18955
+ return frameGit(frame, ["show", "-s", "--format=%B%x00", ...commits]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
18937
18956
  }
18938
18957
  function frameTelemetry(frame, range, divergence) {
18939
18958
  const t = {
@@ -19515,7 +19534,7 @@ function channelSilence(input) {
19515
19534
  // src/lib/cli-version.ts
19516
19535
  function cliVersion() {
19517
19536
  try {
19518
- return true ? "0.31.2" : "dev";
19537
+ return true ? "0.31.3" : "dev";
19519
19538
  } catch {
19520
19539
  return "dev";
19521
19540
  }
@@ -20734,7 +20753,20 @@ async function evidence(run2) {
20734
20753
  widened_to: allForReview.length
20735
20754
  });
20736
20755
  }
20737
- const baseForReview = narrowingIsTrustworthy ? scoped.files : recoveredScope.length > 0 ? recoveredScope : allForReview;
20756
+ const authorshipWasObservable = scoped.signal === "authored" && actionSummary?.transcript_windowed !== "orphaned";
20757
+ if (!narrowingIsTrustworthy && recoveredScope.length === 0 && authorshipWasObservable) {
20758
+ logEvent("authored_nothing_reviewable", {
20759
+ touched: (actionSummary?.files_edited?.length ?? 0) + (actionSummary?.files_created?.length ?? 0),
20760
+ candidates: allForReview.length
20761
+ });
20762
+ }
20763
+ const baseForReview = chooseReviewScope({
20764
+ narrowingIsTrustworthy,
20765
+ scopedFiles: scoped.files,
20766
+ recoveredScope,
20767
+ allForReview,
20768
+ authorshipWasObservable
20769
+ });
20738
20770
  const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
20739
20771
  if (!opts.skipStatic && isCodacyAvailable()) {
20740
20772
  let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
@@ -22817,8 +22849,8 @@ async function runReview(opts, globals) {
22817
22849
  for (const p of specPaths) {
22818
22850
  if (!(0, import_node_fs39.existsSync)(p)) continue;
22819
22851
  try {
22820
- const { readFileSync: readFileSync27 } = await import("node:fs");
22821
- const content = readFileSync27(p, "utf-8");
22852
+ const { readFileSync: readFileSync26 } = await import("node:fs");
22853
+ const content = readFileSync26(p, "utf-8");
22822
22854
  specs.push({ path: p, content: content.slice(0, 10240) });
22823
22855
  } catch {
22824
22856
  }
@@ -22975,13 +23007,14 @@ function registerGuardCommand(program2) {
22975
23007
  });
22976
23008
  }
22977
23009
  function resolveMomentRange(moment, frame, command, on) {
22978
- return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
23010
+ return moment === "pre-commit" ? stagedRange(frame) : resolvePushRange(frame, command, on);
22979
23011
  }
22980
23012
  function describeRange(range) {
22981
23013
  if (range.kind === "staged") return "staged";
22982
- if (range.kind === "nothing" || !range.base) return null;
22983
- const base = /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
22984
- return `${base}..${range.head} via ${range.via}`;
23014
+ if (range.kind === "merge") return `merge (${range.via})`;
23015
+ if (range.kind === "nothing") return range.via === "already-published" ? "already published" : null;
23016
+ const base = range.base && /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
23017
+ return base ? `${base}..${range.head} via ${range.via}` : `via ${range.via}`;
22985
23018
  }
22986
23019
  function matchFlagValue(command, flags) {
22987
23020
  const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
@@ -24811,7 +24844,7 @@ function registerInitCommand(program2) {
24811
24844
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
24812
24845
  init: {
24813
24846
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
24814
- cli_version: true ? "0.31.2" : "dev"
24847
+ cli_version: true ? "0.31.3" : "dev"
24815
24848
  }
24816
24849
  });
24817
24850
  } catch (err) {
@@ -25480,8 +25513,8 @@ function registerTelemetryCommands(program2) {
25480
25513
  }
25481
25514
 
25482
25515
  // src/cli.ts
25483
- program.name("verity").description("CLI for Verity quality gate service").version("0.31.2").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
25484
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.2");
25516
+ program.name("verity").description("CLI for Verity quality gate service").version("0.31.3").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
25517
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.3");
25485
25518
  setUserNamedServiceUrl(program.opts().serviceUrl);
25486
25519
  try {
25487
25520
  await foldLegacyLocalCredential();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",