@kevinrabun/judges 3.28.0 → 3.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +125 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/calibration-dashboard.d.ts +2 -0
  6. package/dist/commands/calibration-dashboard.d.ts.map +1 -0
  7. package/dist/commands/calibration-dashboard.js +97 -0
  8. package/dist/commands/calibration-dashboard.js.map +1 -0
  9. package/dist/commands/community-patterns.d.ts +2 -0
  10. package/dist/commands/community-patterns.d.ts.map +1 -0
  11. package/dist/commands/community-patterns.js +132 -0
  12. package/dist/commands/community-patterns.js.map +1 -0
  13. package/dist/commands/diff.d.ts.map +1 -1
  14. package/dist/commands/diff.js +91 -0
  15. package/dist/commands/diff.js.map +1 -1
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +26 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/evaluators/api-contract.d.ts +10 -0
  20. package/dist/evaluators/api-contract.d.ts.map +1 -0
  21. package/dist/evaluators/api-contract.js +181 -0
  22. package/dist/evaluators/api-contract.js.map +1 -0
  23. package/dist/evaluators/index.d.ts.map +1 -1
  24. package/dist/evaluators/index.js +33 -1
  25. package/dist/evaluators/index.js.map +1 -1
  26. package/dist/evaluators/intent-alignment.d.ts +15 -0
  27. package/dist/evaluators/intent-alignment.d.ts.map +1 -0
  28. package/dist/evaluators/intent-alignment.js +233 -0
  29. package/dist/evaluators/intent-alignment.js.map +1 -0
  30. package/dist/evaluators/model-fingerprint.d.ts +3 -0
  31. package/dist/evaluators/model-fingerprint.d.ts.map +1 -0
  32. package/dist/evaluators/model-fingerprint.js +152 -0
  33. package/dist/evaluators/model-fingerprint.js.map +1 -0
  34. package/dist/evaluators/multi-turn-coherence.d.ts +14 -0
  35. package/dist/evaluators/multi-turn-coherence.d.ts.map +1 -0
  36. package/dist/evaluators/multi-turn-coherence.js +171 -0
  37. package/dist/evaluators/multi-turn-coherence.js.map +1 -0
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/judges/api-contract.d.ts +3 -0
  41. package/dist/judges/api-contract.d.ts.map +1 -0
  42. package/dist/judges/api-contract.js +28 -0
  43. package/dist/judges/api-contract.js.map +1 -0
  44. package/dist/judges/index.d.ts.map +1 -1
  45. package/dist/judges/index.js +16 -0
  46. package/dist/judges/index.js.map +1 -1
  47. package/dist/judges/intent-alignment.d.ts +3 -0
  48. package/dist/judges/intent-alignment.d.ts.map +1 -0
  49. package/dist/judges/intent-alignment.js +28 -0
  50. package/dist/judges/intent-alignment.js.map +1 -0
  51. package/dist/judges/model-fingerprint.d.ts +3 -0
  52. package/dist/judges/model-fingerprint.d.ts.map +1 -0
  53. package/dist/judges/model-fingerprint.js +30 -0
  54. package/dist/judges/model-fingerprint.js.map +1 -0
  55. package/dist/judges/multi-turn-coherence.d.ts +3 -0
  56. package/dist/judges/multi-turn-coherence.d.ts.map +1 -0
  57. package/dist/judges/multi-turn-coherence.js +27 -0
  58. package/dist/judges/multi-turn-coherence.js.map +1 -0
  59. package/dist/patches/index.d.ts.map +1 -1
  60. package/dist/patches/index.js +372 -0
  61. package/dist/patches/index.js.map +1 -1
  62. package/dist/presets.d.ts.map +1 -1
  63. package/dist/presets.js +76 -0
  64. package/dist/presets.js.map +1 -1
  65. package/dist/types.d.ts +44 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/package.json +2 -2
  68. package/server.json +3 -3
@@ -0,0 +1,30 @@
1
+ export const modelFingerprintJudge = {
2
+ id: "model-fingerprint",
3
+ name: "Model Fingerprint Detection",
4
+ domain: "AI Code Provenance & Model Attribution",
5
+ description: "Detects stylistic fingerprints characteristic of specific AI code generators " +
6
+ "(ChatGPT/GPT-4, Claude, Copilot, Gemini) to flag code that may carry " +
7
+ "model-specific biases, hallucinations, or blind spots.",
8
+ rulePrefix: "MFPR",
9
+ systemPrompt: `You are Judge Model Fingerprint Detection — an expert in identifying stylistic signatures of AI-generated code.
10
+
11
+ YOUR EVALUATION CRITERIA:
12
+ 1. **ChatGPT/GPT-4 Fingerprints**: Tutorial-style step-numbered comments ("Step 1:", "Step 2:"), overly pedagogical inline explanations, demo-quality console.log statements.
13
+ 2. **Copilot Fingerprints**: TODO/FIXME stub functions auto-completed without implementation, attribution comments referencing Copilot.
14
+ 3. **Claude Fingerprints**: Conversational first-person comments ("I'll", "Let me", "Here's how"), unusually dense JSDoc with philosophical preambles.
15
+ 4. **Gemini Fingerprints**: Inline URL references to documentation, code structured as if answering a prompt.
16
+ 5. **Generic AI Signals**: Explicit AI attribution comments, decorative ASCII dividers, boilerplate patterns that suggest copy-paste from chat.
17
+
18
+ SEVERITY MAPPING:
19
+ - **info**: All model fingerprint detections — these are informational, not errors
20
+
21
+ FALSE POSITIVE AVOIDANCE:
22
+ - Require at least two distinct signal types before flagging.
23
+ - Do NOT flag well-written documentation simply because it is thorough.
24
+ - Single generic comments are not sufficient evidence.
25
+
26
+ ADVERSARIAL MANDATE:
27
+ - Flag AI-generated code that may carry model-specific biases or blind spots.
28
+ - Treat provenance transparency as a code quality concern.`,
29
+ };
30
+ //# sourceMappingURL=model-fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-fingerprint.js","sourceRoot":"","sources":["../../src/judges/model-fingerprint.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAoB;IACpD,EAAE,EAAE,mBAAmB;IACvB,IAAI,EAAE,6BAA6B;IACnC,MAAM,EAAE,wCAAwC;IAChD,WAAW,EACT,+EAA+E;QAC/E,uEAAuE;QACvE,wDAAwD;IAC1D,UAAU,EAAE,MAAM;IAClB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;2DAmB2C;CAC1D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { JudgeDefinition } from "../types.js";
2
+ export declare const multiTurnCoherenceJudge: JudgeDefinition;
3
+ //# sourceMappingURL=multi-turn-coherence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-turn-coherence.d.ts","sourceRoot":"","sources":["../../src/judges/multi-turn-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,uBAAuB,EAAE,eA0BrC,CAAC"}
@@ -0,0 +1,27 @@
1
+ export const multiTurnCoherenceJudge = {
2
+ id: "multi-turn-coherence",
3
+ name: "Multi-Turn Coherence",
4
+ description: "Detects self-contradicting patterns: duplicate function definitions, contradictory " +
5
+ "boolean assignments, dead code after returns, conflicting configs, and TODO density.",
6
+ domain: "Code Coherence & Consistency",
7
+ rulePrefix: "COH",
8
+ systemPrompt: `You are Judge Multi-Turn Coherence — an expert in detecting self-contradicting and incoherent code patterns.
9
+
10
+ YOUR EVALUATION CRITERIA:
11
+ 1. **Duplicate Definitions**: Multiple function/class/variable declarations with the same name in the same scope.
12
+ 2. **Contradictory Assignments**: Boolean or config variables assigned opposite values in close proximity without branching logic.
13
+ 3. **Dead Code After Returns**: Unreachable statements after return/throw/break/continue.
14
+ 4. **Conflicting Configuration**: Config objects that set contradictory options (e.g., debug: true and production: true simultaneously).
15
+ 5. **TODO Density**: Files where more than 20% of functions contain TODO/FIXME/HACK comments indicating incomplete implementation.
16
+
17
+ SEVERITY MAPPING:
18
+ - **critical**: Contradictory security settings (e.g., auth enabled and bypassed simultaneously)
19
+ - **high**: Duplicate function definitions that shadow each other, dead code after returns
20
+ - **medium**: Contradictory boolean assignments, conflicting configuration
21
+ - **low**: Excessive TODO density, minor style inconsistencies
22
+
23
+ ADVERSARIAL MANDATE:
24
+ - Treat every contradiction as a potential logic bug.
25
+ - Do NOT assume dead code is intentionally left for debugging.`,
26
+ };
27
+ //# sourceMappingURL=multi-turn-coherence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-turn-coherence.js","sourceRoot":"","sources":["../../src/judges/multi-turn-coherence.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,uBAAuB,GAAoB;IACtD,EAAE,EAAE,sBAAsB;IAC1B,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EACT,qFAAqF;QACrF,sFAAsF;IACxF,MAAM,EAAE,8BAA8B;IACtC,UAAU,EAAE,KAAK;IACjB,YAAY,EAAE;;;;;;;;;;;;;;;;;+DAiB+C;CAC9D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA6yD3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAyC9E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAkqE3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAyC9E"}
@@ -1484,6 +1484,378 @@ const PATCH_RULES = [
1484
1484
  return { oldText: m[0], newText: `${m[1]}${m[2]}@v4 /* TODO: pin to specific SHA */` };
1485
1485
  },
1486
1486
  },
1487
+ // ═══ Authentication Patches ═══
1488
+ // AUTH: plaintext password comparison → bcrypt
1489
+ {
1490
+ match: /AUTH-.*|plaintext.*password|password.*comparison.*plain|comparing.*password/i,
1491
+ generate: (line) => {
1492
+ const m = line.match(/(password|passwd)\s*===?\s*(\w+)/i);
1493
+ if (!m)
1494
+ return null;
1495
+ return { oldText: m[0], newText: `await bcrypt.compare(${m[2]}, hashedPassword) /* TODO: use bcrypt */` };
1496
+ },
1497
+ },
1498
+ // AUTH: session without expiry → add maxAge
1499
+ {
1500
+ match: /session.*expir|session.*timeout|session.*no.*expir/i,
1501
+ generate: (line) => {
1502
+ const m = line.match(/(session\s*\(\s*\{[^}]*?)(\})/);
1503
+ if (!m)
1504
+ return null;
1505
+ if (/maxAge|expires/.test(m[1]))
1506
+ return null;
1507
+ return { oldText: m[0], newText: `${m[1]}, maxAge: 1800000 /* 30 min */}` };
1508
+ },
1509
+ },
1510
+ // AUTH: jwt.decode → jwt.verify
1511
+ {
1512
+ match: /jwt.*decode.*verify|unverified.*jwt|jwt.*without.*verif/i,
1513
+ generate: (line) => {
1514
+ const m = line.match(/jwt\.decode\s*\(/);
1515
+ if (!m)
1516
+ return null;
1517
+ return { oldText: m[0], newText: `jwt.verify(` };
1518
+ },
1519
+ },
1520
+ // AUTH: missing rate limit on login
1521
+ {
1522
+ match: /brute.*force|login.*rate.*limit|missing.*rate.*limit.*auth/i,
1523
+ generate: (line) => {
1524
+ const m = line.match(/^(\s*)((?:app|router)\.post\s*\(\s*["']\/(?:login|auth|signin)["'])/);
1525
+ if (!m)
1526
+ return null;
1527
+ return {
1528
+ oldText: m[0],
1529
+ newText: `${m[1]}/* TODO: add rate limiting middleware (e.g., express-rate-limit) */\n${m[1]}${m[2]}`,
1530
+ };
1531
+ },
1532
+ },
1533
+ // ═══ Data Security Patches ═══
1534
+ // DSEC: storing PII in localStorage
1535
+ {
1536
+ match: /pii.*localStorage|sensitive.*local.*storage|localStorage.*personal/i,
1537
+ generate: (line) => {
1538
+ const m = line.match(/localStorage\.(setItem)\s*\(\s*(["'][^"']*(?:email|ssn|phone|name|address|dob)[^"']*["'])/i);
1539
+ if (!m)
1540
+ return null;
1541
+ return {
1542
+ oldText: `localStorage.${m[1]}`,
1543
+ newText: `sessionStorage.setItem /* TODO: encrypt or use httpOnly cookie instead */`,
1544
+ };
1545
+ },
1546
+ },
1547
+ // DSEC: logging PII fields
1548
+ {
1549
+ match: /pii.*log|logging.*personal|log.*sensitive|LOGPRIV/i,
1550
+ generate: (line) => {
1551
+ const m = line.match(/(console\.log|logger\.\w+)\s*\([^)]*\b(email|ssn|password|creditCard|phoneNumber|socialSecurity)\b/i);
1552
+ if (!m)
1553
+ return null;
1554
+ return { oldText: m[2], newText: `[REDACTED:${m[2]}]` };
1555
+ },
1556
+ },
1557
+ // ═══ Accessibility Patches ═══
1558
+ // A11Y: img without alt → add alt=""
1559
+ {
1560
+ match: /A11Y-.*|missing.*alt|img.*alt|image.*alt/i,
1561
+ generate: (line) => {
1562
+ const m = line.match(/(<img\s+(?:(?!alt=)[^>])*?)(\/?>)/i);
1563
+ if (!m)
1564
+ return null;
1565
+ if (/alt=/i.test(m[1]))
1566
+ return null;
1567
+ return { oldText: m[0], newText: `${m[1]} alt="" /* TODO: add descriptive alt text */${m[2]}` };
1568
+ },
1569
+ },
1570
+ // A11Y: button/link without aria-label (icon-only)
1571
+ {
1572
+ match: /aria.*label|icon.*button.*access|accessible.*name/i,
1573
+ generate: (line) => {
1574
+ const m = line.match(/(<(?:button|a)\s+(?:(?!aria-label)[^>])*?)(>)/i);
1575
+ if (!m)
1576
+ return null;
1577
+ if (/aria-label=/.test(m[1]))
1578
+ return null;
1579
+ if (!/icon|svg|fa-|material-icon/i.test(line))
1580
+ return null;
1581
+ return { oldText: m[0], newText: `${m[1]} aria-label="TODO: describe action"${m[2]}` };
1582
+ },
1583
+ },
1584
+ // A11Y: onClick div → button
1585
+ {
1586
+ match: /interactive.*div|div.*onclick|click.*handler.*div/i,
1587
+ generate: (line) => {
1588
+ const m = line.match(/<div(\s+[^>]*?)onClick/i);
1589
+ if (!m)
1590
+ return null;
1591
+ return { oldText: `<div${m[1]}onClick`, newText: `<button${m[1]}onClick` };
1592
+ },
1593
+ },
1594
+ // A11Y: autocomplete off on login fields
1595
+ {
1596
+ match: /autocomplete.*off.*password|login.*autocomplete/i,
1597
+ generate: (line) => {
1598
+ const m = line.match(/(type=["']password["'][^>]*?)autocomplete=["']off["']/i);
1599
+ if (!m)
1600
+ return null;
1601
+ return { oldText: `autocomplete="off"`, newText: `autocomplete="current-password"` };
1602
+ },
1603
+ },
1604
+ // ═══ AI Code Safety Patches ═══
1605
+ // AICS: user input concatenated into prompt
1606
+ {
1607
+ match: /AICS-.*|prompt.*inject|user.*input.*prompt|llm.*inject/i,
1608
+ generate: (line) => {
1609
+ const m = line.match(/(`[^`]*\$\{(?:req\.body|req\.query|request\.\w+|user[Ii]nput|input)\b[^}]*\}[^`]*`)/);
1610
+ if (!m)
1611
+ return null;
1612
+ return { oldText: m[0], newText: `/* TODO: sanitize user input before LLM prompt */ ${m[0]}` };
1613
+ },
1614
+ },
1615
+ // AICS: LLM output in innerHTML
1616
+ {
1617
+ match: /llm.*output.*html|ai.*output.*innerhtml|inject.*llm.*output/i,
1618
+ generate: (line) => {
1619
+ const m = line.match(/\.innerHTML\s*=\s*(\w+(?:\.(?:response|output|text|completion|content))?)/);
1620
+ if (!m)
1621
+ return null;
1622
+ return { oldText: `.innerHTML = ${m[1]}`, newText: `.textContent = ${m[1]} /* sanitize LLM output */` };
1623
+ },
1624
+ },
1625
+ // ═══ Compliance Patches ═══
1626
+ // COMP: cookie without consent check
1627
+ {
1628
+ match: /cookie.*consent|gdpr.*cookie|tracking.*consent/i,
1629
+ generate: (line) => {
1630
+ const m = line.match(/^(\s*)(document\.cookie\s*=)/);
1631
+ if (!m)
1632
+ return null;
1633
+ return { oldText: m[0], newText: `${m[1]}if (hasUserConsent()) ${m[2]} /* TODO: implement consent check */` };
1634
+ },
1635
+ },
1636
+ // COMP: collecting data without purpose
1637
+ {
1638
+ match: /data.*purpose|purpose.*limitation|gdpr.*purpose/i,
1639
+ generate: (line) => {
1640
+ const m = line.match(/^(\s*)((?:const|let|var)\s+\w+\s*=\s*(?:req\.body|request\.(?:form|json)))/);
1641
+ if (!m)
1642
+ return null;
1643
+ return {
1644
+ oldText: m[0],
1645
+ newText: `${m[1]}/* TODO: validate data collection purpose against privacy policy */\n${m[1]}${m[2]}`,
1646
+ };
1647
+ },
1648
+ },
1649
+ // ═══ Performance Patches ═══
1650
+ // PERF: regex in loop → pre-compile
1651
+ {
1652
+ match: /regex.*loop|regexp.*inside.*loop|pattern.*repeated/i,
1653
+ generate: (line) => {
1654
+ const m = line.match(/(new\s+RegExp\s*\(\s*(["'][^"']+["'])\s*(?:,\s*["'][^"']*["'])?\s*\))/);
1655
+ if (!m)
1656
+ return null;
1657
+ return { oldText: m[0], newText: `/* TODO: pre-compile regex outside loop */ ${m[0]}` };
1658
+ },
1659
+ },
1660
+ // PERF: Array spread in reduce → push
1661
+ {
1662
+ match: /spread.*reduce|array.*spread.*accumul|O\(n.\).*reduce/i,
1663
+ generate: (line) => {
1664
+ const m = line.match(/\[\.\.\.(acc|accumulator|result)\s*,/);
1665
+ if (!m)
1666
+ return null;
1667
+ return { oldText: `[...${m[1]},`, newText: `/* TODO: use push() instead of spread in reduce */ [...${m[1]},` };
1668
+ },
1669
+ },
1670
+ // ═══ Observability Patches ═══
1671
+ // OBS: catch without logging
1672
+ {
1673
+ match: /catch.*no.*log|error.*not.*logged|swallow.*without.*log/i,
1674
+ generate: (line) => {
1675
+ const m = line.match(/^(\s*)\}\s*catch\s*\((\w+)\)\s*\{\s*$/);
1676
+ if (!m)
1677
+ return null;
1678
+ return { oldText: m[0], newText: `${m[0]}\n${m[1]} console.error('Error:', ${m[2]});` };
1679
+ },
1680
+ },
1681
+ // ═══ IaC Security Patches ═══
1682
+ // IaC: Dockerfile USER root → USER node
1683
+ {
1684
+ match: /docker.*root|container.*root|USER.*root/i,
1685
+ generate: (line) => {
1686
+ const m = line.match(/^USER\s+root\s*$/);
1687
+ if (!m)
1688
+ return null;
1689
+ return { oldText: m[0], newText: `USER node` };
1690
+ },
1691
+ },
1692
+ // IaC: Terraform allow all ingress → restrict CIDR
1693
+ {
1694
+ match: /ingress.*0\.0\.0\.0|open.*ingress|unrestricted.*ingress/i,
1695
+ generate: (line) => {
1696
+ const m = line.match(/(cidr_blocks\s*=\s*\[)"0\.0\.0\.0\/0"(\])/);
1697
+ if (!m)
1698
+ return null;
1699
+ return { oldText: m[0], newText: `${m[1]}"10.0.0.0/8"${m[2]} /* TODO: restrict to your CIDR */` };
1700
+ },
1701
+ },
1702
+ // IaC: Kubernetes privileged container → drop privileges
1703
+ {
1704
+ match: /privileged.*true|container.*privileged|security.*context.*privileged/i,
1705
+ generate: (line) => {
1706
+ const m = line.match(/privileged:\s*true/);
1707
+ if (!m)
1708
+ return null;
1709
+ return { oldText: m[0], newText: `privileged: false` };
1710
+ },
1711
+ },
1712
+ // ═══ Database Patches ═══
1713
+ // DB: missing index comment
1714
+ {
1715
+ match: /missing.*index|no.*index.*query|full.*table.*scan/i,
1716
+ generate: (line) => {
1717
+ const m = line.match(/^(\s*)((?:SELECT|FROM|WHERE)\b.*)$/i);
1718
+ if (!m)
1719
+ return null;
1720
+ return { oldText: m[0], newText: `${m[1]}/* TODO: ensure WHERE-clause columns are indexed */ ${m[2]}` };
1721
+ },
1722
+ },
1723
+ // DB: transaction missing rollback
1724
+ {
1725
+ match: /transaction.*rollback|missing.*rollback|no.*rollback/i,
1726
+ generate: (line) => {
1727
+ const m = line.match(/^(\s*)((?:await\s+)?(?:client|conn|db|connection)\.query\s*\(\s*["']BEGIN)/);
1728
+ if (!m)
1729
+ return null;
1730
+ return { oldText: m[0], newText: `${m[1]}/* TODO: wrap in try/catch with ROLLBACK on error */\n${m[1]}${m[2]}` };
1731
+ },
1732
+ },
1733
+ // ═══ Concurrency Patches ═══
1734
+ // CONC: shared mutable variable → atomics hint
1735
+ {
1736
+ match: /race.*condition|shared.*mutable|concurrent.*access/i,
1737
+ generate: (line) => {
1738
+ const m = line.match(/^(\s*)(let|var)\s+(\w+)\s*=\s*(\d+)/);
1739
+ if (!m)
1740
+ return null;
1741
+ return { oldText: m[0], newText: `${m[1]}/* TODO: protect with mutex/lock */ ${m[2]} ${m[3]} = ${m[4]}` };
1742
+ },
1743
+ },
1744
+ // ═══ API Design Patches ═══
1745
+ // API: missing pagination
1746
+ {
1747
+ match: /missing.*pagination|no.*pagina|unbounded.*list/i,
1748
+ generate: (line) => {
1749
+ const m = line.match(/(\.find\s*\(\s*\{[^}]*\})\s*\)/);
1750
+ if (!m)
1751
+ return null;
1752
+ return { oldText: m[0], newText: `${m[1]}).limit(100) /* TODO: add proper pagination */` };
1753
+ },
1754
+ },
1755
+ // API: missing content-type validation
1756
+ {
1757
+ match: /content.*type.*valid|missing.*content.*type/i,
1758
+ generate: (line) => {
1759
+ const m = line.match(/^(\s*)((?:app|router)\.(post|put|patch)\s*\()/);
1760
+ if (!m)
1761
+ return null;
1762
+ return { oldText: m[0], newText: `${m[1]}/* TODO: validate Content-Type header */\n${m[1]}${m[2]}` };
1763
+ },
1764
+ },
1765
+ // ═══ Internationalization Patches ═══
1766
+ // I18N: hardcoded user-facing string → i18n key
1767
+ {
1768
+ match: /I18N-.*|hardcoded.*string|user.*facing.*literal/i,
1769
+ generate: (line) => {
1770
+ const m = line.match(/((?:label|title|message|placeholder|text|heading)\s*[:=]\s*)(["'])([A-Z][a-z].*?)\2/);
1771
+ if (!m)
1772
+ return null;
1773
+ const key = m[3]
1774
+ .toLowerCase()
1775
+ .replace(/\s+/g, "_")
1776
+ .replace(/[^a-z0-9_]/g, "")
1777
+ .slice(0, 30);
1778
+ return {
1779
+ oldText: `${m[1]}${m[2]}${m[3]}${m[2]}`,
1780
+ newText: `${m[1]}t("${key}") /* was: ${m[2]}${m[3]}${m[2]} */`,
1781
+ };
1782
+ },
1783
+ },
1784
+ // ═══ Scalability Patches ═══
1785
+ // SCAL: in-memory session store → comment
1786
+ {
1787
+ match: /in.*memory.*session|session.*memory.*store|express.*session.*default/i,
1788
+ generate: (line) => {
1789
+ const m = line.match(/^(\s*)(app\.use\s*\(\s*session\s*\(\s*\{)/);
1790
+ if (!m)
1791
+ return null;
1792
+ return {
1793
+ oldText: m[0],
1794
+ newText: `${m[1]}/* TODO: use Redis/database session store for multi-instance */ ${m[2]}`,
1795
+ };
1796
+ },
1797
+ },
1798
+ // ═══ Sovereignty Patches ═══
1799
+ // SOV: data sent to external analytics
1800
+ {
1801
+ match: /data.*third.*party|analytics.*external|sov.*data.*transfer/i,
1802
+ generate: (line) => {
1803
+ const m = line.match(/^(\s*)((?:fetch|axios\.\w+|https?\.(?:get|post))\s*\(\s*["']https?:\/\/(?:analytics|tracking|telemetry)\b)/);
1804
+ if (!m)
1805
+ return null;
1806
+ return { oldText: m[0], newText: `${m[1]}/* TODO: verify data residency compliance before sending */ ${m[2]}` };
1807
+ },
1808
+ },
1809
+ // ═══ Framework Safety Patches ═══
1810
+ // FW: Express without helmet → add helmet
1811
+ {
1812
+ match: /missing.*helmet|no.*security.*headers|express.*headers/i,
1813
+ generate: (line) => {
1814
+ const m = line.match(/^(\s*)(const\s+app\s*=\s*express\s*\(\s*\)\s*;?)/);
1815
+ if (!m)
1816
+ return null;
1817
+ return {
1818
+ oldText: m[0],
1819
+ newText: `${m[1]}const helmet = require("helmet"); /* TODO: npm install helmet */\n${m[1]}${m[2]}\n${m[1]}app.use(helmet());`,
1820
+ };
1821
+ },
1822
+ },
1823
+ // FW: Flask debug mode in production
1824
+ {
1825
+ match: /flask.*debug|debug.*production|app\.run.*debug/i,
1826
+ generate: (line) => {
1827
+ const m = line.match(/app\.run\s*\(\s*([^)]*)debug\s*=\s*True/);
1828
+ if (!m)
1829
+ return null;
1830
+ return { oldText: `debug=True`, newText: `debug=os.environ.get("FLASK_DEBUG", "false") == "true"` };
1831
+ },
1832
+ },
1833
+ // FW: Django SECRET_KEY hardcoded
1834
+ {
1835
+ match: /django.*secret|SECRET_KEY.*hardcoded|hardcoded.*django/i,
1836
+ generate: (line) => {
1837
+ const m = line.match(/^(\s*)SECRET_KEY\s*=\s*(["'])[^"']+\2/);
1838
+ if (!m)
1839
+ return null;
1840
+ return {
1841
+ oldText: m[0],
1842
+ newText: `${m[1]}SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-in-production")`,
1843
+ };
1844
+ },
1845
+ },
1846
+ // ═══ Supply Chain Patches ═══
1847
+ // DEP: importing from CDN without SRI
1848
+ {
1849
+ match: /sri.*missing|subresource.*integrity|cdn.*integrity/i,
1850
+ generate: (line) => {
1851
+ const m = line.match(/(<script\s+src=["']https?:\/\/cdn[^"']*["'])(\s*>)/i);
1852
+ if (!m)
1853
+ return null;
1854
+ if (/integrity=/i.test(m[1]))
1855
+ return null;
1856
+ return { oldText: m[0], newText: `${m[1]} integrity="TODO:sha384-hash" crossorigin="anonymous"${m[2]}` };
1857
+ },
1858
+ },
1487
1859
  ];
1488
1860
  const MULTI_LINE_PATCH_RULES = [
1489
1861
  // ── Multi-line empty catch block → re-throw with error parameter ──