Skip to content

Commit a17f781

Browse files
fix(opencode): ignore node_modules during config and skill discovery
Closes #30337 Config and skill discovery use glob scans rooted at .opencode/ with recursive patterns (**/*.md, **/SKILL.md) and follow every directory — including node_modules/ installed by @opencode-ai/plugin. Add ignore: ["**/node_modules/**"] to all .opencode/ glob scans and thread the ignore option through Glob.scan's Options interface.
1 parent a63173d commit a17f781

8 files changed

Lines changed: 105 additions & 0 deletions

File tree

packages/core/src/util/glob.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export namespace Glob {
88
include?: "file" | "all"
99
dot?: boolean
1010
symlink?: boolean
11+
ignore?: string | string[]
1112
}
1213

1314
function toGlobOptions(options: Options): GlobOptions {
@@ -17,6 +18,7 @@ export namespace Glob {
1718
dot: options.dot,
1819
follow: options.symlink ?? false,
1920
nodir: options.include !== "all",
21+
ignore: options.ignore,
2022
}
2123
}
2224

packages/opencode/src/config/agent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export async function load(dir: string) {
1818
absolute: true,
1919
dot: true,
2020
symlink: true,
21+
ignore: ["**/node_modules/**"],
2122
})) {
2223
const md = await ConfigMarkdown.parse(item).catch((err) => {
2324
log.error("failed to load agent", { agent: item, err })
@@ -44,6 +45,7 @@ export async function loadMode(dir: string) {
4445
absolute: true,
4546
dot: true,
4647
symlink: true,
48+
ignore: ["**/node_modules/**"],
4749
})) {
4850
const md = await ConfigMarkdown.parse(item).catch((err) => {
4951
log.error("failed to load mode", { mode: item, err })

packages/opencode/src/config/command.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function load(dir: string) {
2020
absolute: true,
2121
dot: true,
2222
symlink: true,
23+
ignore: ["**/node_modules/**"],
2324
})) {
2425
const md = await ConfigMarkdown.parse(item).catch((err) => {
2526
log.error("failed to load command", { command: item, err })

packages/opencode/src/config/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function load(dir: string) {
2323
absolute: true,
2424
dot: true,
2525
symlink: true,
26+
ignore: ["**/node_modules/**"],
2627
})) {
2728
plugins.push(pathToFileURL(item).href)
2829
}

packages/opencode/src/skill/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const scan = Effect.fnUntraced(function* (
153153
include: "file",
154154
symlink: true,
155155
dot: opts?.dot,
156+
ignore: ["**/node_modules/**"],
156157
}),
157158
catch: (error) => error,
158159
}).pipe(

packages/opencode/test/config/config.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,3 +1989,52 @@ test("parseManagedPlist handles empty config", async () => {
19891989
)
19901990
expect(config.$schema).toBe("https://opencode.ai/config.json")
19911991
})
1992+
1993+
it.instance("ignores markdown files under .opencode/node_modules during config discovery", () =>
1994+
Effect.gen(function* () {
1995+
const test = yield* TestInstance
1996+
yield* FSUtil.use.writeWithDirs(
1997+
path.join(test.directory, ".opencode", "node_modules", "pkg", "agents", "ignored.md"),
1998+
`---
1999+
model: test/model
2000+
---
2001+
Ignored agent prompt`,
2002+
)
2003+
yield* FSUtil.use.writeWithDirs(
2004+
path.join(test.directory, ".opencode", "agents", "loaded.md"),
2005+
`---
2006+
model: test/model
2007+
---
2008+
Loaded agent prompt`,
2009+
)
2010+
yield* FSUtil.use.writeWithDirs(
2011+
path.join(test.directory, ".opencode", "node_modules", "pkg", "commands", "ignored.md"),
2012+
`---
2013+
description: ignored
2014+
---
2015+
Ignored command template`,
2016+
)
2017+
yield* FSUtil.use.writeWithDirs(
2018+
path.join(test.directory, ".opencode", "commands", "loaded.md"),
2019+
`---
2020+
description: loaded
2021+
---
2022+
Loaded command template`,
2023+
)
2024+
2025+
const config = yield* Config.use.get()
2026+
2027+
expect(config.agent?.["loaded"]).toMatchObject({
2028+
name: "loaded",
2029+
model: "test/model",
2030+
prompt: "Loaded agent prompt",
2031+
})
2032+
expect(config.agent?.["pkg/agents/ignored"]).toBeUndefined()
2033+
2034+
expect(config.command?.["loaded"]).toEqual({
2035+
description: "loaded",
2036+
template: "Loaded command template",
2037+
})
2038+
expect(config.command?.["pkg/commands/ignored"]).toBeUndefined()
2039+
}),
2040+
)

packages/opencode/test/skill/skill.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,44 @@ Just some content without YAML frontmatter.
197197
),
198198
)
199199

200+
it.live("ignores skills under .opencode/node_modules", () =>
201+
provideTmpdirInstance(
202+
(dir) =>
203+
Effect.gen(function* () {
204+
yield* Effect.promise(() =>
205+
Bun.write(
206+
path.join(dir, ".opencode", "skill", "kept-skill", "SKILL.md"),
207+
`---
208+
name: kept-skill
209+
description: Should be discovered.
210+
---
211+
212+
# Kept Skill
213+
`,
214+
),
215+
)
216+
yield* Effect.promise(() =>
217+
Bun.write(
218+
path.join(dir, ".opencode", "node_modules", "pkg", "skill", "ignored-skill", "SKILL.md"),
219+
`---
220+
name: ignored-skill
221+
description: Should be ignored.
222+
---
223+
224+
# Ignored Skill
225+
`,
226+
),
227+
)
228+
229+
const skill = yield* Skill.Service
230+
const list = (yield* skill.all()).filter((s) => s.location !== "<built-in>")
231+
expect(list.find((x) => x.name === "kept-skill")).toBeDefined()
232+
expect(list.find((x) => x.name === "ignored-skill")).toBeUndefined()
233+
}),
234+
{ git: true },
235+
),
236+
)
237+
200238
it.live("discovers skills without descriptions", () =>
201239
provideTmpdirInstance(
202240
(dir) =>

packages/opencode/test/util/glob.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,17 @@ describe("Glob", () => {
115115

116116
expect(results).toEqual(["visible"])
117117
})
118+
119+
test("supports ignore patterns", async () => {
120+
await using tmp = await tmpdir()
121+
await fs.mkdir(path.join(tmp.path, "node_modules", "pkg"), { recursive: true })
122+
await fs.writeFile(path.join(tmp.path, "node_modules", "pkg", "blocked.md"), "", "utf-8")
123+
await fs.writeFile(path.join(tmp.path, "allowed.md"), "", "utf-8")
124+
125+
const results = await Glob.scan("**/*.md", { cwd: tmp.path, ignore: ["**/node_modules/**"] })
126+
127+
expect(results).toEqual(["allowed.md"])
128+
})
118129
})
119130

120131
describe("scanSync()", () => {

0 commit comments

Comments
 (0)