kodelyth-ecc 1.9.1 → 2.1.2
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/CHANGELOG.md +87 -0
- package/VERSION +1 -1
- package/agents/code-reviewer.md +11 -0
- package/agents/release-captain.md +10 -0
- package/bin/kodelyth-ecc.js +157 -2
- package/commands/terse-compress.md +50 -0
- package/commands/terse.md +40 -0
- package/install.sh +10 -6
- package/package.json +1 -1
- package/scripts/dashboard/server.js +6 -0
- package/scripts/dashboard/static/index.html +42 -2
- package/scripts/rtk/index.js +22 -9
- package/scripts/terse/compress.js +0 -0
- package/scripts/terse/ledger.js +102 -0
- package/skills/terse-mode/SKILL.md +97 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.1.2 — Install-flow fixes caught by live user testing (July 2026)
|
|
6
|
+
|
|
7
|
+
Ran the actual `npx kodelyth-ecc --target claude-code` flow as a user would, end-to-end. Three real bugs surfaced and got fixed.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`--target claude-code` was rejected** with "Unknown target." install.sh only knew `claude-home`, but every doc, README, and CHANGELOG example uses `claude-code`. install.sh now accepts both (`claude-home|claude-code)` case)
|
|
12
|
+
- **Post-install RTK block silently skipped** on the (correct) `--target claude-code` because `TARGET_MAP` was keyed on `claude-home`. Added `normalizeTarget()` and both the RTK and terse post-install blocks now normalise the target before lookup. `gemini-cli` → `gemini-home`, `cursor` → `cursor-project` also normalised
|
|
13
|
+
- **install.sh `/dev/tty` read failed on piped/CI shells** even though the guard tested `-e /dev/tty`. Guard now also tests `-r /dev/tty` and honours `KODELYTH_NONINTERACTIVE=1` env (Windows PowerShell path already set this)
|
|
14
|
+
- **install.sh footer showed stale `v1.8.1`** from a leftover VERSION file. Bumped to current
|
|
15
|
+
|
|
16
|
+
### Verified end-to-end on this Mac
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
$ npm i -g kodelyth-ecc && kodelyth-ecc --target claude-code
|
|
20
|
+
...
|
|
21
|
+
━ RTK token savings ─────────────────────────────
|
|
22
|
+
✓ RTK 0.42.4 — wired for claude-home
|
|
23
|
+
✓ Restart your AI tool to activate. 60-90% token savings on shell commands.
|
|
24
|
+
|
|
25
|
+
━ Terse mode ────────────────────────────────────
|
|
26
|
+
✓ /terse and /terse-compress installed for claude-home
|
|
27
|
+
· Activate any time: type /terse in your AI tool (dormant until you do)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Files land at `~/.claude/skills/terse-mode/SKILL.md` and `~/.claude/commands/terse.md`. Discoverable by the AI on next session.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `scripts/rtk/index.js` — `normalizeTarget(target)` exported. Accepts `claude-code`, `gemini-cli`, `cursor` and returns the canonical install.sh target names. `roocode` and `kimi` also added to `TARGET_MAP`
|
|
35
|
+
|
|
36
|
+
## v2.0.0 — Terse mode: output-token compressor + memory compressor (July 2026)
|
|
37
|
+
|
|
38
|
+
RTK saves input tokens. Terse mode now saves output tokens. Together — on a typical coding session — ECC cuts ~55-65% of total token cost while keeping code, commands, and errors byte-exact.
|
|
39
|
+
|
|
40
|
+
**Inspired by [Caveman](https://github.com/JuliusBrussee/caveman) (MIT, by Julius Brussee).** Our implementation is independent: our own prompt, own compressor, own ledger, own dashboard tile. Credit to Julius for the core insight — "make the mouth smaller, not the brain smaller."
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
**Terse mode skill + slash commands** (works across every ECC-installed IDE)
|
|
45
|
+
- `skills/terse-mode/SKILL.md` — 4-level dial (lite / full / ultra / off), byte-preserves code/commands/URLs/paths
|
|
46
|
+
- `commands/terse.md` — `/terse [lite|full|ultra|off]` sticks for the session
|
|
47
|
+
- `commands/terse-compress.md` — one-shot memory-file compression via LLM
|
|
48
|
+
|
|
49
|
+
**Deterministic memory compressor** (scriptable, no LLM required)
|
|
50
|
+
- `scripts/terse/compress.js` — zero-dep markdown compressor. Strips 40+ filler patterns, merges wrapped prose, byte-preserves fenced code / inline code / URLs / paths / YAML frontmatter. Idempotent, safe to re-run
|
|
51
|
+
- `kodelyth-ecc terse compress <file> [--dry-run] [--no-backup]` — CLI wrapper
|
|
52
|
+
- On real prose-heavy content: ~30% byte reduction, 100% code/URL/path integrity
|
|
53
|
+
|
|
54
|
+
**Output-token savings ledger + dashboard tile**
|
|
55
|
+
- `scripts/terse/ledger.js` — JSONL ledger at `~/.kodelythecc/terse/ledger.jsonl`. Per-turn record: level, actual output tokens, estimated baseline, saved
|
|
56
|
+
- `/api/terse` dashboard endpoint
|
|
57
|
+
- New "Output savings (Terse mode)" section on the RTK Savings tab: totals, level breakdown, 30-day daily bar chart
|
|
58
|
+
- Renamed the tab's implicit RTK header to "Input savings (RTK)" so both axes read cleanly
|
|
59
|
+
|
|
60
|
+
**CLI**
|
|
61
|
+
- `kodelyth-ecc terse status` — shipped/installed/ledger paths
|
|
62
|
+
- `kodelyth-ecc terse stats [--json]` — turns tracked, tokens saved, savings %, level breakdown
|
|
63
|
+
- `kodelyth-ecc terse enable [--target X | --all]` — installs skill + commands into one or every ECC-detected IDE
|
|
64
|
+
|
|
65
|
+
**Auto-install on ECC install**
|
|
66
|
+
- After the base installer succeeds, terse-mode files are copied into the target IDE's `skills/` and `commands/` directories automatically. Dormant until user types `/terse` — respects "no forced verbosity change"
|
|
67
|
+
|
|
68
|
+
**Phase C — bake-in to existing agents**
|
|
69
|
+
- `agents/code-reviewer.md` — opt-in terse section: one-line PR comments when `/terse` active
|
|
70
|
+
- `agents/release-captain.md` — opt-in terse section: Conventional Commit ≤50-char subjects, terse changelog rows, rollback plan stays complete
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- Major version bump: adds a new user-visible mode (terse) that changes AI output style. Breaking only in the sense of "your AI now has a new toggle." No existing behavior removed
|
|
75
|
+
- Dashboard RTK Savings tab now shows both input (RTK) and output (Terse) savings side by side
|
|
76
|
+
|
|
77
|
+
### Compatibility
|
|
78
|
+
|
|
79
|
+
- Fully backwards-compatible with v1.9.x installs
|
|
80
|
+
- Terse mode never auto-activates — user opts in per session
|
|
81
|
+
- RTK integration unchanged
|
|
82
|
+
- Memory paths (`~/.kodelythecc/`) unchanged
|
|
83
|
+
- Zero-dep: terse mode ships as a prompt + a plain-JS compressor. No extra npm dependencies
|
|
84
|
+
|
|
85
|
+
### Honest math
|
|
86
|
+
|
|
87
|
+
- Combined RTK + Terse on a typical coding session: 55-65% total token reduction
|
|
88
|
+
- On explain-heavy or review sessions: closer to 65-70%
|
|
89
|
+
- Terse mode adds ~800-1200 input tokens per turn (the skill prompt) — net-negative on turns with <2k output tokens
|
|
90
|
+
- Memory compressor: one-time rewrite of `CLAUDE.md` / `lessons.md` — cuts ~30-46% every session forever
|
|
91
|
+
|
|
5
92
|
## v1.9.1 — Smoothness pass on RTK integration (July 2026)
|
|
6
93
|
|
|
7
94
|
Follow-up polish on 1.9.0. Cleaner output, agents now say the right paths, one-shot multi-IDE RTK setup.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
2.1.2
|
package/agents/code-reviewer.md
CHANGED
|
@@ -235,3 +235,14 @@ When reviewing AI-generated changes, prioritize:
|
|
|
235
235
|
Cost-awareness check:
|
|
236
236
|
- Flag workflows that escalate to higher-cost models without clear reasoning need.
|
|
237
237
|
- Recommend defaulting to lower-cost tiers for deterministic refactors.
|
|
238
|
+
|
|
239
|
+
## Terse mode (opt-in)
|
|
240
|
+
|
|
241
|
+
If the user has typed `/terse` (any level) this session, respond in the terse-mode voice:
|
|
242
|
+
|
|
243
|
+
- One line per finding: `L42: 🔴 bug: user null. Add guard.`
|
|
244
|
+
- No preamble, no "here's what I found"
|
|
245
|
+
- Sections only if 3+ findings share a theme
|
|
246
|
+
- Code snippets stay byte-exact (never compress the actual fix)
|
|
247
|
+
|
|
248
|
+
Normal review still runs — only the writing style compresses.
|
|
@@ -189,3 +189,13 @@ Ready? (y/N)
|
|
|
189
189
|
```
|
|
190
190
|
|
|
191
191
|
You ship calm releases. You leave a paper trail. The next on-call will thank you.
|
|
192
|
+
|
|
193
|
+
## Terse mode (opt-in)
|
|
194
|
+
|
|
195
|
+
If the user has typed `/terse` (any level) this session, apply to release artifacts:
|
|
196
|
+
|
|
197
|
+
- Commit messages: Conventional Commit, subject ≤50 chars, body only when the "why" is non-obvious
|
|
198
|
+
- Release notes: one line per PR, grouped by type (feat/fix/perf), no marketing filler
|
|
199
|
+
- Changelog entries: terse — same rules as commit bodies
|
|
200
|
+
|
|
201
|
+
Rollback plan, deploy checklist, and every technical fact stays complete — only the prose is compressed.
|
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -260,6 +260,135 @@ if (args[0] === 'rtk') {
|
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// ── Subcommand: terse (output token compression) ─────────────────────────────
|
|
264
|
+
// Usage:
|
|
265
|
+
// kodelyth-ecc terse status
|
|
266
|
+
// kodelyth-ecc terse stats [--json]
|
|
267
|
+
// kodelyth-ecc terse compress <file> [--dry-run] [--no-backup]
|
|
268
|
+
// kodelyth-ecc terse enable [--target X | --all]
|
|
269
|
+
if (args[0] === 'terse') {
|
|
270
|
+
const sub = args[1] || 'status';
|
|
271
|
+
const rest = args.slice(2);
|
|
272
|
+
const log = (m) => process.stdout.write(m + '\n');
|
|
273
|
+
function flag(name, dflt) {
|
|
274
|
+
const i = rest.indexOf(name);
|
|
275
|
+
return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
if (sub === 'status') {
|
|
279
|
+
const skill = path.join(ROOT, 'skills', 'terse-mode', 'SKILL.md');
|
|
280
|
+
const cmd = path.join(ROOT, 'commands', 'terse.md');
|
|
281
|
+
const inClaude = fs.existsSync(path.join(os.homedir(), '.claude', 'skills', 'terse-mode', 'SKILL.md'));
|
|
282
|
+
log(`Terse mode skill: ${fs.existsSync(skill) ? 'shipped' : 'missing'}`);
|
|
283
|
+
log(`Terse mode command: ${fs.existsSync(cmd) ? 'shipped' : 'missing'}`);
|
|
284
|
+
log(`Installed to Claude Code: ${inClaude ? 'yes' : 'no'}`);
|
|
285
|
+
log(`Ledger: ${require(path.join(ROOT, 'scripts', 'terse', 'ledger.js')).LEDGER}`);
|
|
286
|
+
log('Activate in your AI tool with: /terse [lite|full|ultra|off]');
|
|
287
|
+
process.exit(0);
|
|
288
|
+
}
|
|
289
|
+
if (sub === 'stats') {
|
|
290
|
+
const ledger = require(path.join(ROOT, 'scripts', 'terse', 'ledger.js'));
|
|
291
|
+
const s = ledger.summary({ days: 30 });
|
|
292
|
+
if (rest.includes('--json')) { log(JSON.stringify(s, null, 2)); process.exit(0); }
|
|
293
|
+
log(`Terse mode — output token savings`);
|
|
294
|
+
log(` turns: ${s.totalTurns.toLocaleString()}`);
|
|
295
|
+
log(` tokens out: ${s.totalActual.toLocaleString()}`);
|
|
296
|
+
log(` tokens saved: ${s.totalSaved.toLocaleString()} (${s.avgSavingsPct}% vs baseline)`);
|
|
297
|
+
log(` by level: ${JSON.stringify(s.byLevel)}`);
|
|
298
|
+
log(` 30d days: ${s.daily.length}`);
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
if (sub === 'compress') {
|
|
302
|
+
const file = rest.find(a => !a.startsWith('-'));
|
|
303
|
+
if (!file) { process.stderr.write('usage: kodelyth-ecc terse compress <file> [--dry-run] [--no-backup]\n'); process.exit(2); }
|
|
304
|
+
const { compressFile } = require(path.join(ROOT, 'scripts', 'terse', 'compress.js'));
|
|
305
|
+
const dry = rest.includes('--dry-run');
|
|
306
|
+
const backup = !rest.includes('--no-backup');
|
|
307
|
+
const r = compressFile(file, { write: !dry, backup });
|
|
308
|
+
log(`${r.path}`);
|
|
309
|
+
log(` before: ${r.stats.originalBytes.toLocaleString()} bytes`);
|
|
310
|
+
log(` after: ${r.stats.newBytes.toLocaleString()} bytes`);
|
|
311
|
+
log(` saved: ${r.stats.saved.toLocaleString()} bytes (${r.stats.savedPct}%) ~${r.stats.estimatedTokensSaved.toLocaleString()} tokens`);
|
|
312
|
+
log(dry ? ' (dry-run — nothing written)' : (backup ? ` backup: ${r.path}.pre-terse.bak` : ' (no backup)'));
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
if (sub === 'enable') {
|
|
316
|
+
// Install skill + command into the chosen IDE(s) by running the base
|
|
317
|
+
// installer with just those files. Simplest reliable path: copy directly.
|
|
318
|
+
const targetIdx = rest.indexOf('--target');
|
|
319
|
+
const single = targetIdx >= 0 ? rest[targetIdx + 1] : null;
|
|
320
|
+
const useAll = rest.includes('--all');
|
|
321
|
+
const rtk = require(path.join(ROOT, 'scripts', 'rtk', 'index.js'));
|
|
322
|
+
const targets = useAll ? rtk.detectInstalledTargets() : [single || 'claude-code'];
|
|
323
|
+
let ok = 0;
|
|
324
|
+
for (const t of targets) {
|
|
325
|
+
try {
|
|
326
|
+
const skillSrc = path.join(ROOT, 'skills', 'terse-mode', 'SKILL.md');
|
|
327
|
+
const cmdSrc = path.join(ROOT, 'commands', 'terse.md');
|
|
328
|
+
const cmdCompress = path.join(ROOT, 'commands', 'terse-compress.md');
|
|
329
|
+
const destSkillDir = getTargetSkillsDir(t);
|
|
330
|
+
const destCmdDir = getTargetCommandsDir(t);
|
|
331
|
+
if (!destSkillDir || !destCmdDir) { log(` · ${t} — no skills/commands path`); continue; }
|
|
332
|
+
fs.mkdirSync(path.join(destSkillDir, 'terse-mode'), { recursive: true });
|
|
333
|
+
fs.mkdirSync(destCmdDir, { recursive: true });
|
|
334
|
+
fs.copyFileSync(skillSrc, path.join(destSkillDir, 'terse-mode', 'SKILL.md'));
|
|
335
|
+
fs.copyFileSync(cmdSrc, path.join(destCmdDir, 'terse.md'));
|
|
336
|
+
fs.copyFileSync(cmdCompress, path.join(destCmdDir, 'terse-compress.md'));
|
|
337
|
+
log(` ✓ ${t}`);
|
|
338
|
+
ok++;
|
|
339
|
+
} catch (e) {
|
|
340
|
+
log(` · ${t} — ${e.message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
log(`\nTerse mode installed on ${ok}/${targets.length} IDE${targets.length === 1 ? '' : 's'}. Use /terse to activate.`);
|
|
344
|
+
process.exit(ok ? 0 : 1);
|
|
345
|
+
}
|
|
346
|
+
process.stderr.write('unknown terse subcommand. try: status | stats | compress | enable\n');
|
|
347
|
+
process.exit(2);
|
|
348
|
+
} catch (e) {
|
|
349
|
+
process.stderr.write(`[terse] ${e.message}\n`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Normalise alias → canonical target (matches scripts/rtk/index.js).
|
|
355
|
+
function _canon(target) {
|
|
356
|
+
if (target === 'claude-code') return 'claude-home';
|
|
357
|
+
if (target === 'gemini-cli') return 'gemini-home';
|
|
358
|
+
if (target === 'cursor') return 'cursor-project';
|
|
359
|
+
return target;
|
|
360
|
+
}
|
|
361
|
+
function getTargetSkillsDir(target) {
|
|
362
|
+
const home = os.homedir();
|
|
363
|
+
switch (_canon(target)) {
|
|
364
|
+
case 'claude-home': return path.join(home, '.claude', 'skills');
|
|
365
|
+
case 'cursor-project': return path.join(home, '.cursor', 'skills');
|
|
366
|
+
case 'windsurf-home':
|
|
367
|
+
case 'windsurf-project': return path.join(home, '.codeium', 'windsurf', 'skills');
|
|
368
|
+
case 'antigravity': return path.join(home, '.antigravity', 'skills');
|
|
369
|
+
case 'codex-home': return path.join(home, '.codex', 'skills');
|
|
370
|
+
case 'gemini-home': return path.join(home, '.gemini', 'skills');
|
|
371
|
+
case 'gemini-project': return path.join(process.cwd(), '.gemini', 'skills');
|
|
372
|
+
case 'opencode': return path.join(home, '.config', 'opencode', 'skills');
|
|
373
|
+
default: return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function getTargetCommandsDir(target) {
|
|
377
|
+
const home = os.homedir();
|
|
378
|
+
switch (_canon(target)) {
|
|
379
|
+
case 'claude-home': return path.join(home, '.claude', 'commands');
|
|
380
|
+
case 'cursor-project': return path.join(home, '.cursor', 'commands');
|
|
381
|
+
case 'windsurf-home':
|
|
382
|
+
case 'windsurf-project': return path.join(home, '.codeium', 'windsurf', 'commands');
|
|
383
|
+
case 'antigravity': return path.join(home, '.antigravity', 'commands');
|
|
384
|
+
case 'codex-home': return path.join(home, '.codex', 'commands');
|
|
385
|
+
case 'gemini-home': return path.join(home, '.gemini', 'commands');
|
|
386
|
+
case 'gemini-project': return path.join(process.cwd(), '.gemini', 'commands');
|
|
387
|
+
case 'opencode': return path.join(home, '.config', 'opencode', 'commands');
|
|
388
|
+
default: return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
263
392
|
// ── Subcommand: route (cost-aware model tier recommendation) ──────────────────
|
|
264
393
|
// Usage: npx kodelyth-ecc route "<task description>" [--files N] [--agent <name>] [--current <model-id>]
|
|
265
394
|
if (args[0] === 'route') {
|
|
@@ -1112,7 +1241,8 @@ if (isWin) {
|
|
|
1112
1241
|
try {
|
|
1113
1242
|
const rtk = require(path.join(ROOT, 'scripts', 'rtk', 'index.js'));
|
|
1114
1243
|
const targetIdx = args.indexOf('--target');
|
|
1115
|
-
const
|
|
1244
|
+
const rawTarget = targetIdx >= 0 && args[targetIdx + 1] ? args[targetIdx + 1] : 'claude-code';
|
|
1245
|
+
const target = rtk.normalizeTarget(rawTarget);
|
|
1116
1246
|
if (rtk.TARGET_MAP[target]) {
|
|
1117
1247
|
const w = (m) => process.stdout.write(m + '\n');
|
|
1118
1248
|
w('');
|
|
@@ -1134,7 +1264,32 @@ if (isWin) {
|
|
|
1134
1264
|
w('');
|
|
1135
1265
|
}
|
|
1136
1266
|
} catch (e) {
|
|
1137
|
-
|
|
1267
|
+
/* fall through */
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Also install terse-mode skill + commands (dormant until user types /terse).
|
|
1271
|
+
try {
|
|
1272
|
+
const rtk2 = require(path.join(ROOT, 'scripts', 'rtk', 'index.js'));
|
|
1273
|
+
const targetIdx = args.indexOf('--target');
|
|
1274
|
+
const rawTarget = targetIdx >= 0 && args[targetIdx + 1] ? args[targetIdx + 1] : 'claude-code';
|
|
1275
|
+
const target = rtk2.normalizeTarget(rawTarget);
|
|
1276
|
+
const skillsDir = getTargetSkillsDir(target);
|
|
1277
|
+
const cmdsDir = getTargetCommandsDir(target);
|
|
1278
|
+
if (skillsDir && cmdsDir) {
|
|
1279
|
+
fs.mkdirSync(path.join(skillsDir, 'terse-mode'), { recursive: true });
|
|
1280
|
+
fs.mkdirSync(cmdsDir, { recursive: true });
|
|
1281
|
+
fs.copyFileSync(path.join(ROOT, 'skills', 'terse-mode', 'SKILL.md'),
|
|
1282
|
+
path.join(skillsDir, 'terse-mode', 'SKILL.md'));
|
|
1283
|
+
fs.copyFileSync(path.join(ROOT, 'commands', 'terse.md'),
|
|
1284
|
+
path.join(cmdsDir, 'terse.md'));
|
|
1285
|
+
fs.copyFileSync(path.join(ROOT, 'commands', 'terse-compress.md'),
|
|
1286
|
+
path.join(cmdsDir, 'terse-compress.md'));
|
|
1287
|
+
process.stdout.write('━ Terse mode ' + '─'.repeat(47) + '\n');
|
|
1288
|
+
process.stdout.write(` ✓ /terse and /terse-compress installed for ${target}\n`);
|
|
1289
|
+
process.stdout.write(` · Activate any time: type /terse in your AI tool (dormant until you do)\n\n`);
|
|
1290
|
+
}
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
process.stderr.write(`[terse] setup skipped: ${e.message}\n`);
|
|
1138
1293
|
}
|
|
1139
1294
|
}
|
|
1140
1295
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Compress a markdown file into terse form for permanent input-token savings. Byte-preserves code, URLs, paths.
|
|
3
|
+
argument-hint: "<file>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /terse-compress — rewrite a memory file to save tokens forever
|
|
7
|
+
|
|
8
|
+
Compresses a markdown file (like `CLAUDE.md`, `tasks/lessons.md`, `AGENTS.md`) into terse form so it costs fewer tokens to load every session.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
- `/terse-compress CLAUDE.md`
|
|
13
|
+
- `/terse-compress tasks/lessons.md`
|
|
14
|
+
- `/terse-compress ~/.claude/CLAUDE.md`
|
|
15
|
+
|
|
16
|
+
## What gets compressed
|
|
17
|
+
|
|
18
|
+
Prose only. Filler-word trims, sentence merges, fragment style.
|
|
19
|
+
|
|
20
|
+
## What is byte-preserved
|
|
21
|
+
|
|
22
|
+
- Fenced code blocks ` ```lang ... ``` ` — exact
|
|
23
|
+
- Inline code `` ` `` — exact
|
|
24
|
+
- URLs — exact
|
|
25
|
+
- File paths — exact
|
|
26
|
+
- YAML frontmatter (between `---` markers) — exact
|
|
27
|
+
- List markers (`-`, `*`, `1.`) — kept, but item text may be shortened
|
|
28
|
+
- Section headings — kept, but text may be shortened
|
|
29
|
+
|
|
30
|
+
## Instructions to the assistant
|
|
31
|
+
|
|
32
|
+
1. Read the target file from the argument. If no argument, ask which file.
|
|
33
|
+
2. Show the user a diff (original vs compressed).
|
|
34
|
+
3. Ask for confirmation before writing.
|
|
35
|
+
4. On confirm: write the compressed version, keep the original at `<path>.pre-terse.bak`.
|
|
36
|
+
5. Report savings: original bytes → new bytes, percent saved, estimated tokens saved (bytes / 4).
|
|
37
|
+
|
|
38
|
+
Alternatively, use the deterministic compressor:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
kodelyth-ecc terse compress <path> [--dry-run] [--backup]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That runs `scripts/terse/compress.js` — a zero-dep Node script that:
|
|
45
|
+
- Byte-preserves code, URLs, paths, frontmatter
|
|
46
|
+
- Removes 40+ filler patterns
|
|
47
|
+
- Merges wrapped prose paragraphs
|
|
48
|
+
- Reports byte and token savings
|
|
49
|
+
|
|
50
|
+
Prefer the CLI for automated pipelines. Use the assistant path when you want a judgment-based rewrite that also restructures for clarity.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Switch reply compression level — /terse [lite|full|ultra|off]. Complements RTK (input savings) with output-side savings.
|
|
3
|
+
argument-hint: "[lite|full|ultra|off]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /terse — output compression
|
|
7
|
+
|
|
8
|
+
Activate the [terse-mode](../skills/terse-mode/SKILL.md) skill and set its dial.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
- `/terse` — set to `full` (default)
|
|
13
|
+
- `/terse lite` — light trim
|
|
14
|
+
- `/terse full` — telegram-style fragments
|
|
15
|
+
- `/terse ultra` — maximum compression
|
|
16
|
+
- `/terse off` — restore normal voice
|
|
17
|
+
|
|
18
|
+
## Instructions to the assistant
|
|
19
|
+
|
|
20
|
+
Read the arguments passed to this command. Set the terse-mode level:
|
|
21
|
+
|
|
22
|
+
- If args are empty → use `full`
|
|
23
|
+
- If args are one of `lite / full / ultra / off` → use that
|
|
24
|
+
- Any other value → answer briefly with the valid options and do not change the level
|
|
25
|
+
|
|
26
|
+
For the rest of this session (until `/terse off` or a new `/terse <level>`):
|
|
27
|
+
|
|
28
|
+
1. Load the rules from `skills/terse-mode/SKILL.md`
|
|
29
|
+
2. Apply the level's compression rules to every reply
|
|
30
|
+
3. **Preserve byte-exact**: code blocks, inline code, shell commands, error text, URLs, paths, identifiers, numbers, versions
|
|
31
|
+
4. Never translate — keep the user's language
|
|
32
|
+
5. Never compress memory captures, tool outputs, or file contents
|
|
33
|
+
|
|
34
|
+
Confirm activation in one line:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
terse: <level> — code and commands preserved byte-exact
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then answer whatever the user asks — in the new voice.
|
package/install.sh
CHANGED
|
@@ -233,7 +233,7 @@ HOME_DIR="$HOME"
|
|
|
233
233
|
WINDSURFRULES_DEST="" # only set for windsurf targets
|
|
234
234
|
|
|
235
235
|
case "$TARGET" in
|
|
236
|
-
claude-home)
|
|
236
|
+
claude-home|claude-code)
|
|
237
237
|
DEST="$HOME_DIR/.claude"
|
|
238
238
|
AGENTS_DEST="$DEST/agents"
|
|
239
239
|
SKILLS_DEST="$DEST/skills"
|
|
@@ -361,7 +361,7 @@ case "$TARGET" in
|
|
|
361
361
|
;;
|
|
362
362
|
*)
|
|
363
363
|
echo -e "${RED}Unknown target: $TARGET${RESET}"
|
|
364
|
-
echo "Valid targets: claude-home, antigravity, cursor-project, windsurf-project, windsurf-home, codex-home, opencode, cline, roocode, aider, kimi, gemini-project, gemini-home"
|
|
364
|
+
echo "Valid targets: claude-home (aka claude-code), antigravity, cursor-project, windsurf-project, windsurf-home, codex-home, opencode, cline, roocode, aider, kimi, gemini-project, gemini-home"
|
|
365
365
|
exit 1
|
|
366
366
|
;;
|
|
367
367
|
esac
|
|
@@ -375,8 +375,12 @@ fi
|
|
|
375
375
|
echo ""
|
|
376
376
|
|
|
377
377
|
# ── Confirm ───────────────────────────────────────────────────────────────────
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
# Honour KODELYTH_NONINTERACTIVE=1 for CI, npx pipes, and Windows powershell path.
|
|
379
|
+
if [[ "${KODELYTH_NONINTERACTIVE:-}" == "1" ]]; then
|
|
380
|
+
CONFIRM="Y"
|
|
381
|
+
echo -e "${CYAN} Non-interactive mode (KODELYTH_NONINTERACTIVE=1) — proceeding.${RESET}"
|
|
382
|
+
elif [[ -e /dev/tty ]] && [[ -r /dev/tty ]]; then
|
|
383
|
+
read -r -p "$(echo -e "${YELLOW}Proceed with install? [Y/n] ${RESET}")" CONFIRM </dev/tty || CONFIRM="Y"
|
|
380
384
|
CONFIRM="${CONFIRM:-Y}"
|
|
381
385
|
else
|
|
382
386
|
CONFIRM="Y"
|
|
@@ -476,7 +480,7 @@ echo -e "${BOLD}Installing components...${RESET}"
|
|
|
476
480
|
echo ""
|
|
477
481
|
|
|
478
482
|
case "$TARGET" in
|
|
479
|
-
claude-home)
|
|
483
|
+
claude-home|claude-code)
|
|
480
484
|
install_dir "$SCRIPT_DIR/agents" "$AGENTS_DEST" "Agents ($AGENT_COUNT)"
|
|
481
485
|
install_dir "$SCRIPT_DIR/skills" "$SKILLS_DEST" "Skills ($SKILL_COUNT)"
|
|
482
486
|
install_dir "$SCRIPT_DIR/commands" "$COMMANDS_DEST" "Commands ($CMD_COUNT)"
|
|
@@ -685,7 +689,7 @@ echo -e "${GREEN}${BOLD} ARMED AND OPERATIONAL.${RESET}"
|
|
|
685
689
|
echo ""
|
|
686
690
|
|
|
687
691
|
case "$TARGET" in
|
|
688
|
-
claude-home)
|
|
692
|
+
claude-home|claude-code)
|
|
689
693
|
echo -e "${BOLD} Claude Code — what to do now:${RESET}"
|
|
690
694
|
echo ""
|
|
691
695
|
echo " Open any project in Claude Code, then:"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 194 skills, 97 commands, parallel multi-agent commands, semantic intent routing, self-learning memory, and a built-in MCP server (16 tools / 6 prompts / 377 resources) that bridges to Claude Desktop, LangGraph, AutoGen, CrewAI, and OpenAI Agents SDK. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, RooCode, Aider, Kimi, and Gemini CLI.",
|
|
5
5
|
"author": "Kodelyth <github.com/sifxprime>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -298,6 +298,12 @@ function handleRequest(req, res) {
|
|
|
298
298
|
return jsonResponse(res, 200, { ok: true, installed: true, version: st.version, active: st.active, ...s });
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
if (p === '/api/terse') {
|
|
302
|
+
const ledger = require('../terse/ledger.js');
|
|
303
|
+
const s = ledger.summary({ days: Number(q.get('days')) || 30 });
|
|
304
|
+
return jsonResponse(res, 200, { ok: true, ...s });
|
|
305
|
+
}
|
|
306
|
+
|
|
301
307
|
if (p.startsWith('/api/')) return notFound(res);
|
|
302
308
|
|
|
303
309
|
// ── static fallback ───────────────────────────────────────────────────
|
|
@@ -246,8 +246,9 @@
|
|
|
246
246
|
</div>
|
|
247
247
|
</section>
|
|
248
248
|
|
|
249
|
-
<!-- ─────────
|
|
249
|
+
<!-- ───────── TOKEN SAVINGS (RTK + TERSE) ───────── -->
|
|
250
250
|
<section data-panel="rtk" hidden>
|
|
251
|
+
<h2 style="margin:0 0 10px 0;">Input savings (RTK)</h2>
|
|
251
252
|
<div class="grid" id="rtkCards"></div>
|
|
252
253
|
<hr class="sep">
|
|
253
254
|
<div class="row">
|
|
@@ -262,9 +263,17 @@
|
|
|
262
263
|
</div>
|
|
263
264
|
<hr class="sep">
|
|
264
265
|
<div class="card">
|
|
265
|
-
<h2>Daily savings (last 30 days)</h2>
|
|
266
|
+
<h2>Daily input savings (last 30 days)</h2>
|
|
266
267
|
<div id="rtkDaily">loading…</div>
|
|
267
268
|
</div>
|
|
269
|
+
<hr class="sep">
|
|
270
|
+
<h2 style="margin:20px 0 10px 0;">Output savings (Terse mode)</h2>
|
|
271
|
+
<div class="grid" id="terseCards"></div>
|
|
272
|
+
<hr class="sep">
|
|
273
|
+
<div class="card">
|
|
274
|
+
<h2>Daily output savings (last 30 days)</h2>
|
|
275
|
+
<div id="terseDaily">loading…</div>
|
|
276
|
+
</div>
|
|
268
277
|
</section>
|
|
269
278
|
|
|
270
279
|
<!-- ───────── MEMORY ───────── -->
|
|
@@ -715,6 +724,37 @@
|
|
|
715
724
|
} catch (e) {
|
|
716
725
|
$('#rtkCards').innerHTML = `<div class="empty">failed: ${escapeHtml(e.message)}</div>`;
|
|
717
726
|
}
|
|
727
|
+
// Load Terse-mode output-savings section in parallel.
|
|
728
|
+
loadTerse();
|
|
729
|
+
}
|
|
730
|
+
async function loadTerse() {
|
|
731
|
+
try {
|
|
732
|
+
const t = await fetch('/api/terse').then(r => r.json());
|
|
733
|
+
$('#terseCards').innerHTML = [
|
|
734
|
+
statCard(fmtN(t.totalSaved), 'Output tokens saved', `${t.avgSavingsPct}% vs baseline`),
|
|
735
|
+
statCard(fmtN(t.totalActual), 'Output tokens produced', 'under terse mode'),
|
|
736
|
+
statCard(fmtN(t.totalTurns), 'Turns tracked', 'with terse active'),
|
|
737
|
+
statCard(Object.keys(t.byLevel || {}).join(' · ') || '—', 'Levels used', 'lite / full / ultra'),
|
|
738
|
+
].join('');
|
|
739
|
+
const daily = t.daily || [];
|
|
740
|
+
if (daily.length) {
|
|
741
|
+
const max = Math.max(...daily.map(d => d.saved || 0));
|
|
742
|
+
$('#terseDaily').innerHTML = daily.slice(-30).map(d => {
|
|
743
|
+
const pct = max ? (d.saved / max) * 100 : 0;
|
|
744
|
+
return `<div style="display:flex;gap:8px;align-items:center;margin:3px 0;font-size:12px;">
|
|
745
|
+
<span class="muted" style="width:90px;font-family:monospace;">${escapeHtml(d.date || '')}</span>
|
|
746
|
+
<div style="flex:1;background:#f1f5f9;border-radius:3px;height:14px;overflow:hidden;">
|
|
747
|
+
<div style="background:#60a5fa;height:100%;width:${pct}%;"></div>
|
|
748
|
+
</div>
|
|
749
|
+
<span style="width:110px;text-align:right;font-family:monospace;">${fmtN(d.saved)}</span>
|
|
750
|
+
</div>`;
|
|
751
|
+
}).join('');
|
|
752
|
+
} else {
|
|
753
|
+
$('#terseDaily').innerHTML = '<div class="empty">no terse-mode turns tracked yet — activate with <code>/terse</code></div>';
|
|
754
|
+
}
|
|
755
|
+
} catch (e) {
|
|
756
|
+
$('#terseCards').innerHTML = `<div class="empty">terse: ${escapeHtml(e.message)}</div>`;
|
|
757
|
+
}
|
|
718
758
|
}
|
|
719
759
|
function fmtN(n) { return Number(n || 0).toLocaleString(); }
|
|
720
760
|
|
package/scripts/rtk/index.js
CHANGED
|
@@ -18,9 +18,10 @@ const fs = require('fs');
|
|
|
18
18
|
|
|
19
19
|
// ── ECC install target → RTK agent flag ──────────────────────────────────────
|
|
20
20
|
// Reference: `rtk init --help` and RTK README "Supported AI Tools" table.
|
|
21
|
+
// Keyed by canonical install.sh target names (claude-home, gemini-home, ...).
|
|
22
|
+
// Common aliases (claude-code, gemini-cli) are normalised in normalizeTarget().
|
|
21
23
|
const TARGET_MAP = {
|
|
22
|
-
'claude-
|
|
23
|
-
'cursor': ['init', '-g', '--agent', 'cursor'],
|
|
24
|
+
'claude-home': ['init', '-g'],
|
|
24
25
|
'cursor-project': ['init', '--agent', 'cursor'],
|
|
25
26
|
'windsurf-home': ['init', '-g', '--agent', 'windsurf'],
|
|
26
27
|
'windsurf-project': ['init', '--agent', 'windsurf'],
|
|
@@ -28,22 +29,32 @@ const TARGET_MAP = {
|
|
|
28
29
|
'codex-home': ['init', '-g', '--codex'],
|
|
29
30
|
'opencode': ['init', '-g', '--opencode'],
|
|
30
31
|
'cline': ['init', '--agent', 'cline'],
|
|
31
|
-
'gemini-
|
|
32
|
+
'gemini-home': ['init', '-g', '--gemini'],
|
|
33
|
+
'roocode': ['init', '--agent', 'cline'], // roocode is cline-compatible per RTK
|
|
34
|
+
'kimi': ['init', '-g'], // treat like claude-code hook
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
// Normalise well-known aliases → canonical target names used by install.sh.
|
|
38
|
+
function normalizeTarget(t) {
|
|
39
|
+
if (t === 'claude-code') return 'claude-home';
|
|
40
|
+
if (t === 'gemini-cli') return 'gemini-home';
|
|
41
|
+
if (t === 'cursor') return 'cursor-project';
|
|
42
|
+
return t;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
// ── Detect which IDEs ECC has already been installed for on this machine ─────
|
|
35
46
|
// Returns list of ECC install-target strings that have visible ECC artifacts.
|
|
36
47
|
function detectInstalledTargets() {
|
|
37
48
|
const home = os.homedir();
|
|
38
49
|
const targets = [];
|
|
39
50
|
const checks = [
|
|
40
|
-
{ target: 'claude-
|
|
41
|
-
{ target: 'cursor',
|
|
51
|
+
{ target: 'claude-home', dir: path.join(home, '.claude', 'agents') },
|
|
52
|
+
{ target: 'cursor-project', dir: path.join(home, '.cursor', 'rules') },
|
|
42
53
|
{ target: 'windsurf-home', dir: path.join(home, '.codeium', 'windsurf', 'memories') },
|
|
43
54
|
{ target: 'antigravity', dir: path.join(home, '.antigravity') },
|
|
44
55
|
{ target: 'codex-home', dir: path.join(home, '.codex') },
|
|
45
56
|
{ target: 'opencode', dir: path.join(home, '.config', 'opencode') },
|
|
46
|
-
{ target: 'gemini-
|
|
57
|
+
{ target: 'gemini-home', dir: path.join(home, '.gemini') },
|
|
47
58
|
];
|
|
48
59
|
for (const { target, dir } of checks) {
|
|
49
60
|
try {
|
|
@@ -116,15 +127,16 @@ function enableFor(target, { log = () => {} } = {}) {
|
|
|
116
127
|
if (!isInstalled()) {
|
|
117
128
|
return { enabled: false, skipped: true, reason: 'rtk binary not on PATH' };
|
|
118
129
|
}
|
|
119
|
-
const
|
|
130
|
+
const canonical = normalizeTarget(target);
|
|
131
|
+
const rtkArgs = TARGET_MAP[canonical];
|
|
120
132
|
if (!rtkArgs) {
|
|
121
133
|
return { enabled: false, skipped: true, reason: `no RTK mapping for target "${target}"` };
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
log(`[rtk] wiring RTK into ${
|
|
136
|
+
log(`[rtk] wiring RTK into ${canonical} …`);
|
|
125
137
|
// --auto-patch is only accepted by the default Claude Code hook flow.
|
|
126
138
|
// Other agent flags (--codex, --gemini, --opencode, --agent X) reject it.
|
|
127
|
-
const finalArgs =
|
|
139
|
+
const finalArgs = canonical === 'claude-home' ? [...rtkArgs, '--auto-patch'] : rtkArgs;
|
|
128
140
|
const r = spawnSync('rtk', finalArgs, { encoding: 'utf8' });
|
|
129
141
|
const output = (r.stdout || '') + (r.stderr || '');
|
|
130
142
|
if (r.status !== 0) {
|
|
@@ -175,6 +187,7 @@ function savings({ days = 30 } = {}) {
|
|
|
175
187
|
|
|
176
188
|
module.exports = {
|
|
177
189
|
TARGET_MAP,
|
|
190
|
+
normalizeTarget,
|
|
178
191
|
detectInstalledTargets,
|
|
179
192
|
isInstalled,
|
|
180
193
|
getVersion,
|
|
Binary file
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// scripts/terse/ledger.js
|
|
2
|
+
// Output-token savings ledger. Stores one JSONL row per terse-active turn.
|
|
3
|
+
// Zero deps. Reads only. Writes append-only.
|
|
4
|
+
//
|
|
5
|
+
// Row shape:
|
|
6
|
+
// { ts, level, rawEstimate, actual, saved, projectHash?, source }
|
|
7
|
+
//
|
|
8
|
+
// Path: ~/.kodelythecc/terse/ledger.jsonl (overridable via KODELYTH_TERSE_DIR)
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const DIR = process.env.KODELYTH_TERSE_DIR
|
|
17
|
+
|| path.join(os.homedir(), '.kodelythecc', 'terse');
|
|
18
|
+
const LEDGER = path.join(DIR, 'ledger.jsonl');
|
|
19
|
+
|
|
20
|
+
function ensureDir() { fs.mkdirSync(DIR, { recursive: true }); }
|
|
21
|
+
|
|
22
|
+
// Rough token count: ~4 chars/token English. Not exact — good enough for savings math.
|
|
23
|
+
function estimateTokens(text) {
|
|
24
|
+
if (!text) return 0;
|
|
25
|
+
return Math.round(Buffer.byteLength(text, 'utf8') / 4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Baseline output multiplier per level.
|
|
29
|
+
// Empirically: full ≈ 0.5x normal, ultra ≈ 0.35x, lite ≈ 0.75x.
|
|
30
|
+
const RAW_MULT = { lite: 1.33, full: 2.0, ultra: 2.85, off: 1.0 };
|
|
31
|
+
|
|
32
|
+
function appendTurn({ actualText, level = 'full', source = 'unknown', projectHash = null }) {
|
|
33
|
+
ensureDir();
|
|
34
|
+
const actual = estimateTokens(actualText);
|
|
35
|
+
const rawEstimate = Math.round(actual * (RAW_MULT[level] || 1));
|
|
36
|
+
const saved = Math.max(0, rawEstimate - actual);
|
|
37
|
+
const row = {
|
|
38
|
+
ts: new Date().toISOString(),
|
|
39
|
+
level,
|
|
40
|
+
rawEstimate,
|
|
41
|
+
actual,
|
|
42
|
+
saved,
|
|
43
|
+
source,
|
|
44
|
+
...(projectHash ? { projectHash } : {}),
|
|
45
|
+
};
|
|
46
|
+
fs.appendFileSync(LEDGER, JSON.stringify(row) + '\n');
|
|
47
|
+
return row;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readAll() {
|
|
51
|
+
if (!fs.existsSync(LEDGER)) return [];
|
|
52
|
+
return fs.readFileSync(LEDGER, 'utf8')
|
|
53
|
+
.split('\n')
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
56
|
+
.filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function summary({ days = 30 } = {}) {
|
|
60
|
+
const rows = readAll();
|
|
61
|
+
if (rows.length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
totalTurns: 0, totalActual: 0, totalSaved: 0, totalRawEstimate: 0,
|
|
64
|
+
avgSavingsPct: 0, daily: [], byLevel: {},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
let totalActual = 0, totalSaved = 0, totalRaw = 0;
|
|
68
|
+
const daily = new Map();
|
|
69
|
+
const byLevel = {};
|
|
70
|
+
const cutoff = Date.now() - days * 24 * 3600 * 1000;
|
|
71
|
+
|
|
72
|
+
for (const r of rows) {
|
|
73
|
+
totalActual += r.actual || 0;
|
|
74
|
+
totalSaved += r.saved || 0;
|
|
75
|
+
totalRaw += r.rawEstimate || 0;
|
|
76
|
+
|
|
77
|
+
const ts = new Date(r.ts).getTime();
|
|
78
|
+
if (!isNaN(ts) && ts >= cutoff) {
|
|
79
|
+
const day = r.ts.slice(0, 10);
|
|
80
|
+
const d = daily.get(day) || { date: day, actual: 0, saved: 0, turns: 0 };
|
|
81
|
+
d.actual += r.actual || 0;
|
|
82
|
+
d.saved += r.saved || 0;
|
|
83
|
+
d.turns += 1;
|
|
84
|
+
daily.set(day, d);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const lv = r.level || 'unknown';
|
|
88
|
+
byLevel[lv] = (byLevel[lv] || 0) + 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
totalTurns: rows.length,
|
|
93
|
+
totalActual,
|
|
94
|
+
totalSaved,
|
|
95
|
+
totalRawEstimate: totalRaw,
|
|
96
|
+
avgSavingsPct: totalRaw ? Math.round((totalSaved / totalRaw) * 100) : 0,
|
|
97
|
+
daily: [...daily.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
98
|
+
byLevel,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { appendTurn, readAll, summary, estimateTokens, LEDGER, DIR };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: terse-mode
|
|
3
|
+
description: Compress LLM output tokens by 40-70% without losing information. Keeps code, commands, URLs, paths byte-exact. Four dial levels (lite / full / ultra / off). Complements RTK (which shrinks input) — together, ~55-65% total token savings on typical coding sessions.
|
|
4
|
+
origin: ECC (inspired by Caveman by juliusbrussee — MIT)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Terse Mode — Output Token Compressor
|
|
8
|
+
|
|
9
|
+
Make the AI's **mouth** smaller, not its **brain** smaller. Same answers, fewer words, byte-exact code.
|
|
10
|
+
|
|
11
|
+
## What this is
|
|
12
|
+
|
|
13
|
+
A prompt-level output compression skill. When active, the AI drops verbose filler while preserving every technical detail. Signals correctness, not chattiness.
|
|
14
|
+
|
|
15
|
+
## When to activate
|
|
16
|
+
|
|
17
|
+
- Any coding session where reply length is dominant (explanations, reviews, planning)
|
|
18
|
+
- Extended sessions where you want output tokens to stretch further
|
|
19
|
+
- Reading the AI's output out loud sounds like padding — that's the tell
|
|
20
|
+
|
|
21
|
+
## When to skip
|
|
22
|
+
|
|
23
|
+
- User explicitly wants long, teaching-oriented explanations
|
|
24
|
+
- Documentation-writing tasks where the output IS the artifact
|
|
25
|
+
- First contact with a new user who hasn't opted in
|
|
26
|
+
|
|
27
|
+
## The rules — ALWAYS PRESERVED
|
|
28
|
+
|
|
29
|
+
The AI must **byte-preserve** these no matter which level:
|
|
30
|
+
|
|
31
|
+
1. Fenced code blocks (```lang ... ```) — exact contents, no changes
|
|
32
|
+
2. Inline `code` — exact
|
|
33
|
+
3. Shell commands and error text — exact
|
|
34
|
+
4. URLs, file paths, function names, identifiers — exact
|
|
35
|
+
5. Numbers, versions, hashes — exact
|
|
36
|
+
6. YAML/JSON/config snippets — exact
|
|
37
|
+
|
|
38
|
+
## Levels
|
|
39
|
+
|
|
40
|
+
### `off` — normal AI voice
|
|
41
|
+
Default. No compression.
|
|
42
|
+
|
|
43
|
+
### `lite` — light trim
|
|
44
|
+
- Drop obvious filler ("basically", "essentially", "in order to", "the reason is that")
|
|
45
|
+
- Convert "you should X" → "X"
|
|
46
|
+
- Merge sentences that repeat the same idea
|
|
47
|
+
- Keep normal-looking paragraphs
|
|
48
|
+
|
|
49
|
+
Example — same info, ~25% shorter:
|
|
50
|
+
> The React component re-renders because a new object reference is created on each render. Wrap the object in `useMemo`.
|
|
51
|
+
|
|
52
|
+
### `full` — default terse
|
|
53
|
+
- Fragment sentences: "New ref each render. Wrap in `useMemo`."
|
|
54
|
+
- Drop transitional phrases entirely
|
|
55
|
+
- Use `→` and `=` freely instead of prose connectors
|
|
56
|
+
- Assume user is a senior engineer
|
|
57
|
+
|
|
58
|
+
Example — ~50% shorter:
|
|
59
|
+
> New ref each render → re-render. Wrap object in `useMemo`.
|
|
60
|
+
|
|
61
|
+
### `ultra` — maximum compression
|
|
62
|
+
- Telegram-style. Symbols over words. Numbered points, one line each.
|
|
63
|
+
- Only expand if the compression would lose a technical fact.
|
|
64
|
+
|
|
65
|
+
Example — ~70% shorter:
|
|
66
|
+
> Ref/render. `useMemo` it.
|
|
67
|
+
|
|
68
|
+
## Interaction with other ECC systems
|
|
69
|
+
|
|
70
|
+
- **RTK** compresses input tokens (tool output → LLM). Terse compresses output tokens (LLM → user). Stack together for ~55-65% total savings.
|
|
71
|
+
- **kodelyth-memory** captures still capture in normal voice — memory recall is for machines, not humans. Terse mode does NOT affect memory captures.
|
|
72
|
+
- **`code-reviewer`** and **`release-captain`** agents can opt in via `--terse` flag for one-line PR comments and short commit messages.
|
|
73
|
+
|
|
74
|
+
## What terse mode NEVER does
|
|
75
|
+
|
|
76
|
+
- Change what the AI knows
|
|
77
|
+
- Skip technical details or trade-offs
|
|
78
|
+
- Compress code, commands, or errors
|
|
79
|
+
- Translate — write in the user's own language, just tighter
|
|
80
|
+
- Auto-activate — user opts in via `/terse` or CLI
|
|
81
|
+
|
|
82
|
+
## Activation
|
|
83
|
+
|
|
84
|
+
- Slash command: `/terse [lite|full|ultra|off]` — sticks for the session
|
|
85
|
+
- CLI: `kodelyth-ecc terse enable [--target claude-code|--all]` — installs the skill + command into your AI tool
|
|
86
|
+
- Statusline (Claude Code): shows `[TERSE ⚡ 12.4k]` — lifetime output tokens saved
|
|
87
|
+
|
|
88
|
+
## Honest numbers
|
|
89
|
+
|
|
90
|
+
- On verbose explain-heavy tasks: 60-70% output token reduction
|
|
91
|
+
- On terse debugging chats: 20-30% (less to compress)
|
|
92
|
+
- On documentation writing: net zero — skip this mode
|
|
93
|
+
- Skill itself adds ~800-1200 input tokens per turn. Below ~2k output tokens, may be net-negative
|
|
94
|
+
|
|
95
|
+
## Attribution
|
|
96
|
+
|
|
97
|
+
Design inspired by [Caveman](https://github.com/JuliusBrussee/caveman) (MIT, by Julius Brussee). ECC's implementation is independent — different prompt, different levels dial (no `wenyan`, no cavespeak persona), and integrated with our RTK ledger for combined input+output tracking.
|