@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.
Files changed (2) hide show
  1. package/bin/gate.js +135 -41
  2. 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
- return { files: Array.from(sets), hasRecentCommitFiles };
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, contentHash) {
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 storedContentHash = parts[2] ?? "";
11656
+ const storedTimestamp = parseInt(parts[2] ?? "0", 10);
11656
11657
  if (isNaN(iter)) return 1;
11657
11658
  if (storedCommit !== currentCommit) return 1;
11658
- if (contentHash && storedContentHash && storedContentHash !== contentHash) return 1;
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, contentHash) {
11679
+ function writeIteration(iteration, commit, _contentHash) {
11676
11680
  (0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
11677
- (0, import_node_fs5.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${contentHash ?? ""}`);
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
- if (analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0) {
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
- if (opts.skipStatic || !isCodacyAvailable()) {
12030
- staticResults = {
12031
- tool: "@codacy/analysis-cli",
12032
- findings: [],
12033
- summary: { total_findings: 0, by_severity: {}, tools_run: [] }
12034
- };
12035
- } else {
12036
- const allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
12037
- staticResults = runCodacyAnalysis(allScannable);
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
- const codeDelta = collectCodeDelta(recentForReview, {
12040
- maxFiles: parseInt(opts.maxFiles, 10),
12041
- maxFileBytes: parseInt(opts.maxFileSize, 10),
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.7.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/gate-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "CLI for GATE.md quality gate service",
5
5
  "bin": {
6
6
  "gate": "./bin/gate.js"