kodelyth-ecc 2.24.10 → 2.25.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/CHANGELOG.md +72 -0
- package/README.md +5 -2
- package/bin/kodelyth-ecc.js +18 -1
- package/package.json +1 -1
- package/rules/common/agent-intent-routing.md +4 -0
- package/scripts/codebase/index.js +59 -2
- package/scripts/hooks/session-start.js +12 -0
- package/scripts/mcp/register-self.js +77 -3
- package/scripts/memory/instinct-inject.js +67 -0
- package/scripts/rtk/index.js +43 -1
- package/scripts/terse/ledger.js +16 -5
- package/skills/terse-mode/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.25.0 — Foundations: honest numbers, a memory that learns, MCP beyond Claude (November 2026)
|
|
6
|
+
|
|
7
|
+
The first cut of the v3.0 "Foundations" track. Almost no new surface area — this
|
|
8
|
+
release makes existing systems tell the truth and wires up machinery that was
|
|
9
|
+
built but never connected. Every change ships with tests (48 test files, all
|
|
10
|
+
passing).
|
|
11
|
+
|
|
12
|
+
**Truth-in-measurement.** Three claims did not match the code, and an enterprise
|
|
13
|
+
audit would catch all three:
|
|
14
|
+
|
|
15
|
+
- The Terse runtime ledger claimed "every terse-active turn is recorded" — but
|
|
16
|
+
nothing wrote it, and its per-turn savings were modeled from a fixed multiplier,
|
|
17
|
+
not measured. Rows are now flagged `estimated: true`, the docs say plainly the
|
|
18
|
+
figure is an estimate (the Terse *file* compressor stays a real measurement),
|
|
19
|
+
and the unshipped statusline badge claim is gone.
|
|
20
|
+
- The RTK "5,107,394 tokens / 64.1% — all real" headline was hand-authored copy,
|
|
21
|
+
not a computed number. It is now labelled an example snapshot that points at
|
|
22
|
+
`rtk gain` for your own live figures. RTK's `gain` measurement was, and stays,
|
|
23
|
+
real.
|
|
24
|
+
- The codebase-graph binary's `--version` is unreliable — builds report an old
|
|
25
|
+
number while exposing much newer tools. ECC no longer trusts it: it detects
|
|
26
|
+
what the binary can actually do and flags the inconsistency instead of showing
|
|
27
|
+
a number as fact.
|
|
28
|
+
|
|
29
|
+
**The memory that learns, now actually learns.** The structured instinct engine
|
|
30
|
+
recorded every correction with confidence, decay, and outcome scoring — and
|
|
31
|
+
nothing ever read it, so none of it influenced a session. Session start now
|
|
32
|
+
injects the high-confidence, non-stale instincts for the current project. The
|
|
33
|
+
dormant loop is closed.
|
|
34
|
+
|
|
35
|
+
**RTK won't wire the wrong binary.** `reachingforthejack/rtk` (Rust Type Kit)
|
|
36
|
+
ships a binary also called `rtk`; ECC used to treat any `rtk` on PATH as Rust
|
|
37
|
+
Token Killer and would patch hooks into the wrong tool. It now verifies the
|
|
38
|
+
signature (`rtk gain`) before wiring or measuring anything.
|
|
39
|
+
|
|
40
|
+
**MCP — and therefore memory — reaches beyond Claude Code.** ECC auto-registered
|
|
41
|
+
its MCP server for Claude only, so the whole tool surface, memory included, was
|
|
42
|
+
effectively Claude-exclusive. It now also writes the MCP config for Cursor,
|
|
43
|
+
Windsurf, and Gemini CLI (the IDEs that use the same JSON config shape), reusing
|
|
44
|
+
the hardened merge that never clobbers your other servers. Codex (TOML) and
|
|
45
|
+
Cline/Roo (editor storage) are the next step.
|
|
46
|
+
|
|
47
|
+
**Fixes found along the way.** `statusAll()` in the MCP registrar called a
|
|
48
|
+
function that no longer existed (`readJson`), so `kodelythecc doctor` and the
|
|
49
|
+
`mcp` status command both threw "readJson is not defined". Fixed.
|
|
50
|
+
|
|
51
|
+
**Routing.** The intent router learned the newer codebase-graph capabilities:
|
|
52
|
+
smart test selection (`detect_changes scope:tests`), hot-path/complexity queries,
|
|
53
|
+
code-to-docs links, and ADRs.
|
|
54
|
+
|
|
55
|
+
## v2.24.11 — every docs page gets its own diagram (October 2026)
|
|
56
|
+
|
|
57
|
+
Documentation and site only. No code change.
|
|
58
|
+
|
|
59
|
+
The website renders a doc's `og_image` on the page now, not just in the social
|
|
60
|
+
card, so the image a doc points at is something readers actually see above the
|
|
61
|
+
content. Most docs pointed at shared generic cards — one card was doing duty for
|
|
62
|
+
four unrelated pages — which is worse on-page than no image at all.
|
|
63
|
+
|
|
64
|
+
Seventeen of the eighteen docs now carry a purpose-built 1200x630 diagram that
|
|
65
|
+
teaches their own subject: the Arena's GOD-vs-EVIL loop, swarm's fan-out into
|
|
66
|
+
isolated worktrees, the real RTK token ledger, the eight hook events sized by how
|
|
67
|
+
many entries each carries, evolve's propose-then-you-review gate, the MCP tool
|
|
68
|
+
surface, and so on. Each gains `hero_alt` describing the diagram's data, so it is
|
|
69
|
+
legible to a screen reader and in the social card. The index hub keeps the main
|
|
70
|
+
product card, which is the right image for a landing page.
|
|
71
|
+
|
|
72
|
+
The diagrams live as static SVG in the website repo; this release carries the
|
|
73
|
+
`og_image` / `hero_alt` frontmatter for the docs that ship from here.
|
|
74
|
+
|
|
75
|
+
**693 tests across 44 files.**
|
|
76
|
+
|
|
5
77
|
## v2.24.10 — the same config-destroying read, now on settings.json (October 2026)
|
|
6
78
|
|
|
7
79
|
**Data-loss fix.** 2.24.9 fixed a nullable JSON read coalesced to `{}` in
|
package/README.md
CHANGED
|
@@ -524,13 +524,16 @@ kodelythecc rtk gain --all # raw rtk savings output
|
|
|
524
524
|
kodelythecc rtk --help # focused help
|
|
525
525
|
```
|
|
526
526
|
|
|
527
|
-
###
|
|
527
|
+
### Example snapshot (one maintainer's Mac, one point in time)
|
|
528
|
+
|
|
529
|
+
Not a guaranteed figure — RTK measures savings per command, and yours depend on
|
|
530
|
+
which commands you run. Run `rtk gain` to see your own live numbers.
|
|
528
531
|
|
|
529
532
|
```
|
|
530
533
|
total_commands: 1,285
|
|
531
534
|
total_input: 7,964,612 (raw)
|
|
532
535
|
total_output: 2,858,501 (after RTK filter)
|
|
533
|
-
total_saved: 5,107,394 ← 64.1%
|
|
536
|
+
total_saved: 5,107,394 ← 64.1% on this sample
|
|
534
537
|
```
|
|
535
538
|
|
|
536
539
|
---
|
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -1883,7 +1883,24 @@ function runPostInstall(status, args) {
|
|
|
1883
1883
|
const marker = r.action === 'added' ? '✓ added' : r.action === 'updated' ? '✓ updated' : r.action === 'unchanged' ? '· unchanged' : `· ${r.action}`;
|
|
1884
1884
|
process.stdout.write(` ${marker}: ${r.file}\n`);
|
|
1885
1885
|
}
|
|
1886
|
-
|
|
1886
|
+
|
|
1887
|
+
// Also wire ECC's MCP server into the specific non-Claude IDE this install
|
|
1888
|
+
// targeted, when that IDE uses a JSON MCP config we can safely merge
|
|
1889
|
+
// (Cursor, Windsurf, Gemini CLI). This is what extends memory and the whole
|
|
1890
|
+
// MCP tool surface beyond Claude Code — previously they were Claude-only.
|
|
1891
|
+
const mcpTargetIdx = args.indexOf('--target');
|
|
1892
|
+
const mcpRawTarget = mcpTargetIdx >= 0 && args[mcpTargetIdx + 1] ? args[mcpTargetIdx + 1] : 'claude-code';
|
|
1893
|
+
if (!/^claude/.test(mcpRawTarget)) {
|
|
1894
|
+
for (const r of reg.registerForTarget(mcpRawTarget, { cwd: process.cwd(), log: () => {} })) {
|
|
1895
|
+
if (r.action === 'added' || r.action === 'updated') {
|
|
1896
|
+
process.stdout.write(` ✓ ${r.action}: ${r.file}\n`);
|
|
1897
|
+
} else if (r.action === 'error') {
|
|
1898
|
+
process.stdout.write(` · skipped ${r.file}: ${r.error}\n`);
|
|
1899
|
+
}
|
|
1900
|
+
// 'unsupported' / 'unchanged' stay quiet — not every target has a JSON MCP config
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
process.stdout.write(` · Restart your AI tool to pick up the server\n`);
|
|
1887
1904
|
} catch (e) {
|
|
1888
1905
|
process.stderr.write(`[mcp] self-register skipped: ${e.message}\n`);
|
|
1889
1906
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.25.0",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 196 skills, 103 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 on macOS, Linux and Windows 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",
|
|
@@ -60,6 +60,10 @@ Beyond the 10 priority tiers below, watch for these newer signals — they map t
|
|
|
60
60
|
| "talk shorter" / "be brief" / "less verbose" / "caveman mode" | `/terse` (skill activation) |
|
|
61
61
|
| "who calls X" / "where is X used" / "trace the call chain" / "impact analysis" | `codebase-memory-mcp` via `search_graph` / `trace_path` |
|
|
62
62
|
| "index this project" / "build the graph" / "scan the codebase" | `codebase-memory-mcp` via `index_repository` |
|
|
63
|
+
| "which tests does this change affect" / "what tests should I run" / "smart test selection" | `codebase-memory-mcp` via `detect_changes` with `scope: "tests"` (Smart CI) |
|
|
64
|
+
| "find the bottleneck" / "hottest paths" / "most complex functions" / "refactor candidates" | `codebase-memory-mcp` via `query_graph` (complexity / hot-path metrics) |
|
|
65
|
+
| "what docs explain X" / "where is X documented" / "link code to docs" | `codebase-memory-mcp` via `trace_path` (inbound `MENTIONS` edges) |
|
|
66
|
+
| "record this decision" / "write an ADR" / "architecture decision record" | `codebase-memory-mcp` via `manage_adr` |
|
|
63
67
|
| "remove ECC" / "uninstall kodelythecc" / "clean this up" | Suggest `kodelythecc uninstall --dry-run` |
|
|
64
68
|
| "how much did I save" / "show my token savings" / "RTK stats" | `kodelythecc dashboard` OR `kodelythecc rtk gain` |
|
|
65
69
|
| "install for another IDE" / "add to Cursor / Windsurf / Antigravity" | `kodelythecc --target <ide>` |
|
|
@@ -22,6 +22,52 @@ function getVersion() {
|
|
|
22
22
|
catch { return null; }
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// The binary's `--version` string has proven unreliable: builds have reported an
|
|
26
|
+
// old number (e.g. 0.8.1) while exposing much newer tools and the `--ui` (CBM
|
|
27
|
+
// Lens) flag. So detect what the binary can actually DO from its own help text
|
|
28
|
+
// rather than trusting the version. Pure parser (unit-tested) + a thin probe.
|
|
29
|
+
const KNOWN_CAPABILITIES = {
|
|
30
|
+
lens: /--ui\b/, // CBM Lens UI (v0.12+)
|
|
31
|
+
traces: /\bingest_traces\b/, // runtime-trace ingestion
|
|
32
|
+
cypher: /\bquery_graph\b/, // Cypher + complexity/hot-path metrics
|
|
33
|
+
smart_ci: /\bdetect_changes\b/, // Smart CI test selection
|
|
34
|
+
adr: /\bmanage_adr\b/, // ADR nodes
|
|
35
|
+
cross_repo: /cross[-_]repo/, // cross-repo intelligence
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function parseCapabilities(helpText) {
|
|
39
|
+
const h = String(helpText || '');
|
|
40
|
+
const caps = {};
|
|
41
|
+
for (const [name, re] of Object.entries(KNOWN_CAPABILITIES)) caps[name] = re.test(h);
|
|
42
|
+
return caps;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// When the binary exposes clearly-newer capabilities but reports an old version,
|
|
46
|
+
// the version string is not trustworthy — say so rather than displaying a number
|
|
47
|
+
// ECC would otherwise treat as fact. Returns a note string, or null.
|
|
48
|
+
function versionNote(reportedVersion, caps) {
|
|
49
|
+
const hasNew = !!(caps && (caps.lens || caps.traces || caps.cross_repo));
|
|
50
|
+
const looksOld = !!(reportedVersion && /\b0\.[0-8]\./.test(reportedVersion));
|
|
51
|
+
if (hasNew && looksOld) {
|
|
52
|
+
return `binary reports "${reportedVersion}" but exposes newer capabilities — the version string is unreliable; capability detection is authoritative. Run \`kodelythecc codebase install\` to refresh.`;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Probe the binary's help (different builds print tools under `--help` or
|
|
58
|
+
// `cli --help`) and parse once. Returns the detected capability map, or all-false
|
|
59
|
+
// if the binary is absent.
|
|
60
|
+
function capabilities() {
|
|
61
|
+
if (!isInstalled()) return parseCapabilities('');
|
|
62
|
+
let help = '';
|
|
63
|
+
for (const argv of [['--help'], ['cli', '--help']]) {
|
|
64
|
+
try {
|
|
65
|
+
help += '\n' + execFileSync(BIN, argv, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
66
|
+
} catch { /* try the next help surface */ }
|
|
67
|
+
}
|
|
68
|
+
return parseCapabilities(help);
|
|
69
|
+
}
|
|
70
|
+
|
|
25
71
|
function install({ log = () => {} } = {}) {
|
|
26
72
|
if (isInstalled()) return { installed: false, skipped: true, reason: 'already installed', version: getVersion() };
|
|
27
73
|
if (os.platform() === 'win32') {
|
|
@@ -95,7 +141,15 @@ function status() {
|
|
|
95
141
|
const home = os.homedir();
|
|
96
142
|
const c = path.join(home, '.cache', 'codebase-memory-mcp');
|
|
97
143
|
if (fs.existsSync(c)) cacheDir = c;
|
|
98
|
-
|
|
144
|
+
const caps = capabilities();
|
|
145
|
+
return {
|
|
146
|
+
installed: true,
|
|
147
|
+
version,
|
|
148
|
+
version_note: versionNote(version, caps),
|
|
149
|
+
capabilities: caps,
|
|
150
|
+
indexed_projects,
|
|
151
|
+
cache_dir: cacheDir,
|
|
152
|
+
};
|
|
99
153
|
}
|
|
100
154
|
|
|
101
155
|
// ── Structural query proxy (thin wrapper over `codebase-memory-mcp cli`) ────
|
|
@@ -140,4 +194,7 @@ function dashboardSnapshot() {
|
|
|
140
194
|
return out;
|
|
141
195
|
}
|
|
142
196
|
|
|
143
|
-
module.exports = {
|
|
197
|
+
module.exports = {
|
|
198
|
+
isInstalled, getVersion, install, autoConfigureAgents, status, query, dashboardSnapshot,
|
|
199
|
+
capabilities, parseCapabilities, versionNote,
|
|
200
|
+
};
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
log
|
|
23
23
|
} = require('../lib/utils');
|
|
24
24
|
const { resolveProjectContext, writeSessionLease, resolveSessionId } = require('../lib/observer-sessions');
|
|
25
|
+
const { summarizeStructuredInstincts } = require('../memory/instinct-inject');
|
|
25
26
|
const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager');
|
|
26
27
|
const { listAliases } = require('../lib/session-aliases');
|
|
27
28
|
const { detectProjectType } = require('../lib/project-detect');
|
|
@@ -380,6 +381,17 @@ async function main() {
|
|
|
380
381
|
additionalContextParts.push(instinctSummary);
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
// Also inject from the structured instinct store (scripts/memory/instincts.js).
|
|
385
|
+
// It was written on every correction but never read — this activates the
|
|
386
|
+
// dormant self-learning loop. Fully fail-safe: never breaks session start.
|
|
387
|
+
const structuredInstinctSummary = summarizeStructuredInstincts(
|
|
388
|
+
(observerContext && observerContext.projectRoot) || process.cwd(),
|
|
389
|
+
{ log }
|
|
390
|
+
);
|
|
391
|
+
if (structuredInstinctSummary) {
|
|
392
|
+
additionalContextParts.push(structuredInstinctSummary);
|
|
393
|
+
}
|
|
394
|
+
|
|
383
395
|
// Check for recent session files (last 7 days)
|
|
384
396
|
const recentSessions = dedupeRecentSessions(sessionSearchDirs);
|
|
385
397
|
|
|
@@ -25,6 +25,67 @@ function claudeDesktopConfigPath() {
|
|
|
25
25
|
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// MCP config paths for the non-Claude IDEs ECC can auto-wire. Every target below
|
|
29
|
+
// uses the SAME `{ mcpServers: { <name>: { command, args, env } } }` shape as
|
|
30
|
+
// Claude Code, so the hardened readConfig + registerInFile merge works for all of
|
|
31
|
+
// them unchanged — only the file location differs. That is why memory and the
|
|
32
|
+
// whole MCP tool surface, previously Claude-only, reach these IDEs out of the
|
|
33
|
+
// box once this runs.
|
|
34
|
+
//
|
|
35
|
+
// Deliberately excluded (different format or fragile location — handled manually
|
|
36
|
+
// for now, tracked as a follow-up):
|
|
37
|
+
// - Codex CLI → ~/.codex/config.toml (TOML, not JSON)
|
|
38
|
+
// - Cline / Roo → VS Code globalStorage (editor-version-specific path)
|
|
39
|
+
function mcpPathsForTarget(target, cwd = process.cwd()) {
|
|
40
|
+
const home = os.homedir();
|
|
41
|
+
switch (target) {
|
|
42
|
+
case 'claude-code':
|
|
43
|
+
case 'claude-home':
|
|
44
|
+
return [claudeCodeConfigPath()];
|
|
45
|
+
case 'cursor':
|
|
46
|
+
case 'cursor-project':
|
|
47
|
+
return [path.join(cwd, '.cursor', 'mcp.json')];
|
|
48
|
+
case 'windsurf':
|
|
49
|
+
case 'windsurf-home':
|
|
50
|
+
case 'windsurf-project':
|
|
51
|
+
return [path.join(home, '.codeium', 'windsurf', 'mcp_config.json')];
|
|
52
|
+
case 'gemini-cli':
|
|
53
|
+
case 'gemini-home':
|
|
54
|
+
case 'gemini-project':
|
|
55
|
+
return [path.join(home, '.gemini', 'settings.json')];
|
|
56
|
+
default:
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Returns the list of targets this module can auto-wire (for help text / docs).
|
|
62
|
+
function supportedMcpTargets() {
|
|
63
|
+
return ['claude-code', 'cursor', 'windsurf', 'gemini-cli'];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Register ECC's MCP server for one install target. Reuses the same merge that
|
|
67
|
+
// protects ~/.claude.json: absent files are created, present files are merged
|
|
68
|
+
// without clobbering other servers, and an unparseable file is refused, never
|
|
69
|
+
// overwritten. Never throws — a bad config for one IDE must not fail an install.
|
|
70
|
+
function registerForTarget(target, { cwd = process.cwd(), log = () => {}, dryRun = false } = {}) {
|
|
71
|
+
const paths = mcpPathsForTarget(target, cwd);
|
|
72
|
+
if (paths.length === 0) {
|
|
73
|
+
return [{ target, action: 'unsupported', reason: `no known JSON MCP config path for "${target}"` }];
|
|
74
|
+
}
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const p of paths) {
|
|
77
|
+
try {
|
|
78
|
+
const r = registerInFile(p, { dryRun });
|
|
79
|
+
log(` ${r.action}: ${r.file}`);
|
|
80
|
+
results.push({ target, ...r });
|
|
81
|
+
} catch (e) {
|
|
82
|
+
log(` skipped ${p}: ${e.message}`);
|
|
83
|
+
results.push({ target, file: p, action: 'error', error: e.message });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
28
89
|
// Reading this file has THREE outcomes, and collapsing them is destructive.
|
|
29
90
|
//
|
|
30
91
|
// readJson used to return null for every failure, and the caller did
|
|
@@ -145,9 +206,19 @@ function unregisterAll({ log = () => {} } = {}) {
|
|
|
145
206
|
function statusAll() {
|
|
146
207
|
const rows = [];
|
|
147
208
|
for (const p of [claudeCodeConfigPath(), claudeDesktopConfigPath()]) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
// Was `readJson(p)` — a function that no longer exists after the
|
|
210
|
+
// absent/present/unreadable refactor, so statusAll() threw
|
|
211
|
+
// "readJson is not defined" and took `kodelythecc doctor` and the `mcp`
|
|
212
|
+
// status command down with it. Use the real reader and its tri-state result.
|
|
213
|
+
const cfg = readConfig(p);
|
|
214
|
+
const data = (cfg && cfg.data) || {};
|
|
215
|
+
const present = !!(data.mcpServers && data.mcpServers[SERVER_NAME]);
|
|
216
|
+
rows.push({
|
|
217
|
+
file: p,
|
|
218
|
+
present,
|
|
219
|
+
exists: cfg.state !== 'absent',
|
|
220
|
+
readable: cfg.state !== 'unreadable',
|
|
221
|
+
});
|
|
151
222
|
}
|
|
152
223
|
return rows;
|
|
153
224
|
}
|
|
@@ -157,6 +228,9 @@ module.exports = {
|
|
|
157
228
|
serverEntry,
|
|
158
229
|
claudeCodeConfigPath,
|
|
159
230
|
claudeDesktopConfigPath,
|
|
231
|
+
mcpPathsForTarget,
|
|
232
|
+
supportedMcpTargets,
|
|
233
|
+
registerForTarget,
|
|
160
234
|
registerAll,
|
|
161
235
|
unregisterAll,
|
|
162
236
|
statusAll,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// scripts/memory/instinct-inject.js
|
|
2
|
+
//
|
|
3
|
+
// Consume the structured instinct store (scripts/memory/instincts.js) at session
|
|
4
|
+
// start. That store is written by the correction-capture hook, but until now
|
|
5
|
+
// nothing ever READ it — all of its confidence / decay / outcome machinery ran
|
|
6
|
+
// and never influenced a single session. This module closes that loop: it picks
|
|
7
|
+
// the high-confidence, non-stale instincts relevant to the current project and
|
|
8
|
+
// formats them for injection into SessionStart context.
|
|
9
|
+
//
|
|
10
|
+
// Kept separate from session-start.js (which executes main() at import time) so
|
|
11
|
+
// the selection logic can be unit-tested without running the whole hook.
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MAX = 6;
|
|
15
|
+
const DEFAULT_THRESHOLD = 0.7;
|
|
16
|
+
|
|
17
|
+
// Pure selection + formatting. Given raw instinct records, keep the ones that
|
|
18
|
+
// are non-stale, confident enough, and in scope for `projectPath`; rank by
|
|
19
|
+
// confidence; cap at `max`; render as injectable markdown. No I/O — this is the
|
|
20
|
+
// unit the tests exercise.
|
|
21
|
+
function formatStructuredInstincts(instincts, { projectPath, max = DEFAULT_MAX, threshold = DEFAULT_THRESHOLD } = {}) {
|
|
22
|
+
const candidates = (Array.isArray(instincts) ? instincts : [])
|
|
23
|
+
.filter(i => i && typeof i.rule === 'string' && i.rule.trim())
|
|
24
|
+
.filter(i => !i.stale && (i.confidence || 0) >= threshold)
|
|
25
|
+
// Global instincts always apply; project-scoped ones only in their project.
|
|
26
|
+
// A record with no project_path is treated as unscoped (applies anywhere).
|
|
27
|
+
.filter(i => i.scope === 'global' || !i.project_path || i.project_path === projectPath);
|
|
28
|
+
|
|
29
|
+
if (candidates.length === 0) return '';
|
|
30
|
+
|
|
31
|
+
const ranked = candidates
|
|
32
|
+
.slice()
|
|
33
|
+
.sort((a, b) => (b.confidence || 0) - (a.confidence || 0))
|
|
34
|
+
.slice(0, max);
|
|
35
|
+
|
|
36
|
+
const lines = ranked.map(i => {
|
|
37
|
+
const scope = i.scope === 'project' ? 'project' : 'global';
|
|
38
|
+
const confidence = `${Math.round((i.confidence || 0) * 100)}%`;
|
|
39
|
+
return `- [${scope} ${confidence}] ${i.rule.trim()}`;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return `Learned instincts (from past corrections):\n${lines.join('\n')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Read the store and format. Never throws: the structured store must not be able
|
|
46
|
+
// to break session start, so any failure degrades to injecting nothing.
|
|
47
|
+
function summarizeStructuredInstincts(projectPath, { log = () => {} } = {}) {
|
|
48
|
+
try {
|
|
49
|
+
const store = require('./instincts');
|
|
50
|
+
const summary = formatStructuredInstincts(store.list(), { projectPath });
|
|
51
|
+
if (summary) {
|
|
52
|
+
const count = summary.split('\n').length - 1;
|
|
53
|
+
log(`[SessionStart] Injecting ${count} learned instinct(s) from the structured store`);
|
|
54
|
+
}
|
|
55
|
+
return summary;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
log(`[SessionStart] Structured instinct injection skipped: ${err && err.message}`);
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
formatStructuredInstincts,
|
|
64
|
+
summarizeStructuredInstincts,
|
|
65
|
+
DEFAULT_MAX,
|
|
66
|
+
DEFAULT_THRESHOLD,
|
|
67
|
+
};
|
package/scripts/rtk/index.js
CHANGED
|
@@ -64,11 +64,51 @@ function detectInstalledTargets() {
|
|
|
64
64
|
return targets;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Guard against the `rtk` name collision. reachingforthejack/rtk (Rust Type Kit)
|
|
68
|
+
// also ships a binary called `rtk`; wiring or measuring it as if it were Rust
|
|
69
|
+
// Token Killer would be wrong and silent — ECC would patch hooks and read a
|
|
70
|
+
// `gain` ledger that does not exist. Rust Token Killer is the only `rtk` with a
|
|
71
|
+
// `gain` subcommand (the token-savings ledger ECC reads), so probing it
|
|
72
|
+
// distinguishes the two with no side effects and without depending on exact
|
|
73
|
+
// `--version` text. `rtk gain` is the documented signature command in RTK.md.
|
|
74
|
+
//
|
|
75
|
+
// The decision is a pure function of two probe results so it can be unit-tested
|
|
76
|
+
// without spawning a binary:
|
|
77
|
+
// gainHelpOk — did `rtk gain --help` exit 0?
|
|
78
|
+
// helpText — stdout of `rtk --help` (or '' if it failed)
|
|
79
|
+
function looksLikeRealRtk({ gainHelpOk, helpText } = {}) {
|
|
80
|
+
if (gainHelpOk) return true;
|
|
81
|
+
const h = String(helpText || '');
|
|
82
|
+
// The token killer's help lists the `gain` command and calls itself an
|
|
83
|
+
// LLM-output proxy; Rust Type Kit's help does neither.
|
|
84
|
+
return /\bgain\b/.test(h) && /(LLM|token|proxy)/i.test(h);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isRealRtk() {
|
|
88
|
+
let gainHelpOk = false;
|
|
89
|
+
try {
|
|
90
|
+
execFileSync('rtk', ['gain', '--help'], { stdio: 'ignore' });
|
|
91
|
+
gainHelpOk = true;
|
|
92
|
+
} catch { /* `gain` subcommand absent, or `rtk` not runnable */ }
|
|
93
|
+
|
|
94
|
+
let helpText = '';
|
|
95
|
+
if (!gainHelpOk) {
|
|
96
|
+
try {
|
|
97
|
+
helpText = execFileSync('rtk', ['--help'], {
|
|
98
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
99
|
+
});
|
|
100
|
+
} catch { /* no help available */ }
|
|
101
|
+
}
|
|
102
|
+
return looksLikeRealRtk({ gainHelpOk, helpText });
|
|
103
|
+
}
|
|
104
|
+
|
|
67
105
|
function isInstalled() {
|
|
68
106
|
try {
|
|
69
107
|
execFileSync('rtk', ['--version'], { stdio: 'ignore' });
|
|
70
|
-
return true;
|
|
71
108
|
} catch { return false; }
|
|
109
|
+
// A binary named `rtk` exists — confirm it is Rust Token Killer, not the Rust
|
|
110
|
+
// Type Kit collision, before any caller wires or measures it.
|
|
111
|
+
return isRealRtk();
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
function getVersion() {
|
|
@@ -214,6 +254,8 @@ module.exports = {
|
|
|
214
254
|
normalizeTarget,
|
|
215
255
|
detectInstalledTargets,
|
|
216
256
|
isInstalled,
|
|
257
|
+
isRealRtk,
|
|
258
|
+
looksLikeRealRtk,
|
|
217
259
|
getVersion,
|
|
218
260
|
install,
|
|
219
261
|
enableFor,
|
package/scripts/terse/ledger.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// scripts/terse/ledger.js
|
|
2
|
-
// Output-token savings ledger. Stores one JSONL row per terse
|
|
3
|
-
// Zero deps.
|
|
2
|
+
// Output-token savings ledger. Stores one JSONL row per recorded terse turn.
|
|
3
|
+
// Zero deps. Writes append-only.
|
|
4
|
+
//
|
|
5
|
+
// IMPORTANT — these rows are ESTIMATES, not measured savings. There is no
|
|
6
|
+
// non-terse copy of the same reply to diff against, so `saved` is modeled:
|
|
7
|
+
// actual output tokens times a per-level baseline multiplier (RAW_MULT below).
|
|
8
|
+
// Every figure here is an estimate and each row is flagged `estimated: true`.
|
|
9
|
+
// (The Terse *file* compressor in compress.js, by contrast, reports real,
|
|
10
|
+
// measured before/after bytes.)
|
|
4
11
|
//
|
|
5
12
|
// Row shape:
|
|
6
|
-
// { ts, level, rawEstimate, actual, saved, projectHash?, source }
|
|
13
|
+
// { ts, level, rawEstimate, actual, saved, estimated, baseline, projectHash?, source }
|
|
7
14
|
//
|
|
8
15
|
// Path: ~/.kodelythecc/terse/ledger.jsonl (overridable via KODELYTH_TERSE_DIR)
|
|
9
16
|
|
|
@@ -31,8 +38,10 @@ function estimateTokens(text) {
|
|
|
31
38
|
return Math.round(Buffer.byteLength(text, 'utf8') / 4);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
41
|
+
// Per-level baseline multiplier used to MODEL what the same reply would have
|
|
42
|
+
// cost without terse (full ≈ 2x the terse size, ultra ≈ 2.85x, lite ≈ 1.33x).
|
|
43
|
+
// These are assumptions, not measurements — every `saved` figure derived from
|
|
44
|
+
// them is an estimate, which is why each row is flagged `estimated: true`.
|
|
36
45
|
const RAW_MULT = { lite: 1.33, full: 2.0, ultra: 2.85, off: 1.0 };
|
|
37
46
|
|
|
38
47
|
function appendTurn({ actualText, level = 'full', source = 'unknown', projectHash = null }) {
|
|
@@ -46,6 +55,8 @@ function appendTurn({ actualText, level = 'full', source = 'unknown', projectHas
|
|
|
46
55
|
rawEstimate,
|
|
47
56
|
actual,
|
|
48
57
|
saved,
|
|
58
|
+
estimated: true, // modeled from RAW_MULT, not a measured A/B
|
|
59
|
+
baseline: RAW_MULT[level] || 1,
|
|
49
60
|
source,
|
|
50
61
|
...(projectHash ? { projectHash } : {}),
|
|
51
62
|
};
|
|
@@ -83,7 +83,7 @@ Example — ~70% shorter:
|
|
|
83
83
|
|
|
84
84
|
- Slash command: `/terse [lite|full|ultra|off]` — sticks for the session
|
|
85
85
|
- CLI: `kodelyth-ecc terse enable [--target claude-code|--all]` — installs the skill + command into your AI tool
|
|
86
|
-
-
|
|
86
|
+
- Dashboard (Claude Code): the **Token Savings** tab shows lifetime *estimated* output savings from the terse ledger — modeled, not measured (see the ledger note in `docs/terse-mode.md`)
|
|
87
87
|
|
|
88
88
|
## Honest numbers
|
|
89
89
|
|