Add git_branch and export the git runner
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
1 parent
16d27e1611
commit
6b67465723
2 files changed
+57
-5
No files matched your search
+28
-3
@@ -16,7 +16,7 @@ type GitResult = { ok: true; stdout: string } | { ok: false; message: string };
|
|||||||
* an injection. Spawning the binary directly with a fixed argv removes that entirely,
|
* an injection. Spawning the binary directly with a fixed argv removes that entirely,
|
||||||
* which is also why these tools can be auto-approved.
|
* which is also why these tools can be auto-approved.
|
||||||
*/
|
*/
|
||||||
async function git(args: string[], cwd: string, timeout = 30_000): Promise<GitResult> {
|
export async function git(args: string[], cwd: string, timeout = 30_000): Promise<GitResult> {
|
||||||
let proc: Bun.Subprocess<'ignore', 'pipe', 'pipe'>;
|
let proc: Bun.Subprocess<'ignore', 'pipe', 'pipe'>;
|
||||||
try {
|
try {
|
||||||
proc = Bun.spawn(['git', ...args], { cwd, stdout: 'pipe', stderr: 'pipe', timeout });
|
proc = Bun.spawn(['git', ...args], { cwd, stdout: 'pipe', stderr: 'pipe', timeout });
|
||||||
@@ -152,13 +152,38 @@ export const gitBlameTool = tool({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const gitBranchTool = tool({
|
||||||
|
description:
|
||||||
|
'Branches in this repository, newest commit first, with the current one marked. Pass remote to include ' +
|
||||||
|
'remote-tracking branches. Use it before proposing a branch name, so a name already taken is obvious.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
remote: z.boolean().optional().describe('Include remote-tracking branches'),
|
||||||
|
}),
|
||||||
|
execute: async ({ remote }) => {
|
||||||
|
const args = [
|
||||||
|
'branch',
|
||||||
|
'--list',
|
||||||
|
'--sort=-committerdate',
|
||||||
|
'--format=%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short) %(committerdate:short) %(contents:subject)',
|
||||||
|
];
|
||||||
|
if (remote) args.push('--all');
|
||||||
|
return run(args, 'No branches yet.');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const gitTools = {
|
export const gitTools = {
|
||||||
git_status: gitStatusTool,
|
git_status: gitStatusTool,
|
||||||
git_diff: gitDiffTool,
|
git_diff: gitDiffTool,
|
||||||
git_log: gitLogTool,
|
git_log: gitLogTool,
|
||||||
git_show: gitShowTool,
|
git_show: gitShowTool,
|
||||||
git_blame: gitBlameTool,
|
git_blame: gitBlameTool,
|
||||||
|
git_branch: gitBranchTool,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Read-only, so none of these ever prompt for approval. */
|
/**
|
||||||
export const GIT_TOOL_NAMES = Object.keys(gitTools);
|
* Read-only, so none of these ever prompt for approval.
|
||||||
|
*
|
||||||
|
* `git_commit_message` is built in `src/commit.ts` and wired in `cli.tsx`, because it
|
||||||
|
* needs the model at construction. It belongs to this set for gating like the rest.
|
||||||
|
*/
|
||||||
|
export const GIT_TOOL_NAMES = [...Object.keys(gitTools), 'git_commit_message'];
|
||||||
+29
-2
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
|||||||
import {
|
import {
|
||||||
GIT_TOOL_NAMES,
|
GIT_TOOL_NAMES,
|
||||||
gitBlameTool,
|
gitBlameTool,
|
||||||
|
gitBranchTool,
|
||||||
gitDiffTool,
|
gitDiffTool,
|
||||||
gitLogTool,
|
gitLogTool,
|
||||||
gitShowTool,
|
gitShowTool,
|
||||||
@@ -45,8 +46,34 @@ async function repoWithOneCommit(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test('every git tool is registered and named consistently', () => {
|
test('every git tool is registered and named consistently', () => {
|
||||||
expect(GIT_TOOL_NAMES.sort()).toEqual(['git_blame', 'git_diff', 'git_log', 'git_show', 'git_status']);
|
// git_commit_message is built in src/commit.ts with the model at construction,
|
||||||
expect(Object.keys(gitTools).sort()).toEqual(GIT_TOOL_NAMES.sort());
|
// so it is not part of the static gitTools object — but it belongs to the set.
|
||||||
|
expect(GIT_TOOL_NAMES.sort()).toEqual([
|
||||||
|
'git_blame',
|
||||||
|
'git_branch',
|
||||||
|
'git_commit_message',
|
||||||
|
'git_diff',
|
||||||
|
'git_log',
|
||||||
|
'git_show',
|
||||||
|
'git_status',
|
||||||
|
]);
|
||||||
|
expect([...Object.keys(gitTools), 'git_commit_message'].sort()).toEqual(GIT_TOOL_NAMES.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('git_branch marks the current branch and lists the others', async () => {
|
||||||
|
await repoWithOneCommit();
|
||||||
|
const one = await run(gitBranchTool, {});
|
||||||
|
expect(one).toContain('* main');
|
||||||
|
|
||||||
|
await git('branch', 'feature/pagination');
|
||||||
|
const two = await run(gitBranchTool, {});
|
||||||
|
expect(two).toContain('* main');
|
||||||
|
expect(two).toContain('feature/pagination');
|
||||||
|
expect(two).toContain('add the server port');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('git_branch outside a repository says so', async () => {
|
||||||
|
expect(run(gitBranchTool, {})).rejects.toThrow(/not a git repository/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('git_status names the branch and describes each change', async () => {
|
test('git_status names the branch and describes each change', async () => {
|
||||||
|
|||||||
Reference in new issue
Block a user