fix(shared): stop ancestor discovery at worktree root
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
||||||
import { mkdirSync, rmSync } from "node:fs"
|
import { mkdirSync, realpathSync, rmSync } from "node:fs"
|
||||||
import { tmpdir } from "node:os"
|
import { tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +11,10 @@ import {
|
|||||||
|
|
||||||
const TEST_DIR = join(tmpdir(), `project-discovery-dirs-${Date.now()}`)
|
const TEST_DIR = join(tmpdir(), `project-discovery-dirs-${Date.now()}`)
|
||||||
|
|
||||||
|
function canonicalPath(path: string): string {
|
||||||
|
return realpathSync(path)
|
||||||
|
}
|
||||||
|
|
||||||
describe("project-discovery-dirs", () => {
|
describe("project-discovery-dirs", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mkdirSync(TEST_DIR, { recursive: true })
|
mkdirSync(TEST_DIR, { recursive: true })
|
||||||
@@ -33,9 +37,9 @@ describe("project-discovery-dirs", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(directories).toEqual([
|
expect(directories).toEqual([
|
||||||
join(projectDir, ".opencode", "skills"),
|
canonicalPath(join(projectDir, ".opencode", "skills")),
|
||||||
join(projectDir, ".opencode", "skill"),
|
canonicalPath(join(projectDir, ".opencode", "skill")),
|
||||||
join(TEST_DIR, ".opencode", "skills"),
|
canonicalPath(join(TEST_DIR, ".opencode", "skills")),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -51,8 +55,8 @@ describe("project-discovery-dirs", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(directories).toEqual([
|
expect(directories).toEqual([
|
||||||
join(projectDir, ".opencode", "commands"),
|
canonicalPath(join(projectDir, ".opencode", "commands")),
|
||||||
join(TEST_DIR, ".opencode", "command"),
|
canonicalPath(join(TEST_DIR, ".opencode", "command")),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -68,7 +72,21 @@ describe("project-discovery-dirs", () => {
|
|||||||
const agentsDirectories = findProjectAgentsSkillDirs(childDir)
|
const agentsDirectories = findProjectAgentsSkillDirs(childDir)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(claudeDirectories).toEqual([join(projectDir, ".claude", "skills")])
|
expect(claudeDirectories).toEqual([canonicalPath(join(projectDir, ".claude", "skills"))])
|
||||||
expect(agentsDirectories).toEqual([join(TEST_DIR, ".agents", "skills")])
|
expect(agentsDirectories).toEqual([canonicalPath(join(TEST_DIR, ".agents", "skills"))])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given a stop directory #when finding ancestor dirs #then it does not scan beyond the stop boundary", () => {
|
||||||
|
// given
|
||||||
|
const projectDir = join(TEST_DIR, "project")
|
||||||
|
const childDir = join(projectDir, "apps", "cli")
|
||||||
|
mkdirSync(join(projectDir, ".opencode", "skills"), { recursive: true })
|
||||||
|
mkdirSync(join(TEST_DIR, ".opencode", "skills"), { recursive: true })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const directories = findProjectOpencodeSkillDirs(childDir, projectDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(directories).toEqual([canonicalPath(join(projectDir, ".opencode", "skills"))])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
import { existsSync } from "node:fs"
|
import { execFileSync } from "node:child_process"
|
||||||
|
import { existsSync, realpathSync } from "node:fs"
|
||||||
import { dirname, join, resolve } from "node:path"
|
import { dirname, join, resolve } from "node:path"
|
||||||
|
|
||||||
|
function normalizePath(path: string): string {
|
||||||
|
const resolvedPath = resolve(path)
|
||||||
|
if (!existsSync(resolvedPath)) {
|
||||||
|
return resolvedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return realpathSync(resolvedPath)
|
||||||
|
} catch {
|
||||||
|
return resolvedPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function findAncestorDirectories(
|
function findAncestorDirectories(
|
||||||
startDirectory: string,
|
startDirectory: string,
|
||||||
targetPaths: ReadonlyArray<ReadonlyArray<string>>,
|
targetPaths: ReadonlyArray<ReadonlyArray<string>>,
|
||||||
|
stopDirectory?: string,
|
||||||
): string[] {
|
): string[] {
|
||||||
const directories: string[] = []
|
const directories: string[] = []
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
let currentDirectory = resolve(startDirectory)
|
let currentDirectory = normalizePath(startDirectory)
|
||||||
|
const resolvedStopDirectory = stopDirectory ? normalizePath(stopDirectory) : undefined
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
for (const targetPath of targetPaths) {
|
for (const targetPath of targetPaths) {
|
||||||
@@ -20,33 +36,66 @@ function findAncestorDirectories(
|
|||||||
directories.push(candidateDirectory)
|
directories.push(candidateDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resolvedStopDirectory === currentDirectory) {
|
||||||
|
return directories
|
||||||
|
}
|
||||||
|
|
||||||
const parentDirectory = dirname(currentDirectory)
|
const parentDirectory = dirname(currentDirectory)
|
||||||
if (parentDirectory === currentDirectory) {
|
if (parentDirectory === currentDirectory) {
|
||||||
return directories
|
return directories
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDirectory = parentDirectory
|
currentDirectory = normalizePath(parentDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findProjectClaudeSkillDirs(startDirectory: string): string[] {
|
function detectWorktreePath(directory: string): string | undefined {
|
||||||
return findAncestorDirectories(startDirectory, [[".claude", "skills"]])
|
try {
|
||||||
|
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||||
|
cwd: directory,
|
||||||
|
encoding: "utf-8",
|
||||||
|
timeout: 5000,
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
}).trim()
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findProjectAgentsSkillDirs(startDirectory: string): string[] {
|
export function findProjectClaudeSkillDirs(startDirectory: string, stopDirectory?: string): string[] {
|
||||||
return findAncestorDirectories(startDirectory, [[".agents", "skills"]])
|
return findAncestorDirectories(
|
||||||
|
startDirectory,
|
||||||
|
[[".claude", "skills"]],
|
||||||
|
stopDirectory ?? detectWorktreePath(startDirectory),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findProjectOpencodeSkillDirs(startDirectory: string): string[] {
|
export function findProjectAgentsSkillDirs(startDirectory: string, stopDirectory?: string): string[] {
|
||||||
return findAncestorDirectories(startDirectory, [
|
return findAncestorDirectories(
|
||||||
[".opencode", "skills"],
|
startDirectory,
|
||||||
[".opencode", "skill"],
|
[[".agents", "skills"]],
|
||||||
])
|
stopDirectory ?? detectWorktreePath(startDirectory),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findProjectOpencodeCommandDirs(startDirectory: string): string[] {
|
export function findProjectOpencodeSkillDirs(startDirectory: string, stopDirectory?: string): string[] {
|
||||||
return findAncestorDirectories(startDirectory, [
|
return findAncestorDirectories(
|
||||||
[".opencode", "commands"],
|
startDirectory,
|
||||||
[".opencode", "command"],
|
[
|
||||||
])
|
[".opencode", "skills"],
|
||||||
|
[".opencode", "skill"],
|
||||||
|
],
|
||||||
|
stopDirectory ?? detectWorktreePath(startDirectory),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProjectOpencodeCommandDirs(startDirectory: string, stopDirectory?: string): string[] {
|
||||||
|
return findAncestorDirectories(
|
||||||
|
startDirectory,
|
||||||
|
[
|
||||||
|
[".opencode", "commands"],
|
||||||
|
[".opencode", "command"],
|
||||||
|
],
|
||||||
|
stopDirectory ?? detectWorktreePath(startDirectory),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user