@delt/claude-jev-advisor 0.3.0 → 0.4.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/README.md +27 -13
- package/dist/cli.js +298 -124
- package/dist/context-hook.js +30 -17
- package/dist/rm-hook.js +2 -0
- package/dist/statusline.js +28 -15
- package/mod/.claude-plugin/plugin.json +2 -2
- package/mod/hooks/register.js +138 -16
- package/package.json +3 -2
package/dist/context-hook.js
CHANGED
|
@@ -13,6 +13,7 @@ var DEFAULT_CONFIG = {
|
|
|
13
13
|
lang: "ko",
|
|
14
14
|
keyFile: null,
|
|
15
15
|
display: "mod",
|
|
16
|
+
shortcut: null,
|
|
16
17
|
context: { enabled: true, minTokens: 25e4, compactMinTokens: 25e4, redRemainingPct: 20, unitDoneYes: 0.6, phaseDoneYes: 0.6 },
|
|
17
18
|
rm: { enabled: true, jev: true, throwawayYes: 0.8, maxDirFiles: 50 }
|
|
18
19
|
};
|
|
@@ -40,6 +41,7 @@ function normalizeConfig(raw) {
|
|
|
40
41
|
lang: typeof r.lang === "string" && LANGS.includes(r.lang) ? r.lang : DEFAULT_CONFIG.lang,
|
|
41
42
|
keyFile: typeof r.keyFile === "string" && r.keyFile !== "" ? r.keyFile : null,
|
|
42
43
|
display: typeof r.display === "string" && DISPLAYS.includes(r.display) ? r.display : DEFAULT_CONFIG.display,
|
|
44
|
+
shortcut: typeof r.shortcut === "string" && r.shortcut !== "" ? r.shortcut : null,
|
|
43
45
|
context: mergeSection(DEFAULT_CONFIG.context, r.version === CONFIG_VERSION ? r.context : withoutLegacyDefaults(r.context)),
|
|
44
46
|
rm: mergeSection(DEFAULT_CONFIG.rm, r.rm)
|
|
45
47
|
};
|
|
@@ -63,18 +65,20 @@ function readConfig(home) {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// src/display/line.ts
|
|
66
|
-
var
|
|
68
|
+
var QUESTIONS = {
|
|
67
69
|
ko: {
|
|
68
|
-
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
69
|
-
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
70
|
-
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
70
|
+
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
71
|
+
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
72
|
+
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?"
|
|
71
73
|
},
|
|
72
74
|
en: {
|
|
73
|
-
clear: "Start fresh?
|
|
74
|
-
compact: "Wrap up what you have and continue?
|
|
75
|
-
compactLater: "When this work is done, wrap up and continue?
|
|
75
|
+
clear: "Start fresh?",
|
|
76
|
+
compact: "Wrap up what you have and continue?",
|
|
77
|
+
compactLater: "When this work is done, wrap up and continue?"
|
|
76
78
|
}
|
|
77
79
|
};
|
|
80
|
+
var COMMANDS = { clear: "/clear", compact: "/compact", compactLater: "/compact" };
|
|
81
|
+
var phrase = (lang, advice) => `${QUESTIONS[lang][advice]} ${COMMANDS[advice]}`;
|
|
78
82
|
function formatSize(tokens) {
|
|
79
83
|
return `${Math.round(tokens / 1e3)}k`;
|
|
80
84
|
}
|
|
@@ -86,20 +90,29 @@ function adviceKind(size, judgment, config) {
|
|
|
86
90
|
if (judgment.clear) return "clear";
|
|
87
91
|
return size >= config.context.compactMinTokens ? "compact" : null;
|
|
88
92
|
}
|
|
89
|
-
function
|
|
93
|
+
function increaseSuffix(increase) {
|
|
94
|
+
if (increase === null || increase === void 0) return "";
|
|
95
|
+
const k = Math.round(increase / 1e3);
|
|
96
|
+
return k > 0 ? ` +${k}k` : "";
|
|
97
|
+
}
|
|
98
|
+
function redRemaining(size, threshold, config) {
|
|
99
|
+
if (threshold === null || threshold <= 0 || size < threshold * (1 - config.context.redRemainingPct / 100)) return null;
|
|
100
|
+
return remainingPct(size, threshold);
|
|
101
|
+
}
|
|
102
|
+
function adviceLine({ size, threshold, judgment, config, increase }) {
|
|
90
103
|
if (size === null) return "";
|
|
91
|
-
const k = formatSize(size)
|
|
92
|
-
const phrases = PHRASES[config.lang];
|
|
104
|
+
const k = `${formatSize(size)}${increaseSuffix(increase)}`;
|
|
93
105
|
const counted = size >= config.context.minTokens ? judgment : null;
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
const red = redRemaining(size, threshold, config);
|
|
107
|
+
if (red !== null) {
|
|
108
|
+
const head = `\u{1F534} ${k} ${red}%`;
|
|
96
109
|
if (!counted) return head;
|
|
97
|
-
if (counted.phase === "working") return `${head} \xB7 ${
|
|
98
|
-
return `${head} \xB7 ${counted.clear ?
|
|
110
|
+
if (counted.phase === "working") return `${head} \xB7 ${phrase(config.lang, "compactLater")}`;
|
|
111
|
+
return `${head} \xB7 ${phrase(config.lang, counted.clear ? "clear" : "compact")}`;
|
|
99
112
|
}
|
|
100
113
|
if (!counted) return k;
|
|
101
114
|
const kind = adviceKind(size, counted, config);
|
|
102
|
-
return kind ? `\u{1F7E1} ${k} ${
|
|
115
|
+
return kind ? `\u{1F7E1} ${k} ${phrase(config.lang, kind)}` : `\u{1F7E2} ${k}`;
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
// src/jev.ts
|
|
@@ -225,7 +238,7 @@ function appendLog(home, now, record, secrets = []) {
|
|
|
225
238
|
|
|
226
239
|
// src/context/judge.ts
|
|
227
240
|
var REPLY_EDGE_CHARS = 1500;
|
|
228
|
-
var
|
|
241
|
+
var QUESTIONS2 = {
|
|
229
242
|
unit_done: {
|
|
230
243
|
type: "noul",
|
|
231
244
|
instructions: "Has Claude finished or handed back the work the user asked for in `recent_requests`, so that `last_assistant_reply` is a natural break before the user's next instruction?",
|
|
@@ -259,7 +272,7 @@ ${text.slice(-edge)}`;
|
|
|
259
272
|
function jevRequest(requests, lastReply) {
|
|
260
273
|
return {
|
|
261
274
|
state: { recent_requests: requests, last_assistant_reply: cutReply(lastReply) },
|
|
262
|
-
questions: { unit_done:
|
|
275
|
+
questions: { unit_done: QUESTIONS2.unit_done, phase_done: QUESTIONS2.phase_done }
|
|
263
276
|
};
|
|
264
277
|
}
|
|
265
278
|
function judgmentFrom(answers, config) {
|
package/dist/rm-hook.js
CHANGED
|
@@ -452,6 +452,7 @@ var DEFAULT_CONFIG = {
|
|
|
452
452
|
lang: "ko",
|
|
453
453
|
keyFile: null,
|
|
454
454
|
display: "mod",
|
|
455
|
+
shortcut: null,
|
|
455
456
|
context: { enabled: true, minTokens: 25e4, compactMinTokens: 25e4, redRemainingPct: 20, unitDoneYes: 0.6, phaseDoneYes: 0.6 },
|
|
456
457
|
rm: { enabled: true, jev: true, throwawayYes: 0.8, maxDirFiles: 50 }
|
|
457
458
|
};
|
|
@@ -479,6 +480,7 @@ function normalizeConfig(raw) {
|
|
|
479
480
|
lang: typeof r.lang === "string" && LANGS.includes(r.lang) ? r.lang : DEFAULT_CONFIG.lang,
|
|
480
481
|
keyFile: typeof r.keyFile === "string" && r.keyFile !== "" ? r.keyFile : null,
|
|
481
482
|
display: typeof r.display === "string" && DISPLAYS.includes(r.display) ? r.display : DEFAULT_CONFIG.display,
|
|
483
|
+
shortcut: typeof r.shortcut === "string" && r.shortcut !== "" ? r.shortcut : null,
|
|
482
484
|
context: mergeSection(DEFAULT_CONFIG.context, r.version === CONFIG_VERSION ? r.context : withoutLegacyDefaults(r.context)),
|
|
483
485
|
rm: mergeSection(DEFAULT_CONFIG.rm, r.rm)
|
|
484
486
|
};
|
package/dist/statusline.js
CHANGED
|
@@ -18,6 +18,7 @@ var DEFAULT_CONFIG = {
|
|
|
18
18
|
lang: "ko",
|
|
19
19
|
keyFile: null,
|
|
20
20
|
display: "mod",
|
|
21
|
+
shortcut: null,
|
|
21
22
|
context: { enabled: true, minTokens: 25e4, compactMinTokens: 25e4, redRemainingPct: 20, unitDoneYes: 0.6, phaseDoneYes: 0.6 },
|
|
22
23
|
rm: { enabled: true, jev: true, throwawayYes: 0.8, maxDirFiles: 50 }
|
|
23
24
|
};
|
|
@@ -45,6 +46,7 @@ function normalizeConfig(raw) {
|
|
|
45
46
|
lang: typeof r.lang === "string" && LANGS.includes(r.lang) ? r.lang : DEFAULT_CONFIG.lang,
|
|
46
47
|
keyFile: typeof r.keyFile === "string" && r.keyFile !== "" ? r.keyFile : null,
|
|
47
48
|
display: typeof r.display === "string" && DISPLAYS.includes(r.display) ? r.display : DEFAULT_CONFIG.display,
|
|
49
|
+
shortcut: typeof r.shortcut === "string" && r.shortcut !== "" ? r.shortcut : null,
|
|
48
50
|
context: mergeSection(DEFAULT_CONFIG.context, r.version === CONFIG_VERSION ? r.context : withoutLegacyDefaults(r.context)),
|
|
49
51
|
rm: mergeSection(DEFAULT_CONFIG.rm, r.rm)
|
|
50
52
|
};
|
|
@@ -73,18 +75,20 @@ import path3 from "path";
|
|
|
73
75
|
|
|
74
76
|
// src/display/line.ts
|
|
75
77
|
var STALE_SIZE_RATIO = 0.7;
|
|
76
|
-
var
|
|
78
|
+
var QUESTIONS = {
|
|
77
79
|
ko: {
|
|
78
|
-
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
79
|
-
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
80
|
-
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
80
|
+
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
81
|
+
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
82
|
+
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?"
|
|
81
83
|
},
|
|
82
84
|
en: {
|
|
83
|
-
clear: "Start fresh?
|
|
84
|
-
compact: "Wrap up what you have and continue?
|
|
85
|
-
compactLater: "When this work is done, wrap up and continue?
|
|
85
|
+
clear: "Start fresh?",
|
|
86
|
+
compact: "Wrap up what you have and continue?",
|
|
87
|
+
compactLater: "When this work is done, wrap up and continue?"
|
|
86
88
|
}
|
|
87
89
|
};
|
|
90
|
+
var COMMANDS = { clear: "/clear", compact: "/compact", compactLater: "/compact" };
|
|
91
|
+
var phrase = (lang, advice) => `${QUESTIONS[lang][advice]} ${COMMANDS[advice]}`;
|
|
88
92
|
function isRecord2(value) {
|
|
89
93
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
90
94
|
}
|
|
@@ -99,20 +103,29 @@ function adviceKind(size, judgment, config) {
|
|
|
99
103
|
if (judgment.clear) return "clear";
|
|
100
104
|
return size >= config.context.compactMinTokens ? "compact" : null;
|
|
101
105
|
}
|
|
102
|
-
function
|
|
106
|
+
function increaseSuffix(increase) {
|
|
107
|
+
if (increase === null || increase === void 0) return "";
|
|
108
|
+
const k = Math.round(increase / 1e3);
|
|
109
|
+
return k > 0 ? ` +${k}k` : "";
|
|
110
|
+
}
|
|
111
|
+
function redRemaining(size, threshold, config) {
|
|
112
|
+
if (threshold === null || threshold <= 0 || size < threshold * (1 - config.context.redRemainingPct / 100)) return null;
|
|
113
|
+
return remainingPct(size, threshold);
|
|
114
|
+
}
|
|
115
|
+
function adviceLine({ size, threshold, judgment, config, increase }) {
|
|
103
116
|
if (size === null) return "";
|
|
104
|
-
const k = formatSize(size)
|
|
105
|
-
const phrases = PHRASES[config.lang];
|
|
117
|
+
const k = `${formatSize(size)}${increaseSuffix(increase)}`;
|
|
106
118
|
const counted = size >= config.context.minTokens ? judgment : null;
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
const red = redRemaining(size, threshold, config);
|
|
120
|
+
if (red !== null) {
|
|
121
|
+
const head = `\u{1F534} ${k} ${red}%`;
|
|
109
122
|
if (!counted) return head;
|
|
110
|
-
if (counted.phase === "working") return `${head} \xB7 ${
|
|
111
|
-
return `${head} \xB7 ${counted.clear ?
|
|
123
|
+
if (counted.phase === "working") return `${head} \xB7 ${phrase(config.lang, "compactLater")}`;
|
|
124
|
+
return `${head} \xB7 ${phrase(config.lang, counted.clear ? "clear" : "compact")}`;
|
|
112
125
|
}
|
|
113
126
|
if (!counted) return k;
|
|
114
127
|
const kind = adviceKind(size, counted, config);
|
|
115
|
-
return kind ? `\u{1F7E1} ${k} ${
|
|
128
|
+
return kind ? `\u{1F7E1} ${k} ${phrase(config.lang, kind)}` : `\u{1F7E2} ${k}`;
|
|
116
129
|
}
|
|
117
130
|
function usableJudgment(state, liveSize, turnStartedAt) {
|
|
118
131
|
if (!state || !state.judgment || state.at < turnStartedAt) return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jev-advisor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Bottom-row compact/clear advice from @delt/claude-jev-advisor (unofficial)",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Bottom-row compact/clear advice and an advice button from @delt/claude-jev-advisor (unofficial)",
|
|
5
5
|
"author": { "name": "delt96" },
|
|
6
6
|
"userConfig": {
|
|
7
7
|
"dataDir": {
|
package/mod/hooks/register.js
CHANGED
|
@@ -4,6 +4,7 @@ var DEFAULT_CONFIG = {
|
|
|
4
4
|
lang: "ko",
|
|
5
5
|
keyFile: null,
|
|
6
6
|
display: "mod",
|
|
7
|
+
shortcut: null,
|
|
7
8
|
context: { enabled: true, minTokens: 25e4, compactMinTokens: 25e4, redRemainingPct: 20, unitDoneYes: 0.6, phaseDoneYes: 0.6 },
|
|
8
9
|
rm: { enabled: true, jev: true, throwawayYes: 0.8, maxDirFiles: 50 }
|
|
9
10
|
};
|
|
@@ -31,6 +32,7 @@ function normalizeConfig(raw) {
|
|
|
31
32
|
lang: typeof r.lang === "string" && LANGS.includes(r.lang) ? r.lang : DEFAULT_CONFIG.lang,
|
|
32
33
|
keyFile: typeof r.keyFile === "string" && r.keyFile !== "" ? r.keyFile : null,
|
|
33
34
|
display: typeof r.display === "string" && DISPLAYS.includes(r.display) ? r.display : DEFAULT_CONFIG.display,
|
|
35
|
+
shortcut: typeof r.shortcut === "string" && r.shortcut !== "" ? r.shortcut : null,
|
|
34
36
|
context: mergeSection(DEFAULT_CONFIG.context, r.version === CONFIG_VERSION ? r.context : withoutLegacyDefaults(r.context)),
|
|
35
37
|
rm: mergeSection(DEFAULT_CONFIG.rm, r.rm)
|
|
36
38
|
};
|
|
@@ -38,21 +40,26 @@ function normalizeConfig(raw) {
|
|
|
38
40
|
|
|
39
41
|
// src/display/line.ts
|
|
40
42
|
var STALE_SIZE_RATIO = 0.7;
|
|
41
|
-
var
|
|
43
|
+
var QUESTIONS = {
|
|
42
44
|
ko: {
|
|
43
|
-
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
44
|
-
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
45
|
-
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
45
|
+
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
46
|
+
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
47
|
+
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?"
|
|
46
48
|
},
|
|
47
49
|
en: {
|
|
48
|
-
clear: "Start fresh?
|
|
49
|
-
compact: "Wrap up what you have and continue?
|
|
50
|
-
compactLater: "When this work is done, wrap up and continue?
|
|
50
|
+
clear: "Start fresh?",
|
|
51
|
+
compact: "Wrap up what you have and continue?",
|
|
52
|
+
compactLater: "When this work is done, wrap up and continue?"
|
|
51
53
|
}
|
|
52
54
|
};
|
|
55
|
+
var COMMANDS = { clear: "/clear", compact: "/compact", compactLater: "/compact" };
|
|
56
|
+
var phrase = (lang, advice) => `${QUESTIONS[lang][advice]} ${COMMANDS[advice]}`;
|
|
53
57
|
function isRecord2(value) {
|
|
54
58
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
59
|
}
|
|
60
|
+
function adviceQuestion(kind, lang) {
|
|
61
|
+
return QUESTIONS[lang][kind];
|
|
62
|
+
}
|
|
56
63
|
function formatSize(tokens) {
|
|
57
64
|
return `${Math.round(tokens / 1e3)}k`;
|
|
58
65
|
}
|
|
@@ -64,20 +71,42 @@ function adviceKind(size, judgment, config) {
|
|
|
64
71
|
if (judgment.clear) return "clear";
|
|
65
72
|
return size >= config.context.compactMinTokens ? "compact" : null;
|
|
66
73
|
}
|
|
67
|
-
function
|
|
74
|
+
function increaseSuffix(increase) {
|
|
75
|
+
if (increase === null || increase === void 0) return "";
|
|
76
|
+
const k = Math.round(increase / 1e3);
|
|
77
|
+
return k > 0 ? ` +${k}k` : "";
|
|
78
|
+
}
|
|
79
|
+
function redRemaining(size, threshold, config) {
|
|
80
|
+
if (threshold === null || threshold <= 0 || size < threshold * (1 - config.context.redRemainingPct / 100)) return null;
|
|
81
|
+
return remainingPct(size, threshold);
|
|
82
|
+
}
|
|
83
|
+
function adviceLine({ size, threshold, judgment, config, increase }) {
|
|
68
84
|
if (size === null) return "";
|
|
69
|
-
const k = formatSize(size)
|
|
70
|
-
const phrases = PHRASES[config.lang];
|
|
85
|
+
const k = `${formatSize(size)}${increaseSuffix(increase)}`;
|
|
71
86
|
const counted = size >= config.context.minTokens ? judgment : null;
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
const red = redRemaining(size, threshold, config);
|
|
88
|
+
if (red !== null) {
|
|
89
|
+
const head = `\u{1F534} ${k} ${red}%`;
|
|
74
90
|
if (!counted) return head;
|
|
75
|
-
if (counted.phase === "working") return `${head} \xB7 ${
|
|
76
|
-
return `${head} \xB7 ${counted.clear ?
|
|
91
|
+
if (counted.phase === "working") return `${head} \xB7 ${phrase(config.lang, "compactLater")}`;
|
|
92
|
+
return `${head} \xB7 ${phrase(config.lang, counted.clear ? "clear" : "compact")}`;
|
|
77
93
|
}
|
|
78
94
|
if (!counted) return k;
|
|
79
95
|
const kind = adviceKind(size, counted, config);
|
|
80
|
-
return kind ? `\u{1F7E1} ${k} ${
|
|
96
|
+
return kind ? `\u{1F7E1} ${k} ${phrase(config.lang, kind)}` : `\u{1F7E2} ${k}`;
|
|
97
|
+
}
|
|
98
|
+
function bandAdvice({ size, threshold, judgment, config }) {
|
|
99
|
+
if (size === null) return null;
|
|
100
|
+
const counted = size >= config.context.minTokens ? judgment : null;
|
|
101
|
+
if (!counted || counted.phase === "working") return null;
|
|
102
|
+
const red = redRemaining(size, threshold, config);
|
|
103
|
+
if (red !== null) return { kind: counted.clear ? "clear" : "compact", remainingPct: red };
|
|
104
|
+
const kind = adviceKind(size, counted, config);
|
|
105
|
+
return kind ? { kind, remainingPct: null } : null;
|
|
106
|
+
}
|
|
107
|
+
function modTail(input) {
|
|
108
|
+
if (input.size === null) return "";
|
|
109
|
+
return bandAdvice(input) ? `${formatSize(input.size)}${increaseSuffix(input.increase)}` : adviceLine(input);
|
|
81
110
|
}
|
|
82
111
|
function usableJudgment(state, liveSize2, turnStartedAt) {
|
|
83
112
|
if (!state || !state.judgment || state.at < turnStartedAt) return null;
|
|
@@ -91,6 +120,43 @@ function parseState(raw) {
|
|
|
91
120
|
return { sessionId: raw.sessionId, at: raw.at, size: typeof raw.size === "number" ? raw.size : null, judgment };
|
|
92
121
|
}
|
|
93
122
|
|
|
123
|
+
// src/display/shortcut.ts
|
|
124
|
+
var SHORTCUT_ACTION = "diff:back";
|
|
125
|
+
|
|
126
|
+
// src/mod/band.ts
|
|
127
|
+
var ICONS = { compact: "\u{1F4E6}", clear: "\u{1F9F9}" };
|
|
128
|
+
function drawBand(ui, { advice, config, onPress }) {
|
|
129
|
+
const { Box, Text, Button } = ui;
|
|
130
|
+
const head = advice.remainingPct === null ? "\u{1F7E1}" : `\u{1F534} ${advice.remainingPct}%`;
|
|
131
|
+
const controls = [
|
|
132
|
+
h(Button, {
|
|
133
|
+
key: `jev-${advice.kind}`,
|
|
134
|
+
label: `${ICONS[advice.kind]} /${advice.kind}`,
|
|
135
|
+
variant: "primary",
|
|
136
|
+
autoFocus: true,
|
|
137
|
+
hover: { bold: true },
|
|
138
|
+
onPress,
|
|
139
|
+
...config.shortcut ? { action: SHORTCUT_ACTION } : {}
|
|
140
|
+
})
|
|
141
|
+
];
|
|
142
|
+
if (config.shortcut) controls.push(h(Text, { dimColor: true }, config.shortcut));
|
|
143
|
+
return h(
|
|
144
|
+
Box,
|
|
145
|
+
{ flexDirection: "row", flexWrap: "wrap", columnGap: 2, paddingTop: 1 },
|
|
146
|
+
h(
|
|
147
|
+
Box,
|
|
148
|
+
{ flexDirection: "row", gap: 1, minWidth: 0, flexShrink: 1 },
|
|
149
|
+
h(Box, { flexShrink: 0 }, h(Text, { bold: advice.remainingPct !== null }, head)),
|
|
150
|
+
h(
|
|
151
|
+
Box,
|
|
152
|
+
{ minWidth: 0, flexShrink: 1 },
|
|
153
|
+
h(Text, { wrap: "truncate-end" }, adviceQuestion(advice.kind, config.lang))
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
h(Box, { key: `jev-${advice.kind}-control`, flexDirection: "row", gap: 1, flexShrink: 0 }, ...controls)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
94
160
|
// src/mod/register.ts
|
|
95
161
|
var REFRESH_MS = 2e3;
|
|
96
162
|
async function readJson($, file) {
|
|
@@ -130,13 +196,36 @@ async function liveSize($) {
|
|
|
130
196
|
return null;
|
|
131
197
|
}
|
|
132
198
|
}
|
|
199
|
+
function reason(err) {
|
|
200
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
201
|
+
return message.replace(/^[^:]+: \$\.[\w]+(?:\.[\w]+)*: /, "");
|
|
202
|
+
}
|
|
203
|
+
async function press($, kind) {
|
|
204
|
+
try {
|
|
205
|
+
$.ui.invalidate("ui.render");
|
|
206
|
+
if (kind === "compact") {
|
|
207
|
+
const result = await $.session.compact();
|
|
208
|
+
if (result?.skip) $.ui.toast("/compact: skipped by a hook");
|
|
209
|
+
} else {
|
|
210
|
+
await $.command.run({ command: "clear" });
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
try {
|
|
214
|
+
$.ui.toast(`/${kind}: ${reason(err)}`);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
133
219
|
function register(on, options) {
|
|
134
220
|
const dataDir = typeof options.dataDir === "string" ? options.dataDir.replace(/[\\/]+$/, "") : "";
|
|
135
221
|
let config = null;
|
|
136
222
|
let state = null;
|
|
137
223
|
let threshold = null;
|
|
138
224
|
let turnStartedAt = 0;
|
|
225
|
+
let baseline = null;
|
|
139
226
|
let seen = "";
|
|
227
|
+
let handledAt = -Infinity;
|
|
228
|
+
const judgmentFor = (size) => state && state.at <= handledAt ? null : usableJudgment(state, size, turnStartedAt);
|
|
140
229
|
on("session.start", async ($, e, next) => {
|
|
141
230
|
const result = await next(e);
|
|
142
231
|
if (!dataDir || e.isInteractive === false) return result;
|
|
@@ -159,8 +248,10 @@ function register(on, options) {
|
|
|
159
248
|
return result;
|
|
160
249
|
});
|
|
161
250
|
on("turn.start", async ($, e, next) => {
|
|
251
|
+
baseline = null;
|
|
162
252
|
try {
|
|
163
253
|
turnStartedAt = await $.clock.now();
|
|
254
|
+
baseline = await liveSize($);
|
|
164
255
|
$.ui.invalidate("ui.render");
|
|
165
256
|
} catch {
|
|
166
257
|
}
|
|
@@ -171,19 +262,50 @@ function register(on, options) {
|
|
|
171
262
|
threshold = await autoCompactThreshold($);
|
|
172
263
|
return result;
|
|
173
264
|
});
|
|
265
|
+
on("session.end", async ($, e, next) => {
|
|
266
|
+
baseline = null;
|
|
267
|
+
state = null;
|
|
268
|
+
seen = "";
|
|
269
|
+
try {
|
|
270
|
+
$.ui.invalidate("ui.render");
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
return next(e);
|
|
274
|
+
});
|
|
174
275
|
on("ui.render", { component: "PromptHint" }, async ($, e, next) => {
|
|
175
276
|
let tail = "";
|
|
176
277
|
try {
|
|
177
278
|
const current = config;
|
|
178
279
|
if (current && current.context.enabled && current.display === "mod") {
|
|
179
280
|
const size = await liveSize($);
|
|
180
|
-
|
|
281
|
+
const increase = baseline !== null && size !== null ? size - baseline : null;
|
|
282
|
+
tail = modTail({ size, threshold, judgment: judgmentFor(size), config: current, increase });
|
|
181
283
|
}
|
|
182
284
|
} catch {
|
|
183
285
|
tail = "";
|
|
184
286
|
}
|
|
185
287
|
return tail ? next({ ...e, props: { ...e.props, tail } }) : next(e);
|
|
186
288
|
});
|
|
289
|
+
on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
|
|
290
|
+
let band = null;
|
|
291
|
+
try {
|
|
292
|
+
const current = config;
|
|
293
|
+
const props = e.props ?? {};
|
|
294
|
+
if (current && current.context.enabled && current.display === "mod" && props.isWorking !== true && props.hasSurvey !== true) {
|
|
295
|
+
const size = await liveSize($);
|
|
296
|
+
const advice = bandAdvice({ size, threshold, judgment: judgmentFor(size), config: current });
|
|
297
|
+
const at = state?.at;
|
|
298
|
+
if (advice && at !== void 0) band = drawBand($.ui.resolve(e), { advice, config: current, onPress: () => {
|
|
299
|
+
if (at <= handledAt) return;
|
|
300
|
+
handledAt = at;
|
|
301
|
+
void press($, advice.kind);
|
|
302
|
+
} });
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
band = null;
|
|
306
|
+
}
|
|
307
|
+
return band ?? next(e);
|
|
308
|
+
});
|
|
187
309
|
}
|
|
188
310
|
export {
|
|
189
311
|
register
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delt/claude-jev-advisor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Unofficial Claude Code helpers using TypeSafe Jev: compact/clear advice and an rm guard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"mod",
|
|
11
|
+
"mod/.claude-plugin/plugin.json",
|
|
12
|
+
"mod/hooks",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|