@codacy/gate-cli 0.13.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 +204 -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);
@@ -12268,6 +12456,7 @@ async function runAnalyze(opts, globals) {
12268
12456
  }
12269
12457
  if (specs.length > 0) intentContext.specs = specs;
12270
12458
  if (plans.length > 0) intentContext.plans = plans;
12459
+ if (actionSummary) intentContext.action_summary = actionSummary;
12271
12460
  for (const key of Object.keys(intentContext)) {
12272
12461
  if (intentContext[key] == null) delete intentContext[key];
12273
12462
  }
@@ -12397,7 +12586,7 @@ async function runAnalyze(opts, globals) {
12397
12586
  }
12398
12587
 
12399
12588
  // src/commands/review.ts
12400
- var import_node_fs10 = require("node:fs");
12589
+ var import_node_fs11 = require("node:fs");
12401
12590
  function registerReviewCommand(program2) {
12402
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) => {
12403
12592
  const globals = program2.opts();
@@ -12416,7 +12605,7 @@ async function runReview(opts, globals) {
12416
12605
  const securityFiles = filterSecurity(allFiles);
12417
12606
  let staticResults;
12418
12607
  if (isCodacyAvailable()) {
12419
- 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);
12420
12609
  staticResults = runCodacyAnalysis(scannable);
12421
12610
  } else {
12422
12611
  staticResults = {
@@ -12441,10 +12630,10 @@ async function runReview(opts, globals) {
12441
12630
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
12442
12631
  specs = [];
12443
12632
  for (const p of specPaths) {
12444
- if (!(0, import_node_fs10.existsSync)(p)) continue;
12633
+ if (!(0, import_node_fs11.existsSync)(p)) continue;
12445
12634
  try {
12446
- const { readFileSync: readFileSync5 } = await import("node:fs");
12447
- const content = readFileSync5(p, "utf-8");
12635
+ const { readFileSync: readFileSync6 } = await import("node:fs");
12636
+ const content = readFileSync6(p, "utf-8");
12448
12637
  specs.push({ path: p, content: content.slice(0, 10240) });
12449
12638
  } catch {
12450
12639
  }
@@ -12502,7 +12691,7 @@ async function runReview(opts, globals) {
12502
12691
  }
12503
12692
 
12504
12693
  // src/commands/init.ts
12505
- var import_node_fs11 = require("node:fs");
12694
+ var import_node_fs12 = require("node:fs");
12506
12695
  var import_promises8 = require("node:fs/promises");
12507
12696
  var import_node_path8 = require("node:path");
12508
12697
  var import_node_child_process8 = require("node:child_process");
@@ -12516,7 +12705,7 @@ function resolveDataDir() {
12516
12705
  // local dev: running from repo root
12517
12706
  ];
12518
12707
  for (const candidate of candidates) {
12519
- 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"))) {
12520
12709
  return candidate;
12521
12710
  }
12522
12711
  }
@@ -12532,7 +12721,7 @@ function registerInitCommand(program2) {
12532
12721
  program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
12533
12722
  const force = opts.force ?? false;
12534
12723
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
12535
- const isProject = projectMarkers.some((m) => (0, import_node_fs11.existsSync)(m));
12724
+ const isProject = projectMarkers.some((m) => (0, import_node_fs12.existsSync)(m));
12536
12725
  if (!isProject) {
12537
12726
  printError("No project detected in the current directory.");
12538
12727
  printInfo('Run "gate init" from your project root.');
@@ -12592,14 +12781,14 @@ function registerInitCommand(program2) {
12592
12781
  for (const skill of skills) {
12593
12782
  const src = (0, import_node_path8.join)(skillsSource, skill);
12594
12783
  const dest = (0, import_node_path8.join)(skillsDest, skill);
12595
- if (!(0, import_node_fs11.existsSync)(src)) {
12784
+ if (!(0, import_node_fs12.existsSync)(src)) {
12596
12785
  printWarn(` Skill data not found: ${skill}`);
12597
12786
  continue;
12598
12787
  }
12599
- if ((0, import_node_fs11.existsSync)(dest) && !force) {
12788
+ if ((0, import_node_fs12.existsSync)(dest) && !force) {
12600
12789
  const srcSkill = (0, import_node_path8.join)(src, "SKILL.md");
12601
12790
  const destSkill = (0, import_node_path8.join)(dest, "SKILL.md");
12602
- if ((0, import_node_fs11.existsSync)(destSkill)) {
12791
+ if ((0, import_node_fs12.existsSync)(destSkill)) {
12603
12792
  try {
12604
12793
  const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
12605
12794
  const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
@@ -12645,7 +12834,7 @@ function registerInitCommand(program2) {
12645
12834
  }
12646
12835
 
12647
12836
  // src/cli.ts
12648
- program.name("gate").description("CLI for GATE.md quality gate service").version("0.13.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");
12649
12838
  registerAuthCommands(program);
12650
12839
  registerHooksCommands(program);
12651
12840
  registerIntentCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/gate-cli",
3
- "version": "0.13.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"