getprismo 0.1.25 → 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
@@ -552,6 +560,16 @@ watch reads local session logs from codex and claude code. it detects:
552
560
 
553
561
  watch tells you the single most useful action to take right now. usually: start a fresh session, or switch to a scoped context pack.
554
562
 
563
+ if you run multiple agents in the same repo, use:
564
+
565
+ ```bash
566
+ npx getprismo watch --agents
567
+ ```
568
+
569
+ multi-agent watch shows every visible local Codex/Claude Code session for the repo, ranks each agent by context pressure, and flags coordination risks like two agents repeatedly loading the same file, shared artifact leaks, multiple high-pressure sessions, or agents that should move noisy commands into `shield`.
570
+
571
+ the same multi-agent coordination signal is included in `usage --json`, `scan --usage --json`, doctor output, and the generated markdown report whenever multiple local sessions are visible for the repo.
572
+
555
573
  `watch --rescue` prints a paste-ready prompt for the active coding session. use it when the agent is looping, reading too many files, or flooding context with logs:
556
574
 
557
575
  ```bash
@@ -644,6 +662,8 @@ npx getprismo doctor --json # machine-readable output
644
662
  ```bash
645
663
  npx getprismo watch # live refresh
646
664
  npx getprismo watch --once # single snapshot
665
+ npx getprismo watch --agents # multi-agent coordination view
666
+ npx getprismo watch --agents --json # machine-readable multi-agent state
647
667
  npx getprismo watch --once --report # write .prismo/watch-report.md
648
668
  npx getprismo watch --once --json # machine-readable
649
669
  npx getprismo watch --auto # guardrails + throttle + 600k budget
@@ -681,6 +701,7 @@ npx getprismo mcp /path/to/repo
681
701
  - `prismo_scan`
682
702
  - `prismo_doctor_dry_run`
683
703
  - `prismo_watch_snapshot`
704
+ - `prismo_multi_agent_watch`
684
705
  - `prismo_shield_run`
685
706
  - `prismo_shield_search`
686
707
  - `prismo_shield_last`
@@ -688,7 +709,7 @@ npx getprismo mcp /path/to/repo
688
709
  - `prismo_firewall`
689
710
  - `prismo_cc_timeline`
690
711
 
691
- This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs, or inspect token-waste signals without pasting giant logs into the conversation.
712
+ This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs, inspect token-waste signals, or coordinate multiple local agents without pasting giant logs into the conversation.
692
713
 
693
714
  Generic MCP client config:
694
715
 
@@ -723,6 +744,7 @@ For local development from this repo:
723
744
  ```bash
724
745
  npx getprismo cc # latest session cost
725
746
  npx getprismo cc timeline # event timeline for latest session
747
+ npx getprismo cc timeline --firewall --task auth-bug # suggest next-session firewall rules
726
748
  npx getprismo cc list # list recent sessions
727
749
  npx getprismo cc last 5 # last 5 sessions
728
750
  npx getprismo cc all # everything
package/docs/mcp.md CHANGED
@@ -49,6 +49,7 @@ For local development from this repo:
49
49
  - `prismo_scan`: scan repo context/token waste
50
50
  - `prismo_doctor_dry_run`: preview doctor payoff without writing files
51
51
  - `prismo_watch_snapshot`: inspect live context pressure
52
+ - `prismo_multi_agent_watch`: inspect coordination risks across parallel local agents
52
53
  - `prismo_shield_run`: run a noisy command and store full output locally
53
54
  - `prismo_shield_search`: search stored shield output
54
55
  - `prismo_shield_last`: list recent shielded command runs
@@ -324,6 +324,7 @@ function toDoctorJsonPayload(result) {
324
324
  confidence: usage.confidence,
325
325
  totals: usage.totals,
326
326
  sources: usage.sources,
327
+ multiAgent: usage.multiAgent || null,
327
328
  }
328
329
  : null;
329
330
  return {
@@ -381,6 +382,13 @@ function renderDoctorTerminal(result) {
381
382
  if (result.before.realUsage && result.before.realUsage.sessions.length) {
382
383
  lines.push(`Local usage: ${formatTokenCount(result.before.realUsage.totals.displayTokens)} tokens across ${result.before.realUsage.sessions.length} recent session(s)`);
383
384
  }
385
+ const multiAgent = result.before.realUsage && result.before.realUsage.multiAgent;
386
+ if (multiAgent && multiAgent.agentCount > 1) {
387
+ lines.push(`Multi-agent: ${multiAgent.agentCount} agents visible; highest pressure ${multiAgent.highestPressure}`);
388
+ if (multiAgent.coordinationWarnings && multiAgent.coordinationWarnings[0]) {
389
+ lines.push(`Coordination warning: ${multiAgent.coordinationWarnings[0]}`);
390
+ }
391
+ }
384
392
  if (!(result.dryRun && result.applySuggestions)) lines.push(`Estimated exposed context reduction: ${result.exposedTokenReductionPercent}%`);
385
393
  if (!result.dryRun) {
386
394
  lines.push(`Payoff: ${scoreDelta > 0 ? `repo is ${scoreDelta} points cleaner for AI coding sessions` : "safe fixes applied; remaining risk needs manual cleanup"}`);
@@ -457,6 +465,10 @@ function renderDevTerminal(result) {
457
465
  } else if (result.scan.realUsage) {
458
466
  lines.push("Real local usage: no matching local sessions found for this repo");
459
467
  }
468
+ const multiAgent = result.scan.realUsage && result.scan.realUsage.multiAgent;
469
+ if (multiAgent && multiAgent.agentCount > 1) {
470
+ lines.push(`Multi-agent: ${multiAgent.agentCount} agents visible; highest pressure ${multiAgent.highestPressure}`);
471
+ }
460
472
  lines.push("");
461
473
  lines.push("Generated:");
462
474
  result.optimize.generatedFiles.slice(0, 8).forEach((file) => lines.push(`- ${file}`));
@@ -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
  };
@@ -69,6 +69,11 @@ function createMcpTools(deps) {
69
69
  tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
70
70
  limit: limitProperty,
71
71
  }),
72
+ makeTool("prismo_multi_agent_watch", "Return multi-agent coordination risks across visible local Codex/Claude sessions.", {
73
+ path: pathProperty,
74
+ tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
75
+ limit: limitProperty,
76
+ }),
72
77
  makeTool("prismo_shield_run", "Run a noisy command through Prismo shield and store full output locally.", {
73
78
  path: pathProperty,
74
79
  command: {
@@ -131,11 +136,27 @@ function createMcpTools(deps) {
131
136
  const summary = getUsageSummary({
132
137
  cwd: target,
133
138
  limit: Number(args.limit) || 3,
134
- usageTool: args.tool || "all",
139
+ tool: args.tool || "all",
135
140
  });
136
141
  return createTextResult(summary);
137
142
  }
138
143
 
144
+ if (name === "prismo_multi_agent_watch") {
145
+ const summary = getUsageSummary({
146
+ cwd: target,
147
+ limit: Number(args.limit) || 8,
148
+ tool: args.tool || "all",
149
+ });
150
+ return createTextResult({
151
+ schemaVersion: 1,
152
+ generatedAt: summary.generatedAt,
153
+ scannedPath: summary.scannedPath,
154
+ tool: summary.tool,
155
+ totals: summary.totals,
156
+ multiAgent: summary.multiAgent,
157
+ });
158
+ }
159
+
139
160
  if (name === "prismo_shield_run") {
140
161
  return createTextResult(runShield(target, args.command));
141
162
  }
@@ -265,6 +286,7 @@ async function runMcpDoctor(deps) {
265
286
  "prismo_scan",
266
287
  "prismo_doctor_dry_run",
267
288
  "prismo_watch_snapshot",
289
+ "prismo_multi_agent_watch",
268
290
  "prismo_shield_run",
269
291
  "prismo_shield_search",
270
292
  "prismo_shield_last",
@@ -315,7 +337,7 @@ async function runMcpDoctor(deps) {
315
337
  next: [
316
338
  "Add the config snippet to your MCP-compatible client.",
317
339
  "Restart the client and confirm prismodev appears in the MCP tool list.",
318
- "Ask the agent to call prismo_scan or prismo_shield_run.",
340
+ "Ask the agent to call prismo_scan, prismo_multi_agent_watch, or prismo_shield_run.",
319
341
  ],
320
342
  };
321
343
  }
@@ -45,6 +45,10 @@ function renderTerminalReport(result, options = {}) {
45
45
  if (result.realUsage && result.realUsage.sessions.length) {
46
46
  lines.push(`- Real local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
47
47
  lines.push(`- Usage confidence: ${result.realUsage.confidence}`);
48
+ if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
49
+ lines.push(`- Multi-agent: ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
50
+ if (result.realUsage.multiAgent.coordinationWarnings[0]) lines.push(`- Coordination warning: ${result.realUsage.multiAgent.coordinationWarnings[0]}`);
51
+ }
48
52
  } else if (result.realUsage) {
49
53
  lines.push("- Real local usage: no matching local Codex/Claude Code sessions found for this repo");
50
54
  }
@@ -116,6 +120,9 @@ function renderOptimizerFitTerminal(result, options = {}) {
116
120
  lines.push(`Primary bottleneck: ${color(fit.summary, tone, useColor)}`);
117
121
  if (result.realUsage && result.realUsage.sessions.length) {
118
122
  lines.push(`Local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
123
+ if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
124
+ lines.push(`Multi-agent: ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
125
+ }
119
126
  } else if (result.realUsage) {
120
127
  lines.push("Local usage: no matching local Claude/Codex sessions found");
121
128
  }
@@ -261,6 +268,9 @@ function renderMarkdownReport(result) {
261
268
  if (result.realUsage) {
262
269
  lines.push(`- **Real Local Usage:** ${result.realUsage.totals.displayTokens.toLocaleString()} tokens across ${result.realUsage.sessions.length} session(s)`);
263
270
  lines.push(`- **Usage Confidence:** ${result.realUsage.confidence}`);
271
+ if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
272
+ lines.push(`- **Multi-Agent:** ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
273
+ }
264
274
  }
265
275
  lines.push("");
266
276
  lines.push("Estimates are based on local file-size and configuration heuristics. They are not provider billing data and are not guaranteed savings.");
@@ -346,6 +356,20 @@ function renderMarkdownReport(result) {
346
356
  lines.push(`- Estimated tool/output tokens: ${result.realUsage.totals.toolTokens.toLocaleString()}`);
347
357
  lines.push(`- Confidence: ${result.realUsage.confidence}`);
348
358
  lines.push("");
359
+ if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
360
+ lines.push("### Multi-Agent Coordination");
361
+ lines.push("");
362
+ lines.push(`- Agents visible: ${result.realUsage.multiAgent.agentCount}`);
363
+ lines.push(`- Highest pressure: ${result.realUsage.multiAgent.highestPressure}`);
364
+ result.realUsage.multiAgent.coordinationWarnings.slice(0, 5).forEach((warning) => lines.push(`- ${warning}`));
365
+ if (result.realUsage.multiAgent.sharedFiles.length) {
366
+ lines.push(`- Shared repeated files: ${result.realUsage.multiAgent.sharedFiles.slice(0, 5).map((item) => `\`${item.path}\` (${item.agents} agents)`).join(", ")}`);
367
+ }
368
+ if (result.realUsage.multiAgent.sharedArtifacts.length) {
369
+ lines.push(`- Shared artifact leaks: ${result.realUsage.multiAgent.sharedArtifacts.slice(0, 5).map((item) => `${item.type} (${item.agents} agents)`).join(", ")}`);
370
+ }
371
+ lines.push("");
372
+ }
349
373
  result.realUsage.sessions.slice(0, 5).forEach((session, index) => {
350
374
  lines.push(`${index + 1}. ${session.tool} - ${session.title || session.sessionId}`);
351
375
  lines.push(` - Tokens: ${session.displayTokens.toLocaleString()} (${session.confidence})`);
@@ -53,6 +53,7 @@ const {
53
53
 
54
54
  const {
55
55
  buildLiveSessionView,
56
+ buildMultiAgentView,
56
57
  } = require("./watch-live")({
57
58
  NPX_COMMAND,
58
59
  formatTokenCount,
@@ -65,7 +66,7 @@ const {
65
66
  getAllClaudeSessionFiles,
66
67
  getClaudeSessionFiles,
67
68
  getCodexSessionFiles,
68
- getUsageSummary,
69
+ getUsageSummary: getBaseUsageSummary,
69
70
  } = require("./usage-sessions")({
70
71
  fs,
71
72
  os,
@@ -76,15 +77,25 @@ const {
76
77
  readIfText,
77
78
  });
78
79
 
80
+ function getUsageSummary(options = {}) {
81
+ const summary = getBaseUsageSummary(options);
82
+ if ((summary.sessions || []).length > 1) {
83
+ summary.multiAgent = buildMultiAgentView(summary);
84
+ }
85
+ return summary;
86
+ }
87
+
79
88
  const {
80
89
  renderContextThrottle,
81
90
  renderLiveGuardrails,
91
+ renderMultiAgentWatchTerminal,
82
92
  renderRescuePrompt,
83
93
  renderUsageTerminal,
84
94
  renderWatchTerminal,
85
95
  } = require("./watch-render")({
86
96
  NPX_COMMAND,
87
97
  buildLiveSessionView,
98
+ buildMultiAgentView,
88
99
  color,
89
100
  formatTokenCount,
90
101
  getActionableRepeatedPaths,
@@ -199,24 +210,6 @@ function parseScopeAndTarget(args, valueFlags = new Set()) {
199
210
  return { scope: null, target: positional[0] || process.cwd() };
200
211
  }
201
212
 
202
- function formatTokenCount(value) {
203
- const n = Number(value || 0);
204
- if (n >= 1000000) return `${(n / 1000000).toFixed(2)}M`;
205
- if (n >= 1000) return `${Math.round(n / 1000)}k`;
206
- return String(Math.round(n));
207
- }
208
-
209
- function formatMoney(value) {
210
- const n = Number(value || 0);
211
- if (n >= 1) return `$${n.toFixed(2)}`;
212
- return `$${n.toFixed(4)}`;
213
- }
214
-
215
-
216
-
217
-
218
-
219
-
220
213
  function writeLiveFile(root, relPath, contents) {
221
214
  const fullPath = path.join(root || process.cwd(), relPath);
222
215
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
@@ -432,6 +425,7 @@ function compactUsageSummary(summary) {
432
425
  totals: summary.totals,
433
426
  sources: summary.sources,
434
427
  sessions: (summary.sessions || []).map(compactWatchSession),
428
+ multiAgent: summary.multiAgent || ((summary.sessions || []).length > 1 ? buildMultiAgentView(summary) : null),
435
429
  };
436
430
  }
437
431
 
@@ -484,6 +478,7 @@ function toWatchJsonPayload(summary) {
484
478
  sources: summary.sources,
485
479
  sessions: summary.sessions.map(compactWatchSession),
486
480
  live: summary.live || buildLiveSessionView(summary),
481
+ multiAgent: summary.multiAgent || (summary.agents ? buildMultiAgentView(summary) : null),
487
482
  auto: Boolean(summary.auto),
488
483
  rescuePrompt: summary.includeRescuePrompt ? renderRescuePrompt(summary) : null,
489
484
  guardrailsPath: summary.guardrailsPath || null,
@@ -502,7 +497,11 @@ async function watchUsage(options = {}) {
502
497
  for (let i = 0; i < iterations; i += 1) {
503
498
  const summary = getUsageSummary(options);
504
499
  summary.auto = Boolean(options.auto);
500
+ summary.agents = Boolean(options.agents);
505
501
  summary.live = buildLiveSessionView(summary);
502
+ if (options.agents) {
503
+ summary.multiAgent = buildMultiAgentView(summary);
504
+ }
506
505
  if (options.guardrails) {
507
506
  const written = writeLiveGuardrails(summary);
508
507
  summary.guardrailsPath = written.guardrailsPath;
@@ -524,7 +523,11 @@ async function watchUsage(options = {}) {
524
523
  }
525
524
  summary.redactPaths = Boolean(options.redactPaths);
526
525
  summary.includeRescuePrompt = Boolean(options.rescue);
527
- if (options.rescue && !options.json) {
526
+ if (options.agents && !options.json) {
527
+ console.clear();
528
+ console.log(renderMultiAgentWatchTerminal(summary));
529
+ if (!options.once) console.log(`\nRefreshing every ${Math.round(intervalMs / 1000)}s. Press Ctrl+C to stop.`);
530
+ } else if (options.rescue && !options.json) {
528
531
  console.log(renderRescuePrompt(summary));
529
532
  } else if (options.json) {
530
533
  console.log(JSON.stringify(toWatchJsonPayload(summary), null, 2));
@@ -345,8 +345,111 @@ module.exports = function createWatchLive(deps) {
345
345
  };
346
346
  }
347
347
 
348
+ function agentLabel(session, index) {
349
+ const tool = session.tool === "claude-code" ? "claude" : session.tool || "agent";
350
+ const id = String(session.sessionId || index + 1).slice(0, 8);
351
+ return `${tool}-${id}`;
352
+ }
353
+
354
+ function buildMultiAgentView(summary) {
355
+ const agents = (summary.sessions || []).map((session, index) => {
356
+ const singleSummary = {
357
+ ...summary,
358
+ sessions: [session],
359
+ totals: {
360
+ sessions: 1,
361
+ displayTokens: session.displayTokens || 0,
362
+ contextTokens: session.contextTokens || 0,
363
+ estimatedTokens: session.estimatedTotalTokens || 0,
364
+ exactTokens: session.exactAvailable ? session.exactTotalTokens || 0 : 0,
365
+ toolTokens: session.estimatedToolTokens || 0,
366
+ },
367
+ };
368
+ const live = buildLiveSessionView(singleSummary);
369
+ return {
370
+ label: agentLabel(session, index),
371
+ tool: session.tool,
372
+ sessionId: session.sessionId,
373
+ title: session.title,
374
+ model: session.model,
375
+ updatedAt: session.updatedAt,
376
+ contextPressure: live.contextPressure,
377
+ tokens: session.displayTokens || 0,
378
+ contextTokens: session.contextTokens || 0,
379
+ toolOutputTokens: session.estimatedToolTokens || 0,
380
+ turns: session.turns || 0,
381
+ toolCalls: session.toolCalls || 0,
382
+ liveAction: live.liveAction,
383
+ warnings: live.warnings,
384
+ repeatedFiles: live.activeSession?.actionableRepeatedPaths || [],
385
+ artifactGroups: live.activeSession?.generatedArtifactGroups || [],
386
+ repeatedCommands: session.repeatedCommands || [],
387
+ };
388
+ });
389
+
390
+ const warnings = [];
391
+ const highPressureAgents = agents.filter((agent) => agent.contextPressure === "High");
392
+ if (highPressureAgents.length >= 2) {
393
+ warnings.push(`${highPressureAgents.length} agents are under high context pressure; pause broad exploration and split work by task boundary.`);
394
+ }
395
+ const noisyAgents = agents.filter((agent) => ["tool-output-flood", "possible-loop"].includes(agent.liveAction?.cause));
396
+ if (noisyAgents.length) {
397
+ warnings.push(`${noisyAgents.length} agent${noisyAgents.length === 1 ? "" : "s"} should route noisy commands through shield before rerunning.`);
398
+ }
399
+
400
+ const fileOwners = new Map();
401
+ for (const agent of agents) {
402
+ for (const item of agent.repeatedFiles || []) {
403
+ const owners = fileOwners.get(item.value) || [];
404
+ owners.push({ agent: agent.label, count: item.count });
405
+ fileOwners.set(item.value, owners);
406
+ }
407
+ }
408
+ const sharedFiles = Array.from(fileOwners.entries())
409
+ .filter(([, owners]) => owners.length >= 2)
410
+ .map(([path, owners]) => ({ path, owners }))
411
+ .slice(0, 5);
412
+ for (const item of sharedFiles.slice(0, 3)) {
413
+ warnings.push(`${item.path} is repeatedly entering context across ${item.owners.length} agents.`);
414
+ }
415
+
416
+ const artifactOwners = new Map();
417
+ for (const agent of agents) {
418
+ for (const group of agent.artifactGroups || []) {
419
+ const owners = artifactOwners.get(group.type) || [];
420
+ owners.push({ agent: agent.label, count: group.count, example: group.examples?.[0] || null });
421
+ artifactOwners.set(group.type, owners);
422
+ }
423
+ }
424
+ const sharedArtifacts = Array.from(artifactOwners.entries())
425
+ .filter(([, owners]) => owners.length >= 2)
426
+ .map(([type, owners]) => ({ type, owners }))
427
+ .slice(0, 5);
428
+ for (const item of sharedArtifacts.slice(0, 2)) {
429
+ warnings.push(`${item.type} artifacts appear across ${item.owners.length} agents; add/verify ignore coverage before continuing.`);
430
+ }
431
+
432
+ const recommendedActions = [];
433
+ if (noisyAgents.length) recommendedActions.push(`${NPX_COMMAND} shield -- <noisy command>`);
434
+ if (sharedFiles.length || sharedArtifacts.length) recommendedActions.push(`${NPX_COMMAND} doctor --apply-suggestions --dry-run`);
435
+ if (highPressureAgents.length) recommendedActions.push("Ask each high-pressure agent for a compact handoff summary, then restart scoped sessions.");
436
+ if (!recommendedActions.length) recommendedActions.push("Keep agents scoped to separate files/tasks and continue watching.");
437
+
438
+ return {
439
+ enabled: true,
440
+ agentCount: agents.length,
441
+ agents,
442
+ highestPressure: agents.reduce((pressure, agent) => (getRiskRank(agent.contextPressure) > getRiskRank(pressure) ? agent.contextPressure : pressure), "Low"),
443
+ coordinationWarnings: Array.from(new Set(warnings)).slice(0, 8),
444
+ sharedFiles,
445
+ sharedArtifacts,
446
+ recommendedActions: Array.from(new Set(recommendedActions)).slice(0, 5),
447
+ };
448
+ }
449
+
348
450
  return {
349
451
  buildLiveSessionView,
452
+ buildMultiAgentView,
350
453
  getRiskRank,
351
454
  getTokenBudgetStatus,
352
455
  };
@@ -2,6 +2,7 @@ module.exports = function createWatchRender(deps) {
2
2
  const {
3
3
  NPX_COMMAND,
4
4
  buildLiveSessionView,
5
+ buildMultiAgentView,
5
6
  color,
6
7
  formatTokenCount,
7
8
  getActionableRepeatedPaths,
@@ -33,6 +34,15 @@ function renderUsageTerminal(summary, title = "Prismo Usage") {
33
34
  }
34
35
  lines.push("");
35
36
  lines.push("Notes: exact means the local tool log exposed token fields. Estimated means Prismo used local text size heuristics only.");
37
+ const multi = summary.multiAgent || ((summary.sessions || []).length > 1 && buildMultiAgentView ? buildMultiAgentView(summary) : null);
38
+ if (multi && multi.agentCount > 1) {
39
+ lines.push("");
40
+ lines.push("Multi-Agent Coordination:");
41
+ lines.push(`- Agents visible: ${multi.agentCount}`);
42
+ lines.push(`- Highest pressure: ${multi.highestPressure}`);
43
+ if (multi.coordinationWarnings.length) lines.push(`- Top warning: ${multi.coordinationWarnings[0]}`);
44
+ lines.push(`- Live view: ${NPX_COMMAND} watch --agents`);
45
+ }
36
46
  return lines.join("\n");
37
47
  }
38
48
  function renderWatchTerminal(summary) {
@@ -107,6 +117,60 @@ function renderWatchTerminal(summary) {
107
117
  lines.push("Expected project instruction files are muted unless they combine with stronger context-pressure signals.");
108
118
  return lines.join("\n");
109
119
  }
120
+ function renderMultiAgentWatchTerminal(summary) {
121
+ const multi = summary.multiAgent;
122
+ const lines = [];
123
+ lines.push("");
124
+ lines.push(color("Prismo Multi-Agent Watch", "bold"));
125
+ lines.push("");
126
+ if (!multi || !multi.agentCount) {
127
+ lines.push("Agents: 0");
128
+ lines.push("- No local Codex/Claude Code sessions detected for this repo yet.");
129
+ lines.push("");
130
+ lines.push("Suggested Action");
131
+ lines.push(`Run: ${NPX_COMMAND} setup`);
132
+ return lines.join("\n");
133
+ }
134
+
135
+ const pressureTone = multi.highestPressure === "High" ? "red" : multi.highestPressure === "Medium" ? "yellow" : "green";
136
+ lines.push(`Agents: ${multi.agentCount}`);
137
+ lines.push(`Highest Pressure: ${color(String(multi.highestPressure).toUpperCase(), pressureTone)}`);
138
+ lines.push("");
139
+ lines.push("Active Agents");
140
+ multi.agents.slice(0, 8).forEach((agent) => {
141
+ const tone = agent.contextPressure === "High" ? "red" : agent.contextPressure === "Medium" ? "yellow" : "green";
142
+ lines.push(`- ${agent.label} ${agent.tool} ${color(agent.contextPressure.toUpperCase(), tone)} ${formatTokenCount(agent.tokens)} tokens ${agent.liveAction.cause}`);
143
+ if (agent.updatedAt) lines.push(` updated: ${agent.updatedAt}`);
144
+ });
145
+
146
+ lines.push("");
147
+ lines.push("Coordination Warnings");
148
+ if (multi.coordinationWarnings.length) multi.coordinationWarnings.forEach((warning) => lines.push(`- ${warning}`));
149
+ else lines.push("- No cross-agent coordination risks detected.");
150
+
151
+ if (multi.sharedFiles.length) {
152
+ lines.push("");
153
+ lines.push("Shared Repeated Files");
154
+ multi.sharedFiles.slice(0, 5).forEach((item) => {
155
+ lines.push(`- ${item.path} (${item.owners.map((owner) => `${owner.agent}:${owner.count}x`).join(", ")})`);
156
+ });
157
+ }
158
+
159
+ if (multi.sharedArtifacts.length) {
160
+ lines.push("");
161
+ lines.push("Shared Artifact Leaks");
162
+ multi.sharedArtifacts.slice(0, 5).forEach((item) => {
163
+ lines.push(`- ${item.type} (${item.owners.map((owner) => `${owner.agent}:${owner.count}x`).join(", ")})`);
164
+ });
165
+ }
166
+
167
+ lines.push("");
168
+ lines.push("Do This Now");
169
+ multi.recommendedActions.forEach((action, index) => lines.push(`${index + 1}. ${action}`));
170
+ lines.push("");
171
+ lines.push("Signals are local estimates from available coding-agent logs. Use this view to coordinate parallel agents before they duplicate work or flood context.");
172
+ return lines.join("\n");
173
+ }
110
174
  function renderRescuePrompt(summary) {
111
175
  const live = summary.live || buildLiveSessionView(summary);
112
176
  const active = live.activeSession;
@@ -283,6 +347,7 @@ function renderContextThrottle(summary) {
283
347
  return {
284
348
  renderContextThrottle,
285
349
  renderLiveGuardrails,
350
+ renderMultiAgentWatchTerminal,
286
351
  renderRescuePrompt,
287
352
  renderUsageTerminal,
288
353
  renderWatchTerminal,
@@ -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,9 +264,9 @@ 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
- prismo watch [codex|claude|all] [--json] [--once] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
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
270
271
 
271
272
  Commands:
@@ -295,12 +296,15 @@ 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.
301
304
  --apply-suggestions Append missing recommended ignore rules with backups.
302
305
  --no-context-packs Skip .prismo context-pack generation in doctor mode.
303
306
  --report Write .prismo/watch-report.md in watch mode.
307
+ --agents Show multi-agent coordination view for parallel local sessions.
304
308
  --rescue Print a paste-ready live-session rescue prompt in watch mode.
305
309
  --guardrails Write/update .prismo/live-guardrails.md and .prismo/live-rescue-prompt.md.
306
310
  --throttle Write/update .prismo/live-context-throttle.md in watch mode.
@@ -376,7 +380,7 @@ Output:
376
380
 
377
381
  Usage:
378
382
  prismo cc [--json] [path]
379
- prismo cc timeline [--json] [path]
383
+ prismo cc timeline [--json] [--firewall] [--task TASK] [path]
380
384
  prismo cc list [--json] [--limit N] [path]
381
385
  prismo cc last N [--json] [path]
382
386
  prismo cc all [--json] [path]
@@ -384,6 +388,7 @@ Usage:
384
388
  Examples:
385
389
  prismo cc
386
390
  prismo cc timeline
391
+ prismo cc timeline --firewall --task auth-bug
387
392
  prismo cc list
388
393
  prismo cc last 5
389
394
  prismo cc all --json
@@ -392,6 +397,7 @@ Examples:
392
397
  Reads ~/.claude/projects session logs and estimates Claude API token cost from input, output, cache write, and cache read tokens.
393
398
  Adds Prismo diagnosis: cost drivers, estimated avoidable spend, and context-optimization next actions.
394
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.
395
401
  Without a path, cc commands read all Claude Code projects. Passing a path filters to that project.`,
396
402
  usage: `Prismo Usage
397
403
 
@@ -405,11 +411,12 @@ Examples:
405
411
  watch: `Prismo Watch
406
412
 
407
413
  Usage:
408
- prismo watch [codex|claude|all] [--json] [--once] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
414
+ prismo watch [codex|claude|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
409
415
 
410
416
  Examples:
411
417
  prismo watch codex
412
418
  prismo watch claude --once --json
419
+ prismo watch --agents --once
413
420
  prismo watch --once --report
414
421
  prismo watch --once --redact-paths
415
422
  prismo watch --rescue
@@ -421,6 +428,7 @@ Examples:
421
428
 
422
429
  Output:
423
430
  Shows context pressure, recent growth, likely context leaks, repeated reads/commands, loop suspicion, one suggested action, and live intervention steps.
431
+ --agents shows all visible local sessions for this repo and coordination warnings.
424
432
  --rescue prints a paste-ready prompt to recover a noisy or looping active session.
425
433
  --guardrails continuously updates .prismo/live-guardrails.md and .prismo/live-rescue-prompt.md for the active session.
426
434
  --throttle writes a stricter live context budget file for the current agent session.
@@ -756,7 +764,17 @@ async function runCli(argv) {
756
764
  if (command === "cc") {
757
765
  const json = rest.includes("--json");
758
766
  const limitIndex = rest.indexOf("--limit");
759
- 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"]));
760
778
  const subcommand = positional[0] && ["list", "last", "all", "timeline"].includes(positional[0].toLowerCase()) ? positional[0].toLowerCase() : "latest";
761
779
  const lastCount = subcommand === "last" ? parsePositiveInt(positional[1], 5) : null;
762
780
  const limit = subcommand === "list"
@@ -777,6 +795,9 @@ async function runCli(argv) {
777
795
  if (json) {
778
796
  if (subcommand === "timeline") {
779
797
  const latest = summary.sessions[0] || null;
798
+ const firewallSuggestions = firewall && latest
799
+ ? runTimelineFirewallSuggestions(path.resolve(target), latest, { task: firewallTask, dryRun: false })
800
+ : null;
780
801
  console.log(JSON.stringify({
781
802
  schemaVersion: 1,
782
803
  generatedAt: summary.generatedAt,
@@ -793,6 +814,7 @@ async function runCli(argv) {
793
814
  }
794
815
  : null,
795
816
  timeline: latest ? latest.timeline || [] : [],
817
+ firewallSuggestions,
796
818
  suggestedAction: latest?.prismo?.recommendations?.[0] || `${NPX_COMMAND} doctor`,
797
819
  }, null, 2));
798
820
  return;
@@ -800,7 +822,20 @@ async function runCli(argv) {
800
822
  console.log(JSON.stringify(summary, null, 2));
801
823
  return;
802
824
  }
803
- 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"));
804
839
  return;
805
840
  }
806
841
 
@@ -825,6 +860,7 @@ async function runCli(argv) {
825
860
  limit,
826
861
  tokenBudget,
827
862
  auto,
863
+ agents: rest.includes("--agents"),
828
864
  json,
829
865
  once: rest.includes("--once"),
830
866
  report: rest.includes("--report"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.25",
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",