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 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/ → 569 passing tests across 30 test files
29
+ tests/ → 572 passing tests across 30 test files
30
30
  ```
31
31
 
32
32
  ## Running Tests
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.13.0
1
+ 2.14.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.13.0",
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",
@@ -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', /\bmemory|heap|rss|oom|amplification|exhaust\b/i],
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
- ['input-validation', /validat|wrong shape|silently accept|malformed|type ?error|unsanitiz/i],
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
- fs.writeFileSync(configPath, nextRaw, 'utf8');
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
- fs.writeFileSync(configPath, cleaned + (toAppend.length > 0 ? appendText : ''), 'utf8');
323
+ safeFs.replaceFilePreservingMode(configPath, cleaned + (toAppend.length > 0 ? appendText : ''));
323
324
  } else {
324
325
  fs.appendFileSync(configPath, appendText, 'utf8');
325
326
  }
@@ -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
- fs.writeFileSync(p, JSON.stringify(data, null, 2));
58
+ safeFs.replaceFilePreservingMode(p, JSON.stringify(data, null, 2));
58
59
  return true;
59
60
  } catch { return false; }
60
61
  }
@@ -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
  };
@@ -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
- fs.writeFileSync(REGISTRY_FILE, JSON.stringify(reg, null, 2) + '\n');
58
+ safeFs.replaceFilePreservingMode(REGISTRY_FILE, JSON.stringify(reg, null, 2) + '\n');
58
59
  }
59
60
 
60
61
  // ── Registry mutations ───────────────────────────────────────────────────────
@@ -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
- fs.writeFileSync(PATHS.index, JSON.stringify({ ...index, logSize: logSize() }, null, 2));
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 —