refactor(rules-engine): centralize rule constants and AGENTS.md walk-up
Promote the project-rule constants (PROJECT_MARKERS, PROJECT_RULE_SUBDIRS, PROJECT_RULE_FILES, OPENCODE_USER_RULE_DIRS, USER_RULE_DIR, GITHUB_INSTRUCTIONS_PATTERN, RULE_EXTENSIONS, AGENTS_FILENAME, etc.) and the findAgentsMdUp walk-up helper out of the agents-md-core and rules-injector adapters and into @oh-my-opencode/rules-engine, the single owner of rule discovery. - packages/agents-md-core/ drops the findAgentsMdUp/AgentsMdDiscoveryInput wrappers (now sourced directly from rules-engine) and its constants module re-exports AGENTS_FILENAME from rules-engine instead of duplicating it. - src/hooks/directory-agents-injector/finder.ts pulls findAgentsMdUp from rules-engine directly while still re-exporting resolveFilePath from agents-md-core. - src/hooks/rules-injector/constants.ts becomes a pure re-export shim over the rules-engine constants. Add packages/agents-md-core/src/injector.test.ts to lock the root-skipping AGENTS.md injection order so future changes to findAgentsMdUp cannot silently regress the [Directory Context: ...] block format the injector emits. Tests: bun test packages/agents-md-core packages/rules-engine src/hooks/directory-agents-injector src/hooks/rules-injector
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
export const AGENTS_FILENAME = "AGENTS.md";
|
export { AGENTS_FILENAME } from "../../rules-engine/src/constants";
|
||||||
|
|
||||||
export const TRUNCATION_NOTICE_PREFIX =
|
export const TRUNCATION_NOTICE_PREFIX =
|
||||||
"\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ";
|
"\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ";
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import {
|
|
||||||
findAgentsMdUp as findAgentsMdUpCore,
|
|
||||||
} from "@oh-my-opencode/rules-engine";
|
|
||||||
import { isAbsolute, resolve } from "node:path";
|
import { isAbsolute, resolve } from "node:path";
|
||||||
|
|
||||||
import type { AgentsMdDiscoveryInput } from "./types";
|
|
||||||
|
|
||||||
export function resolveFilePath(rootDirectory: string, path: string): string | null {
|
export function resolveFilePath(rootDirectory: string, path: string): string | null {
|
||||||
if (!path) return null;
|
if (!path) return null;
|
||||||
if (isAbsolute(path)) return path;
|
if (isAbsolute(path)) return path;
|
||||||
return resolve(rootDirectory, path);
|
return resolve(rootDirectory, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findAgentsMdUp(input: AgentsMdDiscoveryInput): Promise<string[]> {
|
|
||||||
return findAgentsMdUpCore({
|
|
||||||
startDir: input.startDir,
|
|
||||||
rootDir: input.rootDir,
|
|
||||||
cache: input.cache,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
export { AGENTS_FILENAME } from "./constants";
|
export { AGENTS_FILENAME } from "./constants";
|
||||||
export { findAgentsMdUp, resolveFilePath } from "./finder";
|
export { resolveFilePath } from "./finder";
|
||||||
export { formatAgentsMdContextBlock } from "./formatter";
|
export { formatAgentsMdContextBlock } from "./formatter";
|
||||||
export { getSessionCache } from "./injection-cache";
|
export { getSessionCache } from "./injection-cache";
|
||||||
export { processFilePathForAgentsInjection } from "./injector";
|
export { processFilePathForAgentsInjection } from "./injector";
|
||||||
export type {
|
export type {
|
||||||
AgentsMdContextOutput,
|
AgentsMdContextOutput,
|
||||||
AgentsMdDiscoveryInput,
|
|
||||||
AgentsMdInjectedPathsStorage,
|
AgentsMdInjectedPathsStorage,
|
||||||
AgentsMdTruncator,
|
AgentsMdTruncator,
|
||||||
TruncationResult,
|
TruncationResult,
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { afterEach, describe, expect, it } from "bun:test";
|
||||||
|
|
||||||
|
import { processFilePathForAgentsInjection } from "./injector";
|
||||||
|
|
||||||
|
describe("processFilePathForAgentsInjection", () => {
|
||||||
|
const sessionCaches = new Map<string, Set<string>>();
|
||||||
|
const storageBackfill = new Map<string, Set<string>>();
|
||||||
|
const storage = {
|
||||||
|
loadInjectedPaths: (sessionID: string): Set<string> =>
|
||||||
|
storageBackfill.get(sessionID) ?? new Set<string>(),
|
||||||
|
saveInjectedPaths: (sessionID: string, paths: Set<string>): void => {
|
||||||
|
storageBackfill.set(sessionID, paths);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const truncator = {
|
||||||
|
truncate: async (_sessionID: string, content: string) => ({
|
||||||
|
result: content,
|
||||||
|
truncated: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rootDirectory = "";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (rootDirectory) {
|
||||||
|
rmSync(rootDirectory, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
rootDirectory = "";
|
||||||
|
sessionCaches.clear();
|
||||||
|
storageBackfill.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("injects AGENTS.md chain in root-skipping order with unchanged context format", async () => {
|
||||||
|
// given
|
||||||
|
rootDirectory = join(tmpdir(), `agents-md-core-injector-${randomUUID()}`);
|
||||||
|
const srcDirectory = join(rootDirectory, "src");
|
||||||
|
const nestedDirectory = join(srcDirectory, "components");
|
||||||
|
mkdirSync(nestedDirectory, { recursive: true });
|
||||||
|
|
||||||
|
const rootAgents = "# root";
|
||||||
|
const srcAgents = "# src";
|
||||||
|
const nestedAgents = "# nested";
|
||||||
|
|
||||||
|
writeFileSync(join(rootDirectory, "AGENTS.md"), rootAgents);
|
||||||
|
writeFileSync(join(srcDirectory, "AGENTS.md"), srcAgents);
|
||||||
|
writeFileSync(join(nestedDirectory, "AGENTS.md"), nestedAgents);
|
||||||
|
writeFileSync(join(nestedDirectory, "button.ts"), "export const button = true;\n");
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
title: "read result",
|
||||||
|
output: "base output",
|
||||||
|
metadata: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const srcAgentsPath = join(srcDirectory, "AGENTS.md");
|
||||||
|
const nestedAgentsPath = join(nestedDirectory, "AGENTS.md");
|
||||||
|
const expectedOutput =
|
||||||
|
"base output" +
|
||||||
|
`\n\n[Directory Context: ${srcAgentsPath}]\n${srcAgents}` +
|
||||||
|
`\n\n[Directory Context: ${nestedAgentsPath}]\n${nestedAgents}`;
|
||||||
|
|
||||||
|
// when
|
||||||
|
await processFilePathForAgentsInjection({
|
||||||
|
rootDirectory,
|
||||||
|
truncator,
|
||||||
|
sessionCaches,
|
||||||
|
storage,
|
||||||
|
filePath: join(nestedDirectory, "button.ts"),
|
||||||
|
sessionID: "session-regression",
|
||||||
|
output,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(output.output).toBe(expectedOutput);
|
||||||
|
expect(output.output).not.toContain(rootAgents);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { AgentsMdCache } from "@oh-my-opencode/rules-engine";
|
import {
|
||||||
|
findAgentsMdUp,
|
||||||
|
type AgentsMdCache,
|
||||||
|
type FindAgentsMdUpInput,
|
||||||
|
} from "@oh-my-opencode/rules-engine";
|
||||||
import { promises as fsPromises } from "node:fs";
|
import { promises as fsPromises } from "node:fs";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
|
|
||||||
import { findAgentsMdUp, resolveFilePath } from "./finder";
|
import { resolveFilePath } from "./finder";
|
||||||
import { formatAgentsMdContextBlock } from "./formatter";
|
import { formatAgentsMdContextBlock } from "./formatter";
|
||||||
import { getSessionCache } from "./injection-cache";
|
import { getSessionCache } from "./injection-cache";
|
||||||
import type {
|
import type {
|
||||||
@@ -33,11 +37,12 @@ export async function processFilePathForAgentsInjection(input: {
|
|||||||
storage: input.storage,
|
storage: input.storage,
|
||||||
});
|
});
|
||||||
|
|
||||||
const agentsPaths = await findAgentsMdUp({
|
const agentsMdDiscoveryInput: FindAgentsMdUpInput = {
|
||||||
startDir: dir,
|
startDir: dir,
|
||||||
rootDir: input.rootDirectory,
|
rootDir: input.rootDirectory,
|
||||||
cache: input.agentsMdCache,
|
cache: input.agentsMdCache,
|
||||||
});
|
};
|
||||||
|
const agentsPaths = await findAgentsMdUp(agentsMdDiscoveryInput);
|
||||||
|
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
for (const agentsPath of agentsPaths) {
|
for (const agentsPath of agentsPaths) {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { AgentsMdCache } from "@oh-my-opencode/rules-engine";
|
|
||||||
|
|
||||||
export interface TruncationResult {
|
export interface TruncationResult {
|
||||||
readonly result: string;
|
readonly result: string;
|
||||||
readonly truncated: boolean;
|
readonly truncated: boolean;
|
||||||
@@ -19,9 +17,3 @@ export interface AgentsMdInjectedPathsStorage {
|
|||||||
loadInjectedPaths(sessionID: string): Set<string>;
|
loadInjectedPaths(sessionID: string): Set<string>;
|
||||||
saveInjectedPaths(sessionID: string, paths: Set<string>): void;
|
saveInjectedPaths(sessionID: string, paths: Set<string>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentsMdDiscoveryInput {
|
|
||||||
readonly startDir: string;
|
|
||||||
readonly rootDir: string;
|
|
||||||
readonly cache?: AgentsMdCache;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ export { shouldApplyRule, createContentHash, isDuplicateByContentHash, isDuplica
|
|||||||
export { findProjectRoot, clearProjectRootCache } from "./project-root";
|
export { findProjectRoot, clearProjectRootCache } from "./project-root";
|
||||||
export { calculateDistance } from "./distance";
|
export { calculateDistance } from "./distance";
|
||||||
export { findRuleFilesRecursive, safeRealpathSync } from "./scanner";
|
export { findRuleFilesRecursive, safeRealpathSync } from "./scanner";
|
||||||
|
export {
|
||||||
|
AGENTS_FILENAME,
|
||||||
|
EXCLUDED_DIRS,
|
||||||
|
GITHUB_INSTRUCTIONS_PATTERN,
|
||||||
|
GLOBAL_DISTANCE,
|
||||||
|
OPENCODE_USER_RULE_DIRS,
|
||||||
|
PROJECT_MARKERS,
|
||||||
|
PROJECT_RULE_FILES,
|
||||||
|
PROJECT_RULE_SUBDIRS,
|
||||||
|
RULE_EXTENSIONS,
|
||||||
|
SOURCE_PRIORITY,
|
||||||
|
USER_RULE_DIR,
|
||||||
|
} from "./constants";
|
||||||
export type {
|
export type {
|
||||||
AgentsMdCache,
|
AgentsMdCache,
|
||||||
DirectoryScanEntry,
|
DirectoryScanEntry,
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
export {
|
export { resolveFilePath } from "@oh-my-opencode/agents-md-core";
|
||||||
findAgentsMdUp,
|
export { findAgentsMdUp } from "@oh-my-opencode/rules-engine";
|
||||||
resolveFilePath,
|
|
||||||
} from "@oh-my-opencode/agents-md-core";
|
|
||||||
|
|||||||
@@ -2,30 +2,12 @@ import { join } from "node:path";
|
|||||||
import { OPENCODE_STORAGE } from "../../shared";
|
import { OPENCODE_STORAGE } from "../../shared";
|
||||||
export const RULES_INJECTOR_STORAGE = join(OPENCODE_STORAGE, "rules-injector");
|
export const RULES_INJECTOR_STORAGE = join(OPENCODE_STORAGE, "rules-injector");
|
||||||
|
|
||||||
export const PROJECT_MARKERS = [
|
export {
|
||||||
".git",
|
GITHUB_INSTRUCTIONS_PATTERN,
|
||||||
"pyproject.toml",
|
OPENCODE_USER_RULE_DIRS,
|
||||||
"package.json",
|
PROJECT_MARKERS,
|
||||||
"Cargo.toml",
|
PROJECT_RULE_FILES,
|
||||||
"go.mod",
|
PROJECT_RULE_SUBDIRS,
|
||||||
".venv",
|
RULE_EXTENSIONS,
|
||||||
];
|
USER_RULE_DIR,
|
||||||
|
} from "@oh-my-opencode/rules-engine";
|
||||||
export const PROJECT_RULE_SUBDIRS: [string, string][] = [
|
|
||||||
[".github", "instructions"],
|
|
||||||
[".cursor", "rules"],
|
|
||||||
[".claude", "rules"],
|
|
||||||
[".omo", "rules"],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const PROJECT_RULE_FILES: string[] = [
|
|
||||||
".github/copilot-instructions.md",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const GITHUB_INSTRUCTIONS_PATTERN = /\.instructions\.md$/;
|
|
||||||
|
|
||||||
export const USER_RULE_DIR = ".claude/rules";
|
|
||||||
|
|
||||||
export const OPENCODE_USER_RULE_DIRS = [".omo/rules", ".opencode/rules"];
|
|
||||||
|
|
||||||
export const RULE_EXTENSIONS = [".md", ".mdc"];
|
|
||||||
|
|||||||
Reference in New Issue
Block a user