@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.
- package/bin/gate.js +204 -15
- 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
|
|
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,
|
|
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,
|
|
12633
|
+
if (!(0, import_node_fs11.existsSync)(p)) continue;
|
|
12445
12634
|
try {
|
|
12446
|
-
const { readFileSync:
|
|
12447
|
-
const content =
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
12784
|
+
if (!(0, import_node_fs12.existsSync)(src)) {
|
|
12596
12785
|
printWarn(` Skill data not found: ${skill}`);
|
|
12597
12786
|
continue;
|
|
12598
12787
|
}
|
|
12599
|
-
if ((0,
|
|
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,
|
|
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.
|
|
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);
|