mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
acfe80ab6b
## What Adds type-aware linting to the workspace. Until now the only static gate on JS/TS was Prettier (formatting) and `tsc` — there was no linter, so the whole class of bugs `tsc` doesn't catch (floating promises, misused promises, throwing non-Errors, unsafe stringification, dead code) went unguarded across ~450 TS files. - **`eslint.config.mjs`** — flat config. `recommendedTypeChecked` runs on the TS sources via an explicit `project` list (each package keeps tests in a separate `tsconfig.test.json` that `projectService` can't auto-discover, so it's listed explicitly). Type-checked rules are disabled for the loosely-typed plain-JS build/dev scripts. The `argent-private` submodule and build artifacts are ignored (mirrors `.prettierignore`). - **`lint` / `lint:fix`** npm scripts. - **`.github/workflows/lint.yml`** — builds the workspace (type-aware rules need the referenced projects' declarations) then runs `eslint .`. Node 24 (ESLint 10 requires `^20.19 || ^22.13 || >=24`). ## Rule calibration This is an adoption PR, so it's deliberately calibrated rather than maximal: - **Kept on (bug-catchers):** `no-floating-promises`, `no-misused-promises` (with `checksVoidReturn: false` to drop the noisy callback variant), `only-throw-error`, `no-base-to-string`, `prefer-promise-reject-errors`, `no-unused-vars` (`_`-prefix escape hatch), plus the ESLint recommended set. - **Parked off as documented debt:** the high-volume style/`any`-family rules — `no-unsafe-*`, `require-await`, `no-unnecessary-type-assertion`, `restrict-template-expressions`, `unbound-method`, `no-explicit-any`, etc. These flag broad classes of pre-existing code and are out of scope here; each is commented as a ratchet target for follow-up passes. All findings from the kept-on rules are fixed in this PR (dead code/imports, error-cause chaining, **3 genuine floating promises**, non-Error throws, unsafe stringification). Stale unused `eslint-disable` directives left over in the tree were removed by `--fix`. ## Verification Locally, all green: `eslint .` (0 errors), `tsc --build`, `typecheck:tests`/`typecheck:scripts`, the full `vitest` suite (no behavior change from the fixes), and `prettier --check`. ## Not included (intentionally) Other static-analysis gates discussed (CodeQL, dependency-audit, ShellCheck, actionlint) are out of scope for this PR.
101 lines
2.8 KiB
JavaScript
101 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const os = require("os");
|
|
|
|
const LOG_FILE = process.env.ARGENT_MCP_LOG ?? path.join(os.homedir(), ".argent", "mcp-calls.log");
|
|
|
|
// Ensure log file exists
|
|
fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
|
|
if (!fs.existsSync(LOG_FILE)) {
|
|
fs.writeFileSync(LOG_FILE, "");
|
|
}
|
|
|
|
// ANSI colours
|
|
const DIM = "\x1b[2m";
|
|
const CYAN = "\x1b[36m";
|
|
const GREEN = "\x1b[32m";
|
|
const RED = "\x1b[31m";
|
|
const YELLOW = "\x1b[33m";
|
|
const RESET = "\x1b[0m";
|
|
|
|
function formatTime(ts) {
|
|
const d = new Date(ts);
|
|
return d.toTimeString().slice(0, 8);
|
|
}
|
|
|
|
function pad(str, len) {
|
|
return String(str).padEnd(len);
|
|
}
|
|
|
|
function formatEntry(entry) {
|
|
const time = formatTime(entry.ts);
|
|
if (entry.event === "list_tools") {
|
|
return `${DIM}${time}${RESET} ${YELLOW}·${RESET} list_tools ${entry.count} tools`;
|
|
}
|
|
if (entry.event === "tool_called") {
|
|
const args = entry.args ? JSON.stringify(entry.args) : "";
|
|
return `${DIM}${time}${RESET} ${CYAN}→${RESET} tool_called ${pad(entry.name, 12)} ${DIM}${args}${RESET}`;
|
|
}
|
|
if (entry.event === "tool_result") {
|
|
let status;
|
|
if (entry.isError) {
|
|
status = `${RED}✗${RESET} ${DIM}${entry.error ?? ""}${RESET}`;
|
|
} else {
|
|
let resultStr = "";
|
|
if (entry.result !== undefined) {
|
|
const raw = typeof entry.result === "string" ? entry.result : JSON.stringify(entry.result);
|
|
resultStr = " " + DIM + (raw.length > 120 ? raw.slice(0, 117) + "…" : raw) + RESET;
|
|
}
|
|
status = `${GREEN}✓${RESET}${resultStr}`;
|
|
}
|
|
return `${DIM}${time}${RESET} ${entry.isError ? RED : GREEN}←${RESET} tool_result ${pad(entry.name, 12)} ${entry.durationMs}ms ${status}`;
|
|
}
|
|
return `${DIM}${time}${RESET} ${JSON.stringify(entry)}`;
|
|
}
|
|
|
|
// Seek to end of file — only tail new lines
|
|
let filePos = fs.statSync(LOG_FILE).size;
|
|
|
|
console.log(`Waiting for calls at ${LOG_FILE} ...\n`);
|
|
|
|
let buffer = "";
|
|
|
|
function readNewLines() {
|
|
const stat = fs.statSync(LOG_FILE);
|
|
if (stat.size < filePos) {
|
|
// File was truncated/rotated — reset
|
|
filePos = 0;
|
|
buffer = "";
|
|
}
|
|
if (stat.size === filePos) return;
|
|
|
|
const fd = fs.openSync(LOG_FILE, "r");
|
|
const len = stat.size - filePos;
|
|
const buf = Buffer.alloc(len);
|
|
fs.readSync(fd, buf, 0, len, filePos);
|
|
fs.closeSync(fd);
|
|
filePos = stat.size;
|
|
|
|
buffer += buf.toString("utf8");
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop() ?? ""; // keep incomplete last line
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) continue;
|
|
try {
|
|
const entry = JSON.parse(trimmed);
|
|
console.log(formatEntry(entry));
|
|
} catch {
|
|
console.log(trimmed);
|
|
}
|
|
}
|
|
}
|
|
|
|
fs.watch(LOG_FILE, () => {
|
|
readNewLines();
|
|
});
|