perf(rules-injector): cache compiled glob matchers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/// <reference path="../../../bun-test.d.ts" />
|
||||
|
||||
import { beforeEach, describe, expect, it } from "bun:test"
|
||||
import {
|
||||
createContentHash,
|
||||
getMatcherCacheStats,
|
||||
isDuplicateByContentHash,
|
||||
isDuplicateByRealPath,
|
||||
resetMatcherCache,
|
||||
shouldApplyRule,
|
||||
} from "./matcher"
|
||||
|
||||
describe("shouldApplyRule", () => {
|
||||
beforeEach(() => {
|
||||
resetMatcherCache()
|
||||
})
|
||||
|
||||
it("#given repeated glob metadata #when matching many files #then compiles each pattern once", () => {
|
||||
// given
|
||||
const metadata = { globs: ["src/**/*.ts", "test/**/*.ts"] }
|
||||
const projectRoot = "/workspace/project"
|
||||
|
||||
// when
|
||||
for (let index = 0; index < 20; index += 1) {
|
||||
shouldApplyRule(metadata, `${projectRoot}/src/file-${index}.ts`, projectRoot)
|
||||
shouldApplyRule(metadata, `${projectRoot}/test/file-${index}.ts`, projectRoot)
|
||||
}
|
||||
|
||||
// then
|
||||
expect(getMatcherCacheStats()).toEqual({ entries: 2 })
|
||||
})
|
||||
|
||||
it("#given matching glob #when path is under project root #then returns matching reason", () => {
|
||||
// given / when
|
||||
const result = shouldApplyRule({ globs: "src/**/*.ts" }, "/workspace/project/src/index.ts", "/workspace/project")
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ applies: true, reason: "glob: src/**/*.ts" })
|
||||
})
|
||||
|
||||
it("#given always apply metadata #when no globs exist #then applies without compiling matchers", () => {
|
||||
// given / when
|
||||
const result = shouldApplyRule({ alwaysApply: true }, "/workspace/project/src/index.ts", "/workspace/project")
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ applies: true, reason: "alwaysApply" })
|
||||
expect(getMatcherCacheStats()).toEqual({ entries: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
describe("rule duplicate helpers", () => {
|
||||
it("#given real path cache #when path exists #then reports duplicate", () => {
|
||||
// given
|
||||
const cache = new Set(["/workspace/project/AGENTS.md"])
|
||||
|
||||
// when / then
|
||||
expect(isDuplicateByRealPath("/workspace/project/AGENTS.md", cache)).toBe(true)
|
||||
expect(isDuplicateByRealPath("/workspace/project/src/AGENTS.md", cache)).toBe(false)
|
||||
})
|
||||
|
||||
it("#given content #when hashing #then duplicate helper uses truncated hash", () => {
|
||||
// given
|
||||
const hash = createContentHash("rule-content")
|
||||
const cache = new Set([hash])
|
||||
|
||||
// when / then
|
||||
expect(hash).toHaveLength(16)
|
||||
expect(isDuplicateByContentHash(hash, cache)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -3,11 +3,37 @@ import { relative } from "node:path"
|
||||
import picomatch from "picomatch"
|
||||
import type { RuleMetadata } from "./types"
|
||||
|
||||
type PathMatcher = (path: string) => boolean
|
||||
|
||||
export interface MatchResult {
|
||||
applies: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface MatcherCacheStats {
|
||||
entries: number
|
||||
}
|
||||
|
||||
const PICOMATCH_OPTIONS = { dot: true, bash: true } as const
|
||||
const matcherCache = new Map<string, PathMatcher>()
|
||||
|
||||
function matcherFor(pattern: string): PathMatcher {
|
||||
const cached = matcherCache.get(pattern)
|
||||
if (cached) return cached
|
||||
|
||||
const matcher = picomatch(pattern, PICOMATCH_OPTIONS)
|
||||
matcherCache.set(pattern, matcher)
|
||||
return matcher
|
||||
}
|
||||
|
||||
export function resetMatcherCache(): void {
|
||||
matcherCache.clear()
|
||||
}
|
||||
|
||||
export function getMatcherCacheStats(): MatcherCacheStats {
|
||||
return { entries: matcherCache.size }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a rule should apply to the current file based on metadata
|
||||
*/
|
||||
@@ -33,7 +59,7 @@ export function shouldApplyRule(
|
||||
const relativePath = projectRoot ? relative(projectRoot, currentFilePath) : currentFilePath
|
||||
|
||||
for (const pattern of patterns) {
|
||||
if (picomatch.isMatch(relativePath, pattern, { dot: true, bash: true })) {
|
||||
if (matcherFor(pattern)(relativePath)) {
|
||||
return { applies: true, reason: `glob: ${pattern}` }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user