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,42 +534,54 @@ export async function generatePrompt(
mode: Mode,
): Promise<string> {
// 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);
}

View File

@@ -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(

View File

@@ -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 ?? "",

View File

@@ -74,4 +74,4 @@ export function getDefaultPromptForMode(
default:
return undefined;
}
}
}

View File

@@ -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];

View File

@@ -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

View File

@@ -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 };
}
}

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("<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("<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(
@@ -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(
@@ -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(
@@ -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(
"IMPORTANT: The following are direct instructions",
);
expect(prompt).toContain("Fix the bug in the login form");
expect(prompt).toContain("</direct_prompt>");
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("<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");
});
@@ -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("<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>");
// 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");

View File

@@ -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", () => {