@delt/claude-jev-advisor 0.3.0 → 0.4.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 +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 +139 -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.1",
|
|
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,44 @@ 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, size, increase }) {
|
|
129
|
+
const { Box, Text, Button } = ui;
|
|
130
|
+
const k = `${formatSize(size)}${increaseSuffix(increase)}`;
|
|
131
|
+
const head = advice.remainingPct === null ? `\u{1F7E1} ${k}` : `\u{1F534} ${k} ${advice.remainingPct}%`;
|
|
132
|
+
const controls = [
|
|
133
|
+
h(Button, {
|
|
134
|
+
key: `jev-${advice.kind}`,
|
|
135
|
+
label: `${ICONS[advice.kind]} /${advice.kind}`,
|
|
136
|
+
variant: "primary",
|
|
137
|
+
autoFocus: true,
|
|
138
|
+
hover: { bold: true },
|
|
139
|
+
onPress,
|
|
140
|
+
...config.shortcut ? { action: SHORTCUT_ACTION } : {}
|
|
141
|
+
})
|
|
142
|
+
];
|
|
143
|
+
if (config.shortcut) controls.push(h(Text, { dimColor: true }, config.shortcut));
|
|
144
|
+
return h(
|
|
145
|
+
Box,
|
|
146
|
+
{ flexDirection: "row", flexWrap: "wrap", columnGap: 2, paddingTop: 1 },
|
|
147
|
+
h(
|
|
148
|
+
Box,
|
|
149
|
+
{ flexDirection: "row", gap: 1, minWidth: 0, flexShrink: 1 },
|
|
150
|
+
h(Box, { flexShrink: 0 }, h(Text, { bold: advice.remainingPct !== null }, head)),
|
|
151
|
+
h(
|
|
152
|
+
Box,
|
|
153
|
+
{ minWidth: 0, flexShrink: 1 },
|
|
154
|
+
h(Text, { wrap: "truncate-end" }, adviceQuestion(advice.kind, config.lang))
|
|
155
|
+
)
|
|
156
|
+
),
|
|
157
|
+
h(Box, { key: `jev-${advice.kind}-control`, flexDirection: "row", gap: 1, flexShrink: 0 }, ...controls)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
94
161
|
// src/mod/register.ts
|
|
95
162
|
var REFRESH_MS = 2e3;
|
|
96
163
|
async function readJson($, file) {
|
|
@@ -130,13 +197,37 @@ async function liveSize($) {
|
|
|
130
197
|
return null;
|
|
131
198
|
}
|
|
132
199
|
}
|
|
200
|
+
function reason(err) {
|
|
201
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
202
|
+
return message.replace(/^[^:]+: \$\.[\w]+(?:\.[\w]+)*: /, "");
|
|
203
|
+
}
|
|
204
|
+
async function press($, kind) {
|
|
205
|
+
try {
|
|
206
|
+
$.ui.invalidate("ui.render");
|
|
207
|
+
if (kind === "compact") {
|
|
208
|
+
const result = await $.session.compact();
|
|
209
|
+
if (result?.skip) $.ui.toast("/compact: skipped by a hook");
|
|
210
|
+
} else {
|
|
211
|
+
await $.command.run({ command: "clear" });
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
try {
|
|
215
|
+
$.ui.toast(`/${kind}: ${reason(err)}`);
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
133
220
|
function register(on, options) {
|
|
134
221
|
const dataDir = typeof options.dataDir === "string" ? options.dataDir.replace(/[\\/]+$/, "") : "";
|
|
135
222
|
let config = null;
|
|
136
223
|
let state = null;
|
|
137
224
|
let threshold = null;
|
|
138
225
|
let turnStartedAt = 0;
|
|
226
|
+
let baseline = null;
|
|
139
227
|
let seen = "";
|
|
228
|
+
let handledAt = -Infinity;
|
|
229
|
+
const increaseTo = (size) => baseline !== null && size !== null ? size - baseline : null;
|
|
230
|
+
const judgmentFor = (size) => state && state.at <= handledAt ? null : usableJudgment(state, size, turnStartedAt);
|
|
140
231
|
on("session.start", async ($, e, next) => {
|
|
141
232
|
const result = await next(e);
|
|
142
233
|
if (!dataDir || e.isInteractive === false) return result;
|
|
@@ -159,8 +250,10 @@ function register(on, options) {
|
|
|
159
250
|
return result;
|
|
160
251
|
});
|
|
161
252
|
on("turn.start", async ($, e, next) => {
|
|
253
|
+
baseline = null;
|
|
162
254
|
try {
|
|
163
255
|
turnStartedAt = await $.clock.now();
|
|
256
|
+
baseline = await liveSize($);
|
|
164
257
|
$.ui.invalidate("ui.render");
|
|
165
258
|
} catch {
|
|
166
259
|
}
|
|
@@ -171,19 +264,49 @@ function register(on, options) {
|
|
|
171
264
|
threshold = await autoCompactThreshold($);
|
|
172
265
|
return result;
|
|
173
266
|
});
|
|
267
|
+
on("session.end", async ($, e, next) => {
|
|
268
|
+
baseline = null;
|
|
269
|
+
state = null;
|
|
270
|
+
seen = "";
|
|
271
|
+
try {
|
|
272
|
+
$.ui.invalidate("ui.render");
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
return next(e);
|
|
276
|
+
});
|
|
174
277
|
on("ui.render", { component: "PromptHint" }, async ($, e, next) => {
|
|
175
278
|
let tail = "";
|
|
176
279
|
try {
|
|
177
280
|
const current = config;
|
|
178
281
|
if (current && current.context.enabled && current.display === "mod") {
|
|
179
282
|
const size = await liveSize($);
|
|
180
|
-
tail =
|
|
283
|
+
tail = modTail({ size, threshold, judgment: judgmentFor(size), config: current, increase: increaseTo(size) });
|
|
181
284
|
}
|
|
182
285
|
} catch {
|
|
183
286
|
tail = "";
|
|
184
287
|
}
|
|
185
288
|
return tail ? next({ ...e, props: { ...e.props, tail } }) : next(e);
|
|
186
289
|
});
|
|
290
|
+
on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
|
|
291
|
+
let band = null;
|
|
292
|
+
try {
|
|
293
|
+
const current = config;
|
|
294
|
+
const props = e.props ?? {};
|
|
295
|
+
if (current && current.context.enabled && current.display === "mod" && props.isWorking !== true && props.hasSurvey !== true) {
|
|
296
|
+
const size = await liveSize($);
|
|
297
|
+
const advice = bandAdvice({ size, threshold, judgment: judgmentFor(size), config: current });
|
|
298
|
+
const at = state?.at;
|
|
299
|
+
if (advice && at !== void 0 && size !== null) band = drawBand($.ui.resolve(e), { advice, config: current, size, increase: increaseTo(size), onPress: () => {
|
|
300
|
+
if (at <= handledAt) return;
|
|
301
|
+
handledAt = at;
|
|
302
|
+
void press($, advice.kind);
|
|
303
|
+
} });
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
band = null;
|
|
307
|
+
}
|
|
308
|
+
return band ?? next(e);
|
|
309
|
+
});
|
|
187
310
|
}
|
|
188
311
|
export {
|
|
189
312
|
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.1",
|
|
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": {
|