@codacy/gate-cli 0.14.2 → 0.15.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 +490 -35
- package/package.json +1 -1
package/bin/gate.js
CHANGED
|
@@ -10627,7 +10627,18 @@ function rotateIfNeeded(file) {
|
|
|
10627
10627
|
|
|
10628
10628
|
// src/lib/api-client.ts
|
|
10629
10629
|
async function apiRequest(options) {
|
|
10630
|
-
const {
|
|
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;
|
|
10631
10642
|
const url = `${serviceUrl}${path}`;
|
|
10632
10643
|
const headers = {
|
|
10633
10644
|
"Content-Type": "application/json"
|
|
@@ -10636,13 +10647,20 @@ async function apiRequest(options) {
|
|
|
10636
10647
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10637
10648
|
}
|
|
10638
10649
|
printVerbose(`${method} ${url}`, verbose);
|
|
10639
|
-
|
|
10640
|
-
if (body) {
|
|
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
|
+
}
|
|
10641
10659
|
printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
|
|
10642
10660
|
}
|
|
10643
10661
|
const startedAt = Date.now();
|
|
10644
10662
|
const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
|
|
10645
|
-
const logBase = { cmd, method, url, body_bytes: bodyBytes, retry };
|
|
10663
|
+
const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
|
|
10646
10664
|
let response;
|
|
10647
10665
|
try {
|
|
10648
10666
|
response = await fetch(url, {
|
|
@@ -10924,6 +10942,9 @@ function registerHooksCommands(program2) {
|
|
|
10924
10942
|
});
|
|
10925
10943
|
}
|
|
10926
10944
|
|
|
10945
|
+
// src/commands/intent.ts
|
|
10946
|
+
var import_node_crypto = require("node:crypto");
|
|
10947
|
+
|
|
10927
10948
|
// src/lib/conversation-buffer.ts
|
|
10928
10949
|
var import_promises5 = require("node:fs/promises");
|
|
10929
10950
|
var import_node_fs2 = require("node:fs");
|
|
@@ -11052,10 +11073,41 @@ function registerIntentCommands(program2) {
|
|
|
11052
11073
|
process.exit(0);
|
|
11053
11074
|
}
|
|
11054
11075
|
await appendToConversationBuffer(prompt, event.session_id ?? "");
|
|
11076
|
+
fireClassify(prompt, event.session_id ?? "").catch(() => {
|
|
11077
|
+
});
|
|
11055
11078
|
} catch {
|
|
11056
11079
|
}
|
|
11057
11080
|
process.exit(0);
|
|
11058
11081
|
});
|
|
11082
|
+
intent.command("classify-body").description("Output JSON body for POST /classify-task (internal, used by hooks)").requiredOption("--session-id <id>", "Session ID").requiredOption("--prompt <text>", "User prompt").requiredOption("--prompt-hash <hash>", "SHA-256 hash of the prompt").action((opts) => {
|
|
11083
|
+
const body = JSON.stringify({
|
|
11084
|
+
session_id: opts.sessionId,
|
|
11085
|
+
user_prompt: opts.prompt.slice(0, 4e3),
|
|
11086
|
+
user_prompt_hash: opts.promptHash
|
|
11087
|
+
});
|
|
11088
|
+
process.stdout.write(body);
|
|
11089
|
+
});
|
|
11090
|
+
}
|
|
11091
|
+
async function fireClassify(prompt, sessionId) {
|
|
11092
|
+
if (!sessionId) return;
|
|
11093
|
+
const tokenResult = await resolveToken();
|
|
11094
|
+
if (!tokenResult.ok) return;
|
|
11095
|
+
const urlResult = await resolveServiceUrl();
|
|
11096
|
+
if (!urlResult.ok) return;
|
|
11097
|
+
const promptHash = (0, import_node_crypto.createHash)("sha256").update(prompt).digest("hex");
|
|
11098
|
+
await apiRequest({
|
|
11099
|
+
method: "POST",
|
|
11100
|
+
path: "/classify-task",
|
|
11101
|
+
serviceUrl: urlResult.data,
|
|
11102
|
+
token: tokenResult.data.token,
|
|
11103
|
+
body: {
|
|
11104
|
+
session_id: sessionId,
|
|
11105
|
+
user_prompt: prompt.slice(0, 4e3),
|
|
11106
|
+
user_prompt_hash: promptHash
|
|
11107
|
+
},
|
|
11108
|
+
timeout: 2e3,
|
|
11109
|
+
cmd: "classify"
|
|
11110
|
+
});
|
|
11059
11111
|
}
|
|
11060
11112
|
|
|
11061
11113
|
// src/commands/standard.ts
|
|
@@ -11234,6 +11286,16 @@ function registerConfigCommands(program2) {
|
|
|
11234
11286
|
}
|
|
11235
11287
|
|
|
11236
11288
|
// src/commands/status.ts
|
|
11289
|
+
function timeAgo(isoDate) {
|
|
11290
|
+
const ms = Date.now() - new Date(isoDate).getTime();
|
|
11291
|
+
const mins = Math.floor(ms / 6e4);
|
|
11292
|
+
if (mins < 1) return "just now";
|
|
11293
|
+
if (mins < 60) return `${mins}m ago`;
|
|
11294
|
+
const hrs = Math.floor(mins / 60);
|
|
11295
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
11296
|
+
const days = Math.floor(hrs / 24);
|
|
11297
|
+
return `${days}d ago`;
|
|
11298
|
+
}
|
|
11237
11299
|
function registerStatusCommand(program2) {
|
|
11238
11300
|
program2.command("status").description("Show project quality status").option("--history", "Include recent run history").option("--limit <n>", "Number of history entries", "5").option("--json", "Output raw JSON").action(async (opts) => {
|
|
11239
11301
|
const globals = program2.opts();
|
|
@@ -11306,6 +11368,27 @@ function registerStatusCommand(program2) {
|
|
|
11306
11368
|
printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
|
|
11307
11369
|
}
|
|
11308
11370
|
}
|
|
11371
|
+
const recentTasks = mem.recent_tasks;
|
|
11372
|
+
const currentTask = mem.current_task;
|
|
11373
|
+
const contextFiles = mem.context_files;
|
|
11374
|
+
if (recentTasks && recentTasks.length > 0) {
|
|
11375
|
+
printInfo("");
|
|
11376
|
+
printInfo("--- Active Tasks ---");
|
|
11377
|
+
for (const task of recentTasks) {
|
|
11378
|
+
const isCurrent = currentTask && task.id === currentTask.id;
|
|
11379
|
+
const marker = isCurrent ? "\u25CF" : "\u25CB";
|
|
11380
|
+
const runCount = task.run_count ?? 0;
|
|
11381
|
+
const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
|
|
11382
|
+
printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
|
|
11383
|
+
}
|
|
11384
|
+
if (currentTask) {
|
|
11385
|
+
printInfo("");
|
|
11386
|
+
printInfo(`Current task: ${currentTask.label}`);
|
|
11387
|
+
if (contextFiles && contextFiles.length > 0) {
|
|
11388
|
+
printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
|
|
11389
|
+
}
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11309
11392
|
if (opts.history) {
|
|
11310
11393
|
const runsResult = await apiRequest({
|
|
11311
11394
|
method: "GET",
|
|
@@ -11626,7 +11709,7 @@ function collectCodeDelta(files, opts) {
|
|
|
11626
11709
|
|
|
11627
11710
|
// src/lib/debounce.ts
|
|
11628
11711
|
var import_node_fs6 = require("node:fs");
|
|
11629
|
-
var
|
|
11712
|
+
var import_node_crypto2 = require("node:crypto");
|
|
11630
11713
|
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
|
|
11631
11714
|
if (!(0, import_node_fs6.existsSync)(DEBOUNCE_FILE)) return null;
|
|
11632
11715
|
try {
|
|
@@ -11664,7 +11747,7 @@ function checkMtime(files, bypassForRecentCommits) {
|
|
|
11664
11747
|
return "No files modified since last analysis";
|
|
11665
11748
|
}
|
|
11666
11749
|
function computeContentHash(files) {
|
|
11667
|
-
const hash = (0,
|
|
11750
|
+
const hash = (0, import_node_crypto2.createHash)("sha1");
|
|
11668
11751
|
const sorted = [...files].sort();
|
|
11669
11752
|
for (const f of sorted) {
|
|
11670
11753
|
const resolved = resolveFile(f) ?? f;
|
|
@@ -12049,11 +12132,11 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
12049
12132
|
|
|
12050
12133
|
// src/lib/offline.ts
|
|
12051
12134
|
var import_node_fs10 = require("node:fs");
|
|
12052
|
-
var
|
|
12135
|
+
var import_node_crypto3 = require("node:crypto");
|
|
12053
12136
|
function cacheRequest(body) {
|
|
12054
12137
|
try {
|
|
12055
12138
|
(0, import_node_fs10.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
12056
|
-
const suffix = (0,
|
|
12139
|
+
const suffix = (0, import_node_crypto3.randomBytes)(4).toString("hex");
|
|
12057
12140
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
12058
12141
|
(0, import_node_fs10.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
|
|
12059
12142
|
} catch {
|
|
@@ -12068,6 +12151,90 @@ function buildOfflineFallback(reason, staticResults) {
|
|
|
12068
12151
|
};
|
|
12069
12152
|
}
|
|
12070
12153
|
|
|
12154
|
+
// src/lib/context-files.ts
|
|
12155
|
+
var import_node_fs11 = require("node:fs");
|
|
12156
|
+
var MAX_CONTEXT_FILES = 10;
|
|
12157
|
+
var MAX_CONTEXT_FILE_BYTES = 10240;
|
|
12158
|
+
var MAX_CONTEXT_TOTAL_BYTES = 51200;
|
|
12159
|
+
function gatherContextFiles(contextPaths, deltaFiles) {
|
|
12160
|
+
const deltaPaths = new Set(deltaFiles.map((f) => f.path));
|
|
12161
|
+
const result = [];
|
|
12162
|
+
let totalBytes = 0;
|
|
12163
|
+
for (const filePath of contextPaths) {
|
|
12164
|
+
if (result.length >= MAX_CONTEXT_FILES) break;
|
|
12165
|
+
if (deltaPaths.has(filePath)) continue;
|
|
12166
|
+
try {
|
|
12167
|
+
const content = (0, import_node_fs11.readFileSync)(filePath, "utf8");
|
|
12168
|
+
const bytes = Buffer.byteLength(content);
|
|
12169
|
+
if (bytes > MAX_CONTEXT_FILE_BYTES) {
|
|
12170
|
+
logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
|
|
12171
|
+
continue;
|
|
12172
|
+
}
|
|
12173
|
+
if (totalBytes + bytes > MAX_CONTEXT_TOTAL_BYTES) {
|
|
12174
|
+
logEvent("context_file_skipped", { path: filePath, reason: "budget", bytes });
|
|
12175
|
+
continue;
|
|
12176
|
+
}
|
|
12177
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
12178
|
+
const langMap = {
|
|
12179
|
+
ts: "typescript",
|
|
12180
|
+
tsx: "typescript",
|
|
12181
|
+
js: "javascript",
|
|
12182
|
+
jsx: "javascript",
|
|
12183
|
+
py: "python",
|
|
12184
|
+
rb: "ruby",
|
|
12185
|
+
go: "go",
|
|
12186
|
+
rs: "rust",
|
|
12187
|
+
java: "java",
|
|
12188
|
+
yaml: "yaml",
|
|
12189
|
+
yml: "yaml",
|
|
12190
|
+
json: "json",
|
|
12191
|
+
md: "markdown",
|
|
12192
|
+
sql: "sql",
|
|
12193
|
+
sh: "bash",
|
|
12194
|
+
css: "css",
|
|
12195
|
+
html: "html"
|
|
12196
|
+
};
|
|
12197
|
+
result.push({
|
|
12198
|
+
path: filePath,
|
|
12199
|
+
content,
|
|
12200
|
+
language: langMap[ext],
|
|
12201
|
+
role: "context"
|
|
12202
|
+
});
|
|
12203
|
+
totalBytes += bytes;
|
|
12204
|
+
} catch {
|
|
12205
|
+
logEvent("context_file_skipped", { path: filePath, reason: "missing" });
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
return result;
|
|
12209
|
+
}
|
|
12210
|
+
|
|
12211
|
+
// src/lib/cache-cleanup.ts
|
|
12212
|
+
var import_node_fs12 = require("node:fs");
|
|
12213
|
+
var import_node_path8 = require("node:path");
|
|
12214
|
+
var CACHE_TTL_DAYS = 7;
|
|
12215
|
+
function pruneStaleCache() {
|
|
12216
|
+
try {
|
|
12217
|
+
const dir = projectPath(CACHE_DIR);
|
|
12218
|
+
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
12219
|
+
for (const entry of (0, import_node_fs12.readdirSync)(dir)) {
|
|
12220
|
+
if (!entry.startsWith("pending-")) continue;
|
|
12221
|
+
const path = (0, import_node_path8.join)(dir, entry);
|
|
12222
|
+
try {
|
|
12223
|
+
const stat = (0, import_node_fs12.statSync)(path);
|
|
12224
|
+
if (stat.mtimeMs < cutoff) {
|
|
12225
|
+
(0, import_node_fs12.unlinkSync)(path);
|
|
12226
|
+
logEvent("cache_entry_pruned", {
|
|
12227
|
+
path: entry,
|
|
12228
|
+
age_days: Math.round((Date.now() - stat.mtimeMs) / 864e5)
|
|
12229
|
+
});
|
|
12230
|
+
}
|
|
12231
|
+
} catch {
|
|
12232
|
+
}
|
|
12233
|
+
}
|
|
12234
|
+
} catch {
|
|
12235
|
+
}
|
|
12236
|
+
}
|
|
12237
|
+
|
|
12071
12238
|
// src/lib/analysis-mode.ts
|
|
12072
12239
|
var DEBUG_PHRASES = [
|
|
12073
12240
|
"not working",
|
|
@@ -12138,7 +12305,7 @@ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPromp
|
|
|
12138
12305
|
}
|
|
12139
12306
|
|
|
12140
12307
|
// src/lib/transcript.ts
|
|
12141
|
-
var
|
|
12308
|
+
var import_node_fs13 = require("node:fs");
|
|
12142
12309
|
var MAX_READ_BYTES = 256 * 1024;
|
|
12143
12310
|
var SMALL_FILE_BYTES = 64 * 1024;
|
|
12144
12311
|
var MAX_FILES_LIST = 20;
|
|
@@ -12160,14 +12327,14 @@ async function extractActionSummary(transcriptPath) {
|
|
|
12160
12327
|
function readTurnLines(transcriptPath) {
|
|
12161
12328
|
let size;
|
|
12162
12329
|
try {
|
|
12163
|
-
size = (0,
|
|
12330
|
+
size = (0, import_node_fs13.statSync)(transcriptPath).size;
|
|
12164
12331
|
} catch {
|
|
12165
12332
|
return null;
|
|
12166
12333
|
}
|
|
12167
12334
|
if (size === 0) return null;
|
|
12168
12335
|
let raw;
|
|
12169
12336
|
if (size <= SMALL_FILE_BYTES) {
|
|
12170
|
-
raw = (0,
|
|
12337
|
+
raw = (0, import_node_fs13.readFileSync)(transcriptPath, "utf-8");
|
|
12171
12338
|
} else {
|
|
12172
12339
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
12173
12340
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
@@ -12484,6 +12651,33 @@ async function runAnalyze(opts, globals) {
|
|
|
12484
12651
|
if (!urlResult.ok) {
|
|
12485
12652
|
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
12486
12653
|
}
|
|
12654
|
+
const sessionIdForMemory = process.env.CLAUDE_SESSION_ID ?? "";
|
|
12655
|
+
let contextFilePaths = [];
|
|
12656
|
+
try {
|
|
12657
|
+
const memoryPath2 = sessionIdForMemory ? `/memory?session_id=${encodeURIComponent(sessionIdForMemory)}` : "/memory";
|
|
12658
|
+
const memoryResult = await apiRequest({
|
|
12659
|
+
method: "GET",
|
|
12660
|
+
path: memoryPath2,
|
|
12661
|
+
serviceUrl: urlResult.data,
|
|
12662
|
+
token: tokenResult.data.token,
|
|
12663
|
+
verbose: globals.verbose,
|
|
12664
|
+
timeout: 1e4,
|
|
12665
|
+
cmd: "analyze_context"
|
|
12666
|
+
});
|
|
12667
|
+
if (memoryResult.ok && Array.isArray(memoryResult.data.context_files)) {
|
|
12668
|
+
contextFilePaths = memoryResult.data.context_files;
|
|
12669
|
+
}
|
|
12670
|
+
} catch {
|
|
12671
|
+
logEvent("memory_fetch_failed", { reason: "exception" });
|
|
12672
|
+
}
|
|
12673
|
+
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
12674
|
+
for (const f of codeDelta.files) {
|
|
12675
|
+
f.role = "delta";
|
|
12676
|
+
}
|
|
12677
|
+
for (const cf of contextFiles) {
|
|
12678
|
+
codeDelta.files.push(cf);
|
|
12679
|
+
}
|
|
12680
|
+
pruneStaleCache();
|
|
12487
12681
|
const requestBody = {
|
|
12488
12682
|
static_results: staticResults,
|
|
12489
12683
|
code_delta: codeDelta,
|
|
@@ -12540,7 +12734,8 @@ async function runAnalyze(opts, globals) {
|
|
|
12540
12734
|
body: requestBody,
|
|
12541
12735
|
verbose: globals.verbose,
|
|
12542
12736
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
12543
|
-
cmd: "analyze"
|
|
12737
|
+
cmd: "analyze",
|
|
12738
|
+
encodeBody: true
|
|
12544
12739
|
});
|
|
12545
12740
|
if (!result.ok && (result.category === "network" || result.category === "timeout")) {
|
|
12546
12741
|
logEvent("analyze_warm_retry", { first_error: result.error, category: result.category });
|
|
@@ -12562,7 +12757,8 @@ async function runAnalyze(opts, globals) {
|
|
|
12562
12757
|
verbose: globals.verbose,
|
|
12563
12758
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
12564
12759
|
cmd: "analyze",
|
|
12565
|
-
retry: true
|
|
12760
|
+
retry: true,
|
|
12761
|
+
encodeBody: true
|
|
12566
12762
|
});
|
|
12567
12763
|
}
|
|
12568
12764
|
if (!result.ok) {
|
|
@@ -12576,6 +12772,26 @@ async function runAnalyze(opts, globals) {
|
|
|
12576
12772
|
}
|
|
12577
12773
|
const response = result.data;
|
|
12578
12774
|
const decision = response.gate_decision ?? "PASS";
|
|
12775
|
+
const metadata = response.metadata ?? {};
|
|
12776
|
+
const intentAmbiguity = metadata.intent_ambiguity;
|
|
12777
|
+
if (intentAmbiguity != null && intentAmbiguity > 5) {
|
|
12778
|
+
const taskInfo = response.task ?? {};
|
|
12779
|
+
if (intentAmbiguity > 7) {
|
|
12780
|
+
process.stderr.write(`${YELLOW}\u26A0 Vague prompt (${intentAmbiguity}/10) \u2014 Analysis will proceed but findings may not match your intent.${NC}
|
|
12781
|
+
`);
|
|
12782
|
+
} else {
|
|
12783
|
+
process.stderr.write(`${YELLOW}\u26A0 Ambiguous prompt (${intentAmbiguity}/10) \u2014 Consider being more specific about what to change and where.${NC}
|
|
12784
|
+
`);
|
|
12785
|
+
}
|
|
12786
|
+
}
|
|
12787
|
+
const taskMeta = response.task ?? {};
|
|
12788
|
+
if (taskMeta.label) {
|
|
12789
|
+
const taskLabel = taskMeta.label;
|
|
12790
|
+
const isNew = taskMeta.is_new ?? false;
|
|
12791
|
+
const prefix = isNew ? `${BOLD}\u25B6 NEW${NC} ` : "";
|
|
12792
|
+
process.stderr.write(`${DIM}Task: ${prefix}${taskLabel}${NC}
|
|
12793
|
+
`);
|
|
12794
|
+
}
|
|
12579
12795
|
saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
|
|
12580
12796
|
switch (decision) {
|
|
12581
12797
|
case "FAIL": {
|
|
@@ -12680,7 +12896,7 @@ async function runAnalyze(opts, globals) {
|
|
|
12680
12896
|
}
|
|
12681
12897
|
|
|
12682
12898
|
// src/commands/review.ts
|
|
12683
|
-
var
|
|
12899
|
+
var import_node_fs14 = require("node:fs");
|
|
12684
12900
|
function registerReviewCommand(program2) {
|
|
12685
12901
|
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) => {
|
|
12686
12902
|
const globals = program2.opts();
|
|
@@ -12699,7 +12915,7 @@ async function runReview(opts, globals) {
|
|
|
12699
12915
|
const securityFiles = filterSecurity(allFiles);
|
|
12700
12916
|
let staticResults;
|
|
12701
12917
|
if (isCodacyAvailable()) {
|
|
12702
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
12918
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs14.existsSync)(f) || resolveFile(f) !== null);
|
|
12703
12919
|
staticResults = runCodacyAnalysis(scannable);
|
|
12704
12920
|
} else {
|
|
12705
12921
|
staticResults = {
|
|
@@ -12724,10 +12940,10 @@ async function runReview(opts, globals) {
|
|
|
12724
12940
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
12725
12941
|
specs = [];
|
|
12726
12942
|
for (const p of specPaths) {
|
|
12727
|
-
if (!(0,
|
|
12943
|
+
if (!(0, import_node_fs14.existsSync)(p)) continue;
|
|
12728
12944
|
try {
|
|
12729
|
-
const { readFileSync:
|
|
12730
|
-
const content =
|
|
12945
|
+
const { readFileSync: readFileSync8 } = await import("node:fs");
|
|
12946
|
+
const content = readFileSync8(p, "utf-8");
|
|
12731
12947
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
12732
12948
|
} catch {
|
|
12733
12949
|
}
|
|
@@ -12785,21 +13001,21 @@ async function runReview(opts, globals) {
|
|
|
12785
13001
|
}
|
|
12786
13002
|
|
|
12787
13003
|
// src/commands/init.ts
|
|
12788
|
-
var
|
|
13004
|
+
var import_node_fs15 = require("node:fs");
|
|
12789
13005
|
var import_promises8 = require("node:fs/promises");
|
|
12790
|
-
var
|
|
13006
|
+
var import_node_path9 = require("node:path");
|
|
12791
13007
|
var import_node_child_process8 = require("node:child_process");
|
|
12792
13008
|
function resolveDataDir() {
|
|
12793
13009
|
const candidates = [
|
|
12794
|
-
(0,
|
|
13010
|
+
(0, import_node_path9.join)(__dirname, "..", "data"),
|
|
12795
13011
|
// installed: node_modules/@codacy/gate-cli/data
|
|
12796
|
-
(0,
|
|
13012
|
+
(0, import_node_path9.join)(__dirname, "..", "..", "data"),
|
|
12797
13013
|
// edge case: nested resolution
|
|
12798
|
-
(0,
|
|
13014
|
+
(0, import_node_path9.join)(process.cwd(), "cli", "data")
|
|
12799
13015
|
// local dev: running from repo root
|
|
12800
13016
|
];
|
|
12801
13017
|
for (const candidate of candidates) {
|
|
12802
|
-
if ((0,
|
|
13018
|
+
if ((0, import_node_fs15.existsSync)((0, import_node_path9.join)(candidate, "skills"))) {
|
|
12803
13019
|
return candidate;
|
|
12804
13020
|
}
|
|
12805
13021
|
}
|
|
@@ -12815,7 +13031,7 @@ function registerInitCommand(program2) {
|
|
|
12815
13031
|
program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
12816
13032
|
const force = opts.force ?? false;
|
|
12817
13033
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
12818
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
13034
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs15.existsSync)(m));
|
|
12819
13035
|
if (!isProject) {
|
|
12820
13036
|
printError("No project detected in the current directory.");
|
|
12821
13037
|
printInfo('Run "gate init" from your project root.');
|
|
@@ -12868,21 +13084,21 @@ function registerInitCommand(program2) {
|
|
|
12868
13084
|
console.log("");
|
|
12869
13085
|
printInfo("Installing skills...");
|
|
12870
13086
|
const dataDir = resolveDataDir();
|
|
12871
|
-
const skillsSource = (0,
|
|
13087
|
+
const skillsSource = (0, import_node_path9.join)(dataDir, "skills");
|
|
12872
13088
|
const skillsDest = ".claude/skills";
|
|
12873
13089
|
const skills = ["gate-setup", "gate-analyze", "gate-status", "gate-feedback"];
|
|
12874
13090
|
let skillsInstalled = 0;
|
|
12875
13091
|
for (const skill of skills) {
|
|
12876
|
-
const src = (0,
|
|
12877
|
-
const dest = (0,
|
|
12878
|
-
if (!(0,
|
|
13092
|
+
const src = (0, import_node_path9.join)(skillsSource, skill);
|
|
13093
|
+
const dest = (0, import_node_path9.join)(skillsDest, skill);
|
|
13094
|
+
if (!(0, import_node_fs15.existsSync)(src)) {
|
|
12879
13095
|
printWarn(` Skill data not found: ${skill}`);
|
|
12880
13096
|
continue;
|
|
12881
13097
|
}
|
|
12882
|
-
if ((0,
|
|
12883
|
-
const srcSkill = (0,
|
|
12884
|
-
const destSkill = (0,
|
|
12885
|
-
if ((0,
|
|
13098
|
+
if ((0, import_node_fs15.existsSync)(dest) && !force) {
|
|
13099
|
+
const srcSkill = (0, import_node_path9.join)(src, "SKILL.md");
|
|
13100
|
+
const destSkill = (0, import_node_path9.join)(dest, "SKILL.md");
|
|
13101
|
+
if ((0, import_node_fs15.existsSync)(destSkill)) {
|
|
12886
13102
|
try {
|
|
12887
13103
|
const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
|
|
12888
13104
|
const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
|
|
@@ -12910,7 +13126,7 @@ function registerInitCommand(program2) {
|
|
|
12910
13126
|
printInfo(' Run "gate hooks install --force" to overwrite.');
|
|
12911
13127
|
}
|
|
12912
13128
|
await (0, import_promises8.mkdir)(GATE_DIR, { recursive: true });
|
|
12913
|
-
const globalGateDir = (0,
|
|
13129
|
+
const globalGateDir = (0, import_node_path9.join)(process.env.HOME ?? "", ".gate");
|
|
12914
13130
|
await (0, import_promises8.mkdir)(globalGateDir, { recursive: true });
|
|
12915
13131
|
console.log("");
|
|
12916
13132
|
printInfo("GATE.md initialized!");
|
|
@@ -12927,8 +13143,245 @@ function registerInitCommand(program2) {
|
|
|
12927
13143
|
});
|
|
12928
13144
|
}
|
|
12929
13145
|
|
|
13146
|
+
// src/commands/task.ts
|
|
13147
|
+
function memoryPath() {
|
|
13148
|
+
const sid = process.env.CLAUDE_SESSION_ID;
|
|
13149
|
+
return sid ? `/memory?session_id=${encodeURIComponent(sid)}` : "/memory";
|
|
13150
|
+
}
|
|
13151
|
+
function timeAgo2(isoDate) {
|
|
13152
|
+
const ms = Date.now() - new Date(isoDate).getTime();
|
|
13153
|
+
const mins = Math.floor(ms / 6e4);
|
|
13154
|
+
if (mins < 1) return "just now";
|
|
13155
|
+
if (mins < 60) return `${mins}m ago`;
|
|
13156
|
+
const hrs = Math.floor(mins / 60);
|
|
13157
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
13158
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
13159
|
+
}
|
|
13160
|
+
async function getAuth(globals) {
|
|
13161
|
+
const tokenResult = await resolveToken(globals.token);
|
|
13162
|
+
if (!tokenResult.ok) {
|
|
13163
|
+
printError(tokenResult.error);
|
|
13164
|
+
process.exit(1);
|
|
13165
|
+
}
|
|
13166
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
13167
|
+
if (!urlResult.ok) {
|
|
13168
|
+
printError(urlResult.error);
|
|
13169
|
+
process.exit(1);
|
|
13170
|
+
}
|
|
13171
|
+
return { token: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
13172
|
+
}
|
|
13173
|
+
function registerTaskCommands(program2) {
|
|
13174
|
+
const task = program2.command("task").description("Manage tasks");
|
|
13175
|
+
task.command("list").description("List tasks").option("--status <status>", "Filter by status (active, closed, merged)").action(async (opts) => {
|
|
13176
|
+
const globals = program2.opts();
|
|
13177
|
+
const { token, serviceUrl } = await getAuth(globals);
|
|
13178
|
+
const statusParam = opts.status ? `?status=${opts.status}` : "";
|
|
13179
|
+
const result = await apiRequest({
|
|
13180
|
+
method: "GET",
|
|
13181
|
+
path: `/tasks${statusParam}`,
|
|
13182
|
+
serviceUrl,
|
|
13183
|
+
token,
|
|
13184
|
+
verbose: globals.verbose,
|
|
13185
|
+
cmd: "task_list"
|
|
13186
|
+
});
|
|
13187
|
+
if (!result.ok) {
|
|
13188
|
+
printError(result.error);
|
|
13189
|
+
process.exit(1);
|
|
13190
|
+
}
|
|
13191
|
+
const tasks = result.data.tasks;
|
|
13192
|
+
if (tasks.length === 0) {
|
|
13193
|
+
printInfo("No tasks found.");
|
|
13194
|
+
return;
|
|
13195
|
+
}
|
|
13196
|
+
printInfo("Tasks:");
|
|
13197
|
+
for (const t of tasks) {
|
|
13198
|
+
const status = t.status ?? "active";
|
|
13199
|
+
const statusTag = status !== "active" ? ` [${status}]` : "";
|
|
13200
|
+
const runs = t.run_count;
|
|
13201
|
+
const ago = t.last_run_at ? timeAgo2(t.last_run_at) : "";
|
|
13202
|
+
const detail = runs != null ? `${runs} run${runs === 1 ? "" : "s"}${ago ? ", " + ago : ""}` : "";
|
|
13203
|
+
printInfo(` ${t.label}${statusTag}${detail ? " (" + detail + ")" : ""}`);
|
|
13204
|
+
}
|
|
13205
|
+
});
|
|
13206
|
+
task.command("close").description("Close the current task (next run starts a new task)").option("--id <taskId>", "Close a specific task by ID").action(async (opts) => {
|
|
13207
|
+
const globals = program2.opts();
|
|
13208
|
+
const { token, serviceUrl } = await getAuth(globals);
|
|
13209
|
+
let taskId = opts.id;
|
|
13210
|
+
if (!taskId) {
|
|
13211
|
+
const mem = await apiRequest({
|
|
13212
|
+
method: "GET",
|
|
13213
|
+
path: memoryPath(),
|
|
13214
|
+
serviceUrl,
|
|
13215
|
+
token,
|
|
13216
|
+
verbose: globals.verbose,
|
|
13217
|
+
cmd: "task_close_lookup"
|
|
13218
|
+
});
|
|
13219
|
+
if (!mem.ok) {
|
|
13220
|
+
printError(mem.error);
|
|
13221
|
+
process.exit(1);
|
|
13222
|
+
}
|
|
13223
|
+
const ct = mem.data.current_task;
|
|
13224
|
+
if (!ct?.id) {
|
|
13225
|
+
printError("No active task to close.");
|
|
13226
|
+
process.exit(1);
|
|
13227
|
+
}
|
|
13228
|
+
taskId = ct.id;
|
|
13229
|
+
}
|
|
13230
|
+
const result = await apiRequest({
|
|
13231
|
+
method: "POST",
|
|
13232
|
+
path: `/tasks/${taskId}/close`,
|
|
13233
|
+
serviceUrl,
|
|
13234
|
+
token,
|
|
13235
|
+
verbose: globals.verbose,
|
|
13236
|
+
cmd: "task_close"
|
|
13237
|
+
});
|
|
13238
|
+
if (!result.ok) {
|
|
13239
|
+
printError(result.error);
|
|
13240
|
+
process.exit(1);
|
|
13241
|
+
}
|
|
13242
|
+
printInfo(`Closed task: "${result.data.task.label}"`);
|
|
13243
|
+
});
|
|
13244
|
+
task.command("rename <label>").description("Rename the current task").option("--id <taskId>", "Rename a specific task by ID").action(async (label, opts) => {
|
|
13245
|
+
const globals = program2.opts();
|
|
13246
|
+
const { token, serviceUrl } = await getAuth(globals);
|
|
13247
|
+
let taskId = opts.id;
|
|
13248
|
+
if (!taskId) {
|
|
13249
|
+
const mem = await apiRequest({
|
|
13250
|
+
method: "GET",
|
|
13251
|
+
path: memoryPath(),
|
|
13252
|
+
serviceUrl,
|
|
13253
|
+
token,
|
|
13254
|
+
verbose: globals.verbose,
|
|
13255
|
+
cmd: "task_rename_lookup"
|
|
13256
|
+
});
|
|
13257
|
+
if (!mem.ok) {
|
|
13258
|
+
printError(mem.error);
|
|
13259
|
+
process.exit(1);
|
|
13260
|
+
}
|
|
13261
|
+
const ct = mem.data.current_task;
|
|
13262
|
+
if (!ct?.id) {
|
|
13263
|
+
printError("No active task to rename.");
|
|
13264
|
+
process.exit(1);
|
|
13265
|
+
}
|
|
13266
|
+
taskId = ct.id;
|
|
13267
|
+
}
|
|
13268
|
+
const result = await apiRequest({
|
|
13269
|
+
method: "POST",
|
|
13270
|
+
path: `/tasks/${taskId}/rename`,
|
|
13271
|
+
serviceUrl,
|
|
13272
|
+
token,
|
|
13273
|
+
body: { label: label.slice(0, 120) },
|
|
13274
|
+
verbose: globals.verbose,
|
|
13275
|
+
cmd: "task_rename"
|
|
13276
|
+
});
|
|
13277
|
+
if (!result.ok) {
|
|
13278
|
+
printError(result.error);
|
|
13279
|
+
process.exit(1);
|
|
13280
|
+
}
|
|
13281
|
+
printInfo(`Renamed task to: "${result.data.task.label}"`);
|
|
13282
|
+
});
|
|
13283
|
+
task.command("merge <source> <target>").description("Merge source task into target task").action(async (source, target) => {
|
|
13284
|
+
const globals = program2.opts();
|
|
13285
|
+
const { token, serviceUrl } = await getAuth(globals);
|
|
13286
|
+
const result = await apiRequest({
|
|
13287
|
+
method: "POST",
|
|
13288
|
+
path: "/tasks/merge",
|
|
13289
|
+
serviceUrl,
|
|
13290
|
+
token,
|
|
13291
|
+
body: { source_id: source, target_id: target },
|
|
13292
|
+
verbose: globals.verbose,
|
|
13293
|
+
cmd: "task_merge"
|
|
13294
|
+
});
|
|
13295
|
+
if (!result.ok) {
|
|
13296
|
+
printError(result.error);
|
|
13297
|
+
process.exit(1);
|
|
13298
|
+
}
|
|
13299
|
+
printInfo(`Merged task ${source} into ${target}`);
|
|
13300
|
+
});
|
|
13301
|
+
}
|
|
13302
|
+
|
|
13303
|
+
// src/commands/reset.ts
|
|
13304
|
+
var import_node_fs16 = require("node:fs");
|
|
13305
|
+
var import_node_path10 = require("node:path");
|
|
13306
|
+
function registerResetCommand(program2) {
|
|
13307
|
+
program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.gate/.logs/)").action(async (opts) => {
|
|
13308
|
+
const globals = program2.opts();
|
|
13309
|
+
if (!opts.keepTask) {
|
|
13310
|
+
try {
|
|
13311
|
+
const tokenResult = await resolveToken(globals.token);
|
|
13312
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
13313
|
+
if (tokenResult.ok && urlResult.ok) {
|
|
13314
|
+
const mem = await apiRequest({
|
|
13315
|
+
method: "GET",
|
|
13316
|
+
path: process.env.CLAUDE_SESSION_ID ? `/memory?session_id=${encodeURIComponent(process.env.CLAUDE_SESSION_ID)}` : "/memory",
|
|
13317
|
+
serviceUrl: urlResult.data,
|
|
13318
|
+
token: tokenResult.data.token,
|
|
13319
|
+
timeout: 1e4,
|
|
13320
|
+
cmd: "reset_lookup"
|
|
13321
|
+
});
|
|
13322
|
+
const ct = mem.ok ? mem.data.current_task : null;
|
|
13323
|
+
if (ct?.id) {
|
|
13324
|
+
const closeResult = await apiRequest({
|
|
13325
|
+
method: "POST",
|
|
13326
|
+
path: `/tasks/${ct.id}/close`,
|
|
13327
|
+
serviceUrl: urlResult.data,
|
|
13328
|
+
token: tokenResult.data.token,
|
|
13329
|
+
timeout: 1e4,
|
|
13330
|
+
cmd: "reset_close"
|
|
13331
|
+
});
|
|
13332
|
+
if (closeResult.ok) {
|
|
13333
|
+
printInfo(`Closed task: "${ct.label}"`);
|
|
13334
|
+
}
|
|
13335
|
+
}
|
|
13336
|
+
}
|
|
13337
|
+
} catch {
|
|
13338
|
+
}
|
|
13339
|
+
}
|
|
13340
|
+
const cacheDir = projectPath(CACHE_DIR);
|
|
13341
|
+
let purged = 0;
|
|
13342
|
+
if ((0, import_node_fs16.existsSync)(cacheDir)) {
|
|
13343
|
+
for (const entry of (0, import_node_fs16.readdirSync)(cacheDir)) {
|
|
13344
|
+
if (entry.startsWith("pending-")) {
|
|
13345
|
+
try {
|
|
13346
|
+
(0, import_node_fs16.unlinkSync)((0, import_node_path10.join)(cacheDir, entry));
|
|
13347
|
+
purged++;
|
|
13348
|
+
} catch {
|
|
13349
|
+
}
|
|
13350
|
+
}
|
|
13351
|
+
}
|
|
13352
|
+
}
|
|
13353
|
+
const filesToClear = [
|
|
13354
|
+
projectPath(INTENT_FILE),
|
|
13355
|
+
projectPath(HASH_FILE),
|
|
13356
|
+
projectPath(`${GATE_DIR}/.iteration-count`),
|
|
13357
|
+
projectPath(`${GATE_DIR}/.last-analysis`)
|
|
13358
|
+
];
|
|
13359
|
+
for (const file of filesToClear) {
|
|
13360
|
+
if ((0, import_node_fs16.existsSync)(file)) {
|
|
13361
|
+
try {
|
|
13362
|
+
(0, import_node_fs16.writeFileSync)(file, "");
|
|
13363
|
+
} catch {
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13366
|
+
}
|
|
13367
|
+
if (opts.all) {
|
|
13368
|
+
const logsDir = projectPath(`${GATE_DIR}/.logs`);
|
|
13369
|
+
if ((0, import_node_fs16.existsSync)(logsDir)) {
|
|
13370
|
+
for (const entry of (0, import_node_fs16.readdirSync)(logsDir)) {
|
|
13371
|
+
try {
|
|
13372
|
+
(0, import_node_fs16.unlinkSync)((0, import_node_path10.join)(logsDir, entry));
|
|
13373
|
+
} catch {
|
|
13374
|
+
}
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
printInfo("Cleared diagnostic logs.");
|
|
13378
|
+
}
|
|
13379
|
+
printInfo(`Reset complete. Purged ${purged} cached request${purged !== 1 ? "s" : ""}.${opts.keepTask ? "" : " Next run starts a new task."}`);
|
|
13380
|
+
});
|
|
13381
|
+
}
|
|
13382
|
+
|
|
12930
13383
|
// src/cli.ts
|
|
12931
|
-
program.name("gate").description("CLI for GATE.md quality gate service").version("0.
|
|
13384
|
+
program.name("gate").description("CLI for GATE.md quality gate service").version("0.15.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
12932
13385
|
registerAuthCommands(program);
|
|
12933
13386
|
registerHooksCommands(program);
|
|
12934
13387
|
registerIntentCommands(program);
|
|
@@ -12939,4 +13392,6 @@ registerFeedbackCommand(program);
|
|
|
12939
13392
|
registerAnalyzeCommand(program);
|
|
12940
13393
|
registerReviewCommand(program);
|
|
12941
13394
|
registerInitCommand(program);
|
|
13395
|
+
registerTaskCommands(program);
|
|
13396
|
+
registerResetCommand(program);
|
|
12942
13397
|
program.parse();
|