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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"name": "kensai",
"description": "Multi-agent code review pipeline with adversarial falsification, orchestrated by a stateful MCP server",
"source": "./claude/kensai",
"version": "0.1.3",
"version": "0.1.4",
"license": "MIT",
"keywords": [
"code-review",
Expand Down
27 changes: 11 additions & 16 deletions .github/workflows/kensai-review-mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,35 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
package_json_file: claude/kensai/mcp/review/package.json

- uses: actions/setup-node@v4
version: "latest"
- uses: actions/setup-node@v6
with:
node-version: "26"
cache: pnpm
cache-dependency-path: claude/kensai/mcp/review/pnpm-lock.yaml

- run: pnpm install --frozen-lockfile

- run: pnpm lint

test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: ["lts/*", "26"]
os: ["ubuntu-latest", "windows-latest"]
node-version: ["24", "26"]
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
package_json_file: claude/kensai/mcp/review/package.json

- uses: actions/setup-node@v4
version: "latest"
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
cache-dependency-path: claude/kensai/mcp/review/pnpm-lock.yaml

- run: pnpm install --frozen-lockfile

- run: pnpm test
2 changes: 1 addition & 1 deletion claude/kensai/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kensai",
"version": "0.1.3",
"version": "0.1.4",
"description": "Multi-agent code review pipeline with adversarial falsification, orchestrated by a stateful MCP server",
"author": {
"name": "xobotyi",
Expand Down
2 changes: 1 addition & 1 deletion claude/kensai/mcp/review/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gaijin/kensai-review-mcp",
"version": "0.1.3",
"version": "0.1.4",
"description": "Stateful MCP server for the kensai multi-agent code review pipeline",
"license": "MIT",
"author": {
Expand Down
7 changes: 4 additions & 3 deletions claude/kensai/mcp/review/src/repofs/pathindex/pathindex.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createReadStream } from "node:fs";
import fs from "node:fs/promises";
import nativePath from "node:path";
import path from "node:path/posix";

import fuzzysort from "fuzzysort";
Expand Down Expand Up @@ -58,7 +59,7 @@ export class PathIndex {
readonly #fdPool = new Pool(256);

private constructor(rootPath: string) {
this.absRoot = path.resolve(rootPath);
this.absRoot = nativePath.resolve(rootPath);
}

/** Paths ending with "/" are directories; without are files. */
Expand Down Expand Up @@ -162,13 +163,13 @@ export class PathIndex {
async #walkDir(dir: string, signal?: AbortSignal): Promise<IndexEntryDir> {
signal?.throwIfAborted();

const dirEntries = await fs.readdir(path.resolve(this.absRoot, dir), { withFileTypes: true });
const dirEntries = await fs.readdir(nativePath.resolve(this.absRoot, dir), { withFileTypes: true });
const dirEntry: IndexEntryDir = { type: "dir", name: path.basename(dir), children: [] };
const work: Promise<void>[] = [];

for (const dirent of dirEntries) {
const relPath = path.join(dir, dirent.name);
const absPath = path.join(this.absRoot, relPath);
const absPath = nativePath.join(this.absRoot, relPath);

if (dirent.isSymbolicLink()) {
work.push(
Expand Down
10 changes: 9 additions & 1 deletion claude/kensai/mcp/review/src/repofs/repofs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ beforeAll(async () => {
await writeFile(join(rootDir, "empty.txt"), "");
await writeFile(join(rootDir, "image.png"), Buffer.from([0x89, 0x50, 0x4e, 0x47]));

await symlink("/tmp", join(rootDir, "escape-link"));
await symlink(tmpdir(), join(rootDir, "escape-link"));
await symlink(join(rootDir, "src/main.ts"), join(rootDir, "link-to-file"));

await git(["init"], rootDir);
Expand Down Expand Up @@ -74,6 +74,14 @@ describe("resolve", () => {
expect(rfs.resolve("src/../README.md")).toBe("README.md");
});

it("normalizes backslash separators", () => {
expect(rfs.resolve("src\\main.ts")).toBe("src/main.ts");
});

it("normalizes mixed separators", () => {
expect(rfs.resolve("src\\nested/deep.ts")).toBe("src/nested/deep.ts");
});

it("accepts absolute path inside root", () => {
expect(rfs.resolve(join(resolve(rootDir), "src/main.ts"))).toBe("src/main.ts");
});
Expand Down
2 changes: 1 addition & 1 deletion claude/kensai/mcp/review/src/repofs/repofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class RepoFs {
/** Normalizes any path (absolute or relative) to POSIX relative from root. Throws on escape. */
resolve(p: string): string {
const abs = path.resolve(this.#root, path.posix.normalize(p.replaceAll("\\", "/")));
const rel = path.relative(this.#root, abs);
const rel = path.relative(this.#root, abs).replaceAll("\\", "/");

if (rel.startsWith("..")) {
throw new RepoFsError(`path escapes root: ${p}`);
Expand Down
Loading