@codacy/gate-cli 0.14.3 → 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.
Files changed (2) hide show
  1. package/bin/gate.js +464 -29
  2. package/package.json +1 -1
package/bin/gate.js CHANGED
@@ -10942,6 +10942,9 @@ function registerHooksCommands(program2) {
10942
10942
  });
10943
10943
  }
10944
10944
 
10945
+ // src/commands/intent.ts
10946
+ var import_node_crypto = require("node:crypto");
10947
+
10945
10948
  // src/lib/conversation-buffer.ts
10946
10949
  var import_promises5 = require("node:fs/promises");
10947
10950
  var import_node_fs2 = require("node:fs");
@@ -11070,10 +11073,41 @@ function registerIntentCommands(program2) {
11070
11073
  process.exit(0);
11071
11074
  }
11072
11075
  await appendToConversationBuffer(prompt, event.session_id ?? "");
11076
+ fireClassify(prompt, event.session_id ?? "").catch(() => {
11077
+ });
11073
11078
  } catch {
11074
11079
  }
11075
11080
  process.exit(0);
11076
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
+ });
11077
11111
  }
11078
11112
 
11079
11113
  // src/commands/standard.ts
@@ -11252,6 +11286,16 @@ function registerConfigCommands(program2) {
11252
11286
  }
11253
11287
 
11254
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
+ }
11255
11299
  function registerStatusCommand(program2) {
11256
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) => {
11257
11301
  const globals = program2.opts();
@@ -11324,6 +11368,27 @@ function registerStatusCommand(program2) {
11324
11368
  printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
11325
11369
  }
11326
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
+ }
11327
11392
  if (opts.history) {
11328
11393
  const runsResult = await apiRequest({
11329
11394
  method: "GET",
@@ -11644,7 +11709,7 @@ function collectCodeDelta(files, opts) {
11644
11709
 
11645
11710
  // src/lib/debounce.ts
11646
11711
  var import_node_fs6 = require("node:fs");
11647
- var import_node_crypto = require("node:crypto");
11712
+ var import_node_crypto2 = require("node:crypto");
11648
11713
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
11649
11714
  if (!(0, import_node_fs6.existsSync)(DEBOUNCE_FILE)) return null;
11650
11715
  try {
@@ -11682,7 +11747,7 @@ function checkMtime(files, bypassForRecentCommits) {
11682
11747
  return "No files modified since last analysis";
11683
11748
  }
11684
11749
  function computeContentHash(files) {
11685
- const hash = (0, import_node_crypto.createHash)("sha1");
11750
+ const hash = (0, import_node_crypto2.createHash)("sha1");
11686
11751
  const sorted = [...files].sort();
11687
11752
  for (const f of sorted) {
11688
11753
  const resolved = resolveFile(f) ?? f;
@@ -12067,11 +12132,11 @@ function cleanStaleSnapshots(dir, keepSet) {
12067
12132
 
12068
12133
  // src/lib/offline.ts
12069
12134
  var import_node_fs10 = require("node:fs");
12070
- var import_node_crypto2 = require("node:crypto");
12135
+ var import_node_crypto3 = require("node:crypto");
12071
12136
  function cacheRequest(body) {
12072
12137
  try {
12073
12138
  (0, import_node_fs10.mkdirSync)(CACHE_DIR, { recursive: true });
12074
- const suffix = (0, import_node_crypto2.randomBytes)(4).toString("hex");
12139
+ const suffix = (0, import_node_crypto3.randomBytes)(4).toString("hex");
12075
12140
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
12076
12141
  (0, import_node_fs10.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
12077
12142
  } catch {
@@ -12086,6 +12151,90 @@ function buildOfflineFallback(reason, staticResults) {
12086
12151
  };
12087
12152
  }
12088
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
+
12089
12238
  // src/lib/analysis-mode.ts
12090
12239
  var DEBUG_PHRASES = [
12091
12240
  "not working",
@@ -12156,7 +12305,7 @@ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPromp
12156
12305
  }
12157
12306
 
12158
12307
  // src/lib/transcript.ts
12159
- var import_node_fs11 = require("node:fs");
12308
+ var import_node_fs13 = require("node:fs");
12160
12309
  var MAX_READ_BYTES = 256 * 1024;
12161
12310
  var SMALL_FILE_BYTES = 64 * 1024;
12162
12311
  var MAX_FILES_LIST = 20;
@@ -12178,14 +12327,14 @@ async function extractActionSummary(transcriptPath) {
12178
12327
  function readTurnLines(transcriptPath) {
12179
12328
  let size;
12180
12329
  try {
12181
- size = (0, import_node_fs11.statSync)(transcriptPath).size;
12330
+ size = (0, import_node_fs13.statSync)(transcriptPath).size;
12182
12331
  } catch {
12183
12332
  return null;
12184
12333
  }
12185
12334
  if (size === 0) return null;
12186
12335
  let raw;
12187
12336
  if (size <= SMALL_FILE_BYTES) {
12188
- raw = (0, import_node_fs11.readFileSync)(transcriptPath, "utf-8");
12337
+ raw = (0, import_node_fs13.readFileSync)(transcriptPath, "utf-8");
12189
12338
  } else {
12190
12339
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
12191
12340
  const fd = require("node:fs").openSync(transcriptPath, "r");
@@ -12502,6 +12651,33 @@ async function runAnalyze(opts, globals) {
12502
12651
  if (!urlResult.ok) {
12503
12652
  passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
12504
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();
12505
12681
  const requestBody = {
12506
12682
  static_results: staticResults,
12507
12683
  code_delta: codeDelta,
@@ -12596,6 +12772,26 @@ async function runAnalyze(opts, globals) {
12596
12772
  }
12597
12773
  const response = result.data;
12598
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
+ }
12599
12795
  saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
12600
12796
  switch (decision) {
12601
12797
  case "FAIL": {
@@ -12700,7 +12896,7 @@ async function runAnalyze(opts, globals) {
12700
12896
  }
12701
12897
 
12702
12898
  // src/commands/review.ts
12703
- var import_node_fs12 = require("node:fs");
12899
+ var import_node_fs14 = require("node:fs");
12704
12900
  function registerReviewCommand(program2) {
12705
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) => {
12706
12902
  const globals = program2.opts();
@@ -12719,7 +12915,7 @@ async function runReview(opts, globals) {
12719
12915
  const securityFiles = filterSecurity(allFiles);
12720
12916
  let staticResults;
12721
12917
  if (isCodacyAvailable()) {
12722
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs12.existsSync)(f) || resolveFile(f) !== null);
12918
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs14.existsSync)(f) || resolveFile(f) !== null);
12723
12919
  staticResults = runCodacyAnalysis(scannable);
12724
12920
  } else {
12725
12921
  staticResults = {
@@ -12744,10 +12940,10 @@ async function runReview(opts, globals) {
12744
12940
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
12745
12941
  specs = [];
12746
12942
  for (const p of specPaths) {
12747
- if (!(0, import_node_fs12.existsSync)(p)) continue;
12943
+ if (!(0, import_node_fs14.existsSync)(p)) continue;
12748
12944
  try {
12749
- const { readFileSync: readFileSync6 } = await import("node:fs");
12750
- const content = readFileSync6(p, "utf-8");
12945
+ const { readFileSync: readFileSync8 } = await import("node:fs");
12946
+ const content = readFileSync8(p, "utf-8");
12751
12947
  specs.push({ path: p, content: content.slice(0, 10240) });
12752
12948
  } catch {
12753
12949
  }
@@ -12805,21 +13001,21 @@ async function runReview(opts, globals) {
12805
13001
  }
12806
13002
 
12807
13003
  // src/commands/init.ts
12808
- var import_node_fs13 = require("node:fs");
13004
+ var import_node_fs15 = require("node:fs");
12809
13005
  var import_promises8 = require("node:fs/promises");
12810
- var import_node_path8 = require("node:path");
13006
+ var import_node_path9 = require("node:path");
12811
13007
  var import_node_child_process8 = require("node:child_process");
12812
13008
  function resolveDataDir() {
12813
13009
  const candidates = [
12814
- (0, import_node_path8.join)(__dirname, "..", "data"),
13010
+ (0, import_node_path9.join)(__dirname, "..", "data"),
12815
13011
  // installed: node_modules/@codacy/gate-cli/data
12816
- (0, import_node_path8.join)(__dirname, "..", "..", "data"),
13012
+ (0, import_node_path9.join)(__dirname, "..", "..", "data"),
12817
13013
  // edge case: nested resolution
12818
- (0, import_node_path8.join)(process.cwd(), "cli", "data")
13014
+ (0, import_node_path9.join)(process.cwd(), "cli", "data")
12819
13015
  // local dev: running from repo root
12820
13016
  ];
12821
13017
  for (const candidate of candidates) {
12822
- if ((0, import_node_fs13.existsSync)((0, import_node_path8.join)(candidate, "skills"))) {
13018
+ if ((0, import_node_fs15.existsSync)((0, import_node_path9.join)(candidate, "skills"))) {
12823
13019
  return candidate;
12824
13020
  }
12825
13021
  }
@@ -12835,7 +13031,7 @@ function registerInitCommand(program2) {
12835
13031
  program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
12836
13032
  const force = opts.force ?? false;
12837
13033
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
12838
- const isProject = projectMarkers.some((m) => (0, import_node_fs13.existsSync)(m));
13034
+ const isProject = projectMarkers.some((m) => (0, import_node_fs15.existsSync)(m));
12839
13035
  if (!isProject) {
12840
13036
  printError("No project detected in the current directory.");
12841
13037
  printInfo('Run "gate init" from your project root.');
@@ -12888,21 +13084,21 @@ function registerInitCommand(program2) {
12888
13084
  console.log("");
12889
13085
  printInfo("Installing skills...");
12890
13086
  const dataDir = resolveDataDir();
12891
- const skillsSource = (0, import_node_path8.join)(dataDir, "skills");
13087
+ const skillsSource = (0, import_node_path9.join)(dataDir, "skills");
12892
13088
  const skillsDest = ".claude/skills";
12893
13089
  const skills = ["gate-setup", "gate-analyze", "gate-status", "gate-feedback"];
12894
13090
  let skillsInstalled = 0;
12895
13091
  for (const skill of skills) {
12896
- const src = (0, import_node_path8.join)(skillsSource, skill);
12897
- const dest = (0, import_node_path8.join)(skillsDest, skill);
12898
- if (!(0, import_node_fs13.existsSync)(src)) {
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)) {
12899
13095
  printWarn(` Skill data not found: ${skill}`);
12900
13096
  continue;
12901
13097
  }
12902
- if ((0, import_node_fs13.existsSync)(dest) && !force) {
12903
- const srcSkill = (0, import_node_path8.join)(src, "SKILL.md");
12904
- const destSkill = (0, import_node_path8.join)(dest, "SKILL.md");
12905
- if ((0, import_node_fs13.existsSync)(destSkill)) {
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)) {
12906
13102
  try {
12907
13103
  const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
12908
13104
  const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
@@ -12930,7 +13126,7 @@ function registerInitCommand(program2) {
12930
13126
  printInfo(' Run "gate hooks install --force" to overwrite.');
12931
13127
  }
12932
13128
  await (0, import_promises8.mkdir)(GATE_DIR, { recursive: true });
12933
- const globalGateDir = (0, import_node_path8.join)(process.env.HOME ?? "", ".gate");
13129
+ const globalGateDir = (0, import_node_path9.join)(process.env.HOME ?? "", ".gate");
12934
13130
  await (0, import_promises8.mkdir)(globalGateDir, { recursive: true });
12935
13131
  console.log("");
12936
13132
  printInfo("GATE.md initialized!");
@@ -12947,8 +13143,245 @@ function registerInitCommand(program2) {
12947
13143
  });
12948
13144
  }
12949
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
+
12950
13383
  // src/cli.ts
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");
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");
12952
13385
  registerAuthCommands(program);
12953
13386
  registerHooksCommands(program);
12954
13387
  registerIntentCommands(program);
@@ -12959,4 +13392,6 @@ registerFeedbackCommand(program);
12959
13392
  registerAnalyzeCommand(program);
12960
13393
  registerReviewCommand(program);
12961
13394
  registerInitCommand(program);
13395
+ registerTaskCommands(program);
13396
+ registerResetCommand(program);
12962
13397
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/gate-cli",
3
- "version": "0.14.3",
3
+ "version": "0.15.0",
4
4
  "description": "CLI for GATE.md quality gate service",
5
5
  "bin": {
6
6
  "gate": "./bin/gate.js"