@codacy/verity-cli 0.30.0-experimental.452ead4 → 0.30.0-experimental.5188287

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 +289 -235
  2. package/package.json +1 -1
package/bin/verity.js CHANGED
@@ -10342,6 +10342,10 @@ function repoRoot() {
10342
10342
  }
10343
10343
  return _repoRoot;
10344
10344
  }
10345
+ function _resetRepoRoot() {
10346
+ _repoRoot = null;
10347
+ _mainRoot = void 0;
10348
+ }
10345
10349
  var _mainRoot;
10346
10350
  function mainWorktreeRoot() {
10347
10351
  if (_mainRoot !== void 0) return _mainRoot;
@@ -10804,13 +10808,6 @@ function execGit(cmd) {
10804
10808
  return "";
10805
10809
  }
10806
10810
  }
10807
- function execGitArgs(args) {
10808
- try {
10809
- return (0, import_node_child_process3.execFileSync)("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10810
- } catch {
10811
- return "";
10812
- }
10813
- }
10814
10811
  function splitLines(s) {
10815
10812
  return s.split("\n").filter((l) => l.length > 0);
10816
10813
  }
@@ -10877,9 +10874,6 @@ function getChangedFiles() {
10877
10874
  const filtered = Array.from(sets).filter((f) => !isVerityOwnedPath(f));
10878
10875
  return { files: filtered, hasRecentCommitFiles };
10879
10876
  }
10880
- function getStagedFiles() {
10881
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !isVerityOwnedPath(f));
10882
- }
10883
10877
  function getDirtyFiles() {
10884
10878
  const set = /* @__PURE__ */ new Set();
10885
10879
  for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
@@ -10900,33 +10894,6 @@ function showContentAtRef(ref, repoRelPath) {
10900
10894
  return null;
10901
10895
  }
10902
10896
  }
10903
- function getPushRangeFiles() {
10904
- const diff = (range) => splitLines(execGitArgs(["diff", "--name-only", range])).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10905
- const resolvers = [
10906
- () => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{push}"]) ? "@{push}..HEAD" : null,
10907
- () => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]) ? "@{upstream}..HEAD" : null,
10908
- () => {
10909
- const branch = execGitArgs(["rev-parse", "--abbrev-ref", "HEAD"]);
10910
- return branch && branch !== "HEAD" && execGitArgs(["rev-parse", "--verify", "-q", `origin/${branch}`]) ? `origin/${branch}..HEAD` : null;
10911
- }
10912
- ];
10913
- for (const resolve4 of resolvers) {
10914
- const range = resolve4();
10915
- if (range) return { files: diff(range), range };
10916
- }
10917
- const baseline = readBaselineSha();
10918
- if (baseline) {
10919
- const files = diff(`${baseline}..HEAD`);
10920
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
10921
- }
10922
- const last = diff("HEAD~1..HEAD");
10923
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
10924
- }
10925
- function getPushRangeMessages() {
10926
- const { range } = getPushRangeFiles();
10927
- if (!range) return "";
10928
- return execGitArgs(["log", range, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10929
- }
10930
10897
  function filterAnalyzable(files) {
10931
10898
  return files.filter((f) => {
10932
10899
  const ext = (0, import_node_path3.extname)(f).slice(1);
@@ -13747,6 +13714,160 @@ var import_node_fs14 = require("node:fs");
13747
13714
  var import_node_crypto7 = require("node:crypto");
13748
13715
  var import_node_path13 = require("node:path");
13749
13716
 
13717
+ // src/lib/analysis-mode.ts
13718
+ var DEBUG_PHRASES = [
13719
+ "not working",
13720
+ "doesn't work",
13721
+ "doesn't work",
13722
+ "does not work",
13723
+ "isn't working",
13724
+ "is not working",
13725
+ "can't figure out",
13726
+ "stack trace"
13727
+ ];
13728
+ var DEBUG_WORDS = [
13729
+ "fix",
13730
+ "bug",
13731
+ "broken",
13732
+ "crash",
13733
+ "crashing",
13734
+ "failing",
13735
+ "debug",
13736
+ "debugging",
13737
+ "investigate",
13738
+ "troubleshoot",
13739
+ "regression",
13740
+ "wrong"
13741
+ ];
13742
+ var DEBUG_PATTERN = new RegExp(
13743
+ [
13744
+ ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
13745
+ ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
13746
+ ].join("|"),
13747
+ "i"
13748
+ );
13749
+ var FALSE_POSITIVE_PATTERNS = [
13750
+ /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
13751
+ /\berror\s+handling\b/i,
13752
+ /\berror\s+boundar(?:y|ies)\b/i,
13753
+ /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
13754
+ /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
13755
+ ];
13756
+ function hasDebugIntent(prompt) {
13757
+ if (!DEBUG_PATTERN.test(prompt)) return false;
13758
+ for (const fp of FALSE_POSITIVE_PATTERNS) {
13759
+ if (fp.test(prompt)) return false;
13760
+ }
13761
+ return true;
13762
+ }
13763
+ var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
13764
+ 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;
13765
+ function isGitOnlyPrompt(prompt) {
13766
+ if (!GIT_ONLY_PATTERN.test(prompt)) return false;
13767
+ if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
13768
+ return true;
13769
+ }
13770
+ function reconcileAnalysisMode(predictedMode, signals) {
13771
+ const mode2 = resolveAnalysisMode(predictedMode, signals);
13772
+ if (mode2 !== "skip") return mode2;
13773
+ const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
13774
+ if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
13775
+ return mode2;
13776
+ }
13777
+ function resolveAnalysisMode(predictedMode, signals) {
13778
+ if (!predictedMode || !isValidMode(predictedMode)) {
13779
+ return detectAnalysisMode(
13780
+ signals.noFilesChanged,
13781
+ signals.assistantResponse,
13782
+ signals.conversationPrompts,
13783
+ signals.actionSummary,
13784
+ signals.sessionAuthoredCode
13785
+ );
13786
+ }
13787
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13788
+ const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13789
+ switch (predictedMode) {
13790
+ case "skip":
13791
+ if (agentAuthoredCode) return "standard";
13792
+ return "skip";
13793
+ case "plan":
13794
+ if (agentAuthoredCode) return "standard";
13795
+ return "plan";
13796
+ case "debug":
13797
+ return "debug";
13798
+ case "standard":
13799
+ if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
13800
+ return agentInvestigated ? "plan" : "skip";
13801
+ }
13802
+ return "standard";
13803
+ }
13804
+ }
13805
+ function didAgentInvestigate(summary) {
13806
+ if (!summary) return false;
13807
+ return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
13808
+ }
13809
+ function isValidMode(mode2) {
13810
+ return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
13811
+ }
13812
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
13813
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13814
+ if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13815
+ if (!agentAuthoredCode) return "skip";
13816
+ }
13817
+ if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
13818
+ return "plan";
13819
+ }
13820
+ if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
13821
+ return didAgentInvestigate(actionSummary) ? "plan" : "skip";
13822
+ }
13823
+ for (const prompt of conversationPrompts) {
13824
+ if (hasDebugIntent(prompt)) {
13825
+ return "debug";
13826
+ }
13827
+ }
13828
+ return "standard";
13829
+ }
13830
+ 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;
13831
+ var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
13832
+ 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;
13833
+ var CHAIN_RE = /&&|\||;|\$\(|\x60/;
13834
+ function isNonAuthoringCommand(cmd) {
13835
+ if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
13836
+ if (FILE_MUTATE_RE.test(cmd)) return false;
13837
+ if (CHAIN_RE.test(cmd)) return false;
13838
+ return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
13839
+ }
13840
+ function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
13841
+ if (!actionSummary) return sessionAuthoredCode;
13842
+ if ((actionSummary.subagents ?? 0) > 0) return true;
13843
+ if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
13844
+ const commands = actionSummary.commands ?? [];
13845
+ if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
13846
+ if (sessionAuthoredCode) {
13847
+ const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
13848
+ if (!allSafe) return true;
13849
+ }
13850
+ return false;
13851
+ }
13852
+ function scopeToAuthored(files, actionSummary) {
13853
+ if (!actionSummary) return { files, signal: "no-transcript" };
13854
+ const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
13855
+ if (touched.length === 0) return { files: [], signal: "none-authored" };
13856
+ return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
13857
+ }
13858
+ function narrowToAgentAuthored(files, actionSummary) {
13859
+ if (!actionSummary) return files;
13860
+ const touched = [
13861
+ ...actionSummary.files_edited,
13862
+ ...actionSummary.files_created
13863
+ ];
13864
+ if (touched.length === 0) return files;
13865
+ return files.filter((f) => {
13866
+ const suffix = "/" + f;
13867
+ return touched.some((t) => t === f || t.endsWith(suffix));
13868
+ });
13869
+ }
13870
+
13750
13871
  // src/lib/skip-detection.ts
13751
13872
  function isBareAckPrompt(prompt) {
13752
13873
  if (typeof prompt !== "string") return false;
@@ -13810,6 +13931,15 @@ function shouldSkipForBareAck(input) {
13810
13931
  if (input.turnAuthoredCode) return false;
13811
13932
  return input.canSeeTurnAuthorship;
13812
13933
  }
13934
+ function isCommandOnlyTurn(input) {
13935
+ if (!input.authorshipIsObservable) return false;
13936
+ if (input.userCommandsTruncated) return false;
13937
+ const commands = input.userCommands ?? [];
13938
+ if (commands.length === 0) return false;
13939
+ if (input.agentAuthoredFiles > 0) return false;
13940
+ if (input.agentToolCalls > 0) return false;
13941
+ return commands.every(isNonAuthoringCommand);
13942
+ }
13813
13943
 
13814
13944
  // src/lib/pending-repeat.ts
13815
13945
  var STOP = /* @__PURE__ */ new Set([
@@ -16949,6 +17079,10 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
16949
17079
  dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
16950
17080
  }
16951
17081
  const seg = segments[segmentIndex];
17082
+ const overrideMatch = /--(?:git-dir|work-tree)(?:=|\s)|\bGIT_(?:DIR|WORK_TREE|INDEX_FILE)=/.exec(seg);
17083
+ if (overrideMatch) {
17084
+ return { dir: null, named: true, unresolvable: `git-dir/work-tree/index override in command: ${overrideMatch[0].trim()}` };
17085
+ }
16952
17086
  const gitMatch = seg.match(new RegExp(`(?:^|[\\s;&|(])(?:[^\\s;&|()'"]*\\/)?git(${GIT_GLOBAL_OPTS})\\s`));
16953
17087
  if (gitMatch) {
16954
17088
  const optsRegion = gitMatch[1] ?? "";
@@ -17040,14 +17174,22 @@ function resolveFrame(input) {
17040
17174
  });
17041
17175
  let dir = baseDir;
17042
17176
  if (found) {
17043
- const target = extractCommandTarget(input.command, found.segmentIndex, baseDir);
17044
- if (target.named) anchor = "command-target";
17045
- if (target.unresolvable) return refuse(`target:${target.unresolvable}`);
17046
- if (target.dir !== null && target.dir !== baseDir) {
17047
- if (!(0, import_node_fs20.existsSync)(target.dir)) return refuse(`target:directory does not exist: ${target.dir}`);
17048
- dir = target.dir;
17049
- } else if (target.named) {
17050
- dir = target.dir ?? baseDir;
17177
+ const segments = splitSegments(input.command);
17178
+ const kindRes = found.moment === "pre-commit" ? [COMMIT_RE] : [PUSH_RE, GH_PR_RE];
17179
+ const dirs = /* @__PURE__ */ new Set();
17180
+ for (let i = 0; i < segments.length; i++) {
17181
+ if (/--dry-run\b/.test(segments[i])) continue;
17182
+ if (!kindRes.some((re) => re.test(segments[i]))) continue;
17183
+ const target = extractCommandTarget(input.command, i, baseDir);
17184
+ if (target.named) anchor = "command-target";
17185
+ if (target.unresolvable) return refuse(`target:${target.unresolvable}`);
17186
+ dirs.add(target.dir ?? baseDir);
17187
+ }
17188
+ if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
17189
+ const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
17190
+ if (targetDir !== baseDir) {
17191
+ if (!(0, import_node_fs20.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
17192
+ dir = targetDir;
17051
17193
  }
17052
17194
  }
17053
17195
  const toplevel = gitAt(dir, ["rev-parse", "--show-toplevel"]);
@@ -17148,6 +17290,10 @@ function rangeFiles(frame, range) {
17148
17290
  }
17149
17291
  return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
17150
17292
  }
17293
+ function rangeMessages(frame, range) {
17294
+ if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
17295
+ 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");
17296
+ }
17151
17297
  function frameTelemetry(frame, range, divergence) {
17152
17298
  const t = {
17153
17299
  anchor: frame.anchor,
@@ -17204,6 +17350,8 @@ var MAX_COMMAND_CHARS = 80;
17204
17350
  var MAX_TOOL_BLOCKS = 200;
17205
17351
  var MAX_SUMMARY_BYTES = 4096;
17206
17352
  var HOME = process.env.HOME ?? "";
17353
+ var BASH_INPUT_RE = /^\s*<bash-input>([\s\S]*?)<\/bash-input>/;
17354
+ var BASH_ECHO_RE = /^\s*<bash-(?:stdout|stderr)>/;
17207
17355
  async function extractActionSummary(transcriptPath) {
17208
17356
  try {
17209
17357
  const read = readTurnLines(transcriptPath);
@@ -17267,7 +17415,7 @@ function isRealUserMessage(parsed) {
17267
17415
  const message = parsed.message;
17268
17416
  if (!message) return false;
17269
17417
  const content = message.content;
17270
- if (typeof content === "string") return true;
17418
+ if (typeof content === "string") return !BASH_ECHO_RE.test(content);
17271
17419
  if (Array.isArray(content)) {
17272
17420
  return content.some((b) => {
17273
17421
  if (typeof b !== "object" || b === null) return false;
@@ -17282,6 +17430,8 @@ function buildSummary(lines) {
17282
17430
  const filesEdited = /* @__PURE__ */ new Set();
17283
17431
  const filesCreated = /* @__PURE__ */ new Set();
17284
17432
  const commands = [];
17433
+ const userCommands = [];
17434
+ let userCommandsTruncated = false;
17285
17435
  let searches = 0;
17286
17436
  let subagents = 0;
17287
17437
  let webFetches = 0;
@@ -17299,6 +17449,17 @@ function buildSummary(lines) {
17299
17449
  if (entry.type === "user" && !firstTimestamp) {
17300
17450
  firstTimestamp = entry.timestamp ?? null;
17301
17451
  }
17452
+ if (entry.type === "user") {
17453
+ const typed = userTypedCommands(entry);
17454
+ if (typed.truncated) userCommandsTruncated = true;
17455
+ for (const cmd of typed.commands) {
17456
+ if (userCommands.length >= MAX_COMMANDS) {
17457
+ userCommandsTruncated = true;
17458
+ break;
17459
+ }
17460
+ userCommands.push(cmd);
17461
+ }
17462
+ }
17302
17463
  if (entry.type !== "assistant") continue;
17303
17464
  turnMessages++;
17304
17465
  lastTimestamp = entry.timestamp ?? lastTimestamp;
@@ -17376,6 +17537,8 @@ function buildSummary(lines) {
17376
17537
  ],
17377
17538
  searches,
17378
17539
  commands,
17540
+ user_commands: userCommands,
17541
+ ...userCommandsTruncated ? { user_commands_truncated: true } : {},
17379
17542
  subagents,
17380
17543
  web_fetches: webFetches,
17381
17544
  total_tool_calls: totalToolCalls,
@@ -17384,6 +17547,8 @@ function buildSummary(lines) {
17384
17547
  };
17385
17548
  if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
17386
17549
  summary.commands = [];
17550
+ summary.user_commands = [];
17551
+ summary.user_commands_truncated = true;
17387
17552
  if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
17388
17553
  summary.files_read = summary.files_read.slice(0, 10);
17389
17554
  summary.files_edited = summary.files_edited.slice(0, 10);
@@ -17401,6 +17566,22 @@ function addPath(set, rawPath) {
17401
17566
  if (p.length > 200) p = p.slice(0, 200);
17402
17567
  set.add(p);
17403
17568
  }
17569
+ function userTypedCommands(entry) {
17570
+ const none = { commands: [], truncated: false };
17571
+ const message = entry.message;
17572
+ const content = message?.content;
17573
+ if (typeof content !== "string") return none;
17574
+ const m = BASH_INPUT_RE.exec(content);
17575
+ if (!m) return none;
17576
+ const commands = [];
17577
+ let truncated = false;
17578
+ for (const line of m[1].split("\n")) {
17579
+ if (line.length > MAX_COMMAND_CHARS) truncated = true;
17580
+ const cmd = sanitizeCommand(line);
17581
+ if (cmd) commands.push(cmd);
17582
+ }
17583
+ return { commands, truncated };
17584
+ }
17404
17585
  function sanitizeCommand(rawCmd) {
17405
17586
  if (typeof rawCmd !== "string" || !rawCmd) return null;
17406
17587
  let cmd = rawCmd.split("\n")[0];
@@ -17635,7 +17816,7 @@ function truncateToCap(text) {
17635
17816
  function buildHookOutput(gateDecision, systemMessage, agentContext) {
17636
17817
  return {
17637
17818
  gate_decision: gateDecision,
17638
- systemMessage,
17819
+ ...systemMessage === null ? {} : { systemMessage },
17639
17820
  ...agentContext ? {
17640
17821
  hookSpecificOutput: {
17641
17822
  hookEventName: "Stop",
@@ -17656,7 +17837,7 @@ function channelSilence(input) {
17656
17837
  // src/lib/cli-version.ts
17657
17838
  function cliVersion() {
17658
17839
  try {
17659
- return true ? "0.30.0-experimental.452ead4" : "dev";
17840
+ return true ? "0.30.0-experimental.5188287" : "dev";
17660
17841
  } catch {
17661
17842
  return "dev";
17662
17843
  }
@@ -17942,6 +18123,7 @@ async function passAndExit(run, reason, skip, kindOverride) {
17942
18123
  "verity-command",
17943
18124
  "bare-acknowledgment",
17944
18125
  "reflection-prompt",
18126
+ "command-only-turn",
17945
18127
  "skip-mode",
17946
18128
  "zero-increment",
17947
18129
  "debounce",
@@ -17960,10 +18142,12 @@ async function passAndExit(run, reason, skip, kindOverride) {
17960
18142
  }
17961
18143
  const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
17962
18144
  const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
18145
+ const HUMAN_SILENT_SKIPS = /* @__PURE__ */ new Set(["command-only-turn"]);
18146
+ const humanNote = HUMAN_SILENT_SKIPS.has(skip) ? null : `Verity: ${reason}`;
17963
18147
  printJsonCompact(
17964
18148
  buildHookOutput(
17965
18149
  verdict,
17966
- `Verity: ${reason}`,
18150
+ humanNote,
17967
18151
  // `additionalContext` is the agent's ONLY input. Writing only
17968
18152
  // `systemMessage` — the human's field — tells the agent nothing at all,
17969
18153
  // which is what sixteen of the nineteen terminating paths used to do.
@@ -18113,6 +18297,15 @@ function discoverPlans() {
18113
18297
  // src/commands/analyze/phases/03-intent-inputs.ts
18114
18298
  async function intentInputs(run) {
18115
18299
  const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
18300
+ if (isCommandOnlyTurn({
18301
+ userCommands: actionSummary?.user_commands,
18302
+ userCommandsTruncated: actionSummary?.user_commands_truncated,
18303
+ agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
18304
+ agentToolCalls: actionSummary?.total_tool_calls ?? 0,
18305
+ authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
18306
+ })) {
18307
+ await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
18308
+ }
18116
18309
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
18117
18310
  const specs = discoverSpecs(actionSummary?.files_read ?? []);
18118
18311
  const plans = discoverPlans();
@@ -18153,156 +18346,6 @@ async function connect(run) {
18153
18346
  Object.assign(run, { urlResult, serviceUrl: urlResult.data, token: tokenResult.data.token });
18154
18347
  }
18155
18348
 
18156
- // src/lib/analysis-mode.ts
18157
- var DEBUG_PHRASES = [
18158
- "not working",
18159
- "doesn't work",
18160
- "doesn't work",
18161
- "does not work",
18162
- "isn't working",
18163
- "is not working",
18164
- "can't figure out",
18165
- "stack trace"
18166
- ];
18167
- var DEBUG_WORDS = [
18168
- "fix",
18169
- "bug",
18170
- "broken",
18171
- "crash",
18172
- "crashing",
18173
- "failing",
18174
- "debug",
18175
- "debugging",
18176
- "investigate",
18177
- "troubleshoot",
18178
- "regression",
18179
- "wrong"
18180
- ];
18181
- var DEBUG_PATTERN = new RegExp(
18182
- [
18183
- ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
18184
- ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
18185
- ].join("|"),
18186
- "i"
18187
- );
18188
- var FALSE_POSITIVE_PATTERNS = [
18189
- /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
18190
- /\berror\s+handling\b/i,
18191
- /\berror\s+boundar(?:y|ies)\b/i,
18192
- /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
18193
- /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
18194
- ];
18195
- function hasDebugIntent(prompt) {
18196
- if (!DEBUG_PATTERN.test(prompt)) return false;
18197
- for (const fp of FALSE_POSITIVE_PATTERNS) {
18198
- if (fp.test(prompt)) return false;
18199
- }
18200
- return true;
18201
- }
18202
- var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
18203
- 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;
18204
- function isGitOnlyPrompt(prompt) {
18205
- if (!GIT_ONLY_PATTERN.test(prompt)) return false;
18206
- if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
18207
- return true;
18208
- }
18209
- function reconcileAnalysisMode(predictedMode, signals) {
18210
- const mode2 = resolveAnalysisMode(predictedMode, signals);
18211
- if (mode2 !== "skip") return mode2;
18212
- const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
18213
- if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
18214
- return mode2;
18215
- }
18216
- function resolveAnalysisMode(predictedMode, signals) {
18217
- if (!predictedMode || !isValidMode(predictedMode)) {
18218
- return detectAnalysisMode(
18219
- signals.noFilesChanged,
18220
- signals.assistantResponse,
18221
- signals.conversationPrompts,
18222
- signals.actionSummary,
18223
- signals.sessionAuthoredCode
18224
- );
18225
- }
18226
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
18227
- const agentInvestigated = didAgentInvestigate(signals.actionSummary);
18228
- switch (predictedMode) {
18229
- case "skip":
18230
- if (agentAuthoredCode) return "standard";
18231
- return "skip";
18232
- case "plan":
18233
- if (agentAuthoredCode) return "standard";
18234
- return "plan";
18235
- case "debug":
18236
- return "debug";
18237
- case "standard":
18238
- if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
18239
- return agentInvestigated ? "plan" : "skip";
18240
- }
18241
- return "standard";
18242
- }
18243
- }
18244
- function didAgentInvestigate(summary) {
18245
- if (!summary) return false;
18246
- return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
18247
- }
18248
- function isValidMode(mode2) {
18249
- return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
18250
- }
18251
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
18252
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
18253
- if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
18254
- if (!agentAuthoredCode) return "skip";
18255
- }
18256
- if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
18257
- return "plan";
18258
- }
18259
- if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
18260
- return didAgentInvestigate(actionSummary) ? "plan" : "skip";
18261
- }
18262
- for (const prompt of conversationPrompts) {
18263
- if (hasDebugIntent(prompt)) {
18264
- return "debug";
18265
- }
18266
- }
18267
- return "standard";
18268
- }
18269
- 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;
18270
- var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
18271
- 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;
18272
- var CHAIN_RE = /&&|\||;|\$\(|\x60/;
18273
- function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
18274
- if (!actionSummary) return sessionAuthoredCode;
18275
- if ((actionSummary.subagents ?? 0) > 0) return true;
18276
- if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
18277
- const commands = actionSummary.commands ?? [];
18278
- if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
18279
- if (sessionAuthoredCode) {
18280
- const allSafe = commands.length > 0 && commands.every(
18281
- (c) => !CHAIN_RE.test(c) && (GIT_PLUMBING_RE.test(c) || READ_ONLY_RE.test(c))
18282
- );
18283
- if (!allSafe) return true;
18284
- }
18285
- return false;
18286
- }
18287
- function scopeToAuthored(files, actionSummary) {
18288
- if (!actionSummary) return { files, signal: "no-transcript" };
18289
- const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
18290
- if (touched.length === 0) return { files: [], signal: "none-authored" };
18291
- return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
18292
- }
18293
- function narrowToAgentAuthored(files, actionSummary) {
18294
- if (!actionSummary) return files;
18295
- const touched = [
18296
- ...actionSummary.files_edited,
18297
- ...actionSummary.files_created
18298
- ];
18299
- if (touched.length === 0) return files;
18300
- return files.filter((f) => {
18301
- const suffix = "/" + f;
18302
- return touched.some((t) => t === f || t.endsWith(suffix));
18303
- });
18304
- }
18305
-
18306
18349
  // src/commands/analyze/phases/05-mode.ts
18307
18350
  async function mode(run) {
18308
18351
  const { opts, globals } = run;
@@ -20155,7 +20198,10 @@ async function buildRequest(run) {
20155
20198
  }
20156
20199
  if (specs.length > 0) intentContext.specs = specs;
20157
20200
  if (plans.length > 0) intentContext.plans = plans;
20158
- if (actionSummary) intentContext.action_summary = actionSummary;
20201
+ if (actionSummary) {
20202
+ const { user_commands: _uc, user_commands_truncated: _uct, ...onTheWire } = actionSummary;
20203
+ intentContext.action_summary = onTheWire;
20204
+ }
20159
20205
  for (const key of Object.keys(intentContext)) {
20160
20206
  if (intentContext[key] == null) delete intentContext[key];
20161
20207
  }
@@ -21210,9 +21256,14 @@ function registerGuardCommand(program2) {
21210
21256
  }
21211
21257
  });
21212
21258
  }
21213
- function getMomentScope(moment) {
21214
- if (moment === "pre-commit") return { files: getStagedFiles(), range: "staged" };
21215
- return getPushRangeFiles();
21259
+ function resolveMomentRange(moment, frame, command, on) {
21260
+ return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
21261
+ }
21262
+ function describeRange(range) {
21263
+ if (range.kind === "staged") return "staged";
21264
+ if (range.kind === "nothing" || !range.base) return null;
21265
+ const base = /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
21266
+ return `${base}..${range.head} via ${range.via}`;
21216
21267
  }
21217
21268
  function matchFlagValue(command, flags) {
21218
21269
  const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
@@ -21247,15 +21298,15 @@ function isSubstantiveIntent(text) {
21247
21298
  if (SHELL_PLUMBING.test(t)) return false;
21248
21299
  return true;
21249
21300
  }
21250
- function extractStatedIntent(moment, command) {
21251
- const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? (getPushRangeMessages() || null);
21301
+ function extractStatedIntent(moment, command, pushedMessages = null) {
21302
+ const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
21252
21303
  return isSubstantiveIntent(text) ? text : null;
21253
21304
  }
21254
21305
  function hasBlockingFinding(response) {
21255
21306
  const findings = response.findings ?? [];
21256
21307
  return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
21257
21308
  }
21258
- function buildGuardRequest(moment, files, codeDelta, iter, sessionId, command, coverageTelemetry) {
21309
+ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
21259
21310
  const analyzable = filterAnalyzable(files);
21260
21311
  const securityFiles = filterSecurity(files);
21261
21312
  let staticResults;
@@ -21284,7 +21335,6 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, command, c
21284
21335
  if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
21285
21336
  const specs = discoverSpecs();
21286
21337
  const plans = discoverPlans();
21287
- const statedIntent = extractStatedIntent(moment, command);
21288
21338
  if (specs.length > 0 || plans.length > 0 || statedIntent) {
21289
21339
  const intentContext = {};
21290
21340
  if (statedIntent) intentContext.user_prompt = statedIntent;
@@ -21294,7 +21344,7 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, command, c
21294
21344
  }
21295
21345
  return requestBody;
21296
21346
  }
21297
- function buildGuardCoverage(files, codeDelta, frame, frameRange, frameFiles, actualRoot) {
21347
+ function buildGuardCoverage(files, codeDelta, frame, frameRange) {
21298
21348
  const byReason = {};
21299
21349
  for (const e of codeDelta.excluded) byReason[e.reason] = (byReason[e.reason] ?? 0) + 1;
21300
21350
  return {
@@ -21308,11 +21358,7 @@ function buildGuardCoverage(files, codeDelta, frame, frameRange, frameFiles, act
21308
21358
  excluded: codeDelta.excluded.length,
21309
21359
  excluded_by_reason: byReason,
21310
21360
  transcript_windowed: null,
21311
- guard_frame: frameTelemetry(frame, frameRange, {
21312
- actualRoot,
21313
- actualFiles: files,
21314
- frameFiles
21315
- })
21361
+ guard_frame: frameTelemetry(frame, frameRange)
21316
21362
  };
21317
21363
  }
21318
21364
  function coverageSummary(c) {
@@ -21341,15 +21387,24 @@ function emitAllowNotice(userMsg, agentMsg) {
21341
21387
  async function runGuard(opts, globals) {
21342
21388
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
21343
21389
  const { command, cwd, sessionId } = await readPreToolUseStdin();
21344
- if (cwd && (0, import_node_fs35.existsSync)(cwd)) {
21345
- try {
21346
- process.chdir(cwd);
21347
- } catch {
21348
- }
21349
- }
21350
21390
  const moment = classifyCommand(command, on);
21351
21391
  if (!moment) process.exit(0);
21352
21392
  const verb = moment === "pre-commit" ? "commit" : "push";
21393
+ const { frame } = resolveFrame({ command, on, hookCwd: cwd });
21394
+ if (frame.refusal || !frame.worktreeRoot) {
21395
+ logEvent("guard_frame", { moment, ...frameTelemetry(frame, null) });
21396
+ if ((frame.refusal ?? "").startsWith("anchor:")) process.exit(0);
21397
+ emitAllowNotice(
21398
+ `\u26A0 Verity ${moment}: could not resolve the tree this ${verb} targets \u2014 ${verb}ed WITHOUT review`,
21399
+ `Verity ${moment}: the target tree could not be resolved (${frame.refusal}); the ${verb} was allowed WITHOUT a Verity review.`
21400
+ );
21401
+ }
21402
+ try {
21403
+ process.chdir(frame.worktreeRoot);
21404
+ } catch {
21405
+ process.exit(0);
21406
+ }
21407
+ _resetRepoRoot();
21353
21408
  const iter = readIter(moment);
21354
21409
  if (iter >= GUARD_BLOCK_CAP) {
21355
21410
  resetIter(moment);
@@ -21358,36 +21413,35 @@ async function runGuard(opts, globals) {
21358
21413
  `Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
21359
21414
  );
21360
21415
  }
21361
- const { files, range: actualRange } = getMomentScope(moment);
21416
+ const range = resolveMomentRange(moment, frame, command, on);
21417
+ const files = rangeFiles(frame, range);
21362
21418
  if (files.length === 0) process.exit(0);
21363
21419
  const tokenResult = await resolveToken(globals.token);
21364
21420
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
21365
21421
  if (!tokenResult.ok || !urlResult.ok) process.exit(0);
21366
- const { frame } = resolveFrame({ command, on, hookCwd: cwd });
21367
- const frameRange = moment === "pre-push" ? resolvePushRange(frame, command, on) : stagedRange();
21368
- const frameFiles = frame.refusal ? [] : rangeFiles(frame, frameRange);
21369
- const actualRoot = repoRoot();
21370
- logEvent("guard_frame", {
21371
- moment,
21372
- ...frameTelemetry(frame, frameRange, { actualRoot, actualFiles: files, frameFiles })
21373
- });
21422
+ logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
21374
21423
  const codeDelta = collectCodeDelta(files);
21375
21424
  if (codeDelta.total_files === 0) process.exit(0);
21425
+ const statedIntent = extractStatedIntent(
21426
+ moment,
21427
+ command,
21428
+ moment === "pre-push" ? rangeMessages(frame, range) || null : null
21429
+ );
21376
21430
  const requestBody = buildGuardRequest(
21377
21431
  moment,
21378
21432
  files,
21379
21433
  codeDelta,
21380
21434
  iter,
21381
21435
  sessionId,
21382
- command,
21383
- buildGuardCoverage(files, codeDelta, frame, frameRange, frameFiles, actualRoot)
21436
+ statedIntent,
21437
+ buildGuardCoverage(files, codeDelta, frame, range)
21384
21438
  );
21385
21439
  const coverage = {
21386
21440
  moment,
21387
- root: actualRoot,
21388
- branch: frame.refusal ? null : frame.branch,
21441
+ root: frame.worktreeRoot,
21442
+ branch: frame.branch,
21389
21443
  linked: frame.isLinkedWorktree,
21390
- range: actualRange,
21444
+ range: describeRange(range),
21391
21445
  sent: codeDelta.files.map((f) => f.path),
21392
21446
  excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
21393
21447
  };
@@ -22811,8 +22865,8 @@ function registerTelemetryCommands(program2) {
22811
22865
  }
22812
22866
 
22813
22867
  // src/cli.ts
22814
- program.name("verity").description("CLI for Verity quality gate service").version("0.30.0-experimental.452ead4").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) => {
22815
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.0-experimental.452ead4");
22868
+ program.name("verity").description("CLI for Verity quality gate service").version("0.30.0-experimental.5188287").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) => {
22869
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.0-experimental.5188287");
22816
22870
  setUserNamedServiceUrl(program.opts().serviceUrl);
22817
22871
  try {
22818
22872
  await foldLegacyLocalCredential();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.30.0-experimental.452ead4",
3
+ "version": "0.30.0-experimental.5188287",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",