@codacy/gate-cli 0.12.0 → 0.14.0

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/gate.js +208 -15
  2. package/package.json +1 -1
package/bin/gate.js CHANGED
@@ -12068,10 +12068,196 @@ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPromp
12068
12068
  return "standard";
12069
12069
  }
12070
12070
 
12071
+ // src/lib/transcript.ts
12072
+ var import_node_fs10 = require("node:fs");
12073
+ var MAX_READ_BYTES = 256 * 1024;
12074
+ var SMALL_FILE_BYTES = 64 * 1024;
12075
+ var MAX_FILES_LIST = 20;
12076
+ var MAX_CREATED_LIST = 10;
12077
+ var MAX_COMMANDS = 10;
12078
+ var MAX_COMMAND_CHARS = 80;
12079
+ var MAX_TOOL_BLOCKS = 200;
12080
+ var MAX_SUMMARY_BYTES = 4096;
12081
+ var HOME = process.env.HOME ?? "";
12082
+ async function extractActionSummary(transcriptPath) {
12083
+ try {
12084
+ const lines = readTurnLines(transcriptPath);
12085
+ if (!lines || lines.length === 0) return null;
12086
+ return buildSummary(lines);
12087
+ } catch {
12088
+ return null;
12089
+ }
12090
+ }
12091
+ function readTurnLines(transcriptPath) {
12092
+ let size;
12093
+ try {
12094
+ size = (0, import_node_fs10.statSync)(transcriptPath).size;
12095
+ } catch {
12096
+ return null;
12097
+ }
12098
+ if (size === 0) return null;
12099
+ let raw;
12100
+ if (size <= SMALL_FILE_BYTES) {
12101
+ raw = (0, import_node_fs10.readFileSync)(transcriptPath, "utf-8");
12102
+ } else {
12103
+ const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
12104
+ const fd = require("node:fs").openSync(transcriptPath, "r");
12105
+ try {
12106
+ const offset = Math.max(0, size - MAX_READ_BYTES);
12107
+ require("node:fs").readSync(fd, buf, 0, buf.length, offset);
12108
+ } finally {
12109
+ require("node:fs").closeSync(fd);
12110
+ }
12111
+ raw = buf.toString("utf-8");
12112
+ const firstNewline = raw.indexOf("\n");
12113
+ if (firstNewline > 0) {
12114
+ raw = raw.slice(firstNewline + 1);
12115
+ }
12116
+ }
12117
+ const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
12118
+ if (allLines.length === 0) return null;
12119
+ let turnStart = 0;
12120
+ for (let i = allLines.length - 1; i >= 0; i--) {
12121
+ try {
12122
+ const parsed = JSON.parse(allLines[i]);
12123
+ if (parsed.type === "user") {
12124
+ turnStart = i;
12125
+ break;
12126
+ }
12127
+ } catch {
12128
+ }
12129
+ }
12130
+ return allLines.slice(turnStart);
12131
+ }
12132
+ function buildSummary(lines) {
12133
+ const toolCounts = {};
12134
+ const filesRead = /* @__PURE__ */ new Set();
12135
+ const filesEdited = /* @__PURE__ */ new Set();
12136
+ const filesCreated = /* @__PURE__ */ new Set();
12137
+ const commands = [];
12138
+ let searches = 0;
12139
+ let subagents = 0;
12140
+ let webFetches = 0;
12141
+ let totalToolCalls = 0;
12142
+ let turnMessages = 0;
12143
+ let firstTimestamp = null;
12144
+ let lastTimestamp = null;
12145
+ for (const line of lines) {
12146
+ let entry;
12147
+ try {
12148
+ entry = JSON.parse(line);
12149
+ } catch {
12150
+ continue;
12151
+ }
12152
+ if (entry.type === "user" && !firstTimestamp) {
12153
+ firstTimestamp = entry.timestamp ?? null;
12154
+ }
12155
+ if (entry.type !== "assistant") continue;
12156
+ turnMessages++;
12157
+ lastTimestamp = entry.timestamp ?? lastTimestamp;
12158
+ const message = entry.message;
12159
+ if (!message) continue;
12160
+ const content = message.content;
12161
+ if (!Array.isArray(content)) continue;
12162
+ for (const block of content) {
12163
+ if (block.type !== "tool_use") continue;
12164
+ totalToolCalls++;
12165
+ const toolName = block.name ?? "unknown";
12166
+ toolCounts[toolName] = (toolCounts[toolName] ?? 0) + 1;
12167
+ if (totalToolCalls > MAX_TOOL_BLOCKS) continue;
12168
+ const input = block.input ?? {};
12169
+ switch (toolName) {
12170
+ case "Read":
12171
+ addPath(filesRead, input.file_path);
12172
+ break;
12173
+ case "Edit":
12174
+ addPath(filesEdited, input.file_path);
12175
+ break;
12176
+ case "Write":
12177
+ addPath(filesCreated, input.file_path);
12178
+ addPath(filesEdited, input.file_path);
12179
+ break;
12180
+ case "Bash": {
12181
+ const cmd = sanitizeCommand(input.command);
12182
+ if (cmd && commands.length < MAX_COMMANDS) {
12183
+ commands.push(cmd);
12184
+ }
12185
+ break;
12186
+ }
12187
+ case "Grep":
12188
+ case "Glob":
12189
+ searches++;
12190
+ break;
12191
+ case "Agent":
12192
+ subagents++;
12193
+ break;
12194
+ case "WebFetch":
12195
+ case "WebSearch":
12196
+ webFetches++;
12197
+ break;
12198
+ }
12199
+ }
12200
+ }
12201
+ let turnDurationMs = null;
12202
+ if (firstTimestamp && lastTimestamp) {
12203
+ const start = new Date(firstTimestamp).getTime();
12204
+ const end = new Date(lastTimestamp).getTime();
12205
+ if (!isNaN(start) && !isNaN(end) && end > start) {
12206
+ turnDurationMs = end - start;
12207
+ }
12208
+ }
12209
+ const summary = {
12210
+ tool_counts: toolCounts,
12211
+ files_read: capArray(filesRead, MAX_FILES_LIST),
12212
+ files_edited: capArray(filesEdited, MAX_FILES_LIST),
12213
+ files_created: capArray(filesCreated, MAX_CREATED_LIST),
12214
+ searches,
12215
+ commands,
12216
+ subagents,
12217
+ web_fetches: webFetches,
12218
+ total_tool_calls: totalToolCalls,
12219
+ turn_messages: turnMessages,
12220
+ turn_duration_ms: turnDurationMs
12221
+ };
12222
+ if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
12223
+ summary.commands = [];
12224
+ if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
12225
+ summary.files_read = summary.files_read.slice(0, 10);
12226
+ summary.files_edited = summary.files_edited.slice(0, 10);
12227
+ summary.files_created = summary.files_created.slice(0, 5);
12228
+ }
12229
+ }
12230
+ return summary;
12231
+ }
12232
+ function addPath(set, rawPath) {
12233
+ if (typeof rawPath !== "string" || !rawPath) return;
12234
+ let p = rawPath;
12235
+ if (HOME && p.startsWith(HOME)) {
12236
+ p = p.slice(HOME.length + 1);
12237
+ }
12238
+ if (p.length > 200) p = p.slice(0, 200);
12239
+ set.add(p);
12240
+ }
12241
+ function sanitizeCommand(rawCmd) {
12242
+ if (typeof rawCmd !== "string" || !rawCmd) return null;
12243
+ let cmd = rawCmd.split("\n")[0];
12244
+ for (const sep of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
12245
+ const idx = cmd.indexOf(sep);
12246
+ if (idx > 0) cmd = cmd.slice(0, idx);
12247
+ }
12248
+ if (cmd.length > MAX_COMMAND_CHARS) {
12249
+ cmd = cmd.slice(0, MAX_COMMAND_CHARS);
12250
+ }
12251
+ return cmd.trim() || null;
12252
+ }
12253
+ function capArray(set, max) {
12254
+ return Array.from(set).slice(0, max);
12255
+ }
12256
+
12071
12257
  // src/commands/analyze.ts
12072
12258
  var MAX_ASSISTANT_RESPONSE_CHARS = 8e3;
12073
12259
  async function readStopHookStdin() {
12074
- const empty = { assistantMessage: null, stopReason: null };
12260
+ const empty = { assistantMessage: null, stopReason: null, transcriptPath: null };
12075
12261
  try {
12076
12262
  if (process.stdin.isTTY) return empty;
12077
12263
  const chunks = [];
@@ -12088,7 +12274,8 @@ async function readStopHookStdin() {
12088
12274
  const data = JSON.parse(raw);
12089
12275
  resolve({
12090
12276
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
12091
- stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null
12277
+ stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
12278
+ transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null
12092
12279
  });
12093
12280
  } catch {
12094
12281
  resolve(empty);
@@ -12117,7 +12304,8 @@ function registerAnalyzeCommand(program2) {
12117
12304
  });
12118
12305
  }
12119
12306
  async function runAnalyze(opts, globals) {
12120
- const { assistantMessage: assistantResponse, stopReason } = await readStopHookStdin();
12307
+ const { assistantMessage: assistantResponse, stopReason, transcriptPath } = await readStopHookStdin();
12308
+ const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
12121
12309
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
12122
12310
  const analyzable = filterAnalyzable(allChanged);
12123
12311
  const reviewable = filterReviewable(allChanged);
@@ -12214,6 +12402,10 @@ async function runAnalyze(opts, globals) {
12214
12402
  if (analysisMode === "plan") {
12215
12403
  recordAnalysisStart();
12216
12404
  currentCommit = getCurrentCommit();
12405
+ const maxIterations = parseInt(opts.maxIterations, 10);
12406
+ const iterResult = checkMaxIterations(currentCommit, maxIterations);
12407
+ if (iterResult.skip) passAndExit(iterResult.skip);
12408
+ iteration = iterResult.iteration;
12217
12409
  }
12218
12410
  const tokenResult = await resolveToken(globals.token);
12219
12411
  if (!tokenResult.ok) {
@@ -12264,6 +12456,7 @@ async function runAnalyze(opts, globals) {
12264
12456
  }
12265
12457
  if (specs.length > 0) intentContext.specs = specs;
12266
12458
  if (plans.length > 0) intentContext.plans = plans;
12459
+ if (actionSummary) intentContext.action_summary = actionSummary;
12267
12460
  for (const key of Object.keys(intentContext)) {
12268
12461
  if (intentContext[key] == null) delete intentContext[key];
12269
12462
  }
@@ -12393,7 +12586,7 @@ async function runAnalyze(opts, globals) {
12393
12586
  }
12394
12587
 
12395
12588
  // src/commands/review.ts
12396
- var import_node_fs10 = require("node:fs");
12589
+ var import_node_fs11 = require("node:fs");
12397
12590
  function registerReviewCommand(program2) {
12398
12591
  program2.command("review").description("Run on-demand GATE.md analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
12399
12592
  const globals = program2.opts();
@@ -12412,7 +12605,7 @@ async function runReview(opts, globals) {
12412
12605
  const securityFiles = filterSecurity(allFiles);
12413
12606
  let staticResults;
12414
12607
  if (isCodacyAvailable()) {
12415
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs10.existsSync)(f) || resolveFile(f) !== null);
12608
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs11.existsSync)(f) || resolveFile(f) !== null);
12416
12609
  staticResults = runCodacyAnalysis(scannable);
12417
12610
  } else {
12418
12611
  staticResults = {
@@ -12437,10 +12630,10 @@ async function runReview(opts, globals) {
12437
12630
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
12438
12631
  specs = [];
12439
12632
  for (const p of specPaths) {
12440
- if (!(0, import_node_fs10.existsSync)(p)) continue;
12633
+ if (!(0, import_node_fs11.existsSync)(p)) continue;
12441
12634
  try {
12442
- const { readFileSync: readFileSync5 } = await import("node:fs");
12443
- const content = readFileSync5(p, "utf-8");
12635
+ const { readFileSync: readFileSync6 } = await import("node:fs");
12636
+ const content = readFileSync6(p, "utf-8");
12444
12637
  specs.push({ path: p, content: content.slice(0, 10240) });
12445
12638
  } catch {
12446
12639
  }
@@ -12498,7 +12691,7 @@ async function runReview(opts, globals) {
12498
12691
  }
12499
12692
 
12500
12693
  // src/commands/init.ts
12501
- var import_node_fs11 = require("node:fs");
12694
+ var import_node_fs12 = require("node:fs");
12502
12695
  var import_promises8 = require("node:fs/promises");
12503
12696
  var import_node_path8 = require("node:path");
12504
12697
  var import_node_child_process8 = require("node:child_process");
@@ -12512,7 +12705,7 @@ function resolveDataDir() {
12512
12705
  // local dev: running from repo root
12513
12706
  ];
12514
12707
  for (const candidate of candidates) {
12515
- if ((0, import_node_fs11.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
12708
+ if ((0, import_node_fs12.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
12516
12709
  return candidate;
12517
12710
  }
12518
12711
  }
@@ -12528,7 +12721,7 @@ function registerInitCommand(program2) {
12528
12721
  program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
12529
12722
  const force = opts.force ?? false;
12530
12723
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
12531
- const isProject = projectMarkers.some((m) => (0, import_node_fs11.existsSync)(m));
12724
+ const isProject = projectMarkers.some((m) => (0, import_node_fs12.existsSync)(m));
12532
12725
  if (!isProject) {
12533
12726
  printError("No project detected in the current directory.");
12534
12727
  printInfo('Run "gate init" from your project root.');
@@ -12588,14 +12781,14 @@ function registerInitCommand(program2) {
12588
12781
  for (const skill of skills) {
12589
12782
  const src = (0, import_node_path8.join)(skillsSource, skill);
12590
12783
  const dest = (0, import_node_path8.join)(skillsDest, skill);
12591
- if (!(0, import_node_fs11.existsSync)(src)) {
12784
+ if (!(0, import_node_fs12.existsSync)(src)) {
12592
12785
  printWarn(` Skill data not found: ${skill}`);
12593
12786
  continue;
12594
12787
  }
12595
- if ((0, import_node_fs11.existsSync)(dest) && !force) {
12788
+ if ((0, import_node_fs12.existsSync)(dest) && !force) {
12596
12789
  const srcSkill = (0, import_node_path8.join)(src, "SKILL.md");
12597
12790
  const destSkill = (0, import_node_path8.join)(dest, "SKILL.md");
12598
- if ((0, import_node_fs11.existsSync)(destSkill)) {
12791
+ if ((0, import_node_fs12.existsSync)(destSkill)) {
12599
12792
  try {
12600
12793
  const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
12601
12794
  const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
@@ -12641,7 +12834,7 @@ function registerInitCommand(program2) {
12641
12834
  }
12642
12835
 
12643
12836
  // src/cli.ts
12644
- program.name("gate").description("CLI for GATE.md quality gate service").version("0.12.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
12837
+ program.name("gate").description("CLI for GATE.md quality gate service").version("0.14.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
12645
12838
  registerAuthCommands(program);
12646
12839
  registerHooksCommands(program);
12647
12840
  registerIntentCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/gate-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "CLI for GATE.md quality gate service",
5
5
  "bin": {
6
6
  "gate": "./bin/gate.js"