From 0c1626aaeee58b194a466e54b13f0c24a471ecc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:27:38 +0000 Subject: [PATCH 1/2] simplify: extract traverseObjectTree helper and hasLabelIntentMetadata in safe-output JS modules - ai_credits_context.cjs: extract shared traverseObjectTree() helper to eliminate three separate duplicated stack-based depth-first traversal loops (parseMaxAICredits, parseAICreditsErrorInfo, parseUnknownModelAICredits all shared identical traversal logic) - add_labels.cjs: extract hasLabelIntentMetadata() helper to consolidate three inline Boolean(spec.rationale || spec.confidence || spec.suggest) checks at lines 134, 135, 220 Behavior is unchanged. make build and make lint pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- actions/setup/js/add_labels.cjs | 14 +++-- actions/setup/js/ai_credits_context.cjs | 72 +++++++++++++------------ 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/actions/setup/js/add_labels.cjs b/actions/setup/js/add_labels.cjs index 4ebc617749b..03ca05050c0 100644 --- a/actions/setup/js/add_labels.cjs +++ b/actions/setup/js/add_labels.cjs @@ -35,6 +35,14 @@ const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs"); const { resolveInvocationContext } = require("./invocation_context_helpers.cjs"); const { normalizeIssueIntentLabelInputs } = require("./issue_intents.cjs"); +/** + * @param {{ rationale?: string, confidence?: string, suggest?: boolean } | null} spec + * @returns {boolean} + */ +function hasLabelIntentMetadata(spec) { + return Boolean(spec && (spec.rationale || spec.confidence || spec.suggest)); +} + /** * Main handler factory for add_labels * Uses shared count-gated scaffold for max-limit enforcement. @@ -131,8 +139,8 @@ const main = createCountGatedHandler({ } const key = label.name.toLowerCase(); const existing = requestedLabelSpecByLowerName.get(key); - const newHasMetadata = Boolean(label.rationale || label.confidence || label.suggest); - const existingHasMetadata = existing && Boolean(existing.rationale || existing.confidence || existing.suggest); + const newHasMetadata = hasLabelIntentMetadata(label); + const existingHasMetadata = hasLabelIntentMetadata(existing ?? null); if (!existing || (!existingHasMetadata && newHasMetadata)) { requestedLabelSpecByLowerName.set(key, label); } @@ -217,7 +225,7 @@ const main = createCountGatedHandler({ const labelsRequestPayload = uniqueLabels.map(name => { const labelSpec = requestedLabelSpecByLowerName.get(name.toLowerCase()) ?? { name }; - const hasIntentMetadata = Boolean(labelSpec.rationale || labelSpec.confidence || labelSpec.suggest); + const hasIntentMetadata = hasLabelIntentMetadata(labelSpec); return issueIntentEnabled && hasIntentMetadata ? labelSpec : labelSpec.name; }); diff --git a/actions/setup/js/ai_credits_context.cjs b/actions/setup/js/ai_credits_context.cjs index bd5611f543e..176ebf6a49c 100644 --- a/actions/setup/js/ai_credits_context.cjs +++ b/actions/setup/js/ai_credits_context.cjs @@ -94,24 +94,43 @@ function resolveFirewallAuditLogPath(auditJsonlPathOverride) { } /** + * Depth-first traversal of a nested object, calling visitor for each [key, value] pair. + * Traversal stops early if visitor returns true. + * * @param {unknown} entry - * @returns {string} + * @param {(key: string, value: unknown) => boolean | void} visitor - return true to stop early + * @returns {boolean} true if visitor stopped traversal early */ -function parseMaxAICreditsFromAuditEntry(entry) { - if (!entry || typeof entry !== "object") return ""; +function traverseObjectTree(entry, visitor) { + if (!entry || typeof entry !== "object") return false; const stack = [entry]; while (stack.length > 0) { const node = stack.pop(); if (!node || typeof node !== "object") continue; for (const [key, value] of Object.entries(node)) { - if (MAX_AI_CREDITS_FIELDS.has(key)) { - const parsed = parsePositiveNumberString(value); - if (parsed) return parsed; - } + if (visitor(key, value) === true) return true; if (value && typeof value === "object") stack.push(value); } } - return ""; + return false; +} + +/** + * @param {unknown} entry + * @returns {string} + */ +function parseMaxAICreditsFromAuditEntry(entry) { + let result = ""; + traverseObjectTree(entry, (key, value) => { + if (MAX_AI_CREDITS_FIELDS.has(key)) { + const parsed = parsePositiveNumberString(value); + if (parsed) { + result = parsed; + return true; + } + } + }); + return result; } /** @@ -119,25 +138,18 @@ function parseMaxAICreditsFromAuditEntry(entry) { * @returns {{ aiCredits: string, rateLimitError: boolean }} */ function parseAICreditsErrorInfoFromAuditEntry(entry) { - if (!entry || typeof entry !== "object") return { aiCredits: "", rateLimitError: false }; - const stack = [entry]; let aiCredits = ""; let rateLimitError = false; - while (stack.length > 0) { - const node = stack.pop(); - if (!node || typeof node !== "object") continue; - for (const [key, value] of Object.entries(node)) { - if (AI_CREDITS_FIELDS.has(key)) { - const parsed = parsePositiveNumberString(value); - if (parsed) aiCredits = parsed; - } - if (AI_CREDITS_RATE_LIMIT_ERROR_FIELDS.has(key) && isTrueLike(value)) rateLimitError = true; - if (AI_CREDITS_RATE_LIMIT_TEXT_FIELDS.has(key) && typeof value === "string") { - if (AI_CREDITS_RATE_LIMIT_PATTERNS.some(pattern => pattern.test(value))) rateLimitError = true; - } - if (value && typeof value === "object") stack.push(value); + traverseObjectTree(entry, (key, value) => { + if (AI_CREDITS_FIELDS.has(key)) { + const parsed = parsePositiveNumberString(value); + if (parsed) aiCredits = parsed; } - } + if (AI_CREDITS_RATE_LIMIT_ERROR_FIELDS.has(key) && isTrueLike(value)) rateLimitError = true; + if (AI_CREDITS_RATE_LIMIT_TEXT_FIELDS.has(key) && typeof value === "string") { + if (AI_CREDITS_RATE_LIMIT_PATTERNS.some(pattern => pattern.test(value))) rateLimitError = true; + } + }); return { aiCredits, rateLimitError }; } @@ -257,17 +269,7 @@ function parseMaxAICreditsExceededFromAuditLog(auditJsonlPathOverride) { * @returns {boolean} */ function parseUnknownModelAICreditsFromAuditEntry(entry) { - if (!entry || typeof entry !== "object") return false; - const stack = [entry]; - while (stack.length > 0) { - const node = stack.pop(); - if (!node || typeof node !== "object") continue; - for (const [, value] of Object.entries(node)) { - if (value === UNKNOWN_MODEL_AI_CREDITS_TYPE) return true; - if (value && typeof value === "object") stack.push(value); - } - } - return false; + return traverseObjectTree(entry, (_key, value) => value === UNKNOWN_MODEL_AI_CREDITS_TYPE || undefined); } /** From 7c4aa7185a47374b184bb6afcda0df0d568beb60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Jul 2026 04:54:16 +0000 Subject: [PATCH 2/2] fix: resolve CJS typecheck failure and simplify reviewer nits Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_labels.cjs | 4 ++-- actions/setup/js/ai_credits_context.cjs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/add_labels.cjs b/actions/setup/js/add_labels.cjs index 03ca05050c0..15e5e33fb47 100644 --- a/actions/setup/js/add_labels.cjs +++ b/actions/setup/js/add_labels.cjs @@ -36,7 +36,7 @@ const { resolveInvocationContext } = require("./invocation_context_helpers.cjs") const { normalizeIssueIntentLabelInputs } = require("./issue_intents.cjs"); /** - * @param {{ rationale?: string, confidence?: string, suggest?: boolean } | null} spec + * @param {{ rationale?: string, confidence?: string, suggest?: boolean } | null | undefined} spec * @returns {boolean} */ function hasLabelIntentMetadata(spec) { @@ -140,7 +140,7 @@ const main = createCountGatedHandler({ const key = label.name.toLowerCase(); const existing = requestedLabelSpecByLowerName.get(key); const newHasMetadata = hasLabelIntentMetadata(label); - const existingHasMetadata = hasLabelIntentMetadata(existing ?? null); + const existingHasMetadata = hasLabelIntentMetadata(existing); if (!existing || (!existingHasMetadata && newHasMetadata)) { requestedLabelSpecByLowerName.set(key, label); } diff --git a/actions/setup/js/ai_credits_context.cjs b/actions/setup/js/ai_credits_context.cjs index 176ebf6a49c..69f659261fd 100644 --- a/actions/setup/js/ai_credits_context.cjs +++ b/actions/setup/js/ai_credits_context.cjs @@ -129,6 +129,7 @@ function parseMaxAICreditsFromAuditEntry(entry) { return true; } } + return false; }); return result; } @@ -269,7 +270,10 @@ function parseMaxAICreditsExceededFromAuditLog(auditJsonlPathOverride) { * @returns {boolean} */ function parseUnknownModelAICreditsFromAuditEntry(entry) { - return traverseObjectTree(entry, (_key, value) => value === UNKNOWN_MODEL_AI_CREDITS_TYPE || undefined); + return traverseObjectTree(entry, (_key, value) => { + if (value === UNKNOWN_MODEL_AI_CREDITS_TYPE) return true; + return false; + }); } /**