diff --git a/actions/setup/js/checkout_pr_branch.cjs b/actions/setup/js/checkout_pr_branch.cjs index 4506e3b837..0add419699 100644 --- a/actions/setup/js/checkout_pr_branch.cjs +++ b/actions/setup/js/checkout_pr_branch.cjs @@ -26,7 +26,7 @@ */ const { getErrorMessage } = require("./error_helpers.cjs"); -const { renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); const { detectForkPR } = require("./pr_helpers.cjs"); const { ERR_API } = require("./error_codes.cjs"); @@ -323,7 +323,7 @@ Pull request #${pullRequest.number} was merged after this workflow was triggered core.setOutput("checkout_pr_success", "false"); // Load and render step summary template - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/pr_checkout_failure.md`; + const templatePath = getPromptPath("pr_checkout_failure.md"); const summaryContent = renderTemplateFromFile(templatePath, { error_message: errorMsg, }); diff --git a/actions/setup/js/checkout_pr_branch.test.cjs b/actions/setup/js/checkout_pr_branch.test.cjs index 224f3b01cb..ddb937d729 100644 --- a/actions/setup/js/checkout_pr_branch.test.cjs +++ b/actions/setup/js/checkout_pr_branch.test.cjs @@ -121,6 +121,7 @@ describe("checkout_pr_branch.cjs", () => { } if (module === "./messages_core.cjs") { return { + getPromptPath: name => `/mock/prompts/${name}`, renderTemplateFromFile: (templatePath, context) => { const template = mockRequire("fs").readFileSync(templatePath, "utf8"); return template.replace(/\{(\w+)\}/g, (match, key) => { diff --git a/actions/setup/js/create_missing_data_issue.cjs b/actions/setup/js/create_missing_data_issue.cjs index c5d1f3f34a..c08dfd6038 100644 --- a/actions/setup/js/create_missing_data_issue.cjs +++ b/actions/setup/js/create_missing_data_issue.cjs @@ -2,6 +2,7 @@ /// const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs"); +const { getPromptPath } = require("./messages_core.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -20,7 +21,7 @@ const main = buildMissingIssueHandler({ defaultTitlePrefix: "[missing data]", defaultLabels: ["agentic-workflows"], itemsField: "missing_data", - templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_data_issue.md`, + templatePath: getPromptPath("missing_data_issue.md"), templateListKey: "missing_data_list", buildCommentHeader: runUrl => [`## Missing Data Reported`, ``, `The following data was reported as missing during [workflow run](${runUrl}):`, ``], renderCommentItem: (item, index) => { diff --git a/actions/setup/js/create_missing_tool_issue.cjs b/actions/setup/js/create_missing_tool_issue.cjs index 14ce8af1bb..4df226438a 100644 --- a/actions/setup/js/create_missing_tool_issue.cjs +++ b/actions/setup/js/create_missing_tool_issue.cjs @@ -2,6 +2,7 @@ /// const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs"); +const { getPromptPath } = require("./messages_core.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -20,7 +21,7 @@ const main = buildMissingIssueHandler({ defaultTitlePrefix: "[missing tool]", defaultLabels: ["agentic-workflows"], itemsField: "missing_tools", - templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_tool_issue.md`, + templatePath: getPromptPath("missing_tool_issue.md"), templateListKey: "missing_tools_list", buildCommentHeader: runUrl => [`## Missing Tools Reported`, ``, `The following tools were reported as missing during [workflow run](${runUrl}):`, ``], renderCommentItem: (tool, index) => { diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index bbee0b6bbe..d90ac8d2d1 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -26,7 +26,7 @@ const { getBaseBranch } = require("./get_base_branch.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { checkFileProtection } = require("./manifest_file_helpers.cjs"); -const { renderTemplateFromFile, buildProtectedFileList, encodePathSegments } = require("./messages_core.cjs"); +const { renderTemplateFromFile, buildProtectedFileList, encodePathSegments, getPromptPath } = require("./messages_core.cjs"); const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL, MAX_ASSIGNEES } = require("./constants.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); const { withRetry, isTransientError } = require("./error_recovery.cjs"); @@ -1617,7 +1617,7 @@ ${patchPreview}`; // Use the push-failed template with artifact download instructions. const runId = context.runId; const patchFileName = patchFilePath ? patchFilePath.replace("/tmp/gh-aw/", "") : "aw-unknown.patch"; - const pushFailedTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/manifest_protection_push_failed_fallback.md`; + const pushFailedTemplatePath = getPromptPath("manifest_protection_push_failed_fallback.md"); fallbackBody = renderTemplateFromFile(pushFailedTemplatePath, { main_body: mainBodyContent, footer: footerContent, @@ -1634,7 +1634,7 @@ ${patchPreview}`; const encodedBase = encodePathSegments(baseBranch); const encodedHead = encodePathSegments(branchName); const createPrUrl = `${githubServer}/${repoParts.owner}/${repoParts.repo}/compare/${encodedBase}...${encodedHead}?expand=1&title=${encodeURIComponent(title)}`; - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/manifest_protection_create_pr_fallback.md`; + const templatePath = getPromptPath("manifest_protection_create_pr_fallback.md"); fallbackBody = renderTemplateFromFile(templatePath, { main_body: mainBodyContent, footer: footerContent, @@ -1834,7 +1834,7 @@ ${patchPreview}`; patchPreview = generatePatchPreview(patchContent); } - const fallbackTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/pr_permission_denied_fallback.md`; + const fallbackTemplatePath = getPromptPath("pr_permission_denied_fallback.md"); const fallbackBody = renderTemplateFromFile(fallbackTemplatePath, { body, branch_name: branchName, diff --git a/actions/setup/js/create_report_incomplete_issue.cjs b/actions/setup/js/create_report_incomplete_issue.cjs index 63cca4ffc7..09001cbc78 100644 --- a/actions/setup/js/create_report_incomplete_issue.cjs +++ b/actions/setup/js/create_report_incomplete_issue.cjs @@ -2,6 +2,7 @@ /// const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs"); +const { getPromptPath } = require("./messages_core.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -22,7 +23,7 @@ const main = buildMissingIssueHandler({ defaultTitlePrefix: "[incomplete]", defaultLabels: ["agentic-workflows"], itemsField: "incomplete_signals", - templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_tool_issue.md`, + templatePath: getPromptPath("missing_tool_issue.md"), templateListKey: "incomplete_signals_list", buildCommentHeader: runUrl => [`## Incomplete Run Reported`, ``, `The agent reported that the task could not be completed during [workflow run](${runUrl}):`, ``], renderCommentItem: (item, index) => { diff --git a/actions/setup/js/firewall_blocked_domains.cjs b/actions/setup/js/firewall_blocked_domains.cjs index d9cd415042..eb5dbc07ca 100644 --- a/actions/setup/js/firewall_blocked_domains.cjs +++ b/actions/setup/js/firewall_blocked_domains.cjs @@ -11,7 +11,7 @@ const fs = require("fs"); const path = require("path"); const { sanitizeDomainName } = require("./sanitize_content_core.cjs"); -const { renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); const { renderMarkdownTemplate } = require("./render_template.cjs"); /** @@ -207,10 +207,11 @@ function generateBlockedDomainsSection(blockedDomains, templatePath) { const hasGitHubApiBlocked = blockedDomains.includes("api.github.com"); - // Resolve template path: explicit > RUNNER_TEMP (production) > source tree (local dev/test) + // Resolve template path: explicit > GH_AW_PROMPTS_DIR / RUNNER_TEMP (production) > source tree (local dev/test) let resolvedTemplatePath = templatePath; if (!resolvedTemplatePath) { - resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md"); + const hasRuntimePromptsDir = !!(process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR); + resolvedTemplatePath = hasRuntimePromptsDir ? getPromptPath("firewall_blocked_domains.md") : path.join(__dirname, "../md/firewall_blocked_domains.md"); } // First pass: substitute {key} placeholders. diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index cc68ab0825..ef87832807 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -4,7 +4,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getDetectionCautionAlert, getFooterAgentFailureIssueMessage, getFooterAgentFailureCommentMessage, generateXMLMarker } = require("./messages.cjs"); -const { renderTemplate, renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplate, renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); const { getCurrentBranch } = require("./get_current_branch.cjs"); const { createExpirationLine, generateFooterWithExpiration } = require("./ephemerals.cjs"); const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs"); @@ -618,7 +618,7 @@ function buildMissingDataContext(cacheMemoryEnabled) { const hasCacheMiss = missingDataMessages.some(m => m.reason === "cache_memory_miss"); if (cacheMemoryEnabled && hasCacheMiss) { core.info("Cache-miss detected despite cache-memory being available — likely a configuration problem"); - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/cache_memory_miss.md`; + const templatePath = getPromptPath("cache_memory_miss.md"); context += "\n" + renderTemplateFromFile(templatePath, {}) + "\n"; } @@ -697,7 +697,7 @@ function buildTimeoutContext(isTimedOut, timeoutMinutes) { const currentMinutes = parseInt(timeoutMinutes || "20", 10); const suggestedMinutes = currentMinutes + 10; - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_timeout.md`; + const templatePath = getPromptPath("agent_timeout.md"); return "\n" + renderTemplateFromFile(templatePath, { current_minutes: currentMinutes, suggested_minutes: suggestedMinutes }); } @@ -711,7 +711,7 @@ function buildInferenceAccessErrorContext(hasInferenceAccessError) { return ""; } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/inference_access_error.md`; + const templatePath = getPromptPath("inference_access_error.md"); const template = fs.readFileSync(templatePath, "utf8"); return "\n" + template; } @@ -727,7 +727,7 @@ function buildMCPPolicyErrorContext(hasMCPPolicyError) { return ""; } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/mcp_policy_error.md`; + const templatePath = getPromptPath("mcp_policy_error.md"); try { const template = fs.readFileSync(templatePath, "utf8"); return "\n" + template; @@ -752,7 +752,7 @@ function buildModelNotSupportedErrorContext(hasModelNotSupportedError) { return ""; } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/model_not_supported_error.md`; + const templatePath = getPromptPath("model_not_supported_error.md"); try { const template = fs.readFileSync(templatePath, "utf8"); return "\n" + template; @@ -790,7 +790,7 @@ function buildLockdownCheckFailedContext(hasLockdownCheckFailed) { return ""; } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/lockdown_check_failed.md`; + const templatePath = getPromptPath("lockdown_check_failed.md"); const template = fs.readFileSync(templatePath, "utf8"); return "\n" + template; } @@ -807,7 +807,7 @@ function buildStaleLockFileFailedContext(hasStaleLockFileFailed) { return ""; } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/stale_lock_file_failed.md`; + const templatePath = getPromptPath("stale_lock_file_failed.md"); const template = fs.readFileSync(templatePath, "utf8"); return "\n" + template; } @@ -837,7 +837,7 @@ function buildAssignCopilotFailureContext(hasAssignCopilotFailures, assignCopilo } } - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/assign_copilot_to_created_issues_failure.md`; + const templatePath = getPromptPath("assign_copilot_to_created_issues_failure.md"); return "\n" + renderTemplateFromFile(templatePath, { issues: issueList }); } @@ -915,7 +915,7 @@ function buildEngineFailureContext() { const hasCyberPolicyViolation = Array.from(errorMessages).some(msg => msg.includes("cyber_policy_violation")); if (hasCyberPolicyViolation) { core.info("Detected cyber_policy_violation error — using dedicated context message"); - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/cyber_policy_violation.md`; + const templatePath = getPromptPath("cyber_policy_violation.md"); try { return "\n" + renderTemplateFromFile(templatePath, {}); } catch { @@ -1263,7 +1263,7 @@ async function main() { core.info(`Found existing issue #${existingIssue.number}: ${existingIssue.html_url}`); // Read comment template - const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_failure_comment.md`; + const commentTemplatePath = getPromptPath("agent_failure_comment.md"); const commentTemplate = fs.readFileSync(commentTemplatePath, "utf8"); // Extract run ID from URL (e.g., https://github.com/owner/repo/actions/runs/123 -> "123") @@ -1428,7 +1428,7 @@ async function main() { core.info("No existing issue found, creating a new one"); // Read issue template - const issueTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_failure_issue.md`; + const issueTemplatePath = getPromptPath("agent_failure_issue.md"); const issueTemplate = fs.readFileSync(issueTemplatePath, "utf8"); // Get current branch information diff --git a/actions/setup/js/handle_agent_failure.test.cjs b/actions/setup/js/handle_agent_failure.test.cjs index 4934ffdd4a..acc08845cd 100644 --- a/actions/setup/js/handle_agent_failure.test.cjs +++ b/actions/setup/js/handle_agent_failure.test.cjs @@ -505,6 +505,7 @@ describe("handle_agent_failure", () => { beforeEach(() => { vi.resetModules(); + process.env.RUNNER_TEMP = "/nonexistent"; // Stub readFileSync so the runtime path resolves to the source-tree template fs.readFileSync = (filePath, encoding) => { if (typeof filePath === "string" && filePath.includes("lockdown_check_failed.md")) { @@ -517,6 +518,7 @@ describe("handle_agent_failure", () => { afterEach(() => { fs.readFileSync = originalReadFileSync; + delete process.env.RUNNER_TEMP; }); it("returns empty string when no failure", () => { @@ -553,6 +555,7 @@ describe("handle_agent_failure", () => { beforeEach(() => { vi.resetModules(); + process.env.RUNNER_TEMP = "/nonexistent"; fs.readFileSync = (filePath, encoding) => { if (typeof filePath === "string" && filePath.includes("stale_lock_file_failed.md")) { return templateContent; @@ -564,6 +567,7 @@ describe("handle_agent_failure", () => { afterEach(() => { fs.readFileSync = originalReadFileSync; + delete process.env.RUNNER_TEMP; }); it("returns empty string when check did not fail", () => { @@ -605,6 +609,7 @@ describe("handle_agent_failure", () => { beforeEach(() => { vi.resetModules(); + process.env.RUNNER_TEMP = "/nonexistent"; // Stub readFileSync so the runtime path resolves to the source-tree template fs.readFileSync = (filePath, encoding) => { if (typeof filePath === "string" && filePath.includes("agent_timeout.md")) { @@ -617,6 +622,7 @@ describe("handle_agent_failure", () => { afterEach(() => { fs.readFileSync = originalReadFileSync; + delete process.env.RUNNER_TEMP; }); it("returns empty string when not timed out", () => { diff --git a/actions/setup/js/handle_detection_runs.cjs b/actions/setup/js/handle_detection_runs.cjs index ff86d0e3de..91fe932988 100644 --- a/actions/setup/js/handle_detection_runs.cjs +++ b/actions/setup/js/handle_detection_runs.cjs @@ -6,7 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { ERR_API } = require("./error_codes.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { generateFooterWithExpiration } = require("./ephemerals.cjs"); -const { renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); const { getEffectiveTokensSuffix } = require("./effective_tokens.cjs"); /** @@ -46,7 +46,7 @@ async function ensureDetectionRunsIssue() { core.info(`No detection runs issue found, creating one`); // Load template from file - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/detection_runs_issue.md`; + const templatePath = getPromptPath("detection_runs_issue.md"); const parentBodyContent = fs.readFileSync(templatePath, "utf8"); const parentBody = generateFooterWithExpiration({ @@ -107,7 +107,7 @@ async function main() { } // Load and render comment template from file - const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/detection_runs_comment.md`; + const commentTemplatePath = getPromptPath("detection_runs_comment.md"); // Compute effective tokens suffix from environment variable (set by parse_token_usage.cjs / parse_mcp_gateway_log.cjs) const effectiveTokensSuffix = getEffectiveTokensSuffix(); diff --git a/actions/setup/js/handle_noop_message.cjs b/actions/setup/js/handle_noop_message.cjs index e583b6e310..bd522b7698 100644 --- a/actions/setup/js/handle_noop_message.cjs +++ b/actions/setup/js/handle_noop_message.cjs @@ -6,7 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { ERR_API } = require("./error_codes.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { generateFooterWithExpiration } = require("./ephemerals.cjs"); -const { renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); const { loadAgentOutput } = require("./load_agent_output.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); const { getEffectiveTokensSuffix } = require("./effective_tokens.cjs"); @@ -48,7 +48,7 @@ async function ensureAgentRunsIssue() { core.info(`No no-op runs issue found, creating one`); // Load template from file - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/noop_runs_issue.md`; + const templatePath = getPromptPath("noop_runs_issue.md"); const parentBodyContent = fs.readFileSync(templatePath, "utf8"); const parentBody = generateFooterWithExpiration({ @@ -184,7 +184,7 @@ async function main() { } // Load and render comment template from file - const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/noop_comment.md`; + const commentTemplatePath = getPromptPath("noop_comment.md"); // Compute effective tokens suffix from environment variable (set by parse_token_usage.cjs / parse_mcp_gateway_log.cjs) const effectiveTokensSuffix = getEffectiveTokensSuffix(); diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index ad1c3e8880..646f467f6f 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -84,6 +84,22 @@ function renderTemplate(template, context) { }); } +/** + * Resolve the absolute path to a prompt template file. + * Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to + * ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production). + * Throws if neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set. + * @param {string} name - Template filename (e.g. "agent_timeout.md") + * @returns {string} Absolute path to the prompt template file + */ +function getPromptPath(name) { + const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null); + if (!promptsDir) { + throw new Error("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); + } + return `${promptsDir}/${name}`; +} + /** * Read a template file and render it with the given context. * Combines file loading and template rendering into a single helper. @@ -169,6 +185,7 @@ function buildProtectedFileList(files, githubServer, owner, repo, branch) { module.exports = { getMessages, + getPromptPath, renderTemplate, renderTemplateFromFile, toSnakeCase, diff --git a/actions/setup/js/messages_core.test.cjs b/actions/setup/js/messages_core.test.cjs index 078dc273b0..69ca483d64 100644 --- a/actions/setup/js/messages_core.test.cjs +++ b/actions/setup/js/messages_core.test.cjs @@ -26,6 +26,8 @@ describe("messages_core.cjs", () => { vi.clearAllMocks(); vi.resetModules(); delete process.env.GH_AW_SAFE_OUTPUT_MESSAGES; + delete process.env.GH_AW_PROMPTS_DIR; + delete process.env.RUNNER_TEMP; }); describe("renderTemplate", () => { @@ -181,6 +183,42 @@ describe("messages_core.cjs", () => { }); }); + describe("getPromptPath", () => { + it("should use RUNNER_TEMP when no override is set", async () => { + process.env.RUNNER_TEMP = "/tmp/runner"; + const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); + const result = getPromptPath("agent_timeout.md"); + expect(result).toBe("/tmp/runner/gh-aw/prompts/agent_timeout.md"); + }); + + it("should prefer GH_AW_PROMPTS_DIR over RUNNER_TEMP", async () => { + process.env.RUNNER_TEMP = "/tmp/runner"; + process.env.GH_AW_PROMPTS_DIR = "/custom/prompts"; + const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); + const result = getPromptPath("agent_timeout.md"); + expect(result).toBe("/custom/prompts/agent_timeout.md"); + }); + + it("should use GH_AW_PROMPTS_DIR when RUNNER_TEMP is not set", async () => { + process.env.GH_AW_PROMPTS_DIR = "/custom/prompts"; + const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); + const result = getPromptPath("cache_memory_miss.md"); + expect(result).toBe("/custom/prompts/cache_memory_miss.md"); + }); + + it("should include the template name in the path", async () => { + process.env.RUNNER_TEMP = "/tmp/runner"; + const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); + expect(getPromptPath("foo.md")).toBe("/tmp/runner/gh-aw/prompts/foo.md"); + expect(getPromptPath("bar.md")).toBe("/tmp/runner/gh-aw/prompts/bar.md"); + }); + + it("should throw when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set", async () => { + const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); + expect(() => getPromptPath("any.md")).toThrow("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); + }); + }); + describe("getMessages", () => { it("should return null when env var is not set", async () => { const { getMessages } = await import("./messages_core.cjs?" + Date.now()); diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 70d9bf14ba..7742d6585f 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -15,7 +15,7 @@ const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_help const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { checkFileProtection } = require("./manifest_file_helpers.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); -const { renderTemplateFromFile, buildProtectedFileList } = require("./messages_core.cjs"); +const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs"); const { getGitAuthEnv } = require("./git_helpers.cjs"); const { findRepoCheckout } = require("./find_repo_checkout.cjs"); @@ -467,7 +467,7 @@ async function main(config = {}) { const prUrl = `${githubServer}/${repoParts.owner}/${repoParts.repo}/pull/${pullNumber}`; const issueTitle = `[gh-aw] Protected Files: ${prTitle || `PR #${pullNumber}`}`; const fileList = buildProtectedFileList(protectedFilesForFallback, githubServer, repoParts.owner, repoParts.repo, branchName); - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/manifest_protection_push_to_pr_fallback.md`; + const templatePath = getPromptPath("manifest_protection_push_to_pr_fallback.md"); const issueBody = renderTemplateFromFile(templatePath, { files: fileList, pull_number: pullNumber, diff --git a/actions/setup/js/setup_threat_detection.cjs b/actions/setup/js/setup_threat_detection.cjs index 754f990171..febd94eb7b 100644 --- a/actions/setup/js/setup_threat_detection.cjs +++ b/actions/setup/js/setup_threat_detection.cjs @@ -17,6 +17,7 @@ const path = require("path"); const { checkFileExists } = require("./file_helpers.cjs"); const { AGENT_OUTPUT_FILENAME } = require("./constants.cjs"); const { ERR_VALIDATION } = require("./error_codes.cjs"); +const { getPromptPath } = require("./messages_core.cjs"); /** * Main entry point for setting up threat detection @@ -24,8 +25,7 @@ const { ERR_VALIDATION } = require("./error_codes.cjs"); */ async function main() { // Read the threat detection template from file - // At runtime, markdown files are copied to ${RUNNER_TEMP}/gh-aw/prompts/ by the setup action - const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/threat_detection.md`; + const templatePath = getPromptPath("threat_detection.md"); if (!fs.existsSync(templatePath)) { core.setFailed(`${ERR_VALIDATION}: Threat detection template not found at: ${templatePath}`); return;