diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96d29ba..89e729e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,26 @@ concurrency: cancel-in-progress: true jobs: + lint: + name: Lint (Biome) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run Biome lint + run: npm run lint + validate: name: Validate on Node ${{ matrix.node-version }} runs-on: ubuntu-latest diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..c5df6d9 --- /dev/null +++ b/biome.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json", + "vcs": { "enabled": false }, + "formatter": { "enabled": false }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noImplicitAnyLet": "off", + "noAssignInExpressions": "off", + "noDuplicateProperties": "off", + "noDuplicateCustomProperties": "off" + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 0f2c492..0eb78ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "opencode-goal-mode-install": "scripts/install.mjs" }, "devDependencies": { + "@biomejs/biome": "2.5.0", "@opencode-ai/plugin": "1.17.6", "fast-check": "^4.8.0", "yaml": "^2.6.1" @@ -41,6 +42,181 @@ } } }, + "node_modules/@biomejs/biome": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.0.tgz", + "integrity": "sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.0", + "@biomejs/cli-darwin-x64": "2.5.0", + "@biomejs/cli-linux-arm64": "2.5.0", + "@biomejs/cli-linux-arm64-musl": "2.5.0", + "@biomejs/cli-linux-x64": "2.5.0", + "@biomejs/cli-linux-x64-musl": "2.5.0", + "@biomejs/cli-win32-arm64": "2.5.0", + "@biomejs/cli-win32-x64": "2.5.0" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-Mn3Fwi3SA5fgmfCPqmzpWF2DLZnms3BVAhM088nTnGrTZmHS3wwIjcoZPqpXeNgd3DrrLH6xp8vTLIBuJoZiXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-rg3VPL5P8mYro6pqlXYXuJWph21slVp3SZtAqWSrkZs40d2gTzYmHF8E/X1iTID25btmNKltNDJ926sqVBp7DQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.0.tgz", + "integrity": "sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-vQdM4oSGaf7ZNeGO9w5+Y8SBtyser9M6znxYbm7Ec8wInxJu1WiKxFYZW5Auj2d80bcVvefuGGRxoFOE0eee8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.0.tgz", + "integrity": "sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.0.tgz", + "integrity": "sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.0.tgz", + "integrity": "sha512-VT/lF+GId+67j8aDfLkxdxNoVApsPSTbyAtB3jJq0IWTrY77WXfbPfpngxq0bA6JCEv/7k8C9qWjDRKRznDlyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", diff --git a/package.json b/package.json index b03ef86..5db6ee3 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ "bench:truthfulness": "node benchmarks/truthfulness.mjs", "bench:compare": "node benchmarks/comparison.mjs", "pack:check": "npm pack --dry-run", + "lint": "biome lint plugins/ scripts/ benchmarks/ tests/ tools/", + "lint:fix": "biome lint --write plugins/ scripts/ benchmarks/ tests/ tools/", "audit": "npm audit --audit-level=moderate", "publish:check": "node scripts/check-npm-publish-ready.mjs", "validate": "npm test && node scripts/validate-opencode-config.mjs && npm run publish:check -- --skip-registry && npm run pack:check", @@ -94,6 +96,7 @@ "@opencode-ai/sdk": "1.17.6" }, "devDependencies": { + "@biomejs/biome": "2.5.0", "@opencode-ai/plugin": "1.17.6", "fast-check": "^4.8.0", "yaml": "^2.6.1" diff --git a/plugins/goal-guard/shell.js b/plugins/goal-guard/shell.js index 98852a3..c412309 100644 --- a/plugins/goal-guard/shell.js +++ b/plugins/goal-guard/shell.js @@ -480,7 +480,6 @@ function structure(tokens) { } else { redirects.push({ op: t.value, target: "" }); } - continue; } } flushPipeline(); diff --git a/tests/plugin.test.mjs b/tests/plugin.test.mjs index 7c6a119..a38b9b7 100644 --- a/tests/plugin.test.mjs +++ b/tests/plugin.test.mjs @@ -800,7 +800,7 @@ test("goal_status returns structured status", async () => { const tools = createGoalTools({ store: guard.store, config: guard.config, persist: guard.persist }); await guard.hooks["chat.params"]({ sessionID: "stx", agent: "goal" }, {}); const res = await tools.goal_status.execute({}, { sessionID: "stx" }); - let report = JSON.parse(res.output); + const report = JSON.parse(res.output); assert.equal(report.active, true); assert.ok(Array.isArray(report.requiredGates)); assert.equal(report.reviewerMemory.open.length, 0); diff --git a/tests/review-runner.test.mjs b/tests/review-runner.test.mjs index aead469..b921c31 100644 --- a/tests/review-runner.test.mjs +++ b/tests/review-runner.test.mjs @@ -113,7 +113,7 @@ test("the cycle: FAIL → fix(edit) → re-review PASS counts 2 review cycles", const store = createStore(); const sid = "g3"; const state = goalState(store, sid); - let r1 = await runReviewCycle(mockReviewClient((a) => (a === "goal-final-auditor" ? "FAIL" : "PASS"), sid), store, state, DEFAULT_CONFIG, fastOpts(sid)); + const r1 = await runReviewCycle(mockReviewClient((a) => (a === "goal-final-auditor" ? "FAIL" : "PASS"), sid), store, state, DEFAULT_CONFIG, fastOpts(sid)); assert.equal(r1.completionAllowed, false); assert.equal(state.reviewCycles, 1); markEdit(store, state, "fix after review"); diff --git a/tools/lab/server/server.mjs b/tools/lab/server/server.mjs index b460843..dff1154 100644 --- a/tools/lab/server/server.mjs +++ b/tools/lab/server/server.mjs @@ -117,7 +117,7 @@ function buildGraph(store, runId) { export function createApiServer(store, orchestrator) { async function serveStatic(req, res, pathname) { - let rel = pathname === "/" ? "/index.html" : pathname; + const rel = pathname === "/" ? "/index.html" : pathname; const full = normalize(join(WEB_ROOT, rel)); if (!full.startsWith(WEB_ROOT)) { res.writeHead(403); diff --git a/tools/lab/web/js/diagrams.js b/tools/lab/web/js/diagrams.js index c379ac6..96a5434 100644 --- a/tools/lab/web/js/diagrams.js +++ b/tools/lab/web/js/diagrams.js @@ -170,7 +170,9 @@ export function orchestrationGraph(graph) { el.insertBefore(s("path", { class: "edge", d: `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`, "stroke-width": Math.min(3, 0.6 + e.count * 0.4) }), el.firstChild); } // column captions - ["GOAL", "SUBAGENTS", "TOOLS"].forEach((t, i) => el.append(s("text", { class: "lbl", x: colX[i], y: 12, "font-size": 9 }, t))); + ["GOAL", "SUBAGENTS", "TOOLS"].forEach((t, i) => { + el.append(s("text", { class: "lbl", x: colX[i], y: 12, "font-size": 9 }, t)); + }); return el; } diff --git a/tools/lab/web/js/views/agent.js b/tools/lab/web/js/views/agent.js index 7244249..51e363c 100644 --- a/tools/lab/web/js/views/agent.js +++ b/tools/lab/web/js/views/agent.js @@ -159,7 +159,7 @@ export function AgentView(navigate, runId) { list.append(h("div.eh-head", {}, h("span", { text: "#" }), h("span", { style: { textAlign: "right" }, text: "offset" }), h("span", { text: "time" }), h("span", { text: "type" }), h("span", { text: "detail" }))); const shown = events.filter(matchFilter); if (!shown.length) list.append(empty("no events match this filter")); - let atBottom = false; + const atBottom = false; for (const e of shown) { const off = t0 ? "+" + ((e.ts - t0) / 1000).toFixed(1) + "s" : ""; const isOpen = openRows.has(e.seq);