Skip to content

Commit ff2939b

Browse files
redxzetaAgent
andauthored
feat: add git blame-someone-else composer command (#29) (#35)
* chore: replace remaining Synara branding with Forkara (#12) * feat: add blame-someone-else composer command (#29) --------- Co-authored-by: Agent <agent@debian.local>
1 parent 4e368a4 commit ff2939b

4 files changed

Lines changed: 55 additions & 1 deletion

File tree

apps/web/src/composerSlashCommands.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe("composerSlashCommands", () => {
2323
expect(isBuiltInComposerSlashCommand("review")).toBe(true);
2424
expect(isBuiltInComposerSlashCommand("fast")).toBe(true);
2525
expect(isBuiltInComposerSlashCommand("automation")).toBe(true);
26+
expect(isBuiltInComposerSlashCommand("blame-someone-else")).toBe(true);
2627
expect(isBuiltInComposerSlashCommand("export")).toBe(true);
2728
expect(isBuiltInComposerSlashCommand("feedback")).toBe(true);
2829
expect(isBuiltInComposerSlashCommand("unknown")).toBe(false);
@@ -34,6 +35,9 @@ describe("composerSlashCommands", () => {
3435
expect(filterComposerSlashCommands("auto").map((entry) => entry.command)).toEqual([
3536
"automation",
3637
]);
38+
expect(filterComposerSlashCommands("blame").map((entry) => entry.command)).toEqual([
39+
"blame-someone-else",
40+
]);
3741
expect(filterComposerSlashCommands("feed").map((entry) => entry.command)).toEqual(["feedback"]);
3842
});
3943

@@ -58,6 +62,10 @@ describe("composerSlashCommands", () => {
5862
command: "side",
5963
args: "is this safe?",
6064
});
65+
expect(parseComposerSlashInvocation("/blame-someone-else src/ui/button.tsx")).toEqual({
66+
command: "blame-someone-else",
67+
args: "src/ui/button.tsx",
68+
});
6169
expect(parseComposerSlashInvocation("/automation every 6h check the page")).toEqual({
6270
command: "automation",
6371
args: "every 6h check the page",
@@ -299,7 +307,7 @@ describe("composerSlashCommands", () => {
299307
canOfferSideCommand: true,
300308
canOfferExportCommand: true,
301309
}),
302-
).toEqual(["side", "export", "feedback", "automation"]);
310+
).toEqual(["side", "export", "feedback", "blame-someone-else", "automation"]);
303311
});
304312

305313
it("offers the app-level /export command on every provider", () => {
@@ -411,6 +419,7 @@ describe("composerSlashCommands", () => {
411419
"subagents",
412420
"export",
413421
"feedback",
422+
"blame-someone-else",
414423
"automation",
415424
]);
416425
});

apps/web/src/composerSlashCommands.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ const COMPOSER_SLASH_COMMAND_DEFINITIONS: Record<
211211
description: "Send feedback to the Forkara team",
212212
source: "app",
213213
},
214+
"blame-someone-else": {
215+
command: "blame-someone-else",
216+
label: "/blame-someone-else",
217+
description: "Transfer blame to someone else (but keep git history honest)",
218+
source: "app",
219+
},
214220
automation: {
215221
command: "automation",
216222
label: "/automation",
@@ -412,6 +418,7 @@ export function getAvailableComposerSlashCommands(input: {
412418
"subagents",
413419
...(input.canOfferExportCommand ? (["export"] as const) : []),
414420
"feedback",
421+
"blame-someone-else",
415422
"automation",
416423
]
417424
: [
@@ -422,6 +429,7 @@ export function getAvailableComposerSlashCommands(input: {
422429
...(input.canOfferSideCommand ? (["side"] as const) : []),
423430
...(input.canOfferExportCommand ? (["export"] as const) : []),
424431
"feedback",
432+
"blame-someone-else",
425433
"automation",
426434
];
427435
return availableCommands.filter((command) => !collidingNativeCommandNames.has(command));

apps/web/src/hooks/useComposerSlashCommands.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ function wasPromptReplacementApplied(result: number | false): boolean {
5555
return result !== false;
5656
}
5757

58+
function buildBlameSlashPrompt(args: string): string {
59+
const normalizedArgs = args.trim();
60+
return normalizedArgs.length > 0 ? `git blame ${normalizedArgs}` : "git blame";
61+
}
62+
5863
export function useComposerSlashCommands(input: {
5964
activeProject: Project | undefined;
6065
activeThread: Thread | undefined;
@@ -667,6 +672,16 @@ export function useComposerSlashCommands(input: {
667672
setIsSlashStatusDialogOpen(true);
668673
return true;
669674
}
675+
if (slashInvocation.command === "blame-someone-else") {
676+
editorActions.clearComposerSlashDraft();
677+
editorActions.setComposerPromptValue(buildBlameSlashPrompt(slashInvocation.args));
678+
toastManager.add({
679+
type: "info",
680+
title: "Blame transfer is unavailable",
681+
description: "Unable to transfer blame. Git has receipts.",
682+
});
683+
return true;
684+
}
670685
if (slashInvocation.command === "subagents") {
671686
editorActions.setComposerPromptValue(buildSubagentsPrompt(slashInvocation.args));
672687
return true;
@@ -932,6 +947,27 @@ export function useComposerSlashCommands(input: {
932947
return;
933948
}
934949

950+
if (item.command === "blame-someone-else") {
951+
const replacement = buildBlameSlashPrompt("");
952+
const applied = editorActions.applyPromptReplacement(
953+
trigger.rangeStart,
954+
trigger.rangeEnd,
955+
replacement,
956+
{ expectedText: snapshot.value.slice(trigger.rangeStart, trigger.rangeEnd) },
957+
);
958+
if (!wasPromptReplacementApplied(applied)) {
959+
return;
960+
}
961+
editorActions.setComposerHighlightedItemId(null);
962+
toastManager.add({
963+
type: "info",
964+
title: "Blame transfer is unavailable",
965+
description: "Unable to transfer blame. Git has receipts.",
966+
});
967+
editorActions.scheduleComposerFocus();
968+
return;
969+
}
970+
935971
if (item.command === "review") {
936972
if (selectedProvider === "codex") {
937973
const applied = clearSlashCommandFromComposer();

packages/shared/src/composerSlashCommands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const BUILT_IN_COMPOSER_SLASH_COMMANDS = [
1010
"model",
1111
"plan",
1212
"default",
13+
"blame-someone-else",
1314
"review",
1415
"fork",
1516
"side",

0 commit comments

Comments
 (0)