diff --git a/docs/knowledgebase/Agent-Cost-Telemetry-Surfaces.md b/docs/knowledgebase/Agent-Cost-Telemetry-Surfaces.md index f7957de58..3f03b19fb 100644 --- a/docs/knowledgebase/Agent-Cost-Telemetry-Surfaces.md +++ b/docs/knowledgebase/Agent-Cost-Telemetry-Surfaces.md @@ -286,7 +286,10 @@ The hook projects: - observed operator-equivalent labor - observed blended lower-bound spend - operator budget cap / remaining lower bound -- operational invoice-turn remainder +- operator budget spendable status +- active invoice-turn remainder +- whole-account remaining USD estimate from the latest credit snapshot +- operational headroom after protected reserve - reserved calibration funding window state - live/background/total turn counts @@ -296,6 +299,11 @@ consuming it. The current intent is: - operational invoice turn may spend - calibration invoice turn remains on hold +When operator labor timing is incomplete, the hook still records the lower-bound +cap math but marks spendable operator remaining as unreconciled. That prevents +the comment trail from implying the full configured operator cap is immediately +safe to spend. + Use the wrappers by default so GitHub issue and PR comments pick up the durable budget hook automatically. Pass `-SkipBudgetHook` only for narrow test or break-glass cases where the attestation must be suppressed deliberately. diff --git a/docs/schemas/github-comment-budget-hook-report-v1.schema.json b/docs/schemas/github-comment-budget-hook-report-v1.schema.json index 9506f490e..6674a11e8 100644 --- a/docs/schemas/github-comment-budget-hook-report-v1.schema.json +++ b/docs/schemas/github-comment-budget-hook-report-v1.schema.json @@ -41,7 +41,13 @@ "knownBlendedUsd", "operatorBudgetCapUsd", "operatorBudgetRemainingLowerBoundUsd", - "operatorBudgetRemainingStatus" + "operatorBudgetRemainingStatus", + "operatorBudgetSpendableUsd", + "operatorBudgetSpendableStatus", + "accountRemainingUsdEstimate", + "operationalHeadroomUsd", + "operationalHeadroomStatus", + "budgetPressureState" ], "properties": { "status": { "type": "string", "enum": ["pass", "warn", "blocked"] }, @@ -53,7 +59,13 @@ "knownBlendedUsd": { "type": ["number", "null"], "minimum": 0 }, "operatorBudgetCapUsd": { "type": ["number", "null"], "minimum": 0 }, "operatorBudgetRemainingLowerBoundUsd": { "type": ["number", "null"], "minimum": 0 }, - "operatorBudgetRemainingStatus": { "type": "string", "enum": ["observed", "lower-bound", "unknown"] } + "operatorBudgetRemainingStatus": { "type": "string", "enum": ["observed", "lower-bound", "unknown"] }, + "operatorBudgetSpendableUsd": { "type": ["number", "null"], "minimum": 0 }, + "operatorBudgetSpendableStatus": { "type": "string", "enum": ["observed", "unreconciled", "unknown"] }, + "accountRemainingUsdEstimate": { "type": ["number", "null"], "minimum": 0 }, + "operationalHeadroomUsd": { "type": ["number", "null"], "minimum": 0 }, + "operationalHeadroomStatus": { "type": "string", "enum": ["healthy", "reserve-near", "reserve-protected-only", "unknown"] }, + "budgetPressureState": { "type": "string", "enum": ["healthy", "cautious", "tight", "stop-nonessential-spend", "blocked"] } } }, "turns": { @@ -69,7 +81,7 @@ "funding": { "type": "object", "additionalProperties": false, - "required": ["billingWindow", "reservedFunding"], + "required": ["billingWindow", "accountBalance", "reservedFunding"], "properties": { "billingWindow": { "type": ["object", "null"], @@ -88,6 +100,21 @@ "selectionReason": { "type": ["string", "null"] } } }, + "accountBalance": { + "type": ["object", "null"], + "additionalProperties": false, + "required": ["totalCredits", "usedCredits", "remainingCredits", "unitPriceUsd", "remainingUsdEstimate", "sourceKind", "sourcePathEvidence", "operatorNote"], + "properties": { + "totalCredits": { "type": ["number", "null"], "minimum": 0 }, + "usedCredits": { "type": ["number", "null"], "minimum": 0 }, + "remainingCredits": { "type": ["number", "null"], "minimum": 0 }, + "unitPriceUsd": { "type": ["number", "null"], "minimum": 0 }, + "remainingUsdEstimate": { "type": ["number", "null"], "minimum": 0 }, + "sourceKind": { "type": ["string", "null"] }, + "sourcePathEvidence": { "type": ["string", "null"] }, + "operatorNote": { "type": ["string", "null"] } + } + }, "reservedFunding": { "type": "object", "additionalProperties": false, diff --git a/tools/priority/__tests__/github-comment-budget-hook-schema.test.mjs b/tools/priority/__tests__/github-comment-budget-hook-schema.test.mjs index 6e58d71ac..91bc6b216 100644 --- a/tools/priority/__tests__/github-comment-budget-hook-schema.test.mjs +++ b/tools/priority/__tests__/github-comment-budget-hook-schema.test.mjs @@ -58,10 +58,21 @@ test('github-comment-budget-hook report and policy validate against checked-in s operatorLaborUsd: 4, operatorLaborMissingTurnCount: 0, blendedTotalUsd: 5.5, - estimatedPrepaidUsdRemaining: 398.5 + estimatedPrepaidUsdRemaining: 398.5, + accountBalanceTotalCredits: 28750, + accountBalanceUsedCredits: 24600, + accountBalanceRemainingCredits: 4150 }, provenance: { operatorProfiles: [{ operatorProfilePath: 'tools/policy/operator-cost-profile.json' }], + invoiceTurn: { + unitPriceUsd: 0.04 + }, + accountBalance: { + sourceKind: 'operator-account-state', + sourcePathEvidence: 'operator-account-state.json', + operatorNote: 'Latest operator-provided balance snapshot.' + }, invoiceTurns: [] } }, diff --git a/tools/priority/__tests__/github-comment-budget-hook.test.mjs b/tools/priority/__tests__/github-comment-budget-hook.test.mjs index 3802ac3aa..a3193f269 100644 --- a/tools/priority/__tests__/github-comment-budget-hook.test.mjs +++ b/tools/priority/__tests__/github-comment-budget-hook.test.mjs @@ -30,7 +30,10 @@ function createRollupFixture() { operatorLaborUsd: 30, operatorLaborMissingTurnCount: 1, blendedTotalUsd: null, - estimatedPrepaidUsdRemaining: 387.5 + estimatedPrepaidUsdRemaining: 387.5, + accountBalanceTotalCredits: 28750, + accountBalanceUsedCredits: 24600, + accountBalanceRemainingCredits: 4150 }, provenance: { operatorProfiles: [ @@ -38,6 +41,14 @@ function createRollupFixture() { operatorProfilePath: 'tools/policy/operator-cost-profile.json' } ], + invoiceTurn: { + unitPriceUsd: 0.04 + }, + accountBalance: { + sourceKind: 'operator-account-state', + sourcePathEvidence: 'operator-account-state.json', + operatorNote: 'Latest operator-provided balance snapshot.' + }, invoiceTurns: [ { invoiceTurnId: 'invoice-turn-2026-03-HQ1VJLMV-0027', @@ -134,12 +145,23 @@ test('runGitHubCommentBudgetHook emits a durable lower-bound budget hook with re assert.equal(result.report.summary.status, 'warn'); assert.equal(result.report.summary.operatorBudgetCapUsd, 50000); assert.equal(result.report.summary.operatorBudgetRemainingStatus, 'lower-bound'); + assert.equal(result.report.summary.operatorBudgetSpendableStatus, 'unreconciled'); + assert.equal(result.report.summary.operatorBudgetSpendableUsd, null); assert.equal(result.report.summary.observedBlendedLowerBoundUsd, 42.5); + assert.equal(result.report.summary.accountRemainingUsdEstimate, 166); + assert.equal(result.report.summary.operationalHeadroomUsd, 66); + assert.equal(result.report.summary.operationalHeadroomStatus, 'reserve-near'); + assert.equal(result.report.summary.budgetPressureState, 'tight'); assert.equal(result.report.turns.backgroundTurnCount, 2); + assert.equal(result.report.funding.accountBalance.remainingCredits, 4150); + assert.equal(result.report.funding.accountBalance.remainingUsdEstimate, 166); assert.equal(result.report.funding.reservedFunding.count, 1); assert.equal(result.report.funding.reservedFunding.totalReservedUsd, 100); assert.equal(fs.existsSync(outputPath), true); assert.equal(fs.existsSync(markdownOutputPath), true); assert.match(result.markdown, /blended lower bound \$42\.500000/); + assert.match(result.markdown, /account est \$166\.000000 remaining/); + assert.match(result.markdown, /operational headroom \$66\.000000 \(reserve-near\)/); + assert.match(result.markdown, /pressure tight/); assert.match(result.markdown, /calibration reserve \$100\.000000/); }); diff --git a/tools/priority/__tests__/materialize-agent-cost-rollup.test.mjs b/tools/priority/__tests__/materialize-agent-cost-rollup.test.mjs index 56ba44a5c..91a796b82 100644 --- a/tools/priority/__tests__/materialize-agent-cost-rollup.test.mjs +++ b/tools/priority/__tests__/materialize-agent-cost-rollup.test.mjs @@ -216,6 +216,13 @@ function setupMaterializationFixture() { path.join(donorRoot, 'tests', 'results', '_agent', 'cost', 'invoice-turns', 'HQ1VJLMV-0027.local.json'), createInvoiceTurnPayload() ); + writeJson( + path.join(donorRoot, 'tests', 'results', '_agent', 'cost', 'invoice-turns', 'HQ1VJLMV-0027.metadata.local.json'), + { + schema: 'priority/agent-cost-private-invoice-metadata@v1', + invoiceId: 'HQ1VJLMV-0027' + } + ); writeJson( path.join(donorRoot, 'tests', 'results', '_agent', 'cost', 'usage-exports', 'usage-export-2026-03-15.json'), createUsageExportPayload() @@ -224,6 +231,17 @@ function setupMaterializationFixture() { path.join(donorRoot, 'tests', 'results', '_agent', 'cost', 'account-balances', 'account-balance-2026-03-21.json'), createAccountBalancePayload() ); + writeJson( + path.join(donorRoot, 'tests', 'results', '_agent', 'cost', 'account-balances', 'account-balance-2026-03-21.private.local.json'), + { + schema: 'priority/agent-cost-private-account-balance@v1', + balances: { + totalCredits: 27500, + usedCredits: 15800, + remainingCredits: 11700 + } + } + ); return { repoRoot }; } @@ -245,6 +263,14 @@ test('runMaterializeAgentCostRollup materializes a heuristic turn and rollup in assert.equal(result.report.syncedReceipts.invoiceTurns.materializedCount, 1); assert.equal(result.report.syncedReceipts.usageExports.materializedCount, 1); assert.equal(result.report.syncedReceipts.accountBalances.materializedCount, 1); + assert.equal( + result.report.syncedReceipts.invoiceTurns.files.some((entry) => entry.fileName.includes('.metadata.')), + false + ); + assert.equal( + result.report.syncedReceipts.accountBalances.files.some((entry) => entry.fileName.includes('.private.')), + false + ); assert.equal(result.report.heuristicTurn.requestedModel, 'gpt-5.4'); assert.equal(fs.existsSync(result.outputPath), true); assert.equal(fs.existsSync(result.costRollupPath), true); diff --git a/tools/priority/github-comment-budget-hook.mjs b/tools/priority/github-comment-budget-hook.mjs index 78cfe2735..d6b02093e 100644 --- a/tools/priority/github-comment-budget-hook.mjs +++ b/tools/priority/github-comment-budget-hook.mjs @@ -157,23 +157,24 @@ function chooseTargetRepository(repo, rollup) { function summarizeBillingWindow(rollup) { const billingWindow = rollup?.billingWindow; + const invoiceTurn = rollup?.summary?.provenance?.invoiceTurn; if (!billingWindow || typeof billingWindow !== 'object') { return null; } - const prepaidUsd = toNonNegativeNumber(billingWindow.prepaidUsd); + const prepaidUsd = toNonNegativeNumber(billingWindow.prepaidUsd) ?? toNonNegativeNumber(invoiceTurn?.prepaidUsd); const tokenSpendUsd = toNonNegativeNumber(rollup?.summary?.metrics?.totalUsd) ?? 0; const remainingUsd = toNonNegativeNumber(rollup?.summary?.metrics?.estimatedPrepaidUsdRemaining) ?? (prepaidUsd != null ? roundUsd(prepaidUsd - tokenSpendUsd) : null); return { - invoiceTurnId: asOptional(billingWindow.invoiceTurnId), - invoiceId: asOptional(billingWindow.invoiceId), - fundingPurpose: asOptional(billingWindow.fundingPurpose), - activationState: asOptional(billingWindow.activationState), + invoiceTurnId: asOptional(billingWindow.invoiceTurnId) ?? asOptional(invoiceTurn?.invoiceTurnId), + invoiceId: asOptional(billingWindow.invoiceId) ?? asOptional(invoiceTurn?.invoiceId), + fundingPurpose: asOptional(billingWindow.fundingPurpose) ?? asOptional(invoiceTurn?.fundingPurpose), + activationState: asOptional(billingWindow.activationState) ?? asOptional(invoiceTurn?.activationState), prepaidUsd, tokenSpendUsd, remainingUsd, - pricingBasis: asOptional(billingWindow.pricingBasis), + pricingBasis: asOptional(billingWindow.pricingBasis) ?? asOptional(invoiceTurn?.pricingBasis), selectionMode: asOptional(billingWindow?.selection?.mode), selectionReason: asOptional(billingWindow?.selection?.reason) }; @@ -209,6 +210,64 @@ function summarizeReservedFundingWindows(rollup, policy, billingWindowInvoiceTur }; } +function summarizeAccountBalance(rollup) { + const metrics = rollup?.summary?.metrics ?? {}; + const accountBalance = rollup?.summary?.provenance?.accountBalance ?? {}; + const totalCredits = toNonNegativeNumber(metrics.accountBalanceTotalCredits ?? accountBalance?.totalCredits); + const usedCredits = toNonNegativeNumber(metrics.accountBalanceUsedCredits ?? accountBalance?.usedCredits); + const remainingCredits = toNonNegativeNumber(metrics.accountBalanceRemainingCredits ?? accountBalance?.remainingCredits); + const unitPriceUsd = + toNonNegativeNumber(rollup?.summary?.provenance?.invoiceTurn?.unitPriceUsd) ?? + toNonNegativeNumber(rollup?.billingWindow?.credits?.unitPriceUsd); + const remainingUsdEstimate = + remainingCredits != null && unitPriceUsd != null + ? roundUsd(remainingCredits * unitPriceUsd) + : null; + + if (totalCredits == null && usedCredits == null && remainingCredits == null && remainingUsdEstimate == null) { + return null; + } + + return { + totalCredits, + usedCredits, + remainingCredits, + unitPriceUsd, + remainingUsdEstimate, + sourceKind: asOptional(accountBalance?.sourceKind), + sourcePathEvidence: asOptional(accountBalance?.sourcePathEvidence), + operatorNote: asOptional(accountBalance?.operatorNote) + }; +} + +function summarizeOperationalHeadroom(accountBalance, reservedFunding) { + const reservedUsd = toNonNegativeNumber(reservedFunding?.totalReservedUsd) ?? 0; + const accountRemainingUsdEstimate = toNonNegativeNumber(accountBalance?.remainingUsdEstimate); + if (accountRemainingUsdEstimate == null) { + return { + accountRemainingUsdEstimate: null, + operationalHeadroomUsd: null, + status: 'unknown', + basis: 'missing-account-balance' + }; + } + + const operationalHeadroomUsd = roundUsd(Math.max(accountRemainingUsdEstimate - reservedUsd, 0)) ?? 0; + const status = + operationalHeadroomUsd <= 0 + ? 'reserve-protected-only' + : operationalHeadroomUsd <= 100 + ? 'reserve-near' + : 'healthy'; + + return { + accountRemainingUsdEstimate, + operationalHeadroomUsd, + status, + basis: 'account-balance-minus-reserve' + }; +} + function buildJsonHookPayload(report) { return { schema: report.schema, @@ -224,7 +283,14 @@ function buildJsonHookPayload(report) { operatorBudgetCapUsd: report.summary.operatorBudgetCapUsd, operatorBudgetRemainingLowerBoundUsd: report.summary.operatorBudgetRemainingLowerBoundUsd, operatorBudgetRemainingStatus: report.summary.operatorBudgetRemainingStatus, + operatorBudgetSpendableUsd: report.summary.operatorBudgetSpendableUsd, + operatorBudgetSpendableStatus: report.summary.operatorBudgetSpendableStatus, + accountRemainingUsdEstimate: report.summary.accountRemainingUsdEstimate, + operationalHeadroomUsd: report.summary.operationalHeadroomUsd, + operationalHeadroomStatus: report.summary.operationalHeadroomStatus, + budgetPressureState: report.summary.budgetPressureState, billingWindow: report.funding.billingWindow, + accountBalance: report.funding.accountBalance, reservedFunding: report.funding.reservedFunding, turns: report.turns, source: report.source, @@ -244,20 +310,29 @@ function buildMarkdown(report) { lines.push(`_Budget hook_: unavailable (${blockerCodes || '`unknown-blocker`'}). Receipt: \`${report.source.outputPath ?? 'none'}\`.`); } else { const billingWindow = report.funding.billingWindow; + const accountBalance = report.funding.accountBalance; const reservedFunding = report.funding.reservedFunding; const operatorBudgetText = report.summary.operatorBudgetCapUsd == null ? 'operator cap unknown' - : `operator ${formatUsd(report.summary.operatorLaborObservedUsd)} of ${formatUsd(report.summary.operatorBudgetCapUsd)} cap (remaining ${report.summary.operatorBudgetRemainingStatus === 'lower-bound' ? '>=' : ''}${formatUsd(report.summary.operatorBudgetRemainingLowerBoundUsd)})`; + : report.summary.operatorBudgetSpendableStatus === 'observed' + ? `operator ${formatUsd(report.summary.operatorLaborObservedUsd)} of ${formatUsd(report.summary.operatorBudgetCapUsd)} cap (spendable remaining ${formatUsd(report.summary.operatorBudgetSpendableUsd)})` + : `operator ${formatUsd(report.summary.operatorLaborObservedUsd)} of ${formatUsd(report.summary.operatorBudgetCapUsd)} cap (spendable remaining unreconciled; lower bound ${report.summary.operatorBudgetRemainingStatus === 'lower-bound' ? '>=' : ''}${formatUsd(report.summary.operatorBudgetRemainingLowerBoundUsd)})`; const billingWindowText = billingWindow?.invoiceTurnId ? `window \`${billingWindow.invoiceTurnId}\` spent ${formatUsd(billingWindow.tokenSpendUsd)} remaining ${formatUsd(billingWindow.remainingUsd)}` : 'window unavailable'; + const accountText = accountBalance?.remainingUsdEstimate != null + ? `account est ${formatUsd(accountBalance.remainingUsdEstimate)} remaining from ${accountBalance.remainingCredits} credits @ ${formatUsd(accountBalance.unitPriceUsd)} per credit` + : 'account headroom unavailable'; + const headroomText = report.summary.operationalHeadroomUsd != null + ? `operational headroom ${formatUsd(report.summary.operationalHeadroomUsd)} (${report.summary.operationalHeadroomStatus})` + : `operational headroom unavailable (${report.summary.operationalHeadroomStatus})`; const reserveText = reservedFunding.count > 0 ? `; calibration reserve ${formatUsd(reservedFunding.totalReservedUsd)} across ${reservedFunding.count} held window(s)` : ''; const timingText = report.summary.operatorLaborMissingTurnCount > 0 ? `; ${report.summary.operatorLaborMissingTurnCount} turn(s) still pending labor timing` : ''; - lines.push(`_Budget hook_: blended lower bound ${formatUsd(report.summary.observedBlendedLowerBoundUsd)}; ${operatorBudgetText}; ${billingWindowText}; turns ${report.turns.totalTurns} total (${report.turns.liveTurnCount} live, ${report.turns.backgroundTurnCount} background)${timingText}${reserveText}. Receipt: \`${report.source.outputPath}\`.`); + lines.push(`_Budget hook_: blended lower bound ${formatUsd(report.summary.observedBlendedLowerBoundUsd)}; ${operatorBudgetText}; ${billingWindowText}; ${accountText}; ${headroomText}; pressure ${report.summary.budgetPressureState}; turns ${report.turns.totalTurns} total (${report.turns.liveTurnCount} live, ${report.turns.backgroundTurnCount} background)${timingText}${reserveText}. Receipt: \`${report.source.outputPath}\`.`); } lines.push(COMMENT_HOOK_END_MARKER, ''); @@ -301,18 +376,43 @@ export function buildGitHubCommentBudgetHookReport({ rollup, repository, targetK const operatorLaborMissingTurnCount = Number(metrics.operatorLaborMissingTurnCount ?? 0) || 0; const observedBlendedLowerBoundUsd = roundUsd(tokenSpendUsd + operatorLaborObservedUsd) ?? 0; const knownBlendedUsd = toNonNegativeNumber(metrics.blendedTotalUsd); + const accountBalance = summarizeAccountBalance(rollup); + const operationalHeadroom = summarizeOperationalHeadroom(accountBalance, reservedFunding); const operatorBudgetRemainingLowerBoundUsd = operatorBudgetCapUsd == null ? null : roundUsd(Math.max(0, operatorBudgetCapUsd - operatorLaborObservedUsd)); const operatorBudgetRemainingStatus = operatorBudgetCapUsd == null ? 'unknown' : operatorLaborMissingTurnCount > 0 ? 'lower-bound' : 'observed'; + const operatorBudgetSpendableStatus = + operatorBudgetCapUsd == null ? 'unknown' : operatorLaborMissingTurnCount > 0 ? 'unreconciled' : 'observed'; + const operatorBudgetSpendableUsd = + operatorBudgetSpendableStatus === 'observed' ? operatorBudgetRemainingLowerBoundUsd : null; const blocking = Array.isArray(blockers) ? blockers.filter(Boolean) : []; - const status = blocking.length > 0 ? 'blocked' : operatorLaborMissingTurnCount > 0 ? 'warn' : 'pass'; + const budgetPressureState = + blocking.length > 0 + ? 'blocked' + : operationalHeadroom.status === 'reserve-protected-only' + ? 'stop-nonessential-spend' + : operationalHeadroom.status === 'reserve-near' + ? 'tight' + : operationalHeadroom.status === 'unknown' || operatorBudgetSpendableStatus !== 'observed' + ? 'cautious' + : 'healthy'; + const status = + blocking.length > 0 + ? 'blocked' + : budgetPressureState === 'healthy' + ? 'pass' + : 'warn'; const recommendation = status === 'blocked' ? 'repair-comment-budget-hook-inputs' + : budgetPressureState === 'stop-nonessential-spend' + ? 'protect-calibration-reserve' + : budgetPressureState === 'tight' + ? 'constrain-spend' : operatorLaborMissingTurnCount > 0 - ? 'continue-observing-labor-timing' + ? 'reconcile-operator-labor-before-assuming-headroom' : 'comment-budget-hook-ready'; return { @@ -333,7 +433,13 @@ export function buildGitHubCommentBudgetHookReport({ rollup, repository, targetK knownBlendedUsd, operatorBudgetCapUsd, operatorBudgetRemainingLowerBoundUsd, - operatorBudgetRemainingStatus + operatorBudgetRemainingStatus, + operatorBudgetSpendableUsd, + operatorBudgetSpendableStatus, + accountRemainingUsdEstimate: operationalHeadroom.accountRemainingUsdEstimate, + operationalHeadroomUsd: operationalHeadroom.operationalHeadroomUsd, + operationalHeadroomStatus: operationalHeadroom.status, + budgetPressureState }, turns: { totalTurns: Number(metrics.totalTurns ?? 0) || 0, @@ -342,6 +448,7 @@ export function buildGitHubCommentBudgetHookReport({ rollup, repository, targetK }, funding: { billingWindow, + accountBalance, reservedFunding }, source, diff --git a/tools/priority/materialize-agent-cost-rollup.mjs b/tools/priority/materialize-agent-cost-rollup.mjs index 3325edc81..b6ecff629 100644 --- a/tools/priority/materialize-agent-cost-rollup.mjs +++ b/tools/priority/materialize-agent-cost-rollup.mjs @@ -147,10 +147,15 @@ function listJsonFiles(dirPath) { return []; } return fs.readdirSync(resolved, { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.json')) + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.json') && !shouldIgnoreDiscoveredReceipt(entry.name)) .map((entry) => path.join(resolved, entry.name)); } +function shouldIgnoreDiscoveredReceipt(fileName) { + const normalized = normalizeText(fileName).toLowerCase(); + return normalized.includes('.private.') || normalized.includes('.metadata.'); +} + function chooseNewestByBasename(filePaths) { const winners = new Map(); for (const filePath of filePaths) {