trimference 0.3.0

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 (117) hide show
  1. package/BENCHMARK.md +39 -0
  2. package/CHANGELOG.md +41 -0
  3. package/CONTRIBUTING.md +43 -0
  4. package/DEMO.md +106 -0
  5. package/LICENSE +21 -0
  6. package/README.md +268 -0
  7. package/SECURITY.md +17 -0
  8. package/action.yml +182 -0
  9. package/dist/baseline.d.ts +13 -0
  10. package/dist/baseline.d.ts.map +1 -0
  11. package/dist/baseline.js +23 -0
  12. package/dist/baseline.js.map +1 -0
  13. package/dist/cli.d.ts +3 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +245 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/config.d.ts +31 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +127 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/ecoLLM.d.ts +33 -0
  22. package/dist/ecoLLM.d.ts.map +1 -0
  23. package/dist/ecoLLM.js +98 -0
  24. package/dist/ecoLLM.js.map +1 -0
  25. package/dist/impact.d.ts +22 -0
  26. package/dist/impact.d.ts.map +1 -0
  27. package/dist/impact.js +80 -0
  28. package/dist/impact.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +14 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/models.d.ts +21 -0
  34. package/dist/models.d.ts.map +1 -0
  35. package/dist/models.js +58 -0
  36. package/dist/models.js.map +1 -0
  37. package/dist/prComment.d.ts +36 -0
  38. package/dist/prComment.d.ts.map +1 -0
  39. package/dist/prComment.js +45 -0
  40. package/dist/prComment.js.map +1 -0
  41. package/dist/reporters/impactTracker.d.ts +21 -0
  42. package/dist/reporters/impactTracker.d.ts.map +1 -0
  43. package/dist/reporters/impactTracker.js +54 -0
  44. package/dist/reporters/impactTracker.js.map +1 -0
  45. package/dist/reporters/jsonReporter.d.ts +4 -0
  46. package/dist/reporters/jsonReporter.d.ts.map +1 -0
  47. package/dist/reporters/jsonReporter.js +34 -0
  48. package/dist/reporters/jsonReporter.js.map +1 -0
  49. package/dist/reporters/markdownReporter.d.ts +4 -0
  50. package/dist/reporters/markdownReporter.d.ts.map +1 -0
  51. package/dist/reporters/markdownReporter.js +97 -0
  52. package/dist/reporters/markdownReporter.js.map +1 -0
  53. package/dist/reporters/sarifReporter.d.ts +4 -0
  54. package/dist/reporters/sarifReporter.d.ts.map +1 -0
  55. package/dist/reporters/sarifReporter.js +63 -0
  56. package/dist/reporters/sarifReporter.js.map +1 -0
  57. package/dist/reporters/terminalReporter.d.ts +10 -0
  58. package/dist/reporters/terminalReporter.d.ts.map +1 -0
  59. package/dist/reporters/terminalReporter.js +80 -0
  60. package/dist/reporters/terminalReporter.js.map +1 -0
  61. package/dist/rules/agentLoopWithoutBudget.d.ts +3 -0
  62. package/dist/rules/agentLoopWithoutBudget.d.ts.map +1 -0
  63. package/dist/rules/agentLoopWithoutBudget.js +105 -0
  64. package/dist/rules/agentLoopWithoutBudget.js.map +1 -0
  65. package/dist/rules/expensiveModelSimpleTask.d.ts +3 -0
  66. package/dist/rules/expensiveModelSimpleTask.d.ts.map +1 -0
  67. package/dist/rules/expensiveModelSimpleTask.js +119 -0
  68. package/dist/rules/expensiveModelSimpleTask.js.map +1 -0
  69. package/dist/rules/frequentCron.d.ts +3 -0
  70. package/dist/rules/frequentCron.d.ts.map +1 -0
  71. package/dist/rules/frequentCron.js +64 -0
  72. package/dist/rules/frequentCron.js.map +1 -0
  73. package/dist/rules/helpers.d.ts +65 -0
  74. package/dist/rules/helpers.d.ts.map +1 -0
  75. package/dist/rules/helpers.js +383 -0
  76. package/dist/rules/helpers.js.map +1 -0
  77. package/dist/rules/hugeContext.d.ts +3 -0
  78. package/dist/rules/hugeContext.d.ts.map +1 -0
  79. package/dist/rules/hugeContext.js +74 -0
  80. package/dist/rules/hugeContext.js.map +1 -0
  81. package/dist/rules/imageGenerationLoop.d.ts +3 -0
  82. package/dist/rules/imageGenerationLoop.d.ts.map +1 -0
  83. package/dist/rules/imageGenerationLoop.js +72 -0
  84. package/dist/rules/imageGenerationLoop.js.map +1 -0
  85. package/dist/rules/index.d.ts +14 -0
  86. package/dist/rules/index.d.ts.map +1 -0
  87. package/dist/rules/index.js +24 -0
  88. package/dist/rules/index.js.map +1 -0
  89. package/dist/rules/missingRateLimit.d.ts +3 -0
  90. package/dist/rules/missingRateLimit.d.ts.map +1 -0
  91. package/dist/rules/missingRateLimit.js +78 -0
  92. package/dist/rules/missingRateLimit.js.map +1 -0
  93. package/dist/rules/noLlmCache.d.ts +3 -0
  94. package/dist/rules/noLlmCache.d.ts.map +1 -0
  95. package/dist/rules/noLlmCache.js +71 -0
  96. package/dist/rules/noLlmCache.js.map +1 -0
  97. package/dist/rules/noTokenLimit.d.ts +3 -0
  98. package/dist/rules/noTokenLimit.d.ts.map +1 -0
  99. package/dist/rules/noTokenLimit.js +59 -0
  100. package/dist/rules/noTokenLimit.js.map +1 -0
  101. package/dist/rules/repeatedEmbeddings.d.ts +3 -0
  102. package/dist/rules/repeatedEmbeddings.d.ts.map +1 -0
  103. package/dist/rules/repeatedEmbeddings.js +81 -0
  104. package/dist/rules/repeatedEmbeddings.js.map +1 -0
  105. package/dist/rules/sequentialLlmCalls.d.ts +3 -0
  106. package/dist/rules/sequentialLlmCalls.d.ts.map +1 -0
  107. package/dist/rules/sequentialLlmCalls.js +69 -0
  108. package/dist/rules/sequentialLlmCalls.js.map +1 -0
  109. package/dist/scanner.d.ts +23 -0
  110. package/dist/scanner.d.ts.map +1 -0
  111. package/dist/scanner.js +217 -0
  112. package/dist/scanner.js.map +1 -0
  113. package/dist/types.d.ts +93 -0
  114. package/dist/types.d.ts.map +1 -0
  115. package/dist/types.js +11 -0
  116. package/dist/types.js.map +1 -0
  117. package/package.json +75 -0
@@ -0,0 +1,65 @@
1
+ import type { Finding, ImpactEstimate, Severity, SourceFile, WasteCategory } from "../types.js";
2
+ export type MatchLocation = {
3
+ index: number;
4
+ line: number;
5
+ column: number;
6
+ matchText: string;
7
+ };
8
+ /** Convert a string index into a 1-based line and column. */
9
+ export declare function lineNumberForIndex(content: string, index: number): {
10
+ line: number;
11
+ column: number;
12
+ };
13
+ /**
14
+ * Find all matches for any of the given needles (plain strings or RegExp).
15
+ * Plain strings are matched case-insensitively as literals.
16
+ */
17
+ export declare function findMatches(content: string, needles: Array<string | RegExp>): MatchLocation[];
18
+ /**
19
+ * Return a same-length view of source with comments, quoted strings, template
20
+ * literals, and regex literals replaced by spaces. Newlines are preserved, so
21
+ * indexes and line numbers still map back to the original source.
22
+ *
23
+ * This is intentionally a small lexer rather than a parser. Rules use it only
24
+ * to prove that a call/identifier occurs in executable code; they can still
25
+ * inspect the original source around that proven call for model names and
26
+ * prompt text.
27
+ */
28
+ export declare function codeOnly(content: string): string;
29
+ /** Find matches that occur in executable code, excluding comments/literals. */
30
+ export declare function findCodeMatches(content: string, needles: Array<string | RegExp>): MatchLocation[];
31
+ /** Cached executable-code matching for rule scans. */
32
+ export declare function findCodeMatchesInFile(file: SourceFile, needles: Array<string | RegExp>): MatchLocation[];
33
+ /**
34
+ * Return the text within `windowLines` lines before and after the line that
35
+ * contains `index`. Used to check for nearby caching / limit / persistence terms.
36
+ */
37
+ export declare function getNearbyText(file: SourceFile, index: number, windowLines?: number): string;
38
+ /** Case-insensitive check for any needle appearing in text near an index. */
39
+ export declare function hasNearby(file: SourceFile, index: number, needles: Array<string | RegExp>, windowLines?: number): boolean;
40
+ /** Like hasNearby, but ignores terms that occur only in comments/literals. */
41
+ export declare function hasNearbyCode(file: SourceFile, index: number, needles: Array<string | RegExp>, windowLines?: number): boolean;
42
+ /** Grab a single trimmed line of source for use as a snippet. */
43
+ export declare function snippetForLine(file: SourceFile, line: number): string;
44
+ export declare function isTestFile(path: string): boolean;
45
+ export type CreateFindingArgs = {
46
+ ruleId: string;
47
+ title: string;
48
+ severity: Severity;
49
+ wasteCategory: WasteCategory;
50
+ file: SourceFile;
51
+ index: number;
52
+ message: string;
53
+ recommendation: string;
54
+ fixRecipe: string[];
55
+ impact: ImpactEstimate;
56
+ };
57
+ /** Build a Finding from a match index, computing line/column/snippet. */
58
+ export declare function createFinding(args: CreateFindingArgs): Finding;
59
+ /** Deduplicate findings that share ruleId + filePath + line. */
60
+ export declare function dedupeFindings(findings: Finding[]): Finding[];
61
+ /** Remove findings suppressed by inline Trimference directives. */
62
+ export declare function applyInlineDisables(file: SourceFile, findings: Finding[]): Finding[];
63
+ /** Shared list of patterns that strongly suggest an LLM chat/completion call. */
64
+ export declare const LLM_CALL_PATTERNS: Array<string | RegExp>;
65
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/rules/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,QAAQ,EACR,UAAU,EACV,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAaA;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAC9B,aAAa,EAAE,CA+CjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiGhD;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAC9B,aAAa,EAAE,CAKjB;AAYD,sDAAsD;AACtD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAC9B,aAAa,EAAE,CAQjB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,WAAW,SAAK,GACf,MAAM,CAKR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAC/B,WAAW,SAAK,GACf,OAAO,CAMT;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAC/B,WAAW,SAAK,GACf,OAAO,CAQT;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACxB,CAAC;AAEF,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAgB9D;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAU7D;AAoFD,mEAAmE;AACnE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,OAAO,EAAE,GAClB,OAAO,EAAE,CAOX;AAED,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAgBpD,CAAC"}
@@ -0,0 +1,383 @@
1
+ /** Convert a string index into a 1-based line and column. */
2
+ export function lineNumberForIndex(content, index) {
3
+ let line = 1;
4
+ let column = 1;
5
+ const bounded = Math.max(0, Math.min(index, content.length));
6
+ for (let i = 0; i < bounded; i++) {
7
+ if (content[i] === "\n") {
8
+ line++;
9
+ column = 1;
10
+ }
11
+ else {
12
+ column++;
13
+ }
14
+ }
15
+ return { line, column };
16
+ }
17
+ /**
18
+ * Find all matches for any of the given needles (plain strings or RegExp).
19
+ * Plain strings are matched case-insensitively as literals.
20
+ */
21
+ export function findMatches(content, needles) {
22
+ const results = [];
23
+ const lineStarts = [0];
24
+ for (let i = 0; i < content.length; i++) {
25
+ if (content[i] === "\n")
26
+ lineStarts.push(i + 1);
27
+ }
28
+ const fastLocation = (index) => {
29
+ let low = 0;
30
+ let high = lineStarts.length - 1;
31
+ while (low <= high) {
32
+ const mid = Math.floor((low + high) / 2);
33
+ if (lineStarts[mid] <= index)
34
+ low = mid + 1;
35
+ else
36
+ high = mid - 1;
37
+ }
38
+ const start = lineStarts[Math.max(0, high)];
39
+ return { line: high + 1, column: index - start + 1 };
40
+ };
41
+ for (const needle of needles) {
42
+ const regex = typeof needle === "string"
43
+ ? new RegExp(escapeRegExp(needle), "gi")
44
+ : ensureGlobal(needle);
45
+ let match;
46
+ regex.lastIndex = 0;
47
+ while ((match = regex.exec(content)) !== null) {
48
+ const { line, column } = fastLocation(match.index);
49
+ results.push({
50
+ index: match.index,
51
+ line,
52
+ column,
53
+ matchText: match[0],
54
+ });
55
+ if (match.index === regex.lastIndex)
56
+ regex.lastIndex++;
57
+ }
58
+ }
59
+ results.sort((a, b) => a.index - b.index);
60
+ // Collapse overlapping matches so that a single call site matched by several
61
+ // patterns (e.g. a literal and a regex) is only counted once.
62
+ const collapsed = [];
63
+ let prevEnd = -1;
64
+ for (const m of results) {
65
+ if (m.index < prevEnd)
66
+ continue;
67
+ collapsed.push(m);
68
+ prevEnd = m.index + m.matchText.length;
69
+ }
70
+ return collapsed;
71
+ }
72
+ /**
73
+ * Return a same-length view of source with comments, quoted strings, template
74
+ * literals, and regex literals replaced by spaces. Newlines are preserved, so
75
+ * indexes and line numbers still map back to the original source.
76
+ *
77
+ * This is intentionally a small lexer rather than a parser. Rules use it only
78
+ * to prove that a call/identifier occurs in executable code; they can still
79
+ * inspect the original source around that proven call for model names and
80
+ * prompt text.
81
+ */
82
+ export function codeOnly(content) {
83
+ const out = [...content];
84
+ let state = "code";
85
+ let escaped = false;
86
+ let inRegexClass = false;
87
+ const mask = (i) => {
88
+ if (out[i] !== "\n" && out[i] !== "\r")
89
+ out[i] = " ";
90
+ };
91
+ const previousSignificant = (i) => {
92
+ for (let j = i - 1; j >= 0; j--) {
93
+ if (!/\s/.test(content[j]))
94
+ return content[j];
95
+ }
96
+ return "";
97
+ };
98
+ const startsRegex = (i) => {
99
+ const prev = previousSignificant(i);
100
+ return prev === "" || "=(:,![{;?".includes(prev);
101
+ };
102
+ for (let i = 0; i < content.length; i++) {
103
+ const c = content[i];
104
+ const next = content[i + 1] ?? "";
105
+ if (state === "code") {
106
+ if (c === "#" && (i === 0 || /\s/.test(content[i - 1]))) {
107
+ mask(i);
108
+ state = "line-comment";
109
+ }
110
+ else if (c === "/" && next === "/") {
111
+ mask(i);
112
+ mask(i + 1);
113
+ state = "line-comment";
114
+ i++;
115
+ }
116
+ else if (c === "/" && next === "*") {
117
+ mask(i);
118
+ mask(i + 1);
119
+ state = "block-comment";
120
+ i++;
121
+ }
122
+ else if (c === "'") {
123
+ mask(i);
124
+ state = "single";
125
+ escaped = false;
126
+ }
127
+ else if (c === '"') {
128
+ mask(i);
129
+ state = "double";
130
+ escaped = false;
131
+ }
132
+ else if (c === "`") {
133
+ mask(i);
134
+ state = "template";
135
+ escaped = false;
136
+ }
137
+ else if (c === "/" && startsRegex(i)) {
138
+ mask(i);
139
+ state = "regex";
140
+ escaped = false;
141
+ inRegexClass = false;
142
+ }
143
+ continue;
144
+ }
145
+ mask(i);
146
+ if (state === "line-comment") {
147
+ if (c === "\n")
148
+ state = "code";
149
+ continue;
150
+ }
151
+ if (state === "block-comment") {
152
+ if (c === "*" && next === "/") {
153
+ mask(i + 1);
154
+ state = "code";
155
+ i++;
156
+ }
157
+ continue;
158
+ }
159
+ if (escaped) {
160
+ escaped = false;
161
+ continue;
162
+ }
163
+ if (c === "\\") {
164
+ escaped = true;
165
+ continue;
166
+ }
167
+ if (state === "single" && c === "'")
168
+ state = "code";
169
+ else if (state === "double" && c === '"')
170
+ state = "code";
171
+ else if (state === "template" && c === "`")
172
+ state = "code";
173
+ else if (state === "regex") {
174
+ if (c === "[")
175
+ inRegexClass = true;
176
+ else if (c === "]")
177
+ inRegexClass = false;
178
+ else if (c === "/" && !inRegexClass) {
179
+ while (/[a-z]/i.test(content[i + 1] ?? "")) {
180
+ i++;
181
+ mask(i);
182
+ }
183
+ state = "code";
184
+ }
185
+ }
186
+ }
187
+ return out.join("");
188
+ }
189
+ /** Find matches that occur in executable code, excluding comments/literals. */
190
+ export function findCodeMatches(content, needles) {
191
+ return findMatches(codeOnly(content), needles).map((match) => ({
192
+ ...match,
193
+ matchText: content.slice(match.index, match.index + match.matchText.length),
194
+ }));
195
+ }
196
+ const CODE_ONLY_CACHE = new WeakMap();
197
+ function executableCode(file) {
198
+ const cached = CODE_ONLY_CACHE.get(file);
199
+ if (cached !== undefined)
200
+ return cached;
201
+ const masked = codeOnly(file.content);
202
+ CODE_ONLY_CACHE.set(file, masked);
203
+ return masked;
204
+ }
205
+ /** Cached executable-code matching for rule scans. */
206
+ export function findCodeMatchesInFile(file, needles) {
207
+ return findMatches(executableCode(file), needles).map((match) => ({
208
+ ...match,
209
+ matchText: file.content.slice(match.index, match.index + match.matchText.length),
210
+ }));
211
+ }
212
+ /**
213
+ * Return the text within `windowLines` lines before and after the line that
214
+ * contains `index`. Used to check for nearby caching / limit / persistence terms.
215
+ */
216
+ export function getNearbyText(file, index, windowLines = 20) {
217
+ const { line } = lineNumberForIndex(file.content, index);
218
+ const start = Math.max(0, line - 1 - windowLines);
219
+ const end = Math.min(file.lines.length, line - 1 + windowLines + 1);
220
+ return file.lines.slice(start, end).join("\n");
221
+ }
222
+ /** Case-insensitive check for any needle appearing in text near an index. */
223
+ export function hasNearby(file, index, needles, windowLines = 20) {
224
+ const nearby = getNearbyText(file, index, windowLines).toLowerCase();
225
+ return needles.some((needle) => {
226
+ if (typeof needle === "string")
227
+ return nearby.includes(needle.toLowerCase());
228
+ return ensureGlobal(needle).test(nearby);
229
+ });
230
+ }
231
+ /** Like hasNearby, but ignores terms that occur only in comments/literals. */
232
+ export function hasNearbyCode(file, index, needles, windowLines = 20) {
233
+ const masked = executableCode(file);
234
+ const executable = {
235
+ ...file,
236
+ content: masked,
237
+ lines: masked.split(/\r?\n/),
238
+ };
239
+ return hasNearby(executable, index, needles, windowLines);
240
+ }
241
+ /** Grab a single trimmed line of source for use as a snippet. */
242
+ export function snippetForLine(file, line) {
243
+ const raw = file.lines[line - 1] ?? "";
244
+ return raw.trim().slice(0, 200);
245
+ }
246
+ export function isTestFile(path) {
247
+ return /(^|[/\\.])(test|spec|__tests__|__mocks__)([/\\.]|$)/i.test(path);
248
+ }
249
+ /** Build a Finding from a match index, computing line/column/snippet. */
250
+ export function createFinding(args) {
251
+ const { line, column } = lineNumberForIndex(args.file.content, args.index);
252
+ return {
253
+ ruleId: args.ruleId,
254
+ title: args.title,
255
+ severity: args.severity,
256
+ wasteCategory: args.wasteCategory,
257
+ filePath: args.file.path,
258
+ line,
259
+ column,
260
+ snippet: snippetForLine(args.file, line),
261
+ message: args.message,
262
+ recommendation: args.recommendation,
263
+ fixRecipe: args.fixRecipe,
264
+ impact: args.impact,
265
+ };
266
+ }
267
+ /** Deduplicate findings that share ruleId + filePath + line. */
268
+ export function dedupeFindings(findings) {
269
+ const seen = new Set();
270
+ const out = [];
271
+ for (const f of findings) {
272
+ const key = `${f.ruleId}::${f.filePath}::${f.line}`;
273
+ if (seen.has(key))
274
+ continue;
275
+ seen.add(key);
276
+ out.push(f);
277
+ }
278
+ return out;
279
+ }
280
+ /**
281
+ * Inline ignore directives, ESLint-style. Supported in any comment:
282
+ * // trimference-disable-next-line [ruleId ...] -> suppress findings on the next line
283
+ * // trimference-disable-line [ruleId ...] -> suppress findings on this line
284
+ * // trimference-disable [ruleId ...] -> start suppression block
285
+ * // trimference-enable [ruleId ...] -> end suppression block
286
+ * With no rule ids listed, the directive applies to all rules.
287
+ */
288
+ const DIRECTIVE_RE = /(?:trimference|ecolint)-(disable-next-line|disable-line|disable|enable)\b([^\n]*)/i;
289
+ function parseRuleIds(args) {
290
+ return args.match(/[a-z][a-z0-9-]*/gi) ?? [];
291
+ }
292
+ function addRuleLine(maps, line, ruleIds) {
293
+ if (ruleIds.length === 0) {
294
+ maps.allLines.add(line);
295
+ return;
296
+ }
297
+ const set = maps.ruleLines.get(line) ?? new Set();
298
+ for (const id of ruleIds)
299
+ set.add(id);
300
+ maps.ruleLines.set(line, set);
301
+ }
302
+ /** Build per-line suppression maps by scanning the file for ignore directives. */
303
+ function collectSuppressions(file) {
304
+ const maps = { allLines: new Set(), ruleLines: new Map() };
305
+ const totalLines = file.lines.length;
306
+ // Block state, applied from the directive line onward until re-enabled.
307
+ let blockAll = false;
308
+ const blockRules = new Set();
309
+ for (let i = 0; i < totalLines; i++) {
310
+ const lineNo = i + 1; // 1-based
311
+ const match = DIRECTIVE_RE.exec(file.lines[i] ?? "");
312
+ if (match) {
313
+ const kind = match[1].toLowerCase();
314
+ const ruleIds = parseRuleIds(match[2] ?? "");
315
+ switch (kind) {
316
+ case "disable-next-line":
317
+ addRuleLine(maps, lineNo + 1, ruleIds);
318
+ break;
319
+ case "disable-line":
320
+ addRuleLine(maps, lineNo, ruleIds);
321
+ break;
322
+ case "disable":
323
+ if (ruleIds.length === 0)
324
+ blockAll = true;
325
+ else
326
+ for (const id of ruleIds)
327
+ blockRules.add(id);
328
+ break;
329
+ case "enable":
330
+ if (ruleIds.length === 0) {
331
+ blockAll = false;
332
+ blockRules.clear();
333
+ }
334
+ else {
335
+ for (const id of ruleIds)
336
+ blockRules.delete(id);
337
+ }
338
+ break;
339
+ }
340
+ }
341
+ // Apply any active block suppression to this line.
342
+ if (blockAll)
343
+ maps.allLines.add(lineNo);
344
+ if (blockRules.size > 0)
345
+ addRuleLine(maps, lineNo, [...blockRules]);
346
+ }
347
+ return maps;
348
+ }
349
+ /** Remove findings suppressed by inline Trimference directives. */
350
+ export function applyInlineDisables(file, findings) {
351
+ if (findings.length === 0)
352
+ return findings;
353
+ const { allLines, ruleLines } = collectSuppressions(file);
354
+ if (allLines.size === 0 && ruleLines.size === 0)
355
+ return findings;
356
+ return findings.filter((f) => !(allLines.has(f.line) || ruleLines.get(f.line)?.has(f.ruleId)));
357
+ }
358
+ /** Shared list of patterns that strongly suggest an LLM chat/completion call. */
359
+ export const LLM_CALL_PATTERNS = [
360
+ "openai.chat.completions.create",
361
+ "openai.responses.create",
362
+ "client.responses.create",
363
+ "anthropic.messages.create",
364
+ "client.messages.create",
365
+ /chat\.completions\.create/i,
366
+ /\bgenerateText\s*\(/i,
367
+ /\bstreamText\s*\(/i,
368
+ /\bmessages\.create\s*\(/i,
369
+ /\bresponses\.create\s*\(/i,
370
+ /\bchat\.complete\s*\(/i,
371
+ /\bgenerateContent\s*\(/i,
372
+ /\bmodels\.generate_content\s*\(/i,
373
+ /\binvoke_model\s*\(/i,
374
+ /\b(?:llm|chat_model)\.(?:invoke|ainvoke|stream|astream)\s*\(/i,
375
+ ];
376
+ function escapeRegExp(literal) {
377
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
378
+ }
379
+ function ensureGlobal(regex) {
380
+ const flags = regex.flags.includes("g") ? regex.flags : regex.flags + "g";
381
+ return new RegExp(regex.source, flags);
382
+ }
383
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/rules/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const hugeContextRule: Rule;
3
+ //# sourceMappingURL=hugeContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hugeContext.d.ts","sourceRoot":"","sources":["../../src/rules/hugeContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAc,MAAM,aAAa,CAAC;AAwC7D,eAAO,MAAM,eAAe,EAAE,IA8C7B,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { makeImpact } from "../impact.js";
2
+ import { LLM_CALL_PATTERNS, createFinding, dedupeFindings, findCodeMatchesInFile, } from "./helpers.js";
3
+ /**
4
+ * Variable names / patterns that suggest large or unbounded context is being
5
+ * passed into a model call.
6
+ */
7
+ const CONTEXT_PATTERNS = [
8
+ "fullConversation",
9
+ "fullHistory",
10
+ "conversationHistory",
11
+ "entireDocument",
12
+ "entireFile",
13
+ "rawTranscript",
14
+ "allMessages",
15
+ "allDocs",
16
+ "fullText",
17
+ "documentText",
18
+ /messages\s*:\s*history/i,
19
+ /messages\s*:\s*conversation/i,
20
+ /content\s*:\s*full\b/i,
21
+ /content\s*:\s*fullText/i,
22
+ /messages\s*:\s*user\.messages/i,
23
+ /messages\s*:\s*thread\.messages/i,
24
+ /content\s*:\s*document\b/i,
25
+ ];
26
+ const FIX_RECIPE = [
27
+ "Set a maximum conversation length.",
28
+ "Summarize older messages before sending them to the model.",
29
+ "Use retrieval to include only relevant document chunks.",
30
+ "Add tests that fail when prompt size exceeds your limit.",
31
+ ];
32
+ export const hugeContextRule = {
33
+ id: "huge-context",
34
+ title: "Potential oversized prompt or context",
35
+ severity: "medium",
36
+ wasteCategory: "token-bloat",
37
+ description: "Flags variables and message fields whose names imply large or unbounded context being sent to a model.",
38
+ recommendation: "Summarize long histories, chunk documents, retrieve only relevant context, and set explicit context limits.",
39
+ fixRecipe: FIX_RECIPE,
40
+ scan(file) {
41
+ const findings = [];
42
+ const calls = findCodeMatchesInFile(file, LLM_CALL_PATTERNS);
43
+ if (calls.length === 0)
44
+ return findings;
45
+ for (const match of findCodeMatchesInFile(file, CONTEXT_PATTERNS)) {
46
+ // Require the context signal to be close to a proven model call. This
47
+ // avoids flagging unrelated variables and rule/documentation fixtures.
48
+ if (!calls.some((call) => Math.abs(call.line - match.line) <= 30))
49
+ continue;
50
+ findings.push(createFinding({
51
+ ruleId: this.id,
52
+ title: this.title,
53
+ severity: this.severity,
54
+ wasteCategory: this.wasteCategory,
55
+ file,
56
+ index: match.index,
57
+ message: "This code may send large or unbounded context to the model.",
58
+ recommendation: this.recommendation,
59
+ fixRecipe: this.fixRecipe,
60
+ impact: makeImpact({
61
+ computeWaste: "high",
62
+ carbonImpact: "medium",
63
+ waterImpact: "medium",
64
+ costImpact: "high",
65
+ confidence: "medium",
66
+ score: 75,
67
+ explanation: "Oversized prompts increase token processing and can amplify cost and infrastructure usage.",
68
+ }),
69
+ }));
70
+ }
71
+ return dedupeFindings(findings);
72
+ },
73
+ };
74
+ //# sourceMappingURL=hugeContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hugeContext.js","sourceRoot":"","sources":["../../src/rules/hugeContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB;;;GAGG;AACH,MAAM,gBAAgB,GAA2B;IAC/C,kBAAkB;IAClB,aAAa;IACb,qBAAqB;IACrB,gBAAgB;IAChB,YAAY;IACZ,eAAe;IACf,aAAa;IACb,SAAS;IACT,UAAU;IACV,cAAc;IACd,yBAAyB;IACzB,8BAA8B;IAC9B,uBAAuB;IACvB,yBAAyB;IACzB,gCAAgC;IAChC,kCAAkC;IAClC,2BAA2B;CAC5B,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,oCAAoC;IACpC,4DAA4D;IAC5D,yDAAyD;IACzD,0DAA0D;CAC3D,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,cAAc;IAClB,KAAK,EAAE,uCAAuC;IAC9C,QAAQ,EAAE,QAAQ;IAClB,aAAa,EAAE,aAAa;IAC5B,WAAW,EACT,wGAAwG;IAC1G,cAAc,EACZ,6GAA6G;IAC/G,SAAS,EAAE,UAAU;IACrB,IAAI,CAAC,IAAgB;QACnB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAC7D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAExC,KAAK,MAAM,KAAK,IAAI,qBAAqB,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC;YAClE,sEAAsE;YACtE,uEAAuE;YACvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAAE,SAAS;YAC5E,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC;gBACZ,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,IAAI;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE,6DAA6D;gBACtE,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,UAAU,CAAC;oBACjB,YAAY,EAAE,MAAM;oBACpB,YAAY,EAAE,QAAQ;oBACtB,WAAW,EAAE,QAAQ;oBACrB,UAAU,EAAE,MAAM;oBAClB,UAAU,EAAE,QAAQ;oBACpB,KAAK,EAAE,EAAE;oBACT,WAAW,EACT,4FAA4F;iBAC/F,CAAC;aACH,CAAC,CACH,CAAC;QACJ,CAAC;QAED,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const imageGenerationLoopRule: Rule;
3
+ //# sourceMappingURL=imageGenerationLoop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageGenerationLoop.d.ts","sourceRoot":"","sources":["../../src/rules/imageGenerationLoop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAc,MAAM,aAAa,CAAC;AAsC7D,eAAO,MAAM,uBAAuB,EAAE,IAiDrC,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { makeImpact } from "../impact.js";
2
+ import { createFinding, dedupeFindings, findCodeMatchesInFile, hasNearbyCode, } from "./helpers.js";
3
+ const IMAGE_GEN_PATTERNS = [
4
+ /images\.generate/i,
5
+ /generateImage/i,
6
+ /image\.create/i,
7
+ /replicate\.run/i,
8
+ "stability",
9
+ "dall-e",
10
+ "gpt-image",
11
+ ];
12
+ const LOOP_RETRY_TERMS = [
13
+ /for\s*\(/i,
14
+ /while\s*\(/i,
15
+ /\.map\s*\(/i,
16
+ /forEach\s*\(/i,
17
+ "retry",
18
+ "attempt",
19
+ "tries",
20
+ "setInterval",
21
+ "cron",
22
+ ];
23
+ const FIX_RECIPE = [
24
+ "Move image generation outside loops or retry paths when possible.",
25
+ "Add strict per-user and per-job generation caps.",
26
+ "Cache generated images by prompt and parameters.",
27
+ "Require explicit user confirmation before repeated image generation.",
28
+ ];
29
+ export const imageGenerationLoopRule = {
30
+ id: "image-generation-loop",
31
+ title: "Image generation inside loop or retry path",
32
+ severity: "high",
33
+ wasteCategory: "multimodal-cost-explosion",
34
+ description: "Flags image generation calls that appear near loops, retry logic, or scheduled jobs.",
35
+ recommendation: "Add strict caps, caching, idempotency keys, and user confirmation before repeated image generation.",
36
+ fixRecipe: FIX_RECIPE,
37
+ scan(file) {
38
+ const findings = [];
39
+ // A single call site (e.g. `images.generate({ model: "dall-e-3" })`) can
40
+ // match several patterns on adjacent lines; only report it once.
41
+ let lastReportedLine = -Infinity;
42
+ for (const match of findCodeMatchesInFile(file, IMAGE_GEN_PATTERNS)) {
43
+ if (!hasNearbyCode(file, match.index, LOOP_RETRY_TERMS, 20))
44
+ continue;
45
+ if (match.line - lastReportedLine <= 2)
46
+ continue;
47
+ lastReportedLine = match.line;
48
+ findings.push(createFinding({
49
+ ruleId: this.id,
50
+ title: this.title,
51
+ severity: this.severity,
52
+ wasteCategory: this.wasteCategory,
53
+ file,
54
+ index: match.index,
55
+ message: "Image generation appears inside a loop, retry path, or repeated job.",
56
+ recommendation: this.recommendation,
57
+ fixRecipe: this.fixRecipe,
58
+ impact: makeImpact({
59
+ computeWaste: "high",
60
+ carbonImpact: "high",
61
+ waterImpact: "medium",
62
+ costImpact: "high",
63
+ confidence: "medium",
64
+ score: 90,
65
+ explanation: "Repeated image generation can be compute-intensive and expensive, especially in loops or retries.",
66
+ }),
67
+ }));
68
+ }
69
+ return dedupeFindings(findings);
70
+ },
71
+ };
72
+ //# sourceMappingURL=imageGenerationLoop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageGenerationLoop.js","sourceRoot":"","sources":["../../src/rules/imageGenerationLoop.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,MAAM,kBAAkB,GAA2B;IACjD,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,WAAW;IACX,QAAQ;IACR,WAAW;CACZ,CAAC;AAEF,MAAM,gBAAgB,GAA2B;IAC/C,WAAW;IACX,aAAa;IACb,aAAa;IACb,eAAe;IACf,OAAO;IACP,SAAS;IACT,OAAO;IACP,aAAa;IACb,MAAM;CACP,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,mEAAmE;IACnE,kDAAkD;IAClD,kDAAkD;IAClD,sEAAsE;CACvE,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAS;IAC3C,EAAE,EAAE,uBAAuB;IAC3B,KAAK,EAAE,4CAA4C;IACnD,QAAQ,EAAE,MAAM;IAChB,aAAa,EAAE,2BAA2B;IAC1C,WAAW,EACT,sFAAsF;IACxF,cAAc,EACZ,qGAAqG;IACvG,SAAS,EAAE,UAAU;IACrB,IAAI,CAAC,IAAgB;QACnB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,yEAAyE;QACzE,iEAAiE;QACjE,IAAI,gBAAgB,GAAG,CAAC,QAAQ,CAAC;QAEjC,KAAK,MAAM,KAAK,IAAI,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,CAAC;YACpE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC;gBAAE,SAAS;YACtE,IAAI,KAAK,CAAC,IAAI,GAAG,gBAAgB,IAAI,CAAC;gBAAE,SAAS;YACjD,gBAAgB,GAAG,KAAK,CAAC,IAAI,CAAC;YAE9B,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC;gBACZ,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,IAAI;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EACL,sEAAsE;gBACxE,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,UAAU,CAAC;oBACjB,YAAY,EAAE,MAAM;oBACpB,YAAY,EAAE,MAAM;oBACpB,WAAW,EAAE,QAAQ;oBACrB,UAAU,EAAE,MAAM;oBAClB,UAAU,EAAE,QAAQ;oBACpB,KAAK,EAAE,EAAE;oBACT,WAAW,EACT,mGAAmG;iBACtG,CAAC;aACH,CAAC,CACH,CAAC;QACJ,CAAC;QAED,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF,CAAC"}