refactor(packages): extract agents-md-core package

This commit is contained in:
YeonGyu-Kim
2026-05-21 02:45:20 +09:00
parent 2748009ff2
commit 7c7aa28160
18 changed files with 276 additions and 101 deletions
+4 -17
View File
@@ -1,17 +1,4 @@
import { findAgentsMdUp as findAgentsMdUpCore } from "@oh-my-opencode/rules-core";
import type { AgentsMdCache } from "@oh-my-opencode/rules-core";
import { isAbsolute, resolve } from "node:path";
export function resolveFilePath(rootDirectory: string, path: string): string | null {
if (!path) return null;
if (isAbsolute(path)) return path;
return resolve(rootDirectory, path);
}
export async function findAgentsMdUp(input: {
readonly startDir: string;
readonly rootDir: string;
readonly cache?: AgentsMdCache;
}): Promise<string[]> {
return findAgentsMdUpCore({ startDir: input.startDir, rootDir: input.rootDir, cache: input.cache });
}
export {
findAgentsMdUp,
resolveFilePath,
} from "@oh-my-opencode/agents-md-core";
+7 -2
View File
@@ -4,7 +4,11 @@ import type { PluginInput } from "@opencode-ai/plugin";
import { createDynamicTruncator } from "../../shared/dynamic-truncator";
import { resolveSessionEventID } from "../../shared/event-session-id";
import { processFilePathForAgentsInjection } from "./injector";
import { clearInjectedPaths } from "./storage";
import {
clearInjectedPaths,
loadInjectedPaths,
saveInjectedPaths,
} from "./storage";
interface ToolExecuteInput {
tool: string;
@@ -44,9 +48,10 @@ export function createDirectoryAgentsInjectorHook(
if (toolName === "read") {
await processFilePathForAgentsInjection({
ctx,
rootDirectory: ctx.directory,
truncator,
sessionCaches,
storage: { loadInjectedPaths, saveInjectedPaths },
agentsMdCache,
filePath: output.title,
sessionID: input.sessionID,
@@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto"
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import type { PluginInput } from "@opencode-ai/plugin"
import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
const storageMaps = new Map<string, Set<string>>()
@@ -35,6 +34,13 @@ const truncator = {
}),
}
const storage = {
loadInjectedPaths: (sessionID: string) => storageMaps.get(sessionID) ?? new Set<string>(),
saveInjectedPaths: (sessionID: string, paths: Set<string>) => {
storageMaps.set(sessionID, paths)
},
}
describe("processFilePathForAgentsInjection", () => {
let testRoot = ""
let srcDirectory = ""
@@ -71,9 +77,10 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches: new Map(),
storage,
filePath: join(srcDirectory, "file.ts"),
sessionID: "session-parent",
output,
@@ -110,9 +117,10 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches: new Map(),
storage,
filePath: join(testRoot, "file.ts"),
sessionID: "session-root-skip",
output,
@@ -130,9 +138,10 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches: new Map(),
storage,
filePath: join(componentsDirectory, "button.ts"),
sessionID: "session-multiple",
output,
@@ -151,18 +160,20 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches,
storage,
filePath: join(componentsDirectory, "button.ts"),
sessionID: "session-cache",
output,
})
const outputAfterFirstCall = output.output
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches,
storage,
filePath: join(componentsDirectory, "button.ts"),
sessionID: "session-cache",
output,
@@ -191,9 +202,10 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator: truncatedTruncator,
sessionCaches: new Map(),
storage,
filePath: join(srcDirectory, "file.ts"),
sessionID: "session-truncated",
output,
@@ -211,9 +223,10 @@ describe("processFilePathForAgentsInjection", () => {
// when
await processFilePathForAgentsInjection({
ctx: { directory: testRoot } as PluginInput,
rootDirectory: testRoot,
truncator,
sessionCaches: new Map(),
storage,
filePath: "",
sessionID: "session-empty-path",
output,
@@ -1,72 +1 @@
import type { PluginInput } from "@opencode-ai/plugin";
import type { AgentsMdCache } from "@oh-my-opencode/rules-core";
import { promises as fsPromises } from "node:fs";
import { dirname } from "node:path";
import type { createDynamicTruncator } from "../../shared/dynamic-truncator";
import { findAgentsMdUp, resolveFilePath } from "./finder";
import { loadInjectedPaths, saveInjectedPaths } from "./storage";
type DynamicTruncator = ReturnType<typeof createDynamicTruncator>;
function getSessionCache(
sessionCaches: Map<string, Set<string>>,
sessionID: string,
): Set<string> {
if (!sessionCaches.has(sessionID)) {
sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
}
const cache = sessionCaches.get(sessionID);
if (cache) return cache;
const loaded = loadInjectedPaths(sessionID);
sessionCaches.set(sessionID, loaded);
return loaded;
}
export async function processFilePathForAgentsInjection(input: {
ctx: PluginInput;
truncator: DynamicTruncator;
sessionCaches: Map<string, Set<string>>;
agentsMdCache?: AgentsMdCache;
filePath: string;
sessionID: string;
output: { title: string; output: string; metadata: unknown };
}): Promise<void> {
// Guard: output.output may be non-string at runtime (e.g. MCP bridge format changes).
// Consistent with the pattern used in tool-output-truncator and other hooks.
if (typeof input.output.output !== "string") return;
const resolved = resolveFilePath(input.ctx.directory, input.filePath);
if (!resolved) return;
const dir = dirname(resolved);
const cache = getSessionCache(input.sessionCaches, input.sessionID);
const agentsPaths = await findAgentsMdUp({
startDir: dir,
rootDir: input.ctx.directory,
cache: input.agentsMdCache,
});
let dirty = false;
for (const agentsPath of agentsPaths) {
const agentsDir = dirname(agentsPath);
if (cache.has(agentsDir)) continue;
const content = await fsPromises.readFile(agentsPath, "utf-8").catch(() => null);
if (content === null) continue;
cache.add(agentsDir);
const { result, truncated } = await input.truncator.truncate(
input.sessionID,
content,
);
const truncationNotice = truncated
? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]`
: "";
input.output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`;
dirty = true;
}
if (dirty) {
saveInjectedPaths(input.sessionID, cache);
}
}
export { processFilePathForAgentsInjection } from "@oh-my-opencode/agents-md-core";