@mrclrchtr/supi-context 1.13.0 → 1.14.1
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 +4 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +10 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +3 -2
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +98 -17
- package/node_modules/@mrclrchtr/supi-core/src/report.ts +121 -0
- package/package.json +2 -2
- package/src/format-helpers.ts +85 -0
- package/src/format-sections.ts +326 -0
- package/src/format-summary.ts +255 -0
- package/src/format.ts +19 -654
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import {
|
|
4
|
+
formatDimLine,
|
|
5
|
+
formatKeyValueLine,
|
|
6
|
+
formatOverflowHint,
|
|
7
|
+
formatSectionHeader,
|
|
8
|
+
wrapReportText,
|
|
9
|
+
} from "@mrclrchtr/supi-core/report";
|
|
10
|
+
import type { ContextAnalysis } from "./analysis.ts";
|
|
11
|
+
import { padLeft, padRight, pct, sum } from "./format-helpers.ts";
|
|
12
|
+
import { formatTokens, pluralize } from "./utils.ts";
|
|
13
|
+
|
|
14
|
+
interface FileSectionOptions {
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle: string;
|
|
17
|
+
files: Array<{ path: string; tokens: number; lines: number; extra?: string }>;
|
|
18
|
+
total: number;
|
|
19
|
+
theme: Theme;
|
|
20
|
+
width: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function renderFileSection(options: FileSectionOptions): string[] {
|
|
24
|
+
const { title, subtitle, files, total, theme, width } = options;
|
|
25
|
+
if (files.length === 0) return [];
|
|
26
|
+
|
|
27
|
+
const sorted = [...files].sort((a, b) => b.tokens - a.tokens);
|
|
28
|
+
const totalTokens = sum(sorted.map((file) => file.tokens));
|
|
29
|
+
const header = formatSectionHeader(
|
|
30
|
+
title,
|
|
31
|
+
`${pluralize(sorted.length, "file", "files")}, ${formatTokens(totalTokens)} tokens${subtitle ? ` · ${subtitle}` : ""}`,
|
|
32
|
+
theme,
|
|
33
|
+
width,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const tokenWidth = 8;
|
|
37
|
+
const lineWidth = 10;
|
|
38
|
+
const pctWidth = 7;
|
|
39
|
+
const extraWidth = sorted.some((file) => file.extra) ? 10 : 0;
|
|
40
|
+
const reserved =
|
|
41
|
+
2 + 2 + lineWidth + 2 + tokenWidth + 2 + pctWidth + (extraWidth ? 2 + extraWidth : 0);
|
|
42
|
+
const pathWidth = Math.max(16, width - reserved);
|
|
43
|
+
|
|
44
|
+
const lines = [header];
|
|
45
|
+
for (const file of sorted) {
|
|
46
|
+
const path = padRight(truncateToWidth(file.path, pathWidth), pathWidth);
|
|
47
|
+
const lineCol = padLeft(`${file.lines} lines`, lineWidth);
|
|
48
|
+
const tokenCol = padLeft(formatTokens(file.tokens), tokenWidth);
|
|
49
|
+
const pctCol = padLeft(pct(file.tokens, total), pctWidth);
|
|
50
|
+
const extra = extraWidth ? ` ${theme.fg("dim", padRight(file.extra ?? "", extraWidth))}` : "";
|
|
51
|
+
lines.push(
|
|
52
|
+
` ${theme.fg("text", path)} ${theme.fg("dim", lineCol)} ${theme.fg("dim", tokenCol)}${extra} ${theme.fg("dim", pctCol)}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function renderInstructionFilesSection(
|
|
60
|
+
analysis: ContextAnalysis,
|
|
61
|
+
theme: Theme,
|
|
62
|
+
width: number,
|
|
63
|
+
): string[] {
|
|
64
|
+
return renderFileSection({
|
|
65
|
+
title: "Instruction Files (AGENTS.md / CLAUDE.md)",
|
|
66
|
+
subtitle: "share of system prompt",
|
|
67
|
+
files: analysis.systemPromptBreakdown.instructionFiles.map((file) => ({
|
|
68
|
+
path: file.path,
|
|
69
|
+
tokens: file.tokens,
|
|
70
|
+
lines: file.lines,
|
|
71
|
+
extra: file.origin,
|
|
72
|
+
})),
|
|
73
|
+
total: analysis.categories.systemPrompt,
|
|
74
|
+
theme,
|
|
75
|
+
width,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function renderContextFilesSection(
|
|
80
|
+
analysis: ContextAnalysis,
|
|
81
|
+
theme: Theme,
|
|
82
|
+
width: number,
|
|
83
|
+
): string[] {
|
|
84
|
+
return renderFileSection({
|
|
85
|
+
title: "Context Files (system prompt)",
|
|
86
|
+
subtitle: "share of system prompt",
|
|
87
|
+
files: analysis.systemPromptBreakdown.contextFiles.map((file) => ({
|
|
88
|
+
path: file.path,
|
|
89
|
+
tokens: file.tokens,
|
|
90
|
+
lines: file.lines,
|
|
91
|
+
})),
|
|
92
|
+
total: analysis.categories.systemPrompt,
|
|
93
|
+
theme,
|
|
94
|
+
width,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renderInjectedFilesSection(
|
|
99
|
+
analysis: ContextAnalysis,
|
|
100
|
+
theme: Theme,
|
|
101
|
+
width: number,
|
|
102
|
+
): string[] {
|
|
103
|
+
return renderFileSection({
|
|
104
|
+
title: "Context Files (injected · supi-claude-md)",
|
|
105
|
+
subtitle: "share of full context",
|
|
106
|
+
files: analysis.injectedFiles.map((file) => ({
|
|
107
|
+
path: file.file,
|
|
108
|
+
tokens: file.tokens,
|
|
109
|
+
lines: file.lines,
|
|
110
|
+
extra: `turn ${file.turn}`,
|
|
111
|
+
})),
|
|
112
|
+
total: analysis.totalTokens ?? 0,
|
|
113
|
+
theme,
|
|
114
|
+
width,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function renderSkillsSection(
|
|
119
|
+
analysis: ContextAnalysis,
|
|
120
|
+
theme: Theme,
|
|
121
|
+
width: number,
|
|
122
|
+
): string[] {
|
|
123
|
+
const lines: string[] = [];
|
|
124
|
+
const total = sum(analysis.skills.map((skill) => skill.tokens));
|
|
125
|
+
lines.push(
|
|
126
|
+
formatSectionHeader(
|
|
127
|
+
`Skills (${analysis.skills.length})`,
|
|
128
|
+
total > 0 ? `${formatTokens(total)} tokens` : null,
|
|
129
|
+
theme,
|
|
130
|
+
width,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (analysis.skills.length === 0) {
|
|
135
|
+
lines.push(formatDimLine("Send a message to see skill details", theme, width, 2));
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const skillNameWidth =
|
|
140
|
+
analysis.skills.length > 0 ? Math.max(...analysis.skills.map((s) => s.name.length)) : 0;
|
|
141
|
+
for (const skill of analysis.skills) {
|
|
142
|
+
lines.push(
|
|
143
|
+
truncateToWidth(
|
|
144
|
+
` ${theme.fg("text", padRight(skill.name, skillNameWidth))} ${theme.fg("dim", padLeft(formatTokens(skill.tokens), 8))}`,
|
|
145
|
+
width,
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return lines;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderSourceSummaryBar(gs: Array<{ source: string; bulletCount: number }>): string | null {
|
|
154
|
+
if (gs.length === 0) return null;
|
|
155
|
+
const parts: string[] = [];
|
|
156
|
+
for (const s of gs) {
|
|
157
|
+
const label =
|
|
158
|
+
s.source === "default" ? "default" : s.source === "other" ? "extensions" : s.source;
|
|
159
|
+
parts.push(`${pluralize(s.bulletCount, "bullet", "bullets")} from ${label}`);
|
|
160
|
+
}
|
|
161
|
+
return parts.join(" · ");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderBulletLines(
|
|
165
|
+
bullets: string[],
|
|
166
|
+
full: boolean,
|
|
167
|
+
theme: Theme,
|
|
168
|
+
width: number,
|
|
169
|
+
): string[] {
|
|
170
|
+
const previewLimit = full ? bullets.length : Math.min(6, bullets.length);
|
|
171
|
+
const lines: string[] = [];
|
|
172
|
+
for (let i = 0; i < previewLimit; i += 1) {
|
|
173
|
+
const rawText = bullets[i] ?? "";
|
|
174
|
+
const previewText = full || rawText.length <= 90 ? rawText : `${rawText.slice(0, 90)}…`;
|
|
175
|
+
const bullet = `${theme.fg("dim", "•")} ${theme.fg("text", previewText)}`;
|
|
176
|
+
if (full) {
|
|
177
|
+
lines.push(...wrapReportText(bullet, width, { indent: " " }));
|
|
178
|
+
} else {
|
|
179
|
+
lines.push(truncateToWidth(` ${bullet}`, width));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!full && bullets.length > previewLimit) {
|
|
183
|
+
lines.push(
|
|
184
|
+
formatOverflowHint(bullets.length - previewLimit, theme, width, {
|
|
185
|
+
hint: "run /supi-context full",
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return lines;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function renderGuidelinesSection(
|
|
193
|
+
analysis: ContextAnalysis,
|
|
194
|
+
theme: Theme,
|
|
195
|
+
width: number,
|
|
196
|
+
): string[] {
|
|
197
|
+
const sourceSummary = renderSourceSummaryBar(analysis.guidelineSources);
|
|
198
|
+
|
|
199
|
+
const lines = [
|
|
200
|
+
formatSectionHeader(
|
|
201
|
+
`Guidelines (${pluralize(analysis.guidelineBullets.length, "bullet", "bullets")})`,
|
|
202
|
+
`${formatTokens(analysis.guidelines)} tokens`,
|
|
203
|
+
theme,
|
|
204
|
+
width,
|
|
205
|
+
),
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
if (sourceSummary) {
|
|
209
|
+
lines.push(formatDimLine(sourceSummary, theme, width, 2));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const bullets = analysis.guidelineBullets;
|
|
213
|
+
if (bullets.length === 0) {
|
|
214
|
+
return lines;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
lines.push(...renderBulletLines(bullets, analysis.full, theme, width));
|
|
218
|
+
return lines;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function renderToolDefinitionsSection(
|
|
222
|
+
analysis: ContextAnalysis,
|
|
223
|
+
theme: Theme,
|
|
224
|
+
width: number,
|
|
225
|
+
): string[] {
|
|
226
|
+
const tools = [...analysis.toolDefinitions.tools].sort((a, b) => b.tokens - a.tokens);
|
|
227
|
+
if (tools.length === 0) return [];
|
|
228
|
+
|
|
229
|
+
const hasSnippetDetails = analysis.toolSnippetDetails.length > 0;
|
|
230
|
+
|
|
231
|
+
const lines: string[] = [];
|
|
232
|
+
lines.push(
|
|
233
|
+
formatSectionHeader(
|
|
234
|
+
`Tool Definitions (${tools.length} active)`,
|
|
235
|
+
`${formatTokens(analysis.toolDefinitions.tokens)} def tokens${hasSnippetDetails ? ` + ${formatTokens(sum(analysis.toolSnippetDetails.map((s) => s.tokens)))} snippet` : ""}`,
|
|
236
|
+
theme,
|
|
237
|
+
width,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const previewLimit = analysis.full ? tools.length : Math.min(5, tools.length);
|
|
242
|
+
const nameWidth = Math.max(12, Math.min(18, Math.max(...tools.map((tool) => tool.name.length))));
|
|
243
|
+
const defTokenWidth = 8;
|
|
244
|
+
const snippetTokenWidth = hasSnippetDetails ? 10 : 0;
|
|
245
|
+
const reserved =
|
|
246
|
+
2 + nameWidth + 2 + defTokenWidth + 2 + (snippetTokenWidth ? snippetTokenWidth + 2 : 0);
|
|
247
|
+
const descWidth = Math.max(12, width - reserved);
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < previewLimit; i += 1) {
|
|
250
|
+
const tool = tools[i];
|
|
251
|
+
const name = padRight(tool.name, nameWidth);
|
|
252
|
+
const previewDescription =
|
|
253
|
+
analysis.full || tool.description.length <= 50
|
|
254
|
+
? tool.description
|
|
255
|
+
: `${tool.description.slice(0, 50)}…`;
|
|
256
|
+
const description = truncateToWidth(previewDescription, descWidth);
|
|
257
|
+
const defTokens = padLeft(formatTokens(tool.tokens), defTokenWidth);
|
|
258
|
+
const snippet = analysis.toolSnippetDetails.find((s) => s.name === tool.name);
|
|
259
|
+
const snippetCol = snippet
|
|
260
|
+
? ` ${theme.fg("dim", padLeft(`+${formatTokens(snippet.tokens)}snip`, snippetTokenWidth))}`
|
|
261
|
+
: "";
|
|
262
|
+
lines.push(
|
|
263
|
+
truncateToWidth(
|
|
264
|
+
` ${theme.fg("text", name)} ${theme.fg("dim", description)} ${theme.fg("dim", defTokens)}${snippetCol}`,
|
|
265
|
+
width,
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!analysis.full && tools.length > previewLimit) {
|
|
271
|
+
lines.push(
|
|
272
|
+
formatOverflowHint(tools.length - previewLimit, theme, width, {
|
|
273
|
+
hint: "run /supi-context full",
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (hasSnippetDetails) {
|
|
279
|
+
const snippetTotal = sum(analysis.toolSnippetDetails.map((s) => s.tokens));
|
|
280
|
+
lines.push(
|
|
281
|
+
formatDimLine(`→ total tool snippet tokens: ${formatTokens(snippetTotal)}`, theme, width, 2),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return lines;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function renderCompactionNote(
|
|
289
|
+
analysis: ContextAnalysis,
|
|
290
|
+
theme: Theme,
|
|
291
|
+
width: number,
|
|
292
|
+
): string[] {
|
|
293
|
+
if (!analysis.compaction) return [];
|
|
294
|
+
return [
|
|
295
|
+
formatDimLine(
|
|
296
|
+
`↳ ${pluralize(analysis.compaction.summarizedTurns, "older turn", "older turns")} summarized (compaction)`,
|
|
297
|
+
theme,
|
|
298
|
+
width,
|
|
299
|
+
),
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function renderProviderSections(
|
|
304
|
+
analysis: ContextAnalysis,
|
|
305
|
+
theme: Theme,
|
|
306
|
+
width: number,
|
|
307
|
+
): string[] {
|
|
308
|
+
if (analysis.providerSections.length === 0) return [];
|
|
309
|
+
|
|
310
|
+
const lines: string[] = [];
|
|
311
|
+
for (const section of analysis.providerSections) {
|
|
312
|
+
lines.push(formatSectionHeader(section.label, null, theme, width));
|
|
313
|
+
for (const [key, value] of Object.entries(section.data)) {
|
|
314
|
+
lines.push(
|
|
315
|
+
formatKeyValueLine({
|
|
316
|
+
label: key,
|
|
317
|
+
value: String(value),
|
|
318
|
+
theme,
|
|
319
|
+
width,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return lines;
|
|
326
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { formatSectionHeader, wrapReportText } from "@mrclrchtr/supi-core/report";
|
|
4
|
+
import type { ContextAnalysis } from "./analysis.ts";
|
|
5
|
+
import {
|
|
6
|
+
allocateBlocks,
|
|
7
|
+
CATEGORY_COLORS,
|
|
8
|
+
CATEGORY_LABELS,
|
|
9
|
+
CATEGORY_ORDER,
|
|
10
|
+
healthColor,
|
|
11
|
+
padLeft,
|
|
12
|
+
padRight,
|
|
13
|
+
pct,
|
|
14
|
+
sum,
|
|
15
|
+
} from "./format-helpers.ts";
|
|
16
|
+
import { formatTokens } from "./utils.ts";
|
|
17
|
+
|
|
18
|
+
export function renderSummary(analysis: ContextAnalysis, theme: Theme, width: number): string[] {
|
|
19
|
+
const used = analysis.totalTokens ?? 0;
|
|
20
|
+
const health = theme.fg(healthColor(analysis), "●");
|
|
21
|
+
const usage =
|
|
22
|
+
analysis.contextWindow > 0
|
|
23
|
+
? `${formatTokens(used)} / ${formatTokens(analysis.contextWindow)} tokens (${pct(used, analysis.contextWindow)})`
|
|
24
|
+
: `${formatTokens(used)} tokens`;
|
|
25
|
+
|
|
26
|
+
const lines = [
|
|
27
|
+
truncateToWidth(
|
|
28
|
+
`${health} ${theme.fg("text", analysis.modelName)}${theme.fg("dim", ` · ${usage}`)}`,
|
|
29
|
+
width,
|
|
30
|
+
),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
if (analysis.approximationNote) {
|
|
34
|
+
lines.push(...wrapReportText(theme.fg("warning", analysis.approximationNote), width));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function renderUsageBar(analysis: ContextAnalysis, theme: Theme, width: number): string[] {
|
|
41
|
+
if (analysis.contextWindow <= 0) {
|
|
42
|
+
return [theme.fg("dim", "No model selected — usage bar unavailable")];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const percentLabel = pct(analysis.totalTokens ?? 0, analysis.contextWindow);
|
|
46
|
+
const barWidth = Math.max(12, Math.min(48, width - visibleWidth(percentLabel) - 3));
|
|
47
|
+
const values = [
|
|
48
|
+
...CATEGORY_ORDER.map((key) => analysis.categories[key]),
|
|
49
|
+
analysis.categories.autocompactBuffer,
|
|
50
|
+
analysis.categories.freeSpace,
|
|
51
|
+
];
|
|
52
|
+
const counts = allocateBlocks(values, barWidth);
|
|
53
|
+
|
|
54
|
+
const segments = [
|
|
55
|
+
...CATEGORY_ORDER.map((key, index) => ({
|
|
56
|
+
color: CATEGORY_COLORS[key],
|
|
57
|
+
block: "█",
|
|
58
|
+
count: counts[index] ?? 0,
|
|
59
|
+
})),
|
|
60
|
+
{
|
|
61
|
+
color: "warning" as const,
|
|
62
|
+
block: "▒",
|
|
63
|
+
count: counts[CATEGORY_ORDER.length] ?? 0,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
color: "dim" as const,
|
|
67
|
+
block: "░",
|
|
68
|
+
count: counts[CATEGORY_ORDER.length + 1] ?? 0,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const bar = segments
|
|
73
|
+
.map((segment) =>
|
|
74
|
+
segment.count > 0 ? theme.fg(segment.color, segment.block.repeat(segment.count)) : "",
|
|
75
|
+
)
|
|
76
|
+
.join("");
|
|
77
|
+
|
|
78
|
+
const barLine = truncateToWidth(
|
|
79
|
+
`${theme.fg("dim", "[")}${bar}${theme.fg("dim", "]")} ${theme.fg("text", percentLabel)}`,
|
|
80
|
+
width,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const legendParts: string[] = [];
|
|
84
|
+
for (const key of CATEGORY_ORDER) {
|
|
85
|
+
if (analysis.categories[key] <= 0) continue;
|
|
86
|
+
legendParts.push(`${theme.fg(CATEGORY_COLORS[key], "●")} ${CATEGORY_LABELS[key]}`);
|
|
87
|
+
}
|
|
88
|
+
if (analysis.categories.autocompactBuffer > 0) {
|
|
89
|
+
legendParts.push(`${theme.fg("warning", "▒")} Autocompact buffer`);
|
|
90
|
+
}
|
|
91
|
+
if (analysis.categories.freeSpace > 0) {
|
|
92
|
+
legendParts.push(`${theme.fg("dim", "░")} Free space`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [barLine, ...wrapReportText(legendParts.join(theme.fg("dim", " • ")), width)];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renderCategoryBreakdown(
|
|
99
|
+
analysis: ContextAnalysis,
|
|
100
|
+
theme: Theme,
|
|
101
|
+
width: number,
|
|
102
|
+
): string[] {
|
|
103
|
+
const lines: string[] = [];
|
|
104
|
+
lines.push(formatSectionHeader("Usage by category", null, theme, width));
|
|
105
|
+
|
|
106
|
+
const rows: Array<{
|
|
107
|
+
label: string;
|
|
108
|
+
color: Parameters<Theme["fg"]>[0];
|
|
109
|
+
tokens: number;
|
|
110
|
+
}> = [
|
|
111
|
+
...CATEGORY_ORDER.map((key) => ({
|
|
112
|
+
label: CATEGORY_LABELS[key],
|
|
113
|
+
color: CATEGORY_COLORS[key],
|
|
114
|
+
tokens: analysis.categories[key],
|
|
115
|
+
})),
|
|
116
|
+
{
|
|
117
|
+
label: "Autocompact buffer",
|
|
118
|
+
color: "warning",
|
|
119
|
+
tokens: analysis.categories.autocompactBuffer,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
label: "Free space",
|
|
123
|
+
color: "dim",
|
|
124
|
+
tokens: analysis.categories.freeSpace,
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const labelWidth = Math.max(18, Math.min(22, width - 22));
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
if (row.tokens <= 0 && row.label !== "Free space") continue;
|
|
131
|
+
const bullet = theme.fg(row.color, "●");
|
|
132
|
+
const label = padRight(row.label, labelWidth);
|
|
133
|
+
const tokens = padLeft(formatTokens(row.tokens), 8);
|
|
134
|
+
const percentage = padLeft(pct(row.tokens, analysis.contextWindow), 7);
|
|
135
|
+
lines.push(truncateToWidth(` ${bullet} ${label} ${tokens} ${percentage}`, width));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderCompositionGuidelineSubRows(
|
|
142
|
+
sources: Array<{ source: string; tokens: number }>,
|
|
143
|
+
opts: { subLabelWidth: number; total: number; theme: Theme; width: number },
|
|
144
|
+
): string[] {
|
|
145
|
+
const lines: string[] = [];
|
|
146
|
+
|
|
147
|
+
for (const item of sources) {
|
|
148
|
+
const label =
|
|
149
|
+
item.source === "default" ? "default" : item.source === "other" ? "extensions" : item.source;
|
|
150
|
+
lines.push(
|
|
151
|
+
truncateToWidth(
|
|
152
|
+
` ${opts.theme.fg("dim", padRight(label, opts.subLabelWidth))} ${padLeft(formatTokens(item.tokens), 8)} ${padLeft(pct(item.tokens, opts.total), 7)}`,
|
|
153
|
+
opts.width,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return lines;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderCompositionSnippetSubRows(
|
|
161
|
+
details: Array<{ name: string; tokens: number }>,
|
|
162
|
+
subLabelWidth: number,
|
|
163
|
+
theme: Theme,
|
|
164
|
+
width: number,
|
|
165
|
+
): string[] {
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
for (const item of details) {
|
|
168
|
+
lines.push(
|
|
169
|
+
truncateToWidth(
|
|
170
|
+
` ${theme.fg("dim", padRight(item.name, subLabelWidth))} ${padLeft(formatTokens(item.tokens), 8)}`,
|
|
171
|
+
width,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return lines;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function renderSystemPromptComposition(
|
|
179
|
+
analysis: ContextAnalysis,
|
|
180
|
+
theme: Theme,
|
|
181
|
+
width: number,
|
|
182
|
+
): string[] {
|
|
183
|
+
const breakdown = analysis.systemPromptBreakdown;
|
|
184
|
+
const instructionFileTokens = sum(breakdown.instructionFiles.map((f) => f.tokens));
|
|
185
|
+
const contextFileTokens = sum(breakdown.contextFiles.map((f) => f.tokens));
|
|
186
|
+
const skillTokens = sum(breakdown.skills.map((s) => s.tokens));
|
|
187
|
+
|
|
188
|
+
// Use sum of all breakdown components as the denominator so percentages
|
|
189
|
+
// stay internally consistent even when the system prompt token count has
|
|
190
|
+
// been scaled to actual model usage.
|
|
191
|
+
const total =
|
|
192
|
+
breakdown.base +
|
|
193
|
+
instructionFileTokens +
|
|
194
|
+
contextFileTokens +
|
|
195
|
+
skillTokens +
|
|
196
|
+
breakdown.guidelines +
|
|
197
|
+
breakdown.toolSnippets +
|
|
198
|
+
breakdown.appendText;
|
|
199
|
+
|
|
200
|
+
const lines: string[] = [];
|
|
201
|
+
lines.push(
|
|
202
|
+
formatSectionHeader(
|
|
203
|
+
"System prompt composition",
|
|
204
|
+
total > 0 ? `${formatTokens(total)} tokens` : null,
|
|
205
|
+
theme,
|
|
206
|
+
width,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const labelWidth = Math.max(18, Math.min(22, width - 22));
|
|
211
|
+
const subLabelWidth = labelWidth;
|
|
212
|
+
|
|
213
|
+
const rows = [
|
|
214
|
+
{ label: "Base", color: "accent" as const, tokens: breakdown.base },
|
|
215
|
+
{ label: "Instruction files", color: "text" as const, tokens: instructionFileTokens },
|
|
216
|
+
{ label: "Context files", color: "text" as const, tokens: contextFileTokens },
|
|
217
|
+
{ label: "Skills", color: "text" as const, tokens: skillTokens },
|
|
218
|
+
{ label: "Guidelines", color: "text" as const, tokens: breakdown.guidelines },
|
|
219
|
+
{ label: "Tool snippets", color: "text" as const, tokens: breakdown.toolSnippets },
|
|
220
|
+
{ label: "Append text", color: "text" as const, tokens: breakdown.appendText },
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
if (row.tokens <= 0) continue;
|
|
225
|
+
const bullet = theme.fg(row.color, "●");
|
|
226
|
+
const label = padRight(row.label, labelWidth);
|
|
227
|
+
const tokens = padLeft(formatTokens(row.tokens), 8);
|
|
228
|
+
const percentage = padLeft(pct(row.tokens, total), 7);
|
|
229
|
+
lines.push(truncateToWidth(` ${bullet} ${label} ${tokens} ${percentage}`, width));
|
|
230
|
+
|
|
231
|
+
if (row.label === "Guidelines" && breakdown.guidelineSources.length > 0) {
|
|
232
|
+
lines.push(
|
|
233
|
+
...renderCompositionGuidelineSubRows(breakdown.guidelineSources, {
|
|
234
|
+
subLabelWidth,
|
|
235
|
+
total,
|
|
236
|
+
theme,
|
|
237
|
+
width,
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (row.label === "Tool snippets" && breakdown.toolSnippetDetails.length > 0) {
|
|
243
|
+
lines.push(
|
|
244
|
+
...renderCompositionSnippetSubRows(
|
|
245
|
+
breakdown.toolSnippetDetails,
|
|
246
|
+
subLabelWidth,
|
|
247
|
+
theme,
|
|
248
|
+
width,
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return lines;
|
|
255
|
+
}
|