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
+1
View File
@@ -0,0 +1 @@
export * from "./src/index";
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@oh-my-opencode/agents-md-core",
"version": "0.1.0",
"type": "module",
"private": true,
"description": "Pure TypeScript AGENTS.md discovery and injection core for oh-my-opencode.",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./src/index.ts"
}
},
"types": "./index.d.ts",
"scripts": {
"typecheck": "tsgo --noEmit -p tsconfig.json",
"test": "bun test src/*.test.ts"
},
"dependencies": {
"@oh-my-opencode/rules-core": "workspace:*",
"@oh-my-opencode/utils": "workspace:*"
}
}
+6
View File
@@ -0,0 +1,6 @@
export const AGENTS_FILENAME = "AGENTS.md";
export const TRUNCATION_NOTICE_PREFIX =
"\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ";
export const TRUNCATION_NOTICE_SUFFIX = "]";
+20
View File
@@ -0,0 +1,20 @@
import {
findAgentsMdUp as findAgentsMdUpCore,
} from "@oh-my-opencode/rules-core";
import { isAbsolute, resolve } from "node:path";
import type { AgentsMdDiscoveryInput } from "./types";
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: AgentsMdDiscoveryInput): Promise<string[]> {
return findAgentsMdUpCore({
startDir: input.startDir,
rootDir: input.rootDir,
cache: input.cache,
});
}
+15
View File
@@ -0,0 +1,15 @@
import {
TRUNCATION_NOTICE_PREFIX,
TRUNCATION_NOTICE_SUFFIX,
} from "./constants";
export function formatAgentsMdContextBlock(input: {
readonly agentsPath: string;
readonly content: string;
readonly truncated: boolean;
}): string {
const truncationNotice = input.truncated
? `${TRUNCATION_NOTICE_PREFIX}${input.agentsPath}${TRUNCATION_NOTICE_SUFFIX}`
: "";
return `\n\n[Directory Context: ${input.agentsPath}]\n${input.content}${truncationNotice}`;
}
+12
View File
@@ -0,0 +1,12 @@
export { AGENTS_FILENAME } from "./constants";
export { findAgentsMdUp, resolveFilePath } from "./finder";
export { formatAgentsMdContextBlock } from "./formatter";
export { getSessionCache } from "./injection-cache";
export { processFilePathForAgentsInjection } from "./injector";
export type {
AgentsMdContextOutput,
AgentsMdDiscoveryInput,
AgentsMdInjectedPathsStorage,
AgentsMdTruncator,
TruncationResult,
} from "./types";
@@ -0,0 +1,14 @@
import type { AgentsMdInjectedPathsStorage } from "./types";
export function getSessionCache(input: {
readonly sessionCaches: Map<string, Set<string>>;
readonly sessionID: string;
readonly storage: Pick<AgentsMdInjectedPathsStorage, "loadInjectedPaths">;
}): Set<string> {
const existing = input.sessionCaches.get(input.sessionID);
if (existing) return existing;
const loaded = input.storage.loadInjectedPaths(input.sessionID);
input.sessionCaches.set(input.sessionID, loaded);
return loaded;
}
+67
View File
@@ -0,0 +1,67 @@
import type { AgentsMdCache } from "@oh-my-opencode/rules-core";
import { promises as fsPromises } from "node:fs";
import { dirname } from "node:path";
import { findAgentsMdUp, resolveFilePath } from "./finder";
import { formatAgentsMdContextBlock } from "./formatter";
import { getSessionCache } from "./injection-cache";
import type {
AgentsMdContextOutput,
AgentsMdInjectedPathsStorage,
AgentsMdTruncator,
} from "./types";
export async function processFilePathForAgentsInjection(input: {
readonly rootDirectory: string;
readonly truncator: AgentsMdTruncator;
readonly sessionCaches: Map<string, Set<string>>;
readonly storage: AgentsMdInjectedPathsStorage;
readonly agentsMdCache?: AgentsMdCache;
readonly filePath: string;
readonly sessionID: string;
readonly output: AgentsMdContextOutput;
}): Promise<void> {
if (typeof input.output.output !== "string") return;
const resolved = resolveFilePath(input.rootDirectory, input.filePath);
if (!resolved) return;
const dir = dirname(resolved);
const cache = getSessionCache({
sessionCaches: input.sessionCaches,
sessionID: input.sessionID,
storage: input.storage,
});
const agentsPaths = await findAgentsMdUp({
startDir: dir,
rootDir: input.rootDirectory,
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,
);
input.output.output += formatAgentsMdContextBlock({
agentsPath,
content: result,
truncated,
});
dirty = true;
}
if (dirty) {
input.storage.saveInjectedPaths(input.sessionID, cache);
}
}
+27
View File
@@ -0,0 +1,27 @@
import type { AgentsMdCache } from "@oh-my-opencode/rules-core";
export interface TruncationResult {
readonly result: string;
readonly truncated: boolean;
}
export interface AgentsMdTruncator {
truncate(sessionID: string, content: string): Promise<TruncationResult>;
}
export interface AgentsMdContextOutput {
readonly title: string;
output: string;
readonly metadata: unknown;
}
export interface AgentsMdInjectedPathsStorage {
loadInjectedPaths(sessionID: string): Set<string>;
saveInjectedPaths(sessionID: string, paths: Set<string>): void;
}
export interface AgentsMdDiscoveryInput {
readonly startDir: string;
readonly rootDir: string;
readonly cache?: AgentsMdCache;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ESNext"],
"types": ["bun-types"]
},
"include": ["src/**/*"]
}