📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-03 16:02:56 +00:00
parent ac7fffe532
commit 5cb67428bd
803 changed files with 78437 additions and 401 deletions
@@ -0,0 +1,38 @@
// Bot Protection evidence is usually account-level summary data. Avoid turning
// observed bot traffic into unsupported statements about exact WAF rule state.
export const metadata = {
id: 'bot-protection-certainty',
description: 'Soften unsupported Bot Protection / WAF certainty and require a staged rollout caveat.',
};
const STRING_FIELDS = ['what', 'why', 'fix', 'currentBehavior', 'desiredBehavior', 'verify'];
export function apply(rec) {
if (!String(rec?.candidateRef ?? '').startsWith('platform_bot_protection:')) return {};
const tags = [];
for (const field of STRING_FIELDS) {
if (typeof rec?.[field] !== 'string') continue;
const before = rec[field];
let after = before
.replace(/\bno\s+(?:firewall\s+)?bot_filter\s+rule\b/gi, 'the collected firewall summary did not show an enforced bot-filter rule')
.replace(/\b(?:bots?|bot traffic)\s+(?:is|are)\s+the\s+cause\b/gi, 'bot traffic is a likely contributor')
.replace(/\bwithout\s+false[- ]positive\s+risk\b/gi, 'with false-positive risk monitored during rollout')
.replace(/\bno\s+false[- ]positive\s+risk\b/gi, 'false-positive risk still needs rollout monitoring');
if (after !== before) {
rec[field] = after;
tags.push(`bot-protection-certainty:${field}`);
}
}
const text = STRING_FIELDS.map((field) => rec?.[field]).filter((s) => typeof s === 'string').join('\n');
if (/\b(?:Bot Protection|BotID|bot_filter|WAF)\b/i.test(text) &&
!/\bstaged\b[\s\S]{0,80}\b(?:log|allowlist|exclusions?)\b/i.test(text)) {
const caveat = ' Use a staged rollout that starts in Log mode where available, then moves to the appropriate Challenge or Deny action only after allowlist/exclusion review for known monitoring and partner clients.';
if (typeof rec.fix === 'string') rec.fix += caveat;
else rec.fix = caveat.trim();
tags.push('bot-protection-certainty:staged-rollout');
}
return tags.length > 0 ? { tags, needsReview: true } : {};
}
@@ -0,0 +1,30 @@
// A cacheTag() in the cached function is not proof that CMS edits invalidate it.
// The report must not claim "existing tags preserve instant updates" unless the
// investigation verifies matching revalidateTag/updateTag paths.
export const metadata = {
id: 'cache-tag-invalidation-certainty',
description: 'Remove unsupported certainty that existing cache tags already preserve CMS/on-demand invalidation.',
};
const STRING_FIELDS = ['what', 'why', 'fix', 'currentBehavior', 'desiredBehavior', 'verify'];
const UNSUPPORTED_TAG_CERTAINTY =
/\b(?:existing|current)\s+(?:cache\s+)?tags?\b[^.!?\n]{0,160}\b(?:preserve|keep|cover|maintain|ensure)\b[^.!?\n]{0,160}\b(?:instant|on-demand|CMS|content|publish|update|updates|invalidation|revalidation)\b[^.!?\n]*(?:[.!?]|$)/gi;
const SAFE_REPLACEMENT =
'Confirm a matching revalidateTag() or updateTag() path for each cacheTag() before increasing the cache lifetime.';
export function apply(rec) {
const text = STRING_FIELDS.map((field) => rec?.[field]).filter((s) => typeof s === 'string').join('\n');
if (!/\bcache(?:Life|Tag)\b/.test(text)) return {};
const tags = [];
for (const field of STRING_FIELDS) {
if (typeof rec?.[field] !== 'string') continue;
const before = rec[field];
const after = before.replace(UNSUPPORTED_TAG_CERTAINTY, SAFE_REPLACEMENT);
if (after !== before) {
rec[field] = after;
tags.push(`cache-tag-invalidation-certainty:${field}`);
}
}
return tags.length > 0 ? { tags, needsReview: true } : {};
}
@@ -0,0 +1,52 @@
// Rewrite verifier-failed count claims to ground truth (or "a number of"
// when actual isn't numeric) so we don't ship false precision.
import { escapeRegex } from '../util.mjs';
export const metadata = {
id: 'count-correct',
description: 'Rewrite count claims to verified ground truth (count-correct) or "a number of" (count-strip) when verifier disagrees.',
};
const COUNT_CLAIM_TYPES = new Set(['pattern_count', 'repo_count', 'cited_count_literal']);
export function apply(rec, ctx = {}) {
const results = ctx.verifyResults ?? rec.verifyResults ?? rec.verification?.failed ?? null;
if (!Array.isArray(results) || results.length === 0) return {};
const tags = [];
for (const r of results) {
if (!r) continue;
const type = r.type ?? r.claimType;
if (!COUNT_CLAIM_TYPES.has(type)) continue;
const disp = r.disposition ?? (r.actual !== r.expected ? 'failed' : 'verified');
if (disp !== 'failed') continue;
const expected = r.expected;
const actual = r.actual;
const token = r.token ?? r.text ?? expected;
if (expected == null || token == null) continue;
if (typeof actual === 'number' && Number.isFinite(actual)) {
rewriteCount(rec, token, expected, `~${actual}`);
tags.push(`count-correct:${token}:${expected}->${actual}`);
} else {
rewriteCount(rec, token, expected, 'a number of');
tags.push(`count-strip:${token}`);
}
}
if (tags.length === 0) return {};
return { tags };
}
function rewriteCount(rec, token, oldCount, replacement) {
const fields = ['what', 'why', 'fix', 'currentBehavior', 'desiredBehavior'];
// Matches "60", "~60", and "60+" — LLM commonly writes "60+ icons".
const oldEsc = escapeRegex(String(oldCount));
const re = new RegExp(`\\b~?${oldEsc}\\+?\\s+${escapeRegex(token)}\\b`, 'g');
for (const f of fields) {
if (typeof rec[f] !== 'string') continue;
rec[f] = rec[f].replace(re, `${replacement} ${token}`);
}
}
@@ -0,0 +1,38 @@
// A function-duration optimization can reduce p95/CPU/GB-hr. It does not by
// itself reduce function invocation count unless the fix also adds CDN/static
// response caching.
export const metadata = {
id: 'function-duration-invocations',
description: 'Remove false claims that slow-route data-cache fixes reduce function invocation count.',
};
const STRING_FIELDS = [
'what',
'why',
'fix',
'currentBehavior',
'desiredBehavior',
'verify',
];
const BAD_INVOCATION_CLAIM =
/\bfunction invocations?\b[^.!?\n]{0,120}\b(?:drop|drops|fall|falls|decrease|decreases|decline|declines|reduce|reduces|reduced|cut|cuts)\b[^.!?\n]*(?:[.!?]|$)|\b(?:drop|drops|fall|falls|decrease|decreases|decline|declines|reduce|reduces|reduced|cut|cuts)\b[^.!?\n]{0,120}\bfunction invocations?\b[^.!?\n]*(?:[.!?]|$)/gi;
const SAFE_REPLACEMENT =
'95th percentile duration should drop; function invocation count may stay flat unless a separate CDN or static-rendering change is made.';
export function apply(rec) {
if (!String(rec?.candidateRef ?? '').startsWith('slow_route:')) return {};
const tags = [];
for (const field of STRING_FIELDS) {
if (typeof rec?.[field] !== 'string') continue;
const before = rec[field];
const after = before.replace(BAD_INVOCATION_CLAIM, SAFE_REPLACEMENT);
if (after !== before) {
rec[field] = after;
tags.push(`function-duration-invocations:${field}`);
}
}
return tags.length > 0 ? { tags } : {};
}
@@ -0,0 +1,79 @@
// Sanitizer orchestrator. Order matters: citation strippers must run
// before missing-citation so an emptied citations[] still drops the rec.
import { applyDollarStrip } from '../impact-magnitude.mjs';
import { sanitizeCitations } from '../citations.mjs';
import * as vercelDirectiveStrip from './vercel-directive-strip.mjs';
import * as rateLimit from './rate-limit.mjs';
import * as preRelease from './pre-release.mjs';
import * as middlewareConflict from './middleware-conflict.mjs';
import * as undeclaredDep from './undeclared-dep.mjs';
import * as countCorrect from './count-correct.mjs';
import * as renderingModeMislabel from './rendering-mode-mislabel.mjs';
import * as windowUnits from './window-units.mjs';
import * as functionDurationInvocations from './function-duration-invocations.mjs';
import * as botProtectionCertainty from './bot-protection-certainty.mjs';
import * as cacheTagInvalidationCertainty from './cache-tag-invalidation-certainty.mjs';
import * as missingCitation from './missing-citation.mjs';
export const SANITIZERS = [
vercelDirectiveStrip,
rateLimit,
preRelease,
middlewareConflict,
undeclaredDep,
countCorrect,
renderingModeMislabel,
windowUnits,
functionDurationInvocations,
botProtectionCertainty,
cacheTagInvalidationCertainty,
];
export function recordSanitizer(rec, tag) {
rec.sanitizerTrail = rec.sanitizerTrail ?? [];
rec.sanitizerTrail.push(tag);
}
export async function applySanitizers(rec, ctx = {}) {
applyDollarStrip(rec);
for (const s of SANITIZERS) {
const result = s.apply(rec, ctx) ?? {};
const tags = result.tags ?? (result.tag ? [result.tag] : []);
for (const t of tags) recordSanitizer(rec, t);
if (result.needsReview) rec.needsReview = true;
if (result.dropped) {
return { kept: false, rec, dropReason: tags[0] ?? `dropped-by:${s.metadata?.id ?? 'unknown'}` };
}
}
if (ctx.framework && ctx.version) {
const before = (rec.citations ?? []).slice();
const { strippedUnknown, strippedVersion } = await sanitizeCitations(rec, ctx.framework, ctx.version);
for (const u of strippedUnknown) recordSanitizer(rec, `unknown-citation:${u}`);
for (const u of strippedVersion) recordSanitizer(rec, `version-mismatch:${u}`);
const lostAny = strippedUnknown.length > 0 || strippedVersion.length > 0;
const lostAll = lostAny && (rec.citations ?? []).length === 0 && before.length > 0;
if (lostAll) rec.needsReview = true;
}
// missing-citation runs LAST so citation strippers above can starve a rec.
const missing = missingCitation.apply(rec, ctx) ?? {};
if (missing.dropped) {
return { kept: false, rec, dropReason: missing.tag ?? 'missing-citation' };
}
return { kept: true, rec };
}
export async function applySanitizersBatch(recs, ctx = {}) {
const kept = [];
const dropped = [];
for (const rec of recs) {
const r = await applySanitizers(rec, ctx);
if (r.kept) kept.push(r.rec);
else dropped.push({ rec: r.rec, dropReason: r.dropReason });
}
return { kept, dropped };
}
@@ -0,0 +1,36 @@
// Append a caveat when a rec targets a route covered by middleware: e.g.
// middleware setting Set-Cookie downstream poisons cache headers the rec
// adds. Trusts finding.routesCovered rather than re-implementing Next's
// matcher algorithm.
import { extractRoute } from '../util.mjs';
export const metadata = {
id: 'middleware-conflict',
description: 'Append caveat when rec targets a route covered by middleware.',
};
export function apply(rec, ctx = {}) {
const findings = ctx?.signals?.codebase?.findings ?? [];
const middlewareFinding = findings.find((f) => f?.scannerId === 'middleware-broad-matcher' || f?.id === 'middleware-broad-matcher');
if (!middlewareFinding) return {};
const route = extractRoute(rec);
if (!route) return {};
const matcher = middlewareFinding.detail?.matcher
?? middlewareFinding.matcher
?? '(unspecified matcher)';
const middlewareFile = middlewareFinding.file ?? middlewareFinding.path ?? 'middleware.ts';
const covered = middlewareFinding.detail?.routesCovered ?? middlewareFinding.routesCovered;
if (Array.isArray(covered) && covered.length > 0 && !covered.includes(route)) {
return {};
}
const tag = `middleware-conflict:${matcher}`;
const caveat = `\n\n_Caveat: Middleware at \`${middlewareFile}\` (matcher: \`${matcher}\`) may intercept \`${route}\` and alter request/response before this fix takes effect. Verify the middleware does not set headers (e.g. \`Set-Cookie\`) that would invalidate caching._`;
if (typeof rec.fix === 'string') rec.fix += caveat;
return { tag, needsReview: true };
}
@@ -0,0 +1,16 @@
// Final-gate sanitizer: drops a rec with no citations left after
// unknown-citation + version-mismatch have run. Every rec must carry ≥1
// citation.
export const metadata = {
id: 'missing-citation',
description: 'Drop rec when citations[] is empty after other sanitizers.',
};
export function apply(rec, _ctx = {}) {
const cites = Array.isArray(rec.citations) ? rec.citations : [];
if (cites.length === 0) {
return { dropped: true, tag: 'missing-citation' };
}
return {};
}
@@ -0,0 +1,74 @@
// Append a caveat (don't drop — customer may opt in to canary) when a
// fix needs a canary/rc/beta dep version.
import { matchesFrameworkVersion } from '../citations.mjs';
const PRE_RELEASE_FEATURES = [
{
match: /\bppr\b|partial[- ]?prerendering/i,
requires: 'next@canary',
message: 'PPR is experimental — verify your Next.js version supports it as stable',
},
{
match: /\buse cache['"]?\s*directive\b|"use cache"|'use cache'/i,
requires: 'next@>=15.0.0',
message: 'use cache directive is stable in 15+',
},
{
match: /\bcacheLife\(/i,
requires: 'next@>=15.0.0',
message: 'cacheLife is stable in 15+',
},
{
match: /\bcacheTag\(/i,
requires: 'next@>=15.0.0',
message: 'cacheTag is stable in 15+',
},
];
const SEMVER_PRE_RELEASE_RE = /\b([\w-]+)@(\d+\.\d+\.\d+-(?:rc|beta|canary|alpha|next|exp)[\w.-]*)/g;
export const metadata = {
id: 'pre-release',
description: 'Append caveat when fix targets a canary/rc/beta feature.',
};
export function apply(rec, ctx = {}) {
const text = [rec.fix, rec.currentBehavior, rec.desiredBehavior]
.filter((s) => typeof s === 'string')
.join('\n');
if (!text) return {};
const tags = [];
const caveats = [];
for (const feat of PRE_RELEASE_FEATURES) {
if (feat.match.test(text)) {
if (featureAvailableForStack(feat, ctx)) continue;
const tag = `pre-release:${feat.requires}`;
if (!tags.includes(tag)) {
tags.push(tag);
caveats.push(`Requires ${feat.requires} (${feat.message}).`);
}
}
}
for (const m of text.matchAll(SEMVER_PRE_RELEASE_RE)) {
const [, pkg, version] = m;
const tag = `pre-release:${pkg}@${version}`;
if (!tags.includes(tag)) {
tags.push(tag);
caveats.push(`Requires pre-release version: \`${pkg}@${version}\`.`);
}
}
if (tags.length === 0) return {};
const caveatBlock = '\n\n_Note: ' + caveats.join(' ') + '_';
if (typeof rec.fix === 'string') rec.fix += caveatBlock;
return { tags, needsReview: true };
}
function featureAvailableForStack(feat, ctx) {
if (!ctx?.framework || !ctx?.version) return false;
return matchesFrameworkVersion(feat.requires, ctx.framework, ctx.version);
}
@@ -0,0 +1,67 @@
// Prepend a caveat (don't drop — customer may be on a higher tier) when a
// rec prescribes concurrency above a known provider rate limit.
const PROVIDER_LIMITS = {
notion: { rps: 3, label: 'Notion', doc: 'https://developers.notion.com/reference/request-limits' },
openai: { rps: 30, label: 'OpenAI', doc: 'https://platform.openai.com/docs/guides/rate-limits' },
stripe: { rps: 100, label: 'Stripe', doc: 'https://docs.stripe.com/rate-limits' },
anthropic: { rps: 10, label: 'Anthropic', doc: 'https://docs.anthropic.com/en/api/rate-limits' },
};
export const metadata = {
id: 'rate-limit',
description: 'Prepend caveat when a rec prescribes concurrency above a known provider rate limit.',
};
const PROVIDER_RE = new RegExp(`\\b(${Object.keys(PROVIDER_LIMITS).join('|')})\\b`, 'gi');
const CONCURRENCY_RE = /\b(?:concurrency|parallel|in\s+parallel|simultaneous|simultaneously|fan[- ]?out|Promise\.all)\b[^\d]{0,40}(\d{1,4})\b/gi;
const CONCURRENCY_RE_REVERSE = /\b(\d{1,4})\s*(?:concurrent|parallel|simultaneous|in flight)\b/gi;
export function apply(rec, _ctx = {}) {
const text = collectText(rec);
const providers = matchProviders(text);
if (providers.length === 0) return {};
const concurrency = matchConcurrency(text);
if (concurrency === null) return {};
const tags = [];
let prepend = '';
for (const key of providers) {
const limit = PROVIDER_LIMITS[key];
if (!limit) continue;
if (concurrency > limit.rps) {
const tag = `rate-limit:${limit.label}:${concurrency}/${limit.rps}`;
tags.push(tag);
prepend += `${limit.label} rate-limits to ~${limit.rps} requests/second on first-tier plans; the prescribed concurrency of ${concurrency} may saturate the limit. Verify your tier before applying.\n\n`;
}
}
if (tags.length === 0) return {};
if (typeof rec.fix === 'string') rec.fix = prepend + rec.fix;
else rec.fix = prepend.trim();
return { tags, needsReview: true };
}
function collectText(rec) {
return [rec.what, rec.why, rec.fix, rec.currentBehavior, rec.desiredBehavior]
.filter((s) => typeof s === 'string')
.join('\n');
}
function matchProviders(text) {
const out = new Set();
for (const m of text.matchAll(PROVIDER_RE)) out.add(m[1].toLowerCase());
return [...out];
}
function matchConcurrency(text) {
let max = null;
for (const m of text.matchAll(CONCURRENCY_RE)) {
const n = Number(m[1]);
if (Number.isFinite(n) && (max === null || n > max)) max = n;
}
for (const m of text.matchAll(CONCURRENCY_RE_REVERSE)) {
const n = Number(m[1]);
if (Number.isFinite(n) && (max === null || n > max)) max = n;
}
return max;
}
@@ -0,0 +1,38 @@
// Warn when a rec's claimed rendering mode (static/ISR/SSR) contradicts
// the scanner-tagged mode for that route. No-op when the scanner didn't
// tag a renderingMode — full AST inference isn't implemented yet.
import { extractRoute } from '../util.mjs';
const MODE_PATTERNS = {
static: /\bstatic(?:ally rendered)?\b|prerender(?:ed)?\b/i,
isr: /\bISR\b|incremental[- ]?static|revalidate\s*:\s*\d/i,
ssr: /\bSSR\b|server[- ]?side rendered|dynamic\s*=\s*['"]force-dynamic['"]/i,
};
export const metadata = {
id: 'rendering-mode-mislabel',
description: 'Catch recs that blame the wrong rendering mode (e.g. "convert from ISR" on a static page).',
};
export function apply(rec, ctx = {}) {
const route = extractRoute(rec);
if (!route) return {};
const routes = ctx?.signals?.codebase?.routes ?? [];
const match = routes.find((r) => r.routePath === route);
const actualMode = match?.renderingMode;
if (!actualMode) return {};
const text = [rec.what, rec.why, rec.fix, rec.currentBehavior, rec.desiredBehavior]
.filter((s) => typeof s === 'string')
.join('\n');
const claimedModes = Object.entries(MODE_PATTERNS)
.filter(([, re]) => re.test(text))
.map(([m]) => m);
if (claimedModes.length === 0 || claimedModes.includes(actualMode)) return {};
const warning = `\n\n_⚠ Rendering-mode mismatch: this rec describes the route as \`${claimedModes.join(', ')}\` but the scanner classified it as \`${actualMode}\`. Verify the rendering mode before applying._`;
if (typeof rec.fix === 'string') rec.fix += warning;
return { tag: `rendering-mode-mislabel:${claimedModes.join(',')}!=${actualMode}`, needsReview: true };
}
@@ -0,0 +1,78 @@
// Prepend `npm i <pkg>` when the fix imports a package missing from
// package.json — otherwise pasted code hits a runtime error.
const IMPORT_RE = /\bimport\s+(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g;
const REQUIRE_RE = /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g;
// Captures package root from `pkg/sub` and `@scope/pkg/sub`.
const PKG_ROOT_RE = /^(@[^/]+\/[^/]+|[^/]+)/;
const NODE_BUILTINS = new Set([
'fs', 'fs/promises', 'path', 'os', 'crypto', 'http', 'https', 'http2', 'net',
'dns', 'tls', 'util', 'url', 'stream', 'buffer', 'events', 'process', 'child_process',
'cluster', 'worker_threads', 'inspector', 'perf_hooks', 'assert', 'console',
'querystring', 'string_decoder', 'tty', 'vm', 'zlib', 'readline', 'punycode',
'module', 'timers', 'async_hooks', 'v8', 'test', 'diagnostics_channel',
]);
export const metadata = {
id: 'undeclared-dep',
description: 'Prepend `npm i <pkg>` when fix imports a package not in package.json.',
};
export function apply(rec, ctx = {}) {
const pkg = ctx?.package ?? ctx?.signals?.package ?? null;
if (!pkg) return {};
const known = new Set([
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.devDependencies ?? {}),
...Object.keys(pkg.peerDependencies ?? {}),
...Object.keys(pkg.optionalDependencies ?? {}),
]);
const text = [rec.fix, rec.currentBehavior, rec.desiredBehavior]
.filter((s) => typeof s === 'string')
.join('\n');
const codeBlocks = extractCodeBlocks(text);
const importedRoots = new Set();
for (const block of codeBlocks) {
for (const m of block.matchAll(IMPORT_RE)) {
const root = pkgRoot(m[1]);
if (root) importedRoots.add(root);
}
for (const m of block.matchAll(REQUIRE_RE)) {
const root = pkgRoot(m[1]);
if (root) importedRoots.add(root);
}
}
const undeclared = [...importedRoots]
.filter((r) => !r.startsWith('.'))
.filter((r) => !NODE_BUILTINS.has(r))
.filter((r) => !r.startsWith('node:'))
.filter((r) => !known.has(r));
if (undeclared.length === 0) return {};
const installLines = undeclared.map((p) => `\`npm i ${p}\``).join(', ');
const prepend = `**Add dependency first**: ${installLines}\n\n`;
if (typeof rec.fix === 'string') rec.fix = prepend + rec.fix;
else rec.fix = prepend.trim();
return { tags: undeclared.map((p) => `undeclared-dep:${p}`), needsReview: true };
}
function pkgRoot(specifier) {
if (!specifier) return null;
if (specifier.startsWith('.')) return specifier;
const m = specifier.match(PKG_ROOT_RE);
return m ? m[1] : null;
}
function extractCodeBlocks(text) {
const out = [];
const re = /```[\w-]*\n?([\s\S]*?)```/g;
let m;
while ((m = re.exec(text)) !== null) out.push(m[1]);
// Also scan raw text for rare inline imports outside code blocks.
out.push(text);
return out;
}
@@ -0,0 +1,37 @@
// Strip Cache-Control directives Vercel's CDN silently ignores
// (stale-if-error, proxy-revalidate, must-revalidate). s-maxage/max-age/
// stale-while-revalidate/no-store/private/public are honored — leave them.
import { escapeRegex } from '../util.mjs';
const STRIP_DIRECTIVES = ['stale-if-error', 'proxy-revalidate', 'must-revalidate'];
export const metadata = {
id: 'vercel-directive-strip',
description: 'Strip cache-control directives Vercel\'s CDN does not honor.',
};
export function apply(rec, _ctx = {}) {
const fields = ['fix', 'currentBehavior', 'desiredBehavior'];
const strippedSet = new Set();
for (const f of fields) {
if (typeof rec[f] !== 'string') continue;
for (const directive of STRIP_DIRECTIVES) {
const re = new RegExp(`(?:,\\s*)?\\b${escapeRegex(directive)}\\b(?:\\s*,)?`, 'g');
if (re.test(rec[f])) {
rec[f] = rec[f]
.replace(new RegExp(`\\b${escapeRegex(directive)}\\b`, 'g'), '')
.replace(/,\s*,/g, ',')
.replace(/(['"])\s*,\s*/g, '$1, ')
.replace(/,\s*(['"])/g, ', $1')
.replace(/(['"])\s*,\s*(['"])/g, '$1, $2')
.replace(/\b(Cache-Control|cache-control)\b:\s*,\s*/g, '$1: ')
.replace(/(['"])\s*,\s*\1/g, '$1');
strippedSet.add(directive);
}
}
}
const stripped = [...strippedSet];
if (stripped.length === 0) return {};
return { tags: stripped.map((d) => `vercel-directive-strip:${d}`) };
}
@@ -0,0 +1,32 @@
// The metrics window is fixed by collect-signals (currently 14d). Do not let
// agent prose turn observed counts into monthly counts.
import { normalizeObservedWindowUnits } from '../display-labels.mjs';
export const metadata = {
id: 'window-units',
description: 'Rewrite observed /mo or monthly count units to /window so reports do not imply extrapolated monthly data.',
};
const STRING_FIELDS = [
'what',
'why',
'fix',
'currentBehavior',
'desiredBehavior',
'verify',
];
export function apply(rec) {
const tags = [];
for (const field of STRING_FIELDS) {
if (typeof rec?.[field] !== 'string') continue;
const before = rec[field];
const after = normalizeObservedWindowUnits(before);
if (after !== before) {
rec[field] = after;
tags.push(`window-units:${field}`);
}
}
return tags.length > 0 ? { tags } : {};
}