getprismo 0.1.21 → 0.1.23
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/README.md +32 -0
- package/lib/prismo-dev/benchmark.js +122 -0
- package/lib/prismo-dev/report.js +37 -0
- package/lib/prismo-dev/scan-path-utils.js +203 -0
- package/lib/prismo-dev/scan.js +34 -191
- package/lib/prismo-dev/usage-log-utils.js +251 -0
- package/lib/prismo-dev/usage-watch.js +20 -226
- package/lib/prismo-dev/utils.js +85 -0
- package/lib/prismo-dev-scan.js +68 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,33 @@ Recommended Stack
|
|
|
180
180
|
|
|
181
181
|
This makes PrismoDev the measure-first layer: it tells you whether you need ignore cleanup, output sandboxing, code indexing, repo packing, instruction trimming, session splitting, or MCP/tool hygiene.
|
|
182
182
|
|
|
183
|
+
For the short version:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npx getprismo scan --report-card
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
That prints the simplest decision:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
PrismoDev Report Card
|
|
193
|
+
|
|
194
|
+
Biggest waste: Generated artifacts / ignore cleanup: High
|
|
195
|
+
Start with: npx getprismo doctor --apply-suggestions --dry-run
|
|
196
|
+
Then: npx getprismo shield -- <noisy command>
|
|
197
|
+
Code index needed: not yet
|
|
198
|
+
Round-trip risk: Low
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To benchmark a noisy command:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx getprismo benchmark -- npm test
|
|
205
|
+
npx getprismo benchmark session
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`benchmark -- <command>` measures raw command output tokens versus the compact shield summary. `benchmark session` summarizes recent local Claude/Codex sessions, including round-trip context signals like tool calls, repeated commands, repeated source reads, and MCP/tool surface.
|
|
209
|
+
|
|
183
210
|
---
|
|
184
211
|
|
|
185
212
|
## new: context shield
|
|
@@ -580,6 +607,8 @@ no install needed. npx runs it directly.
|
|
|
580
607
|
| `cc timeline` | session reconstruction with events |
|
|
581
608
|
| `scan --usage` | full repo scan with local usage data |
|
|
582
609
|
| `scan --optimizer-fit` | recommend which token-optimization path fits your repo/session |
|
|
610
|
+
| `scan --report-card` | shortest decision-layer summary |
|
|
611
|
+
| `benchmark` | measure command-output reduction or recent session round-trip context |
|
|
583
612
|
| `scan --simple` | plain-english summary |
|
|
584
613
|
| `scan --fix` | create safe fix files |
|
|
585
614
|
| `scan --ci` | fail CI when token-risk gates fail |
|
|
@@ -835,8 +864,11 @@ lib/prismo-dev/fixes.js safe ignore/template generation
|
|
|
835
864
|
lib/prismo-dev/mcp.js local MCP server and Prismo tool bindings
|
|
836
865
|
lib/prismo-dev/report.js terminal, markdown, ci reports
|
|
837
866
|
lib/prismo-dev/scan.js repo scanning, scoring, readiness
|
|
867
|
+
lib/prismo-dev/scan-path-utils.js scan ignore/path helper logic
|
|
838
868
|
lib/prismo-dev/shield.js local command shield and searchable output index
|
|
869
|
+
lib/prismo-dev/usage-log-utils.js local session log parsing helpers
|
|
839
870
|
lib/prismo-dev/usage-watch.js local logs, watch, cost, timeline
|
|
871
|
+
lib/prismo-dev/utils.js shared terminal/file/token helpers
|
|
840
872
|
```
|
|
841
873
|
|
|
842
874
|
---
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module.exports = function createBenchmark(deps) {
|
|
2
|
+
const {
|
|
3
|
+
NPX_COMMAND,
|
|
4
|
+
estimateTokens,
|
|
5
|
+
formatTokenCount,
|
|
6
|
+
getUsageSummary,
|
|
7
|
+
runShield,
|
|
8
|
+
scanRepo,
|
|
9
|
+
color,
|
|
10
|
+
} = deps;
|
|
11
|
+
|
|
12
|
+
function estimateSummaryTokens(shieldResult) {
|
|
13
|
+
const lines = shieldResult.output && Array.isArray(shieldResult.output.interestingLines)
|
|
14
|
+
? shieldResult.output.interestingLines.join("\n")
|
|
15
|
+
: "";
|
|
16
|
+
return estimateTokens([
|
|
17
|
+
`Command: ${shieldResult.command}`,
|
|
18
|
+
`Exit: ${shieldResult.exitCode}`,
|
|
19
|
+
`Duration: ${shieldResult.durationMs}ms`,
|
|
20
|
+
lines,
|
|
21
|
+
].join("\n"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runBenchmark(rootDir = process.cwd(), commandArgs = [], options = {}) {
|
|
25
|
+
if (options.sessionOnly || !commandArgs.length) {
|
|
26
|
+
const scan = scanRepo(rootDir, { includeUsage: true, usageLimit: options.limit || 5 });
|
|
27
|
+
const usage = getUsageSummary({ cwd: scan.root, limit: options.limit || 5, tool: "all" });
|
|
28
|
+
const fit = scan.optimizerFit;
|
|
29
|
+
return {
|
|
30
|
+
schemaVersion: 1,
|
|
31
|
+
mode: "session",
|
|
32
|
+
scannedPath: scan.root,
|
|
33
|
+
score: scan.score,
|
|
34
|
+
riskLevel: scan.risk,
|
|
35
|
+
sessions: usage.sessions.length,
|
|
36
|
+
tokens: usage.totals,
|
|
37
|
+
roundTripContext: fit.roundTripContext,
|
|
38
|
+
optimizerFit: fit.summary,
|
|
39
|
+
recommendedStack: fit.recommendedStack,
|
|
40
|
+
next: [`${NPX_COMMAND} scan --optimizer-fit`, `${NPX_COMMAND} watch --once`, `${NPX_COMMAND} cc timeline`],
|
|
41
|
+
generatedAt: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const shield = runShield(rootDir, commandArgs);
|
|
46
|
+
const rawTokens = shield.output.estimatedTokens || 0;
|
|
47
|
+
const summaryTokens = estimateSummaryTokens(shield);
|
|
48
|
+
const reductionPercent = rawTokens > 0 ? Math.max(0, Math.round(((rawTokens - summaryTokens) / rawTokens) * 100)) : 0;
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
mode: "command",
|
|
52
|
+
scannedPath: shield.cwd,
|
|
53
|
+
command: shield.command,
|
|
54
|
+
exitCode: shield.exitCode,
|
|
55
|
+
durationMs: shield.durationMs,
|
|
56
|
+
rawOutput: {
|
|
57
|
+
bytes: shield.output.totalBytes,
|
|
58
|
+
estimatedTokens: rawTokens,
|
|
59
|
+
},
|
|
60
|
+
shieldedSummary: {
|
|
61
|
+
estimatedTokens: summaryTokens,
|
|
62
|
+
interestingLines: shield.output.interestingLines,
|
|
63
|
+
},
|
|
64
|
+
estimatedTokenReductionPercent: reductionPercent,
|
|
65
|
+
stored: shield.stored,
|
|
66
|
+
next: [
|
|
67
|
+
"Give the shield summary to the agent first.",
|
|
68
|
+
`${NPX_COMMAND} shield search "<error text>"`,
|
|
69
|
+
`${NPX_COMMAND} scan --optimizer-fit`,
|
|
70
|
+
],
|
|
71
|
+
generatedAt: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderBenchmarkTerminal(result) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(color("Prismo Benchmark", "bold"));
|
|
79
|
+
lines.push("");
|
|
80
|
+
if (result.mode === "session") {
|
|
81
|
+
lines.push(`Mode: session`);
|
|
82
|
+
lines.push(`Score: ${result.score}/100 (${result.riskLevel} risk)`);
|
|
83
|
+
lines.push(`Sessions: ${result.sessions}`);
|
|
84
|
+
lines.push(`Tokens: ${formatTokenCount(result.tokens.displayTokens || 0)} (${result.tokens.exactTokens ? "exact-local-log" : "estimated/local"})`);
|
|
85
|
+
lines.push(`Optimizer fit: ${result.optimizerFit}`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push("Round-Trip Context:");
|
|
88
|
+
lines.push(`- Level: ${result.roundTripContext.level}`);
|
|
89
|
+
lines.push(`- Tool calls: ${result.roundTripContext.toolCalls}`);
|
|
90
|
+
lines.push(`- Repeated commands: ${result.roundTripContext.repeatedCommandMentions}`);
|
|
91
|
+
lines.push(`- Repeated source reads: ${result.roundTripContext.repeatedSourceReads}`);
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push("Next:");
|
|
94
|
+
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push(`Mode: command`);
|
|
99
|
+
lines.push(`Command: ${result.command}`);
|
|
100
|
+
lines.push(`Exit: ${result.exitCode}`);
|
|
101
|
+
lines.push(`Duration: ${result.durationMs}ms`);
|
|
102
|
+
lines.push(`Raw output: ${result.rawOutput.bytes.toLocaleString()} bytes (~${result.rawOutput.estimatedTokens.toLocaleString()} tokens)`);
|
|
103
|
+
lines.push(`Shield summary: ~${result.shieldedSummary.estimatedTokens.toLocaleString()} tokens`);
|
|
104
|
+
lines.push(`Estimated reduction: ${result.estimatedTokenReductionPercent}%`);
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push("Stored Output:");
|
|
107
|
+
lines.push(`- ${result.stored.stdout}`);
|
|
108
|
+
lines.push(`- ${result.stored.stderr}`);
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("Useful Lines:");
|
|
111
|
+
result.shieldedSummary.interestingLines.slice(0, 12).forEach((line) => lines.push(`- ${line}`));
|
|
112
|
+
lines.push("");
|
|
113
|
+
lines.push("Next:");
|
|
114
|
+
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
renderBenchmarkTerminal,
|
|
120
|
+
runBenchmark,
|
|
121
|
+
};
|
|
122
|
+
};
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -141,11 +141,47 @@ function renderOptimizerFitTerminal(result, options = {}) {
|
|
|
141
141
|
lines.push(` ${item.reason}`);
|
|
142
142
|
});
|
|
143
143
|
lines.push("");
|
|
144
|
+
lines.push(color("Round-Trip Context", "bold", useColor));
|
|
145
|
+
lines.push(`- Level: ${fit.roundTripContext.level}`);
|
|
146
|
+
lines.push(`- Tool calls: ${fit.roundTripContext.toolCalls}`);
|
|
147
|
+
lines.push(`- Repeated command mentions: ${fit.roundTripContext.repeatedCommandMentions}`);
|
|
148
|
+
lines.push(`- Repeated source reads: ${fit.roundTripContext.repeatedSourceReads}`);
|
|
149
|
+
lines.push(`- MCP/tool servers: ${fit.roundTripContext.mcpServers}`);
|
|
150
|
+
lines.push(`- ${fit.roundTripContext.recommendation}`);
|
|
151
|
+
lines.push("");
|
|
144
152
|
lines.push("Notes:");
|
|
145
153
|
fit.caveats.forEach((caveat) => lines.push(`- ${caveat}`));
|
|
146
154
|
return lines.join("\n");
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
function renderReportCardTerminal(result, options = {}) {
|
|
158
|
+
const useColor = options.color !== false;
|
|
159
|
+
const fit = result.optimizerFit;
|
|
160
|
+
const top = fit.recommendedStack[0];
|
|
161
|
+
const second = fit.recommendedStack[1];
|
|
162
|
+
const lines = [];
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push(color("PrismoDev Report Card", "bold", useColor));
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`Score: ${result.score}/100 (${result.risk} risk)`);
|
|
167
|
+
lines.push(`Biggest waste: ${fit.summary}`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
if (top) lines.push(`Start with: ${top.command}`);
|
|
170
|
+
if (second) lines.push(`Then: ${second.command}`);
|
|
171
|
+
lines.push("");
|
|
172
|
+
const codeIndex = fit.bottlenecks.find((item) => item.id === "code-indexing");
|
|
173
|
+
const output = fit.bottlenecks.find((item) => item.id === "output-sandboxing");
|
|
174
|
+
const ignore = fit.bottlenecks.find((item) => item.id === "ignore-cleanup");
|
|
175
|
+
lines.push(`Code index needed: ${codeIndex && codeIndex.level === "High" ? "yes" : codeIndex && codeIndex.level === "Medium" ? "maybe" : "not yet"}`);
|
|
176
|
+
lines.push(`Output sandbox needed: ${output && output.level !== "Low" ? "yes" : "not yet"}`);
|
|
177
|
+
lines.push(`Ignore cleanup needed: ${ignore && ignore.level !== "Low" ? "yes" : "not urgent"}`);
|
|
178
|
+
lines.push(`Round-trip risk: ${fit.roundTripContext.level}`);
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push("Why:");
|
|
181
|
+
fit.bottlenecks.slice(0, 3).forEach((item) => lines.push(`- ${item.label}: ${item.evidence[0]}`));
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
149
185
|
function evaluateCi(result, options = {}) {
|
|
150
186
|
const minScore = Number(options.minScore || 80);
|
|
151
187
|
const failures = [];
|
|
@@ -434,6 +470,7 @@ function writeReport(result) {
|
|
|
434
470
|
renderCiReport,
|
|
435
471
|
renderMarkdownReport,
|
|
436
472
|
renderOptimizerFitTerminal,
|
|
473
|
+
renderReportCardTerminal,
|
|
437
474
|
renderSimpleScanReport,
|
|
438
475
|
renderTerminalReport,
|
|
439
476
|
writeReport,
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
module.exports = function createScanPathUtils(deps) {
|
|
2
|
+
const { fs, path } = deps;
|
|
3
|
+
|
|
4
|
+
function normalizeRel(value) {
|
|
5
|
+
return value.split(path.sep).join("/");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function readIgnoreFile(root, fileName) {
|
|
9
|
+
const filePath = path.join(root, fileName);
|
|
10
|
+
if (!fs.existsSync(filePath)) return [];
|
|
11
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
12
|
+
return text
|
|
13
|
+
.split(/\r?\n/)
|
|
14
|
+
.map((line) => line.trim())
|
|
15
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
16
|
+
.map((line) => line.replace(/^!/, ""));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function patternMatches(pattern, relPath, isDir = false) {
|
|
20
|
+
const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\//, "");
|
|
21
|
+
const normalizedRel = normalizeRel(relPath);
|
|
22
|
+
const dirRel = isDir && !normalizedRel.endsWith("/") ? `${normalizedRel}/` : normalizedRel;
|
|
23
|
+
|
|
24
|
+
if (!normalizedPattern) return false;
|
|
25
|
+
if (normalizedPattern.endsWith("/")) {
|
|
26
|
+
const base = normalizedPattern.slice(0, -1);
|
|
27
|
+
return (
|
|
28
|
+
normalizedRel === base ||
|
|
29
|
+
normalizedRel.startsWith(`${base}/`) ||
|
|
30
|
+
normalizedRel.endsWith(`/${base}`) ||
|
|
31
|
+
normalizedRel.includes(`/${base}/`) ||
|
|
32
|
+
dirRel.includes(`/${base}/`)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (normalizedPattern.startsWith("*.")) {
|
|
36
|
+
return normalizedRel.endsWith(normalizedPattern.slice(1));
|
|
37
|
+
}
|
|
38
|
+
if (normalizedPattern.includes("*")) {
|
|
39
|
+
const escaped = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
40
|
+
return new RegExp(`(^|/)${escaped}$`).test(normalizedRel);
|
|
41
|
+
}
|
|
42
|
+
return (
|
|
43
|
+
normalizedRel === normalizedPattern ||
|
|
44
|
+
dirRel === normalizedPattern ||
|
|
45
|
+
normalizedRel.startsWith(`${normalizedPattern}/`) ||
|
|
46
|
+
normalizedRel.endsWith(`/${normalizedPattern}`)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isIgnored(relPath, patterns, isDir = false) {
|
|
51
|
+
return patterns.some((pattern) => patternMatches(pattern, relPath, isDir));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ignoreSuggestionCovered(pattern, existingPatterns) {
|
|
55
|
+
if (!pattern) return true;
|
|
56
|
+
if (existingPatterns.includes(pattern)) return true;
|
|
57
|
+
const sample = pattern
|
|
58
|
+
.replace(/^\*\//, "")
|
|
59
|
+
.replace(/^\*\*/, "sample")
|
|
60
|
+
.replace(/\*/g, "sample")
|
|
61
|
+
.replace(/\/$/, "");
|
|
62
|
+
const isDir = pattern.endsWith("/") || pattern.endsWith("/**");
|
|
63
|
+
return existingPatterns.some((existing) => {
|
|
64
|
+
if (existing === pattern) return true;
|
|
65
|
+
if (existing.endsWith("/") && pattern.startsWith(existing)) return true;
|
|
66
|
+
return patternMatches(existing, sample, isDir);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function missingIgnoreSuggestions(recommended, existingPatterns) {
|
|
71
|
+
return recommended.filter((pattern) => !ignoreSuggestionCovered(pattern, existingPatterns));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const SESSION_NOISE_DIRS = new Set([
|
|
75
|
+
".next",
|
|
76
|
+
".nuxt",
|
|
77
|
+
".prismo",
|
|
78
|
+
".pytest_cache",
|
|
79
|
+
".turbo",
|
|
80
|
+
"__pycache__",
|
|
81
|
+
"build",
|
|
82
|
+
"calendar-dumps",
|
|
83
|
+
"coverage",
|
|
84
|
+
"dist",
|
|
85
|
+
"event-dumps",
|
|
86
|
+
"events",
|
|
87
|
+
"exports",
|
|
88
|
+
"htmlcov",
|
|
89
|
+
"inbox-dumps",
|
|
90
|
+
"logs",
|
|
91
|
+
"models",
|
|
92
|
+
"node_modules",
|
|
93
|
+
"out",
|
|
94
|
+
"playwright-report",
|
|
95
|
+
"session-dumps",
|
|
96
|
+
"source-streams",
|
|
97
|
+
"state-backups",
|
|
98
|
+
"test-results",
|
|
99
|
+
"tmp",
|
|
100
|
+
"temp",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const SESSION_NOISE_FILE_NAMES = new Set([
|
|
104
|
+
"package-lock.json",
|
|
105
|
+
"pnpm-lock.yaml",
|
|
106
|
+
"yarn.lock",
|
|
107
|
+
"bun.lockb",
|
|
108
|
+
"coverage-final.json",
|
|
109
|
+
"lcov.info",
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const SESSION_NOISE_EXTENSIONS = new Set([
|
|
113
|
+
".db",
|
|
114
|
+
".jsonl",
|
|
115
|
+
".lock",
|
|
116
|
+
".log",
|
|
117
|
+
".sqlite",
|
|
118
|
+
".sqlite3",
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
function cleanSessionPath(value) {
|
|
122
|
+
const text = String(value || "").trim().replace(/\\/g, "/");
|
|
123
|
+
if (!text || /^https?:\/\//.test(text)) return null;
|
|
124
|
+
const withoutQuotes = text.replace(/^["'`]+|["'`.,:;)\]]+$/g, "");
|
|
125
|
+
if (!withoutQuotes || withoutQuotes.includes("\n")) return null;
|
|
126
|
+
const markerIndex = withoutQuotes.indexOf("/Users/");
|
|
127
|
+
if (markerIndex > 0) return withoutQuotes.slice(markerIndex);
|
|
128
|
+
return withoutQuotes;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sessionIgnorePatternForPath(value, root) {
|
|
132
|
+
const cleaned = cleanSessionPath(value);
|
|
133
|
+
if (!cleaned) return null;
|
|
134
|
+
const rootNormalized = normalizeRel(root);
|
|
135
|
+
let rel = cleaned;
|
|
136
|
+
if (path.isAbsolute(cleaned)) {
|
|
137
|
+
const normalized = normalizeRel(cleaned);
|
|
138
|
+
if (!normalized.startsWith(`${rootNormalized}/`)) return null;
|
|
139
|
+
rel = normalizeRel(path.relative(root, cleaned));
|
|
140
|
+
}
|
|
141
|
+
rel = normalizeRel(rel).replace(/^\.\//, "");
|
|
142
|
+
if (!rel || rel === "." || rel.startsWith("../") || rel.includes("..")) return null;
|
|
143
|
+
|
|
144
|
+
const segments = rel.split("/").filter(Boolean);
|
|
145
|
+
if (!segments.length) return null;
|
|
146
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
147
|
+
const segment = segments[index];
|
|
148
|
+
if (SESSION_NOISE_DIRS.has(segment)) {
|
|
149
|
+
return `${segments.slice(0, index + 1).join("/")}/`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const fileName = segments[segments.length - 1];
|
|
154
|
+
const lowerName = fileName.toLowerCase();
|
|
155
|
+
const ext = path.extname(lowerName);
|
|
156
|
+
if (SESSION_NOISE_FILE_NAMES.has(lowerName)) return fileName;
|
|
157
|
+
if (SESSION_NOISE_EXTENSIONS.has(ext)) return rel;
|
|
158
|
+
if (/_state\.json$/i.test(fileName)) return "*_state.json";
|
|
159
|
+
if (/_tokens\.json$/i.test(fileName)) return "*_tokens.json";
|
|
160
|
+
if (/_export\.json$/i.test(fileName)) return "*_export.json";
|
|
161
|
+
if (/secret|credential|token/i.test(fileName) && /\.json$/i.test(fileName)) return rel;
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildSessionIgnoreSuggestions(realUsage, root) {
|
|
166
|
+
if (!realUsage || !Array.isArray(realUsage.sessions)) return [];
|
|
167
|
+
const byPattern = new Map();
|
|
168
|
+
const add = (pattern, item, source, reason) => {
|
|
169
|
+
if (!pattern) return;
|
|
170
|
+
const existing = byPattern.get(pattern) || {
|
|
171
|
+
pattern,
|
|
172
|
+
source,
|
|
173
|
+
reason,
|
|
174
|
+
count: 0,
|
|
175
|
+
examples: [],
|
|
176
|
+
};
|
|
177
|
+
existing.count += Number(item?.count || 1);
|
|
178
|
+
const example = item?.value || item?.path || pattern;
|
|
179
|
+
if (example && !existing.examples.includes(example) && existing.examples.length < 3) existing.examples.push(example);
|
|
180
|
+
byPattern.set(pattern, existing);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
for (const session of realUsage.sessions) {
|
|
184
|
+
for (const item of session.generatedArtifacts || []) {
|
|
185
|
+
add(sessionIgnorePatternForPath(item.value, root), item, session.tool || "session", "Generated artifact entered local session context.");
|
|
186
|
+
}
|
|
187
|
+
for (const item of session.repeatedPathMentions || []) {
|
|
188
|
+
add(sessionIgnorePatternForPath(item.value, root), item, session.tool || "session", "Noisy path appeared repeatedly in local session context.");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return Array.from(byPattern.values())
|
|
192
|
+
.sort((a, b) => b.count - a.count || a.pattern.localeCompare(b.pattern))
|
|
193
|
+
.slice(0, 25);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
buildSessionIgnoreSuggestions,
|
|
198
|
+
isIgnored,
|
|
199
|
+
missingIgnoreSuggestions,
|
|
200
|
+
normalizeRel,
|
|
201
|
+
readIgnoreFile,
|
|
202
|
+
};
|
|
203
|
+
};
|