Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Checks

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

concurrency:
group: checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
contents: read

jobs:
checks:
name: Format, lint and types
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2

- run: bun install --frozen-lockfile

# Every step runs even when an earlier one fails, so one push reports
# every kind of problem at once.
- name: Format
run: bun run format:check

- name: Lint
if: ${{ !cancelled() }}
run: bun run lint

- name: Type check
if: ${{ !cancelled() }}
run: bun run check
21 changes: 0 additions & 21 deletions .github/workflows/lint.yaml

This file was deleted.

33 changes: 33 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"options": {
"typeAware": true,
"reportUnusedDisableDirectives": "error"
},
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"env": {
"builtin": true,
"es2026": true
},
"ignorePatterns": ["dist/**", ".astro/**"],
"rules": {
"complexity": ["error", 10],
"typescript/await-thenable": "error",
"typescript/no-array-delete": "error",
"typescript/no-base-to-string": "error",
"typescript/no-duplicate-type-constituents": "error",
"typescript/no-floating-promises": "error",
"typescript/no-for-in-array": "error",
"typescript/no-implied-eval": "error",
"typescript/no-misused-promises": "error",
"typescript/no-redundant-type-constituents": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/only-throw-error": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/restrict-plus-operands": "error",
"typescript/restrict-template-expressions": "error"
}
}
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ public/assets/
public/openapi/
src/data/ekubo-wallet-policy.schema.json
schemas/
benchmarks/**/*.json
17 changes: 13 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,22 @@ Do not restate math that has canonical published references. Link the reference
- `astro.config.mjs` lists every authored page except the hidden 404 page, and every listed page appears exactly once
- All relative links and `/assets/` references resolve — note that moving a page changes its directory depth and silently breaks `../` paths
- `bun run check` and `bun run build` pass, including live OpenAPI synchronization
- `bun run lint` and `bun run format:check` pass (`bun run format` applies Prettier)
- `bun run check:legacy`, `bun run check:a11y`, `bun run check:overflow`, `bun run check:theme`, and `bun run check:api` pass against a local preview
- No em dashes or entities introduced inside fenced code blocks; shell flags such as `--rm` intact

## Complexity Policy
- Run `bun run lint` before considering a change done. CI runs it on every push and
## Lint Policy

- CI runs `bun run format:check`, `bun run lint` and `bun run check` on every push and
pull request.
- The only rule is ESLint's `complexity`, capped at 10 per function.
- Prefer splitting a function over adding `// eslint-disable-next-line complexity`.
- `bun run lint` runs oxlint with the rules in `.oxlintrc.json`: `complexity`, capped at
10 per function, and a set of type-aware TypeScript rules (floating and misused
promises, template and `+` operands, needless type assertions and so on). Warnings and
unused disable comments fail too.
- Known violations are recorded per file and rule in `lint-suppressions.json`. A new
violation fails; after fixing one, run `bun run lint:prune` and commit the smaller file.
Never raise a count.
- Prefer splitting a function over adding `// oxlint-disable-next-line complexity`.
If a disable really is the right call (a table-driven or numeric routine where the
branches are the point), put the reason on the line above it.
- Prettier stays the formatter here because oxfmt cannot format `.astro` files.
205 changes: 61 additions & 144 deletions bun.lock

Large diffs are not rendered by default.

31 changes: 0 additions & 31 deletions eslint.config.js

This file was deleted.

12 changes: 12 additions & 0 deletions lint-suppressions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"src/components/PolicyEditor.astro": {
"eslint(complexity)": {
"count": 1
}
},
"src/pages/api/index.astro": {
"eslint(complexity)": {
"count": 1
}
}
}
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"format:check": "prettier --check .",
"deploy": "wrangler pages deploy dist --project-name ekubo-docs",
"astro": "astro",
"lint": "eslint ."
"lint": "bun scripts/lint.ts",
"lint:prune": "bun scripts/lint.ts --prune"
},
"dependencies": {
"@astrojs/sitemap": "^3.7.3",
Expand All @@ -41,9 +42,10 @@
"devDependencies": {
"@astrojs/check": "^0.9.10",
"@axe-core/playwright": "^4.12.1",
"@types/bun": "^1.4.3",
"@types/json-source-map": "^0.6.0",
"@typescript-eslint/parser": "8.68.0",
"eslint": "10.9.1",
"oxlint": "1.88.0",
"oxlint-tsgolint": "^7.0.2004",
"playwright": "^1.62.1",
"prettier": "^3.9.6",
"prettier-plugin-astro": "^0.14.1",
Expand Down
153 changes: 153 additions & 0 deletions scripts/lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Runs oxlint over the repository and holds known violations to the per-file,
// per-rule counts in lint-suppressions.json. A file may not gain a violation of
// a recorded rule, and a count that is higher than the file needs fails too, so
// the file only ever shrinks.
//
// bun run lint lint; fails on any unrecorded violation
// bun run lint --fix extra arguments are passed to oxlint
// bun run lint:prune lower recorded counts to what remains
// bun run lint --suppress-rule=CODE record every current violation of CODE,
// e.g. "typescript(no-floating-promises)";
// only when introducing a rule
import { readFileSync, writeFileSync } from "node:fs";

const SUPPRESSIONS_FILE = "lint-suppressions.json";

type Suppressions = Record<string, Record<string, { count: number }>>;

interface Diagnostic {
message: string;
code?: string;
filename: string;
help?: string;
labels?: { span: { line: number; column: number } }[];
}

const args = process.argv.slice(2);
const prune = args.includes("--prune");
const suppressRules = args
.filter((arg) => arg.startsWith("--suppress-rule="))
.map((arg) => arg.slice("--suppress-rule=".length));
const oxlintArgs = args.filter(
(arg) => arg !== "--prune" && !arg.startsWith("--suppress-rule="),
);

const result = Bun.spawnSync(
["oxlint", "--format=json", "--max-warnings=0", ...oxlintArgs],
{ stdout: "pipe", stderr: "inherit" },
);
let diagnostics: Diagnostic[];
try {
diagnostics = (
JSON.parse(result.stdout.toString()) as { diagnostics: Diagnostic[] }
).diagnostics;
} catch {
console.error(result.stdout.toString());
console.error(
`oxlint did not produce a report (exit code ${result.exitCode})`,
);
process.exit(result.exitCode || 1);
}

const ruleOf = (diagnostic: Diagnostic) => diagnostic.code ?? "oxlint";

const found = new Map<string, Map<string, Diagnostic[]>>();
for (const diagnostic of diagnostics) {
const byRule = found.get(diagnostic.filename) ?? new Map();
found.set(diagnostic.filename, byRule);
const list = byRule.get(ruleOf(diagnostic)) ?? [];
byRule.set(ruleOf(diagnostic), list);
list.push(diagnostic);
}

const suppressions = JSON.parse(
readFileSync(SUPPRESSIONS_FILE, "utf8"),
) as Suppressions;

function write(next: Suppressions) {
const byKey = ([a]: [string, unknown], [b]: [string, unknown]) =>
a < b ? -1 : 1;
const sorted: Suppressions = {};
for (const [file, rules] of Object.entries(next).sort(byKey)) {
const entries = Object.entries(rules).sort(byKey);
if (entries.length === 0) continue;
sorted[file] = Object.fromEntries(entries);
}
writeFileSync(SUPPRESSIONS_FILE, `${JSON.stringify(sorted, null, 2)}\n`);
}

if (suppressRules.length > 0) {
for (const [file, byRule] of found) {
for (const rule of suppressRules) {
const count = byRule.get(rule)?.length ?? 0;
if (count === 0) continue;
suppressions[file] ??= {};
suppressions[file][rule] = { count };
}
}
write(suppressions);
}

if (prune) {
for (const [file, rules] of Object.entries(suppressions)) {
for (const [rule, { count }] of Object.entries(rules)) {
const actual = found.get(file)?.get(rule)?.length ?? 0;
if (actual === 0) delete rules[rule];
else rules[rule] = { count: Math.min(count, actual) };
}
}
write(suppressions);
}

const annotate = process.env.GITHUB_ACTIONS === "true";
const escape = (text: string) =>
text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");

let failures = 0;
for (const [file, byRule] of found) {
for (const [rule, list] of byRule) {
const allowed = suppressions[file]?.[rule]?.count ?? 0;
if (list.length <= allowed) continue;
for (const diagnostic of list) {
failures++;
const span = diagnostic.labels?.[0]?.span;
const position = span ? `:${span.line}:${span.column}` : "";
console.log(`${file}${position} ${rule} ${diagnostic.message}`);
if (diagnostic.help) console.log(` help: ${diagnostic.help}`);
if (annotate) {
const location = span ? `,line=${span.line},col=${span.column}` : "";
console.log(
`::error file=${file}${location},title=${escape(rule)}::${escape(diagnostic.message)}`,
);
}
}
if (allowed > 0) {
console.log(
` ${file} is recorded with ${allowed} ${rule} violation(s) and now has ${list.length}; fix the new one, do not raise the count.`,
);
}
}
}

let unused = 0;
for (const [file, rules] of Object.entries(suppressions)) {
for (const [rule, { count }] of Object.entries(rules)) {
const actual = found.get(file)?.get(rule)?.length ?? 0;
if (actual >= count) continue;
unused++;
console.log(
`${file} ${rule} recorded ${count}, found ${actual}: run \`bun run lint:prune\` and commit ${SUPPRESSIONS_FILE}`,
);
}
}

const recorded = diagnostics.length - failures;
if (failures > 0 || unused > 0) {
console.log(
`\n${failures} violation(s) and ${unused} outdated suppression(s); ${recorded} known violation(s) are recorded in ${SUPPRESSIONS_FILE}.`,
);
process.exit(1);
}
console.log(
`No new lint violations; ${recorded} known violation(s) are recorded in ${SUPPRESSIONS_FILE}.`,
);
2 changes: 1 addition & 1 deletion src/content/docs/products/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ More than seventy tools, grouped by what you're trying to accomplish. The catalo
| **[Ve33](/products/ve33/)** | Stake, vote, reallocate, extend, split, merge, increase, withdraw, reinvest, and claim fees — plus current allocations and a STONX allocation recommendation. |
| **[Auctions](/reference/contracts/evm-v3/)** | Create an auction, complete it, and collect creator proceeds. |
| **Rewards** | List claimable Ekubo [rewards](/products/rewards/) for an owner and prepare claims, including recovery fund claims; surface boosted-fee, incentive, and projected ve(3,3) opportunities ranked by APR. |
| **Other protocols** | Aave V3, Morpho Vault V2, Sky Savings, Lido, Merkl, and Aerodrome — see [Protocols beyond Ekubo](#protocols-beyond-ekubo). |
| **Other protocols** | Aave V3, Morpho Vault V2, Sky Savings, Lido, Merkl, and Aerodrome — see [Protocols beyond Ekubo](#protocols-beyond-ekubo). |
| **Transfers** | Prepare one ordered plan of 1 to 4,096 native, ERC-20, ERC-721, and ERC-1155 transfers on a chain, mixed freely. |
| **Utilities** | Revoke approvals, wrap and unwrap tokens, boost a pool manually, expand oracle capacity, unwrap old gEKUBO, trigger revenue buybacks, and export the token list to a wallet by reference. |

Expand Down
Loading
Loading