test + formatting fixes

This commit is contained in:
km-anthropic
2025-08-07 00:27:35 -07:00
parent 9a665625f7
commit da182b6afb
9 changed files with 202 additions and 68 deletions

View File

@@ -534,14 +534,21 @@ export async function generatePrompt(
mode: Mode, mode: Mode,
): Promise<string> { ): Promise<string> {
// Check for unified prompt field first (v1.0) // 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 // Handle slash commands
if (prompt.startsWith("/")) { if (prompt.startsWith("/")) {
const variables = { const variables = {
repository: context.repository, repository: context.repository,
pr_number: context.eventData.isPR && 'prNumber' in context.eventData ? context.eventData.prNumber : "", pr_number:
issue_number: !context.eventData.isPR && 'issueNumber' in context.eventData ? context.eventData.issueNumber : "", 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 || "", branch: context.eventData.claudeBranch || "",
base_branch: context.eventData.baseBranch || "", base_branch: context.eventData.baseBranch || "",
trigger_user: context.triggerUsername, trigger_user: context.triggerUsername,
@@ -553,13 +560,18 @@ export async function generatePrompt(
if (resolved.tools && resolved.tools.length > 0) { if (resolved.tools && resolved.tools.length > 0) {
const currentAllowedTools = process.env.ALLOWED_TOOLS || ""; const currentAllowedTools = process.env.ALLOWED_TOOLS || "";
const newTools = resolved.tools.join(","); const newTools = resolved.tools.join(",");
const combinedTools = currentAllowedTools ? `${currentAllowedTools},${newTools}` : newTools; const combinedTools = currentAllowedTools
? `${currentAllowedTools},${newTools}`
: newTools;
core.exportVariable("ALLOWED_TOOLS", combinedTools); core.exportVariable("ALLOWED_TOOLS", combinedTools);
} }
// Apply any settings from the slash command // Apply any settings from the slash command
if (resolved.settings) { if (resolved.settings) {
core.exportVariable("SLASH_COMMAND_SETTINGS", JSON.stringify(resolved.settings)); core.exportVariable(
"SLASH_COMMAND_SETTINGS",
JSON.stringify(resolved.settings),
);
} }
prompt = resolved.expandedPrompt; prompt = resolved.expandedPrompt;

View File

@@ -29,9 +29,7 @@ async function run() {
// For review mode, use the default GitHub Action token // For review mode, use the default GitHub Action token
githubToken = process.env.DEFAULT_WORKFLOW_TOKEN || ""; githubToken = process.env.DEFAULT_WORKFLOW_TOKEN || "";
if (!githubToken) { if (!githubToken) {
throw new Error( throw new Error("DEFAULT_WORKFLOW_TOKEN not found for review mode");
"DEFAULT_WORKFLOW_TOKEN not found for review mode",
);
} }
console.log("Using default GitHub Action token for review mode"); console.log("Using default GitHub Action token for review mode");
core.setOutput("GITHUB_TOKEN", githubToken); core.setOutput("GITHUB_TOKEN", githubToken);
@@ -41,7 +39,6 @@ async function run() {
} }
const octokit = createOctokit(githubToken); const octokit = createOctokit(githubToken);
// Step 3: Check write permissions (only for entity contexts) // Step 3: Check write permissions (only for entity contexts)
if (isEntityContext(context)) { if (isEntityContext(context)) {
const hasWritePermissions = await checkWritePermissions( const hasWritePermissions = await checkWritePermissions(

View File

@@ -106,7 +106,9 @@ export function parseGitHubContext(): GitHubContext {
const context = github.context; const context = github.context;
// Mode is optional in v1.0 (auto-detected) // 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 = { const commonFields = {
runId: process.env.GITHUB_RUN_ID!, runId: process.env.GITHUB_RUN_ID!,
@@ -120,9 +122,11 @@ export function parseGitHubContext(): GitHubContext {
inputs: { inputs: {
mode: modeInput, mode: modeInput,
// v1.0: Unified prompt field with fallback to legacy fields // v1.0: Unified prompt field with fallback to legacy fields
prompt: process.env.PROMPT || prompt:
process.env.OVERRIDE_PROMPT || process.env.PROMPT ||
process.env.DIRECT_PROMPT || "", process.env.OVERRIDE_PROMPT ||
process.env.DIRECT_PROMPT ||
"",
triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude", triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude",
assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "", assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "",
labelTrigger: process.env.LABEL_TRIGGER ?? "", labelTrigger: process.env.LABEL_TRIGGER ?? "",

View File

@@ -30,10 +30,7 @@ const modes = {
* @param explicitMode Optional explicit mode override (for backward compatibility) * @param explicitMode Optional explicit mode override (for backward compatibility)
* @returns The appropriate mode for the context * @returns The appropriate mode for the context
*/ */
export function getMode( export function getMode(context: GitHubContext, explicitMode?: string): Mode {
context: GitHubContext,
explicitMode?: string,
): Mode {
let modeName: AutoDetectedMode; let modeName: AutoDetectedMode;
if (explicitMode && isValidModeV1(explicitMode)) { if (explicitMode && isValidModeV1(explicitMode)) {
@@ -41,7 +38,9 @@ export function getMode(
modeName = mapLegacyMode(explicitMode); modeName = mapLegacyMode(explicitMode);
} else { } else {
modeName = detectMode(context); 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]; const mode = modes[modeName];

View File

@@ -52,10 +52,7 @@ export async function resolveSlashCommand(
let expandedContent = command.content; let expandedContent = command.content;
if (args.length > 0) { if (args.length > 0) {
expandedContent = expandedContent.replace( expandedContent = expandedContent.replace(/\{args\}/g, args.join(" "));
/\{args\}/g,
args.join(" "),
);
} }
if (variables) { if (variables) {
@@ -107,7 +104,7 @@ function parseCommandFile(name: string, content: string): SlashCommand {
if (frontmatterMatch && frontmatterMatch[1]) { if (frontmatterMatch && frontmatterMatch[1]) {
try { try {
const parsedYaml = yaml.load(frontmatterMatch[1]); const parsedYaml = yaml.load(frontmatterMatch[1]);
if (parsedYaml && typeof parsedYaml === 'object') { if (parsedYaml && typeof parsedYaml === "object") {
metadata = parsedYaml as SlashCommandMetadata; metadata = parsedYaml as SlashCommandMetadata;
} }
commandContent = frontmatterMatch[2]?.trim() || content; commandContent = frontmatterMatch[2]?.trim() || content;

View File

@@ -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("You are Claude, an AI assistant");
expect(prompt).toContain("<event_type>GENERAL_COMMENT</event_type>"); expect(prompt).toContain("<event_type>GENERAL_COMMENT</event_type>");
@@ -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("<event_type>PR_REVIEW</event_type>"); expect(prompt).toContain("<event_type>PR_REVIEW</event_type>");
expect(prompt).toContain("<is_pr>true</is_pr>"); expect(prompt).toContain("<is_pr>true</is_pr>");
@@ -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("<event_type>ISSUE_CREATED</event_type>"); expect(prompt).toContain("<event_type>ISSUE_CREATED</event_type>");
expect(prompt).toContain( 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("<event_type>ISSUE_ASSIGNED</event_type>"); expect(prompt).toContain("<event_type>ISSUE_ASSIGNED</event_type>");
expect(prompt).toContain( 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("<event_type>ISSUE_LABELED</event_type>"); expect(prompt).toContain("<event_type>ISSUE_LABELED</event_type>");
expect(prompt).toContain( 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("<direct_prompt>"); expect(prompt).toContain("<direct_prompt>");
expect(prompt).toContain(
"IMPORTANT: The following are direct instructions",
);
expect(prompt).toContain("Fix the bug in the login form"); expect(prompt).toContain("Fix the bug in the login form");
expect(prompt).toContain("</direct_prompt>"); expect(prompt).toContain("</direct_prompt>");
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("<event_type>PULL_REQUEST</event_type>"); expect(prompt).toContain("<event_type>PULL_REQUEST</event_type>");
expect(prompt).toContain("<is_pr>true</is_pr>"); expect(prompt).toContain("<is_pr>true</is_pr>");
@@ -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"); 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).toBe("Simple prompt for owner/repo PR #123");
expect(prompt).not.toContain("You are Claude, an AI assistant"); 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("Repository: test/repo");
expect(prompt).toContain("PR: 456"); 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"); 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: "); 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("You are Claude, an AI assistant");
expect(prompt).toContain("<event_type>ISSUE_CREATED</event_type>"); expect(prompt).toContain("<event_type>ISSUE_CREATED</event_type>");
@@ -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("<trigger_username>johndoe</trigger_username>"); expect(prompt).toContain("<trigger_username>johndoe</trigger_username>");
// With commit signing disabled, co-author info appears in git commit instructions // 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) // Should contain PR-specific instructions (git commands when not using signing)
expect(prompt).toContain("git push"); 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 // Should contain Issue-specific instructions
expect(prompt).toContain( 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 // Should contain the actual branch name with timestamp
expect(prompt).toContain( 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 // Should contain branch-specific instructions like issues
expect(prompt).toContain( 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) // Should contain open PR instructions (git commands when not using signing)
expect(prompt).toContain("git push"); 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 // Should contain new branch instructions
expect(prompt).toContain( 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 // Should contain new branch instructions
expect(prompt).toContain( 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 // Should contain new branch instructions
expect(prompt).toContain( 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 // Should have git command instructions
expect(prompt).toContain("Use git commands via the Bash tool"); 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 // Should have commit signing tool instructions
expect(prompt).toContain("mcp__github_file_ops__commit_files"); expect(prompt).toContain("mcp__github_file_ops__commit_files");

View File

@@ -7,6 +7,12 @@ import { createMockContext, createMockAutomationContext } from "../mockContext";
describe("Mode Registry", () => { describe("Mode Registry", () => {
const mockContext = createMockContext({ const mockContext = createMockContext({
eventName: "issue_comment", eventName: "issue_comment",
payload: {
action: "created",
comment: {
body: "Test comment without trigger",
},
} as any,
}); });
const mockWorkflowDispatchContext = createMockAutomationContext({ const mockWorkflowDispatchContext = createMockAutomationContext({
@@ -17,9 +23,9 @@ describe("Mode Registry", () => {
eventName: "schedule", 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); 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).toBe(agentMode);
expect(mode.name).toBe("agent"); expect(mode.name).toBe("agent");
}); });
@@ -33,7 +39,7 @@ describe("Mode Registry", () => {
test("getMode can use explicit mode override for review", () => { test("getMode can use explicit mode override for review", () => {
const mode = getMode(mockContext, "review"); const mode = getMode(mockContext, "review");
expect(mode).toBe(reviewMode); expect(mode).toBe(reviewMode);
expect(mode.name).toBe("review"); expect(mode.name).toBe("experimental-review");
}); });
test("getMode auto-detects agent for workflow_dispatch", () => { test("getMode auto-detects agent for workflow_dispatch", () => {
@@ -51,7 +57,7 @@ describe("Mode Registry", () => {
test("getMode supports legacy experimental-review mode name", () => { test("getMode supports legacy experimental-review mode name", () => {
const mode = getMode(mockContext, "experimental-review"); const mode = getMode(mockContext, "experimental-review");
expect(mode).toBe(reviewMode); expect(mode).toBe(reviewMode);
expect(mode.name).toBe("review"); expect(mode.name).toBe("experimental-review");
}); });
test("getMode auto-detects review mode for PR opened", () => { test("getMode auto-detects review mode for PR opened", () => {
@@ -62,13 +68,14 @@ describe("Mode Registry", () => {
}); });
const mode = getMode(prContext); const mode = getMode(prContext);
expect(mode).toBe(reviewMode); expect(mode).toBe(reviewMode);
expect(mode.name).toBe("agent"); expect(mode.name).toBe("experimental-review");
}); });
test("getMode throws error for invalid mode override", () => { test("getMode falls back to auto-detection for invalid mode override", () => {
expect(() => getMode(mockContext, "invalid")).toThrow( const mode = getMode(mockContext, "invalid");
"Mode 'agent' not found. This should not happen. Please report this issue.", // 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", () => { test("isValidMode returns true for all valid modes", () => {