@codacy/gate-cli 0.7.0 → 0.8.1
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 +135 -41
- package/package.json +1 -1
package/bin/gate.js
CHANGED
|
@@ -11357,7 +11357,8 @@ function getChangedFiles() {
|
|
|
11357
11357
|
for (const f of getWorktreeFiles()) {
|
|
11358
11358
|
sets.add(f);
|
|
11359
11359
|
}
|
|
11360
|
-
|
|
11360
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".gate/") && !f.startsWith(".gate\\"));
|
|
11361
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
11361
11362
|
}
|
|
11362
11363
|
function getWorktreeFiles() {
|
|
11363
11364
|
const result = [];
|
|
@@ -11645,17 +11646,20 @@ function narrowToRecent(files) {
|
|
|
11645
11646
|
});
|
|
11646
11647
|
return recent.length > 0 ? recent : files;
|
|
11647
11648
|
}
|
|
11648
|
-
function readIteration(currentCommit,
|
|
11649
|
+
function readIteration(currentCommit, _contentHash) {
|
|
11649
11650
|
if (!(0, import_node_fs5.existsSync)(ITERATION_FILE)) return 1;
|
|
11650
11651
|
try {
|
|
11651
11652
|
const stored = (0, import_node_fs5.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
11652
11653
|
const parts = stored.split(":");
|
|
11653
11654
|
const iter = parseInt(parts[0], 10);
|
|
11654
11655
|
const storedCommit = parts[1] ?? "";
|
|
11655
|
-
const
|
|
11656
|
+
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
11656
11657
|
if (isNaN(iter)) return 1;
|
|
11657
11658
|
if (storedCommit !== currentCommit) return 1;
|
|
11658
|
-
if (
|
|
11659
|
+
if (storedTimestamp > 0) {
|
|
11660
|
+
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
11661
|
+
if (elapsed > 600) return 1;
|
|
11662
|
+
}
|
|
11659
11663
|
return iter;
|
|
11660
11664
|
} catch {
|
|
11661
11665
|
return 1;
|
|
@@ -11672,9 +11676,10 @@ function checkMaxIterations(currentCommit, maxIterations = MAX_ITERATIONS, conte
|
|
|
11672
11676
|
}
|
|
11673
11677
|
return { skip: null, iteration };
|
|
11674
11678
|
}
|
|
11675
|
-
function writeIteration(iteration, commit,
|
|
11679
|
+
function writeIteration(iteration, commit, _contentHash) {
|
|
11676
11680
|
(0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
|
|
11677
|
-
|
|
11681
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
11682
|
+
(0, import_node_fs5.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}`);
|
|
11678
11683
|
}
|
|
11679
11684
|
|
|
11680
11685
|
// src/lib/static-analysis.ts
|
|
@@ -11989,6 +11994,39 @@ function buildOfflineFallback(reason, staticResults) {
|
|
|
11989
11994
|
}
|
|
11990
11995
|
|
|
11991
11996
|
// src/commands/analyze.ts
|
|
11997
|
+
var MAX_ASSISTANT_RESPONSE_CHARS = 8e3;
|
|
11998
|
+
async function readStopHookStdin() {
|
|
11999
|
+
const empty = { assistantMessage: null, stopReason: null };
|
|
12000
|
+
try {
|
|
12001
|
+
if (process.stdin.isTTY) return empty;
|
|
12002
|
+
const chunks = [];
|
|
12003
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve(empty), 500));
|
|
12004
|
+
const read = new Promise((resolve) => {
|
|
12005
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
12006
|
+
process.stdin.on("end", () => {
|
|
12007
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
12008
|
+
if (!raw) {
|
|
12009
|
+
resolve(empty);
|
|
12010
|
+
return;
|
|
12011
|
+
}
|
|
12012
|
+
try {
|
|
12013
|
+
const data = JSON.parse(raw);
|
|
12014
|
+
resolve({
|
|
12015
|
+
assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
|
|
12016
|
+
stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null
|
|
12017
|
+
});
|
|
12018
|
+
} catch {
|
|
12019
|
+
resolve(empty);
|
|
12020
|
+
}
|
|
12021
|
+
});
|
|
12022
|
+
process.stdin.on("error", () => resolve(empty));
|
|
12023
|
+
process.stdin.resume();
|
|
12024
|
+
});
|
|
12025
|
+
return await Promise.race([read, timeout]);
|
|
12026
|
+
} catch {
|
|
12027
|
+
return empty;
|
|
12028
|
+
}
|
|
12029
|
+
}
|
|
11992
12030
|
function passAndExit(reason) {
|
|
11993
12031
|
printJsonCompact({ gate_decision: "PASS", systemMessage: `GATE.md: ${reason}` });
|
|
11994
12032
|
process.exit(0);
|
|
@@ -12004,47 +12042,100 @@ function registerAnalyzeCommand(program2) {
|
|
|
12004
12042
|
});
|
|
12005
12043
|
}
|
|
12006
12044
|
async function runAnalyze(opts, globals) {
|
|
12045
|
+
const { assistantMessage: assistantResponse, stopReason } = await readStopHookStdin();
|
|
12007
12046
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
12008
12047
|
const analyzable = filterAnalyzable(allChanged);
|
|
12009
12048
|
const reviewable = filterReviewable(allChanged);
|
|
12010
12049
|
const securityFiles = filterSecurity(allChanged);
|
|
12011
|
-
|
|
12050
|
+
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
12051
|
+
if (noFilesChanged && !assistantResponse) {
|
|
12012
12052
|
passAndExit("No analyzable files changed");
|
|
12013
12053
|
}
|
|
12054
|
+
let planOnly = noFilesChanged && !!assistantResponse;
|
|
12014
12055
|
const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
|
|
12015
|
-
const debounceSeconds = parseInt(opts.debounce, 10);
|
|
12016
|
-
const debounceSkip = checkDebounce(debounceSeconds);
|
|
12017
|
-
if (debounceSkip) passAndExit(debounceSkip);
|
|
12018
|
-
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
12019
|
-
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
|
|
12020
|
-
if (mtimeSkip) passAndExit(mtimeSkip);
|
|
12021
|
-
recordAnalysisStart();
|
|
12022
|
-
const { skip: hashSkip, hash: contentHash } = checkContentHash(allCheckable);
|
|
12023
|
-
if (hashSkip) passAndExit(hashSkip);
|
|
12024
|
-
const recentForReview = narrowToRecent(allForReview);
|
|
12025
12056
|
const conversation = await readAndClearConversationBuffer();
|
|
12026
12057
|
const specs = discoverSpecs();
|
|
12027
12058
|
const plans = discoverPlans();
|
|
12028
|
-
let staticResults
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12059
|
+
let staticResults = {
|
|
12060
|
+
tool: "@codacy/analysis-cli",
|
|
12061
|
+
findings: [],
|
|
12062
|
+
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
12063
|
+
};
|
|
12064
|
+
let codeDelta = {
|
|
12065
|
+
files: [],
|
|
12066
|
+
total_lines: 0,
|
|
12067
|
+
total_files: 0
|
|
12068
|
+
};
|
|
12069
|
+
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
12070
|
+
let contentHash = null;
|
|
12071
|
+
let iteration = 1;
|
|
12072
|
+
let currentCommit = "";
|
|
12073
|
+
if (!planOnly) {
|
|
12074
|
+
const debounceSeconds = parseInt(opts.debounce, 10);
|
|
12075
|
+
const debounceSkip = checkDebounce(debounceSeconds);
|
|
12076
|
+
if (debounceSkip) {
|
|
12077
|
+
if (assistantResponse) {
|
|
12078
|
+
planOnly = true;
|
|
12079
|
+
} else {
|
|
12080
|
+
passAndExit(debounceSkip);
|
|
12081
|
+
}
|
|
12082
|
+
}
|
|
12083
|
+
if (!planOnly) {
|
|
12084
|
+
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
12085
|
+
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
|
|
12086
|
+
if (mtimeSkip) {
|
|
12087
|
+
if (assistantResponse) {
|
|
12088
|
+
planOnly = true;
|
|
12089
|
+
} else {
|
|
12090
|
+
passAndExit(mtimeSkip);
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
}
|
|
12094
|
+
if (!planOnly) {
|
|
12095
|
+
recordAnalysisStart();
|
|
12096
|
+
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
12097
|
+
const hashResult = checkContentHash(allCheckable);
|
|
12098
|
+
if (hashResult.skip) {
|
|
12099
|
+
if (assistantResponse) {
|
|
12100
|
+
planOnly = true;
|
|
12101
|
+
} else {
|
|
12102
|
+
passAndExit(hashResult.skip);
|
|
12103
|
+
}
|
|
12104
|
+
}
|
|
12105
|
+
contentHash = hashResult.hash;
|
|
12106
|
+
if (!planOnly) {
|
|
12107
|
+
const recentForReview = narrowToRecent(allForReview);
|
|
12108
|
+
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
12109
|
+
const allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
12110
|
+
staticResults = runCodacyAnalysis(allScannable);
|
|
12111
|
+
}
|
|
12112
|
+
codeDelta = collectCodeDelta(recentForReview, {
|
|
12113
|
+
maxFiles: parseInt(opts.maxFiles, 10),
|
|
12114
|
+
maxFileBytes: parseInt(opts.maxFileSize, 10),
|
|
12115
|
+
maxTotalBytes: parseInt(opts.maxTotalSize, 10)
|
|
12116
|
+
});
|
|
12117
|
+
if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
|
|
12118
|
+
if (assistantResponse) {
|
|
12119
|
+
planOnly = true;
|
|
12120
|
+
} else {
|
|
12121
|
+
passAndExit("No files within size limits to analyze");
|
|
12122
|
+
}
|
|
12123
|
+
}
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
12126
|
+
if (!planOnly) {
|
|
12127
|
+
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
12128
|
+
currentCommit = getCurrentCommit();
|
|
12129
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
12130
|
+
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
12131
|
+
if (iterResult.skip) passAndExit(iterResult.skip);
|
|
12132
|
+
iteration = iterResult.iteration;
|
|
12133
|
+
}
|
|
12038
12134
|
}
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
maxTotalBytes: parseInt(opts.maxTotalSize, 10)
|
|
12043
|
-
});
|
|
12044
|
-
if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
|
|
12045
|
-
passAndExit("No files within size limits to analyze");
|
|
12135
|
+
if (planOnly) {
|
|
12136
|
+
recordAnalysisStart();
|
|
12137
|
+
currentCommit = getCurrentCommit();
|
|
12046
12138
|
}
|
|
12047
|
-
const snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
12048
12139
|
const tokenResult = await resolveToken(globals.token);
|
|
12049
12140
|
if (!tokenResult.ok) {
|
|
12050
12141
|
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
@@ -12053,15 +12144,12 @@ async function runAnalyze(opts, globals) {
|
|
|
12053
12144
|
if (!urlResult.ok) {
|
|
12054
12145
|
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
12055
12146
|
}
|
|
12056
|
-
const currentCommit = getCurrentCommit();
|
|
12057
|
-
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
12058
|
-
const { skip: iterSkip, iteration } = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
12059
|
-
if (iterSkip) passAndExit(iterSkip);
|
|
12060
12147
|
const requestBody = {
|
|
12061
12148
|
static_results: staticResults,
|
|
12062
12149
|
code_delta: codeDelta,
|
|
12063
12150
|
changed_files: allForReview,
|
|
12064
12151
|
standard_version: "latest",
|
|
12152
|
+
plan_only: planOnly,
|
|
12065
12153
|
context: {
|
|
12066
12154
|
agent_model: process.env.CLAUDE_MODEL ?? "unknown",
|
|
12067
12155
|
agent_harness: "claude-code",
|
|
@@ -12072,7 +12160,7 @@ async function runAnalyze(opts, globals) {
|
|
|
12072
12160
|
if (snapshotResult.has_snapshots && snapshotResult.diffs.length > 0) {
|
|
12073
12161
|
requestBody.snapshot_diffs = snapshotResult.diffs;
|
|
12074
12162
|
}
|
|
12075
|
-
const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0;
|
|
12163
|
+
const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse;
|
|
12076
12164
|
if (hasIntent) {
|
|
12077
12165
|
const intentContext = {};
|
|
12078
12166
|
if (conversation && conversation.prompts.length > 0) {
|
|
@@ -12087,6 +12175,12 @@ async function runAnalyze(opts, globals) {
|
|
|
12087
12175
|
intentContext.recent_commits = conversation.recent_commits;
|
|
12088
12176
|
}
|
|
12089
12177
|
}
|
|
12178
|
+
if (assistantResponse) {
|
|
12179
|
+
intentContext.assistant_response = assistantResponse.length > MAX_ASSISTANT_RESPONSE_CHARS ? assistantResponse.slice(0, MAX_ASSISTANT_RESPONSE_CHARS) : assistantResponse;
|
|
12180
|
+
}
|
|
12181
|
+
if (stopReason) {
|
|
12182
|
+
intentContext.stop_reason = stopReason;
|
|
12183
|
+
}
|
|
12090
12184
|
if (specs.length > 0) intentContext.specs = specs;
|
|
12091
12185
|
if (plans.length > 0) intentContext.plans = plans;
|
|
12092
12186
|
for (const key of Object.keys(intentContext)) {
|
|
@@ -12448,7 +12542,7 @@ function registerInitCommand(program2) {
|
|
|
12448
12542
|
}
|
|
12449
12543
|
|
|
12450
12544
|
// src/cli.ts
|
|
12451
|
-
program.name("gate").description("CLI for GATE.md quality gate service").version("0.
|
|
12545
|
+
program.name("gate").description("CLI for GATE.md quality gate service").version("0.8.1").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
12452
12546
|
registerAuthCommands(program);
|
|
12453
12547
|
registerHooksCommands(program);
|
|
12454
12548
|
registerIntentCommands(program);
|