kodelyth-ecc 2.13.0 → 2.14.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 +56 -0
- package/CLAUDE.md +1 -1
- package/VERSION +1 -1
- package/package.json +1 -1
- package/scripts/arena/learn.js +4 -2
- package/scripts/codex/merge-codex-config.js +2 -1
- package/scripts/codex/merge-mcp-config.js +2 -1
- package/scripts/evolve/stats.js +2 -1
- package/scripts/lib/safe-fs.js +19 -0
- package/scripts/mcp/client.js +2 -1
- package/scripts/memory/store.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.14.0 — A false recurring class, and five more atomic writes (August 2026)
|
|
6
|
+
|
|
7
|
+
### Fixed — the guard proposal was pointing at the wrong thing
|
|
8
|
+
|
|
9
|
+
After three arena runs the classifier reported **`resource-exhaustion` ×5** as the
|
|
10
|
+
top recurring class. Four of those five were not resource exhaustion at all:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
resource-exhaustion <- Prototype-key collision crashes indexing
|
|
14
|
+
resource-exhaustion <- A crash mid-append fuses the next memory into the torn row
|
|
15
|
+
resource-exhaustion <- A patch row surfaced as a phantom memory
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The pattern contained the bare word `memory`, which matches every finding about
|
|
19
|
+
the memory *store*. Acting on it would have meant building a guard nobody needed
|
|
20
|
+
— exactly the waste the proposal mechanism exists to prevent. This is the second
|
|
21
|
+
generic-word collision in this classifier; `permission` was the first.
|
|
22
|
+
|
|
23
|
+
`resource-exhaustion` now requires an actual exhaustion signal (`heap`, `rss`,
|
|
24
|
+
`oom`, `memory leak/usage/growth`, `allocates N`). A new **`data-integrity`**
|
|
25
|
+
class covers the double-index / desync / phantom-row / torn-row family, and
|
|
26
|
+
prototype-key findings now classify as `input-validation`. The 5 mis-tagged
|
|
27
|
+
memories already in the store were re-tagged.
|
|
28
|
+
|
|
29
|
+
The corrected picture across three runs:
|
|
30
|
+
|
|
31
|
+
| class | count | status |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `filesystem-symlink` | 4 | already guarded by `scripts/lib/safe-fs.js` |
|
|
34
|
+
| `data-integrity` | 4 | all four in one file, all fixed |
|
|
35
|
+
| `input-validation` | 2 | fixed |
|
|
36
|
+
| everything else | 1 each | — |
|
|
37
|
+
|
|
38
|
+
**No new abstraction was built for `data-integrity`.** All four members live in
|
|
39
|
+
`store.js` and are already fixed, and no other subsystem keeps derived state with
|
|
40
|
+
the same drift — so a shared guard would have been speculative.
|
|
41
|
+
|
|
42
|
+
### Added — `replaceFilePreservingMode`, applied to five real writers
|
|
43
|
+
|
|
44
|
+
What the sweep *did* find is the durability pattern in files holding state worth
|
|
45
|
+
keeping. `fs.writeFileSync` opens with `'w'`, truncating to zero before writing,
|
|
46
|
+
so a crash or a full disk part-way through leaves a truncated file and no copy of
|
|
47
|
+
the original:
|
|
48
|
+
|
|
49
|
+
- `scripts/codex/merge-mcp-config.js` — the user's **Codex IDE config**
|
|
50
|
+
- `scripts/codex/merge-codex-config.js` — the user's **Codex IDE config**
|
|
51
|
+
- `scripts/evolve/stats.js` — accumulated reuse and routing-miss stats
|
|
52
|
+
- `scripts/mcp/client.js` — the MCP server registry
|
|
53
|
+
- `scripts/memory/store.js` — the BM25 index
|
|
54
|
+
|
|
55
|
+
All five now write through `safeFs.replaceFilePreservingMode`, which keeps the
|
|
56
|
+
file's existing permissions and renames atomically. A crash leaves the original
|
|
57
|
+
completely untouched.
|
|
58
|
+
|
|
59
|
+
**572 tests passing**, up from 569.
|
|
60
|
+
|
|
5
61
|
## v2.13.0 — Arena run #3: eight bugs in the memory store (August 2026)
|
|
6
62
|
|
|
7
63
|
Pointed the arena at `scripts/memory` — the persistent BM25 store every other
|
package/CLAUDE.md
CHANGED
|
@@ -26,7 +26,7 @@ scripts/ → Node.js utilities: MCP server, dashboard, swarm, replay, router
|
|
|
26
26
|
bundles/ → 3 power bundles (indie-hacker, red-team, enterprise)
|
|
27
27
|
actions/ → GitHub Action (CI/CD integration for PR review)
|
|
28
28
|
docs/ → Feature docs (arena.md, mcp.md, dashboard.md, swarm.md, replay.md, evolve.md, supply-chain.md)
|
|
29
|
-
tests/ →
|
|
29
|
+
tests/ → 572 passing tests across 30 test files
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Running Tests
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.14.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
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",
|
package/scripts/arena/learn.js
CHANGED
|
@@ -35,14 +35,15 @@ const CLASSES = [
|
|
|
35
35
|
['redos', /\bredos|backtrack|quadratic|catastrophic|O\(n\^?2\)|unanchored\b/i],
|
|
36
36
|
['path-traversal', /\btraversal|confinement|arbitrary (?:write|path)|escape the root\b/i],
|
|
37
37
|
['race-condition', /\btoctou|race condition|check.to.use\b/i],
|
|
38
|
-
['resource-exhaustion', /\
|
|
38
|
+
['resource-exhaustion', /\bheap\b|\brss\b|\boom\b|amplification|exhaust|out of memory|memory (?:leak|usage|growth|pressure)|allocates? ~?\d/i],
|
|
39
39
|
['missing-limit', /\bno (?:input )?(?:size )?cap|unbounded|no limit|missing limit\b/i],
|
|
40
40
|
['temp-file-handling', /\btemp(?:orary)? file|tmp file|leftover|predictable (?:name|filename)\b/i],
|
|
41
41
|
['prompt-injection', /\binjection|jailbreak|untrusted (?:text|content|input)|system.prompt leak|inert data|trust.boundary\b/i],
|
|
42
42
|
['secret-exposure', /\bsecret|credential|api key|token leak|password\b/i],
|
|
43
43
|
['semantic-corruption', /\bsemantic|meaning|threshold|negation|inverts?|widen/i],
|
|
44
44
|
['idempotency', /\bidempoten|fixed point|second run|re-?run\b/i],
|
|
45
|
-
['
|
|
45
|
+
['data-integrity', /\bdouble-?index|desync|out of sync|phantom|corrupt|torn row|lost update|inconsisten|silently lost\b/i],
|
|
46
|
+
['input-validation', /validat|wrong shape|silently accept|malformed|type ?error|unsanitiz|prototype[- ]key|__proto__|prototype pollution/i],
|
|
46
47
|
['access-control', /\bbypass(?:es|ed)? the|rebinding|host header|allowlist|authoriz|access control\b/i],
|
|
47
48
|
['supply-chain', /\btyposquat|lockfile|install script|dependency confusion\b/i],
|
|
48
49
|
];
|
|
@@ -229,6 +230,7 @@ const GUARD_ADVICE = {
|
|
|
229
230
|
'secret-exposure': 'Add a secret scan to the pre-commit hook for this path.',
|
|
230
231
|
'semantic-corruption': 'Add golden tests asserting that meaning-bearing tokens survive transformation.',
|
|
231
232
|
'idempotency': 'Assert f(f(x)) === f(x) in the test suite for every transform.',
|
|
233
|
+
'data-integrity': 'Derive the secondary structure from the primary one and add a cheap staleness check, so drift self-heals instead of persisting.',
|
|
232
234
|
'input-validation': 'Validate argument shape at every public boundary and throw — never silently coerce to a plausible default.',
|
|
233
235
|
'access-control': 'Deny by default: an allowlist of permitted values, with every absent or empty case treated as invalid rather than waved through.',
|
|
234
236
|
'supply-chain': 'Pin and verify dependencies; add a lockfile-drift check to CI.',
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const safeFs = require('../lib/safe-fs.js');
|
|
16
17
|
|
|
17
18
|
let TOML;
|
|
18
19
|
try {
|
|
@@ -310,7 +311,7 @@ function main() {
|
|
|
310
311
|
return;
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
|
|
314
|
+
safeFs.replaceFilePreservingMode(configPath, nextRaw);
|
|
314
315
|
log('Done. Baseline Codex settings merged.');
|
|
315
316
|
}
|
|
316
317
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
|
+
const safeFs = require('../lib/safe-fs.js');
|
|
22
23
|
const { parseDisabledMcpServers } = require('../lib/mcp-config');
|
|
23
24
|
|
|
24
25
|
let TOML;
|
|
@@ -319,7 +320,7 @@ function main() {
|
|
|
319
320
|
if (updateMcp || hasRemovals) {
|
|
320
321
|
for (const label of toRemoveLog) log(` [update] ${label}`);
|
|
321
322
|
const cleaned = raw.replace(/\n+$/, '\n');
|
|
322
|
-
|
|
323
|
+
safeFs.replaceFilePreservingMode(configPath, cleaned + (toAppend.length > 0 ? appendText : ''));
|
|
323
324
|
} else {
|
|
324
325
|
fs.appendFileSync(configPath, appendText, 'utf8');
|
|
325
326
|
}
|
package/scripts/evolve/stats.js
CHANGED
|
@@ -29,6 +29,7 @@ const fs = require('fs');
|
|
|
29
29
|
const os = require('os');
|
|
30
30
|
const path = require('path');
|
|
31
31
|
const crypto = require('crypto');
|
|
32
|
+
const safeFs = require('../lib/safe-fs.js');
|
|
32
33
|
|
|
33
34
|
const DEFAULT_DIR = process.env.KODELYTH_EVOLVE_DIR
|
|
34
35
|
|| path.join(os.homedir(), '.kodelythecc', 'evolve');
|
|
@@ -54,7 +55,7 @@ function safeReadJson(p, fallback) {
|
|
|
54
55
|
|
|
55
56
|
function safeWriteJson(p, data) {
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
+
safeFs.replaceFilePreservingMode(p, JSON.stringify(data, null, 2));
|
|
58
59
|
return true;
|
|
59
60
|
} catch { return false; }
|
|
60
61
|
}
|
package/scripts/lib/safe-fs.js
CHANGED
|
@@ -153,10 +153,29 @@ function replaceFileAtomic(absPath, contents, mode) {
|
|
|
153
153
|
return absPath;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Replace an existing file's contents atomically, keeping whatever permissions
|
|
158
|
+
* it already had (or `fallbackMode` when it does not exist yet).
|
|
159
|
+
*
|
|
160
|
+
* This is the safe replacement for `fs.writeFileSync(path, data)` on any file
|
|
161
|
+
* that holds state worth keeping — a user's IDE config, an accumulated stats
|
|
162
|
+
* file, a registry. `writeFileSync` opens with 'w', truncating to zero before
|
|
163
|
+
* writing, so a crash or a full disk part-way through leaves the user with a
|
|
164
|
+
* truncated file and no copy of the original anywhere.
|
|
165
|
+
*/
|
|
166
|
+
function replaceFilePreservingMode(absPath, contents, fallbackMode = 0o644) {
|
|
167
|
+
let mode = fallbackMode;
|
|
168
|
+
try {
|
|
169
|
+
mode = fs.statSync(absPath).mode & 0o7777;
|
|
170
|
+
} catch { /* new file — use the fallback */ }
|
|
171
|
+
return replaceFileAtomic(absPath, contents, mode);
|
|
172
|
+
}
|
|
173
|
+
|
|
156
174
|
module.exports = {
|
|
157
175
|
resolveContained,
|
|
158
176
|
statRegularFile,
|
|
159
177
|
safeConfigDir,
|
|
160
178
|
writeNewFile,
|
|
161
179
|
replaceFileAtomic,
|
|
180
|
+
replaceFilePreservingMode,
|
|
162
181
|
};
|
package/scripts/mcp/client.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
const fs = require('fs');
|
|
31
31
|
const os = require('os');
|
|
32
32
|
const path = require('path');
|
|
33
|
+
const safeFs = require('../lib/safe-fs.js');
|
|
33
34
|
|
|
34
35
|
const REGISTRY_DIR = process.env.KODELYTH_MCP_CLIENT_DIR
|
|
35
36
|
|| path.join(os.homedir(), '.kodelythecc');
|
|
@@ -54,7 +55,7 @@ function loadRegistry() {
|
|
|
54
55
|
|
|
55
56
|
function saveRegistry(reg) {
|
|
56
57
|
ensureDir(REGISTRY_DIR);
|
|
57
|
-
|
|
58
|
+
safeFs.replaceFilePreservingMode(REGISTRY_FILE, JSON.stringify(reg, null, 2) + '\n');
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// ── Registry mutations ───────────────────────────────────────────────────────
|
package/scripts/memory/store.js
CHANGED
|
@@ -18,6 +18,7 @@ const fs = require('fs');
|
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const crypto = require('crypto');
|
|
21
|
+
const safeFs = require('../lib/safe-fs.js');
|
|
21
22
|
|
|
22
23
|
// Auto-migrate legacy ~/.kodelyth/ → ~/.kodelythecc/ before we touch any path.
|
|
23
24
|
try { require('../migrate-legacy').main(); } catch { /* best-effort */ }
|
|
@@ -142,7 +143,7 @@ function logSize() {
|
|
|
142
143
|
|
|
143
144
|
function saveIndex(index) {
|
|
144
145
|
ensureDir(PATHS.dir);
|
|
145
|
-
|
|
146
|
+
safeFs.replaceFilePreservingMode(PATHS.index, JSON.stringify({ ...index, logSize: logSize() }, null, 2));
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// A patch row does not change any searchable text, so the index stays valid —
|