Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/opencode/src/patch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,23 @@ export namespace Patch {
const line = lines[startIdx]

if (line.startsWith("*** Add File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.slice("*** Add File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Delete File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.slice("*** Delete File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Update File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.slice("*** Update File:".length).trim()
let movePath: string | undefined
let nextIdx = startIdx + 1

// Check for move directive
if (nextIdx < lines.length && lines[nextIdx].startsWith("*** Move to:")) {
movePath = lines[nextIdx].split(":", 2)[1]?.trim()
movePath = lines[nextIdx].slice("*** Move to:".length).trim()
nextIdx++
}

Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export namespace Snapshot {
.split("\n")
.map((x) => x.trim())
.filter(Boolean)
.map((x) => path.join(Instance.worktree, x)),
.map((x) => path.join(Instance.worktree, x).replaceAll("\\", "/")),
}
}

Expand Down
12 changes: 6 additions & 6 deletions packages/opencode/src/tool/apply_patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
// Build per-file metadata for UI rendering (used for both permission and result)
const files = fileChanges.map((change) => ({
filePath: change.filePath,
relativePath: path.relative(Instance.worktree, change.movePath ?? change.filePath),
relativePath: path.relative(Instance.worktree, change.movePath ?? change.filePath).replaceAll("\\", "/"),
type: change.type,
diff: change.diff,
before: change.oldContent,
Expand All @@ -172,7 +172,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
}))

// Check permissions if needed
const relativePaths = fileChanges.map((c) => path.relative(Instance.worktree, c.filePath))
const relativePaths = fileChanges.map((c) => path.relative(Instance.worktree, c.filePath).replaceAll("\\", "/"))
await ctx.ask({
permission: "edit",
patterns: relativePaths,
Expand Down Expand Up @@ -242,13 +242,13 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
// Generate output summary
const summaryLines = fileChanges.map((change) => {
if (change.type === "add") {
return `A ${path.relative(Instance.worktree, change.filePath)}`
return `A ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
}
if (change.type === "delete") {
return `D ${path.relative(Instance.worktree, change.filePath)}`
return `D ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
}
const target = change.movePath ?? change.filePath
return `M ${path.relative(Instance.worktree, target)}`
return `M ${path.relative(Instance.worktree, target).replaceAll("\\", "/")}`
})
let output = `Success. Updated the following files:\n${summaryLines.join("\n")}`

Expand All @@ -264,7 +264,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
const suffix =
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
output += `\n\nLSP errors detected in ${path.relative(Instance.worktree, target)}, please fix:\n<diagnostics file="${target}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
output += `\n\nLSP errors detected in ${path.relative(Instance.worktree, target).replaceAll("\\", "/")}, please fix:\n<diagnostics file="${target}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ export const BashTool = Tool.define("bash", async () => {
}

if (directories.size > 0) {
const globs = Array.from(directories).map((dir) => path.join(dir, "*"))
const globs = Array.from(directories).map((dir) => {
// Preserve POSIX-looking paths with /s, even on Windows
if (dir.startsWith("/")) return `${dir.replace(/[\\/]+$/, "")}/*`
return path.join(dir, "*")
})
await ctx.ask({
permission: "external_directory",
patterns: globs,
Expand Down
10 changes: 5 additions & 5 deletions packages/opencode/test/skill/skill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Instructions here.
const testSkill = skills.find((s) => s.name === "test-skill")
expect(testSkill).toBeDefined()
expect(testSkill!.description).toBe("A test skill for verification.")
expect(testSkill!.location).toContain("skill/test-skill/SKILL.md")
expect(testSkill!.location).toContain(path.join("skill", "test-skill", "SKILL.md"))
},
})
})
Expand Down Expand Up @@ -180,7 +180,7 @@ description: A skill in the .claude/skills directory.
expect(skills.length).toBe(1)
const claudeSkill = skills.find((s) => s.name === "claude-skill")
expect(claudeSkill).toBeDefined()
expect(claudeSkill!.location).toContain(".claude/skills/claude-skill/SKILL.md")
expect(claudeSkill!.location).toContain(path.join(".claude", "skills", "claude-skill", "SKILL.md"))
},
})
})
Expand All @@ -200,7 +200,7 @@ test("discovers global skills from ~/.claude/skills/ directory", async () => {
expect(skills.length).toBe(1)
expect(skills[0].name).toBe("global-test-skill")
expect(skills[0].description).toBe("A global skill from ~/.claude/skills for testing.")
expect(skills[0].location).toContain(".claude/skills/global-test-skill/SKILL.md")
expect(skills[0].location).toContain(path.join(".claude", "skills", "global-test-skill", "SKILL.md"))
},
})
} finally {
Expand Down Expand Up @@ -245,7 +245,7 @@ description: A skill in the .agents/skills directory.
expect(skills.length).toBe(1)
const agentSkill = skills.find((s) => s.name === "agent-skill")
expect(agentSkill).toBeDefined()
expect(agentSkill!.location).toContain(".agents/skills/agent-skill/SKILL.md")
expect(agentSkill!.location).toContain(path.join(".agents", "skills", "agent-skill", "SKILL.md"))
},
})
})
Expand Down Expand Up @@ -279,7 +279,7 @@ This skill is loaded from the global home directory.
expect(skills.length).toBe(1)
expect(skills[0].name).toBe("global-agent-skill")
expect(skills[0].description).toBe("A global skill from ~/.agents/skills for testing.")
expect(skills[0].location).toContain(".agents/skills/global-agent-skill/SKILL.md")
expect(skills[0].location).toContain(path.join(".agents", "skills", "global-agent-skill", "SKILL.md"))
},
})
} finally {
Expand Down
7 changes: 7 additions & 0 deletions packages/opencode/test/tool/apply_patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ describe("tool.apply_patch freeform", () => {

expect(result.title).toContain("Success. Updated the following files")
expect(result.output).toContain("Success. Updated the following files")
// Strict formatting assertions for slashes
expect(result.output).toMatch(/A nested\/new\.txt/)
expect(result.output).toMatch(/D delete\.txt/)
expect(result.output).toMatch(/M modify\.txt/)
if (process.platform === "win32") {
expect(result.output).not.toContain("\\")
}
expect(result.metadata.diff).toContain("Index:")
expect(calls.length).toBe(1)

Expand Down
Loading