@codacy/gate-cli 0.14.1 → 0.14.3

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 +209 -96
  2. package/package.json +1 -1
package/bin/gate.js CHANGED
@@ -10355,6 +10355,9 @@ var ITERATION_FILE = `${GATE_DIR}/.iteration-count`;
10355
10355
  var HASH_FILE = `${GATE_DIR}/.last-pass-hash`;
10356
10356
  var INTENT_FILE = `${GATE_DIR}/.last-intent`;
10357
10357
  var CACHE_DIR = `${GATE_DIR}/.cache`;
10358
+ var DEBUG_LOG_DIR = `${GATE_DIR}/.logs`;
10359
+ var DEBUG_LOG_FILE = `${DEBUG_LOG_DIR}/cli.log`;
10360
+ var DEBUG_LOG_MAX_BYTES = 1048576;
10358
10361
  var GATE_MD_FILE = "GATE.md";
10359
10362
  var CLAUDE_SETTINGS_FILE = ".claude/settings.json";
10360
10363
  var STANDARD_FILE = `${GATE_DIR}/standard.yaml`;
@@ -10588,9 +10591,54 @@ function printVerbose(msg, verbose) {
10588
10591
  }
10589
10592
  }
10590
10593
 
10594
+ // src/lib/debug-log.ts
10595
+ var import_node_fs = require("node:fs");
10596
+ function isEnabled() {
10597
+ const v = process.env.GATE_DEBUG;
10598
+ if (v === "0" || v === "false" || v === "off") return false;
10599
+ return true;
10600
+ }
10601
+ function logHttpCall(entry) {
10602
+ if (!isEnabled()) return;
10603
+ writeLine({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "http", ...entry });
10604
+ }
10605
+ function logEvent(event, data) {
10606
+ if (!isEnabled()) return;
10607
+ writeLine({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "event", event, ...data ?? {} });
10608
+ }
10609
+ function writeLine(obj) {
10610
+ try {
10611
+ const dir = projectPath(DEBUG_LOG_DIR);
10612
+ const file = projectPath(DEBUG_LOG_FILE);
10613
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
10614
+ rotateIfNeeded(file);
10615
+ (0, import_node_fs.appendFileSync)(file, JSON.stringify(obj) + "\n");
10616
+ } catch {
10617
+ }
10618
+ }
10619
+ function rotateIfNeeded(file) {
10620
+ try {
10621
+ const s = (0, import_node_fs.statSync)(file);
10622
+ if (s.size < DEBUG_LOG_MAX_BYTES) return;
10623
+ (0, import_node_fs.renameSync)(file, `${file}.1`);
10624
+ } catch {
10625
+ }
10626
+ }
10627
+
10591
10628
  // src/lib/api-client.ts
10592
10629
  async function apiRequest(options) {
10593
- const { method, path, serviceUrl, token, body, verbose, timeout = 9e4 } = options;
10630
+ const {
10631
+ method,
10632
+ path,
10633
+ serviceUrl,
10634
+ token,
10635
+ body,
10636
+ verbose,
10637
+ timeout = 9e4,
10638
+ cmd = "unknown",
10639
+ retry = false,
10640
+ encodeBody = false
10641
+ } = options;
10594
10642
  const url = `${serviceUrl}${path}`;
10595
10643
  const headers = {
10596
10644
  "Content-Type": "application/json"
@@ -10599,36 +10647,74 @@ async function apiRequest(options) {
10599
10647
  headers["Authorization"] = `Bearer ${token}`;
10600
10648
  }
10601
10649
  printVerbose(`${method} ${url}`, verbose);
10602
- if (body) {
10603
- printVerbose(`Body: ${JSON.stringify(body).slice(0, 500)}`, verbose);
10650
+ let serializedBody;
10651
+ if (body !== void 0 && body !== null) {
10652
+ const innerJson = JSON.stringify(body);
10653
+ if (encodeBody) {
10654
+ const payload = Buffer.from(innerJson, "utf8").toString("base64");
10655
+ serializedBody = JSON.stringify({ encoding: "base64", payload });
10656
+ } else {
10657
+ serializedBody = innerJson;
10658
+ }
10659
+ printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
10604
10660
  }
10661
+ const startedAt = Date.now();
10662
+ const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
10663
+ const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
10605
10664
  let response;
10606
10665
  try {
10607
10666
  response = await fetch(url, {
10608
10667
  method,
10609
10668
  headers,
10610
- body: body ? JSON.stringify(body) : void 0,
10669
+ body: serializedBody,
10611
10670
  signal: AbortSignal.timeout(timeout)
10612
10671
  });
10613
10672
  } catch (err) {
10614
- if (err instanceof DOMException && err.name === "TimeoutError") {
10615
- return { ok: false, error: `Request timed out after ${timeout}ms` };
10616
- }
10617
- return { ok: false, error: `Network error: ${err.message}` };
10673
+ const duration2 = Date.now() - startedAt;
10674
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
10675
+ const category = isTimeout ? "timeout" : "network";
10676
+ const error = isTimeout ? `Request timed out after ${timeout}ms` : `Network error: ${err.message}`;
10677
+ logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
10678
+ return { ok: false, error, category };
10618
10679
  }
10619
10680
  let data;
10620
10681
  try {
10621
10682
  data = await response.json();
10622
10683
  } catch {
10623
- return { ok: false, error: `Invalid JSON response (HTTP ${response.status})` };
10684
+ const duration2 = Date.now() - startedAt;
10685
+ const error = `Invalid JSON response (HTTP ${response.status})`;
10686
+ logHttpCall({
10687
+ ...logBase,
10688
+ duration_ms: duration2,
10689
+ http_status: response.status,
10690
+ category: "invalid_json",
10691
+ error
10692
+ });
10693
+ return { ok: false, error, category: "invalid_json" };
10624
10694
  }
10625
10695
  printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
10696
+ const duration = Date.now() - startedAt;
10626
10697
  if (!response.ok) {
10627
10698
  const apiErr = data;
10628
10699
  const code = apiErr?.error?.code ?? "UNKNOWN";
10629
10700
  const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
10630
- return { ok: false, error: `${code}: ${message}` };
10701
+ const category = response.status >= 500 ? "http_5xx" : "http_4xx";
10702
+ const error = `${code}: ${message}`;
10703
+ logHttpCall({
10704
+ ...logBase,
10705
+ duration_ms: duration,
10706
+ http_status: response.status,
10707
+ category,
10708
+ error
10709
+ });
10710
+ return { ok: false, error, category };
10631
10711
  }
10712
+ logHttpCall({
10713
+ ...logBase,
10714
+ duration_ms: duration,
10715
+ http_status: response.status,
10716
+ category: "ok"
10717
+ });
10632
10718
  return { ok: true, data };
10633
10719
  }
10634
10720
 
@@ -10858,7 +10944,7 @@ function registerHooksCommands(program2) {
10858
10944
 
10859
10945
  // src/lib/conversation-buffer.ts
10860
10946
  var import_promises5 = require("node:fs/promises");
10861
- var import_node_fs = require("node:fs");
10947
+ var import_node_fs2 = require("node:fs");
10862
10948
  var import_node_child_process4 = require("node:child_process");
10863
10949
  function stripImageReferences(text) {
10864
10950
  return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
@@ -10890,7 +10976,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
10890
10976
  }
10891
10977
  async function readAndClearConversationBuffer() {
10892
10978
  try {
10893
- if ((0, import_node_fs.existsSync)(CONVERSATION_BUFFER_FILE)) {
10979
+ if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
10894
10980
  const entries = await readBufferEntries();
10895
10981
  await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
10896
10982
  });
@@ -10901,7 +10987,7 @@ async function readAndClearConversationBuffer() {
10901
10987
  };
10902
10988
  }
10903
10989
  }
10904
- if ((0, import_node_fs.existsSync)(INTENT_FILE)) {
10990
+ if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
10905
10991
  try {
10906
10992
  const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
10907
10993
  await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
@@ -10957,12 +11043,12 @@ function getRecentCommitMessages() {
10957
11043
  }
10958
11044
 
10959
11045
  // src/commands/intent.ts
10960
- var import_node_fs2 = require("node:fs");
11046
+ var import_node_fs3 = require("node:fs");
10961
11047
  function registerIntentCommands(program2) {
10962
11048
  const intent = program2.command("intent").description("Manage intent capture");
10963
11049
  intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
10964
11050
  try {
10965
- if (!(0, import_node_fs2.existsSync)(GATE_DIR)) {
11051
+ if (!(0, import_node_fs3.existsSync)(GATE_DIR)) {
10966
11052
  process.exit(0);
10967
11053
  }
10968
11054
  const chunks = [];
@@ -11301,17 +11387,17 @@ function registerFeedbackCommand(program2) {
11301
11387
 
11302
11388
  // src/lib/git.ts
11303
11389
  var import_node_child_process5 = require("node:child_process");
11304
- var import_node_fs3 = require("node:fs");
11390
+ var import_node_fs4 = require("node:fs");
11305
11391
  var import_node_path4 = require("node:path");
11306
11392
  function resolveFile(relpath) {
11307
- if ((0, import_node_fs3.existsSync)(relpath)) return relpath;
11308
- if ((0, import_node_fs3.existsSync)(".claude/worktrees")) {
11393
+ if ((0, import_node_fs4.existsSync)(relpath)) return relpath;
11394
+ if ((0, import_node_fs4.existsSync)(".claude/worktrees")) {
11309
11395
  try {
11310
- const entries = (0, import_node_fs3.readdirSync)(".claude/worktrees", { withFileTypes: true });
11396
+ const entries = (0, import_node_fs4.readdirSync)(".claude/worktrees", { withFileTypes: true });
11311
11397
  for (const entry of entries) {
11312
11398
  if (!entry.isDirectory()) continue;
11313
11399
  const candidate = (0, import_node_path4.join)(".claude/worktrees", entry.name, relpath);
11314
- if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
11400
+ if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
11315
11401
  }
11316
11402
  } catch {
11317
11403
  }
@@ -11363,10 +11449,10 @@ function getChangedFiles() {
11363
11449
  function getWorktreeFiles() {
11364
11450
  const result = [];
11365
11451
  const worktreeDir = ".claude/worktrees";
11366
- if (!(0, import_node_fs3.existsSync)(worktreeDir)) return result;
11452
+ if (!(0, import_node_fs4.existsSync)(worktreeDir)) return result;
11367
11453
  try {
11368
11454
  const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
11369
- const entries = (0, import_node_fs3.readdirSync)(worktreeDir, { withFileTypes: true });
11455
+ const entries = (0, import_node_fs4.readdirSync)(worktreeDir, { withFileTypes: true });
11370
11456
  for (const entry of entries) {
11371
11457
  if (!entry.isDirectory()) continue;
11372
11458
  const wtDir = (0, import_node_path4.join)(worktreeDir, entry.name);
@@ -11378,7 +11464,7 @@ function getWorktreeFiles() {
11378
11464
  }
11379
11465
  function scanDir(baseDir, dir, minMtime, result) {
11380
11466
  try {
11381
- const entries = (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true });
11467
+ const entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
11382
11468
  for (const entry of entries) {
11383
11469
  const fullPath = (0, import_node_path4.join)(dir, entry.name);
11384
11470
  if (entry.isDirectory()) {
@@ -11388,7 +11474,7 @@ function scanDir(baseDir, dir, minMtime, result) {
11388
11474
  const ext = (0, import_node_path4.extname)(entry.name).slice(1);
11389
11475
  if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
11390
11476
  try {
11391
- const stat = (0, import_node_fs3.statSync)(fullPath);
11477
+ const stat = (0, import_node_fs4.statSync)(fullPath);
11392
11478
  if (stat.mtimeMs >= minMtime) {
11393
11479
  const relPath = fullPath.slice(baseDir.length + 1);
11394
11480
  result.push(relPath);
@@ -11427,7 +11513,7 @@ function getCurrentCommit() {
11427
11513
  }
11428
11514
 
11429
11515
  // src/lib/files.ts
11430
- var import_node_fs4 = require("node:fs");
11516
+ var import_node_fs5 = require("node:fs");
11431
11517
  var import_node_path5 = require("node:path");
11432
11518
  var LANG_MAP = {
11433
11519
  // Analyzable (static analysis + Gemini)
@@ -11504,7 +11590,7 @@ function sortByMtime(files) {
11504
11590
  const resolved = resolveFile(f);
11505
11591
  if (!resolved) return null;
11506
11592
  try {
11507
- const stat = (0, import_node_fs4.statSync)(resolved);
11593
+ const stat = (0, import_node_fs5.statSync)(resolved);
11508
11594
  return { path: f, resolved, mtime: stat.mtimeMs };
11509
11595
  } catch {
11510
11596
  return null;
@@ -11526,7 +11612,7 @@ function collectCodeDelta(files, opts) {
11526
11612
  if (!resolved) continue;
11527
11613
  let size;
11528
11614
  try {
11529
- size = (0, import_node_fs4.statSync)(resolved).size;
11615
+ size = (0, import_node_fs5.statSync)(resolved).size;
11530
11616
  } catch {
11531
11617
  continue;
11532
11618
  }
@@ -11534,7 +11620,7 @@ function collectCodeDelta(files, opts) {
11534
11620
  if (totalSize + size > maxTotalBytes) break;
11535
11621
  let content;
11536
11622
  try {
11537
- content = (0, import_node_fs4.readFileSync)(resolved, "utf-8");
11623
+ content = (0, import_node_fs5.readFileSync)(resolved, "utf-8");
11538
11624
  } catch {
11539
11625
  continue;
11540
11626
  }
@@ -11557,12 +11643,12 @@ function collectCodeDelta(files, opts) {
11557
11643
  }
11558
11644
 
11559
11645
  // src/lib/debounce.ts
11560
- var import_node_fs5 = require("node:fs");
11646
+ var import_node_fs6 = require("node:fs");
11561
11647
  var import_node_crypto = require("node:crypto");
11562
11648
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
11563
- if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return null;
11649
+ if (!(0, import_node_fs6.existsSync)(DEBOUNCE_FILE)) return null;
11564
11650
  try {
11565
- const lastTs = parseInt((0, import_node_fs5.readFileSync)(DEBOUNCE_FILE, "utf-8").trim(), 10);
11651
+ const lastTs = parseInt((0, import_node_fs6.readFileSync)(DEBOUNCE_FILE, "utf-8").trim(), 10);
11566
11652
  const nowTs = Math.floor(Date.now() / 1e3);
11567
11653
  const elapsed = nowTs - lastTs;
11568
11654
  if (elapsed < debounceSeconds) {
@@ -11574,10 +11660,10 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
11574
11660
  }
11575
11661
  function checkMtime(files, bypassForRecentCommits) {
11576
11662
  if (bypassForRecentCommits) return null;
11577
- if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return null;
11663
+ if (!(0, import_node_fs6.existsSync)(DEBOUNCE_FILE)) return null;
11578
11664
  let debounceTime;
11579
11665
  try {
11580
- debounceTime = (0, import_node_fs5.statSync)(DEBOUNCE_FILE).mtimeMs;
11666
+ debounceTime = (0, import_node_fs6.statSync)(DEBOUNCE_FILE).mtimeMs;
11581
11667
  } catch {
11582
11668
  return null;
11583
11669
  }
@@ -11585,7 +11671,7 @@ function checkMtime(files, bypassForRecentCommits) {
11585
11671
  const resolved = resolveFile(f);
11586
11672
  if (!resolved) continue;
11587
11673
  try {
11588
- const stat = (0, import_node_fs5.statSync)(resolved);
11674
+ const stat = (0, import_node_fs6.statSync)(resolved);
11589
11675
  if (stat.mtimeMs > debounceTime) {
11590
11676
  return null;
11591
11677
  }
@@ -11601,8 +11687,8 @@ function computeContentHash(files) {
11601
11687
  for (const f of sorted) {
11602
11688
  const resolved = resolveFile(f) ?? f;
11603
11689
  try {
11604
- if ((0, import_node_fs5.existsSync)(resolved)) {
11605
- hash.update((0, import_node_fs5.readFileSync)(resolved));
11690
+ if ((0, import_node_fs6.existsSync)(resolved)) {
11691
+ hash.update((0, import_node_fs6.readFileSync)(resolved));
11606
11692
  }
11607
11693
  } catch {
11608
11694
  }
@@ -11611,9 +11697,9 @@ function computeContentHash(files) {
11611
11697
  }
11612
11698
  function checkContentHash(files) {
11613
11699
  const hash = computeContentHash(files);
11614
- if ((0, import_node_fs5.existsSync)(HASH_FILE)) {
11700
+ if ((0, import_node_fs6.existsSync)(HASH_FILE)) {
11615
11701
  try {
11616
- const storedHash = (0, import_node_fs5.readFileSync)(HASH_FILE, "utf-8").trim();
11702
+ const storedHash = (0, import_node_fs6.readFileSync)(HASH_FILE, "utf-8").trim();
11617
11703
  if (hash === storedHash) {
11618
11704
  return { skip: "No source changes since last analysis", hash };
11619
11705
  }
@@ -11623,23 +11709,23 @@ function checkContentHash(files) {
11623
11709
  return { skip: null, hash };
11624
11710
  }
11625
11711
  function recordAnalysisStart() {
11626
- (0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
11627
- (0, import_node_fs5.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
11712
+ (0, import_node_fs6.mkdirSync)(GATE_DIR, { recursive: true });
11713
+ (0, import_node_fs6.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
11628
11714
  }
11629
11715
  function recordPassHash(hash) {
11630
- (0, import_node_fs5.writeFileSync)(HASH_FILE, hash);
11716
+ (0, import_node_fs6.writeFileSync)(HASH_FILE, hash);
11631
11717
  }
11632
11718
  function narrowToRecent(files) {
11633
- if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return files;
11719
+ if (!(0, import_node_fs6.existsSync)(DEBOUNCE_FILE)) return files;
11634
11720
  let debounceTime;
11635
11721
  try {
11636
- debounceTime = (0, import_node_fs5.statSync)(DEBOUNCE_FILE).mtimeMs;
11722
+ debounceTime = (0, import_node_fs6.statSync)(DEBOUNCE_FILE).mtimeMs;
11637
11723
  } catch {
11638
11724
  return files;
11639
11725
  }
11640
11726
  const recent = files.filter((f) => {
11641
11727
  try {
11642
- return (0, import_node_fs5.existsSync)(f) && (0, import_node_fs5.statSync)(f).mtimeMs > debounceTime;
11728
+ return (0, import_node_fs6.existsSync)(f) && (0, import_node_fs6.statSync)(f).mtimeMs > debounceTime;
11643
11729
  } catch {
11644
11730
  return false;
11645
11731
  }
@@ -11647,9 +11733,9 @@ function narrowToRecent(files) {
11647
11733
  return recent.length > 0 ? recent : files;
11648
11734
  }
11649
11735
  function readIteration(currentCommit, _contentHash) {
11650
- if (!(0, import_node_fs5.existsSync)(ITERATION_FILE)) return 1;
11736
+ if (!(0, import_node_fs6.existsSync)(ITERATION_FILE)) return 1;
11651
11737
  try {
11652
- const stored = (0, import_node_fs5.readFileSync)(ITERATION_FILE, "utf-8").trim();
11738
+ const stored = (0, import_node_fs6.readFileSync)(ITERATION_FILE, "utf-8").trim();
11653
11739
  const parts = stored.split(":");
11654
11740
  const iter = parseInt(parts[0], 10);
11655
11741
  const storedCommit = parts[1] ?? "";
@@ -11677,14 +11763,14 @@ function checkMaxIterations(currentCommit, maxIterations = MAX_ITERATIONS, conte
11677
11763
  return { skip: null, iteration };
11678
11764
  }
11679
11765
  function writeIteration(iteration, commit, _contentHash) {
11680
- (0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
11766
+ (0, import_node_fs6.mkdirSync)(GATE_DIR, { recursive: true });
11681
11767
  const ts = Math.floor(Date.now() / 1e3);
11682
- (0, import_node_fs5.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}`);
11768
+ (0, import_node_fs6.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}`);
11683
11769
  }
11684
11770
 
11685
11771
  // src/lib/static-analysis.ts
11686
11772
  var import_node_child_process6 = require("node:child_process");
11687
- var import_node_fs6 = require("node:fs");
11773
+ var import_node_fs7 = require("node:fs");
11688
11774
  var SEVERITY_ORDER = {
11689
11775
  Error: 0,
11690
11776
  Critical: 0,
@@ -11711,7 +11797,7 @@ function runCodacyAnalysis(files) {
11711
11797
  if (files.length === 0) return empty;
11712
11798
  const existingFiles = files.filter((f) => {
11713
11799
  try {
11714
- return (0, import_node_fs6.existsSync)(f);
11800
+ return (0, import_node_fs7.existsSync)(f);
11715
11801
  } catch {
11716
11802
  return false;
11717
11803
  }
@@ -11773,7 +11859,7 @@ function runCodacyAnalysis(files) {
11773
11859
  }
11774
11860
 
11775
11861
  // src/lib/specs.ts
11776
- var import_node_fs7 = require("node:fs");
11862
+ var import_node_fs8 = require("node:fs");
11777
11863
  var import_node_path6 = require("node:path");
11778
11864
  var SPEC_CANDIDATES = [
11779
11865
  "CLAUDE.md",
@@ -11798,15 +11884,15 @@ function discoverSpecs() {
11798
11884
  if (result.length >= MAX_SPEC_FILES) return false;
11799
11885
  if (totalBytes >= MAX_TOTAL_SPEC_BYTES) return false;
11800
11886
  if (seen.has(specPath)) return true;
11801
- if (!(0, import_node_fs7.existsSync)(specPath)) return true;
11887
+ if (!(0, import_node_fs8.existsSync)(specPath)) return true;
11802
11888
  seen.add(specPath);
11803
11889
  const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
11804
11890
  const readBytes = Math.min(MAX_SPEC_FILE_BYTES, remaining);
11805
11891
  try {
11806
11892
  const buf = Buffer.alloc(readBytes);
11807
- const fd = (0, import_node_fs7.openSync)(specPath, "r");
11808
- const bytesRead = (0, import_node_fs7.readSync)(fd, buf, 0, readBytes, 0);
11809
- (0, import_node_fs7.closeSync)(fd);
11893
+ const fd = (0, import_node_fs8.openSync)(specPath, "r");
11894
+ const bytesRead = (0, import_node_fs8.readSync)(fd, buf, 0, readBytes, 0);
11895
+ (0, import_node_fs8.closeSync)(fd);
11810
11896
  const content = buf.slice(0, bytesRead).toString("utf-8");
11811
11897
  if (!content) return true;
11812
11898
  result.push({ path: specPath, content });
@@ -11819,7 +11905,7 @@ function discoverSpecs() {
11819
11905
  if (!addSpec(candidate)) break;
11820
11906
  }
11821
11907
  for (const dir of ["spec", "docs"]) {
11822
- if (!(0, import_node_fs7.existsSync)(dir)) continue;
11908
+ if (!(0, import_node_fs8.existsSync)(dir)) continue;
11823
11909
  try {
11824
11910
  const mdFiles = findMdFiles(dir, 2).sort();
11825
11911
  for (const mdFile of mdFiles) {
@@ -11834,7 +11920,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
11834
11920
  if (depth >= maxDepth) return [];
11835
11921
  const result = [];
11836
11922
  try {
11837
- const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
11923
+ const entries = (0, import_node_fs8.readdirSync)(dir, { withFileTypes: true });
11838
11924
  for (const entry of entries) {
11839
11925
  const fullPath = (0, import_node_path6.join)(dir, entry.name);
11840
11926
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -11853,14 +11939,14 @@ function discoverPlans() {
11853
11939
  const candidates = [];
11854
11940
  const seen = /* @__PURE__ */ new Set();
11855
11941
  for (const plansDir of [localPlansDir, homePlansDir]) {
11856
- if (!(0, import_node_fs7.existsSync)(plansDir)) continue;
11942
+ if (!(0, import_node_fs8.existsSync)(plansDir)) continue;
11857
11943
  try {
11858
- for (const f of (0, import_node_fs7.readdirSync)(plansDir)) {
11944
+ for (const f of (0, import_node_fs8.readdirSync)(plansDir)) {
11859
11945
  if (!f.endsWith(".md") || seen.has(f)) continue;
11860
11946
  seen.add(f);
11861
11947
  const fullPath = (0, import_node_path6.join)(plansDir, f);
11862
11948
  try {
11863
- const stat = (0, import_node_fs7.statSync)(fullPath);
11949
+ const stat = (0, import_node_fs8.statSync)(fullPath);
11864
11950
  candidates.push({ name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size });
11865
11951
  } catch {
11866
11952
  }
@@ -11873,7 +11959,7 @@ function discoverPlans() {
11873
11959
  for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
11874
11960
  if (entry.size > MAX_PLAN_FILE_BYTES) continue;
11875
11961
  try {
11876
- const content = (0, import_node_fs7.readFileSync)(entry.path, "utf-8");
11962
+ const content = (0, import_node_fs8.readFileSync)(entry.path, "utf-8");
11877
11963
  result.push({ name: entry.name, content });
11878
11964
  } catch {
11879
11965
  }
@@ -11882,19 +11968,19 @@ function discoverPlans() {
11882
11968
  }
11883
11969
 
11884
11970
  // src/lib/snapshot.ts
11885
- var import_node_fs8 = require("node:fs");
11971
+ var import_node_fs9 = require("node:fs");
11886
11972
  var import_node_path7 = require("node:path");
11887
11973
  var import_node_child_process7 = require("node:child_process");
11888
11974
  function generateSnapshotDiffs(files) {
11889
- if (!(0, import_node_fs8.existsSync)(SNAPSHOT_DIR)) {
11975
+ if (!(0, import_node_fs9.existsSync)(SNAPSHOT_DIR)) {
11890
11976
  return { diffs: [], has_snapshots: false };
11891
11977
  }
11892
11978
  const diffs = [];
11893
11979
  for (const file of files) {
11894
11980
  const snapshotPath = (0, import_node_path7.join)(SNAPSHOT_DIR, file.path);
11895
11981
  const language = file.language ?? detectLanguage(file.path);
11896
- if ((0, import_node_fs8.existsSync)(snapshotPath)) {
11897
- const oldContent = (0, import_node_fs8.readFileSync)(snapshotPath, "utf-8");
11982
+ if ((0, import_node_fs9.existsSync)(snapshotPath)) {
11983
+ const oldContent = (0, import_node_fs9.readFileSync)(snapshotPath, "utf-8");
11898
11984
  if (oldContent === file.content) continue;
11899
11985
  const diff = computeDiff(oldContent, file.content, file.path);
11900
11986
  if (diff) {
@@ -11920,8 +12006,8 @@ function saveSnapshots(files) {
11920
12006
  for (const file of files) {
11921
12007
  const snapshotPath = (0, import_node_path7.join)(SNAPSHOT_DIR, file.path);
11922
12008
  snapshotPaths.add(snapshotPath);
11923
- (0, import_node_fs8.mkdirSync)((0, import_node_path7.dirname)(snapshotPath), { recursive: true });
11924
- (0, import_node_fs8.writeFileSync)(snapshotPath, file.content);
12009
+ (0, import_node_fs9.mkdirSync)((0, import_node_path7.dirname)(snapshotPath), { recursive: true });
12010
+ (0, import_node_fs9.writeFileSync)(snapshotPath, file.content);
11925
12011
  }
11926
12012
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
11927
12013
  }
@@ -11929,9 +12015,9 @@ function computeDiff(oldContent, newContent, filePath) {
11929
12015
  const tmpOld = (0, import_node_path7.join)(SNAPSHOT_DIR, ".diff-old.tmp");
11930
12016
  const tmpNew = (0, import_node_path7.join)(SNAPSHOT_DIR, ".diff-new.tmp");
11931
12017
  try {
11932
- (0, import_node_fs8.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
11933
- (0, import_node_fs8.writeFileSync)(tmpOld, oldContent);
11934
- (0, import_node_fs8.writeFileSync)(tmpNew, newContent);
12018
+ (0, import_node_fs9.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
12019
+ (0, import_node_fs9.writeFileSync)(tmpOld, oldContent);
12020
+ (0, import_node_fs9.writeFileSync)(tmpNew, newContent);
11935
12021
  const result = (0, import_node_child_process7.execSync)(
11936
12022
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
11937
12023
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -11945,32 +12031,32 @@ function computeDiff(oldContent, newContent, filePath) {
11945
12031
  return null;
11946
12032
  } finally {
11947
12033
  try {
11948
- (0, import_node_fs8.unlinkSync)(tmpOld);
12034
+ (0, import_node_fs9.unlinkSync)(tmpOld);
11949
12035
  } catch {
11950
12036
  }
11951
12037
  try {
11952
- (0, import_node_fs8.unlinkSync)(tmpNew);
12038
+ (0, import_node_fs9.unlinkSync)(tmpNew);
11953
12039
  } catch {
11954
12040
  }
11955
12041
  }
11956
12042
  }
11957
12043
  function cleanStaleSnapshots(dir, keepSet) {
11958
- if (!(0, import_node_fs8.existsSync)(dir)) return;
12044
+ if (!(0, import_node_fs9.existsSync)(dir)) return;
11959
12045
  try {
11960
- const entries = (0, import_node_fs8.readdirSync)(dir, { withFileTypes: true });
12046
+ const entries = (0, import_node_fs9.readdirSync)(dir, { withFileTypes: true });
11961
12047
  for (const entry of entries) {
11962
12048
  if (entry.name.startsWith(".")) continue;
11963
12049
  const fullPath = (0, import_node_path7.join)(dir, entry.name);
11964
12050
  if (entry.isDirectory()) {
11965
12051
  cleanStaleSnapshots(fullPath, keepSet);
11966
12052
  try {
11967
- const remaining = (0, import_node_fs8.readdirSync)(fullPath);
11968
- if (remaining.length === 0) (0, import_node_fs8.rmdirSync)(fullPath);
12053
+ const remaining = (0, import_node_fs9.readdirSync)(fullPath);
12054
+ if (remaining.length === 0) (0, import_node_fs9.rmdirSync)(fullPath);
11969
12055
  } catch {
11970
12056
  }
11971
12057
  } else if (!keepSet.has(fullPath)) {
11972
12058
  try {
11973
- (0, import_node_fs8.unlinkSync)(fullPath);
12059
+ (0, import_node_fs9.unlinkSync)(fullPath);
11974
12060
  } catch {
11975
12061
  }
11976
12062
  }
@@ -11980,14 +12066,14 @@ function cleanStaleSnapshots(dir, keepSet) {
11980
12066
  }
11981
12067
 
11982
12068
  // src/lib/offline.ts
11983
- var import_node_fs9 = require("node:fs");
12069
+ var import_node_fs10 = require("node:fs");
11984
12070
  var import_node_crypto2 = require("node:crypto");
11985
12071
  function cacheRequest(body) {
11986
12072
  try {
11987
- (0, import_node_fs9.mkdirSync)(CACHE_DIR, { recursive: true });
12073
+ (0, import_node_fs10.mkdirSync)(CACHE_DIR, { recursive: true });
11988
12074
  const suffix = (0, import_node_crypto2.randomBytes)(4).toString("hex");
11989
12075
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
11990
- (0, import_node_fs9.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
12076
+ (0, import_node_fs10.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
11991
12077
  } catch {
11992
12078
  }
11993
12079
  }
@@ -12070,7 +12156,7 @@ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPromp
12070
12156
  }
12071
12157
 
12072
12158
  // src/lib/transcript.ts
12073
- var import_node_fs10 = require("node:fs");
12159
+ var import_node_fs11 = require("node:fs");
12074
12160
  var MAX_READ_BYTES = 256 * 1024;
12075
12161
  var SMALL_FILE_BYTES = 64 * 1024;
12076
12162
  var MAX_FILES_LIST = 20;
@@ -12092,14 +12178,14 @@ async function extractActionSummary(transcriptPath) {
12092
12178
  function readTurnLines(transcriptPath) {
12093
12179
  let size;
12094
12180
  try {
12095
- size = (0, import_node_fs10.statSync)(transcriptPath).size;
12181
+ size = (0, import_node_fs11.statSync)(transcriptPath).size;
12096
12182
  } catch {
12097
12183
  return null;
12098
12184
  }
12099
12185
  if (size === 0) return null;
12100
12186
  let raw;
12101
12187
  if (size <= SMALL_FILE_BYTES) {
12102
- raw = (0, import_node_fs10.readFileSync)(transcriptPath, "utf-8");
12188
+ raw = (0, import_node_fs11.readFileSync)(transcriptPath, "utf-8");
12103
12189
  } else {
12104
12190
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
12105
12191
  const fd = require("node:fs").openSync(transcriptPath, "r");
@@ -12463,19 +12549,46 @@ async function runAnalyze(opts, globals) {
12463
12549
  }
12464
12550
  requestBody.intent_context = intentContext;
12465
12551
  }
12466
- const result = await apiRequest({
12552
+ const ANALYZE_TIMEOUT_MS = 1e5;
12553
+ let result = await apiRequest({
12467
12554
  method: "POST",
12468
12555
  path: "/analyze",
12469
12556
  serviceUrl: urlResult.data,
12470
12557
  token: tokenResult.data.token,
12471
12558
  body: requestBody,
12472
12559
  verbose: globals.verbose,
12473
- timeout: 12e4
12560
+ timeout: ANALYZE_TIMEOUT_MS,
12561
+ cmd: "analyze",
12562
+ encodeBody: true
12474
12563
  });
12564
+ if (!result.ok && (result.category === "network" || result.category === "timeout")) {
12565
+ logEvent("analyze_warm_retry", { first_error: result.error, category: result.category });
12566
+ await apiRequest({
12567
+ method: "GET",
12568
+ path: "/memory",
12569
+ serviceUrl: urlResult.data,
12570
+ token: tokenResult.data.token,
12571
+ verbose: globals.verbose,
12572
+ timeout: 15e3,
12573
+ cmd: "analyze_warmup"
12574
+ });
12575
+ result = await apiRequest({
12576
+ method: "POST",
12577
+ path: "/analyze",
12578
+ serviceUrl: urlResult.data,
12579
+ token: tokenResult.data.token,
12580
+ body: requestBody,
12581
+ verbose: globals.verbose,
12582
+ timeout: ANALYZE_TIMEOUT_MS,
12583
+ cmd: "analyze",
12584
+ retry: true,
12585
+ encodeBody: true
12586
+ });
12587
+ }
12475
12588
  if (!result.ok) {
12476
12589
  cacheRequest(requestBody);
12477
12590
  const fallback = buildOfflineFallback(
12478
- `GATE.md service error \u2014 offline mode`,
12591
+ `GATE.md offline \u2014 ${result.error}`,
12479
12592
  staticResults
12480
12593
  );
12481
12594
  printJsonCompact(fallback);
@@ -12587,7 +12700,7 @@ async function runAnalyze(opts, globals) {
12587
12700
  }
12588
12701
 
12589
12702
  // src/commands/review.ts
12590
- var import_node_fs11 = require("node:fs");
12703
+ var import_node_fs12 = require("node:fs");
12591
12704
  function registerReviewCommand(program2) {
12592
12705
  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) => {
12593
12706
  const globals = program2.opts();
@@ -12606,7 +12719,7 @@ async function runReview(opts, globals) {
12606
12719
  const securityFiles = filterSecurity(allFiles);
12607
12720
  let staticResults;
12608
12721
  if (isCodacyAvailable()) {
12609
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs11.existsSync)(f) || resolveFile(f) !== null);
12722
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs12.existsSync)(f) || resolveFile(f) !== null);
12610
12723
  staticResults = runCodacyAnalysis(scannable);
12611
12724
  } else {
12612
12725
  staticResults = {
@@ -12631,7 +12744,7 @@ async function runReview(opts, globals) {
12631
12744
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
12632
12745
  specs = [];
12633
12746
  for (const p of specPaths) {
12634
- if (!(0, import_node_fs11.existsSync)(p)) continue;
12747
+ if (!(0, import_node_fs12.existsSync)(p)) continue;
12635
12748
  try {
12636
12749
  const { readFileSync: readFileSync6 } = await import("node:fs");
12637
12750
  const content = readFileSync6(p, "utf-8");
@@ -12692,7 +12805,7 @@ async function runReview(opts, globals) {
12692
12805
  }
12693
12806
 
12694
12807
  // src/commands/init.ts
12695
- var import_node_fs12 = require("node:fs");
12808
+ var import_node_fs13 = require("node:fs");
12696
12809
  var import_promises8 = require("node:fs/promises");
12697
12810
  var import_node_path8 = require("node:path");
12698
12811
  var import_node_child_process8 = require("node:child_process");
@@ -12706,7 +12819,7 @@ function resolveDataDir() {
12706
12819
  // local dev: running from repo root
12707
12820
  ];
12708
12821
  for (const candidate of candidates) {
12709
- if ((0, import_node_fs12.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
12822
+ if ((0, import_node_fs13.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
12710
12823
  return candidate;
12711
12824
  }
12712
12825
  }
@@ -12722,7 +12835,7 @@ function registerInitCommand(program2) {
12722
12835
  program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
12723
12836
  const force = opts.force ?? false;
12724
12837
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
12725
- const isProject = projectMarkers.some((m) => (0, import_node_fs12.existsSync)(m));
12838
+ const isProject = projectMarkers.some((m) => (0, import_node_fs13.existsSync)(m));
12726
12839
  if (!isProject) {
12727
12840
  printError("No project detected in the current directory.");
12728
12841
  printInfo('Run "gate init" from your project root.');
@@ -12782,14 +12895,14 @@ function registerInitCommand(program2) {
12782
12895
  for (const skill of skills) {
12783
12896
  const src = (0, import_node_path8.join)(skillsSource, skill);
12784
12897
  const dest = (0, import_node_path8.join)(skillsDest, skill);
12785
- if (!(0, import_node_fs12.existsSync)(src)) {
12898
+ if (!(0, import_node_fs13.existsSync)(src)) {
12786
12899
  printWarn(` Skill data not found: ${skill}`);
12787
12900
  continue;
12788
12901
  }
12789
- if ((0, import_node_fs12.existsSync)(dest) && !force) {
12902
+ if ((0, import_node_fs13.existsSync)(dest) && !force) {
12790
12903
  const srcSkill = (0, import_node_path8.join)(src, "SKILL.md");
12791
12904
  const destSkill = (0, import_node_path8.join)(dest, "SKILL.md");
12792
- if ((0, import_node_fs12.existsSync)(destSkill)) {
12905
+ if ((0, import_node_fs13.existsSync)(destSkill)) {
12793
12906
  try {
12794
12907
  const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
12795
12908
  const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
@@ -12835,7 +12948,7 @@ function registerInitCommand(program2) {
12835
12948
  }
12836
12949
 
12837
12950
  // src/cli.ts
12838
- program.name("gate").description("CLI for GATE.md quality gate service").version("0.14.1").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
12951
+ program.name("gate").description("CLI for GATE.md quality gate service").version("0.14.3").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
12839
12952
  registerAuthCommands(program);
12840
12953
  registerHooksCommands(program);
12841
12954
  registerIntentCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/gate-cli",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "CLI for GATE.md quality gate service",
5
5
  "bin": {
6
6
  "gate": "./bin/gate.js"