| 338 | // ============================================================================ |
| 339 | |
| 340 | function evaluateRules(context) { |
| 341 | const { |
| 342 | prType, effectiveChanges, lowRiskOnly, |
| 343 | domains, headDomains, coreAreas, coreSignals, |
| 344 | sensitiveKeywords, sensitive, newShortcutDomain, |
| 345 | singleDomain, multiDomain, filenames |
| 346 | } = context; |
| 347 | |
| 348 | const reasons = []; |
| 349 | let label; |
| 350 | |
| 351 | if (lowRiskOnly && (LOW_RISK_TYPES.has(prType) || effectiveChanges === 0)) { |
| 352 | reasons.push("Only low-risk docs, CI, test, or chore paths were changed, with no effective business code or Skill changes"); |
| 353 | label = "size/S"; |
| 354 | return { label, reasons }; |
| 355 | } |
| 356 | |
| 357 | // XL is reserved for architecture-level or global-impact changes. |
| 358 | const isXL = |
| 359 | effectiveChanges > THRESHOLD_XL || |
| 360 | (prType === "refactor" && sensitive && effectiveChanges >= THRESHOLD_L) || |
| 361 | (coreAreas.size >= 2 && (multiDomain || effectiveChanges >= THRESHOLD_L)) || |
| 362 | (headDomains.length >= 2 && sensitive); |
| 363 | |
| 364 | if (isXL) { |
| 365 | if (effectiveChanges > THRESHOLD_XL) reasons.push("Effective business code or Skill changes are far beyond the L threshold"); |
| 366 | if (prType === "refactor" && sensitive && effectiveChanges >= THRESHOLD_L) reasons.push("Refactor PR touches core or sensitive paths"); |
| 367 | if (coreAreas.size >= 2) reasons.push("Touches multiple core areas at the same time"); |
| 368 | if (headDomains.length >= 2) reasons.push("Impacts multiple major business domains"); |
| 369 | coreSignals.forEach((signal) => reasons.push(`Core area hit: ${signal}`)); |
| 370 | sensitiveKeywords.forEach((keyword) => reasons.push(`Sensitive keyword hit: ${keyword}`)); |
| 371 | label = "size/XL"; |
| 372 | } else if ( |
| 373 | prType === "refactor" || |
| 374 | effectiveChanges >= THRESHOLD_L || |
| 375 | Boolean(newShortcutDomain) || |
| 376 | multiDomain || |
| 377 | sensitive |
| 378 | ) { |
| 379 | if (prType === "refactor") reasons.push("PR type is refactor"); |
| 380 | if (effectiveChanges >= THRESHOLD_L) reasons.push(`Effective business code or Skill changes exceed ${THRESHOLD_L} lines`); |
| 381 | if (newShortcutDomain) reasons.push(`Introduces a new business domain directory: shortcuts/${newShortcutDomain}/`); |
| 382 | if (multiDomain) reasons.push("Touches multiple business domains"); |
| 383 | coreSignals.forEach((signal) => reasons.push(`Core area hit: ${signal}`)); |
| 384 | sensitiveKeywords.forEach((keyword) => reasons.push(`Sensitive keyword hit: ${keyword}`)); |
| 385 | label = "size/L"; |
| 386 | } else { |
| 387 | if (filenames.some(isBusinessSkillPath) || effectiveChanges > 0) { |
| 388 | reasons.push("Regular feat, fix, or Skill change within a single business domain"); |
| 389 | } |
| 390 | if (singleDomain && domains.size > 0) { |
| 391 | reasons.push(`Impact is limited to a single business domain: ${[...domains].sort().join(", ")}`); |
| 392 | } |
| 393 | if (effectiveChanges < THRESHOLD_L) { |
| 394 | reasons.push(`Effective business code or Skill changes are below ${THRESHOLD_L} lines`); |
| 395 | } |
| 396 | label = "size/M"; |
| 397 | } |