kodelyth-ecc 2.24.9 → 2.24.11

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,82 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.24.11 — every docs page gets its own diagram (October 2026)
6
+
7
+ Documentation and site only. No code change.
8
+
9
+ The website renders a doc's `og_image` on the page now, not just in the social
10
+ card, so the image a doc points at is something readers actually see above the
11
+ content. Most docs pointed at shared generic cards — one card was doing duty for
12
+ four unrelated pages — which is worse on-page than no image at all.
13
+
14
+ Seventeen of the eighteen docs now carry a purpose-built 1200x630 diagram that
15
+ teaches their own subject: the Arena's GOD-vs-EVIL loop, swarm's fan-out into
16
+ isolated worktrees, the real RTK token ledger, the eight hook events sized by how
17
+ many entries each carries, evolve's propose-then-you-review gate, the MCP tool
18
+ surface, and so on. Each gains `hero_alt` describing the diagram's data, so it is
19
+ legible to a screen reader and in the social card. The index hub keeps the main
20
+ product card, which is the right image for a landing page.
21
+
22
+ The diagrams live as static SVG in the website repo; this release carries the
23
+ `og_image` / `hero_alt` frontmatter for the docs that ship from here.
24
+
25
+ **693 tests across 44 files.**
26
+
27
+ ## v2.24.10 — the same config-destroying read, now on settings.json (October 2026)
28
+
29
+ **Data-loss fix.** 2.24.9 fixed a nullable JSON read coalesced to `{}` in
30
+ `register-self.js`, which destroyed `~/.claude.json`. Grepping the codebase for
31
+ that pattern found it a second time, in `register-hooks.js`, on a file that
32
+ matters more.
33
+
34
+ ```js
35
+ const settings = readJsonOrNull(settingsPath) || {};
36
+ // …
37
+ const body = JSON.stringify({ ...settings, hooks: merged }, null, 2);
38
+ ```
39
+
40
+ Same shape, same consequence: a `settings.json` that merely failed to parse was
41
+ indistinguishable from one that did not exist, so the merge wrote it back
42
+ carrying only the hooks block. Measured on a realistic file: **477 bytes in, 106
43
+ bytes out** — losing `permissions.allow` and `.deny`, `env`, `model`,
44
+ `statusLine`, and the user's *own* hook entries. Losing permissions silently
45
+ re-prompts them for every tool; losing hooks silently stops their quality gates.
46
+
47
+ Worth recording how it survived review: the **write** here was already hardened to
48
+ preserve the file's mode, with a comment explaining that an installer has no
49
+ business changing a user config's permissions. Someone thought carefully about the
50
+ write and not at all about the read.
51
+
52
+ ### What changed
53
+
54
+ `readSettings` now returns `absent` / `present` / `unreadable`, and an unreadable
55
+ config returns status `unreadable-settings` with the parse reason, writing
56
+ nothing. The existing `readJsonOrNull` is kept for ECC's own shipped `hooks.json`,
57
+ where null on failure is correct — that is a file we ship, not one a user can
58
+ hand-edit into a broken state.
59
+
60
+ ### A documented behaviour was deliberately reversed
61
+
62
+ A test existed for this case: *"a corrupt settings.json does not lose the hooks"*,
63
+ asserting status `ok` with ECC's three hook events written. The intent — don't
64
+ lose ECC's hooks over a broken config — sounds right until it is priced. The only
65
+ way to write into a file that will not parse is to discard everything else in it.
66
+
67
+ ECC's hooks not installing is recoverable: fix the JSON, re-run. The user's
68
+ permissions and env are not. The test is rewritten rather than deleted, asserting
69
+ the opposite contract with the trade-off stated in the test body.
70
+
71
+ **693 tests across 44 files.**
72
+
73
+ ### Also: brand art pointed at the wrong site
74
+
75
+ Four ECC cards printed `kodelyth.com` — the parent company site — rather than
76
+ `ecc.kodelyth.com`, where ECC actually lives. `og-image`, `github-social-preview`
77
+ and `fb-post-launch` all advertise ECC, so anyone reading the card and typing the
78
+ URL landed somewhere else. `section-author` keeps the parent domain, since that
79
+ one is about the company rather than the product.
80
+
5
81
  ## v2.24.9 — a malformed Claude config was silently replaced on install (October 2026)
6
82
 
7
83
  **Data-loss fix. Upgrade before running an install on a machine whose
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/ → 690 passing tests across 44 test files
29
+ tests/ → 693 passing tests across 44 test files
30
30
  ```
31
31
 
32
32
  ## Running Tests
package/README.md CHANGED
@@ -118,7 +118,7 @@ Most "AI agent kits" are folders of markdown files you have to remember the name
118
118
  | Quality hooks | ✓ 44 | Some | ✗ | ✗ |
119
119
  | IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
120
120
  | Telemetry | **None** | Varies | None | Varies |
121
- | Test coverage | ✓ 690 tests | ✗ | ✗ | ✗ |
121
+ | Test coverage | ✓ 693 tests | ✗ | ✗ | ✗ |
122
122
  | Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
123
123
 
124
124
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.24.9",
3
+ "version": "2.24.11",
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",
@@ -36,6 +36,10 @@ const path = require('path');
36
36
  const { mergeHookEntries } = require('../lib/install/apply.js');
37
37
  const { replaceFilePreservingMode } = require('../lib/safe-fs.js');
38
38
 
39
+ // ECC's OWN shipped hooks.json. Null on failure is right here: it is a file we
40
+ // ship, a caller cannot have hand-edited it into existence, and the
41
+ // 'invalid-hooks-config' status below is the correct response to it being wrong.
42
+ // The user's settings.json is a different matter — see readSettings.
39
43
  function readJsonOrNull(file) {
40
44
  try {
41
45
  const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -45,6 +49,40 @@ function readJsonOrNull(file) {
45
49
  }
46
50
  }
47
51
 
52
+ // Reading settings.json has THREE outcomes, and collapsing them destroys data.
53
+ //
54
+ // This used to be readJsonOrNull() returning null for every failure, with the
55
+ // caller writing `|| {}`. A settings.json that merely failed to parse — one
56
+ // trailing comma from a hand-edit — was therefore indistinguishable from one that
57
+ // did not exist, and the merge wrote it back carrying only the hooks block.
58
+ // Measured on a realistic file: 477 bytes in, 106 bytes out, losing
59
+ // permissions.allow and .deny, env, model, statusLine, and the user's OWN hook
60
+ // entries. Losing permissions silently re-prompts them for every tool; losing
61
+ // hooks silently stops their quality gates.
62
+ //
63
+ // This is the same defect fixed in scripts/mcp/register-self.js for ~/.claude.json.
64
+ // Note the write below was already hardened to preserve file mode — the write was
65
+ // careful and the read was not, which is how it survived review.
66
+ function readSettings(file) {
67
+ let raw;
68
+ try {
69
+ raw = fs.readFileSync(file, 'utf8');
70
+ } catch (err) {
71
+ if (err.code === 'ENOENT') return { state: 'absent', data: {} };
72
+ return { state: 'unreadable', reason: err.message };
73
+ }
74
+ if (raw.trim() === '') return { state: 'absent', data: {} };
75
+ try {
76
+ const parsed = JSON.parse(raw);
77
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
78
+ return { state: 'unreadable', reason: 'top-level value is not an object' };
79
+ }
80
+ return { state: 'present', data: parsed };
81
+ } catch (err) {
82
+ return { state: 'unreadable', reason: err.message };
83
+ }
84
+ }
85
+
48
86
  /**
49
87
  * @param {string} targetRoot e.g. ~/.claude
50
88
  * @param {string} [hooksJsonPath] defaults to <targetRoot>/hooks/hooks.json
@@ -67,7 +105,21 @@ function registerHooks(targetRoot, hooksJsonPath) {
67
105
  }
68
106
 
69
107
  const settingsPath = path.join(targetRoot, 'settings.json');
70
- const settings = readJsonOrNull(settingsPath) || {};
108
+ const read = readSettings(settingsPath);
109
+ if (read.state === 'unreadable') {
110
+ // Refuse rather than merge. There is no safe way to write a file whose current
111
+ // contents cannot be reconstructed, and "best effort" here means discarding
112
+ // whatever did not parse.
113
+ return {
114
+ status: 'unreadable-settings',
115
+ reason: read.reason,
116
+ events: 0,
117
+ added: 0,
118
+ total: 0,
119
+ settingsPath,
120
+ };
121
+ }
122
+ const settings = read.data;
71
123
  const existing = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
72
124
  ? settings.hooks
73
125
  : {};