pi-soly 1.7.0 โ 1.8.0
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/commands.ts +13 -1
- package/core.ts +121 -0
- package/package.json +1 -1
package/commands.ts
CHANGED
|
@@ -21,9 +21,11 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
21
21
|
import {
|
|
22
22
|
analyzeRules,
|
|
23
23
|
buildProgressBar,
|
|
24
|
+
buildRulesContextStats,
|
|
24
25
|
CONTEXT_WINDOW_TOKENS,
|
|
25
26
|
extractFilePathsFromPrompt,
|
|
26
27
|
formatAnalyticsFull,
|
|
28
|
+
formatRulesContextStats,
|
|
27
29
|
formatTok,
|
|
28
30
|
readIfExists,
|
|
29
31
|
solyDirFor,
|
|
@@ -70,7 +72,7 @@ export function registerCommands(pi: ExtensionAPI, deps: CommandsDeps): void {
|
|
|
70
72
|
|
|
71
73
|
pi.registerCommand("rules", {
|
|
72
74
|
description:
|
|
73
|
-
"manage soly rules (list, show, analytics, reload, enable, disable)",
|
|
75
|
+
"manage soly rules (list, show, stats, analytics, reload, enable, disable)",
|
|
74
76
|
handler: async (args, ctx) => {
|
|
75
77
|
const ui: CommandUI = {
|
|
76
78
|
notify: (t, k) => ctx.ui.notify(t, k ?? "info"),
|
|
@@ -129,6 +131,16 @@ export function registerCommands(pi: ExtensionAPI, deps: CommandsDeps): void {
|
|
|
129
131
|
return;
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
if (sub === "stats") {
|
|
135
|
+
// Claude-memory-style breakdown: shows which rules are always-on
|
|
136
|
+
// (every turn) vs glob-matched (only when prompt has file paths).
|
|
137
|
+
// Surfaces context bloat and verifies rules will actually fire.
|
|
138
|
+
const rules = getRules();
|
|
139
|
+
const stats = buildRulesContextStats(rules, CONTEXT_WINDOW_TOKENS);
|
|
140
|
+
ui.notify(formatRulesContextStats(stats), "info");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
if (sub === "show") {
|
|
133
145
|
if (!target) {
|
|
134
146
|
ui.notify("Usage: /rules show <path>", "error");
|
package/core.ts
CHANGED
|
@@ -993,6 +993,127 @@ export function formatAnalyticsFull(analytics: RuleAnalytics): string {
|
|
|
993
993
|
return lines.join("\n");
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
+
// =============================================================================
|
|
997
|
+
// Rules context stats โ Claude-memory-style breakdown
|
|
998
|
+
// =============================================================================
|
|
999
|
+
//
|
|
1000
|
+
// Shows which rules are "always-on" (loaded every turn) vs "glob-matched"
|
|
1001
|
+
// (loaded only when file paths in prompt match). Useful for spotting
|
|
1002
|
+
// context bloat and verifying rules will actually fire.
|
|
1003
|
+
|
|
1004
|
+
export interface RuleStat {
|
|
1005
|
+
relPath: string;
|
|
1006
|
+
tokens: number;
|
|
1007
|
+
sourceLabel: string;
|
|
1008
|
+
description?: string;
|
|
1009
|
+
always: boolean;
|
|
1010
|
+
globs: string[];
|
|
1011
|
+
loadedLastTurn: boolean;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export interface RulesContextStats {
|
|
1015
|
+
totalLoaded: number;
|
|
1016
|
+
totalTokens: number;
|
|
1017
|
+
contextBudgetPct: number;
|
|
1018
|
+
alwaysOn: RuleStat[];
|
|
1019
|
+
globMatched: RuleStat[];
|
|
1020
|
+
disabled: RuleStat[];
|
|
1021
|
+
lastTurn: {
|
|
1022
|
+
promptFiles: string[];
|
|
1023
|
+
matchedRulePaths: string[];
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export function buildRulesContextStats(
|
|
1028
|
+
rules: RuleFile[],
|
|
1029
|
+
contextWindowTokens: number,
|
|
1030
|
+
lastTurn?: { promptFiles: string[]; matchedRelPaths: string[] },
|
|
1031
|
+
): RulesContextStats {
|
|
1032
|
+
const enabled = rules.filter((r) => r.enabled);
|
|
1033
|
+
const disabled = rules.filter((r) => !r.enabled);
|
|
1034
|
+
const lastTurnMatched = new Set(lastTurn?.matchedRelPaths ?? []);
|
|
1035
|
+
const stat = (r: RuleFile, loadedLastTurn: boolean): RuleStat => ({
|
|
1036
|
+
relPath: r.relPath,
|
|
1037
|
+
tokens: estimateTokens(r.body),
|
|
1038
|
+
sourceLabel: r.sourceLabel,
|
|
1039
|
+
description: r.meta.description,
|
|
1040
|
+
always: r.meta.always === true,
|
|
1041
|
+
globs: r.meta.globs ?? [],
|
|
1042
|
+
loadedLastTurn,
|
|
1043
|
+
});
|
|
1044
|
+
const alwaysOn: RuleStat[] = [];
|
|
1045
|
+
const globMatched: RuleStat[] = [];
|
|
1046
|
+
for (const r of enabled) {
|
|
1047
|
+
const isAlways = r.meta.always === true;
|
|
1048
|
+
const isLoadedLastTurn = lastTurnMatched.has(r.relPath) || isAlways;
|
|
1049
|
+
const s = stat(r, isLoadedLastTurn);
|
|
1050
|
+
if (isAlways) alwaysOn.push(s);
|
|
1051
|
+
else globMatched.push(s);
|
|
1052
|
+
}
|
|
1053
|
+
const totalTokens = [...alwaysOn, ...globMatched].reduce((a, b) => a + b.tokens, 0);
|
|
1054
|
+
return {
|
|
1055
|
+
totalLoaded: enabled.length,
|
|
1056
|
+
totalTokens,
|
|
1057
|
+
contextBudgetPct:
|
|
1058
|
+
contextWindowTokens > 0 ? (totalTokens / contextWindowTokens) * 100 : 0,
|
|
1059
|
+
alwaysOn,
|
|
1060
|
+
globMatched,
|
|
1061
|
+
disabled: disabled.map((r) => stat(r, false)),
|
|
1062
|
+
lastTurn: {
|
|
1063
|
+
promptFiles: lastTurn?.promptFiles ?? [],
|
|
1064
|
+
matchedRulePaths: lastTurn?.matchedRelPaths ?? [],
|
|
1065
|
+
},
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export function formatRulesContextStats(stats: RulesContextStats): string {
|
|
1070
|
+
const lines: string[] = [];
|
|
1071
|
+
lines.push(`๐ Rules context stats`);
|
|
1072
|
+
lines.push(``);
|
|
1073
|
+
const ctxWindow = stats.contextBudgetPct > 0
|
|
1074
|
+
? Math.round(stats.totalTokens / (stats.contextBudgetPct / 100))
|
|
1075
|
+
: 0;
|
|
1076
|
+
lines.push(
|
|
1077
|
+
`Loaded: ${stats.totalLoaded} rule(s) ยท ${formatTok(stats.totalTokens)} ยท ${stats.contextBudgetPct.toFixed(1)}% of ${formatTok(ctxWindow)} context`,
|
|
1078
|
+
);
|
|
1079
|
+
lines.push(``);
|
|
1080
|
+
if (stats.alwaysOn.length > 0) {
|
|
1081
|
+
lines.push(`ALWAYS-ON (loaded every turn):`);
|
|
1082
|
+
for (const r of stats.alwaysOn) {
|
|
1083
|
+
const last = r.loadedLastTurn ? " โ" : "";
|
|
1084
|
+
const desc = r.description ? ` โ "${r.description}"` : "";
|
|
1085
|
+
lines.push(` โ ${r.relPath} ${formatTok(r.tokens)}${desc}${last}`);
|
|
1086
|
+
}
|
|
1087
|
+
lines.push(``);
|
|
1088
|
+
}
|
|
1089
|
+
if (stats.globMatched.length > 0) {
|
|
1090
|
+
lines.push(`GLOB-MATCHED (loaded when prompt file matches):`);
|
|
1091
|
+
for (const r of stats.globMatched) {
|
|
1092
|
+
const last = r.loadedLastTurn ? " โ" : "";
|
|
1093
|
+
const desc = r.description ? ` โ "${r.description}"` : "";
|
|
1094
|
+
const globs = r.globs.length > 0 ? ` [globs: ${r.globs.join(", ")}]` : "";
|
|
1095
|
+
lines.push(` โ ${r.relPath} ${formatTok(r.tokens)}${desc}${globs}${last}`);
|
|
1096
|
+
}
|
|
1097
|
+
lines.push(``);
|
|
1098
|
+
}
|
|
1099
|
+
if (stats.disabled.length > 0) {
|
|
1100
|
+
lines.push(`DISABLED:`);
|
|
1101
|
+
for (const r of stats.disabled) {
|
|
1102
|
+
lines.push(` โ ${r.relPath} ${formatTok(r.tokens)} โ disabled`);
|
|
1103
|
+
}
|
|
1104
|
+
lines.push(``);
|
|
1105
|
+
}
|
|
1106
|
+
if (stats.lastTurn.promptFiles.length > 0) {
|
|
1107
|
+
lines.push(`Last turn: ${stats.lastTurn.promptFiles.length} file path(s) in prompt`);
|
|
1108
|
+
for (const f of stats.lastTurn.promptFiles) {
|
|
1109
|
+
lines.push(` โ ${f}`);
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
lines.push(`Last turn: no file paths in prompt (only always-on rules loaded)`);
|
|
1113
|
+
}
|
|
1114
|
+
return lines.join("\n");
|
|
1115
|
+
}
|
|
1116
|
+
|
|
996
1117
|
// ============================================================================
|
|
997
1118
|
// @import resolver (markdown only)
|
|
998
1119
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-soly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Project management framework for pi-coding-agent. Workflows, planning, multi-question picker, agent switcher, live task list โ one npm install, zero config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|