feat: add mode-aware gitea prepare flow

This commit is contained in:
Mark Wylde
2025-09-30 17:31:36 +01:00
parent 582a02ee24
commit 3305a16297
13 changed files with 404 additions and 120 deletions

249
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{
"name": "claude-pr-action",
"name": "@anthropic-ai/claude-code-action",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-pr-action",
"name": "@anthropic-ai/claude-code-action",
"version": "1.0.0",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/sdk": "^0.30.0",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/rest": "^22.0.0",
"@octokit/webhooks-types": "^7.6.1",
"node-fetch": "^3.3.2",
"zod": "^3.24.4"
@@ -209,6 +210,82 @@
"node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.2.tgz",
"integrity": "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^10.0.4",
"@octokit/types": "^15.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/endpoint": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.1.tgz",
"integrity": "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz",
"integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==",
"license": "MIT"
},
"node_modules/@octokit/graphql/node_modules/@octokit/request": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.5.tgz",
"integrity": "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.1",
"@octokit/request-error": "^7.0.1",
"@octokit/types": "^15.0.0",
"fast-content-type-parse": "^3.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.1.tgz",
"integrity": "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.0.tgz",
"integrity": "sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^26.0.0"
}
},
"node_modules/@octokit/graphql/node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/@octokit/openapi-types": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
@@ -304,6 +381,158 @@
"node": ">= 18"
}
},
"node_modules/@octokit/rest": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.0.tgz",
"integrity": "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA==",
"license": "MIT",
"dependencies": {
"@octokit/core": "^7.0.2",
"@octokit/plugin-paginate-rest": "^13.0.1",
"@octokit/plugin-request-log": "^6.0.0",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
"license": "MIT",
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/core": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.5.tgz",
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
"@octokit/request": "^10.0.4",
"@octokit/request-error": "^7.0.1",
"@octokit/types": "^15.0.0",
"before-after-hook": "^4.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.1.tgz",
"integrity": "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz",
"integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==",
"license": "MIT"
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.0.tgz",
"integrity": "sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz",
"integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.1.0.tgz",
"integrity": "sha512-nCsyiKoGRnhH5LkH8hJEZb9swpqOcsW+VXv1QoyUNQXJeVODG4+xM6UICEqyqe9XFr6LkL8BIiFCPev8zMDXPw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0"
},
"engines": {
"node": ">= 20"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.5.tgz",
"integrity": "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.1",
"@octokit/request-error": "^7.0.1",
"@octokit/types": "^15.0.0",
"fast-content-type-parse": "^3.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.1.tgz",
"integrity": "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^15.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/types": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.0.tgz",
"integrity": "sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^26.0.0"
}
},
"node_modules/@octokit/rest/node_modules/before-after-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz",
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
"license": "Apache-2.0"
},
"node_modules/@octokit/rest/node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/@octokit/types": {
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
@@ -785,6 +1014,22 @@
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/fast-content-type-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz",
"integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",

View File

@@ -14,6 +14,7 @@
"@actions/github": "^6.0.1",
"@anthropic-ai/sdk": "^0.30.0",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/rest": "^22.0.0",
"@octokit/webhooks-types": "^7.6.1",
"node-fetch": "^3.3.2",
"zod": "^3.24.4"

View File

@@ -19,7 +19,7 @@ import {
} from "../github/context";
import type { ParsedGitHubContext } from "../github/context";
import type { CommonFields, PreparedContext, EventData } from "./types";
import { GITEA_SERVER_URL } from "../github/api/config";
import type { Mode, ModeContext } from "../modes/types";
export type { CommonFields, PreparedContext } from "./types";
const BASE_ALLOWED_TOOLS = [
@@ -62,37 +62,74 @@ const BASE_ALLOWED_TOOLS = [
];
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
export function buildAllowedToolsString(
customAllowedTools?: string[],
): string {
let baseTools = [...BASE_ALLOWED_TOOLS];
const ACTIONS_ALLOWED_TOOLS = [
"mcp__github_actions__get_ci_status",
"mcp__github_actions__get_workflow_run_details",
"mcp__github_actions__download_job_log",
];
let allAllowedTools = baseTools.join(",");
if (customAllowedTools && customAllowedTools.length > 0) {
allAllowedTools = `${allAllowedTools},${customAllowedTools.join(",")}`;
const COMMIT_SIGNING_TOOLS = [
"mcp__github_file_ops__commit_files",
"mcp__github_file_ops__delete_files",
"mcp__github_file_ops__update_claude_comment",
];
function normalizeToolList(input?: string | string[]): string[] {
if (!input) {
return [];
}
return allAllowedTools;
const tools = Array.isArray(input) ? input : input.split(",");
return tools
.map((tool) => tool.trim())
.filter((tool): tool is string => tool.length > 0);
}
export function buildAllowedToolsString(
customAllowedTools?: string | string[],
includeActionsReadTools = false,
useCommitSigning = false,
): string {
const allowedTools = new Set<string>(BASE_ALLOWED_TOOLS);
if (includeActionsReadTools) {
for (const tool of ACTIONS_ALLOWED_TOOLS) {
allowedTools.add(tool);
}
}
if (useCommitSigning) {
for (const tool of COMMIT_SIGNING_TOOLS) {
allowedTools.add(tool);
}
}
for (const tool of normalizeToolList(customAllowedTools)) {
allowedTools.add(tool);
}
return Array.from(allowedTools).join(",");
}
export function buildDisallowedToolsString(
customDisallowedTools?: string[],
allowedTools?: string[],
customDisallowedTools?: string | string[],
allowedTools?: string | string[],
): string {
let disallowedTools = [...DISALLOWED_TOOLS];
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
if (allowedTools && allowedTools.length > 0) {
disallowedTools = disallowedTools.filter(
(tool) => !allowedTools.includes(tool),
);
const allowedList = normalizeToolList(allowedTools);
if (allowedList.length > 0) {
disallowedTools = disallowedTools.filter((tool) => !allowedList.includes(tool));
}
let allDisallowedTools = disallowedTools.join(",");
if (customDisallowedTools && customDisallowedTools.length > 0) {
const customList = normalizeToolList(customDisallowedTools);
if (customList.length > 0) {
if (allDisallowedTools) {
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools.join(",")}`;
allDisallowedTools = `${allDisallowedTools},${customList.join(",")}`;
} else {
allDisallowedTools = customDisallowedTools.join(",");
allDisallowedTools = customList.join(",");
}
}
return allDisallowedTools;
@@ -240,6 +277,8 @@ export function prepareContext(
throw new Error(
"ISSUE_NUMBER is required for issue_comment event for issues",
);
} else if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
}
eventData = {
@@ -249,7 +288,7 @@ export function prepareContext(
baseBranch,
issueNumber,
commentBody,
...(claudeBranch && { claudeBranch }),
claudeBranch,
};
break;
@@ -266,6 +305,9 @@ export function prepareContext(
if (!baseBranch) {
throw new Error("BASE_BRANCH is required for issues event");
}
if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issues event");
}
if (eventAction === "assigned") {
if (!assigneeTrigger && !directPrompt) {
@@ -279,8 +321,8 @@ export function prepareContext(
isPR: false,
issueNumber,
baseBranch,
assigneeTrigger,
...(claudeBranch && { claudeBranch }),
...(assigneeTrigger && { assigneeTrigger }),
claudeBranch,
};
} else if (eventAction === "labeled") {
if (!labelTrigger) {
@@ -302,7 +344,7 @@ export function prepareContext(
isPR: false,
issueNumber,
baseBranch,
...(claudeBranch && { claudeBranch }),
claudeBranch,
};
} else {
throw new Error(`Unsupported issue action: ${eventAction}`);
@@ -393,64 +435,6 @@ export function getEventTypeAndContext(envVars: PreparedContext): {
}
}
function getCommitInstructions(
eventData: EventData,
githubData: FetchDataResult,
context: PreparedContext,
useCommitSigning: boolean,
): string {
const coAuthorLine =
(githubData.triggerDisplayName ?? context.triggerUsername !== "Unknown")
? `Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>`
: "";
if (useCommitSigning) {
if (eventData.isPR && !eventData.claudeBranch) {
return `
- Push directly using mcp__github_file_ops__commit_files to the existing branch (works for both new and existing files).
- Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
- When pushing changes with this tool and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message.
- Use: "${coAuthorLine}"`;
} else {
return `
- You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch.
- Push changes directly to the current branch using mcp__github_file_ops__commit_files (works for both new and existing files)
- Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files).
- When pushing changes and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message.
- Use: "${coAuthorLine}"`;
}
} else {
// Non-signing instructions
if (eventData.isPR && !eventData.claudeBranch) {
return `
- Use git commands via the Bash tool to commit and push your changes:
- Stage files: Bash(git add <files>)
- Commit with a descriptive message: Bash(git commit -m "<message>")
${
coAuthorLine
? `- When committing and the trigger user is not "Unknown", include a Co-authored-by trailer:
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: ""
}
- Push to the remote: Bash(git push origin HEAD)`;
} else {
const branchName = eventData.claudeBranch || eventData.baseBranch;
return `
- You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch.
- Use git commands via the Bash tool to commit and push your changes:
- Stage files: Bash(git add <files>)
- Commit with a descriptive message: Bash(git commit -m "<message>")
${
coAuthorLine
? `- When committing and the trigger user is not "Unknown", include a Co-authored-by trailer:
Bash(git commit -m "<message>\\n\\n${coAuthorLine}")`
: ""
}
- Push to the remote: Bash(git push origin ${branchName})`;
}
}
}
function substitutePromptVariables(
template: string,
context: PreparedContext,
@@ -517,7 +501,7 @@ function substitutePromptVariables(
export function generatePrompt(
context: PreparedContext,
githubData: FetchDataResult,
useCommitSigning: boolean,
useCommitSigning = false,
): string {
if (context.overridePrompt) {
return substitutePromptVariables(
@@ -527,6 +511,8 @@ export function generatePrompt(
);
}
const triggerDisplayName = context.triggerUsername ?? "Unknown";
const {
contextData,
comments,
@@ -594,7 +580,7 @@ ${
}
<claude_comment_id>${context.claudeCommentId}</claude_comment_id>
<trigger_username>${context.triggerUsername ?? "Unknown"}</trigger_username>
<trigger_display_name>${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}</trigger_display_name>
<trigger_display_name>${triggerDisplayName}</trigger_display_name>
<trigger_phrase>${context.triggerPhrase}</trigger_phrase>
${
(eventData.eventName === "issue_comment" ||

View File

@@ -66,7 +66,7 @@ type IssueAssignedEvent = {
issueNumber: string;
baseBranch: string;
claudeBranch?: string;
assigneeTrigger: string;
assigneeTrigger?: string;
};
type IssueLabeledEvent = {

View File

@@ -18,6 +18,7 @@ import { createPrompt } from "../create-prompt";
import { createClient } from "../github/api/client";
import { fetchGitHubData } from "../github/data/fetcher";
import { parseGitHubContext } from "../github/context";
import { getMode } from "../modes/registry";
async function run() {
try {
@@ -54,9 +55,14 @@ async function run() {
// Step 5: Check if actor is human
await checkHumanActor(client.api, context);
// Step 6: Create initial tracking comment
const commentId = await createInitialComment(client.api, context);
core.setOutput("claude_comment_id", commentId.toString());
const mode = getMode(context.inputs.mode);
// Step 6: Create initial tracking comment (if required by mode)
let commentId: number | undefined;
if (mode.shouldCreateTrackingComment()) {
commentId = await createInitialComment(client.api, context);
core.setOutput("claude_comment_id", commentId!.toString());
}
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
const githubData = await fetchGitHubData({
@@ -74,7 +80,7 @@ async function run() {
}
// Step 9: Update initial comment with branch link (only if a claude branch was created)
if (branchInfo.claudeBranch) {
if (commentId && branchInfo.claudeBranch) {
await updateTrackingComment(
client,
context,
@@ -84,21 +90,24 @@ async function run() {
}
// Step 10: Create prompt file
await createPrompt(
const modeContext = mode.prepareContext(context, {
commentId,
branchInfo.baseBranch,
branchInfo.claudeBranch,
githubData,
context,
);
baseBranch: branchInfo.baseBranch,
claudeBranch: branchInfo.claudeBranch,
});
await createPrompt(mode, modeContext, githubData, context);
// Step 11: Get MCP configuration
const mcpConfig = await prepareMcpConfig(
const mcpConfig = await prepareMcpConfig({
githubToken,
context.repository.owner,
context.repository.repo,
branchInfo.currentBranch,
);
owner: context.repository.owner,
repo: context.repository.repo,
branch: branchInfo.currentBranch,
baseBranch: branchInfo.baseBranch,
allowedTools: context.inputs.allowedTools,
context,
});
core.setOutput("mcp_config", mcpConfig);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);

View File

@@ -29,3 +29,7 @@ export const GITEA_SERVER_URL = getServerUrl();
export const GITEA_API_URL =
process.env.GITEA_API_URL || deriveApiUrl(GITEA_SERVER_URL);
// Backwards-compatible aliases for legacy GitHub-specific naming
export const GITHUB_SERVER_URL = GITEA_SERVER_URL;
export const GITHUB_API_URL = GITEA_API_URL;

View File

@@ -141,14 +141,16 @@ export function updateCommentBody(input: CommentUpdateInput): string {
if (branchLink) {
// Extract the branch URL from the link
const urlMatch = branchLink.match(/\((https:\/\/.*)\)/);
const urlMatch = branchLink.match(/\((https?:\/\/[^\)]+)\)/);
if (urlMatch && urlMatch[1]) {
branchUrl = urlMatch[1];
}
// Extract branch name from link if not provided
if (!finalBranchName) {
const branchNameMatch = branchLink.match(/tree\/([^"'\)]+)/);
const branchNameMatch = branchLink.match(
/(?:tree|src\/branch)\/([^"'\)\s]+)/,
);
if (branchNameMatch) {
finalBranchName = branchNameMatch[1];
}
@@ -157,10 +159,17 @@ export function updateCommentBody(input: CommentUpdateInput): string {
// If we don't have a URL yet but have a branch name, construct it
if (!branchUrl && finalBranchName) {
// Extract owner/repo from jobUrl
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
if (repoMatch) {
branchUrl = `${GITEA_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/src/branch/${finalBranchName}`;
try {
const parsedJobUrl = new URL(jobUrl);
const segments = parsedJobUrl.pathname
.split("/")
.filter((segment) => segment);
const [owner, repo] = segments;
if (owner && repo) {
branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/src/branch/${finalBranchName}`;
}
} catch (error) {
console.warn(`Failed to derive branch URL from job URL: ${error}`);
}
}

View File

@@ -1,6 +1,4 @@
import { GITEA_SERVER_URL } from "../../api/config";
import { readFileSync } from "fs";
import { join } from "path";
function getSpinnerHtml(): string {
return `<img src="https://raw.githubusercontent.com/markwylde/claude-code-gitea-action/refs/heads/gitea/assets/spinner.gif" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />`;

View File

@@ -7,7 +7,7 @@
import { $ } from "bun";
import type { ParsedGitHubContext } from "../context";
import { GITHUB_SERVER_URL } from "../api/config";
import { GITEA_SERVER_URL } from "../api/config";
type GitUser = {
login: string;
@@ -22,7 +22,7 @@ export async function configureGitAuth(
console.log("Configuring git authentication for non-signing mode");
// Determine the noreply email domain based on GITHUB_SERVER_URL
const serverUrl = new URL(GITHUB_SERVER_URL);
const serverUrl = new URL(GITEA_SERVER_URL);
const noreplyDomain =
serverUrl.hostname === "github.com"
? "users.noreply.github.com"
@@ -46,7 +46,7 @@ export async function configureGitAuth(
// Remove the authorization header that actions/checkout sets
console.log("Removing existing git authentication headers...");
try {
await $`git config --unset-all http.${GITHUB_SERVER_URL}/.extraheader`;
await $`git config --unset-all http.${GITEA_SERVER_URL}/.extraheader`;
console.log("✓ Removed existing authentication headers");
} catch (e) {
console.log("No existing authentication headers to remove");

View File

@@ -8,6 +8,7 @@ import {
isPullRequestReviewEvent,
isPullRequestReviewCommentEvent,
} from "../context";
import type { IssuesLabeledEvent } from "@octokit/webhooks-types";
import type { ParsedGitHubContext } from "../context";
export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
@@ -41,6 +42,26 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
}
}
// Check for issue label trigger
if (isIssuesEvent(context) && context.eventAction === "labeled") {
const triggerLabel = context.inputs.labelTrigger?.trim();
const appliedLabel = (context.payload as IssuesLabeledEvent).label?.name
?.trim();
console.log(
`Checking label trigger: expected='${triggerLabel}', applied='${appliedLabel}'`,
);
if (
triggerLabel &&
appliedLabel &&
triggerLabel.localeCompare(appliedLabel, undefined, { sensitivity: "accent" }) === 0
) {
console.log(`Issue labeled with trigger label '${triggerLabel}'`);
return true;
}
}
// Check for issue body and title trigger on issue creation
if (isIssuesEvent(context) && context.eventAction === "opened") {
const issueBody = context.payload.issue.body || "";

View File

@@ -942,7 +942,7 @@ server.tool(
endpoint += `?style=${style}`;
}
const result = await giteaRequest(endpoint, "POST");
await giteaRequest(endpoint, "POST");
return {
content: [

View File

@@ -1,11 +1,24 @@
import * as core from "@actions/core";
import type { ParsedGitHubContext } from "../github/context";
export async function prepareMcpConfig(
githubToken: string,
owner: string,
repo: string,
branch: string,
): Promise<string> {
export type PrepareMcpConfigOptions = {
githubToken: string;
owner: string;
repo: string;
branch: string;
baseBranch?: string;
allowedTools?: string[];
context?: ParsedGitHubContext;
overrideConfig?: string;
additionalMcpConfig?: string;
};
export async function prepareMcpConfig({
githubToken,
owner,
repo,
branch,
}: PrepareMcpConfigOptions): Promise<string> {
console.log("[MCP-INSTALL] Preparing MCP configuration...");
console.log(`[MCP-INSTALL] Owner: ${owner}`);
console.log(`[MCP-INSTALL] Repo: ${repo}`);

View File

@@ -3,8 +3,6 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFile, writeFile } from "fs/promises";
import { join } from "path";
import { execSync } from "child_process";
// Get repository information from environment variables