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.
- package/BENCHMARK.md +39 -0
- package/CHANGELOG.md +41 -0
- package/CONTRIBUTING.md +43 -0
- package/DEMO.md +106 -0
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/SECURITY.md +17 -0
- package/action.yml +182 -0
- package/dist/baseline.d.ts +13 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +23 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +245 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +127 -0
- package/dist/config.js.map +1 -0
- package/dist/ecoLLM.d.ts +33 -0
- package/dist/ecoLLM.d.ts.map +1 -0
- package/dist/ecoLLM.js +98 -0
- package/dist/ecoLLM.js.map +1 -0
- package/dist/impact.d.ts +22 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +80 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +21 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +58 -0
- package/dist/models.js.map +1 -0
- package/dist/prComment.d.ts +36 -0
- package/dist/prComment.d.ts.map +1 -0
- package/dist/prComment.js +45 -0
- package/dist/prComment.js.map +1 -0
- package/dist/reporters/impactTracker.d.ts +21 -0
- package/dist/reporters/impactTracker.d.ts.map +1 -0
- package/dist/reporters/impactTracker.js +54 -0
- package/dist/reporters/impactTracker.js.map +1 -0
- package/dist/reporters/jsonReporter.d.ts +4 -0
- package/dist/reporters/jsonReporter.d.ts.map +1 -0
- package/dist/reporters/jsonReporter.js +34 -0
- package/dist/reporters/jsonReporter.js.map +1 -0
- package/dist/reporters/markdownReporter.d.ts +4 -0
- package/dist/reporters/markdownReporter.d.ts.map +1 -0
- package/dist/reporters/markdownReporter.js +97 -0
- package/dist/reporters/markdownReporter.js.map +1 -0
- package/dist/reporters/sarifReporter.d.ts +4 -0
- package/dist/reporters/sarifReporter.d.ts.map +1 -0
- package/dist/reporters/sarifReporter.js +63 -0
- package/dist/reporters/sarifReporter.js.map +1 -0
- package/dist/reporters/terminalReporter.d.ts +10 -0
- package/dist/reporters/terminalReporter.d.ts.map +1 -0
- package/dist/reporters/terminalReporter.js +80 -0
- package/dist/reporters/terminalReporter.js.map +1 -0
- package/dist/rules/agentLoopWithoutBudget.d.ts +3 -0
- package/dist/rules/agentLoopWithoutBudget.d.ts.map +1 -0
- package/dist/rules/agentLoopWithoutBudget.js +105 -0
- package/dist/rules/agentLoopWithoutBudget.js.map +1 -0
- package/dist/rules/expensiveModelSimpleTask.d.ts +3 -0
- package/dist/rules/expensiveModelSimpleTask.d.ts.map +1 -0
- package/dist/rules/expensiveModelSimpleTask.js +119 -0
- package/dist/rules/expensiveModelSimpleTask.js.map +1 -0
- package/dist/rules/frequentCron.d.ts +3 -0
- package/dist/rules/frequentCron.d.ts.map +1 -0
- package/dist/rules/frequentCron.js +64 -0
- package/dist/rules/frequentCron.js.map +1 -0
- package/dist/rules/helpers.d.ts +65 -0
- package/dist/rules/helpers.d.ts.map +1 -0
- package/dist/rules/helpers.js +383 -0
- package/dist/rules/helpers.js.map +1 -0
- package/dist/rules/hugeContext.d.ts +3 -0
- package/dist/rules/hugeContext.d.ts.map +1 -0
- package/dist/rules/hugeContext.js +74 -0
- package/dist/rules/hugeContext.js.map +1 -0
- package/dist/rules/imageGenerationLoop.d.ts +3 -0
- package/dist/rules/imageGenerationLoop.d.ts.map +1 -0
- package/dist/rules/imageGenerationLoop.js +72 -0
- package/dist/rules/imageGenerationLoop.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +24 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/missingRateLimit.d.ts +3 -0
- package/dist/rules/missingRateLimit.d.ts.map +1 -0
- package/dist/rules/missingRateLimit.js +78 -0
- package/dist/rules/missingRateLimit.js.map +1 -0
- package/dist/rules/noLlmCache.d.ts +3 -0
- package/dist/rules/noLlmCache.d.ts.map +1 -0
- package/dist/rules/noLlmCache.js +71 -0
- package/dist/rules/noLlmCache.js.map +1 -0
- package/dist/rules/noTokenLimit.d.ts +3 -0
- package/dist/rules/noTokenLimit.d.ts.map +1 -0
- package/dist/rules/noTokenLimit.js +59 -0
- package/dist/rules/noTokenLimit.js.map +1 -0
- package/dist/rules/repeatedEmbeddings.d.ts +3 -0
- package/dist/rules/repeatedEmbeddings.d.ts.map +1 -0
- package/dist/rules/repeatedEmbeddings.js +81 -0
- package/dist/rules/repeatedEmbeddings.js.map +1 -0
- package/dist/rules/sequentialLlmCalls.d.ts +3 -0
- package/dist/rules/sequentialLlmCalls.d.ts.map +1 -0
- package/dist/rules/sequentialLlmCalls.js +69 -0
- package/dist/rules/sequentialLlmCalls.js.map +1 -0
- package/dist/scanner.d.ts +23 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +217 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownReporter.d.ts","sourceRoot":"","sources":["../../src/reporters/markdownReporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,UAAU,EAAY,MAAM,aAAa,CAAC;AAgFjE,0DAA0D;AAC1D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuD/D"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { IMPACT_DISCLAIMER, operationalImpactLine } from "../impact.js";
|
|
2
|
+
import { WASTE_CATEGORY_LABEL } from "../types.js";
|
|
3
|
+
import { categoryBreakdown, topFixOpportunities } from "./impactTracker.js";
|
|
4
|
+
const SEVERITY_TITLE = {
|
|
5
|
+
high: "High",
|
|
6
|
+
medium: "Medium",
|
|
7
|
+
low: "Low",
|
|
8
|
+
};
|
|
9
|
+
function escapeCell(text) {
|
|
10
|
+
return text.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
11
|
+
}
|
|
12
|
+
function escapeHtml(text) {
|
|
13
|
+
return text
|
|
14
|
+
.replace(/&/g, "&")
|
|
15
|
+
.replace(/</g, "<")
|
|
16
|
+
.replace(/>/g, ">")
|
|
17
|
+
.replace(/"/g, """);
|
|
18
|
+
}
|
|
19
|
+
function location(finding) {
|
|
20
|
+
const label = `${finding.filePath}:${finding.line}`;
|
|
21
|
+
const repositoryUrl = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY
|
|
22
|
+
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`
|
|
23
|
+
: undefined;
|
|
24
|
+
const revision = process.env.GITHUB_SHA;
|
|
25
|
+
const href = repositoryUrl && revision
|
|
26
|
+
? `${repositoryUrl}/blob/${revision}/${encodeURI(finding.filePath)}#L${finding.line}`
|
|
27
|
+
: `./${encodeURI(finding.filePath)}#L${finding.line}`;
|
|
28
|
+
return `[\`${label}\`](${href})`;
|
|
29
|
+
}
|
|
30
|
+
function findingsTable(findings) {
|
|
31
|
+
const rows = findings.map((finding) => {
|
|
32
|
+
const evidence = finding.snippet
|
|
33
|
+
? `<code>${escapeHtml(escapeCell(finding.snippet))}</code>`
|
|
34
|
+
: "—";
|
|
35
|
+
return `| ${SEVERITY_TITLE[finding.severity]} | ${finding.impact.confidence} | ${WASTE_CATEGORY_LABEL[finding.wasteCategory]} | ${location(finding)} | ${evidence} |`;
|
|
36
|
+
});
|
|
37
|
+
return [
|
|
38
|
+
"| Severity | Confidence | Safeguard | Location | Evidence |",
|
|
39
|
+
"|---|---|---|---|---|",
|
|
40
|
+
...rows,
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
function findingDetail(finding) {
|
|
44
|
+
const title = `${SEVERITY_TITLE[finding.severity]} · ${finding.title} · ${finding.filePath}:${finding.line}`;
|
|
45
|
+
const out = [
|
|
46
|
+
"<details>",
|
|
47
|
+
`<summary>${escapeHtml(title)}</summary>`,
|
|
48
|
+
"",
|
|
49
|
+
`- **Rule:** \`${finding.ruleId}\``,
|
|
50
|
+
`- **Safeguard:** ${WASTE_CATEGORY_LABEL[finding.wasteCategory]}`,
|
|
51
|
+
`- **Location:** ${location(finding)}`,
|
|
52
|
+
`- **Signal:** ${operationalImpactLine(finding.impact)}`,
|
|
53
|
+
];
|
|
54
|
+
if (finding.snippet) {
|
|
55
|
+
out.push("", "**Evidence:**", "", `> <code>${escapeHtml(finding.snippet)}</code>`);
|
|
56
|
+
}
|
|
57
|
+
out.push("", `**Issue:** ${finding.message}`, "", `**Why review it:** ${finding.impact.explanation}`, "", `**Recommended change:** ${finding.recommendation}`, "", "**Fix recipe:**", "");
|
|
58
|
+
finding.fixRecipe.forEach((step, index) => out.push(`${index + 1}. ${step}`));
|
|
59
|
+
out.push("", "</details>");
|
|
60
|
+
return out.join("\n");
|
|
61
|
+
}
|
|
62
|
+
/** Render a compact GitHub-friendly efficiency review. */
|
|
63
|
+
export function renderMarkdownReport(result) {
|
|
64
|
+
const { summary, findings } = result;
|
|
65
|
+
const out = ["# Trimference Efficiency Review", ""];
|
|
66
|
+
if (summary.ruleErrors.length > 0) {
|
|
67
|
+
out.push(`> ⚠️ ${summary.ruleErrors.length} rule execution error(s) occurred; this report may be incomplete.`, "");
|
|
68
|
+
}
|
|
69
|
+
if (findings.length === 0) {
|
|
70
|
+
out.push(`Scanned **${summary.filesScanned} files** in **${summary.durationMs}ms** and found **no obvious missing AI efficiency safeguards**.`);
|
|
71
|
+
if (summary.baselineSuppressed > 0) {
|
|
72
|
+
out.push("", `${summary.baselineSuppressed} accepted baseline finding(s) were hidden.`);
|
|
73
|
+
}
|
|
74
|
+
out.push("", `> ${IMPACT_DISCLAIMER}`);
|
|
75
|
+
return out.join("\n");
|
|
76
|
+
}
|
|
77
|
+
out.push(`Scanned **${summary.filesScanned} files** in **${summary.durationMs}ms** and found **${summary.totalFindings} review item(s)**.`, "", "| High | Medium | Low | Baseline hidden |", "|---:|---:|---:|---:|", `| ${summary.high} | ${summary.medium} | ${summary.low} | ${summary.baselineSuppressed} |`, "", `> ${IMPACT_DISCLAIMER}`, "", "## Safeguards to review", "", "| Area | Findings |", "|---|---:|");
|
|
78
|
+
for (const item of categoryBreakdown(result)) {
|
|
79
|
+
out.push(`| ${item.label} | ${item.count} |`);
|
|
80
|
+
}
|
|
81
|
+
const opportunities = topFixOpportunities(result);
|
|
82
|
+
if (opportunities.length > 0) {
|
|
83
|
+
out.push("", "## Top opportunities", "");
|
|
84
|
+
opportunities.forEach((item, index) => out.push(`${index + 1}. ${item.label}`));
|
|
85
|
+
}
|
|
86
|
+
if (summary.suggestedFirstPass.length > 0) {
|
|
87
|
+
out.push("", "## Suggested first pass", "");
|
|
88
|
+
summary.suggestedFirstPass.forEach((action, index) => {
|
|
89
|
+
out.push(`${index + 1}. ${action}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
out.push("", "## Findings", "", findingsTable(findings), "");
|
|
93
|
+
for (const finding of findings)
|
|
94
|
+
out.push(findingDetail(finding), "");
|
|
95
|
+
return out.join("\n");
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=markdownReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownReporter.js","sourceRoot":"","sources":["../../src/reporters/markdownReporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarifReporter.d.ts","sourceRoot":"","sources":["../../src/reporters/sarifReporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,aAAa,CAAC;AASxD,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAwD5D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { rules } from "../rules/index.js";
|
|
2
|
+
const LEVEL = {
|
|
3
|
+
high: "error",
|
|
4
|
+
medium: "warning",
|
|
5
|
+
low: "note",
|
|
6
|
+
};
|
|
7
|
+
/** Render SARIF 2.1.0 for GitHub code scanning and compatible tooling. */
|
|
8
|
+
export function renderSarifReport(result) {
|
|
9
|
+
const usedRuleIds = new Set(result.findings.map((finding) => finding.ruleId));
|
|
10
|
+
const ruleMetadata = rules
|
|
11
|
+
.filter((rule) => usedRuleIds.has(rule.id))
|
|
12
|
+
.map((rule) => ({
|
|
13
|
+
id: rule.id,
|
|
14
|
+
name: rule.id,
|
|
15
|
+
shortDescription: { text: rule.title },
|
|
16
|
+
fullDescription: { text: rule.description },
|
|
17
|
+
help: { text: rule.recommendation },
|
|
18
|
+
defaultConfiguration: { level: LEVEL[rule.severity] },
|
|
19
|
+
properties: { category: rule.wasteCategory },
|
|
20
|
+
}));
|
|
21
|
+
const payload = {
|
|
22
|
+
version: "2.1.0",
|
|
23
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
24
|
+
runs: [
|
|
25
|
+
{
|
|
26
|
+
tool: {
|
|
27
|
+
driver: {
|
|
28
|
+
name: "Trimference",
|
|
29
|
+
informationUri: "https://github.com/psamme/trimference",
|
|
30
|
+
rules: ruleMetadata,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
results: result.findings.map((finding) => ({
|
|
34
|
+
ruleId: finding.ruleId,
|
|
35
|
+
level: LEVEL[finding.severity],
|
|
36
|
+
message: {
|
|
37
|
+
text: `${finding.message} ${finding.recommendation}`,
|
|
38
|
+
},
|
|
39
|
+
locations: [
|
|
40
|
+
{
|
|
41
|
+
physicalLocation: {
|
|
42
|
+
artifactLocation: { uri: finding.filePath },
|
|
43
|
+
region: {
|
|
44
|
+
startLine: finding.line,
|
|
45
|
+
...(finding.column ? { startColumn: finding.column } : {}),
|
|
46
|
+
...(finding.snippet
|
|
47
|
+
? { snippet: { text: finding.snippet } }
|
|
48
|
+
: {}),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
properties: {
|
|
54
|
+
confidence: finding.impact.confidence,
|
|
55
|
+
safeguard: finding.wasteCategory,
|
|
56
|
+
},
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
return JSON.stringify(payload, null, 2);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=sarifReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarifReporter.js","sourceRoot":"","sources":["../../src/reporters/sarifReporter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,MAAM,KAAK,GAAmD;IAC5D,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,MAAM;CACZ,CAAC;AAEF,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,MAAM,YAAY,GAAG,KAAK;SACvB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,EAAE;QACb,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE;QACtC,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE;QAC3C,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE;QACnC,oBAAoB,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACrD,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE;KAC7C,CAAC,CAAC,CAAC;IAEN,MAAM,OAAO,GAAG;QACd,OAAO,EAAE,OAAO;QAChB,OAAO,EACL,+CAA+C;QACjD,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,aAAa;wBACnB,cAAc,EAAE,uCAAuC;wBACvD,KAAK,EAAE,YAAY;qBACpB;iBACF;gBACD,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;oBACzC,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;oBAC9B,OAAO,EAAE;wBACP,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,cAAc,EAAE;qBACrD;oBACD,SAAS,EAAE;wBACT;4BACE,gBAAgB,EAAE;gCAChB,gBAAgB,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE;gCAC3C,MAAM,EAAE;oCACN,SAAS,EAAE,OAAO,CAAC,IAAI;oCACvB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oCAC1D,GAAG,CAAC,OAAO,CAAC,OAAO;wCACjB,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE;wCACxC,CAAC,CAAC,EAAE,CAAC;iCACR;6BACF;yBACF;qBACF;oBACD,UAAU,EAAE;wBACV,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;wBACrC,SAAS,EAAE,OAAO,CAAC,aAAa;qBACjC;iBACF,CAAC,CAAC;aACJ;SACF;KACF,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ScanResult } from "../types.js";
|
|
2
|
+
/** Default number of compact finding blocks shown in a terminal. */
|
|
3
|
+
export declare const DEFAULT_MAX_FINDINGS = 8;
|
|
4
|
+
export type TerminalReportOptions = {
|
|
5
|
+
maxFindings?: number;
|
|
6
|
+
summary?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** Render a compact, evidence-first terminal review. */
|
|
9
|
+
export declare function renderTerminalReport(result: ScanResult, options?: TerminalReportOptions): string;
|
|
10
|
+
//# sourceMappingURL=terminalReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminalReporter.d.ts","sourceRoot":"","sources":["../../src/reporters/terminalReporter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAW,UAAU,EAAY,MAAM,aAAa,CAAC;AAGjE,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAoBF,wDAAwD;AACxD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkER"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { IMPACT_DISCLAIMER } from "../impact.js";
|
|
3
|
+
import { WASTE_CATEGORY_LABEL } from "../types.js";
|
|
4
|
+
import { categoryBreakdown, topFixOpportunities } from "./impactTracker.js";
|
|
5
|
+
/** Default number of compact finding blocks shown in a terminal. */
|
|
6
|
+
export const DEFAULT_MAX_FINDINGS = 8;
|
|
7
|
+
function severityTag(severity, confidence) {
|
|
8
|
+
const tag = `[${severity.toUpperCase()} · ${confidence.toUpperCase()} CONFIDENCE]`;
|
|
9
|
+
if (severity === "high")
|
|
10
|
+
return pc.bold(pc.red(tag));
|
|
11
|
+
if (severity === "medium")
|
|
12
|
+
return pc.bold(pc.yellow(tag));
|
|
13
|
+
return pc.bold(pc.cyan(tag));
|
|
14
|
+
}
|
|
15
|
+
function renderFinding(finding) {
|
|
16
|
+
const out = [
|
|
17
|
+
`${severityTag(finding.severity, finding.impact.confidence)} ${pc.bold(finding.title)}`,
|
|
18
|
+
`${finding.filePath}:${finding.line} · ${finding.ruleId} · ${WASTE_CATEGORY_LABEL[finding.wasteCategory]}`,
|
|
19
|
+
];
|
|
20
|
+
if (finding.snippet)
|
|
21
|
+
out.push(pc.dim(` ${finding.snippet}`));
|
|
22
|
+
out.push(`Issue: ${finding.message}`);
|
|
23
|
+
out.push(`Fix: ${finding.recommendation}`);
|
|
24
|
+
return out.join("\n");
|
|
25
|
+
}
|
|
26
|
+
/** Render a compact, evidence-first terminal review. */
|
|
27
|
+
export function renderTerminalReport(result, options = {}) {
|
|
28
|
+
const { summary, findings } = result;
|
|
29
|
+
const out = [pc.bold(pc.green("Trimference efficiency review")), ""];
|
|
30
|
+
out.push(`Scanned ${pc.bold(String(summary.filesScanned))} files in ${summary.durationMs}ms.`);
|
|
31
|
+
if (summary.ruleErrors.length > 0) {
|
|
32
|
+
out.push(pc.red(`${summary.ruleErrors.length} rule execution error(s); results may be incomplete.`));
|
|
33
|
+
}
|
|
34
|
+
if (findings.length === 0) {
|
|
35
|
+
out.push("No obvious AI efficiency safeguards are missing.");
|
|
36
|
+
if (summary.baselineSuppressed > 0) {
|
|
37
|
+
out.push(pc.dim(`${summary.baselineSuppressed} baseline finding(s) hidden.`));
|
|
38
|
+
}
|
|
39
|
+
out.push("", pc.dim(`Note: ${IMPACT_DISCLAIMER}`));
|
|
40
|
+
return out.join("\n");
|
|
41
|
+
}
|
|
42
|
+
out.push(`Found ${pc.bold(String(summary.totalFindings))} review item(s): ` +
|
|
43
|
+
`${pc.red(`${summary.high} high`)} · ` +
|
|
44
|
+
`${pc.yellow(`${summary.medium} medium`)} · ` +
|
|
45
|
+
`${pc.cyan(`${summary.low} low`)}`);
|
|
46
|
+
if (summary.baselineSuppressed > 0) {
|
|
47
|
+
out.push(pc.dim(`${summary.baselineSuppressed} accepted baseline finding(s) hidden.`));
|
|
48
|
+
}
|
|
49
|
+
const breakdown = categoryBreakdown(result);
|
|
50
|
+
if (breakdown.length > 0) {
|
|
51
|
+
out.push("", pc.bold("Safeguards to review:"));
|
|
52
|
+
for (const item of breakdown)
|
|
53
|
+
out.push(`- ${item.label}: ${item.count}`);
|
|
54
|
+
}
|
|
55
|
+
const opportunities = topFixOpportunities(result);
|
|
56
|
+
if (opportunities.length > 0) {
|
|
57
|
+
out.push("", pc.bold("Top opportunities:"));
|
|
58
|
+
opportunities.forEach((item, index) => out.push(`${index + 1}. ${item.label}`));
|
|
59
|
+
}
|
|
60
|
+
if (summary.suggestedFirstPass.length > 0) {
|
|
61
|
+
out.push("", pc.bold("Suggested first pass:"));
|
|
62
|
+
summary.suggestedFirstPass.forEach((action, index) => {
|
|
63
|
+
out.push(`${index + 1}. ${action}`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!options.summary) {
|
|
67
|
+
const max = options.maxFindings ?? DEFAULT_MAX_FINDINGS;
|
|
68
|
+
const shown = max === 0 ? findings : findings.slice(0, max);
|
|
69
|
+
out.push("", pc.bold("Prioritized findings:"));
|
|
70
|
+
for (const finding of shown)
|
|
71
|
+
out.push("", renderFinding(finding));
|
|
72
|
+
const hidden = findings.length - shown.length;
|
|
73
|
+
if (hidden > 0) {
|
|
74
|
+
out.push("", pc.dim(`...plus ${hidden} more. Use --markdown, --json, or --max-findings 0 to see all.`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
out.push("", pc.dim(`Note: ${IMPACT_DISCLAIMER}`));
|
|
78
|
+
return out.join("\n");
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=terminalReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminalReporter.js","sourceRoot":"","sources":["../../src/reporters/terminalReporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentLoopWithoutBudget.d.ts","sourceRoot":"","sources":["../../src/rules/agentLoopWithoutBudget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAc,MAAM,aAAa,CAAC;AAgF7D,eAAO,MAAM,0BAA0B,EAAE,IA4CxC,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { makeImpact } from "../impact.js";
|
|
2
|
+
import { createFinding, findCodeMatchesInFile, hasNearbyCode, } from "./helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Strong signals that a file drives an agent / repeated tool-execution loop.
|
|
5
|
+
* We keep this conservative: an explicit agent-execution term, or an infinite
|
|
6
|
+
* loop that clearly sits next to model/tool/agent code.
|
|
7
|
+
*/
|
|
8
|
+
const AGENT_EXECUTION_TERMS = [
|
|
9
|
+
/agent\.run\s*\(/i,
|
|
10
|
+
/\brunAgent\s*\(/i,
|
|
11
|
+
/\bexecuteAgent\s*\(/i,
|
|
12
|
+
/\bagentExecutor\b/i,
|
|
13
|
+
/\bagentLoop\b/i,
|
|
14
|
+
/\btoolCalls\b/i,
|
|
15
|
+
];
|
|
16
|
+
/** Infinite / open-ended loops that can drive runaway agent execution. */
|
|
17
|
+
const INFINITE_LOOP_TERMS = [
|
|
18
|
+
/while\s*\(\s*true\s*\)/i,
|
|
19
|
+
/for\s*\(\s*;\s*;\s*\)/,
|
|
20
|
+
];
|
|
21
|
+
/** Terms that suggest an infinite loop is doing model/tool work (not I/O polling). */
|
|
22
|
+
const AGENT_CONTEXT_TERMS = [
|
|
23
|
+
/agent/i,
|
|
24
|
+
/\btool\b/i,
|
|
25
|
+
/toolCalls/i,
|
|
26
|
+
"openai",
|
|
27
|
+
"anthropic",
|
|
28
|
+
/generateText/i,
|
|
29
|
+
/messages\.create/i,
|
|
30
|
+
/chat\.completions\.create/i,
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Budget / stopping-condition terms. If any appear in the file we assume the
|
|
34
|
+
* loop is bounded and stay quiet.
|
|
35
|
+
*/
|
|
36
|
+
const BUDGET_TERMS = [
|
|
37
|
+
"maxSteps",
|
|
38
|
+
"maxIterations",
|
|
39
|
+
"maxTurns",
|
|
40
|
+
"timeout",
|
|
41
|
+
"AbortController",
|
|
42
|
+
"signal",
|
|
43
|
+
"budget",
|
|
44
|
+
"costLimit",
|
|
45
|
+
"tokenLimit",
|
|
46
|
+
"max_tokens",
|
|
47
|
+
"maxOutputTokens",
|
|
48
|
+
];
|
|
49
|
+
const FIX_RECIPE = [
|
|
50
|
+
"Add a maximum number of agent/tool iterations.",
|
|
51
|
+
"Add a timeout or AbortController.",
|
|
52
|
+
"Add token and cost budgets where possible.",
|
|
53
|
+
"Log when a run stops because it hit a budget.",
|
|
54
|
+
];
|
|
55
|
+
/** Find the first agent-loop signal in the file, or null if none. */
|
|
56
|
+
function firstAgentLoopSignal(file) {
|
|
57
|
+
// (a) An explicit agent-execution term is enough on its own.
|
|
58
|
+
const executionMatches = findCodeMatchesInFile(file, AGENT_EXECUTION_TERMS);
|
|
59
|
+
// (b) An infinite loop only counts when model/tool/agent code is nearby.
|
|
60
|
+
const loopMatches = findCodeMatchesInFile(file, INFINITE_LOOP_TERMS).filter((m) => hasNearbyCode(file, m.index, AGENT_CONTEXT_TERMS, 15));
|
|
61
|
+
const candidates = [...executionMatches, ...loopMatches].sort((a, b) => a.index - b.index);
|
|
62
|
+
return candidates.length > 0 ? candidates[0].index : null;
|
|
63
|
+
}
|
|
64
|
+
export const agentLoopWithoutBudgetRule = {
|
|
65
|
+
id: "agent-loop-without-budget",
|
|
66
|
+
title: "Agent loop without obvious budget",
|
|
67
|
+
severity: "high",
|
|
68
|
+
wasteCategory: "repeated-inference",
|
|
69
|
+
description: "Flags files that appear to run an agent or repeated tool/model loop without an obvious step, token, time, or cost budget.",
|
|
70
|
+
recommendation: "Add explicit max steps, timeouts, token caps, and/or cost budgets to prevent runaway agent loops.",
|
|
71
|
+
fixRecipe: FIX_RECIPE,
|
|
72
|
+
scan(file) {
|
|
73
|
+
const signalIndex = firstAgentLoopSignal(file);
|
|
74
|
+
if (signalIndex === null)
|
|
75
|
+
return [];
|
|
76
|
+
// Only a budget close to the loop/call counts; an unrelated timeout in the
|
|
77
|
+
// same file should not silence the finding.
|
|
78
|
+
if (hasNearbyCode(file, signalIndex, BUDGET_TERMS, 40))
|
|
79
|
+
return [];
|
|
80
|
+
// Report once per file to avoid noise.
|
|
81
|
+
return [
|
|
82
|
+
createFinding({
|
|
83
|
+
ruleId: this.id,
|
|
84
|
+
title: this.title,
|
|
85
|
+
severity: this.severity,
|
|
86
|
+
wasteCategory: this.wasteCategory,
|
|
87
|
+
file,
|
|
88
|
+
index: signalIndex,
|
|
89
|
+
message: "This agentic flow appears to run without an obvious step, token, time, or cost budget.",
|
|
90
|
+
recommendation: this.recommendation,
|
|
91
|
+
fixRecipe: this.fixRecipe,
|
|
92
|
+
impact: makeImpact({
|
|
93
|
+
computeWaste: "high",
|
|
94
|
+
carbonImpact: "medium",
|
|
95
|
+
waterImpact: "medium",
|
|
96
|
+
costImpact: "high",
|
|
97
|
+
confidence: "medium",
|
|
98
|
+
score: 88,
|
|
99
|
+
explanation: "Agent loops can multiply model/tool calls if they do not have explicit stopping budgets.",
|
|
100
|
+
}),
|
|
101
|
+
}),
|
|
102
|
+
];
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=agentLoopWithoutBudget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentLoopWithoutBudget.js","sourceRoot":"","sources":["../../src/rules/agentLoopWithoutBudget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expensiveModelSimpleTask.d.ts","sourceRoot":"","sources":["../../src/rules/expensiveModelSimpleTask.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAA2B,MAAM,aAAa,CAAC;AA8E1E,eAAO,MAAM,4BAA4B,EAAE,IAgE1C,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { makeImpact } from "../impact.js";
|
|
2
|
+
import { detectProvider, suggestSmallerModels } from "../models.js";
|
|
3
|
+
import { LLM_CALL_PATTERNS, createFinding, dedupeFindings, findCodeMatchesInFile, findMatches, getNearbyText, hasNearby, } from "./helpers.js";
|
|
4
|
+
const LARGE_MODEL_PATTERNS = [
|
|
5
|
+
/gpt-4(\.1|o)?/i,
|
|
6
|
+
"opus",
|
|
7
|
+
"claude-3-opus",
|
|
8
|
+
"claude-opus",
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Downsized siblings of large models (e.g. gpt-4o-mini) are the *right* choice
|
|
12
|
+
* for simple tasks, so we must not flag them. Because a pattern like `gpt-4o`
|
|
13
|
+
* is a prefix of `gpt-4o-mini`, we inspect the full model slug at the match.
|
|
14
|
+
*/
|
|
15
|
+
const DOWNSIZED_VARIANT = /-(mini|nano|small|lite|micro|flash|instant)/i;
|
|
16
|
+
/** The full model slug starting at a match, e.g. "gpt-4.1" or "claude-3-opus". */
|
|
17
|
+
function modelSlugAt(content, index) {
|
|
18
|
+
return content.slice(index).match(/^[\w.-]+/)?.[0] ?? "";
|
|
19
|
+
}
|
|
20
|
+
function isDownsizedVariant(content, index) {
|
|
21
|
+
return DOWNSIZED_VARIANT.test(modelSlugAt(content, index));
|
|
22
|
+
}
|
|
23
|
+
const SIMPLE_TASK_LABELS = [
|
|
24
|
+
[/classif|categor|sentiment|label|tag/i, "classification"],
|
|
25
|
+
[/extract/i, "extraction"],
|
|
26
|
+
[/rout/i, "routing"],
|
|
27
|
+
[/moderat/i, "moderation"],
|
|
28
|
+
[/boolean|yes\/no|true\/false/i, "a yes/no decision"],
|
|
29
|
+
];
|
|
30
|
+
/** Name the kind of simple task hinted at near a model call, if any. */
|
|
31
|
+
function describeTask(nearby) {
|
|
32
|
+
for (const [re, label] of SIMPLE_TASK_LABELS) {
|
|
33
|
+
if (re.test(nearby))
|
|
34
|
+
return label;
|
|
35
|
+
}
|
|
36
|
+
return "a simple task";
|
|
37
|
+
}
|
|
38
|
+
const SIMPLE_TASK_TERMS = [
|
|
39
|
+
"classify",
|
|
40
|
+
"classification",
|
|
41
|
+
"sentiment",
|
|
42
|
+
"tag",
|
|
43
|
+
"label",
|
|
44
|
+
"extract",
|
|
45
|
+
"extraction",
|
|
46
|
+
"route",
|
|
47
|
+
"routing",
|
|
48
|
+
"moderate",
|
|
49
|
+
"moderation",
|
|
50
|
+
"boolean",
|
|
51
|
+
"yes/no",
|
|
52
|
+
"true/false",
|
|
53
|
+
"category",
|
|
54
|
+
"categorize",
|
|
55
|
+
];
|
|
56
|
+
const FIX_RECIPE = [
|
|
57
|
+
"Classify the task type: routing, tagging, extraction, moderation, or reasoning.",
|
|
58
|
+
"Use a smaller model for simple structured tasks.",
|
|
59
|
+
"Cache deterministic results.",
|
|
60
|
+
"Add evaluation examples to verify quality remains acceptable.",
|
|
61
|
+
];
|
|
62
|
+
export const expensiveModelSimpleTaskRule = {
|
|
63
|
+
id: "expensive-model-simple-task",
|
|
64
|
+
title: "Large model used for a simple task",
|
|
65
|
+
severity: "medium",
|
|
66
|
+
wasteCategory: "model-overkill",
|
|
67
|
+
description: "Flags top-tier model names that appear near simple classification, extraction, or routing language.",
|
|
68
|
+
recommendation: "Consider a smaller model, rules-based logic, batching, or caching for simple structured tasks.",
|
|
69
|
+
fixRecipe: FIX_RECIPE,
|
|
70
|
+
scan(file, context) {
|
|
71
|
+
const findings = [];
|
|
72
|
+
const calls = findCodeMatchesInFile(file, LLM_CALL_PATTERNS);
|
|
73
|
+
if (calls.length === 0)
|
|
74
|
+
return findings;
|
|
75
|
+
for (const match of findMatches(file.content, LARGE_MODEL_PATTERNS)) {
|
|
76
|
+
if (!calls.some((call) => Math.abs(call.line - match.line) <= 30))
|
|
77
|
+
continue;
|
|
78
|
+
// Don't flag already right-sized models like gpt-4o-mini.
|
|
79
|
+
if (isDownsizedVariant(file.content, match.index))
|
|
80
|
+
continue;
|
|
81
|
+
// Only flag when a simple-task word appears within ~30 lines.
|
|
82
|
+
if (!hasNearby(file, match.index, SIMPLE_TASK_TERMS, 30))
|
|
83
|
+
continue;
|
|
84
|
+
const model = modelSlugAt(file.content, match.index);
|
|
85
|
+
const nearby = getNearbyText(file, match.index, 30);
|
|
86
|
+
const task = describeTask(nearby);
|
|
87
|
+
// Prefer the provider detected from the model slug; fall back to config.
|
|
88
|
+
const detected = detectProvider(model);
|
|
89
|
+
const provider = detected !== "unknown" ? detected : context?.provider ?? "unknown";
|
|
90
|
+
const suggestions = suggestSmallerModels(provider);
|
|
91
|
+
const message = `Detected model: ${model}. Task appears simple: ${task}. ` +
|
|
92
|
+
(suggestions.length > 0
|
|
93
|
+
? `Consider a smaller model tier such as ${suggestions.join(" or ")} if quality is acceptable.`
|
|
94
|
+
: `Consider a smaller model tier if quality is acceptable.`);
|
|
95
|
+
findings.push(createFinding({
|
|
96
|
+
ruleId: this.id,
|
|
97
|
+
title: this.title,
|
|
98
|
+
severity: this.severity,
|
|
99
|
+
wasteCategory: this.wasteCategory,
|
|
100
|
+
file,
|
|
101
|
+
index: match.index,
|
|
102
|
+
message,
|
|
103
|
+
recommendation: this.recommendation,
|
|
104
|
+
fixRecipe: this.fixRecipe,
|
|
105
|
+
impact: makeImpact({
|
|
106
|
+
computeWaste: "medium",
|
|
107
|
+
carbonImpact: "medium",
|
|
108
|
+
waterImpact: "medium",
|
|
109
|
+
costImpact: "high",
|
|
110
|
+
confidence: "low",
|
|
111
|
+
score: 65,
|
|
112
|
+
explanation: "Simple tasks often do not need the most capable model tier, so downsizing can reduce cost and compute demand.",
|
|
113
|
+
}),
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
return dedupeFindings(findings);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=expensiveModelSimpleTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expensiveModelSimpleTask.js","sourceRoot":"","sources":["../../src/rules/expensiveModelSimpleTask.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,MAAM,oBAAoB,GAA2B;IACnD,gBAAgB;IAChB,MAAM;IACN,eAAe;IACf,aAAa;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,8CAA8C,CAAC;AAEzE,kFAAkF;AAClF,SAAS,WAAW,CAAC,OAAe,EAAE,KAAa;IACjD,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAe,EAAE,KAAa;IACxD,OAAO,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,kBAAkB,GAA4B;IAClD,CAAC,sCAAsC,EAAE,gBAAgB,CAAC;IAC1D,CAAC,UAAU,EAAE,YAAY,CAAC;IAC1B,CAAC,OAAO,EAAE,SAAS,CAAC;IACpB,CAAC,UAAU,EAAE,YAAY,CAAC;IAC1B,CAAC,8BAA8B,EAAE,mBAAmB,CAAC;CACtD,CAAC;AAEF,wEAAwE;AACxE,SAAS,YAAY,CAAC,MAAc;IAClC,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,kBAAkB,EAAE,CAAC;QAC7C,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IACpC,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,MAAM,iBAAiB,GAA2B;IAChD,UAAU;IACV,gBAAgB;IAChB,WAAW;IACX,KAAK;IACL,OAAO;IACP,SAAS;IACT,YAAY;IACZ,OAAO;IACP,SAAS;IACT,UAAU;IACV,YAAY;IACZ,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,UAAU;IACV,YAAY;CACb,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,iFAAiF;IACjF,kDAAkD;IAClD,8BAA8B;IAC9B,+DAA+D;CAChE,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAS;IAChD,EAAE,EAAE,6BAA6B;IACjC,KAAK,EAAE,oCAAoC;IAC3C,QAAQ,EAAE,QAAQ;IAClB,aAAa,EAAE,gBAAgB;IAC/B,WAAW,EACT,qGAAqG;IACvG,cAAc,EACZ,gGAAgG;IAClG,SAAS,EAAE,UAAU;IACrB,IAAI,CAAC,IAAgB,EAAE,OAAqB;QAC1C,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,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAC;YACpE,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,0DAA0D;YAC1D,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC5D,8DAA8D;YAC9D,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,CAAC;gBAAE,SAAS;YAEnE,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAElC,yEAAyE;YACzE,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,IAAI,SAAS,CAAC;YACpF,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YAEnD,MAAM,OAAO,GACX,mBAAmB,KAAK,0BAA0B,IAAI,IAAI;gBAC1D,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;oBACrB,CAAC,CAAC,yCAAyC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,4BAA4B;oBAC/F,CAAC,CAAC,yDAAyD,CAAC,CAAC;YAEjE,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;gBACP,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,UAAU,CAAC;oBACjB,YAAY,EAAE,QAAQ;oBACtB,YAAY,EAAE,QAAQ;oBACtB,WAAW,EAAE,QAAQ;oBACrB,UAAU,EAAE,MAAM;oBAClB,UAAU,EAAE,KAAK;oBACjB,KAAK,EAAE,EAAE;oBACT,WAAW,EACT,+GAA+G;iBAClH,CAAC;aACH,CAAC,CACH,CAAC;QACJ,CAAC;QAED,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frequentCron.d.ts","sourceRoot":"","sources":["../../src/rules/frequentCron.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAc,MAAM,aAAa,CAAC;AA2B7D,eAAO,MAAM,gBAAgB,EAAE,IAqD9B,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { makeImpact } from "../impact.js";
|
|
2
|
+
import { LLM_CALL_PATTERNS, createFinding, dedupeFindings, findCodeMatchesInFile, findMatches, } from "./helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Very frequent cron expressions and short setInterval delays.
|
|
5
|
+
* Matches whole-line-ish cron patterns and setInterval with <= 60s delays.
|
|
6
|
+
*/
|
|
7
|
+
const FREQUENT_PATTERNS = [
|
|
8
|
+
/\*\s+\*\s+\*\s+\*\s+\*/, // "* * * * *"
|
|
9
|
+
/\*\/[1-5]\s+\*\s+\*\s+\*\s+\*/, // "*/1..*/5 * * * *"
|
|
10
|
+
/setInterval\s*\([^,]*,\s*(1000|5000|60000)\s*\)/i,
|
|
11
|
+
];
|
|
12
|
+
const FIX_RECIPE = [
|
|
13
|
+
"Replace fixed polling with event-driven triggers when possible.",
|
|
14
|
+
"Add exponential backoff when no work is found.",
|
|
15
|
+
"Batch small jobs into less frequent runs.",
|
|
16
|
+
"Lower the schedule frequency if the job is not time-sensitive.",
|
|
17
|
+
];
|
|
18
|
+
export const frequentCronRule = {
|
|
19
|
+
id: "frequent-cron",
|
|
20
|
+
title: "Very frequent scheduled job",
|
|
21
|
+
severity: "medium",
|
|
22
|
+
wasteCategory: "background-compute-drift",
|
|
23
|
+
description: "Flags cron expressions and setInterval delays that run very frequently.",
|
|
24
|
+
recommendation: "Consider event-driven triggers, backoff, batching, or a lower frequency schedule.",
|
|
25
|
+
fixRecipe: FIX_RECIPE,
|
|
26
|
+
scan(file) {
|
|
27
|
+
const findings = [];
|
|
28
|
+
// Scheduling alone is not an AI-efficiency issue. Require an executable
|
|
29
|
+
// model/embedding/image/agent call in the same file before evaluating its
|
|
30
|
+
// frequency.
|
|
31
|
+
const aiWork = findCodeMatchesInFile(file, [
|
|
32
|
+
...LLM_CALL_PATTERNS,
|
|
33
|
+
/embeddings\.create\s*\(/i,
|
|
34
|
+
/images\.generate\s*\(/i,
|
|
35
|
+
/agent\.run\s*\(/i,
|
|
36
|
+
]);
|
|
37
|
+
if (aiWork.length === 0)
|
|
38
|
+
return findings;
|
|
39
|
+
for (const match of findMatches(file.content, FREQUENT_PATTERNS)) {
|
|
40
|
+
findings.push(createFinding({
|
|
41
|
+
ruleId: this.id,
|
|
42
|
+
title: this.title,
|
|
43
|
+
severity: this.severity,
|
|
44
|
+
wasteCategory: this.wasteCategory,
|
|
45
|
+
file,
|
|
46
|
+
index: match.index,
|
|
47
|
+
message: "This scheduled job may run very frequently, even when there is no useful work to do.",
|
|
48
|
+
recommendation: this.recommendation,
|
|
49
|
+
fixRecipe: this.fixRecipe,
|
|
50
|
+
impact: makeImpact({
|
|
51
|
+
computeWaste: "medium",
|
|
52
|
+
carbonImpact: "low",
|
|
53
|
+
waterImpact: "low",
|
|
54
|
+
costImpact: "medium",
|
|
55
|
+
confidence: "high",
|
|
56
|
+
score: 55,
|
|
57
|
+
explanation: "Frequent background jobs can create steady avoidable compute usage over time.",
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
return dedupeFindings(findings);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=frequentCron.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frequentCron.js","sourceRoot":"","sources":["../../src/rules/frequentCron.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB;;;GAGG;AACH,MAAM,iBAAiB,GAA2B;IAChD,wBAAwB,EAAE,cAAc;IACxC,+BAA+B,EAAE,qBAAqB;IACtD,kDAAkD;CACnD,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,iEAAiE;IACjE,gDAAgD;IAChD,2CAA2C;IAC3C,gEAAgE;CACjE,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,6BAA6B;IACpC,QAAQ,EAAE,QAAQ;IAClB,aAAa,EAAE,0BAA0B;IACzC,WAAW,EACT,yEAAyE;IAC3E,cAAc,EACZ,mFAAmF;IACrF,SAAS,EAAE,UAAU;IACrB,IAAI,CAAC,IAAgB;QACnB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,wEAAwE;QACxE,0EAA0E;QAC1E,aAAa;QACb,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,EAAE;YACzC,GAAG,iBAAiB;YACpB,0BAA0B;YAC1B,wBAAwB;YACxB,kBAAkB;SACnB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAEzC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,EAAE,CAAC;YACjE,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,sFAAsF;gBACxF,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,UAAU,CAAC;oBACjB,YAAY,EAAE,QAAQ;oBACtB,YAAY,EAAE,KAAK;oBACnB,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,QAAQ;oBACpB,UAAU,EAAE,MAAM;oBAClB,KAAK,EAAE,EAAE;oBACT,WAAW,EACT,+EAA+E;iBAClF,CAAC;aACH,CAAC,CACH,CAAC;QACJ,CAAC;QAED,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF,CAAC"}
|