From da182b6afb18b13de99d1d284070efbe790cb114 Mon Sep 17 00:00:00 2001 From: km-anthropic Date: Thu, 7 Aug 2025 00:27:35 -0700 Subject: [PATCH] test + formatting fixes --- src/create-prompt/index.ts | 36 +++++--- src/entrypoints/prepare.ts | 5 +- src/github/context.ts | 12 ++- src/modes/detector.ts | 2 +- src/modes/registry.ts | 9 +- src/modes/review/index.ts | 2 +- src/slash-commands/loader.ts | 13 ++- test/create-prompt.test.ts | 166 ++++++++++++++++++++++++++++++----- test/modes/registry.test.ts | 25 ++++-- 9 files changed, 202 insertions(+), 68 deletions(-) diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index cd2335ed..79be3637 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -534,42 +534,54 @@ export async function generatePrompt( mode: Mode, ): Promise { // Check for unified prompt field first (v1.0) - let prompt = context.prompt || context.overridePrompt || context.directPrompt || ""; - + let prompt = + context.prompt || context.overridePrompt || context.directPrompt || ""; + // Handle slash commands if (prompt.startsWith("/")) { const variables = { repository: context.repository, - pr_number: context.eventData.isPR && 'prNumber' in context.eventData ? context.eventData.prNumber : "", - issue_number: !context.eventData.isPR && 'issueNumber' in context.eventData ? context.eventData.issueNumber : "", + pr_number: + context.eventData.isPR && "prNumber" in context.eventData + ? context.eventData.prNumber + : "", + issue_number: + !context.eventData.isPR && "issueNumber" in context.eventData + ? context.eventData.issueNumber + : "", branch: context.eventData.claudeBranch || "", base_branch: context.eventData.baseBranch || "", trigger_user: context.triggerUsername, }; - + const resolved = await resolveSlashCommand(prompt, variables); - + // Apply any tools from the slash command if (resolved.tools && resolved.tools.length > 0) { const currentAllowedTools = process.env.ALLOWED_TOOLS || ""; const newTools = resolved.tools.join(","); - const combinedTools = currentAllowedTools ? `${currentAllowedTools},${newTools}` : newTools; + const combinedTools = currentAllowedTools + ? `${currentAllowedTools},${newTools}` + : newTools; core.exportVariable("ALLOWED_TOOLS", combinedTools); } - + // Apply any settings from the slash command if (resolved.settings) { - core.exportVariable("SLASH_COMMAND_SETTINGS", JSON.stringify(resolved.settings)); + core.exportVariable( + "SLASH_COMMAND_SETTINGS", + JSON.stringify(resolved.settings), + ); } - + prompt = resolved.expandedPrompt; } - + // If we have a prompt, use it (with variable substitution) if (prompt) { return substitutePromptVariables(prompt, context, githubData); } - + // Otherwise use the mode's default prompt generator return mode.generatePrompt(context, githubData, useCommitSigning); } diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index db1e663b..2f35be5d 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -29,9 +29,7 @@ async function run() { // For review mode, use the default GitHub Action token githubToken = process.env.DEFAULT_WORKFLOW_TOKEN || ""; if (!githubToken) { - throw new Error( - "DEFAULT_WORKFLOW_TOKEN not found for review mode", - ); + throw new Error("DEFAULT_WORKFLOW_TOKEN not found for review mode"); } console.log("Using default GitHub Action token for review mode"); core.setOutput("GITHUB_TOKEN", githubToken); @@ -41,7 +39,6 @@ async function run() { } const octokit = createOctokit(githubToken); - // Step 3: Check write permissions (only for entity contexts) if (isEntityContext(context)) { const hasWritePermissions = await checkWritePermissions( diff --git a/src/github/context.ts b/src/github/context.ts index bf831c17..62f41d09 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -106,7 +106,9 @@ export function parseGitHubContext(): GitHubContext { const context = github.context; // Mode is optional in v1.0 (auto-detected) - const modeInput = process.env.MODE ? process.env.MODE as ModeName : undefined; + const modeInput = process.env.MODE + ? (process.env.MODE as ModeName) + : undefined; const commonFields = { runId: process.env.GITHUB_RUN_ID!, @@ -120,9 +122,11 @@ export function parseGitHubContext(): GitHubContext { inputs: { mode: modeInput, // v1.0: Unified prompt field with fallback to legacy fields - prompt: process.env.PROMPT || - process.env.OVERRIDE_PROMPT || - process.env.DIRECT_PROMPT || "", + prompt: + process.env.PROMPT || + process.env.OVERRIDE_PROMPT || + process.env.DIRECT_PROMPT || + "", triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude", assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "", labelTrigger: process.env.LABEL_TRIGGER ?? "", diff --git a/src/modes/detector.ts b/src/modes/detector.ts index cd0c8ad7..dd1ea572 100644 --- a/src/modes/detector.ts +++ b/src/modes/detector.ts @@ -74,4 +74,4 @@ export function getDefaultPromptForMode( default: return undefined; } -} \ No newline at end of file +} diff --git a/src/modes/registry.ts b/src/modes/registry.ts index f3003eba..b5523e0b 100644 --- a/src/modes/registry.ts +++ b/src/modes/registry.ts @@ -30,10 +30,7 @@ const modes = { * @param explicitMode Optional explicit mode override (for backward compatibility) * @returns The appropriate mode for the context */ -export function getMode( - context: GitHubContext, - explicitMode?: string, -): Mode { +export function getMode(context: GitHubContext, explicitMode?: string): Mode { let modeName: AutoDetectedMode; if (explicitMode && isValidModeV1(explicitMode)) { @@ -41,7 +38,9 @@ export function getMode( modeName = mapLegacyMode(explicitMode); } else { modeName = detectMode(context); - console.log(`Auto-detected mode: ${modeName} for event: ${context.eventName}`); + console.log( + `Auto-detected mode: ${modeName} for event: ${context.eventName}`, + ); } const mode = modes[modeName]; diff --git a/src/modes/review/index.ts b/src/modes/review/index.ts index d8c63fed..6103c0b6 100644 --- a/src/modes/review/index.ts +++ b/src/modes/review/index.ts @@ -94,7 +94,7 @@ export const reviewMode: Mode = { if (userPrompt) { return userPrompt; } - + // Default to /review slash command content // This will be expanded by the slash command system diff --git a/src/slash-commands/loader.ts b/src/slash-commands/loader.ts index b9685f23..a79b27ea 100644 --- a/src/slash-commands/loader.ts +++ b/src/slash-commands/loader.ts @@ -35,11 +35,11 @@ export async function resolveSlashCommand( 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 { @@ -52,10 +52,7 @@ export async function resolveSlashCommand( let expandedContent = command.content; if (args.length > 0) { - expandedContent = expandedContent.replace( - /\{args\}/g, - args.join(" "), - ); + expandedContent = expandedContent.replace(/\{args\}/g, args.join(" ")); } if (variables) { @@ -107,7 +104,7 @@ function parseCommandFile(name: string, content: string): SlashCommand { if (frontmatterMatch && frontmatterMatch[1]) { try { const parsedYaml = yaml.load(frontmatterMatch[1]); - if (parsedYaml && typeof parsedYaml === 'object') { + if (parsedYaml && typeof parsedYaml === "object") { metadata = parsedYaml as SlashCommandMetadata; } commandContent = frontmatterMatch[2]?.trim() || content; @@ -164,4 +161,4 @@ function handleLegacyPrompts(prompt: string): ResolvedCommand { } } return { expandedPrompt: prompt }; -} \ No newline at end of file +} diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 4a8eb905..7823e43e 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -157,7 +157,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("You are Claude, an AI assistant"); expect(prompt).toContain("GENERAL_COMMENT"); @@ -185,7 +190,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("PR_REVIEW"); expect(prompt).toContain("true"); @@ -211,7 +221,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("ISSUE_CREATED"); expect(prompt).toContain( @@ -239,7 +254,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("ISSUE_ASSIGNED"); expect(prompt).toContain( @@ -266,7 +286,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("ISSUE_LABELED"); expect(prompt).toContain( @@ -293,9 +318,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = generateDefaultPrompt(envVars, mockGitHubData, false); expect(prompt).toContain(""); + expect(prompt).toContain( + "IMPORTANT: The following are direct instructions", + ); expect(prompt).toContain("Fix the bug in the login form"); expect(prompt).toContain(""); expect(prompt).toContain( @@ -316,7 +344,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("PULL_REQUEST"); expect(prompt).toContain("true"); @@ -341,7 +374,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("CUSTOM INSTRUCTIONS:\nAlways use TypeScript"); }); @@ -360,7 +398,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toBe("Simple prompt for owner/repo PR #123"); expect(prompt).not.toContain("You are Claude, an AI assistant"); @@ -395,7 +438,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("Repository: test/repo"); expect(prompt).toContain("PR: 456"); @@ -442,7 +490,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, issueGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + issueGitHubData, + false, + mockTagMode, + ); expect(prompt).toBe("Issue #789: Bug: Login form broken in owner/repo"); }); @@ -462,7 +515,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toBe("PR: 123, Issue: , Comment: "); }); @@ -482,7 +540,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("You are Claude, an AI assistant"); expect(prompt).toContain("ISSUE_CREATED"); @@ -505,7 +568,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); expect(prompt).toContain("johndoe"); // With commit signing disabled, co-author info appears in git commit instructions @@ -527,7 +595,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain PR-specific instructions (git commands when not using signing) expect(prompt).toContain("git push"); @@ -558,7 +631,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain Issue-specific instructions expect(prompt).toContain( @@ -597,7 +675,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain the actual branch name with timestamp expect(prompt).toContain( @@ -627,7 +710,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain branch-specific instructions like issues expect(prompt).toContain( @@ -665,7 +753,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain open PR instructions (git commands when not using signing) expect(prompt).toContain("git push"); @@ -696,7 +789,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain new branch instructions expect(prompt).toContain( @@ -724,7 +822,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain new branch instructions expect(prompt).toContain( @@ -752,7 +855,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should contain new branch instructions expect(prompt).toContain( @@ -776,7 +884,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, false, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + false, + mockTagMode, + ); // Should have git command instructions expect(prompt).toContain("Use git commands via the Bash tool"); @@ -805,7 +918,12 @@ describe("generatePrompt", () => { }, }; - const prompt = await generatePrompt(envVars, mockGitHubData, true, mockTagMode); + const prompt = await generatePrompt( + envVars, + mockGitHubData, + true, + mockTagMode, + ); // Should have commit signing tool instructions expect(prompt).toContain("mcp__github_file_ops__commit_files"); diff --git a/test/modes/registry.test.ts b/test/modes/registry.test.ts index ae4848f4..9787394c 100644 --- a/test/modes/registry.test.ts +++ b/test/modes/registry.test.ts @@ -7,6 +7,12 @@ import { createMockContext, createMockAutomationContext } from "../mockContext"; describe("Mode Registry", () => { const mockContext = createMockContext({ eventName: "issue_comment", + payload: { + action: "created", + comment: { + body: "Test comment without trigger", + }, + } as any, }); const mockWorkflowDispatchContext = createMockAutomationContext({ @@ -17,9 +23,9 @@ describe("Mode Registry", () => { eventName: "schedule", }); - test("getMode auto-detects tag mode for issue_comment", () => { + test("getMode auto-detects agent mode for issue_comment without trigger", () => { const mode = getMode(mockContext); - // Issue comment without trigger won't activate tag mode, defaults to agent + // Agent mode is the default when no trigger is found expect(mode).toBe(agentMode); expect(mode.name).toBe("agent"); }); @@ -33,7 +39,7 @@ describe("Mode Registry", () => { test("getMode can use explicit mode override for review", () => { const mode = getMode(mockContext, "review"); expect(mode).toBe(reviewMode); - expect(mode.name).toBe("review"); + expect(mode.name).toBe("experimental-review"); }); test("getMode auto-detects agent for workflow_dispatch", () => { @@ -51,7 +57,7 @@ describe("Mode Registry", () => { test("getMode supports legacy experimental-review mode name", () => { const mode = getMode(mockContext, "experimental-review"); expect(mode).toBe(reviewMode); - expect(mode.name).toBe("review"); + expect(mode.name).toBe("experimental-review"); }); test("getMode auto-detects review mode for PR opened", () => { @@ -62,13 +68,14 @@ describe("Mode Registry", () => { }); const mode = getMode(prContext); expect(mode).toBe(reviewMode); - expect(mode.name).toBe("agent"); + expect(mode.name).toBe("experimental-review"); }); - test("getMode throws error for invalid mode override", () => { - expect(() => getMode(mockContext, "invalid")).toThrow( - "Mode 'agent' not found. This should not happen. Please report this issue.", - ); + test("getMode falls back to auto-detection for invalid mode override", () => { + const mode = getMode(mockContext, "invalid"); + // Should fall back to auto-detection, which returns agent for issue_comment without trigger + expect(mode).toBe(agentMode); + expect(mode.name).toBe("agent"); }); test("isValidMode returns true for all valid modes", () => {