diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index af17d0e..49874b3 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -157,7 +157,6 @@ export function prepareContext( disallowedTools: disallowedTools.join(","), }), ...(prompt && { prompt }), - ...(prompt && { prompt }), ...(claudeBranch && { claudeBranch }), }; @@ -460,12 +459,12 @@ function getCommitInstructions( } } -export async function generatePrompt( +export function generatePrompt( context: PreparedContext, githubData: FetchDataResult, useCommitSigning: boolean, mode: Mode, -): Promise { +): string { // v1.0: Simply pass through the prompt to Claude Code // Claude Code handles slash commands natively const prompt = context.prompt || ""; @@ -767,8 +766,8 @@ export async function createPrompt( recursive: true, }); - // Generate the prompt directly (now async due to slash commands) - const promptContent = await generatePrompt( + // Generate the prompt directly + const promptContent = generatePrompt( preparedContext, githubData, context.inputs.useCommitSigning, diff --git a/src/modes/detector.ts b/src/modes/detector.ts index 12a6cd2..9601b0c 100644 --- a/src/modes/detector.ts +++ b/src/modes/detector.ts @@ -10,6 +10,12 @@ export type AutoDetectedMode = "tag" | "agent"; export function detectMode(context: GitHubContext): AutoDetectedMode { // 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) { return "agent"; } diff --git a/src/slash-commands/loader.ts b/src/slash-commands/loader.ts deleted file mode 100644 index a79b27e..0000000 --- a/src/slash-commands/loader.ts +++ /dev/null @@ -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; - description?: string; -} - -export interface SlashCommand { - name: string; - metadata: SlashCommandMetadata; - content: string; -} - -export interface ResolvedCommand { - expandedPrompt: string; - tools?: string[]; - settings?: Record; -} - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const COMMANDS_DIR = join(__dirname, "../../slash-commands"); - -export async function resolveSlashCommand( - prompt: string, - variables?: Record, -): Promise { - 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 { - 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 { - const commands: string[] = []; - - async function scanDirectory(dir: string, prefix = ""): Promise { - 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 }; -}