@codacy/verity-cli 0.29.4-experimental.6726d0f → 0.29.4-experimental.7126d67

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 +242 -26
  2. package/package.json +1 -1
package/bin/verity.js CHANGED
@@ -10390,11 +10390,11 @@ var MAX_DELTA_BYTES = 194560;
10390
10390
  var MAX_FILES = 40;
10391
10391
  var MAX_FILE_BYTES = 51200;
10392
10392
  var DEBOUNCE_SECONDS = 30;
10393
- var MAX_SPEC_FILES = 10;
10394
- var MAX_SPEC_FILE_BYTES = 10240;
10395
- var MAX_TOTAL_SPEC_BYTES = 30720;
10393
+ var MAX_SPEC_FILES = 6;
10394
+ var MAX_SPEC_FILE_BYTES = 512e3;
10395
+ var MAX_TOTAL_SPEC_BYTES = 512e3;
10396
10396
  var MAX_PLAN_FILES = 3;
10397
- var MAX_PLAN_FILE_BYTES = 10240;
10397
+ var MAX_PLAN_FILE_BYTES = 512e3;
10398
10398
  var MAX_INTENT_CHARS = 2e3;
10399
10399
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10400
10400
  var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
@@ -15060,8 +15060,10 @@ function recordVerdict(d, v) {
15060
15060
  if (summary) appendEvent(d, { k: "goal_delivered", summary });
15061
15061
  }
15062
15062
  const lines = /* @__PURE__ */ new Map();
15063
+ const sent = new Set(v.sentPaths);
15063
15064
  for (const f of v.findings) {
15064
15065
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
15066
+ if (!sent.has(f.file)) continue;
15065
15067
  if (!lines.has(f.file)) {
15066
15068
  try {
15067
15069
  const abs = (0, import_node_path12.join)(root, f.file);
@@ -16601,6 +16603,7 @@ function createRun(opts, globals) {
16601
16603
  urlResult: { ok: false, error: "unresolved" },
16602
16604
  serviceUrl: "",
16603
16605
  token: "",
16606
+ modeDecision: null,
16604
16607
  sessionIdForMemory: "",
16605
16608
  contextFilePaths: [],
16606
16609
  analysisMode: "standard",
@@ -16713,8 +16716,15 @@ function formatRunEvidence(run, startedAt) {
16713
16716
  out += row("changed", `${run.changedUniverse.length} from git \xB7 analyzable ${run.analyzable.length} \xB7 reviewable ${run.reviewable.length} \xB7 security ${run.securityFiles.length} \xB7 forReview ${run.allForReview.length}`);
16714
16717
  const done = (phase) => run.phasesCompleted.includes(phase);
16715
16718
  const ifDone = (phase, value) => done(phase) ? value : "?";
16716
- out += row("signals", `mode=${ifDone("mode", run.analysisMode)} \xB7 baseline=${ifDone("bootstrap", run.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run.authorshipIsObservable ? "yes" : "no")}` + (run.actionSummary?.transcript_windowed ? ` \xB7 window=${run.actionSummary.transcript_windowed}` : ""));
16717
- if (!done("mode") || !done("intentInputs")) {
16719
+ const md = run.modeDecision;
16720
+ if (md) {
16721
+ const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
16722
+ out += row("mode", `${md.resolved} \xB7 ${how}` + (md.flip ? ` (flipped to plan: no delta at ${md.flip})` : "") + ` \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
16723
+ } else {
16724
+ out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16725
+ }
16726
+ out += row("signals", `baseline=${ifDone("bootstrap", run.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run.authorshipIsObservable ? "yes" : "no")}` + (run.actionSummary?.transcript_windowed ? ` \xB7 window=${run.actionSummary.transcript_windowed}` : ""));
16727
+ if (!done("intentInputs")) {
16718
16728
  out += row("", `(\`?\` = the phase that determines it did not complete \u2014 this run stopped in ${run.phaseReached})`);
16719
16729
  }
16720
16730
  out += row("sent", `${sent.length} \xB7 ${list(sent)}`);
@@ -16741,6 +16751,46 @@ function formatRunEvidence(run, startedAt) {
16741
16751
  out += row("withheld", "(nothing \u2014 every changed file was reviewed)");
16742
16752
  }
16743
16753
  }
16754
+ const cov = run.foldResult?.coverage;
16755
+ if (cov) {
16756
+ const delegated = run.foldResult.authored.filter((a) => a.owner === "subagent").length;
16757
+ if (cov.dispatched > 0 || cov.subagentFiles > 0 || cov.subagentSkipped > 0) {
16758
+ out += row("delegated", `${cov.dispatched} dispatched \xB7 ${cov.subagentFiles} agent log(s) read \xB7 ${delegated} path(s) attributed to subagents`);
16759
+ }
16760
+ if (cov.subagentSkipped > 0) {
16761
+ out += row("", `\u26A0 ${cov.subagentSkipped} agent log(s) REFUSED by the byte budget \u2014 the authored set above is PARTIAL`);
16762
+ }
16763
+ if (cov.dispatched > 0 && cov.subagentFiles === 0) {
16764
+ out += row("", `\u26A0 this turn dispatched ${cov.dispatched} agent(s) but no agent log was found \u2014 delegated work is unattributed (has the transcript layout moved?)`);
16765
+ }
16766
+ if (cov.outsideRepo > 0) {
16767
+ out += row("", `${cov.outsideRepo} authored path(s) refused as outside the repo`);
16768
+ }
16769
+ }
16770
+ if (run.foldResult?.tools?.length) {
16771
+ const shown = run.foldResult.tools.slice(0, 6).map((t) => {
16772
+ const outcome = t.failed > 0 ? `${t.failed} failed` : t.last_status === 0 ? "ok" : "?";
16773
+ const where = t.targets.length > 0 ? ` \u2192 ${t.targets.slice(0, 2).join(", ")}` : "";
16774
+ return `${t.runs}\xD7 ${t.name} (${outcome})${where}`;
16775
+ });
16776
+ const more = run.foldResult.tools.length > 6 ? ` \u2026 +${run.foldResult.tools.length - 6} more` : "";
16777
+ out += row("tools", shown.join(" \xB7 ") + more);
16778
+ if (run.foldResult.coverage.toolNamesDropped > 0) {
16779
+ out += row("", `\u26A0 ${run.foldResult.coverage.toolNamesDropped} tool name(s) refused by the cap`);
16780
+ }
16781
+ }
16782
+ if (run.foldResult?.tasks?.length) {
16783
+ const t = run.foldResult.tasks;
16784
+ const done2 = t.filter((x) => x.status === "completed").length;
16785
+ out += row("tasks", `${t.length} \xB7 ${done2} completed \xB7 ` + list(t.slice(0, 4).map((x) => `#${x.id} ${x.name} [${x.status}]`), 4));
16786
+ }
16787
+ if (run.specs?.length) {
16788
+ const readThisSession = new Set(run.actionSummary?.files_read ?? []);
16789
+ const labelled = run.specs.map(
16790
+ (s) => `${s.path}${readThisSession.has(s.path) ? " (read)" : " (positional)"}`
16791
+ );
16792
+ out += row("specs", `${run.specs.length} \xB7 ${list(labelled, 5)}`);
16793
+ }
16744
16794
  if (run.staticResults.findings.length > 0) {
16745
16795
  out += row("static", `${run.staticResults.findings.length} finding(s) from ${run.staticResults.summary.tools_run.join(", ") || "no tools"}`);
16746
16796
  }
@@ -16936,6 +16986,8 @@ function buildSummary(lines) {
16936
16986
  break;
16937
16987
  case "Agent":
16938
16988
  case "Task":
16989
+ case "Workflow":
16990
+ case "SendMessage":
16939
16991
  subagents++;
16940
16992
  break;
16941
16993
  case "WebFetch":
@@ -17190,7 +17242,7 @@ function channelSilence(input) {
17190
17242
  // src/lib/cli-version.ts
17191
17243
  function cliVersion() {
17192
17244
  try {
17193
- return true ? "0.29.4-experimental.6726d0f" : "dev";
17245
+ return true ? "0.29.4-experimental.7126d67" : "dev";
17194
17246
  } catch {
17195
17247
  return "dev";
17196
17248
  }
@@ -17541,18 +17593,26 @@ var SPEC_CANDIDATES = [
17541
17593
  "docs/API.md",
17542
17594
  "spec/ARCHITECTURE.md"
17543
17595
  ];
17544
- function discoverSpecs() {
17596
+ var DOC_EXT = /\.(md|mdx|ya?ml|txt|rst|adoc)$/i;
17597
+ var UNCONSULTED_FILE_BYTES = 10240;
17598
+ var UNCONSULTED_TOTAL_BYTES = 30720;
17599
+ function discoverSpecs(consulted = []) {
17545
17600
  const result = [];
17546
17601
  const seen = /* @__PURE__ */ new Set();
17547
17602
  let totalBytes = 0;
17548
- const addSpec = (specPath) => {
17603
+ const consultedDocs = new Set(
17604
+ consulted.filter((p) => DOC_EXT.test(p) && !p.startsWith("/") && !p.includes(".."))
17605
+ );
17606
+ const addSpec = (specPath, relevant = false) => {
17549
17607
  if (result.length >= MAX_SPEC_FILES) return false;
17550
- if (totalBytes >= MAX_TOTAL_SPEC_BYTES) return false;
17608
+ const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
17609
+ if (totalBytes >= totalCap) return false;
17551
17610
  if (seen.has(specPath)) return true;
17552
17611
  if (!(0, import_node_fs21.existsSync)(specPath)) return true;
17553
17612
  seen.add(specPath);
17554
- const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
17555
- const readBytes = Math.min(MAX_SPEC_FILE_BYTES, remaining);
17613
+ const remaining = totalCap - totalBytes;
17614
+ const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
17615
+ const readBytes = Math.min(fileCap, remaining);
17556
17616
  try {
17557
17617
  const buf = Buffer.alloc(readBytes);
17558
17618
  const fd = (0, import_node_fs21.openSync)(specPath, "r");
@@ -17566,6 +17626,9 @@ function discoverSpecs() {
17566
17626
  }
17567
17627
  return true;
17568
17628
  };
17629
+ for (const doc of consultedDocs) {
17630
+ if (!addSpec(doc, true)) break;
17631
+ }
17569
17632
  for (const candidate of SPEC_CANDIDATES) {
17570
17633
  if (!addSpec(candidate)) break;
17571
17634
  }
@@ -17636,7 +17699,7 @@ function discoverPlans() {
17636
17699
  async function intentInputs(run) {
17637
17700
  const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
17638
17701
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
17639
- const specs = discoverSpecs();
17702
+ const specs = discoverSpecs(actionSummary?.files_read ?? []);
17640
17703
  const plans = discoverPlans();
17641
17704
  const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
17642
17705
  const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
@@ -17859,7 +17922,8 @@ async function mode(run) {
17859
17922
  let analysisMode;
17860
17923
  const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
17861
17924
  const modeOverride = opts.mode;
17862
- if (modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride)) {
17925
+ const forced = !!modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride);
17926
+ if (forced) {
17863
17927
  analysisMode = modeOverride;
17864
17928
  } else {
17865
17929
  analysisMode = reconcileAnalysisMode(
@@ -17867,6 +17931,25 @@ async function mode(run) {
17867
17931
  { noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
17868
17932
  );
17869
17933
  }
17934
+ const investigated = didAgentInvestigate(actionSummary);
17935
+ run.modeDecision = {
17936
+ predicted: predictedMode ?? null,
17937
+ resolved: analysisMode,
17938
+ authored: turnAuthoredCode,
17939
+ investigated,
17940
+ forced
17941
+ };
17942
+ logEvent("mode_resolved", {
17943
+ predicted: predictedMode ?? null,
17944
+ resolved: analysisMode,
17945
+ forced,
17946
+ authored: turnAuthoredCode,
17947
+ investigated,
17948
+ // The two counters that decide `investigated`, so a false reading is
17949
+ // traceable to the tool that was not recognised.
17950
+ subagents: actionSummary?.subagents ?? null,
17951
+ files_read: actionSummary?.files_read.length ?? null
17952
+ });
17870
17953
  if (analysisMode === "skip") {
17871
17954
  await passAndExit(
17872
17955
  run,
@@ -18132,6 +18215,18 @@ function commandShape(cmd) {
18132
18215
  return out.join(" ").slice(0, COMMAND_HEAD_CHARS);
18133
18216
  }
18134
18217
  var COMMAND_HEAD_CHARS = 80;
18218
+ var MAX_TOOL_NAMES = 64;
18219
+ var MAX_TOOL_TARGETS = 3;
18220
+ var MAX_TASKS = 64;
18221
+ var TASK_NAME_CHARS = 120;
18222
+ function toolTarget(name, input) {
18223
+ if (name === "Bash") return null;
18224
+ for (const key of ["file_path", "notebook_path", "path", "filePath"]) {
18225
+ const v = input[key];
18226
+ if (typeof v === "string" && v) return v.slice(0, 120);
18227
+ }
18228
+ return null;
18229
+ }
18135
18230
  var rootCandidateCache = /* @__PURE__ */ new Map();
18136
18231
  function candidateRoots(repoRoot2) {
18137
18232
  const cached2 = rootCandidateCache.get(repoRoot2);
@@ -18160,27 +18255,46 @@ function toRepoRelative(path, repoRoot2) {
18160
18255
  }
18161
18256
  return p.replace(/^\/+/, "");
18162
18257
  }
18258
+ function isInsideRepo(path, repoRoot2) {
18259
+ const p = path.replace(/\\/g, "/");
18260
+ if (!isAbsolutePath(p)) return true;
18261
+ if (!repoRoot2) return true;
18262
+ return candidateRoots(repoRoot2).some((root) => p === root || p.startsWith(root + "/"));
18263
+ }
18264
+ function isAbsolutePath(p) {
18265
+ return p.startsWith("/") || /^[A-Za-z]:\//.test(p);
18266
+ }
18163
18267
  function fold(transcriptPath, opts = {}) {
18164
18268
  const result = {
18165
18269
  authored: [],
18166
18270
  unobserved: [],
18167
18271
  commands: [],
18272
+ tools: [],
18273
+ tasks: [],
18168
18274
  unknownTypes: [],
18169
18275
  coverage: {
18170
18276
  recordCounts: {},
18171
18277
  totalRecords: 0,
18172
18278
  malformed: 0,
18173
18279
  subagentFiles: 0,
18280
+ outsideRepo: 0,
18281
+ toolNamesDropped: 0,
18174
18282
  dispatched: 0,
18175
18283
  userMessages: 0,
18176
18284
  subagentSkipped: 0,
18177
18285
  compactions: 0,
18178
18286
  complete: false
18179
- }
18287
+ },
18288
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18180
18289
  };
18290
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18181
18291
  const byPath = /* @__PURE__ */ new Map();
18182
18292
  const commandStats = /* @__PURE__ */ new Map();
18183
18293
  const pendingByToolUse = /* @__PURE__ */ new Map();
18294
+ const toolStats = /* @__PURE__ */ new Map();
18295
+ const pendingToolName = /* @__PURE__ */ new Map();
18296
+ const taskById = /* @__PURE__ */ new Map();
18297
+ const pendingTaskName = /* @__PURE__ */ new Map();
18184
18298
  const unknown = /* @__PURE__ */ new Set();
18185
18299
  const ingest = (raw, owner) => {
18186
18300
  for (const line of raw.split("\n")) {
@@ -18199,8 +18313,12 @@ function fold(transcriptPath, opts = {}) {
18199
18313
  if (type === "system" && record.subtype === "compact_boundary") {
18200
18314
  result.coverage.compactions++;
18201
18315
  }
18202
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18203
- collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
18316
+ if (type === "user" && hasUserText(record)) {
18317
+ result.coverage.userMessages++;
18318
+ if (owner === "agent") flow.lastPrompt = flow.seq;
18319
+ }
18320
+ if (owner === "agent") flow.seq++;
18321
+ collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
18204
18322
  }
18205
18323
  };
18206
18324
  try {
@@ -18211,7 +18329,11 @@ function fold(transcriptPath, opts = {}) {
18211
18329
  return result;
18212
18330
  }
18213
18331
  try {
18214
- const sidecarDir = (0, import_node_path18.join)((0, import_node_path18.dirname)(transcriptPath), "subagents");
18332
+ const sidecarDir = (0, import_node_path18.join)(
18333
+ (0, import_node_path18.dirname)(transcriptPath),
18334
+ (0, import_node_path18.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18335
+ "subagents"
18336
+ );
18215
18337
  if ((0, import_node_fs23.existsSync)(sidecarDir)) {
18216
18338
  const maxFiles = opts.maxSidecars ?? 200;
18217
18339
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
@@ -18254,12 +18376,24 @@ function fold(transcriptPath, opts = {}) {
18254
18376
  result.authored = [...byPath.values()].sort((a, b) => a.p.localeCompare(b.p));
18255
18377
  result.unknownTypes = [...unknown].sort();
18256
18378
  result.commands = [...commandStats.entries()].map(([cls, s]) => ({ class: cls, ...s })).sort((a, b) => a.class.localeCompare(b.class));
18379
+ result.tools = [...toolStats.entries()].map(([name, s]) => ({
18380
+ name,
18381
+ runs: s.runs,
18382
+ failed: s.failed,
18383
+ last_status: s.last_status,
18384
+ targets: [...s.targets].map((t) => toRepoRelative(t, opts.repoRoot)).sort()
18385
+ })).sort((a, b) => b.runs - a.runs || a.name.localeCompare(b.name));
18386
+ result.tasks = [...taskById.entries()].map(([id, t]) => ({ id, name: t.name, status: t.status })).sort((a, b) => (Number(a.id) || 0) - (Number(b.id) || 0));
18257
18387
  const authoredPaths = new Set(result.authored.map((a) => a.p));
18258
18388
  for (const raw of opts.changedFiles ?? []) {
18259
18389
  const p = toRepoRelative(raw, opts.repoRoot);
18260
18390
  if (!p || authoredPaths.has(p)) continue;
18261
18391
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18262
18392
  }
18393
+ result.planApproval = {
18394
+ approvals: flow.approvals,
18395
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18396
+ };
18263
18397
  return result;
18264
18398
  }
18265
18399
  function classifyUnobserved(path) {
@@ -18272,7 +18406,7 @@ function classifyUnobserved(path) {
18272
18406
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18273
18407
  return "no_edit_record";
18274
18408
  }
18275
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
18409
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18276
18410
  const message = record.message;
18277
18411
  const content = message?.content ?? record.content;
18278
18412
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18281,8 +18415,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18281
18415
  if (blockType === "tool_use") {
18282
18416
  const name = String(block.name ?? "");
18283
18417
  const input = block.input ?? {};
18418
+ if (name && toolStats.size < MAX_TOOL_NAMES) {
18419
+ const prev = toolStats.get(name) ?? { runs: 0, failed: 0, last_status: null, targets: /* @__PURE__ */ new Set() };
18420
+ prev.targets = prev.targets ?? /* @__PURE__ */ new Set();
18421
+ if (prev.targets.size < MAX_TOOL_TARGETS) {
18422
+ const target = toolTarget(name, input);
18423
+ if (target) prev.targets.add(target);
18424
+ }
18425
+ toolStats.set(name, { runs: prev.runs + 1, failed: prev.failed, last_status: null, targets: prev.targets });
18426
+ const toolId = typeof block.id === "string" ? block.id : null;
18427
+ if (toolId) pendingToolName.set(toolId, name);
18428
+ } else if (name && tally) {
18429
+ tally.toolNamesDropped += 1;
18430
+ }
18431
+ if (name === "TaskCreate") {
18432
+ const subject = typeof input.subject === "string" ? input.subject : "";
18433
+ const taskId = typeof block.id === "string" ? block.id : null;
18434
+ if (subject && taskId) pendingTaskName.set(taskId, subject.slice(0, TASK_NAME_CHARS));
18435
+ }
18436
+ if (name === "TaskUpdate") {
18437
+ const id = typeof input.taskId === "string" ? input.taskId : "";
18438
+ const status = typeof input.status === "string" ? input.status : "";
18439
+ if (id && status) {
18440
+ const entry = taskById.get(id);
18441
+ taskById.set(id, { name: entry?.name ?? `#${id}`, status });
18442
+ }
18443
+ }
18284
18444
  if (EDIT_TOOLS.has(name)) {
18285
18445
  const rawPath = typeof input.notebook_path === "string" ? input.notebook_path : typeof input.file_path === "string" ? input.file_path : null;
18446
+ if (rawPath && !isInsideRepo(rawPath, repoRoot2)) {
18447
+ if (tally) tally.outsideRepo += 1;
18448
+ continue;
18449
+ }
18286
18450
  const path = rawPath ? toRepoRelative(rawPath, repoRoot2) : null;
18287
18451
  if (path) {
18288
18452
  const entry = byPath.get(path) ?? { p: path, h: 0, a: 0, d: 0, owner };
@@ -18316,6 +18480,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18316
18480
  }
18317
18481
  if (blockType === "tool_result") {
18318
18482
  const id = typeof block.tool_use_id === "string" ? block.tool_use_id : null;
18483
+ const taskName = id ? pendingTaskName.get(id) : void 0;
18484
+ if (taskName) {
18485
+ pendingTaskName.delete(id);
18486
+ const body = typeof block.content === "string" ? block.content : "";
18487
+ const created = /Task #(\d+)/.exec(body);
18488
+ if (created && taskById.size < MAX_TASKS) {
18489
+ taskById.set(created[1], { name: taskName, status: taskById.get(created[1])?.status ?? "created" });
18490
+ }
18491
+ }
18492
+ const toolName = id ? pendingToolName.get(id) : void 0;
18493
+ if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
18494
+ const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
18495
+ if (/approved your plan/i.test(body)) {
18496
+ flow.lastApproval = flow.seq;
18497
+ flow.approvals += 1;
18498
+ }
18499
+ }
18500
+ if (toolName) {
18501
+ pendingToolName.delete(id);
18502
+ const prevTool = toolStats.get(toolName);
18503
+ if (prevTool) {
18504
+ const failed = block.is_error === true;
18505
+ toolStats.set(toolName, {
18506
+ runs: prevTool.runs,
18507
+ failed: prevTool.failed + (failed ? 1 : 0),
18508
+ last_status: block.is_error === true ? 1 : block.is_error === false ? 0 : null,
18509
+ // Carried, not rebuilt: the targets were collected on the `tool_use`
18510
+ // side and dropping them here would empty the ledger on every result.
18511
+ targets: prevTool.targets
18512
+ });
18513
+ }
18514
+ }
18319
18515
  const cls = id ? pendingByToolUse.get(id) : void 0;
18320
18516
  if (!cls) continue;
18321
18517
  pendingByToolUse.delete(id);
@@ -18358,8 +18554,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18358
18554
  // src/commands/analyze/phases/06-evidence.ts
18359
18555
  async function evidence(run) {
18360
18556
  const { opts } = run;
18361
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
18557
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
18362
18558
  let { analysisMode, earlyFold } = run;
18559
+ const recordFlip = (stage) => {
18560
+ if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
18561
+ logEvent("mode_flipped", { stage, to: "plan" });
18562
+ };
18563
+ const planWorthy = !!assistantResponse && !turnAuthoredCode;
18363
18564
  let staticResults = {
18364
18565
  tool: "@codacy/analysis-cli",
18365
18566
  findings: [],
@@ -18379,8 +18580,9 @@ async function evidence(run) {
18379
18580
  const debounceSeconds = parseInt(opts.debounce, 10);
18380
18581
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18381
18582
  if (debounceSkip) {
18382
- if (assistantResponse) {
18583
+ if (planWorthy) {
18383
18584
  analysisMode = "plan";
18585
+ recordFlip("debounce");
18384
18586
  } else {
18385
18587
  await passAndExit(run, debounceSkip, "debounce");
18386
18588
  }
@@ -18389,8 +18591,9 @@ async function evidence(run) {
18389
18591
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18390
18592
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18391
18593
  if (mtimeSkip) {
18392
- if (assistantResponse) {
18594
+ if (planWorthy) {
18393
18595
  analysisMode = "plan";
18596
+ recordFlip("mtime");
18394
18597
  } else {
18395
18598
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18396
18599
  }
@@ -18401,8 +18604,9 @@ async function evidence(run) {
18401
18604
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18402
18605
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18403
18606
  if (hashResult.skip) {
18404
- if (assistantResponse) {
18607
+ if (planWorthy) {
18405
18608
  analysisMode = "plan";
18609
+ recordFlip("content-hash");
18406
18610
  } else {
18407
18611
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18408
18612
  }
@@ -18464,8 +18668,9 @@ async function evidence(run) {
18464
18668
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18465
18669
  });
18466
18670
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18467
- if (assistantResponse) {
18671
+ if (planWorthy) {
18468
18672
  analysisMode = "plan";
18673
+ recordFlip("empty-after-scoping");
18469
18674
  } else {
18470
18675
  await passAndExit(
18471
18676
  run,
@@ -19309,6 +19514,8 @@ async function buildRequest(run) {
19309
19514
  authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
19310
19515
  unobserved: foldResult?.unobserved ?? [],
19311
19516
  commands: foldResult?.commands ?? [],
19517
+ tools: foldResult?.tools ?? [],
19518
+ tasks: foldResult?.tasks ?? [],
19312
19519
  unknown_types: foldResult?.unknownTypes ?? [],
19313
19520
  coverage: foldResult?.coverage ?? { recordCounts: {}, totalRecords: 0, malformed: 0, subagentFiles: 0, subagentSkipped: 0, complete: false },
19314
19521
  // INV-19, computed client-side and sent so a conservation failure is
@@ -19419,6 +19626,10 @@ async function buildRequest(run) {
19419
19626
  intentContext.user_prompt = w4Task.goal;
19420
19627
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19421
19628
  }
19629
+ if (foldResult?.planApproval?.activeSinceLastPrompt && intentContext.user_prompt) {
19630
+ intentContext.plan_approved = true;
19631
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19632
+ }
19422
19633
  if (assistantResponse) {
19423
19634
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19424
19635
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19682,6 +19893,11 @@ async function reconcile(run) {
19682
19893
  decision,
19683
19894
  branch: getCurrentBranch(),
19684
19895
  watermarkSha: watermarkIsPartial ? null : watermarkHash,
19896
+ // The byte witness — the same "only honest definition of reviewed" the
19897
+ // coverage column uses. A finding on a path outside this set records no
19898
+ // statement (plan-mode prose anchored to unsent files must not become
19899
+ // "STILL OPEN … the tree is not clean").
19900
+ sentPaths,
19685
19901
  findings: response.findings?.map((f) => ({
19686
19902
  file: f.file,
19687
19903
  line: f.line,
@@ -21935,8 +22151,8 @@ function registerTelemetryCommands(program2) {
21935
22151
  }
21936
22152
 
21937
22153
  // src/cli.ts
21938
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.6726d0f").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) => {
21939
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.6726d0f");
22154
+ program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.7126d67").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) => {
22155
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.7126d67");
21940
22156
  setUserNamedServiceUrl(program.opts().serviceUrl);
21941
22157
  try {
21942
22158
  await foldLegacyLocalCredential();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.29.4-experimental.6726d0f",
3
+ "version": "0.29.4-experimental.7126d67",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",