mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
fix: address PR review comments for v1.0 simplification
- Remove duplicate prompt field spread (line 160) - Remove async from generatePrompt since slash commands are handled by Claude Code - Add detailed comment explaining why prompt → agent mode logic - Remove entire slash-commands loader and directories as Claude Code handles natively - Simplify prompt generation to just pass through to Claude Code These changes align with v1.0 philosophy: GitHub Action is a thin wrapper that delegates everything to Claude Code for native handling.
This commit is contained in:
@@ -157,7 +157,6 @@ export function prepareContext(
|
|||||||
disallowedTools: disallowedTools.join(","),
|
disallowedTools: disallowedTools.join(","),
|
||||||
}),
|
}),
|
||||||
...(prompt && { prompt }),
|
...(prompt && { prompt }),
|
||||||
...(prompt && { prompt }),
|
|
||||||
...(claudeBranch && { claudeBranch }),
|
...(claudeBranch && { claudeBranch }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,12 +459,12 @@ function getCommitInstructions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generatePrompt(
|
export function generatePrompt(
|
||||||
context: PreparedContext,
|
context: PreparedContext,
|
||||||
githubData: FetchDataResult,
|
githubData: FetchDataResult,
|
||||||
useCommitSigning: boolean,
|
useCommitSigning: boolean,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
): Promise<string> {
|
): string {
|
||||||
// v1.0: Simply pass through the prompt to Claude Code
|
// v1.0: Simply pass through the prompt to Claude Code
|
||||||
// Claude Code handles slash commands natively
|
// Claude Code handles slash commands natively
|
||||||
const prompt = context.prompt || "";
|
const prompt = context.prompt || "";
|
||||||
@@ -767,8 +766,8 @@ export async function createPrompt(
|
|||||||
recursive: true,
|
recursive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate the prompt directly (now async due to slash commands)
|
// Generate the prompt directly
|
||||||
const promptContent = await generatePrompt(
|
const promptContent = generatePrompt(
|
||||||
preparedContext,
|
preparedContext,
|
||||||
githubData,
|
githubData,
|
||||||
context.inputs.useCommitSigning,
|
context.inputs.useCommitSigning,
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ export type AutoDetectedMode = "tag" | "agent";
|
|||||||
|
|
||||||
export function detectMode(context: GitHubContext): AutoDetectedMode {
|
export function detectMode(context: GitHubContext): AutoDetectedMode {
|
||||||
// If prompt is provided, always use agent mode
|
// If prompt is provided, always use agent mode
|
||||||
|
// Reasoning: When users provide explicit instructions via the prompt parameter,
|
||||||
|
// they want Claude to execute those instructions immediately without waiting for
|
||||||
|
// @claude mentions or other triggers. This aligns with the v1.0 philosophy where
|
||||||
|
// Claude Code handles everything - the GitHub Action is just a thin wrapper that
|
||||||
|
// passes through prompts directly to Claude Code for native handling (including
|
||||||
|
// slash commands). This provides the most direct and flexible interaction model.
|
||||||
if (context.inputs?.prompt) {
|
if (context.inputs?.prompt) {
|
||||||
return "agent";
|
return "agent";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
import { readFile, readdir, stat } from "fs/promises";
|
|
||||||
import { join, dirname } from "path";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import * as yaml from "js-yaml";
|
|
||||||
|
|
||||||
export interface SlashCommandMetadata {
|
|
||||||
tools?: string[];
|
|
||||||
settings?: Record<string, any>;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SlashCommand {
|
|
||||||
name: string;
|
|
||||||
metadata: SlashCommandMetadata;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResolvedCommand {
|
|
||||||
expandedPrompt: string;
|
|
||||||
tools?: string[];
|
|
||||||
settings?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
const COMMANDS_DIR = join(__dirname, "../../slash-commands");
|
|
||||||
|
|
||||||
export async function resolveSlashCommand(
|
|
||||||
prompt: string,
|
|
||||||
variables?: Record<string, string | undefined>,
|
|
||||||
): Promise<ResolvedCommand> {
|
|
||||||
if (!prompt.startsWith("/")) {
|
|
||||||
return handleLegacyPrompts(prompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = prompt.slice(1).split(" ");
|
|
||||||
const commandPath = parts[0];
|
|
||||||
const args = parts.slice(1);
|
|
||||||
|
|
||||||
if (!commandPath) {
|
|
||||||
return { expandedPrompt: prompt };
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandParts = commandPath.split("/");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const command = await loadCommand(commandParts);
|
|
||||||
if (!command) {
|
|
||||||
console.warn(`Slash command not found: ${commandPath}`);
|
|
||||||
return { expandedPrompt: prompt };
|
|
||||||
}
|
|
||||||
|
|
||||||
let expandedContent = command.content;
|
|
||||||
|
|
||||||
if (args.length > 0) {
|
|
||||||
expandedContent = expandedContent.replace(/\{args\}/g, args.join(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variables) {
|
|
||||||
Object.entries(variables).forEach(([key, value]) => {
|
|
||||||
if (value !== undefined) {
|
|
||||||
const regex = new RegExp(`\\{${key}\\}`, "g");
|
|
||||||
expandedContent = expandedContent.replace(regex, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
expandedPrompt: expandedContent,
|
|
||||||
tools: command.metadata.tools,
|
|
||||||
settings: command.metadata.settings,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error loading slash command: ${error}`);
|
|
||||||
return { expandedPrompt: prompt };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadCommand(
|
|
||||||
commandParts: string[],
|
|
||||||
): Promise<SlashCommand | null> {
|
|
||||||
const possiblePaths = [
|
|
||||||
join(COMMANDS_DIR, ...commandParts) + ".md",
|
|
||||||
join(COMMANDS_DIR, ...commandParts, "default.md"),
|
|
||||||
join(COMMANDS_DIR, commandParts[0] + ".md"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const filePath of possiblePaths) {
|
|
||||||
try {
|
|
||||||
const fileContent = await readFile(filePath, "utf-8");
|
|
||||||
return parseCommandFile(commandParts.join("/"), fileContent);
|
|
||||||
} catch (error) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCommandFile(name: string, content: string): SlashCommand {
|
|
||||||
let metadata: SlashCommandMetadata = {};
|
|
||||||
let commandContent = content;
|
|
||||||
|
|
||||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
||||||
if (frontmatterMatch && frontmatterMatch[1]) {
|
|
||||||
try {
|
|
||||||
const parsedYaml = yaml.load(frontmatterMatch[1]);
|
|
||||||
if (parsedYaml && typeof parsedYaml === "object") {
|
|
||||||
metadata = parsedYaml as SlashCommandMetadata;
|
|
||||||
}
|
|
||||||
commandContent = frontmatterMatch[2]?.trim() || content;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to parse frontmatter for command ${name}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
metadata,
|
|
||||||
content: commandContent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listAvailableCommands(): Promise<string[]> {
|
|
||||||
const commands: string[] = [];
|
|
||||||
|
|
||||||
async function scanDirectory(dir: string, prefix = ""): Promise<void> {
|
|
||||||
try {
|
|
||||||
const entries = await readdir(dir);
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = join(dir, entry);
|
|
||||||
const entryStat = await stat(fullPath);
|
|
||||||
|
|
||||||
if (entryStat.isDirectory()) {
|
|
||||||
await scanDirectory(fullPath, prefix ? `${prefix}/${entry}` : entry);
|
|
||||||
} else if (entry.endsWith(".md")) {
|
|
||||||
const commandName = entry.replace(".md", "");
|
|
||||||
if (commandName !== "default") {
|
|
||||||
commands.push(prefix ? `${prefix}/${commandName}` : commandName);
|
|
||||||
} else if (prefix) {
|
|
||||||
commands.push(prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error scanning directory ${dir}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await scanDirectory(COMMANDS_DIR);
|
|
||||||
return commands.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLegacyPrompts(prompt: string): ResolvedCommand {
|
|
||||||
const legacyKeys = ["override_prompt", "direct_prompt"];
|
|
||||||
for (const key of legacyKeys) {
|
|
||||||
const envValue = process.env[key.toUpperCase()];
|
|
||||||
if (envValue) {
|
|
||||||
console.log(`Using legacy ${key} as prompt`);
|
|
||||||
return { expandedPrompt: envValue };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { expandedPrompt: prompt };
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user