getprismo 0.1.26 → 0.1.27
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 +9 -0
- package/lib/prismo-dev/firewall.js +119 -0
- package/lib/prismo-dev-scan.js +36 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -492,6 +492,14 @@ Run npx getprismo optimize, then start from .prismo/architecture-summary.md.
|
|
|
492
492
|
|
|
493
493
|
timeline shows exactly what leaked, what repeated, and what to do differently next time.
|
|
494
494
|
|
|
495
|
+
to turn a postmortem into a safer next-session policy, run:
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
npx getprismo cc timeline --firewall --task auth-bug
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
this writes `.prismo/timeline-firewall-suggestions.md`, `.prismo/context-firewall.suggested.md`, `.prismo/allowed-context.suggested.txt`, and `.prismo/blocked-context.suggested.txt` from the latest session evidence. it does not overwrite your active firewall; it gives you a per-task allow/block recommendation for the next session.
|
|
502
|
+
|
|
495
503
|
---
|
|
496
504
|
|
|
497
505
|
## how doctor improves a repo
|
|
@@ -736,6 +744,7 @@ For local development from this repo:
|
|
|
736
744
|
```bash
|
|
737
745
|
npx getprismo cc # latest session cost
|
|
738
746
|
npx getprismo cc timeline # event timeline for latest session
|
|
747
|
+
npx getprismo cc timeline --firewall --task auth-bug # suggest next-session firewall rules
|
|
739
748
|
npx getprismo cc list # list recent sessions
|
|
740
749
|
npx getprismo cc last 5 # last 5 sessions
|
|
741
750
|
npx getprismo cc all # everything
|
|
@@ -99,6 +99,58 @@ function buildBlockedContext(ctx) {
|
|
|
99
99
|
return uniq(blocked).slice(0, 80);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function isGeneratedLike(value) {
|
|
103
|
+
const text = String(value || "").replace(/\\/g, "/").toLowerCase();
|
|
104
|
+
return /(^|\/)(node_modules|dist|build|coverage|\.next|__pycache__|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)(\/|$)/.test(text)
|
|
105
|
+
|| /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb)$/.test(text)
|
|
106
|
+
|| /\.(log|jsonl|ndjson|map|pyc)$/.test(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function firewallPatternForPath(value) {
|
|
110
|
+
const text = String(value || "").replace(/\\/g, "/").replace(/\s+\(\d+x\)$/, "");
|
|
111
|
+
if (!text) return null;
|
|
112
|
+
if (/(^|\/)__pycache__(\/|$)/.test(text)) return "**/__pycache__/**";
|
|
113
|
+
if (/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//.test(text)) {
|
|
114
|
+
const match = text.match(/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//);
|
|
115
|
+
const prefix = text.slice(0, match.index + match[0].length).replace(/\/$/, "");
|
|
116
|
+
return `${prefix}/**`;
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function timelineEventPath(event) {
|
|
122
|
+
const detail = String(event?.detail || "");
|
|
123
|
+
const match = detail.match(/^(.+?)\s+\(\d+x\)$/);
|
|
124
|
+
return (match ? match[1] : detail).trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildTimelineFirewallSuggestions(ctx, session, taskScope) {
|
|
128
|
+
const baseAllowed = buildAllowedContext(ctx, taskScope);
|
|
129
|
+
const baseBlocked = buildBlockedContext(ctx);
|
|
130
|
+
const timeline = session?.timeline || [];
|
|
131
|
+
const repeated = session?.repeatedPathMentions || [];
|
|
132
|
+
const artifacts = session?.generatedArtifacts || [];
|
|
133
|
+
|
|
134
|
+
const sessionAllowed = repeated
|
|
135
|
+
.map((item) => item.value)
|
|
136
|
+
.filter((value) => value && !isGeneratedLike(value))
|
|
137
|
+
.map(globForFile)
|
|
138
|
+
.slice(0, 20);
|
|
139
|
+
|
|
140
|
+
const sessionBlocked = [
|
|
141
|
+
...artifacts.map((item) => item.value),
|
|
142
|
+
...timeline.filter((event) => event.type === "artifact-leak").map(timelineEventPath),
|
|
143
|
+
...repeated.map((item) => item.value).filter(isGeneratedLike),
|
|
144
|
+
].map(firewallPatternForPath).filter(Boolean);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
allowed: uniq([...sessionAllowed, ...baseAllowed]).slice(0, 80),
|
|
148
|
+
blocked: uniq([...sessionBlocked, ...baseBlocked]).slice(0, 100),
|
|
149
|
+
sessionAllowed: uniq(sessionAllowed),
|
|
150
|
+
sessionBlocked: uniq(sessionBlocked),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
102
154
|
function renderLines(title, items) {
|
|
103
155
|
return [`# ${title}`, "", ...items.map((item) => item)].join("\n") + "\n";
|
|
104
156
|
}
|
|
@@ -187,6 +239,71 @@ function runFirewall(rootDir = process.cwd(), options = {}) {
|
|
|
187
239
|
return result;
|
|
188
240
|
}
|
|
189
241
|
|
|
242
|
+
function renderTimelineFirewallSuggestions(result) {
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push("# Prismo Timeline Firewall Suggestions");
|
|
245
|
+
lines.push("");
|
|
246
|
+
lines.push(`Generated: ${result.generatedAt}`);
|
|
247
|
+
lines.push(`Task: ${result.task}`);
|
|
248
|
+
lines.push(`Scope: ${result.scope}`);
|
|
249
|
+
if (result.sessionId) lines.push(`Session: ${result.sessionId}`);
|
|
250
|
+
lines.push("");
|
|
251
|
+
lines.push("These suggestions came from `cc timeline` session evidence. They are safe recommendation files; Prismo does not overwrite your active firewall unless you copy/apply them.");
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("## Session-Derived Allowed Context");
|
|
254
|
+
lines.push("");
|
|
255
|
+
if (result.sessionAllowed.length) result.sessionAllowed.forEach((item) => lines.push(`- ${item}`));
|
|
256
|
+
else lines.push("- No repeated source files were strong enough to promote.");
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push("## Session-Derived Blocked Context");
|
|
259
|
+
lines.push("");
|
|
260
|
+
if (result.sessionBlocked.length) result.sessionBlocked.forEach((item) => lines.push(`- ${item}`));
|
|
261
|
+
else lines.push("- No generated/noisy paths were strong enough to add.");
|
|
262
|
+
lines.push("");
|
|
263
|
+
lines.push("## Next Session Prompt");
|
|
264
|
+
lines.push("");
|
|
265
|
+
lines.push("```text");
|
|
266
|
+
lines.push(`Use .prismo/context-firewall.suggested.md as the starting context policy for this ${result.task} task.`);
|
|
267
|
+
lines.push("Start from allowed context first. Do not read blocked paths unless you explain why they are required.");
|
|
268
|
+
lines.push("If you need wider context, name the exact file and reason before reading.");
|
|
269
|
+
lines.push("```");
|
|
270
|
+
lines.push("");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function runTimelineFirewallSuggestions(rootDir = process.cwd(), session = null, options = {}) {
|
|
275
|
+
const ctx = createOptimizeContext(rootDir, options.scope || null);
|
|
276
|
+
const task = options.task || options.scope || "timeline-followup";
|
|
277
|
+
const scope = inferTaskScope(task, ctx);
|
|
278
|
+
const suggestions = buildTimelineFirewallSuggestions(ctx, session, scope);
|
|
279
|
+
const result = {
|
|
280
|
+
root: ctx.root,
|
|
281
|
+
task,
|
|
282
|
+
scope,
|
|
283
|
+
sessionId: session?.sessionId || null,
|
|
284
|
+
allowed: suggestions.allowed,
|
|
285
|
+
blocked: suggestions.blocked,
|
|
286
|
+
sessionAllowed: suggestions.sessionAllowed,
|
|
287
|
+
sessionBlocked: suggestions.sessionBlocked,
|
|
288
|
+
generatedAt: new Date().toISOString(),
|
|
289
|
+
generatedFiles: [
|
|
290
|
+
".prismo/timeline-firewall-suggestions.md",
|
|
291
|
+
".prismo/context-firewall.suggested.md",
|
|
292
|
+
".prismo/allowed-context.suggested.txt",
|
|
293
|
+
".prismo/blocked-context.suggested.txt",
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (!options.dryRun) {
|
|
298
|
+
writeGeneratedFile(ctx.root, ".prismo/timeline-firewall-suggestions.md", renderTimelineFirewallSuggestions(result));
|
|
299
|
+
writeGeneratedFile(ctx.root, ".prismo/context-firewall.suggested.md", renderFirewallPolicy(result));
|
|
300
|
+
writeGeneratedFile(ctx.root, ".prismo/allowed-context.suggested.txt", renderLines("Allowed Context Suggestions", result.allowed));
|
|
301
|
+
writeGeneratedFile(ctx.root, ".prismo/blocked-context.suggested.txt", renderLines("Blocked Context Suggestions", result.blocked));
|
|
302
|
+
}
|
|
303
|
+
result.dryRun = Boolean(options.dryRun);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
190
307
|
function renderFirewallTerminal(result) {
|
|
191
308
|
const lines = [];
|
|
192
309
|
lines.push("");
|
|
@@ -218,6 +335,8 @@ function renderFirewallTerminal(result) {
|
|
|
218
335
|
renderFirewallPolicy,
|
|
219
336
|
renderFirewallPrompt,
|
|
220
337
|
renderFirewallTerminal,
|
|
338
|
+
renderTimelineFirewallSuggestions,
|
|
221
339
|
runFirewall,
|
|
340
|
+
runTimelineFirewallSuggestions,
|
|
222
341
|
};
|
|
223
342
|
};
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -202,6 +202,7 @@ const {
|
|
|
202
202
|
const {
|
|
203
203
|
renderFirewallTerminal,
|
|
204
204
|
runFirewall,
|
|
205
|
+
runTimelineFirewallSuggestions,
|
|
205
206
|
} = require("./prismo-dev/firewall")({
|
|
206
207
|
fs,
|
|
207
208
|
path,
|
|
@@ -263,7 +264,7 @@ Usage:
|
|
|
263
264
|
prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
|
|
264
265
|
prismo optimize [scope] [--json] [path]
|
|
265
266
|
prismo context [scope] [--json] [path]
|
|
266
|
-
prismo cc [list|last N|all] [--json] [--limit N] [path]
|
|
267
|
+
prismo cc [list|last N|all|timeline] [--json] [--limit N] [--firewall] [--task TASK] [path]
|
|
267
268
|
prismo usage [codex|claude|all] [--json] [--limit N] [path]
|
|
268
269
|
prismo watch [codex|claude|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
269
270
|
prismo demo
|
|
@@ -295,6 +296,8 @@ Options:
|
|
|
295
296
|
--simple Print a plain-English scan summary for first-time or non-technical users.
|
|
296
297
|
--no-report Do not write .prismo/prismo-dev-report.md.
|
|
297
298
|
--limit N Number of recent local sessions to show.
|
|
299
|
+
--firewall Generate cc timeline-derived firewall suggestion files.
|
|
300
|
+
--task TASK Name the task for timeline-derived firewall suggestions.
|
|
298
301
|
--interval N Refresh interval in seconds for watch mode.
|
|
299
302
|
--dry-run Preview doctor/fix actions without writing files.
|
|
300
303
|
--apply-ignores-only Only create/suggest AI ignore files in doctor mode.
|
|
@@ -377,7 +380,7 @@ Output:
|
|
|
377
380
|
|
|
378
381
|
Usage:
|
|
379
382
|
prismo cc [--json] [path]
|
|
380
|
-
prismo cc timeline [--json] [path]
|
|
383
|
+
prismo cc timeline [--json] [--firewall] [--task TASK] [path]
|
|
381
384
|
prismo cc list [--json] [--limit N] [path]
|
|
382
385
|
prismo cc last N [--json] [path]
|
|
383
386
|
prismo cc all [--json] [path]
|
|
@@ -385,6 +388,7 @@ Usage:
|
|
|
385
388
|
Examples:
|
|
386
389
|
prismo cc
|
|
387
390
|
prismo cc timeline
|
|
391
|
+
prismo cc timeline --firewall --task auth-bug
|
|
388
392
|
prismo cc list
|
|
389
393
|
prismo cc last 5
|
|
390
394
|
prismo cc all --json
|
|
@@ -393,6 +397,7 @@ Examples:
|
|
|
393
397
|
Reads ~/.claude/projects session logs and estimates Claude API token cost from input, output, cache write, and cache read tokens.
|
|
394
398
|
Adds Prismo diagnosis: cost drivers, estimated avoidable spend, and context-optimization next actions.
|
|
395
399
|
timeline shows context spikes, repeated commands, artifact leaks, and tool-output pressure for the latest session.
|
|
400
|
+
--firewall writes .prismo/timeline-firewall-suggestions.md and suggested allow/block files from timeline evidence.
|
|
396
401
|
Without a path, cc commands read all Claude Code projects. Passing a path filters to that project.`,
|
|
397
402
|
usage: `Prismo Usage
|
|
398
403
|
|
|
@@ -759,7 +764,17 @@ async function runCli(argv) {
|
|
|
759
764
|
if (command === "cc") {
|
|
760
765
|
const json = rest.includes("--json");
|
|
761
766
|
const limitIndex = rest.indexOf("--limit");
|
|
762
|
-
const
|
|
767
|
+
const firewall = rest.includes("--firewall");
|
|
768
|
+
const taskIndex = rest.indexOf("--task");
|
|
769
|
+
const firewallTask = taskIndex >= 0 && rest[taskIndex + 1] && !rest[taskIndex + 1].startsWith("-")
|
|
770
|
+
? rest[taskIndex + 1]
|
|
771
|
+
: "timeline-followup";
|
|
772
|
+
const ccArgs = rest.filter((_, index) => {
|
|
773
|
+
if (index === rest.indexOf("--firewall")) return false;
|
|
774
|
+
if (taskIndex >= 0 && (index === taskIndex || index === taskIndex + 1)) return false;
|
|
775
|
+
return true;
|
|
776
|
+
});
|
|
777
|
+
const positional = getPositionals(ccArgs, new Set(["--limit"]));
|
|
763
778
|
const subcommand = positional[0] && ["list", "last", "all", "timeline"].includes(positional[0].toLowerCase()) ? positional[0].toLowerCase() : "latest";
|
|
764
779
|
const lastCount = subcommand === "last" ? parsePositiveInt(positional[1], 5) : null;
|
|
765
780
|
const limit = subcommand === "list"
|
|
@@ -780,6 +795,9 @@ async function runCli(argv) {
|
|
|
780
795
|
if (json) {
|
|
781
796
|
if (subcommand === "timeline") {
|
|
782
797
|
const latest = summary.sessions[0] || null;
|
|
798
|
+
const firewallSuggestions = firewall && latest
|
|
799
|
+
? runTimelineFirewallSuggestions(path.resolve(target), latest, { task: firewallTask, dryRun: false })
|
|
800
|
+
: null;
|
|
783
801
|
console.log(JSON.stringify({
|
|
784
802
|
schemaVersion: 1,
|
|
785
803
|
generatedAt: summary.generatedAt,
|
|
@@ -796,6 +814,7 @@ async function runCli(argv) {
|
|
|
796
814
|
}
|
|
797
815
|
: null,
|
|
798
816
|
timeline: latest ? latest.timeline || [] : [],
|
|
817
|
+
firewallSuggestions,
|
|
799
818
|
suggestedAction: latest?.prismo?.recommendations?.[0] || `${NPX_COMMAND} doctor`,
|
|
800
819
|
}, null, 2));
|
|
801
820
|
return;
|
|
@@ -803,7 +822,20 @@ async function runCli(argv) {
|
|
|
803
822
|
console.log(JSON.stringify(summary, null, 2));
|
|
804
823
|
return;
|
|
805
824
|
}
|
|
806
|
-
|
|
825
|
+
const output = [renderClaudeCostTerminal(summary)];
|
|
826
|
+
if (subcommand === "timeline" && firewall) {
|
|
827
|
+
const latest = summary.sessions[0] || null;
|
|
828
|
+
if (latest) {
|
|
829
|
+
const suggestions = runTimelineFirewallSuggestions(path.resolve(target), latest, { task: firewallTask, dryRun: false });
|
|
830
|
+
output.push("");
|
|
831
|
+
output.push("Timeline Firewall Suggestions");
|
|
832
|
+
output.push(`Wrote: ${suggestions.generatedFiles.join(", ")}`);
|
|
833
|
+
output.push(`Session-derived allowed: ${suggestions.sessionAllowed.length}`);
|
|
834
|
+
output.push(`Session-derived blocked: ${suggestions.sessionBlocked.length}`);
|
|
835
|
+
output.push("Tell your agent: Use .prismo/context-firewall.suggested.md for the next scoped session.");
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
console.log(output.join("\n"));
|
|
807
839
|
return;
|
|
808
840
|
}
|
|
809
841
|
|
package/package.json
CHANGED