@codacy/gate-cli 0.4.1 → 0.6.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 +316 -121
- package/package.json +1 -1
package/bin/gate.js
CHANGED
|
@@ -10373,6 +10373,10 @@ var MAX_TOTAL_SPEC_BYTES = 30720;
|
|
|
10373
10373
|
var MAX_PLAN_FILES = 3;
|
|
10374
10374
|
var MAX_PLAN_FILE_BYTES = 10240;
|
|
10375
10375
|
var MAX_INTENT_CHARS = 2e3;
|
|
10376
|
+
var SNAPSHOT_DIR = `${GATE_DIR}/.snapshot`;
|
|
10377
|
+
var CONVERSATION_BUFFER_FILE = `${GATE_DIR}/.conversation-buffer`;
|
|
10378
|
+
var CONVERSATION_MAX_ENTRIES = 10;
|
|
10379
|
+
var CONVERSATION_WINDOW_MINUTES = 15;
|
|
10376
10380
|
var MAX_FINDINGS = 25;
|
|
10377
10381
|
var ANALYZABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10378
10382
|
"ts",
|
|
@@ -10852,41 +10856,113 @@ function registerHooksCommands(program2) {
|
|
|
10852
10856
|
});
|
|
10853
10857
|
}
|
|
10854
10858
|
|
|
10855
|
-
// src/lib/
|
|
10859
|
+
// src/lib/conversation-buffer.ts
|
|
10856
10860
|
var import_promises5 = require("node:fs/promises");
|
|
10857
|
-
|
|
10861
|
+
var import_node_fs = require("node:fs");
|
|
10862
|
+
var import_node_child_process4 = require("node:child_process");
|
|
10863
|
+
function stripImageReferences(text) {
|
|
10864
|
+
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
10865
|
+
}
|
|
10866
|
+
async function appendToConversationBuffer(prompt, sessionId) {
|
|
10858
10867
|
try {
|
|
10859
10868
|
await (0, import_promises5.mkdir)(GATE_DIR, { recursive: true });
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10869
|
+
let sanitized = prompt.length > MAX_INTENT_CHARS ? prompt.slice(0, MAX_INTENT_CHARS) : prompt;
|
|
10870
|
+
sanitized = stripImageReferences(sanitized);
|
|
10871
|
+
const entry = {
|
|
10872
|
+
prompt: sanitized,
|
|
10863
10873
|
session_id: sessionId,
|
|
10864
10874
|
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10865
10875
|
};
|
|
10866
|
-
const
|
|
10867
|
-
|
|
10868
|
-
|
|
10876
|
+
const entries = await readBufferEntries();
|
|
10877
|
+
const cutoff = Date.now() - CONVERSATION_WINDOW_MINUTES * 60 * 1e3;
|
|
10878
|
+
const recent = entries.filter((e) => {
|
|
10879
|
+
const ts = new Date(e.captured_at).getTime();
|
|
10880
|
+
return !isNaN(ts) && ts > cutoff;
|
|
10881
|
+
});
|
|
10882
|
+
recent.push(entry);
|
|
10883
|
+
const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
|
|
10884
|
+
const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
10885
|
+
const tmpFile = `${CONVERSATION_BUFFER_FILE}.tmp`;
|
|
10886
|
+
await (0, import_promises5.writeFile)(tmpFile, content);
|
|
10887
|
+
await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
|
|
10869
10888
|
} catch {
|
|
10870
10889
|
}
|
|
10871
10890
|
}
|
|
10872
|
-
async function
|
|
10891
|
+
async function readAndClearConversationBuffer() {
|
|
10873
10892
|
try {
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10893
|
+
if ((0, import_node_fs.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
10894
|
+
const entries = await readBufferEntries();
|
|
10895
|
+
await (0, import_promises5.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
|
|
10896
|
+
});
|
|
10897
|
+
if (entries.length > 0) {
|
|
10898
|
+
return {
|
|
10899
|
+
prompts: entries,
|
|
10900
|
+
recent_commits: getRecentCommitMessages()
|
|
10901
|
+
};
|
|
10902
|
+
}
|
|
10903
|
+
}
|
|
10904
|
+
if ((0, import_node_fs.existsSync)(INTENT_FILE)) {
|
|
10905
|
+
try {
|
|
10906
|
+
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
10907
|
+
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
10908
|
+
});
|
|
10909
|
+
const data = JSON.parse(content);
|
|
10910
|
+
if (data.prompt) {
|
|
10911
|
+
return {
|
|
10912
|
+
prompts: [{
|
|
10913
|
+
prompt: stripImageReferences(data.prompt),
|
|
10914
|
+
session_id: data.session_id ?? "",
|
|
10915
|
+
captured_at: data.captured_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
10916
|
+
}],
|
|
10917
|
+
recent_commits: getRecentCommitMessages()
|
|
10918
|
+
};
|
|
10919
|
+
}
|
|
10920
|
+
} catch {
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10923
|
+
return null;
|
|
10878
10924
|
} catch {
|
|
10879
10925
|
return null;
|
|
10880
10926
|
}
|
|
10881
10927
|
}
|
|
10928
|
+
async function readBufferEntries() {
|
|
10929
|
+
try {
|
|
10930
|
+
const content = await (0, import_promises5.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
|
|
10931
|
+
const entries = [];
|
|
10932
|
+
for (const line of content.split("\n")) {
|
|
10933
|
+
const trimmed = line.trim();
|
|
10934
|
+
if (!trimmed) continue;
|
|
10935
|
+
try {
|
|
10936
|
+
const parsed = JSON.parse(trimmed);
|
|
10937
|
+
if (parsed.prompt) entries.push(parsed);
|
|
10938
|
+
} catch {
|
|
10939
|
+
}
|
|
10940
|
+
}
|
|
10941
|
+
return entries;
|
|
10942
|
+
} catch {
|
|
10943
|
+
return [];
|
|
10944
|
+
}
|
|
10945
|
+
}
|
|
10946
|
+
function getRecentCommitMessages() {
|
|
10947
|
+
try {
|
|
10948
|
+
const output = (0, import_node_child_process4.execSync)(
|
|
10949
|
+
'git log --since="30 minutes ago" --format="%s" -5',
|
|
10950
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
10951
|
+
).trim();
|
|
10952
|
+
if (!output) return [];
|
|
10953
|
+
return output.split("\n").filter((l) => l.length > 0);
|
|
10954
|
+
} catch {
|
|
10955
|
+
return [];
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10882
10958
|
|
|
10883
10959
|
// src/commands/intent.ts
|
|
10884
|
-
var
|
|
10960
|
+
var import_node_fs2 = require("node:fs");
|
|
10885
10961
|
function registerIntentCommands(program2) {
|
|
10886
10962
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
10887
10963
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
10888
10964
|
try {
|
|
10889
|
-
if (!(0,
|
|
10965
|
+
if (!(0, import_node_fs2.existsSync)(GATE_DIR)) {
|
|
10890
10966
|
process.exit(0);
|
|
10891
10967
|
}
|
|
10892
10968
|
const chunks = [];
|
|
@@ -10907,7 +10983,7 @@ function registerIntentCommands(program2) {
|
|
|
10907
10983
|
if (!prompt) {
|
|
10908
10984
|
process.exit(0);
|
|
10909
10985
|
}
|
|
10910
|
-
await
|
|
10986
|
+
await appendToConversationBuffer(prompt, event.session_id ?? "");
|
|
10911
10987
|
} catch {
|
|
10912
10988
|
}
|
|
10913
10989
|
process.exit(0);
|
|
@@ -11224,18 +11300,18 @@ function registerFeedbackCommand(program2) {
|
|
|
11224
11300
|
}
|
|
11225
11301
|
|
|
11226
11302
|
// src/lib/git.ts
|
|
11227
|
-
var
|
|
11228
|
-
var
|
|
11303
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11304
|
+
var import_node_fs3 = require("node:fs");
|
|
11229
11305
|
var import_node_path4 = require("node:path");
|
|
11230
11306
|
function resolveFile(relpath) {
|
|
11231
|
-
if ((0,
|
|
11232
|
-
if ((0,
|
|
11307
|
+
if ((0, import_node_fs3.existsSync)(relpath)) return relpath;
|
|
11308
|
+
if ((0, import_node_fs3.existsSync)(".claude/worktrees")) {
|
|
11233
11309
|
try {
|
|
11234
|
-
const entries = (0,
|
|
11310
|
+
const entries = (0, import_node_fs3.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
11235
11311
|
for (const entry of entries) {
|
|
11236
11312
|
if (!entry.isDirectory()) continue;
|
|
11237
11313
|
const candidate = (0, import_node_path4.join)(".claude/worktrees", entry.name, relpath);
|
|
11238
|
-
if ((0,
|
|
11314
|
+
if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
|
|
11239
11315
|
}
|
|
11240
11316
|
} catch {
|
|
11241
11317
|
}
|
|
@@ -11244,7 +11320,7 @@ function resolveFile(relpath) {
|
|
|
11244
11320
|
}
|
|
11245
11321
|
function execGit(cmd) {
|
|
11246
11322
|
try {
|
|
11247
|
-
return (0,
|
|
11323
|
+
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
11248
11324
|
} catch {
|
|
11249
11325
|
return "";
|
|
11250
11326
|
}
|
|
@@ -11254,6 +11330,7 @@ function splitLines(s) {
|
|
|
11254
11330
|
}
|
|
11255
11331
|
function getChangedFiles() {
|
|
11256
11332
|
const sets = /* @__PURE__ */ new Set();
|
|
11333
|
+
let hasRecentCommitFiles = false;
|
|
11257
11334
|
for (const f of splitLines(execGit("git diff --name-only HEAD"))) {
|
|
11258
11335
|
sets.add(f);
|
|
11259
11336
|
}
|
|
@@ -11269,22 +11346,26 @@ function getChangedFiles() {
|
|
|
11269
11346
|
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
11270
11347
|
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
11271
11348
|
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
11272
|
-
|
|
11273
|
-
|
|
11349
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
11350
|
+
if (recentFiles.length > 0) {
|
|
11351
|
+
hasRecentCommitFiles = true;
|
|
11352
|
+
for (const f of recentFiles) {
|
|
11353
|
+
sets.add(f);
|
|
11354
|
+
}
|
|
11274
11355
|
}
|
|
11275
11356
|
}
|
|
11276
11357
|
for (const f of getWorktreeFiles()) {
|
|
11277
11358
|
sets.add(f);
|
|
11278
11359
|
}
|
|
11279
|
-
return Array.from(sets);
|
|
11360
|
+
return { files: Array.from(sets), hasRecentCommitFiles };
|
|
11280
11361
|
}
|
|
11281
11362
|
function getWorktreeFiles() {
|
|
11282
11363
|
const result = [];
|
|
11283
11364
|
const worktreeDir = ".claude/worktrees";
|
|
11284
|
-
if (!(0,
|
|
11365
|
+
if (!(0, import_node_fs3.existsSync)(worktreeDir)) return result;
|
|
11285
11366
|
try {
|
|
11286
11367
|
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
11287
|
-
const entries = (0,
|
|
11368
|
+
const entries = (0, import_node_fs3.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
11288
11369
|
for (const entry of entries) {
|
|
11289
11370
|
if (!entry.isDirectory()) continue;
|
|
11290
11371
|
const wtDir = (0, import_node_path4.join)(worktreeDir, entry.name);
|
|
@@ -11296,7 +11377,7 @@ function getWorktreeFiles() {
|
|
|
11296
11377
|
}
|
|
11297
11378
|
function scanDir(baseDir, dir, minMtime, result) {
|
|
11298
11379
|
try {
|
|
11299
|
-
const entries = (0,
|
|
11380
|
+
const entries = (0, import_node_fs3.readdirSync)(dir, { withFileTypes: true });
|
|
11300
11381
|
for (const entry of entries) {
|
|
11301
11382
|
const fullPath = (0, import_node_path4.join)(dir, entry.name);
|
|
11302
11383
|
if (entry.isDirectory()) {
|
|
@@ -11306,7 +11387,7 @@ function scanDir(baseDir, dir, minMtime, result) {
|
|
|
11306
11387
|
const ext = (0, import_node_path4.extname)(entry.name).slice(1);
|
|
11307
11388
|
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
11308
11389
|
try {
|
|
11309
|
-
const stat = (0,
|
|
11390
|
+
const stat = (0, import_node_fs3.statSync)(fullPath);
|
|
11310
11391
|
if (stat.mtimeMs >= minMtime) {
|
|
11311
11392
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
11312
11393
|
result.push(relPath);
|
|
@@ -11345,7 +11426,7 @@ function getCurrentCommit() {
|
|
|
11345
11426
|
}
|
|
11346
11427
|
|
|
11347
11428
|
// src/lib/files.ts
|
|
11348
|
-
var
|
|
11429
|
+
var import_node_fs4 = require("node:fs");
|
|
11349
11430
|
var import_node_path5 = require("node:path");
|
|
11350
11431
|
var LANG_MAP = {
|
|
11351
11432
|
// Analyzable (static analysis + Gemini)
|
|
@@ -11422,7 +11503,7 @@ function sortByMtime(files) {
|
|
|
11422
11503
|
const resolved = resolveFile(f);
|
|
11423
11504
|
if (!resolved) return null;
|
|
11424
11505
|
try {
|
|
11425
|
-
const stat = (0,
|
|
11506
|
+
const stat = (0, import_node_fs4.statSync)(resolved);
|
|
11426
11507
|
return { path: f, resolved, mtime: stat.mtimeMs };
|
|
11427
11508
|
} catch {
|
|
11428
11509
|
return null;
|
|
@@ -11444,7 +11525,7 @@ function collectCodeDelta(files, opts) {
|
|
|
11444
11525
|
if (!resolved) continue;
|
|
11445
11526
|
let size;
|
|
11446
11527
|
try {
|
|
11447
|
-
size = (0,
|
|
11528
|
+
size = (0, import_node_fs4.statSync)(resolved).size;
|
|
11448
11529
|
} catch {
|
|
11449
11530
|
continue;
|
|
11450
11531
|
}
|
|
@@ -11452,7 +11533,7 @@ function collectCodeDelta(files, opts) {
|
|
|
11452
11533
|
if (totalSize + size > maxTotalBytes) break;
|
|
11453
11534
|
let content;
|
|
11454
11535
|
try {
|
|
11455
|
-
content = (0,
|
|
11536
|
+
content = (0, import_node_fs4.readFileSync)(resolved, "utf-8");
|
|
11456
11537
|
} catch {
|
|
11457
11538
|
continue;
|
|
11458
11539
|
}
|
|
@@ -11475,12 +11556,12 @@ function collectCodeDelta(files, opts) {
|
|
|
11475
11556
|
}
|
|
11476
11557
|
|
|
11477
11558
|
// src/lib/debounce.ts
|
|
11478
|
-
var
|
|
11559
|
+
var import_node_fs5 = require("node:fs");
|
|
11479
11560
|
var import_node_crypto = require("node:crypto");
|
|
11480
11561
|
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
|
|
11481
|
-
if (!(0,
|
|
11562
|
+
if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return null;
|
|
11482
11563
|
try {
|
|
11483
|
-
const lastTs = parseInt((0,
|
|
11564
|
+
const lastTs = parseInt((0, import_node_fs5.readFileSync)(DEBOUNCE_FILE, "utf-8").trim(), 10);
|
|
11484
11565
|
const nowTs = Math.floor(Date.now() / 1e3);
|
|
11485
11566
|
const elapsed = nowTs - lastTs;
|
|
11486
11567
|
if (elapsed < debounceSeconds) {
|
|
@@ -11490,11 +11571,12 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
|
|
|
11490
11571
|
}
|
|
11491
11572
|
return null;
|
|
11492
11573
|
}
|
|
11493
|
-
function checkMtime(files) {
|
|
11494
|
-
if (
|
|
11574
|
+
function checkMtime(files, bypassForRecentCommits) {
|
|
11575
|
+
if (bypassForRecentCommits) return null;
|
|
11576
|
+
if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return null;
|
|
11495
11577
|
let debounceTime;
|
|
11496
11578
|
try {
|
|
11497
|
-
debounceTime = (0,
|
|
11579
|
+
debounceTime = (0, import_node_fs5.statSync)(DEBOUNCE_FILE).mtimeMs;
|
|
11498
11580
|
} catch {
|
|
11499
11581
|
return null;
|
|
11500
11582
|
}
|
|
@@ -11502,7 +11584,7 @@ function checkMtime(files) {
|
|
|
11502
11584
|
const resolved = resolveFile(f);
|
|
11503
11585
|
if (!resolved) continue;
|
|
11504
11586
|
try {
|
|
11505
|
-
const stat = (0,
|
|
11587
|
+
const stat = (0, import_node_fs5.statSync)(resolved);
|
|
11506
11588
|
if (stat.mtimeMs > debounceTime) {
|
|
11507
11589
|
return null;
|
|
11508
11590
|
}
|
|
@@ -11518,8 +11600,8 @@ function computeContentHash(files) {
|
|
|
11518
11600
|
for (const f of sorted) {
|
|
11519
11601
|
const resolved = resolveFile(f) ?? f;
|
|
11520
11602
|
try {
|
|
11521
|
-
if ((0,
|
|
11522
|
-
hash.update((0,
|
|
11603
|
+
if ((0, import_node_fs5.existsSync)(resolved)) {
|
|
11604
|
+
hash.update((0, import_node_fs5.readFileSync)(resolved));
|
|
11523
11605
|
}
|
|
11524
11606
|
} catch {
|
|
11525
11607
|
}
|
|
@@ -11528,9 +11610,9 @@ function computeContentHash(files) {
|
|
|
11528
11610
|
}
|
|
11529
11611
|
function checkContentHash(files) {
|
|
11530
11612
|
const hash = computeContentHash(files);
|
|
11531
|
-
if ((0,
|
|
11613
|
+
if ((0, import_node_fs5.existsSync)(HASH_FILE)) {
|
|
11532
11614
|
try {
|
|
11533
|
-
const storedHash = (0,
|
|
11615
|
+
const storedHash = (0, import_node_fs5.readFileSync)(HASH_FILE, "utf-8").trim();
|
|
11534
11616
|
if (hash === storedHash) {
|
|
11535
11617
|
return { skip: "No source changes since last analysis", hash };
|
|
11536
11618
|
}
|
|
@@ -11540,46 +11622,49 @@ function checkContentHash(files) {
|
|
|
11540
11622
|
return { skip: null, hash };
|
|
11541
11623
|
}
|
|
11542
11624
|
function recordAnalysisStart() {
|
|
11543
|
-
(0,
|
|
11544
|
-
(0,
|
|
11625
|
+
(0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
|
|
11626
|
+
(0, import_node_fs5.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
|
|
11545
11627
|
}
|
|
11546
11628
|
function recordPassHash(hash) {
|
|
11547
|
-
(0,
|
|
11629
|
+
(0, import_node_fs5.writeFileSync)(HASH_FILE, hash);
|
|
11548
11630
|
}
|
|
11549
11631
|
function narrowToRecent(files) {
|
|
11550
|
-
if (!(0,
|
|
11632
|
+
if (!(0, import_node_fs5.existsSync)(DEBOUNCE_FILE)) return files;
|
|
11551
11633
|
let debounceTime;
|
|
11552
11634
|
try {
|
|
11553
|
-
debounceTime = (0,
|
|
11635
|
+
debounceTime = (0, import_node_fs5.statSync)(DEBOUNCE_FILE).mtimeMs;
|
|
11554
11636
|
} catch {
|
|
11555
11637
|
return files;
|
|
11556
11638
|
}
|
|
11557
11639
|
const recent = files.filter((f) => {
|
|
11558
11640
|
try {
|
|
11559
|
-
return (0,
|
|
11641
|
+
return (0, import_node_fs5.existsSync)(f) && (0, import_node_fs5.statSync)(f).mtimeMs > debounceTime;
|
|
11560
11642
|
} catch {
|
|
11561
11643
|
return false;
|
|
11562
11644
|
}
|
|
11563
11645
|
});
|
|
11564
11646
|
return recent.length > 0 ? recent : files;
|
|
11565
11647
|
}
|
|
11566
|
-
function readIteration(currentCommit) {
|
|
11567
|
-
if (!(0,
|
|
11648
|
+
function readIteration(currentCommit, contentHash) {
|
|
11649
|
+
if (!(0, import_node_fs5.existsSync)(ITERATION_FILE)) return 1;
|
|
11568
11650
|
try {
|
|
11569
|
-
const stored = (0,
|
|
11570
|
-
const
|
|
11571
|
-
const iter = parseInt(
|
|
11651
|
+
const stored = (0, import_node_fs5.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
11652
|
+
const parts = stored.split(":");
|
|
11653
|
+
const iter = parseInt(parts[0], 10);
|
|
11654
|
+
const storedCommit = parts[1] ?? "";
|
|
11655
|
+
const storedContentHash = parts[2] ?? "";
|
|
11572
11656
|
if (isNaN(iter)) return 1;
|
|
11573
|
-
if (storedCommit
|
|
11574
|
-
return 1;
|
|
11657
|
+
if (storedCommit !== currentCommit) return 1;
|
|
11658
|
+
if (contentHash && storedContentHash && storedContentHash !== contentHash) return 1;
|
|
11659
|
+
return iter;
|
|
11575
11660
|
} catch {
|
|
11576
11661
|
return 1;
|
|
11577
11662
|
}
|
|
11578
11663
|
}
|
|
11579
|
-
function checkMaxIterations(currentCommit, maxIterations = MAX_ITERATIONS) {
|
|
11580
|
-
const iteration = readIteration(currentCommit);
|
|
11664
|
+
function checkMaxIterations(currentCommit, maxIterations = MAX_ITERATIONS, contentHash) {
|
|
11665
|
+
const iteration = readIteration(currentCommit, contentHash);
|
|
11581
11666
|
if (iteration > maxIterations) {
|
|
11582
|
-
(
|
|
11667
|
+
writeIteration(1, currentCommit, contentHash);
|
|
11583
11668
|
return {
|
|
11584
11669
|
skip: `Max gate iterations (${maxIterations}) reached \u2014 accepting to prevent infinite loop. Human review required before deploying.`,
|
|
11585
11670
|
iteration
|
|
@@ -11587,14 +11672,14 @@ function checkMaxIterations(currentCommit, maxIterations = MAX_ITERATIONS) {
|
|
|
11587
11672
|
}
|
|
11588
11673
|
return { skip: null, iteration };
|
|
11589
11674
|
}
|
|
11590
|
-
function writeIteration(iteration, commit) {
|
|
11591
|
-
(0,
|
|
11592
|
-
(0,
|
|
11675
|
+
function writeIteration(iteration, commit, contentHash) {
|
|
11676
|
+
(0, import_node_fs5.mkdirSync)(GATE_DIR, { recursive: true });
|
|
11677
|
+
(0, import_node_fs5.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${contentHash ?? ""}`);
|
|
11593
11678
|
}
|
|
11594
11679
|
|
|
11595
11680
|
// src/lib/static-analysis.ts
|
|
11596
|
-
var
|
|
11597
|
-
var
|
|
11681
|
+
var import_node_child_process6 = require("node:child_process");
|
|
11682
|
+
var import_node_fs6 = require("node:fs");
|
|
11598
11683
|
var SEVERITY_ORDER = {
|
|
11599
11684
|
Error: 0,
|
|
11600
11685
|
Critical: 0,
|
|
@@ -11606,7 +11691,7 @@ var SEVERITY_ORDER = {
|
|
|
11606
11691
|
};
|
|
11607
11692
|
function isCodacyAvailable() {
|
|
11608
11693
|
try {
|
|
11609
|
-
(0,
|
|
11694
|
+
(0, import_node_child_process6.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
11610
11695
|
return true;
|
|
11611
11696
|
} catch {
|
|
11612
11697
|
return false;
|
|
@@ -11621,7 +11706,7 @@ function runCodacyAnalysis(files) {
|
|
|
11621
11706
|
if (files.length === 0) return empty;
|
|
11622
11707
|
const existingFiles = files.filter((f) => {
|
|
11623
11708
|
try {
|
|
11624
|
-
return (0,
|
|
11709
|
+
return (0, import_node_fs6.existsSync)(f);
|
|
11625
11710
|
} catch {
|
|
11626
11711
|
return false;
|
|
11627
11712
|
}
|
|
@@ -11630,7 +11715,7 @@ function runCodacyAnalysis(files) {
|
|
|
11630
11715
|
const fileArgs = existingFiles.join(" ");
|
|
11631
11716
|
let output;
|
|
11632
11717
|
try {
|
|
11633
|
-
output = (0,
|
|
11718
|
+
output = (0, import_node_child_process6.execSync)(
|
|
11634
11719
|
`codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
|
|
11635
11720
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
|
|
11636
11721
|
);
|
|
@@ -11683,7 +11768,7 @@ function runCodacyAnalysis(files) {
|
|
|
11683
11768
|
}
|
|
11684
11769
|
|
|
11685
11770
|
// src/lib/specs.ts
|
|
11686
|
-
var
|
|
11771
|
+
var import_node_fs7 = require("node:fs");
|
|
11687
11772
|
var import_node_path6 = require("node:path");
|
|
11688
11773
|
var SPEC_CANDIDATES = [
|
|
11689
11774
|
"CLAUDE.md",
|
|
@@ -11708,15 +11793,15 @@ function discoverSpecs() {
|
|
|
11708
11793
|
if (result.length >= MAX_SPEC_FILES) return false;
|
|
11709
11794
|
if (totalBytes >= MAX_TOTAL_SPEC_BYTES) return false;
|
|
11710
11795
|
if (seen.has(specPath)) return true;
|
|
11711
|
-
if (!(0,
|
|
11796
|
+
if (!(0, import_node_fs7.existsSync)(specPath)) return true;
|
|
11712
11797
|
seen.add(specPath);
|
|
11713
11798
|
const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
|
|
11714
11799
|
const readBytes = Math.min(MAX_SPEC_FILE_BYTES, remaining);
|
|
11715
11800
|
try {
|
|
11716
11801
|
const buf = Buffer.alloc(readBytes);
|
|
11717
|
-
const fd = (0,
|
|
11718
|
-
const bytesRead = (0,
|
|
11719
|
-
(0,
|
|
11802
|
+
const fd = (0, import_node_fs7.openSync)(specPath, "r");
|
|
11803
|
+
const bytesRead = (0, import_node_fs7.readSync)(fd, buf, 0, readBytes, 0);
|
|
11804
|
+
(0, import_node_fs7.closeSync)(fd);
|
|
11720
11805
|
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
11721
11806
|
if (!content) return true;
|
|
11722
11807
|
result.push({ path: specPath, content });
|
|
@@ -11729,7 +11814,7 @@ function discoverSpecs() {
|
|
|
11729
11814
|
if (!addSpec(candidate)) break;
|
|
11730
11815
|
}
|
|
11731
11816
|
for (const dir of ["spec", "docs"]) {
|
|
11732
|
-
if (!(0,
|
|
11817
|
+
if (!(0, import_node_fs7.existsSync)(dir)) continue;
|
|
11733
11818
|
try {
|
|
11734
11819
|
const mdFiles = findMdFiles(dir, 2).sort();
|
|
11735
11820
|
for (const mdFile of mdFiles) {
|
|
@@ -11744,7 +11829,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
11744
11829
|
if (depth >= maxDepth) return [];
|
|
11745
11830
|
const result = [];
|
|
11746
11831
|
try {
|
|
11747
|
-
const entries = (0,
|
|
11832
|
+
const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
|
|
11748
11833
|
for (const entry of entries) {
|
|
11749
11834
|
const fullPath = (0, import_node_path6.join)(dir, entry.name);
|
|
11750
11835
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -11759,13 +11844,13 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
11759
11844
|
}
|
|
11760
11845
|
function discoverPlans() {
|
|
11761
11846
|
const plansDir = ".claude/plans";
|
|
11762
|
-
if (!(0,
|
|
11847
|
+
if (!(0, import_node_fs7.existsSync)(plansDir)) return [];
|
|
11763
11848
|
const result = [];
|
|
11764
11849
|
try {
|
|
11765
|
-
const entries = (0,
|
|
11850
|
+
const entries = (0, import_node_fs7.readdirSync)(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
11766
11851
|
const fullPath = (0, import_node_path6.join)(plansDir, f);
|
|
11767
11852
|
try {
|
|
11768
|
-
const stat = (0,
|
|
11853
|
+
const stat = (0, import_node_fs7.statSync)(fullPath);
|
|
11769
11854
|
return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size };
|
|
11770
11855
|
} catch {
|
|
11771
11856
|
return null;
|
|
@@ -11774,7 +11859,7 @@ function discoverPlans() {
|
|
|
11774
11859
|
for (const entry of entries) {
|
|
11775
11860
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
11776
11861
|
try {
|
|
11777
|
-
const content = (0,
|
|
11862
|
+
const content = (0, import_node_fs7.readFileSync)(entry.path, "utf-8");
|
|
11778
11863
|
result.push({ name: entry.name, content });
|
|
11779
11864
|
} catch {
|
|
11780
11865
|
}
|
|
@@ -11784,15 +11869,113 @@ function discoverPlans() {
|
|
|
11784
11869
|
return result;
|
|
11785
11870
|
}
|
|
11786
11871
|
|
|
11872
|
+
// src/lib/snapshot.ts
|
|
11873
|
+
var import_node_fs8 = require("node:fs");
|
|
11874
|
+
var import_node_path7 = require("node:path");
|
|
11875
|
+
var import_node_child_process7 = require("node:child_process");
|
|
11876
|
+
function generateSnapshotDiffs(files) {
|
|
11877
|
+
if (!(0, import_node_fs8.existsSync)(SNAPSHOT_DIR)) {
|
|
11878
|
+
return { diffs: [], has_snapshots: false };
|
|
11879
|
+
}
|
|
11880
|
+
const diffs = [];
|
|
11881
|
+
for (const file of files) {
|
|
11882
|
+
const snapshotPath = (0, import_node_path7.join)(SNAPSHOT_DIR, file.path);
|
|
11883
|
+
const language = file.language ?? detectLanguage(file.path);
|
|
11884
|
+
if ((0, import_node_fs8.existsSync)(snapshotPath)) {
|
|
11885
|
+
const oldContent = (0, import_node_fs8.readFileSync)(snapshotPath, "utf-8");
|
|
11886
|
+
if (oldContent === file.content) continue;
|
|
11887
|
+
const diff = computeDiff(oldContent, file.content, file.path);
|
|
11888
|
+
if (diff) {
|
|
11889
|
+
diffs.push({ path: file.path, language, diff, status: "modified" });
|
|
11890
|
+
}
|
|
11891
|
+
} else {
|
|
11892
|
+
const addedLines = file.content.split("\n").map((l) => `+${l}`).join("\n");
|
|
11893
|
+
diffs.push({
|
|
11894
|
+
path: file.path,
|
|
11895
|
+
language,
|
|
11896
|
+
diff: `--- /dev/null
|
|
11897
|
+
+++ b/${file.path}
|
|
11898
|
+
@@ -0,0 +1,${file.content.split("\n").length} @@
|
|
11899
|
+
${addedLines}`,
|
|
11900
|
+
status: "added"
|
|
11901
|
+
});
|
|
11902
|
+
}
|
|
11903
|
+
}
|
|
11904
|
+
return { diffs, has_snapshots: true };
|
|
11905
|
+
}
|
|
11906
|
+
function saveSnapshots(files) {
|
|
11907
|
+
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
11908
|
+
for (const file of files) {
|
|
11909
|
+
const snapshotPath = (0, import_node_path7.join)(SNAPSHOT_DIR, file.path);
|
|
11910
|
+
snapshotPaths.add(snapshotPath);
|
|
11911
|
+
(0, import_node_fs8.mkdirSync)((0, import_node_path7.dirname)(snapshotPath), { recursive: true });
|
|
11912
|
+
(0, import_node_fs8.writeFileSync)(snapshotPath, file.content);
|
|
11913
|
+
}
|
|
11914
|
+
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
11915
|
+
}
|
|
11916
|
+
function computeDiff(oldContent, newContent, filePath) {
|
|
11917
|
+
const tmpOld = (0, import_node_path7.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
11918
|
+
const tmpNew = (0, import_node_path7.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
11919
|
+
try {
|
|
11920
|
+
(0, import_node_fs8.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
11921
|
+
(0, import_node_fs8.writeFileSync)(tmpOld, oldContent);
|
|
11922
|
+
(0, import_node_fs8.writeFileSync)(tmpNew, newContent);
|
|
11923
|
+
const result = (0, import_node_child_process7.execSync)(
|
|
11924
|
+
`git diff --no-index --unified=3 -- "${tmpOld}" "${tmpNew}"`,
|
|
11925
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11926
|
+
);
|
|
11927
|
+
return result || null;
|
|
11928
|
+
} catch (err) {
|
|
11929
|
+
const error = err;
|
|
11930
|
+
if (error.status === 1 && error.stdout) {
|
|
11931
|
+
return error.stdout.replace(/^diff --git a\/.*$/m, `diff --git a/${filePath} b/${filePath}`).replace(/^--- a\/.*$/m, `--- a/${filePath}`).replace(/^\+\+\+ b\/.*$/m, `+++ b/${filePath}`);
|
|
11932
|
+
}
|
|
11933
|
+
return null;
|
|
11934
|
+
} finally {
|
|
11935
|
+
try {
|
|
11936
|
+
(0, import_node_fs8.unlinkSync)(tmpOld);
|
|
11937
|
+
} catch {
|
|
11938
|
+
}
|
|
11939
|
+
try {
|
|
11940
|
+
(0, import_node_fs8.unlinkSync)(tmpNew);
|
|
11941
|
+
} catch {
|
|
11942
|
+
}
|
|
11943
|
+
}
|
|
11944
|
+
}
|
|
11945
|
+
function cleanStaleSnapshots(dir, keepSet) {
|
|
11946
|
+
if (!(0, import_node_fs8.existsSync)(dir)) return;
|
|
11947
|
+
try {
|
|
11948
|
+
const entries = (0, import_node_fs8.readdirSync)(dir, { withFileTypes: true });
|
|
11949
|
+
for (const entry of entries) {
|
|
11950
|
+
if (entry.name.startsWith(".")) continue;
|
|
11951
|
+
const fullPath = (0, import_node_path7.join)(dir, entry.name);
|
|
11952
|
+
if (entry.isDirectory()) {
|
|
11953
|
+
cleanStaleSnapshots(fullPath, keepSet);
|
|
11954
|
+
try {
|
|
11955
|
+
const remaining = (0, import_node_fs8.readdirSync)(fullPath);
|
|
11956
|
+
if (remaining.length === 0) (0, import_node_fs8.rmdirSync)(fullPath);
|
|
11957
|
+
} catch {
|
|
11958
|
+
}
|
|
11959
|
+
} else if (!keepSet.has(fullPath)) {
|
|
11960
|
+
try {
|
|
11961
|
+
(0, import_node_fs8.unlinkSync)(fullPath);
|
|
11962
|
+
} catch {
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
11966
|
+
} catch {
|
|
11967
|
+
}
|
|
11968
|
+
}
|
|
11969
|
+
|
|
11787
11970
|
// src/lib/offline.ts
|
|
11788
|
-
var
|
|
11971
|
+
var import_node_fs9 = require("node:fs");
|
|
11789
11972
|
var import_node_crypto2 = require("node:crypto");
|
|
11790
11973
|
function cacheRequest(body) {
|
|
11791
11974
|
try {
|
|
11792
|
-
(0,
|
|
11975
|
+
(0, import_node_fs9.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
11793
11976
|
const suffix = (0, import_node_crypto2.randomBytes)(4).toString("hex");
|
|
11794
11977
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
11795
|
-
(0,
|
|
11978
|
+
(0, import_node_fs9.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
|
|
11796
11979
|
} catch {
|
|
11797
11980
|
}
|
|
11798
11981
|
}
|
|
@@ -11821,7 +12004,7 @@ function registerAnalyzeCommand(program2) {
|
|
|
11821
12004
|
});
|
|
11822
12005
|
}
|
|
11823
12006
|
async function runAnalyze(opts, globals) {
|
|
11824
|
-
const allChanged = getChangedFiles();
|
|
12007
|
+
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
11825
12008
|
const analyzable = filterAnalyzable(allChanged);
|
|
11826
12009
|
const reviewable = filterReviewable(allChanged);
|
|
11827
12010
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -11833,13 +12016,13 @@ async function runAnalyze(opts, globals) {
|
|
|
11833
12016
|
const debounceSkip = checkDebounce(debounceSeconds);
|
|
11834
12017
|
if (debounceSkip) passAndExit(debounceSkip);
|
|
11835
12018
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
11836
|
-
const mtimeSkip = checkMtime(allCheckable);
|
|
12019
|
+
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
|
|
11837
12020
|
if (mtimeSkip) passAndExit(mtimeSkip);
|
|
11838
12021
|
recordAnalysisStart();
|
|
11839
12022
|
const { skip: hashSkip, hash: contentHash } = checkContentHash(allCheckable);
|
|
11840
12023
|
if (hashSkip) passAndExit(hashSkip);
|
|
11841
12024
|
const recentForReview = narrowToRecent(allForReview);
|
|
11842
|
-
const
|
|
12025
|
+
const conversation = await readAndClearConversationBuffer();
|
|
11843
12026
|
const specs = discoverSpecs();
|
|
11844
12027
|
const plans = discoverPlans();
|
|
11845
12028
|
let staticResults;
|
|
@@ -11861,6 +12044,7 @@ async function runAnalyze(opts, globals) {
|
|
|
11861
12044
|
if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
|
|
11862
12045
|
passAndExit("No files within size limits to analyze");
|
|
11863
12046
|
}
|
|
12047
|
+
const snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
11864
12048
|
const tokenResult = await resolveToken(globals.token);
|
|
11865
12049
|
if (!tokenResult.ok) {
|
|
11866
12050
|
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
@@ -11871,7 +12055,7 @@ async function runAnalyze(opts, globals) {
|
|
|
11871
12055
|
}
|
|
11872
12056
|
const currentCommit = getCurrentCommit();
|
|
11873
12057
|
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
11874
|
-
const { skip: iterSkip, iteration } = checkMaxIterations(currentCommit, maxIterations);
|
|
12058
|
+
const { skip: iterSkip, iteration } = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
11875
12059
|
if (iterSkip) passAndExit(iterSkip);
|
|
11876
12060
|
const requestBody = {
|
|
11877
12061
|
static_results: staticResults,
|
|
@@ -11885,13 +12069,23 @@ async function runAnalyze(opts, globals) {
|
|
|
11885
12069
|
iteration
|
|
11886
12070
|
}
|
|
11887
12071
|
};
|
|
11888
|
-
|
|
12072
|
+
if (snapshotResult.has_snapshots && snapshotResult.diffs.length > 0) {
|
|
12073
|
+
requestBody.snapshot_diffs = snapshotResult.diffs;
|
|
12074
|
+
}
|
|
12075
|
+
const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0;
|
|
11889
12076
|
if (hasIntent) {
|
|
11890
12077
|
const intentContext = {};
|
|
11891
|
-
if (
|
|
11892
|
-
|
|
11893
|
-
intentContext.
|
|
11894
|
-
intentContext.
|
|
12078
|
+
if (conversation && conversation.prompts.length > 0) {
|
|
12079
|
+
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
12080
|
+
intentContext.user_prompt = latest.prompt;
|
|
12081
|
+
intentContext.session_id = latest.session_id || void 0;
|
|
12082
|
+
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
12083
|
+
if (conversation.prompts.length > 1) {
|
|
12084
|
+
intentContext.conversation = conversation.prompts;
|
|
12085
|
+
}
|
|
12086
|
+
if (conversation.recent_commits.length > 0) {
|
|
12087
|
+
intentContext.recent_commits = conversation.recent_commits;
|
|
12088
|
+
}
|
|
11895
12089
|
}
|
|
11896
12090
|
if (specs.length > 0) intentContext.specs = specs;
|
|
11897
12091
|
if (plans.length > 0) intentContext.plans = plans;
|
|
@@ -11920,9 +12114,10 @@ async function runAnalyze(opts, globals) {
|
|
|
11920
12114
|
}
|
|
11921
12115
|
const response = result.data;
|
|
11922
12116
|
const decision = response.gate_decision ?? "PASS";
|
|
12117
|
+
saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
|
|
11923
12118
|
switch (decision) {
|
|
11924
12119
|
case "FAIL": {
|
|
11925
|
-
writeIteration(iteration + 1, currentCommit);
|
|
12120
|
+
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
11926
12121
|
const assessment = response.assessment;
|
|
11927
12122
|
const narrative = assessment?.narrative ?? "";
|
|
11928
12123
|
const findings = response.findings ?? [];
|
|
@@ -11979,7 +12174,7 @@ async function runAnalyze(opts, globals) {
|
|
|
11979
12174
|
break;
|
|
11980
12175
|
}
|
|
11981
12176
|
case "PASS": {
|
|
11982
|
-
writeIteration(1, currentCommit);
|
|
12177
|
+
writeIteration(1, currentCommit, contentHash ?? void 0);
|
|
11983
12178
|
if (contentHash) recordPassHash(contentHash);
|
|
11984
12179
|
let userSummary = response.user_summary ?? "GATE.md: PASS";
|
|
11985
12180
|
const viewUrl = response.view_url ?? "";
|
|
@@ -12005,7 +12200,7 @@ async function runAnalyze(opts, globals) {
|
|
|
12005
12200
|
}
|
|
12006
12201
|
|
|
12007
12202
|
// src/commands/review.ts
|
|
12008
|
-
var
|
|
12203
|
+
var import_node_fs10 = require("node:fs");
|
|
12009
12204
|
function registerReviewCommand(program2) {
|
|
12010
12205
|
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) => {
|
|
12011
12206
|
const globals = program2.opts();
|
|
@@ -12024,7 +12219,7 @@ async function runReview(opts, globals) {
|
|
|
12024
12219
|
const securityFiles = filterSecurity(allFiles);
|
|
12025
12220
|
let staticResults;
|
|
12026
12221
|
if (isCodacyAvailable()) {
|
|
12027
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
12222
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs10.existsSync)(f) || resolveFile(f) !== null);
|
|
12028
12223
|
staticResults = runCodacyAnalysis(scannable);
|
|
12029
12224
|
} else {
|
|
12030
12225
|
staticResults = {
|
|
@@ -12049,10 +12244,10 @@ async function runReview(opts, globals) {
|
|
|
12049
12244
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
12050
12245
|
specs = [];
|
|
12051
12246
|
for (const p of specPaths) {
|
|
12052
|
-
if (!(0,
|
|
12247
|
+
if (!(0, import_node_fs10.existsSync)(p)) continue;
|
|
12053
12248
|
try {
|
|
12054
|
-
const { readFileSync:
|
|
12055
|
-
const content =
|
|
12249
|
+
const { readFileSync: readFileSync5 } = await import("node:fs");
|
|
12250
|
+
const content = readFileSync5(p, "utf-8");
|
|
12056
12251
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
12057
12252
|
} catch {
|
|
12058
12253
|
}
|
|
@@ -12110,21 +12305,21 @@ async function runReview(opts, globals) {
|
|
|
12110
12305
|
}
|
|
12111
12306
|
|
|
12112
12307
|
// src/commands/init.ts
|
|
12113
|
-
var
|
|
12308
|
+
var import_node_fs11 = require("node:fs");
|
|
12114
12309
|
var import_promises8 = require("node:fs/promises");
|
|
12115
|
-
var
|
|
12116
|
-
var
|
|
12310
|
+
var import_node_path8 = require("node:path");
|
|
12311
|
+
var import_node_child_process8 = require("node:child_process");
|
|
12117
12312
|
function resolveDataDir() {
|
|
12118
12313
|
const candidates = [
|
|
12119
|
-
(0,
|
|
12314
|
+
(0, import_node_path8.join)(__dirname, "..", "data"),
|
|
12120
12315
|
// installed: node_modules/@codacy/gate-cli/data
|
|
12121
|
-
(0,
|
|
12316
|
+
(0, import_node_path8.join)(__dirname, "..", "..", "data"),
|
|
12122
12317
|
// edge case: nested resolution
|
|
12123
|
-
(0,
|
|
12318
|
+
(0, import_node_path8.join)(process.cwd(), "cli", "data")
|
|
12124
12319
|
// local dev: running from repo root
|
|
12125
12320
|
];
|
|
12126
12321
|
for (const candidate of candidates) {
|
|
12127
|
-
if ((0,
|
|
12322
|
+
if ((0, import_node_fs11.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
|
|
12128
12323
|
return candidate;
|
|
12129
12324
|
}
|
|
12130
12325
|
}
|
|
@@ -12140,7 +12335,7 @@ function registerInitCommand(program2) {
|
|
|
12140
12335
|
program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
12141
12336
|
const force = opts.force ?? false;
|
|
12142
12337
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
12143
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
12338
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs11.existsSync)(m));
|
|
12144
12339
|
if (!isProject) {
|
|
12145
12340
|
printError("No project detected in the current directory.");
|
|
12146
12341
|
printInfo('Run "gate init" from your project root.');
|
|
@@ -12158,30 +12353,30 @@ function registerInitCommand(program2) {
|
|
|
12158
12353
|
}
|
|
12159
12354
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
12160
12355
|
try {
|
|
12161
|
-
const gitVersion = (0,
|
|
12356
|
+
const gitVersion = (0, import_node_child_process8.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
12162
12357
|
printInfo(` ${gitVersion} \u2713`);
|
|
12163
12358
|
} catch {
|
|
12164
12359
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
12165
12360
|
process.exit(1);
|
|
12166
12361
|
}
|
|
12167
12362
|
try {
|
|
12168
|
-
(0,
|
|
12363
|
+
(0, import_node_child_process8.execSync)("which claude", { encoding: "utf-8" });
|
|
12169
12364
|
printInfo(" Claude Code \u2713");
|
|
12170
12365
|
} catch {
|
|
12171
12366
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
12172
12367
|
}
|
|
12173
12368
|
try {
|
|
12174
|
-
(0,
|
|
12369
|
+
(0, import_node_child_process8.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
12175
12370
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
12176
12371
|
} catch {
|
|
12177
12372
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
12178
12373
|
try {
|
|
12179
|
-
(0,
|
|
12374
|
+
(0, import_node_child_process8.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
12180
12375
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
12181
12376
|
} catch {
|
|
12182
12377
|
try {
|
|
12183
12378
|
printWarn(" Retrying with sudo...");
|
|
12184
|
-
(0,
|
|
12379
|
+
(0, import_node_child_process8.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
12185
12380
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
12186
12381
|
} catch {
|
|
12187
12382
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -12193,21 +12388,21 @@ function registerInitCommand(program2) {
|
|
|
12193
12388
|
console.log("");
|
|
12194
12389
|
printInfo("Installing skills...");
|
|
12195
12390
|
const dataDir = resolveDataDir();
|
|
12196
|
-
const skillsSource = (0,
|
|
12391
|
+
const skillsSource = (0, import_node_path8.join)(dataDir, "skills");
|
|
12197
12392
|
const skillsDest = ".claude/skills";
|
|
12198
12393
|
const skills = ["gate-setup", "gate-analyze", "gate-status", "gate-feedback"];
|
|
12199
12394
|
let skillsInstalled = 0;
|
|
12200
12395
|
for (const skill of skills) {
|
|
12201
|
-
const src = (0,
|
|
12202
|
-
const dest = (0,
|
|
12203
|
-
if (!(0,
|
|
12396
|
+
const src = (0, import_node_path8.join)(skillsSource, skill);
|
|
12397
|
+
const dest = (0, import_node_path8.join)(skillsDest, skill);
|
|
12398
|
+
if (!(0, import_node_fs11.existsSync)(src)) {
|
|
12204
12399
|
printWarn(` Skill data not found: ${skill}`);
|
|
12205
12400
|
continue;
|
|
12206
12401
|
}
|
|
12207
|
-
if ((0,
|
|
12208
|
-
const srcSkill = (0,
|
|
12209
|
-
const destSkill = (0,
|
|
12210
|
-
if ((0,
|
|
12402
|
+
if ((0, import_node_fs11.existsSync)(dest) && !force) {
|
|
12403
|
+
const srcSkill = (0, import_node_path8.join)(src, "SKILL.md");
|
|
12404
|
+
const destSkill = (0, import_node_path8.join)(dest, "SKILL.md");
|
|
12405
|
+
if ((0, import_node_fs11.existsSync)(destSkill)) {
|
|
12211
12406
|
try {
|
|
12212
12407
|
const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
|
|
12213
12408
|
const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
|
|
@@ -12235,7 +12430,7 @@ function registerInitCommand(program2) {
|
|
|
12235
12430
|
printInfo(' Run "gate hooks install --force" to overwrite.');
|
|
12236
12431
|
}
|
|
12237
12432
|
await (0, import_promises8.mkdir)(GATE_DIR, { recursive: true });
|
|
12238
|
-
const globalGateDir = (0,
|
|
12433
|
+
const globalGateDir = (0, import_node_path8.join)(process.env.HOME ?? "", ".gate");
|
|
12239
12434
|
await (0, import_promises8.mkdir)(globalGateDir, { recursive: true });
|
|
12240
12435
|
console.log("");
|
|
12241
12436
|
printInfo("GATE.md initialized!");
|