@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.
- package/bin/gate.js +208 -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);
|
|
@@ -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
|
|
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,
|
|
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,
|
|
12633
|
+
if (!(0, import_node_fs11.existsSync)(p)) continue;
|
|
12441
12634
|
try {
|
|
12442
|
-
const { readFileSync:
|
|
12443
|
-
const content =
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
12784
|
+
if (!(0, import_node_fs12.existsSync)(src)) {
|
|
12592
12785
|
printWarn(` Skill data not found: ${skill}`);
|
|
12593
12786
|
continue;
|
|
12594
12787
|
}
|
|
12595
|
-
if ((0,
|
|
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,
|
|
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.
|
|
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);
|