perf(test): make git fixtures opt-in for HttpApi exerciser scenarios

Part of #12986, addresses the fixture half of #12999.

Every exerciser scenario defaulted to project: { git: true }, spinning up a
full git repo (~6 subprocess spawns) per scenario even though only 13 of the
82 routes exercise VCS, worktree, or project-identity behavior. Flip the
default to git: false and opt those 13 in explicitly.

Non-git directories resolve to the global project (worktree "/"), so
path.get, project.current, and v2.session.permission.create — which assert
per-project semantics — keep their git fixture.

Local effect-mode wall clock: 6m37s -> ~3m45s (315/315 pass in all modes).
Coverage mode was measured at ~3s and is already static; no change needed
for that part of #12999.
This commit is contained in:
Yury Zialionka
2026-08-13 11:46:02 -06:00
parent 746fa974ec
commit 150641b5a5
3 changed files with 20 additions and 8 deletions
@@ -209,14 +209,17 @@ export const kiloScenarios: Scenario[] = [
.json(200, object),
http.protected
.get("/experimental/worktree/diff", "worktree.diff")
.inProject({ git: true })
.at((ctx) => ({ path: "/experimental/worktree/diff?base=HEAD", headers: ctx.headers() }))
.json(200, array),
http.protected
.get("/experimental/worktree/diff/summary", "worktree.diffSummary")
.inProject({ git: true })
.at((ctx) => ({ path: "/experimental/worktree/diff/summary?base=HEAD", headers: ctx.headers() }))
.json(200, array),
http.protected
.get("/experimental/worktree/diff/file", "worktree.diffFile")
.inProject({ git: true })
.at((ctx) => ({
path: `/experimental/worktree/diff/file?${new URLSearchParams({ base: "HEAD", file: "missing.txt" })}`,
headers: ctx.headers(),
@@ -22,7 +22,9 @@ class ScenarioBuilder<S = undefined> {
method,
path,
name,
project: { git: true },
// kilocode_change: non-git by default — a git repo costs ~6 subprocess spawns per
// scenario; the few VCS/worktree routes that need one opt in via .inProject({ git: true }).
project: { git: false },
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- The unseeded builder state is intentionally undefined until `.seeded(...)` narrows it.
seed: () => Effect.succeed(undefined as S),
request: (ctx) => ({ path, headers: ctx.headers() }),
@@ -39,12 +41,12 @@ class ScenarioBuilder<S = undefined> {
return this.clone({ project: undefined, request: () => ({ path: this.state.path }) })
}
inProject(project: ProjectOptions = { git: true }) {
inProject(project: ProjectOptions = { git: false }) {
return this.clone({ project })
}
withLlm() {
return this.clone({ project: { ...(this.state.project ?? { git: true }), llm: true } })
return this.clone({ project: { ...(this.state.project ?? { git: false }), llm: true } })
}
at(request: BuilderState<S>["request"]) {
@@ -116,18 +116,21 @@ const scenarios: Scenario[] = [
},
"status",
),
http.protected.get("/path", "path.get").json(200, (body, ctx) => {
// Non-git directories resolve to the global project (worktree "/"), so scenarios asserting
// per-project identity or project-scoped state need a real git repo.
http.protected.get("/path", "path.get").inProject({ git: true }).json(200, (body, ctx) => {
object(body)
check(body.directory === ctx.directory, "directory should resolve from x-kilo-directory")
check(body.worktree === ctx.directory, "worktree should resolve from x-kilo-directory")
}),
http.protected.get("/vcs", "vcs.get").json(),
http.protected.get("/vcs/status", "vcs.status").json(200, array),
http.protected.get("/vcs/status", "vcs.status").inProject({ git: true }).json(200, array),
http.protected
.get("/vcs/diff", "vcs.diff")
.inProject({ git: true })
.at((ctx) => ({ path: "/vcs/diff?mode=git", headers: ctx.headers() }))
.json(200, array),
http.protected.get("/vcs/diff/raw", "vcs.diff.raw").status(
http.protected.get("/vcs/diff/raw", "vcs.diff.raw").inProject({ git: true }).status(
200,
(_ctx, result) =>
Effect.sync(() => {
@@ -164,7 +167,7 @@ const scenarios: Scenario[] = [
.status(400),
http.protected.get("/config/providers", "config.providers").json(),
http.protected.get("/project", "project.list").json(200, array, "status"),
http.protected.get("/project/current", "project.current").json(
http.protected.get("/project/current", "project.current").inProject({ git: true }).json(
200,
(body, ctx) => {
object(body)
@@ -577,9 +580,10 @@ const scenarios: Scenario[] = [
}))
.json(200, array, "status"),
http.protected.get("/experimental/tool/ids", "tool.ids").json(200, array),
http.protected.get("/experimental/worktree", "worktree.list").json(200, array),
http.protected.get("/experimental/worktree", "worktree.list").inProject({ git: true }).json(200, array),
http.protected
.post("/experimental/worktree", "worktree.create")
.inProject({ git: true })
.mutating()
.at((ctx) => ({ path: "/experimental/worktree", headers: ctx.headers(), body: { name: "api-dsl" } }))
.jsonEffect(
@@ -598,6 +602,7 @@ const scenarios: Scenario[] = [
.status(400),
http.protected
.delete("/experimental/worktree", "worktree.remove")
.inProject({ git: true })
.mutating()
.seeded((ctx) => ctx.worktree({ name: "api-remove" }))
.at((ctx) => ({ path: "/experimental/worktree", headers: ctx.headers(), body: { directory: ctx.state.directory } }))
@@ -606,6 +611,7 @@ const scenarios: Scenario[] = [
}),
http.protected
.post("/experimental/worktree/reset", "worktree.reset")
.inProject({ git: true })
.mutating()
.seeded((ctx) => ctx.worktree({ name: "api-reset" }))
.at((ctx) => ({
@@ -861,6 +867,7 @@ const scenarios: Scenario[] = [
}),
http.protected
.post("/api/session/{sessionID}/permission", "v2.session.permission.create")
.inProject({ git: true })
.seeded((ctx) => ctx.session({ title: "Permission create owner" }))
.at((ctx) => ({
path: route("/api/session/{sessionID}/permission", { sessionID: ctx.state.id }),