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 picomatch from "picomatch"
|
||||||
import type { RuleMetadata } from "./types"
|
import type { RuleMetadata } from "./types"
|
||||||
|
|
||||||
|
type PathMatcher = (path: string) => boolean
|
||||||
|
|
||||||
export interface MatchResult {
|
export interface MatchResult {
|
||||||
applies: boolean
|
applies: boolean
|
||||||
reason?: string
|
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
|
* 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
|
const relativePath = projectRoot ? relative(projectRoot, currentFilePath) : currentFilePath
|
||||||
|
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
if (picomatch.isMatch(relativePath, pattern, { dot: true, bash: true })) {
|
if (matcherFor(pattern)(relativePath)) {
|
||||||
return { applies: true, reason: `glob: ${pattern}` }
|
return { applies: true, reason: `glob: ${pattern}` }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user