fix(hooks): improve rules injector implementation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -43,33 +43,6 @@ interface ParsedRuleEntry {
|
|||||||
|
|
||||||
const parsedRuleCache = new Map<string, ParsedRuleEntry>();
|
const parsedRuleCache = new Map<string, ParsedRuleEntry>();
|
||||||
|
|
||||||
function getCachedParsedRule(
|
|
||||||
filePath: string,
|
|
||||||
realPath: string
|
|
||||||
): { metadata: RuleMetadata; body: string } {
|
|
||||||
try {
|
|
||||||
const stat = statSync(filePath);
|
|
||||||
const cached = parsedRuleCache.get(realPath);
|
|
||||||
|
|
||||||
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
||||||
return { metadata: cached.metadata, body: cached.body };
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawContent = readFileSync(filePath, "utf-8");
|
|
||||||
const { metadata, body } = parseRuleFrontmatter(rawContent);
|
|
||||||
parsedRuleCache.set(realPath, {
|
|
||||||
mtimeMs: stat.mtimeMs,
|
|
||||||
size: stat.size,
|
|
||||||
metadata,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
return { metadata, body };
|
|
||||||
} catch {
|
|
||||||
const rawContent = readFileSync(filePath, "utf-8");
|
|
||||||
return parseRuleFrontmatter(rawContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveFilePath(
|
function resolveFilePath(
|
||||||
workspaceDirectory: string,
|
workspaceDirectory: string,
|
||||||
path: string
|
path: string
|
||||||
@@ -84,6 +57,14 @@ export function createRuleInjectionProcessor(deps: {
|
|||||||
truncator: DynamicTruncator;
|
truncator: DynamicTruncator;
|
||||||
getSessionCache: (sessionID: string) => SessionInjectedRulesCache;
|
getSessionCache: (sessionID: string) => SessionInjectedRulesCache;
|
||||||
ruleFinderOptions?: FindRuleFilesOptions;
|
ruleFinderOptions?: FindRuleFilesOptions;
|
||||||
|
readFileSync?: typeof readFileSync;
|
||||||
|
statSync?: typeof statSync;
|
||||||
|
homedir?: typeof homedir;
|
||||||
|
shouldApplyRule?: typeof shouldApplyRule;
|
||||||
|
isDuplicateByRealPath?: typeof isDuplicateByRealPath;
|
||||||
|
createContentHash?: typeof createContentHash;
|
||||||
|
isDuplicateByContentHash?: typeof isDuplicateByContentHash;
|
||||||
|
saveInjectedRules?: typeof saveInjectedRules;
|
||||||
}): {
|
}): {
|
||||||
processFilePathForInjection: (
|
processFilePathForInjection: (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@@ -91,7 +72,44 @@ export function createRuleInjectionProcessor(deps: {
|
|||||||
output: ToolExecuteOutput
|
output: ToolExecuteOutput
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
} {
|
} {
|
||||||
const { workspaceDirectory, truncator, getSessionCache, ruleFinderOptions } = deps;
|
const {
|
||||||
|
workspaceDirectory,
|
||||||
|
truncator,
|
||||||
|
getSessionCache,
|
||||||
|
ruleFinderOptions,
|
||||||
|
readFileSync: readRuleFileSync = readFileSync,
|
||||||
|
statSync: statRuleSync = statSync,
|
||||||
|
homedir: getHomeDir = homedir,
|
||||||
|
shouldApplyRule: shouldApplyRuleImpl = shouldApplyRule,
|
||||||
|
isDuplicateByRealPath: isDuplicateByRealPathImpl = isDuplicateByRealPath,
|
||||||
|
createContentHash: createContentHashImpl = createContentHash,
|
||||||
|
isDuplicateByContentHash: isDuplicateByContentHashImpl = isDuplicateByContentHash,
|
||||||
|
saveInjectedRules: saveInjectedRulesImpl = saveInjectedRules,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function getParsedRule(filePath: string, realPath: string): { metadata: RuleMetadata; body: string } {
|
||||||
|
try {
|
||||||
|
const stat = statRuleSync(filePath);
|
||||||
|
const cached = parsedRuleCache.get(realPath);
|
||||||
|
|
||||||
|
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
||||||
|
return { metadata: cached.metadata, body: cached.body };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawContent = readRuleFileSync(filePath, "utf-8");
|
||||||
|
const { metadata, body } = parseRuleFrontmatter(rawContent);
|
||||||
|
parsedRuleCache.set(realPath, {
|
||||||
|
mtimeMs: stat.mtimeMs,
|
||||||
|
size: stat.size,
|
||||||
|
metadata,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
return { metadata, body };
|
||||||
|
} catch {
|
||||||
|
const rawContent = readRuleFileSync(filePath, "utf-8");
|
||||||
|
return parseRuleFrontmatter(rawContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function processFilePathForInjection(
|
async function processFilePathForInjection(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@@ -103,17 +121,17 @@ export function createRuleInjectionProcessor(deps: {
|
|||||||
|
|
||||||
const projectRoot = findProjectRoot(resolved);
|
const projectRoot = findProjectRoot(resolved);
|
||||||
const cache = getSessionCache(sessionID);
|
const cache = getSessionCache(sessionID);
|
||||||
const home = homedir();
|
const home = getHomeDir();
|
||||||
|
|
||||||
const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved, ruleFinderOptions);
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved, ruleFinderOptions);
|
||||||
const toInject: RuleToInject[] = [];
|
const toInject: RuleToInject[] = [];
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
|
|
||||||
for (const candidate of ruleFileCandidates) {
|
for (const candidate of ruleFileCandidates) {
|
||||||
if (isDuplicateByRealPath(candidate.realPath, cache.realPaths)) continue;
|
if (isDuplicateByRealPathImpl(candidate.realPath, cache.realPaths)) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { metadata, body } = getCachedParsedRule(
|
const { metadata, body } = getParsedRule(
|
||||||
candidate.path,
|
candidate.path,
|
||||||
candidate.realPath
|
candidate.realPath
|
||||||
);
|
);
|
||||||
@@ -122,13 +140,13 @@ export function createRuleInjectionProcessor(deps: {
|
|||||||
if (candidate.isSingleFile) {
|
if (candidate.isSingleFile) {
|
||||||
matchReason = "copilot-instructions (always apply)";
|
matchReason = "copilot-instructions (always apply)";
|
||||||
} else {
|
} else {
|
||||||
const matchResult = shouldApplyRule(metadata, resolved, projectRoot);
|
const matchResult = shouldApplyRuleImpl(metadata, resolved, projectRoot);
|
||||||
if (!matchResult.applies) continue;
|
if (!matchResult.applies) continue;
|
||||||
matchReason = matchResult.reason ?? "matched";
|
matchReason = matchResult.reason ?? "matched";
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentHash = createContentHash(body);
|
const contentHash = createContentHashImpl(body);
|
||||||
if (isDuplicateByContentHash(contentHash, cache.contentHashes)) continue;
|
if (isDuplicateByContentHashImpl(contentHash, cache.contentHashes)) continue;
|
||||||
|
|
||||||
const relativePath = projectRoot
|
const relativePath = projectRoot
|
||||||
? relative(projectRoot, candidate.path)
|
? relative(projectRoot, candidate.path)
|
||||||
@@ -163,7 +181,7 @@ export function createRuleInjectionProcessor(deps: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
saveInjectedRules(sessionID, cache);
|
saveInjectedRulesImpl(sessionID, cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user