getprismo 0.1.22 → 0.1.24

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
@@ -864,8 +864,15 @@ lib/prismo-dev/fixes.js safe ignore/template generation
864
864
  lib/prismo-dev/mcp.js local MCP server and Prismo tool bindings
865
865
  lib/prismo-dev/report.js terminal, markdown, ci reports
866
866
  lib/prismo-dev/scan.js repo scanning, scoring, readiness
867
+ lib/prismo-dev/scan-path-utils.js scan ignore/path helper logic
867
868
  lib/prismo-dev/shield.js local command shield and searchable output index
868
- lib/prismo-dev/usage-watch.js local logs, watch, cost, timeline
869
+ lib/prismo-dev/usage-cost.js Claude Code cost and timeline analysis
870
+ lib/prismo-dev/usage-log-utils.js local session log parsing helpers
871
+ lib/prismo-dev/usage-sessions.js local Codex/Claude session discovery
872
+ lib/prismo-dev/usage-watch.js watch orchestration, JSON payloads, live files
873
+ lib/prismo-dev/utils.js shared terminal/file/token helpers
874
+ lib/prismo-dev/watch-live.js live context-pressure decisions
875
+ lib/prismo-dev/watch-render.js watch terminal and guardrail renderers
869
876
  ```
870
877
 
871
878
  ---
@@ -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
+ };
@@ -27,197 +27,13 @@ const ASSUMED_TURNS_PER_AI_SESSION = 40;
27
27
  const ASSUMED_INPUT_COST_PER_1K_TOKENS = 0.003;
28
28
  const ASSUMED_SESSIONS_PER_MONTH = 30;
29
29
 
30
- function normalizeRel(value) {
31
- return value.split(path.sep).join("/");
32
- }
33
-
34
- function readIgnoreFile(root, fileName) {
35
- const filePath = path.join(root, fileName);
36
- if (!fs.existsSync(filePath)) return [];
37
- const text = fs.readFileSync(filePath, "utf8");
38
- return text
39
- .split(/\r?\n/)
40
- .map((line) => line.trim())
41
- .filter((line) => line && !line.startsWith("#"))
42
- .map((line) => line.replace(/^!/, ""));
43
- }
44
-
45
- function patternMatches(pattern, relPath, isDir = false) {
46
- const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\//, "");
47
- const normalizedRel = normalizeRel(relPath);
48
- const dirRel = isDir && !normalizedRel.endsWith("/") ? `${normalizedRel}/` : normalizedRel;
49
-
50
- if (!normalizedPattern) return false;
51
- if (normalizedPattern.endsWith("/")) {
52
- const base = normalizedPattern.slice(0, -1);
53
- return (
54
- normalizedRel === base ||
55
- normalizedRel.startsWith(`${base}/`) ||
56
- normalizedRel.endsWith(`/${base}`) ||
57
- normalizedRel.includes(`/${base}/`) ||
58
- dirRel.includes(`/${base}/`)
59
- );
60
- }
61
- if (normalizedPattern.startsWith("*.")) {
62
- return normalizedRel.endsWith(normalizedPattern.slice(1));
63
- }
64
- if (normalizedPattern.includes("*")) {
65
- const escaped = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
66
- return new RegExp(`(^|/)${escaped}$`).test(normalizedRel);
67
- }
68
- return (
69
- normalizedRel === normalizedPattern ||
70
- dirRel === normalizedPattern ||
71
- normalizedRel.startsWith(`${normalizedPattern}/`) ||
72
- normalizedRel.endsWith(`/${normalizedPattern}`)
73
- );
74
- }
75
-
76
- function isIgnored(relPath, patterns, isDir = false) {
77
- return patterns.some((pattern) => patternMatches(pattern, relPath, isDir));
78
- }
79
-
80
- function ignoreSuggestionCovered(pattern, existingPatterns) {
81
- if (!pattern) return true;
82
- if (existingPatterns.includes(pattern)) return true;
83
- const sample = pattern
84
- .replace(/^\*\//, "")
85
- .replace(/^\*\*/, "sample")
86
- .replace(/\*/g, "sample")
87
- .replace(/\/$/, "");
88
- const isDir = pattern.endsWith("/") || pattern.endsWith("/**");
89
- return existingPatterns.some((existing) => {
90
- if (existing === pattern) return true;
91
- if (existing.endsWith("/") && pattern.startsWith(existing)) return true;
92
- return patternMatches(existing, sample, isDir);
93
- });
94
- }
95
-
96
- function missingIgnoreSuggestions(recommended, existingPatterns) {
97
- return recommended.filter((pattern) => !ignoreSuggestionCovered(pattern, existingPatterns));
98
- }
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
- }
30
+ const {
31
+ buildSessionIgnoreSuggestions,
32
+ isIgnored,
33
+ missingIgnoreSuggestions,
34
+ normalizeRel,
35
+ readIgnoreFile,
36
+ } = require("./scan-path-utils")({ fs, path });
221
37
 
222
38
  function getFileKind(filePath) {
223
39
  const ext = path.extname(filePath).toLowerCase();