Skip to content
Draft
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
87 changes: 87 additions & 0 deletions apps/server/src/git/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2949,6 +2949,93 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect("sends identical AGENTS.md and CLAUDE.md content to a Claude writer once", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* runGit(repoDir, ["init", "--initial-branch=main"]);
yield* runGit(repoDir, ["config", "user.email", "test@example.com"]);
yield* runGit(repoDir, ["config", "user.name", "Test User"]);
const instructions = "Use lowercase source control text.";
// A byte-identical copy stands in for the common `CLAUDE.md -> AGENTS.md`
// symlink: both read to the same string, which is what the dedup compares.
NodeFS.writeFileSync(NodePath.join(repoDir, "AGENTS.md"), instructions);
NodeFS.writeFileSync(NodePath.join(repoDir, "CLAUDE.md"), instructions);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\n");
yield* runGit(repoDir, ["add", "README.md"]);
let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined;

const { manager } = yield* makeManager({
serverSettings: {
textGenerationModelSelection: {
instanceId: ProviderInstanceId.make("claudeAgent"),
model: "claude-sonnet-4-6",
},
sourceControlWritingStyle: {
mode: "repo_conventions" as const,
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedPolicy = input.policy;
return Effect.succeed({ subject: "Create initial commit", body: "" });
},
},
});
yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
});

expect(generatedPolicy).toEqual({
kind: "repo_conventions",
commitInstructions: `Follow the repository's established commit message style when examples are available.\n\nLocal AGENTS.md:\n${instructions}`,
changeRequestInstructions: `Follow the repository's established change request title and body style when examples are available.\n\nLocal AGENTS.md:\n${instructions}`,
inferRepositoryConventions: true,
});
}),
);

it.effect("omits CLAUDE.md from repository conventions for a non-Claude writer", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* runGit(repoDir, ["init", "--initial-branch=main"]);
yield* runGit(repoDir, ["config", "user.email", "test@example.com"]);
yield* runGit(repoDir, ["config", "user.name", "Test User"]);
const agentInstructions = "Use lowercase source control text.";
const claudeInstructions = "Keep pull request bodies brief.";
NodeFS.writeFileSync(NodePath.join(repoDir, "AGENTS.md"), agentInstructions);
NodeFS.writeFileSync(NodePath.join(repoDir, "CLAUDE.md"), claudeInstructions);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\n");
yield* runGit(repoDir, ["add", "README.md"]);
let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined;

const { manager } = yield* makeManager({
serverSettings: {
sourceControlWritingStyle: {
mode: "repo_conventions" as const,
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedPolicy = input.policy;
return Effect.succeed({ subject: "Create initial commit", body: "" });
},
},
});
yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
});

expect(generatedPolicy).toEqual({
kind: "repo_conventions",
commitInstructions: `Follow the repository's established commit message style when examples are available.\n\nLocal AGENTS.md:\n${agentInstructions}`,
changeRequestInstructions: `Follow the repository's established change request title and body style when examples are available.\n\nLocal AGENTS.md:\n${agentInstructions}`,
inferRepositoryConventions: true,
});
}),
);

it.effect("uses custom commit message when provided", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
Expand Down
7 changes: 6 additions & 1 deletion apps/server/src/git/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,9 +755,14 @@ export const make = Effect.gen(function* () {
provider.instanceId === settings.modelSelection.instanceId &&
provider.driver === "claudeAgent",
);
const claudeInstructions = isClaudeWriter
const claudeInstructionsRead = isClaudeWriter
? yield* readRepositoryInstructions(cwd, "CLAUDE.md")
: "";
// CLAUDE.md is commonly a symlink to, or a copy of, AGENTS.md. Both
// reads then return the same text, and the writer would be sent it
// twice. Comparing content covers symlinks, hardlinks and copies.
const claudeInstructions =
claudeInstructionsRead === agentInstructions ? "" : claudeInstructionsRead;
const examples = [
...(subjects.length > 0
? [["Recent commit subjects from this repository:", ...subjects].join("\n")]
Expand Down