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 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
@@ -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) {
@@ -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 .@-]+\/)+[\w .@+-]+\.[A-Za-z0-9]{1,12})/g;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",