Skip to content

Commit 158b11e

Browse files
committed
Stop refusing work when a folder is not a git repository
Required style skill told models to refuse if cwd had no .git. Eval fixtures are tmp copies, so GPT stopped on simple-health. Edits are allowed without a repo; eval workdirs get an unsigned fixture commit so isolated workers have HEAD.
1 parent 8152ca5 commit 158b11e

5 files changed

Lines changed: 121 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,17 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
7575
closed directors — the operator types the slash; the primary does not
7676
do the work. Turn the catalog off in `/plugins` if you want those
7777
commands gone.
78+
- **Style skill no longer refuses non-git folders.** Edits, tests, and
79+
reports are allowed without a repository. Do not `git init` unless
80+
asked. Commits, amends, rebases, and isolated worktree dispatch still
81+
require an existing repo.
7882

7983
### Evals
8084

85+
- **Capability eval workdirs are git repos.** After copying the fixture
86+
and seeding skill stubs, the runner initializes the tmp workdir (`git
87+
init`, `git add -A`, one unsigned hermetic `eval fixture` commit) so isolated
88+
workers have HEAD and git-aware skills have a baseline.
8189
- **Eval runners require an explicit model pair.** `eval:capability` and
8290
`eval:public-swe-one` take `--provider` / `--model` (capability also
8391
accepts `--matrix` with complete cells) so local `.corbits/settings.json`

‎evals/capability/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Local, multi-model capability checks against the **product** agent path (`corbit
88

99
Whether a real model + our directors/tools can complete small coding tasks on fixture repos. Graders are objective shell scripts (`verify.sh`) — pass/fail, not LLM-as-judge.
1010

11+
Eval workdirs are initialized as git repositories (HEAD exists) so isolated workers and git-aware skills have a baseline.
12+
1113
One run can **try different things**: multiple cases × multiple provider/model variants (matrix), with every product-path metric we can record written into the results JSON.
1214

1315
| Tier | Case | Fixture | Intent |

‎plugins/corbits-skills/skills/style/SKILL.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ General guidelines for writing clean, maintainable code.
1010

1111
## Git Repository Requirement
1212

13-
Agents must only operate within git repositories. Before performing any work:
13+
Prefer a git repository so changes can be tracked, reviewed, and reverted.
1414

15-
1. Verify the current working directory is inside a git repository
16-
2. If not in a git repository, refuse to proceed
15+
Edits, tests, and reports are allowed in a folder that is not a git repository. Do not refuse the task.
1716

18-
Without a git repository, it's too hard to succeed with agents - changes can't be tracked, reviewed, or safely reverted.
17+
Do not `git init` unless the user asked you to create a repository.
18+
19+
Commits, amends, rebases, and isolated worktree dispatch require an existing git repository. If the user asked to commit and there is no repository, say so and stop — do not invent a repository.
1920

2021
## Documentation
2122

‎scripts/eval-capability.test.ts‎

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { describe, expect, test } from "bun:test";
1+
import { afterEach, describe, expect, test } from "bun:test";
2+
import { mkdir, mkdtemp, writeFile, rm } from "node:fs/promises";
3+
import { tmpdir } from "node:os";
4+
import { join } from "node:path";
5+
import { execFile } from "node:child_process";
6+
import { promisify } from "node:util";
27

3-
import { parseArgs } from "./eval-capability.ts";
8+
import { initEvalGitRepo, parseArgs } from "./eval-capability.ts";
9+
10+
const execFileAsync = promisify(execFile);
411

512
describe("parseArgs", () => {
613
test("--help does not require provider or model", () => {
@@ -55,3 +62,67 @@ describe("parseArgs", () => {
5562
expect(pair.model).toBe("bar");
5663
});
5764
});
65+
66+
describe("initEvalGitRepo", () => {
67+
const savedGitConfigGlobal = process.env.GIT_CONFIG_GLOBAL;
68+
69+
const restoreGitConfigGlobal = (): void => {
70+
if (savedGitConfigGlobal === undefined) {
71+
delete process.env.GIT_CONFIG_GLOBAL;
72+
} else {
73+
process.env.GIT_CONFIG_GLOBAL = savedGitConfigGlobal;
74+
}
75+
};
76+
77+
afterEach(() => {
78+
restoreGitConfigGlobal();
79+
});
80+
81+
test("makes a fixture copy a git work tree with a commit", async () => {
82+
const dir = await mkdtemp(join(tmpdir(), "corbits-eval-git-"));
83+
try {
84+
await writeFile(join(dir, "README"), "fixture\n", "utf8");
85+
await initEvalGitRepo(dir);
86+
const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
87+
cwd: dir,
88+
});
89+
expect(stdout.trim()).toBe("true");
90+
const { stdout: head } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: dir });
91+
expect(head.trim().length).toBeGreaterThan(0);
92+
const { stdout: count } = await execFileAsync("git", ["rev-list", "--count", "HEAD"], {
93+
cwd: dir,
94+
});
95+
expect(Number(count.trim())).toBeGreaterThanOrEqual(1);
96+
const { stdout: log } = await execFileAsync("git", ["log", "-1", "--pretty=%s"], {
97+
cwd: dir,
98+
});
99+
expect(log.trim()).toBe("eval fixture");
100+
} finally {
101+
await rm(dir, { recursive: true, force: true });
102+
}
103+
});
104+
105+
test("succeeds when the process would otherwise sign", async () => {
106+
const root = await mkdtemp(join(tmpdir(), "corbits-eval-git-sign-"));
107+
const work = join(root, "work");
108+
const configPath = join(root, "gitconfig");
109+
try {
110+
await mkdir(work);
111+
await writeFile(
112+
configPath,
113+
"[commit]\ngpgsign = true\n[user]\nsigningkey = DEADKEY\n",
114+
"utf8",
115+
);
116+
process.env.GIT_CONFIG_GLOBAL = configPath;
117+
await writeFile(join(work, "README"), "fixture\n", "utf8");
118+
await initEvalGitRepo(work);
119+
const { stdout: head } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: work });
120+
expect(head.trim().length).toBeGreaterThan(0);
121+
const { stdout: cat } = await execFileAsync("git", ["cat-file", "-p", "HEAD"], { cwd: work });
122+
expect(cat).not.toContain("gpgsig");
123+
} finally {
124+
restoreGitConfigGlobal();
125+
await rm(root, { recursive: true, force: true });
126+
}
127+
});
128+
});

‎scripts/eval-capability.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,46 @@ async function seedEvalSkillStubs(workdir: string): Promise<void> {
312312
}
313313
}
314314

315+
/**
316+
* Initialize a git repo in an eval tmp workdir so isolated workers have HEAD
317+
* and git-aware skills have a baseline. Identity is `git -c`, never env or
318+
* global config. The fixture commit is unsigned (`--no-gpg-sign`,
319+
* `-c commit.gpgsign=false`) and skips hooks (`--no-verify`) so operator
320+
* `commit.gpgsign` / `core.hooksPath` cannot fail or sign with the operator
321+
* key. Do not call this on source fixtures.
322+
*/
323+
export async function initEvalGitRepo(workdir: string): Promise<void> {
324+
const identity = ["-c", "user.email=eval@local", "-c", "user.name=eval"] as const;
325+
const git = async (args: readonly string[]): Promise<void> => {
326+
const result = await runCommand("git", args, workdir, 30_000);
327+
if (result.exitCode !== 0) {
328+
throw new Error(
329+
`git ${args.join(" ")} failed (${result.exitCode}): ${result.stderr || result.stdout}`,
330+
);
331+
}
332+
};
333+
await git(["init"]);
334+
await git([...identity, "add", "-A"]);
335+
await git([
336+
...identity,
337+
"-c",
338+
"commit.gpgsign=false",
339+
"commit",
340+
"--no-gpg-sign",
341+
"--no-verify",
342+
"-m",
343+
"eval fixture",
344+
]);
345+
}
346+
315347
async function prepareWorkdir(caseDef: EvalCase): Promise<{ workdir: string; capturePath: string }> {
316348
const fixtureAbs = resolveFixturePath(REPO_ROOT, caseDef.fixture);
317349
const work = await mkdtemp(join(tmpdir(), `corbits-eval-${caseDef.id}-`));
318350
await cp(fixtureAbs, work, { recursive: true });
319351
// Global plugins reference style/philosophy/etc.; evals run in a throwaway
320352
// cwd without marketplace skill trees, so seed stubs for project skill dirs.
321353
await seedEvalSkillStubs(work);
354+
await initEvalGitRepo(work);
322355
// Sibling of the workdir so the agent and verify.sh never see the capture.
323356
const capturePath = `${work}-run-summary.json`;
324357
await installRunCaptureHook(work, capturePath);

0 commit comments

Comments
 (0)