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 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
  };
@@ -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 positional = getPositionals(rest, new Set(["--limit"]));
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
- console.log(renderClaudeCostTerminal(summary));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
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",