Improve agent autonomy and diff view readability
Agent behavior improvements: - Add project context detection (tsconfig.json, pom.xml, etc.) - Enforce validation after changes (tsc --noEmit, mvn compile, etc.) - Run tests automatically - never ask "do you want me to run tests" - Complete full loop: create → type-check → test → confirm - Add command detection for direct execution (run tree, run ls) Diff view improvements: - Use darker backgrounds for added/removed lines - Add diffLineBgAdded, diffLineBgRemoved, diffLineText theme colors - Improve text visibility with white text on dark backgrounds - Update both React/Ink and SolidJS diff components Streaming fixes: - Fix tool call argument accumulation using OpenAI index field - Fix streaming content display after tool calls - Add consecutive error tracking to prevent token waste Other changes: - ESC to abort operations, Ctrl+C to exit - Fix model selection when provider changes in cascade mode - Add debug logging for troubleshooting - Move tests to root tests/ folder - Fix banner test GRADIENT_COLORS reference
This commit is contained in:
158
test/path-matcher.test.ts
Normal file
158
test/path-matcher.test.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Unit tests for Path Pattern Matcher
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "bun:test";
|
||||
|
||||
import {
|
||||
matchesPathPattern,
|
||||
matchesFilePattern,
|
||||
isFileOpAllowedByIndex,
|
||||
findMatchingFilePatterns,
|
||||
generateFilePattern,
|
||||
normalizePath,
|
||||
isPathInDirectory,
|
||||
} from "@services/permissions/matchers/path";
|
||||
import { buildPatternIndex } from "@services/permissions/pattern-index";
|
||||
import type { PermissionPattern } from "@/types/permissions";
|
||||
|
||||
describe("Path Pattern Matcher", () => {
|
||||
describe("matchesPathPattern", () => {
|
||||
it("should match wildcard pattern", () => {
|
||||
expect(matchesPathPattern("/any/path/file.ts", "*")).toBe(true);
|
||||
expect(matchesPathPattern("relative/file.js", "*")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match directory prefix pattern", () => {
|
||||
expect(matchesPathPattern("src/file.ts", "src/*")).toBe(true);
|
||||
expect(matchesPathPattern("src/nested/file.ts", "src/*")).toBe(true);
|
||||
expect(matchesPathPattern("tests/file.ts", "src/*")).toBe(false);
|
||||
});
|
||||
|
||||
it("should match extension pattern", () => {
|
||||
expect(matchesPathPattern("file.ts", "*.ts")).toBe(true);
|
||||
expect(matchesPathPattern("src/nested/file.ts", "*.ts")).toBe(true);
|
||||
expect(matchesPathPattern("file.js", "*.ts")).toBe(false);
|
||||
});
|
||||
|
||||
it("should match exact path", () => {
|
||||
expect(matchesPathPattern("src/file.ts", "src/file.ts")).toBe(true);
|
||||
expect(matchesPathPattern("src/other.ts", "src/file.ts")).toBe(false);
|
||||
});
|
||||
|
||||
it("should match substring", () => {
|
||||
expect(
|
||||
matchesPathPattern("/path/to/config/settings.json", "config"),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("matchesFilePattern", () => {
|
||||
it("should match with parsed pattern", () => {
|
||||
const pattern: PermissionPattern = {
|
||||
tool: "Read",
|
||||
path: "*.ts",
|
||||
};
|
||||
|
||||
expect(matchesFilePattern("file.ts", pattern)).toBe(true);
|
||||
expect(matchesFilePattern("file.js", pattern)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for pattern without path", () => {
|
||||
const pattern: PermissionPattern = {
|
||||
tool: "Bash",
|
||||
command: "git",
|
||||
};
|
||||
|
||||
expect(matchesFilePattern("file.ts", pattern)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isFileOpAllowedByIndex", () => {
|
||||
it("should check Read operations", () => {
|
||||
const index = buildPatternIndex(["Read(*.ts)", "Read(src/*)"]);
|
||||
|
||||
expect(isFileOpAllowedByIndex("Read", "file.ts", index)).toBe(true);
|
||||
expect(isFileOpAllowedByIndex("Read", "src/nested.js", index)).toBe(true);
|
||||
expect(isFileOpAllowedByIndex("Read", "tests/file.js", index)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("should check Write operations separately", () => {
|
||||
const index = buildPatternIndex(["Read(*)", "Write(src/*)"]);
|
||||
|
||||
expect(isFileOpAllowedByIndex("Read", "any/file.ts", index)).toBe(true);
|
||||
expect(isFileOpAllowedByIndex("Write", "any/file.ts", index)).toBe(false);
|
||||
expect(isFileOpAllowedByIndex("Write", "src/file.ts", index)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for empty index", () => {
|
||||
const index = buildPatternIndex([]);
|
||||
|
||||
expect(isFileOpAllowedByIndex("Read", "file.ts", index)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findMatchingFilePatterns", () => {
|
||||
it("should find all matching patterns", () => {
|
||||
const index = buildPatternIndex(["Read(*)", "Read(*.ts)", "Read(src/*)"]);
|
||||
|
||||
const matches = findMatchingFilePatterns("Read", "src/file.ts", index);
|
||||
|
||||
expect(matches.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should return empty for no matches", () => {
|
||||
const index = buildPatternIndex(["Read(src/*)"]);
|
||||
|
||||
const matches = findMatchingFilePatterns("Read", "tests/file.ts", index);
|
||||
|
||||
expect(matches).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateFilePattern", () => {
|
||||
it("should generate extension-based pattern for common extensions", () => {
|
||||
expect(generateFilePattern("Read", "file.ts")).toBe("Read(*.ts)");
|
||||
expect(generateFilePattern("Write", "file.json")).toBe("Write(*.json)");
|
||||
expect(generateFilePattern("Edit", "file.tsx")).toBe("Edit(*.tsx)");
|
||||
});
|
||||
|
||||
it("should generate directory-based pattern when appropriate", () => {
|
||||
expect(generateFilePattern("Read", "src/file.xyz")).toBe("Read(src/*)");
|
||||
});
|
||||
|
||||
it("should fall back to basename", () => {
|
||||
expect(generateFilePattern("Read", "Makefile")).toBe("Read(Makefile)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizePath", () => {
|
||||
it("should normalize path separators", () => {
|
||||
expect(normalizePath("src/file.ts")).toBe("src/file.ts");
|
||||
expect(normalizePath("src//file.ts")).toBe("src/file.ts");
|
||||
expect(normalizePath("./src/file.ts")).toBe("src/file.ts");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPathInDirectory", () => {
|
||||
it("should check if path is in directory", () => {
|
||||
expect(isPathInDirectory("/project/src/file.ts", "/project/src")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isPathInDirectory("/project/src/nested/file.ts", "/project/src"),
|
||||
).toBe(true);
|
||||
expect(isPathInDirectory("/project/tests/file.ts", "/project/src")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("should not match partial directory names", () => {
|
||||
expect(
|
||||
isPathInDirectory("/project/src-backup/file.ts", "/project/src"),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user