getprismo 0.1.28 → 0.1.30

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.
@@ -0,0 +1,291 @@
1
+ module.exports = function createReceipt(deps) {
2
+ const {
3
+ fs,
4
+ path,
5
+ GENERATED_ARTIFACT_PATTERNS,
6
+ NPX_COMMAND,
7
+ formatTokenCount,
8
+ getUsageSummary,
9
+ readIfText,
10
+ } = deps;
11
+
12
+ const {
13
+ getActionableRepeatedPaths,
14
+ summarizeGeneratedArtifacts,
15
+ } = require("./usage-log-utils")({
16
+ fs,
17
+ path,
18
+ GENERATED_ARTIFACT_PATTERNS,
19
+ readIfText,
20
+ });
21
+
22
+ function sumCounts(items) {
23
+ return (items || []).reduce((sum, item) => sum + Number(item.count || 0), 0);
24
+ }
25
+
26
+ function topItems(items, limit = 5) {
27
+ return (items || [])
28
+ .slice()
29
+ .sort((a, b) => Number(b.count || 0) - Number(a.count || 0))
30
+ .slice(0, limit)
31
+ .map((item) => ({
32
+ value: item.value,
33
+ count: Number(item.count || 0),
34
+ }));
35
+ }
36
+
37
+ function sessionScore(session) {
38
+ let score = 0;
39
+ if (session.contextRisk === "High") score += 45;
40
+ else if (session.contextRisk === "Medium") score += 25;
41
+ if ((session.estimatedToolTokens || 0) >= 75000) score += 30;
42
+ else if ((session.estimatedToolTokens || 0) >= 25000) score += 15;
43
+ score += Math.min(25, sumCounts(session.repeatedPathMentions) * 2);
44
+ score += Math.min(20, sumCounts(session.generatedArtifacts) * 2);
45
+ score += Math.min(20, sumCounts(session.repeatedCommands) * 3);
46
+ if (session.loopSuspicion) score += 20;
47
+ if ((session.turns || 0) >= 30) score += 10;
48
+ return score;
49
+ }
50
+
51
+ function chooseRootCause(session) {
52
+ if (!session) return { cause: "no-session", confidence: "low", summary: "No local coding-agent sessions were found for this repo." };
53
+ if ((session.estimatedToolTokens || 0) >= 75000) {
54
+ return {
55
+ cause: "tool-output-flood",
56
+ confidence: session.estimatedToolTokens >= 200000 ? "high" : "medium",
57
+ summary: `Tool/output tokens dominated the session (${formatTokenCount(session.estimatedToolTokens)} estimated tokens).`,
58
+ };
59
+ }
60
+ if (session.loopSuspicion && (session.repeatedCommands || []).length) {
61
+ return {
62
+ cause: "possible-command-loop",
63
+ confidence: session.loopConfidence || "medium",
64
+ summary: `${session.repeatedCommands[0].value} repeated ${session.repeatedCommands[0].count}x.`,
65
+ };
66
+ }
67
+ const artifactGroup = summarizeGeneratedArtifacts(session.generatedArtifacts || [], 1)[0];
68
+ if (artifactGroup) {
69
+ return {
70
+ cause: "artifact-leak",
71
+ confidence: artifactGroup.count >= 10 ? "high" : "medium",
72
+ summary: `${artifactGroup.type} artifacts appeared in context (${artifactGroup.count} mentions).`,
73
+ };
74
+ }
75
+ const repeatedPath = getActionableRepeatedPaths(session, 1)[0] || topItems(session.repeatedPathMentions, 1)[0];
76
+ if (repeatedPath) {
77
+ return {
78
+ cause: "repeated-file-read",
79
+ confidence: repeatedPath.count >= 20 ? "high" : "medium",
80
+ summary: `${repeatedPath.value} repeatedly entered context (${repeatedPath.count}x).`,
81
+ };
82
+ }
83
+ if (session.contextRisk === "High") {
84
+ return {
85
+ cause: "high-context-pressure",
86
+ confidence: "medium",
87
+ summary: `Session reached high context pressure (${formatTokenCount(session.displayTokens || session.contextTokens)} tokens).`,
88
+ };
89
+ }
90
+ return {
91
+ cause: "healthy-or-low-signal",
92
+ confidence: "low",
93
+ summary: "No major repeated reads, command loops, tool-output floods, or artifact leaks were detected.",
94
+ };
95
+ }
96
+
97
+ function buildReceiptSession(session) {
98
+ const repeatedReads = topItems(getActionableRepeatedPaths(session, 10).length ? getActionableRepeatedPaths(session, 10) : session.repeatedPathMentions, 10);
99
+ const artifactGroups = summarizeGeneratedArtifacts(session.generatedArtifacts || [], 10);
100
+ const repeatedCommands = topItems(session.repeatedCommands, 10);
101
+ const likelyInfluence = [];
102
+ for (const item of repeatedReads.slice(0, 5)) {
103
+ likelyInfluence.push({
104
+ type: "repeated-read",
105
+ value: item.value,
106
+ reason: `Read or mentioned ${item.count}x in the session.`,
107
+ });
108
+ }
109
+ for (const command of repeatedCommands.slice(0, 3)) {
110
+ likelyInfluence.push({
111
+ type: "repeated-command",
112
+ value: command.value,
113
+ reason: `Command appeared ${command.count}x${session.loopSuspicion ? "; possible loop signal." : "."}`,
114
+ });
115
+ }
116
+ if ((session.estimatedToolTokens || 0) > 0) {
117
+ likelyInfluence.push({
118
+ type: "tool-output",
119
+ value: `${formatTokenCount(session.estimatedToolTokens)} estimated tokens`,
120
+ reason: "Tool output likely shaped later turns because it remained in session context.",
121
+ });
122
+ }
123
+
124
+ const shouldIgnore = [];
125
+ for (const group of artifactGroups) {
126
+ shouldIgnore.push({
127
+ value: group.type,
128
+ reason: `${group.count} artifact mention${group.count === 1 ? "" : "s"} in context.`,
129
+ });
130
+ }
131
+ for (const item of topItems(session.generatedArtifacts, 5)) {
132
+ shouldIgnore.push({
133
+ value: item.value,
134
+ reason: `${item.count} mention${item.count === 1 ? "" : "s"} in context.`,
135
+ });
136
+ }
137
+
138
+ const nextRun = [];
139
+ if (session.contextRisk === "High") nextRun.push("Start a fresh scoped session before continuing.");
140
+ if ((session.estimatedToolTokens || 0) >= 25000) nextRun.push(`Run noisy commands through ${NPX_COMMAND} shield -- <command>.`);
141
+ if (artifactGroups.length || shouldIgnore.length) nextRun.push(`Run ${NPX_COMMAND} doctor --apply-suggestions --dry-run and review ignore candidates.`);
142
+ if (repeatedReads.length) nextRun.push(`Use ${NPX_COMMAND} firewall <task> so the agent starts with a smaller read boundary.`);
143
+ if (session.loopSuspicion) nextRun.push(`Run ${NPX_COMMAND} watch --rescue and ask the agent to summarize state before another command.`);
144
+ if (!nextRun.length) nextRun.push("Keep the next session scoped; no urgent corrective action was detected.");
145
+
146
+ return {
147
+ sessionId: session.sessionId,
148
+ tool: session.tool,
149
+ model: session.model || "",
150
+ startedAt: session.startedAt || null,
151
+ updatedAt: session.updatedAt || null,
152
+ contextRisk: session.contextRisk,
153
+ confidence: session.confidence,
154
+ turns: session.turns || 0,
155
+ toolCalls: session.toolCalls || 0,
156
+ toolResults: session.toolResults || 0,
157
+ tokens: {
158
+ display: session.displayTokens || 0,
159
+ context: session.contextTokens || 0,
160
+ exact: session.exactTotalTokens || 0,
161
+ toolOutput: session.estimatedToolTokens || 0,
162
+ },
163
+ rootCause: chooseRootCause(session),
164
+ readReceipt: {
165
+ repeatedReads,
166
+ repeatedReadMentions: sumCounts(repeatedReads),
167
+ },
168
+ outputReceipt: {
169
+ toolOutputTokens: session.estimatedToolTokens || 0,
170
+ repeatedCommands,
171
+ loopSuspicion: Boolean(session.loopSuspicion),
172
+ loopConfidence: session.loopConfidence || "low",
173
+ },
174
+ artifactReceipt: {
175
+ artifactGroups,
176
+ generatedArtifacts: topItems(session.generatedArtifacts, 10),
177
+ },
178
+ likelyInfluence: likelyInfluence.slice(0, 10),
179
+ nextRun,
180
+ score: sessionScore(session),
181
+ };
182
+ }
183
+
184
+ function buildReceipt(options = {}) {
185
+ const cwd = options.cwd || process.cwd();
186
+ const limit = options.limit || 5;
187
+ const tool = options.tool || "all";
188
+ const usage = getUsageSummary({ cwd, limit, tool });
189
+ const sessions = (usage.sessions || [])
190
+ .map(buildReceiptSession)
191
+ .sort((a, b) => b.score - a.score || new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0));
192
+ const primary = sessions[0] || null;
193
+ const aggregate = sessions.reduce((acc, session) => {
194
+ acc.sessions += 1;
195
+ acc.displayTokens += session.tokens.display || 0;
196
+ acc.contextTokens += session.tokens.context || 0;
197
+ acc.toolOutputTokens += session.tokens.toolOutput || 0;
198
+ acc.repeatedReadMentions += session.readReceipt.repeatedReadMentions || 0;
199
+ acc.repeatedCommands += session.outputReceipt.repeatedCommands.reduce((sum, item) => sum + Number(item.count || 0), 0);
200
+ acc.artifactMentions += session.artifactReceipt.generatedArtifacts.reduce((sum, item) => sum + Number(item.count || 0), 0);
201
+ return acc;
202
+ }, {
203
+ sessions: 0,
204
+ displayTokens: 0,
205
+ contextTokens: 0,
206
+ toolOutputTokens: 0,
207
+ repeatedReadMentions: 0,
208
+ repeatedCommands: 0,
209
+ artifactMentions: 0,
210
+ });
211
+
212
+ return {
213
+ schemaVersion: 1,
214
+ command: "receipt",
215
+ generatedAt: new Date().toISOString(),
216
+ scannedPath: cwd,
217
+ tool,
218
+ confidence: usage.confidence,
219
+ sources: usage.sources || [],
220
+ aggregate,
221
+ primary,
222
+ sessions,
223
+ next: primary ? primary.nextRun : [`Run ${NPX_COMMAND} watch --once after starting a coding-agent session in this repo.`],
224
+ };
225
+ }
226
+
227
+ function renderReceiptTerminal(receipt) {
228
+ const lines = [];
229
+ lines.push("");
230
+ lines.push("Prismo Run Receipt");
231
+ lines.push("");
232
+ if (!receipt.sessions.length) {
233
+ lines.push("No matching local coding-agent sessions found for this repo.");
234
+ lines.push("");
235
+ lines.push(`Next: ${receipt.next[0]}`);
236
+ return lines.join("\n");
237
+ }
238
+
239
+ const primary = receipt.primary;
240
+ lines.push(`Session: ${primary.sessionId || "unknown"} (${primary.tool})`);
241
+ lines.push(`Model: ${primary.model || "unknown"}`);
242
+ lines.push(`Updated: ${primary.updatedAt || "unknown"}`);
243
+ lines.push(`Context: ${formatTokenCount(primary.tokens.display || primary.tokens.context)} tokens (${primary.contextRisk || "Unknown"} risk, ${primary.confidence})`);
244
+ lines.push("");
245
+ lines.push("Root Cause");
246
+ lines.push(`- ${primary.rootCause.cause} (${primary.rootCause.confidence})`);
247
+ lines.push(`- ${primary.rootCause.summary}`);
248
+ lines.push("");
249
+ lines.push("Read Receipt");
250
+ if (primary.readReceipt.repeatedReads.length) {
251
+ primary.readReceipt.repeatedReads.slice(0, 5).forEach((item) => lines.push(`- ${item.value} (${item.count}x)`));
252
+ } else {
253
+ lines.push("- No repeated source/file reads detected.");
254
+ }
255
+ lines.push("");
256
+ lines.push("Output Receipt");
257
+ lines.push(`- Tool/output tokens: ${formatTokenCount(primary.outputReceipt.toolOutputTokens)}`);
258
+ if (primary.outputReceipt.repeatedCommands.length) {
259
+ primary.outputReceipt.repeatedCommands.slice(0, 5).forEach((item) => lines.push(`- Repeated command: ${item.value} (${item.count}x)`));
260
+ } else {
261
+ lines.push("- No repeated commands detected.");
262
+ }
263
+ lines.push("");
264
+ lines.push("Artifact Receipt");
265
+ if (primary.artifactReceipt.artifactGroups.length) {
266
+ primary.artifactReceipt.artifactGroups.slice(0, 5).forEach((item) => lines.push(`- ${item.type}: ${item.count} mentions`));
267
+ } else {
268
+ lines.push("- No generated artifact leaks detected.");
269
+ }
270
+ lines.push("");
271
+ lines.push("Likely Influence");
272
+ if (primary.likelyInfluence.length) {
273
+ primary.likelyInfluence.slice(0, 5).forEach((item) => lines.push(`- ${item.value}: ${item.reason}`));
274
+ } else {
275
+ lines.push("- No strong influence signals detected.");
276
+ }
277
+ lines.push("");
278
+ lines.push("Next Run");
279
+ primary.nextRun.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
280
+ if (receipt.sessions.length > 1) {
281
+ lines.push("");
282
+ lines.push(`Receipt covered ${receipt.sessions.length} session(s): ${formatTokenCount(receipt.aggregate.displayTokens)} displayed tokens, ${formatTokenCount(receipt.aggregate.toolOutputTokens)} tool/output tokens.`);
283
+ }
284
+ return lines.join("\n");
285
+ }
286
+
287
+ return {
288
+ buildReceipt,
289
+ renderReceiptTerminal,
290
+ };
291
+ };
@@ -0,0 +1,191 @@
1
+ module.exports = function createReplay(deps) {
2
+ const {
3
+ NPX_COMMAND,
4
+ buildMultiSessionTimeline,
5
+ buildReceipt,
6
+ formatTokenCount,
7
+ } = deps;
8
+
9
+ function classifyIncident(receipt, timeline) {
10
+ const primary = receipt.primary;
11
+ if (!primary) {
12
+ return {
13
+ type: "no-session",
14
+ severity: "low",
15
+ summary: "No local coding-agent sessions were found for this repo.",
16
+ };
17
+ }
18
+ if (primary.rootCause.cause === "tool-output-flood") {
19
+ return {
20
+ type: "tool-output-spiral",
21
+ severity: primary.tokens.toolOutput >= 200000 ? "high" : "medium",
22
+ summary: "Tool output dominated the session and likely crowded out useful working state.",
23
+ };
24
+ }
25
+ if (primary.outputReceipt.loopSuspicion) {
26
+ return {
27
+ type: "command-loop",
28
+ severity: primary.outputReceipt.loopConfidence === "high" ? "high" : "medium",
29
+ summary: "The same command or workflow repeated enough times to look like a loop.",
30
+ };
31
+ }
32
+ if (primary.rootCause.cause === "artifact-leak" || timeline.repeatedArtifacts.length) {
33
+ return {
34
+ type: "artifact-context-leak",
35
+ severity: timeline.repeatedArtifacts.some((item) => item.sessions >= 2) ? "high" : "medium",
36
+ summary: "Generated or low-signal artifacts repeatedly entered agent context.",
37
+ };
38
+ }
39
+ if (primary.rootCause.cause === "repeated-file-read" || timeline.repeatedFiles.length) {
40
+ return {
41
+ type: "repeated-exploration",
42
+ severity: timeline.repeatedFiles.some((item) => item.sessions >= 2) ? "medium" : "low",
43
+ summary: "The agent repeatedly revisited the same files instead of converging.",
44
+ };
45
+ }
46
+ if (primary.contextRisk === "High") {
47
+ return {
48
+ type: "stale-large-session",
49
+ severity: "medium",
50
+ summary: "The session reached high context pressure and should likely have split earlier.",
51
+ };
52
+ }
53
+ return {
54
+ type: "low-signal",
55
+ severity: "low",
56
+ summary: "No obvious incident pattern was detected.",
57
+ };
58
+ }
59
+
60
+ function buildRecoveryPrompt(replay) {
61
+ const primary = replay.receipt.primary;
62
+ if (!primary) return "Start a coding-agent session in this repo, then rerun Prismo replay.";
63
+ const blocked = [
64
+ ...primary.artifactReceipt.generatedArtifacts.map((item) => item.value),
65
+ ...replay.timeline.repeatedArtifacts.map((item) => item.value),
66
+ ].slice(0, 8);
67
+ const reads = primary.readReceipt.repeatedReads.slice(0, 5).map((item) => item.value);
68
+ const lines = [];
69
+ if (replay.incident.type === "low-signal") {
70
+ lines.push("No high-confidence waste incident was detected. Use this as a lightweight handoff before continuing.");
71
+ } else {
72
+ lines.push("We are recovering from a high-waste AI coding session. Stop broad exploration and rebuild working state before continuing.");
73
+ }
74
+ lines.push("");
75
+ lines.push(`Incident pattern: ${replay.incident.type} (${replay.incident.severity}).`);
76
+ lines.push(`Root cause: ${primary.rootCause.summary}`);
77
+ if (reads.length) lines.push(`Do not reread these unless they changed: ${reads.join(", ")}.`);
78
+ if (blocked.length) lines.push(`Do not read these generated/noisy artifacts unless explicitly required: ${Array.from(new Set(blocked)).join(", ")}.`);
79
+ lines.push("");
80
+ lines.push("Before editing anything else, summarize:");
81
+ lines.push("- files changed so far");
82
+ lines.push("- exact failing command or error");
83
+ lines.push("- current hypothesis");
84
+ lines.push("- next smallest file or test to inspect");
85
+ lines.push("");
86
+ lines.push(`Use ${NPX_COMMAND} shield -- <command> for noisy commands.`);
87
+ lines.push(`Use ${NPX_COMMAND} firewall <task> before the next scoped session.`);
88
+ return lines.join("\n");
89
+ }
90
+
91
+ function buildReplay(options = {}) {
92
+ const receipt = buildReceipt(options);
93
+ const timeline = buildMultiSessionTimeline(options);
94
+ const incident = classifyIncident(receipt, timeline);
95
+ const primary = receipt.primary;
96
+ const sequence = [];
97
+ if (primary) {
98
+ if (primary.tokens.toolOutput) {
99
+ sequence.push({
100
+ type: "tool-output",
101
+ detail: `${formatTokenCount(primary.tokens.toolOutput)} tool/output tokens entered the session.`,
102
+ });
103
+ }
104
+ primary.artifactReceipt.artifactGroups.forEach((group) => {
105
+ sequence.push({
106
+ type: "artifact-leak",
107
+ detail: `${group.type} artifacts appeared ${group.count} time(s).`,
108
+ });
109
+ });
110
+ primary.readReceipt.repeatedReads.forEach((item) => {
111
+ sequence.push({
112
+ type: "repeated-read",
113
+ detail: `${item.value} appeared ${item.count} time(s).`,
114
+ });
115
+ });
116
+ primary.outputReceipt.repeatedCommands.forEach((item) => {
117
+ sequence.push({
118
+ type: "repeated-command",
119
+ detail: `${item.value} repeated ${item.count} time(s).`,
120
+ });
121
+ });
122
+ if (primary.contextRisk === "High") {
123
+ sequence.push({
124
+ type: "split-needed",
125
+ detail: "Context pressure reached High; future work should split at a task boundary.",
126
+ });
127
+ }
128
+ }
129
+
130
+ const replay = {
131
+ schemaVersion: 1,
132
+ command: "replay",
133
+ generatedAt: new Date().toISOString(),
134
+ scannedPath: receipt.scannedPath,
135
+ tool: receipt.tool,
136
+ incident,
137
+ sequence,
138
+ receipt,
139
+ timeline,
140
+ recoveryPrompt: "",
141
+ next: primary
142
+ ? [
143
+ ...primary.nextRun,
144
+ `${NPX_COMMAND} timeline --last ${Math.max(timeline.sessionsAnalyzed || 5, 5)}`,
145
+ ]
146
+ : receipt.next,
147
+ };
148
+ replay.recoveryPrompt = buildRecoveryPrompt(replay);
149
+ return replay;
150
+ }
151
+
152
+ function renderReplayTerminal(replay) {
153
+ const lines = [];
154
+ lines.push("");
155
+ lines.push("Prismo Incident Replay");
156
+ lines.push("");
157
+ lines.push(`Incident: ${replay.incident.type} (${replay.incident.severity})`);
158
+ lines.push(replay.incident.summary);
159
+ lines.push("");
160
+ if (!replay.receipt.primary) {
161
+ lines.push("No session evidence available yet.");
162
+ lines.push(`Next: ${replay.next[0]}`);
163
+ return lines.join("\n");
164
+ }
165
+ lines.push("What Happened");
166
+ if (replay.sequence.length) replay.sequence.slice(0, 10).forEach((event) => lines.push(`- ${event.type}: ${event.detail}`));
167
+ else lines.push("- No high-signal sequence detected.");
168
+ lines.push("");
169
+ lines.push("Why It Matters");
170
+ lines.push(`- Root cause: ${replay.receipt.primary.rootCause.summary}`);
171
+ lines.push(`- Sessions analyzed: ${replay.timeline.sessionsAnalyzed}`);
172
+ if (replay.timeline.repeatedArtifacts.length) {
173
+ lines.push(`- Recurring artifacts: ${replay.timeline.repeatedArtifacts.slice(0, 3).map((item) => item.value).join(", ")}`);
174
+ }
175
+ if (replay.timeline.repeatedFiles.length) {
176
+ lines.push(`- Recurring repeated reads: ${replay.timeline.repeatedFiles.slice(0, 3).map((item) => item.value).join(", ")}`);
177
+ }
178
+ lines.push("");
179
+ lines.push("Recovery Prompt");
180
+ lines.push(replay.recoveryPrompt);
181
+ lines.push("");
182
+ lines.push("Next");
183
+ replay.next.slice(0, 5).forEach((item, index) => lines.push(`${index + 1}. ${item}`));
184
+ return lines.join("\n");
185
+ }
186
+
187
+ return {
188
+ buildReplay,
189
+ renderReplayTerminal,
190
+ };
191
+ };
@@ -20,6 +20,7 @@ module.exports = function createScan(deps) {
20
20
  getCodexSessionFiles,
21
21
  compactUsageSummary,
22
22
  formatTokenCount,
23
+ color,
23
24
  } = deps;
24
25
 
25
26
  const {
@@ -0,0 +1,185 @@
1
+ module.exports = function createTimeline(deps) {
2
+ const {
3
+ fs,
4
+ path,
5
+ GENERATED_ARTIFACT_PATTERNS,
6
+ NPX_COMMAND,
7
+ formatTokenCount,
8
+ getUsageSummary,
9
+ readIfText,
10
+ } = deps;
11
+
12
+ const {
13
+ getActionableRepeatedPaths,
14
+ summarizeGeneratedArtifacts,
15
+ } = require("./usage-log-utils")({
16
+ fs,
17
+ path,
18
+ GENERATED_ARTIFACT_PATTERNS,
19
+ readIfText,
20
+ });
21
+
22
+ function bump(map, key, count = 1, sessionId = null) {
23
+ if (!key) return;
24
+ const item = map.get(key) || { value: key, count: 0, sessions: new Set() };
25
+ item.count += Number(count || 0);
26
+ if (sessionId) item.sessions.add(sessionId);
27
+ map.set(key, item);
28
+ }
29
+
30
+ function sortedMap(map, limit = 10) {
31
+ return Array.from(map.values())
32
+ .map((item) => ({
33
+ value: item.value,
34
+ count: item.count,
35
+ sessions: item.sessions.size,
36
+ }))
37
+ .sort((a, b) => b.sessions - a.sessions || b.count - a.count)
38
+ .slice(0, limit);
39
+ }
40
+
41
+ function buildMultiSessionTimeline(options = {}) {
42
+ const cwd = options.cwd || process.cwd();
43
+ const limit = options.limit || 20;
44
+ const tool = options.tool || "all";
45
+ const usage = getUsageSummary({ cwd, limit, tool });
46
+ const sessions = usage.sessions || [];
47
+ const artifactMap = new Map();
48
+ const fileMap = new Map();
49
+ const commandMap = new Map();
50
+ const events = [];
51
+ let highRiskSessions = 0;
52
+ let toolFloodSessions = 0;
53
+ let loopSessions = 0;
54
+ let totalToolOutputTokens = 0;
55
+
56
+ for (const session of sessions) {
57
+ const sessionId = session.sessionId || session.updatedAt || "unknown";
58
+ if (session.contextRisk === "High") highRiskSessions += 1;
59
+ if ((session.estimatedToolTokens || 0) >= 75000) toolFloodSessions += 1;
60
+ if (session.loopSuspicion) loopSessions += 1;
61
+ totalToolOutputTokens += Number(session.estimatedToolTokens || 0);
62
+
63
+ for (const group of summarizeGeneratedArtifacts(session.generatedArtifacts || [], 10)) {
64
+ bump(artifactMap, group.type, group.count, sessionId);
65
+ }
66
+ for (const item of getActionableRepeatedPaths(session, 10)) {
67
+ bump(fileMap, item.value, item.count, sessionId);
68
+ }
69
+ for (const item of session.repeatedCommands || []) {
70
+ bump(commandMap, item.value, item.count, sessionId);
71
+ }
72
+
73
+ if (session.contextRisk === "High") {
74
+ events.push({
75
+ timestamp: session.updatedAt || null,
76
+ sessionId,
77
+ tool: session.tool,
78
+ type: "high-context-pressure",
79
+ detail: `${formatTokenCount(session.displayTokens || session.contextTokens)} tokens`,
80
+ });
81
+ }
82
+ if ((session.estimatedToolTokens || 0) >= 75000) {
83
+ events.push({
84
+ timestamp: session.updatedAt || null,
85
+ sessionId,
86
+ tool: session.tool,
87
+ type: "tool-output-flood",
88
+ detail: `${formatTokenCount(session.estimatedToolTokens)} tool/output tokens`,
89
+ });
90
+ }
91
+ if (session.loopSuspicion) {
92
+ events.push({
93
+ timestamp: session.updatedAt || null,
94
+ sessionId,
95
+ tool: session.tool,
96
+ type: "possible-loop",
97
+ detail: `${session.repeatedCommands?.[0]?.value || "command"} repeated`,
98
+ });
99
+ }
100
+ }
101
+
102
+ const repeatedArtifacts = sortedMap(artifactMap, 10);
103
+ const repeatedFiles = sortedMap(fileMap, 10);
104
+ const repeatedCommands = sortedMap(commandMap, 10);
105
+ const recommendations = [];
106
+ if (repeatedArtifacts.length) recommendations.push(`Add/verify ignore coverage for ${repeatedArtifacts.slice(0, 3).map((item) => item.value).join(", ")}.`);
107
+ if (toolFloodSessions) recommendations.push(`Route noisy commands through ${NPX_COMMAND} shield -- <command>.`);
108
+ if (repeatedFiles.length) recommendations.push(`Use ${NPX_COMMAND} firewall <task> to start sessions with narrower read boundaries.`);
109
+ if (highRiskSessions >= 2) recommendations.push("Split future work earlier; multiple recent sessions reached high context pressure.");
110
+ if (loopSessions) recommendations.push(`Use ${NPX_COMMAND} watch --rescue when loops appear.`);
111
+ if (!recommendations.length) recommendations.push("No recurring multi-session waste pattern detected yet.");
112
+
113
+ return {
114
+ schemaVersion: 1,
115
+ command: "timeline",
116
+ generatedAt: new Date().toISOString(),
117
+ scannedPath: cwd,
118
+ tool,
119
+ sessionsAnalyzed: sessions.length,
120
+ confidence: usage.confidence,
121
+ sources: usage.sources || [],
122
+ summary: {
123
+ highRiskSessions,
124
+ toolFloodSessions,
125
+ loopSessions,
126
+ totalToolOutputTokens,
127
+ },
128
+ repeatedArtifacts,
129
+ repeatedFiles,
130
+ repeatedCommands,
131
+ events: events.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0)).slice(-50),
132
+ recommendations,
133
+ };
134
+ }
135
+
136
+ function renderMultiSessionTimelineTerminal(timeline) {
137
+ const lines = [];
138
+ lines.push("");
139
+ lines.push("Prismo Multi-Session Timeline");
140
+ lines.push("");
141
+ lines.push(`Sessions analyzed: ${timeline.sessionsAnalyzed}`);
142
+ lines.push(`Sources: ${timeline.sources.join(", ") || "none"}`);
143
+ lines.push(`High-risk sessions: ${timeline.summary.highRiskSessions}`);
144
+ lines.push(`Tool-output flood sessions: ${timeline.summary.toolFloodSessions}`);
145
+ lines.push(`Possible loop sessions: ${timeline.summary.loopSessions}`);
146
+ lines.push(`Total tool/output tokens: ${formatTokenCount(timeline.summary.totalToolOutputTokens)}`);
147
+ lines.push("");
148
+ lines.push("Recurring Artifacts");
149
+ if (timeline.repeatedArtifacts.length) {
150
+ timeline.repeatedArtifacts.slice(0, 5).forEach((item) => lines.push(`- ${item.value}: ${item.count} mentions across ${item.sessions} session(s)`));
151
+ } else {
152
+ lines.push("- None detected.");
153
+ }
154
+ lines.push("");
155
+ lines.push("Recurring Repeated Files");
156
+ if (timeline.repeatedFiles.length) {
157
+ timeline.repeatedFiles.slice(0, 5).forEach((item) => lines.push(`- ${item.value}: ${item.count} mentions across ${item.sessions} session(s)`));
158
+ } else {
159
+ lines.push("- None detected.");
160
+ }
161
+ lines.push("");
162
+ lines.push("Recurring Commands");
163
+ if (timeline.repeatedCommands.length) {
164
+ timeline.repeatedCommands.slice(0, 5).forEach((item) => lines.push(`- ${item.value}: ${item.count} mentions across ${item.sessions} session(s)`));
165
+ } else {
166
+ lines.push("- None detected.");
167
+ }
168
+ lines.push("");
169
+ lines.push("Recent Events");
170
+ if (timeline.events.length) {
171
+ timeline.events.slice(-8).forEach((event) => lines.push(`- ${event.timestamp || "unknown"} ${event.type}: ${event.detail}`));
172
+ } else {
173
+ lines.push("- No high-signal events detected.");
174
+ }
175
+ lines.push("");
176
+ lines.push("Next");
177
+ timeline.recommendations.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
178
+ return lines.join("\n");
179
+ }
180
+
181
+ return {
182
+ buildMultiSessionTimeline,
183
+ renderMultiSessionTimelineTerminal,
184
+ };
185
+ };
@@ -571,6 +571,7 @@ async function watchUsage(options = {}) {
571
571
  analyzeCursorSessions,
572
572
  buildCursorDiagnosis,
573
573
  buildCursorSessionTimeline,
574
+ buildMultiAgentView,
574
575
  calculateClaudeCost,
575
576
  compactUsageSummary,
576
577
  formatMoney,