getprismo 0.1.17 → 0.1.18
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 +3 -0
- package/docs/live-demo.md +1 -0
- package/lib/prismo-dev/report.js +10 -0
- package/lib/prismo-dev/scan.js +138 -0
- package/lib/prismo-dev/usage-watch.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ agent-native npx getprismo mcp
|
|
|
52
52
|
- repeated file reads (same file loaded 100+ times in one session)
|
|
53
53
|
- repeated commands (agent running the same command in a loop)
|
|
54
54
|
- high context risk sessions that should have been split at task boundaries
|
|
55
|
+
- session-derived ignore candidates from actual Claude/Codex logs (`logs/debug.log`, `dist/app.js`, `package-lock.json`, source-stream dumps)
|
|
55
56
|
|
|
56
57
|
---
|
|
57
58
|
|
|
@@ -96,6 +97,8 @@ Next:
|
|
|
96
97
|
|
|
97
98
|
doctor went from 79 to 91 in one run. the repo now has proper ignore files, compact context packs, and a clear starting point for the next coding session.
|
|
98
99
|
|
|
100
|
+
`scan --usage` and `doctor` can also turn real session leaks into concrete ignore suggestions. if local Claude/Codex logs show `logs/debug.log`, `dist/app.js`, `package-lock.json`, source-stream dumps, or other noisy files repeatedly entering context, prismodev adds conservative `.claudeignore` / `.cursorignore` candidate rules instead of only reporting the problem.
|
|
101
|
+
|
|
99
102
|
---
|
|
100
103
|
|
|
101
104
|
## real output: watch
|
package/docs/live-demo.md
CHANGED
|
@@ -12,6 +12,7 @@ Shows:
|
|
|
12
12
|
|
|
13
13
|
- before/after repo score
|
|
14
14
|
- missing `.claudeignore` / `.cursorignore`
|
|
15
|
+
- ignore suggestions derived from actual local Claude/Codex session leaks
|
|
15
16
|
- generated artifacts exposed to AI context
|
|
16
17
|
- compact `.prismo/` context packs
|
|
17
18
|
- recommended next starting context
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -277,6 +277,16 @@ function renderMarkdownReport(result) {
|
|
|
277
277
|
});
|
|
278
278
|
lines.push("");
|
|
279
279
|
}
|
|
280
|
+
if (result.sessionIgnoreSuggestions && result.sessionIgnoreSuggestions.length) {
|
|
281
|
+
lines.push("## Session-Derived Ignore Suggestions");
|
|
282
|
+
lines.push("");
|
|
283
|
+
lines.push("These rules came from local Claude/Codex session logs where noisy paths already entered context.");
|
|
284
|
+
lines.push("");
|
|
285
|
+
result.sessionIgnoreSuggestions.slice(0, 20).forEach((item) => {
|
|
286
|
+
lines.push(`- \`${item.pattern}\` - ${item.reason} (${item.count} mention${item.count === 1 ? "" : "s"}${item.examples.length ? `; e.g. ${item.examples[0]}` : ""})`);
|
|
287
|
+
});
|
|
288
|
+
lines.push("");
|
|
289
|
+
}
|
|
280
290
|
lines.push("## Issues");
|
|
281
291
|
lines.push("");
|
|
282
292
|
if (!result.issues.length) {
|
package/lib/prismo-dev/scan.js
CHANGED
|
@@ -97,6 +97,128 @@ function missingIgnoreSuggestions(recommended, existingPatterns) {
|
|
|
97
97
|
return recommended.filter((pattern) => !ignoreSuggestionCovered(pattern, existingPatterns));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
const SESSION_NOISE_DIRS = new Set([
|
|
101
|
+
".next",
|
|
102
|
+
".nuxt",
|
|
103
|
+
".prismo",
|
|
104
|
+
".pytest_cache",
|
|
105
|
+
".turbo",
|
|
106
|
+
"__pycache__",
|
|
107
|
+
"build",
|
|
108
|
+
"calendar-dumps",
|
|
109
|
+
"coverage",
|
|
110
|
+
"dist",
|
|
111
|
+
"event-dumps",
|
|
112
|
+
"events",
|
|
113
|
+
"exports",
|
|
114
|
+
"htmlcov",
|
|
115
|
+
"inbox-dumps",
|
|
116
|
+
"logs",
|
|
117
|
+
"models",
|
|
118
|
+
"node_modules",
|
|
119
|
+
"out",
|
|
120
|
+
"playwright-report",
|
|
121
|
+
"session-dumps",
|
|
122
|
+
"source-streams",
|
|
123
|
+
"state-backups",
|
|
124
|
+
"test-results",
|
|
125
|
+
"tmp",
|
|
126
|
+
"temp",
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const SESSION_NOISE_FILE_NAMES = new Set([
|
|
130
|
+
"package-lock.json",
|
|
131
|
+
"pnpm-lock.yaml",
|
|
132
|
+
"yarn.lock",
|
|
133
|
+
"bun.lockb",
|
|
134
|
+
"coverage-final.json",
|
|
135
|
+
"lcov.info",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const SESSION_NOISE_EXTENSIONS = new Set([
|
|
139
|
+
".db",
|
|
140
|
+
".jsonl",
|
|
141
|
+
".lock",
|
|
142
|
+
".log",
|
|
143
|
+
".sqlite",
|
|
144
|
+
".sqlite3",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
function cleanSessionPath(value) {
|
|
148
|
+
const text = String(value || "").trim().replace(/\\/g, "/");
|
|
149
|
+
if (!text || /^https?:\/\//.test(text)) return null;
|
|
150
|
+
const withoutQuotes = text.replace(/^["'`]+|["'`.,:;)\]]+$/g, "");
|
|
151
|
+
if (!withoutQuotes || withoutQuotes.includes("\n")) return null;
|
|
152
|
+
const markerIndex = withoutQuotes.indexOf("/Users/");
|
|
153
|
+
if (markerIndex > 0) return withoutQuotes.slice(markerIndex);
|
|
154
|
+
return withoutQuotes;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function sessionIgnorePatternForPath(value, root) {
|
|
158
|
+
const cleaned = cleanSessionPath(value);
|
|
159
|
+
if (!cleaned) return null;
|
|
160
|
+
const rootNormalized = normalizeRel(root);
|
|
161
|
+
let rel = cleaned;
|
|
162
|
+
if (path.isAbsolute(cleaned)) {
|
|
163
|
+
const normalized = normalizeRel(cleaned);
|
|
164
|
+
if (!normalized.startsWith(`${rootNormalized}/`)) return null;
|
|
165
|
+
rel = normalizeRel(path.relative(root, cleaned));
|
|
166
|
+
}
|
|
167
|
+
rel = normalizeRel(rel).replace(/^\.\//, "");
|
|
168
|
+
if (!rel || rel === "." || rel.startsWith("../") || rel.includes("..")) return null;
|
|
169
|
+
|
|
170
|
+
const segments = rel.split("/").filter(Boolean);
|
|
171
|
+
if (!segments.length) return null;
|
|
172
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
173
|
+
const segment = segments[index];
|
|
174
|
+
if (SESSION_NOISE_DIRS.has(segment)) {
|
|
175
|
+
return `${segments.slice(0, index + 1).join("/")}/`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const fileName = segments[segments.length - 1];
|
|
180
|
+
const lowerName = fileName.toLowerCase();
|
|
181
|
+
const ext = path.extname(lowerName);
|
|
182
|
+
if (SESSION_NOISE_FILE_NAMES.has(lowerName)) return fileName;
|
|
183
|
+
if (SESSION_NOISE_EXTENSIONS.has(ext)) return rel;
|
|
184
|
+
if (/_state\.json$/i.test(fileName)) return "*_state.json";
|
|
185
|
+
if (/_tokens\.json$/i.test(fileName)) return "*_tokens.json";
|
|
186
|
+
if (/_export\.json$/i.test(fileName)) return "*_export.json";
|
|
187
|
+
if (/secret|credential|token/i.test(fileName) && /\.json$/i.test(fileName)) return rel;
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function buildSessionIgnoreSuggestions(realUsage, root) {
|
|
192
|
+
if (!realUsage || !Array.isArray(realUsage.sessions)) return [];
|
|
193
|
+
const byPattern = new Map();
|
|
194
|
+
const add = (pattern, item, source, reason) => {
|
|
195
|
+
if (!pattern) return;
|
|
196
|
+
const existing = byPattern.get(pattern) || {
|
|
197
|
+
pattern,
|
|
198
|
+
source,
|
|
199
|
+
reason,
|
|
200
|
+
count: 0,
|
|
201
|
+
examples: [],
|
|
202
|
+
};
|
|
203
|
+
existing.count += Number(item?.count || 1);
|
|
204
|
+
const example = item?.value || item?.path || pattern;
|
|
205
|
+
if (example && !existing.examples.includes(example) && existing.examples.length < 3) existing.examples.push(example);
|
|
206
|
+
byPattern.set(pattern, existing);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
for (const session of realUsage.sessions) {
|
|
210
|
+
for (const item of session.generatedArtifacts || []) {
|
|
211
|
+
add(sessionIgnorePatternForPath(item.value, root), item, session.tool || "session", "Generated artifact entered local session context.");
|
|
212
|
+
}
|
|
213
|
+
for (const item of session.repeatedPathMentions || []) {
|
|
214
|
+
add(sessionIgnorePatternForPath(item.value, root), item, session.tool || "session", "Noisy path appeared repeatedly in local session context.");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return Array.from(byPattern.values())
|
|
218
|
+
.sort((a, b) => b.count - a.count || a.pattern.localeCompare(b.pattern))
|
|
219
|
+
.slice(0, 25);
|
|
220
|
+
}
|
|
221
|
+
|
|
100
222
|
function getFileKind(filePath) {
|
|
101
223
|
const ext = path.extname(filePath).toLowerCase();
|
|
102
224
|
const name = path.basename(filePath).toLowerCase();
|
|
@@ -775,6 +897,7 @@ function toJsonPayload(result) {
|
|
|
775
897
|
optimizationStack: result.optimizationStack,
|
|
776
898
|
toolOutputRisk: result.toolOutputRisk,
|
|
777
899
|
operationalNoise: result.operationalNoise,
|
|
900
|
+
sessionIgnoreSuggestions: result.sessionIgnoreSuggestions || [],
|
|
778
901
|
proxyTrackingReadiness: result.proxyTrackingReadiness,
|
|
779
902
|
suggestedClaudeIgnore: result.recommendedClaudeIgnore,
|
|
780
903
|
suggestedCursorIgnore: result.recommendedCursorIgnore,
|
|
@@ -1061,7 +1184,19 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1061
1184
|
}
|
|
1062
1185
|
|
|
1063
1186
|
const realUsage = options.includeUsage ? getUsageSummary({ tool: options.usageTool || "all", cwd: root, limit: options.usageLimit || 5 }) : null;
|
|
1187
|
+
const sessionIgnoreSuggestions = buildSessionIgnoreSuggestions(realUsage, root);
|
|
1064
1188
|
addRealUsageIssues(issues, realUsage);
|
|
1189
|
+
if (sessionIgnoreSuggestions.length) {
|
|
1190
|
+
addIssue(
|
|
1191
|
+
issues,
|
|
1192
|
+
"medium",
|
|
1193
|
+
"ignore_file",
|
|
1194
|
+
`${sessionIgnoreSuggestions.length} session-derived ignore suggestion${sessionIgnoreSuggestions.length === 1 ? "" : "s"}`,
|
|
1195
|
+
sessionIgnoreSuggestions.slice(0, 5).map((item) => `${item.pattern} (${item.count}x)`).join(", "),
|
|
1196
|
+
"Review the generated .claudeignore/.cursorignore suggestions from actual local session context.",
|
|
1197
|
+
"Likely avoidable token exposure: these paths already appeared in local coding-agent context."
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1065
1200
|
const optimizationStack = detectOptimizationStack(root, claudeConfig, codexConfig);
|
|
1066
1201
|
const agentReadiness = detectAgentReadiness(root, claudeConfig, codexConfig, realUsage);
|
|
1067
1202
|
const toolOutputRisk = detectToolOutputRisk({ exposedLargeFiles, exposedHighRiskDirs, highRiskDirs });
|
|
@@ -1145,11 +1280,13 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1145
1280
|
.filter((file) => file.size >= 1024 * 1024 || ["log", "json", "minified", "lock/generated"].includes(file.kind))
|
|
1146
1281
|
.map((file) => file.path);
|
|
1147
1282
|
const operationalNoiseSuggestions = operationalNoise.files.map((file) => file.path);
|
|
1283
|
+
const sessionIgnorePatterns = sessionIgnoreSuggestions.map((item) => item.pattern);
|
|
1148
1284
|
const recommendedClaudeIgnore = Array.from(new Set([
|
|
1149
1285
|
...DEFAULT_CLAUDEIGNORE,
|
|
1150
1286
|
...gitignorePatterns.filter((line) => !line.startsWith("!")),
|
|
1151
1287
|
...largeFileSuggestions,
|
|
1152
1288
|
...operationalNoiseSuggestions,
|
|
1289
|
+
...sessionIgnorePatterns,
|
|
1153
1290
|
]));
|
|
1154
1291
|
const recommendedCursorIgnore = Array.from(new Set([
|
|
1155
1292
|
...recommendedClaudeIgnore,
|
|
@@ -1203,6 +1340,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
1203
1340
|
recommendedCursorIgnore,
|
|
1204
1341
|
missingClaudeIgnoreSuggestions,
|
|
1205
1342
|
missingCursorIgnoreSuggestions,
|
|
1343
|
+
sessionIgnoreSuggestions,
|
|
1206
1344
|
topTokenLeaks: getTopTokenLeaks(issues),
|
|
1207
1345
|
generatedAt: new Date().toISOString(),
|
|
1208
1346
|
};
|
|
@@ -186,7 +186,7 @@ function looksLikeUsefulPath(relPath) {
|
|
|
186
186
|
function extractMentionedPaths(text, cwd = "") {
|
|
187
187
|
const found = new Set();
|
|
188
188
|
const source = String(text || "");
|
|
189
|
-
const pathPattern = /(?:^|[\s"'`])((?:\.{0,2}\/)?(?:[\w
|
|
189
|
+
const pathPattern = /(?:^|[\s"'`])((?:\.{0,2}\/)?(?:[\w.@-]+\/)+[\w.@+-]+\.[A-Za-z0-9]{1,12})/g;
|
|
190
190
|
const filePattern = /(?:^|[\s"'`])((?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|coverage-final\.json|tsconfig\.json|pyproject\.toml|requirements\.txt|README\.md|CLAUDE\.md|AGENTS\.md))/g;
|
|
191
191
|
for (const pattern of [pathPattern, filePattern]) {
|
|
192
192
|
let match;
|
package/package.json
CHANGED