fix(shared): add ancestor project discovery helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -67,5 +67,6 @@ export * from "./session-directory-resolver"
|
|||||||
export * from "./prompt-tools"
|
export * from "./prompt-tools"
|
||||||
export * from "./internal-initiator-marker"
|
export * from "./internal-initiator-marker"
|
||||||
export * from "./plugin-command-discovery"
|
export * from "./plugin-command-discovery"
|
||||||
|
export * from "./project-discovery-dirs"
|
||||||
export { SessionCategoryRegistry } from "./session-category-registry"
|
export { SessionCategoryRegistry } from "./session-category-registry"
|
||||||
export * from "./plugin-identity"
|
export * from "./plugin-identity"
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
||||||
|
import { mkdirSync, rmSync } from "node:fs"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import {
|
||||||
|
findProjectAgentsSkillDirs,
|
||||||
|
findProjectClaudeSkillDirs,
|
||||||
|
findProjectOpencodeCommandDirs,
|
||||||
|
findProjectOpencodeSkillDirs,
|
||||||
|
} from "./project-discovery-dirs"
|
||||||
|
|
||||||
|
const TEST_DIR = join(tmpdir(), `project-discovery-dirs-${Date.now()}`)
|
||||||
|
|
||||||
|
describe("project-discovery-dirs", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mkdirSync(TEST_DIR, { recursive: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(TEST_DIR, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given nested .opencode skill directories #when finding project opencode skill dirs #then returns nearest-first with aliases", () => {
|
||||||
|
// given
|
||||||
|
const projectDir = join(TEST_DIR, "project")
|
||||||
|
const childDir = join(projectDir, "apps", "cli")
|
||||||
|
mkdirSync(join(projectDir, ".opencode", "skill"), { recursive: true })
|
||||||
|
mkdirSync(join(projectDir, ".opencode", "skills"), { recursive: true })
|
||||||
|
mkdirSync(join(TEST_DIR, ".opencode", "skills"), { recursive: true })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const directories = findProjectOpencodeSkillDirs(childDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(directories).toEqual([
|
||||||
|
join(projectDir, ".opencode", "skills"),
|
||||||
|
join(projectDir, ".opencode", "skill"),
|
||||||
|
join(TEST_DIR, ".opencode", "skills"),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given nested .opencode command directories #when finding project opencode command dirs #then returns nearest-first with aliases", () => {
|
||||||
|
// given
|
||||||
|
const projectDir = join(TEST_DIR, "project")
|
||||||
|
const childDir = join(projectDir, "packages", "tool")
|
||||||
|
mkdirSync(join(projectDir, ".opencode", "commands"), { recursive: true })
|
||||||
|
mkdirSync(join(TEST_DIR, ".opencode", "command"), { recursive: true })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const directories = findProjectOpencodeCommandDirs(childDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(directories).toEqual([
|
||||||
|
join(projectDir, ".opencode", "commands"),
|
||||||
|
join(TEST_DIR, ".opencode", "command"),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given ancestor claude and agents skill directories #when finding project compatibility dirs #then discovers both scopes", () => {
|
||||||
|
// given
|
||||||
|
const projectDir = join(TEST_DIR, "project")
|
||||||
|
const childDir = join(projectDir, "src", "nested")
|
||||||
|
mkdirSync(join(projectDir, ".claude", "skills"), { recursive: true })
|
||||||
|
mkdirSync(join(TEST_DIR, ".agents", "skills"), { recursive: true })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const claudeDirectories = findProjectClaudeSkillDirs(childDir)
|
||||||
|
const agentsDirectories = findProjectAgentsSkillDirs(childDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(claudeDirectories).toEqual([join(projectDir, ".claude", "skills")])
|
||||||
|
expect(agentsDirectories).toEqual([join(TEST_DIR, ".agents", "skills")])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { existsSync } from "node:fs"
|
||||||
|
import { dirname, join, resolve } from "node:path"
|
||||||
|
|
||||||
|
function findAncestorDirectories(
|
||||||
|
startDirectory: string,
|
||||||
|
targetPaths: ReadonlyArray<ReadonlyArray<string>>,
|
||||||
|
): string[] {
|
||||||
|
const directories: string[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
let currentDirectory = resolve(startDirectory)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
for (const targetPath of targetPaths) {
|
||||||
|
const candidateDirectory = join(currentDirectory, ...targetPath)
|
||||||
|
if (!existsSync(candidateDirectory) || seen.has(candidateDirectory)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.add(candidateDirectory)
|
||||||
|
directories.push(candidateDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentDirectory = dirname(currentDirectory)
|
||||||
|
if (parentDirectory === currentDirectory) {
|
||||||
|
return directories
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDirectory = parentDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProjectClaudeSkillDirs(startDirectory: string): string[] {
|
||||||
|
return findAncestorDirectories(startDirectory, [[".claude", "skills"]])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProjectAgentsSkillDirs(startDirectory: string): string[] {
|
||||||
|
return findAncestorDirectories(startDirectory, [[".agents", "skills"]])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProjectOpencodeSkillDirs(startDirectory: string): string[] {
|
||||||
|
return findAncestorDirectories(startDirectory, [
|
||||||
|
[".opencode", "skills"],
|
||||||
|
[".opencode", "skill"],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProjectOpencodeCommandDirs(startDirectory: string): string[] {
|
||||||
|
return findAncestorDirectories(startDirectory, [
|
||||||
|
[".opencode", "commands"],
|
||||||
|
[".opencode", "command"],
|
||||||
|
])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user