Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9e816a2
feat(vcs): add status raw diff and guarded apply
Astro-Han May 27, 2026
dd985ba
Merge remote-tracking branch 'origin/dev' into codex/i936-vcs-parity
Astro-Han May 27, 2026
f8ba927
fix(vcs): type apply failure response
Astro-Han May 27, 2026
a76bbbd
fix(vcs): harden raw diff patches
Astro-Han May 27, 2026
15cc497
fix(vcs): include initial worktree edits in raw diff
Astro-Han May 28, 2026
c631e87
Merge remote-tracking branch 'origin/dev' into codex/i936-vcs-parity
Astro-Han May 28, 2026
7b19266
ci: repair incomplete Electron dist installs
Astro-Han May 28, 2026
64e746d
test: sync smoke inventory after dev merge
Astro-Han May 28, 2026
cdbf820
ci: force Electron repair downloads
Astro-Han May 28, 2026
48d55c0
fix(vcs): resolve raw diffs from worktree root
Astro-Han May 28, 2026
423dedc
Merge remote-tracking branch 'origin/dev' into codex/i936-vcs-parity
Astro-Han May 28, 2026
6cec399
fix(vcs): apply patches from worktree root
Astro-Han May 28, 2026
1eead57
test(app): keep module mocks complete
Astro-Han May 28, 2026
e6a8683
test(app): expose persist test helpers in mock
Astro-Han May 28, 2026
3106ffc
test(app): stabilize session module mocks
Astro-Han May 28, 2026
d11e667
test(app): restore followup module mocks
Astro-Han May 28, 2026
fb5c7b6
fix(vcs): cap apply patch input size
Astro-Han May 28, 2026
76af663
test(vcs): run service tests in effect harness
Astro-Han May 28, 2026
4ac6497
fix(vcs): reject oversized apply bodies early
Astro-Han May 28, 2026
d32231e
fix(vcs): allow escaped apply bodies within patch limit
Astro-Han May 28, 2026
7e21ec1
Merge remote-tracking branch 'origin/dev' into codex/i936-vcs-parity
Astro-Han May 28, 2026
fc14a9e
fix(vcs): type invalid apply input failures
Astro-Han May 28, 2026
b2da258
refactor(vcs): extract bounded json body middleware
Astro-Han May 28, 2026
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
2 changes: 1 addition & 1 deletion packages/app/src/context/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let isStructurallyEmpty: typeof import("./prompt").isStructurallyEmpty

beforeAll(async () => {
mock.module("@solidjs/router", () => ({
useNavigate: () => () => undefined,
useParams: () => ({}),
}))
mock.module("@opencode-ai/ui/context", () => ({
Expand Down Expand Up @@ -182,4 +183,3 @@ describe("isStructurallyEmpty", () => {
expect(isStructurallyEmpty(DEFAULT_PROMPT, [], [image])).toBe(false)
})
})

1 change: 1 addition & 0 deletions packages/app/src/hooks/use-providers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, mock, test } from "bun:test"

mock.module("@solidjs/router", () => ({
useNavigate: () => () => undefined,
useParams: () => ({}),
}))

Expand Down
1 change: 1 addition & 0 deletions packages/app/src/pages/session/session-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let sessionRouteLayoutKey: typeof import("./session-layout").sessionRouteLayoutK

beforeAll(async () => {
mock.module("@solidjs/router", () => ({
useNavigate: () => () => undefined,
useParams: () => ({}),
}))
const mod = await import("./session-layout")
Expand Down
17 changes: 11 additions & 6 deletions packages/app/src/pages/session/use-session-followups.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import { createRoot, createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import type { FollowupDraft } from "@/components/prompt-input/followup-draft"
import { normalize, readPersistedAsync, readPersistedSync } from "@/utils/persist-read"
import type {
canSendFollowupItem as CanSendFollowupItem,
Expand All @@ -25,15 +26,14 @@ let followupDraftMatchesScope: typeof FollowupDraftMatchesScope
const sendFollowupCalls: unknown[] = []
let sendFollowupDraftImpl: (input: unknown) => Promise<boolean>

type FollowupDraft = any

function workspaceStorage(dir: string) {
const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
let sum = 0
let hash = 0x811c9dc5
for (let index = 0; index < dir.length; index++) {
sum = (sum + dir.charCodeAt(index) * (index + 1)) >>> 0
hash ^= dir.charCodeAt(index)
hash = Math.imul(hash, 0x01000193)
}
return `pawwork.workspace.${head}.${sum.toString(36)}.dat`
return `pawwork.workspace.${head}.${(hash >>> 0).toString(36)}.dat`
}

const PersistMock = {
Expand Down Expand Up @@ -82,6 +82,7 @@ beforeAll(async () => {
workspaceStorage,
},
persisted: (_target: unknown, store: unknown) => store,
removePersisted: () => undefined,
}))
mock.module("@/utils/id", () => ({
Identifier: {
Expand All @@ -99,6 +100,10 @@ beforeAll(async () => {
followupDraftMatchesScope = mod.followupDraftMatchesScope
})

afterAll(() => {
mock.restore()
})

function deferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>((done) => {
Expand Down
54 changes: 49 additions & 5 deletions packages/opencode/src/git/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export namespace Git {
export interface PatchOptions {
readonly context?: number
readonly maxOutputBytes?: number
readonly binary?: boolean
}

export interface Result {
Expand All @@ -73,6 +74,7 @@ export namespace Git {
readonly cwd: string
readonly env?: Record<string, string>
readonly maxOutputBytes?: number
readonly stdin?: ChildProcess.CommandInput
}

export interface Interface {
Expand All @@ -95,11 +97,14 @@ export namespace Git {
readonly statsStaged: (cwd: string) => Effect.Effect<Stat[]>
readonly statsHead: (cwd: string, ref: string) => Effect.Effect<Stat[]>
readonly patch: (cwd: string, ref: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchAll: (cwd: string, ref: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchStagedAll: (cwd: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchUnstaged: (cwd: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchStaged: (cwd: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchHead: (cwd: string, ref: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly patchUntracked: (cwd: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
readonly statUntracked: (cwd: string, file: string) => Effect.Effect<Stat | undefined>
readonly applyPatch: (cwd: string, patch: string) => Effect.Effect<Result>
}

const kind = (code: string): Kind => {
Expand All @@ -116,14 +121,16 @@ export namespace Git {
Service,
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const encoder = new TextEncoder()
const stdin = (text: string) => Stream.make(encoder.encode(text))

const run = Effect.fn("Git.run")(
function* (args: string[], opts: Options) {
const proc = ChildProcess.make("git", [...cfg, ...args], {
cwd: opts.cwd,
env: opts.env,
extendEnv: true,
stdin: "ignore",
stdin: opts.stdin ?? "ignore",
stdout: "pipe",
stderr: "pipe",
})
Expand Down Expand Up @@ -429,17 +436,35 @@ export namespace Git {
return { text: result.truncated ? "" : result.text(), truncated: result.truncated } satisfies Patch
})

const binary = (options?: PatchOptions) => (options?.binary ? ["--binary"] : [])

const patch = Effect.fn("Git.patch")(function* (cwd: string, ref: string, file: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "--", file],
["diff", "--patch", ...binary(options), "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "--", file],
cwd,
options,
)
})

const patchAll = Effect.fn("Git.patchAll")(function* (cwd: string, ref: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--patch", ...binary(options), "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "--", "."],
cwd,
options,
)
})

const patchStagedAll = Effect.fn("Git.patchStagedAll")(function* (cwd: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--cached", "--patch", ...binary(options), "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, "--", "."],
cwd,
options,
)
})

const patchUnstaged = Effect.fn("Git.patchUnstaged")(function* (cwd: string, file: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, "--", file],
["diff", "--patch", ...binary(options), "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, "--", file],
cwd,
options,
)
Expand All @@ -451,6 +476,7 @@ export namespace Git {
"diff",
"--cached",
"--patch",
...binary(options),
"--no-ext-diff",
"--no-renames",
`--unified=${options?.context ?? 3}`,
Expand All @@ -464,15 +490,26 @@ export namespace Git {

const patchHead = Effect.fn("Git.patchHead")(function* (cwd: string, ref: string, file: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "HEAD", "--", file],
["diff", "--patch", ...binary(options), "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "HEAD", "--", file],
cwd,
options,
)
})

const patchUntracked = Effect.fn("Git.patchUntracked")(function* (cwd: string, file: string, options?: PatchOptions) {
return yield* patchResult(
["diff", "--no-index", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, "--", "/dev/null", file],
[
"diff",
"--no-index",
"--patch",
...binary(options),
"--no-ext-diff",
"--no-renames",
`--unified=${options?.context ?? 3}`,
"--",
"/dev/null",
file,
],
cwd,
options,
)
Expand All @@ -498,6 +535,10 @@ export namespace Git {
} satisfies Stat
})

const applyPatch = Effect.fn("Git.applyPatch")(function* (cwd: string, patch: string) {
return yield* run(["apply", "-"], { cwd, stdin: stdin(patch) })
})

return Service.of({
run,
branch,
Expand All @@ -518,11 +559,14 @@ export namespace Git {
statsStaged,
statsHead,
patch,
patchAll,
patchStagedAll,
patchUnstaged,
patchStaged,
patchHead,
patchUntracked,
statUntracked,
applyPatch,
})
}),
)
Expand Down
Loading