{"version":3,"file":"emission-guard.d.ts","sourceRoot":"","sources":["../../../src/watchdog/emission-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,MAAM,iCAAiC,GAAG,cAAc,GAAG,WAAW,GAAG,cAAc,GAAG,eAAe,CAAC;AAEhH,MAAM,MAAM,wBAAwB,GACjC;IACA,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;CACnB,GACD;IACA,QAAQ,EAAE,KAAK,CAAC;IAChB,MAAM,EAAE,iCAAiC,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEL,MAAM,WAAW,4BAA4B;IAC5C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAsBD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQnE;AAED,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG,MAAM,CAEhH;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC,GAAG,MAAM,CAEnH;AAOD,qBAAa,qBAAqB;IACjC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,4BAA4B,CAAuC;IAC3E,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gCAAgC,CAAqB;IAC7D,OAAO,CAAC,sBAAsB,CAA+B;IAE7D,YAAY,OAAO,GAAE,4BAAiC,EAGrD;IAED,gBAAgB,IAAI,IAAI,CAGvB;IAED,KAAK,IAAI,IAAI,CAKZ;IAED,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,wBAAwB,CAqC3D;CACD","sourcesContent":["import type { WatchdogSeverity, WatchdogWarning } from \"./types.ts\";\n\nexport type WatchdogEmissionSuppressionReason = \"content-free\" | \"duplicate\" | \"max-warnings\" | \"update-budget\";\n\nexport type WatchdogEmissionDecision =\n\t| {\n\t\t\taccepted: true;\n\t\t\tidentity: string;\n\t\t\tunderlyingIdentity: string;\n\t\t\tescalation: boolean;\n\t  }\n\t| {\n\t\t\taccepted: false;\n\t\t\treason: WatchdogEmissionSuppressionReason;\n\t\t\tidentity?: string;\n\t\t\tunderlyingIdentity?: string;\n\t  };\n\nexport interface WatchdogEmissionGuardOptions {\n\tmaxWarnings?: number | null;\n\tdedupeHistoryLimit?: number;\n}\n\nconst CONTENT_FREE_PHRASES = new Set([\n\t\"stop\",\n\t\"done\",\n\t\"complete\",\n\t\"completed\",\n\t\"no issue\",\n\t\"no issues\",\n\t\"no concern\",\n\t\"no concerns\",\n\t\"nothing to add\",\n\t\"lgtm\",\n\t\"looks good\",\n\t\"looks good to me\",\n\t\"all good\",\n\t\"ok\",\n\t\"okay\",\n\t\"none\",\n\t\"n a\",\n]);\n\nexport function normalizeWatchdogEmissionText(value: string): string {\n\treturn value\n\t\t.normalize(\"NFKC\")\n\t\t.toLowerCase()\n\t\t.replace(/[’'`]/g, \"\")\n\t\t.replace(/[^\\p{L}\\p{N}]+/gu, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n}\n\nexport function watchdogWarningUnderlyingIdentity(warning: Pick<WatchdogWarning, \"summary\" | \"evidence\">): string {\n\treturn [normalizeWatchdogEmissionText(warning.summary), normalizeWatchdogEmissionText(warning.evidence)].join(\"\\n\");\n}\n\nexport function watchdogWarningIdentity(warning: Pick<WatchdogWarning, \"severity\" | \"summary\" | \"evidence\">): string {\n\treturn [warning.severity, watchdogWarningUnderlyingIdentity(warning)].join(\"\\n\");\n}\n\nfunction isContentFree(value: string): boolean {\n\tconst normalized = normalizeWatchdogEmissionText(value);\n\treturn !normalized || CONTENT_FREE_PHRASES.has(normalized);\n}\n\nexport class WatchdogEmissionGuard {\n\tprivate maxWarnings: number | null;\n\tprivate dedupeHistoryLimit: number;\n\tprivate acceptedCount = 0;\n\tprivate acceptedByUnderlyingIdentity = new Map<string, WatchdogSeverity>();\n\tprivate historyOrder: string[] = [];\n\tprivate updateAcceptedUnderlyingIdentity: string | undefined;\n\tprivate updateAcceptedSeverity: WatchdogSeverity | undefined;\n\n\tconstructor(options: WatchdogEmissionGuardOptions = {}) {\n\t\tthis.maxWarnings = options.maxWarnings ?? null;\n\t\tthis.dedupeHistoryLimit = options.dedupeHistoryLimit ?? 200;\n\t}\n\n\tstartModelUpdate(): void {\n\t\tthis.updateAcceptedUnderlyingIdentity = undefined;\n\t\tthis.updateAcceptedSeverity = undefined;\n\t}\n\n\treset(): void {\n\t\tthis.acceptedCount = 0;\n\t\tthis.acceptedByUnderlyingIdentity.clear();\n\t\tthis.historyOrder = [];\n\t\tthis.startModelUpdate();\n\t}\n\n\tevaluate(warning: WatchdogWarning): WatchdogEmissionDecision {\n\t\tif (\n\t\t\tisContentFree(warning.summary) ||\n\t\t\tisContentFree(warning.evidence) ||\n\t\t\tisContentFree(warning.recommendedAction)\n\t\t) {\n\t\t\treturn { accepted: false, reason: \"content-free\" };\n\t\t}\n\t\tconst underlyingIdentity = watchdogWarningUnderlyingIdentity(warning);\n\t\tconst identity = watchdogWarningIdentity(warning);\n\t\tconst priorSeverity = this.acceptedByUnderlyingIdentity.get(underlyingIdentity);\n\t\tconst escalation = priorSeverity === \"concern\" && warning.severity === \"blocker\";\n\t\tif (this.updateAcceptedUnderlyingIdentity !== undefined) {\n\t\t\tconst updateEscalation =\n\t\t\t\tthis.updateAcceptedUnderlyingIdentity === underlyingIdentity &&\n\t\t\t\tthis.updateAcceptedSeverity === \"concern\" &&\n\t\t\t\twarning.severity === \"blocker\";\n\t\t\tif (!updateEscalation) return { accepted: false, reason: \"update-budget\", identity, underlyingIdentity };\n\t\t}\n\t\tif (priorSeverity !== undefined && !escalation)\n\t\t\treturn { accepted: false, reason: \"duplicate\", identity, underlyingIdentity };\n\t\tif (this.maxWarnings !== null && this.acceptedCount >= this.maxWarnings && !escalation) {\n\t\t\treturn { accepted: false, reason: \"max-warnings\", identity, underlyingIdentity };\n\t\t}\n\n\t\tthis.acceptedByUnderlyingIdentity.set(underlyingIdentity, warning.severity);\n\t\tif (!priorSeverity) {\n\t\t\tthis.acceptedCount++;\n\t\t\tthis.historyOrder.push(underlyingIdentity);\n\t\t}\n\t\twhile (this.historyOrder.length > this.dedupeHistoryLimit) {\n\t\t\tconst stale = this.historyOrder.shift();\n\t\t\tif (stale) this.acceptedByUnderlyingIdentity.delete(stale);\n\t\t}\n\t\tthis.updateAcceptedUnderlyingIdentity = underlyingIdentity;\n\t\tthis.updateAcceptedSeverity = warning.severity;\n\t\treturn { accepted: true, identity, underlyingIdentity, escalation };\n\t}\n}\n"]}