kodelyth-ecc 2.20.0 → 2.21.1

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,167 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.21.1 — the release pipeline itself (September 2026)
6
+
7
+ **No change to the toolkit.** The shipped package is byte-identical to 2.21.0 —
8
+ 797 files, zero differences. Every change below is CI and deploy infrastructure,
9
+ which the package excludes. Upgrading gains you nothing; it is recorded here
10
+ because the changelog is the project's memory.
11
+
12
+ Hence a patch, not a minor: nothing was added to what you install.
13
+
14
+ ### Fixed — releases were not reaching the website
15
+
16
+ The workflow that tells the website a release happened had been removed after it
17
+ was found reporting green while returning HTTP 403. Its last line was:
18
+
19
+ ```bash
20
+ [ "$STATUS" = "204" ] && echo ok || echo failed
21
+ ```
22
+
23
+ which exits 0 either way, so a broken dispatch and a working one produced the
24
+ same green check. It had silently done nothing for an unknown number of
25
+ releases.
26
+
27
+ Restored, with every failure path exiting non-zero. A preflight now separates
28
+ the failure modes, which a bare response cannot: the site repo is private, so
29
+ GitHub answers 404 rather than 403 to a token that cannot see it, making "wrong
30
+ repo path", "no repository access", and "access but no write permission" look
31
+ identical. It also reports the token type and its expiry, warning under 30 days
32
+ and erroring once lapsed — an expired token is what broke this the first time.
33
+
34
+ One correction worth recording: the preflight originally passed on
35
+ `permissions.push` from the repo API. That field is the permission of the *user*
36
+ who owns the token, not the permissions granted *to* the token, so a token
37
+ holding only `Metadata: Read` reported `push=true` and was still refused every
38
+ write. It now logs that as context and never gates on it.
39
+
40
+ ### Fixed — the site redeployed every cron tick for identical content
41
+
42
+ Each content sync rewrites a `syncedAt` timestamp whether or not anything
43
+ changed, so `git status` was never clean, `changed` was permanently true, and
44
+ every scheduled run rebuilt and redeployed the server.
45
+
46
+ Measured over 24 hours: **14 sync commits, 2 with a real change.** The other 12
47
+ each ran a full install, build, rsync, an `rm -rf` and `mv` over the live
48
+ directories, and a container recreate — to ship byte-identical output.
49
+ Timestamp-only churn is now discarded rather than committed.
50
+
51
+ ### Fixed — the cache purge reported success when it failed
52
+
53
+ The Cloudflare purge ended in the same shape as the dispatch bug —
54
+ `grep -q ... && echo OK || { echo failed; exit 0; }` — always exiting 0 with the
55
+ failure buried in a plain echo inside a green job. It now parses the response,
56
+ surfaces Cloudflare's own error codes as annotations, and stays non-fatal on
57
+ purpose: the site is live at origin and only the edge is stale, so failing the
58
+ job would misreport a good deploy.
59
+
60
+ ### Added — deploys cannot collide
61
+
62
+ Push, dispatch and cron could all fire at once, and the swap step `rm -rf`s and
63
+ `mv`s live directories on the server. Runs are now queued on one concurrency
64
+ group rather than cancelled, since cancelling mid-swap is the state being
65
+ avoided. It caught three real collisions on the day it was added.
66
+
67
+ ### Changed — actions on v7
68
+
69
+ `actions/checkout` and `actions/setup-node` moved v4 to v7, clearing GitHub's
70
+ Node 20 deprecation warning on every run. The majors were checked rather than
71
+ crossed blind: `setup-node` v5 auto-enables caching when `package.json` declares
72
+ `packageManager`, which neither repo does. `appleboy/ssh-action` stays on v1,
73
+ already current within that major.
74
+
75
+ ### Measured — the cron is not a 10-minute backstop
76
+
77
+ The schedule is configured `*/10`, but GitHub throttles high-frequency
78
+ schedules. Across the last 14 scheduled runs the average gap was **228 minutes**,
79
+ the longest **345**. Documented in the workflow so nobody plans around ten
80
+ minutes again.
81
+
82
+ **647 tests passing.**
83
+
84
+ ## v2.21.0 — the hooks were never actually turned on (September 2026)
85
+
86
+ Every hook ECC ships has been inert since the shell installer was written. Not
87
+ misconfigured — never registered.
88
+
89
+ ### What was broken
90
+
91
+ `install_hooks()` in `install.sh` did exactly one thing:
92
+
93
+ ```bash
94
+ cp "$SCRIPT_DIR/hooks/hooks.json" "$dest/hooks.json"
95
+ ```
96
+
97
+ Claude Code does not read that file. It reads `hooks` out of `settings.json`.
98
+ So the manifest landed on disk and nothing was ever wired up.
99
+
100
+ It also copied only the manifest, not the `hooks/memory/` and `hooks/safety/`
101
+ scripts every command inside it points at. Of the 12 files under `hooks/`, an
102
+ install copied **1**.
103
+
104
+ Measured on a fresh install into a throwaway `HOME`:
105
+
106
+ ```
107
+ before after
108
+ settings.json events 1 8
109
+ settings.json entries 1 45
110
+ hook scripts on disk 0 9
111
+ entries whose script exists n/a 36 of 36
112
+ ```
113
+
114
+ That one `PreToolUse` entry was RTK's — its installer writes `settings.json`
115
+ for its own hook. Nothing ECC ships was in there.
116
+
117
+ So none of this had ever run: memory inject on `SessionStart`, memory capture
118
+ and correction encoding on `Stop`, auto-recall on `UserPromptSubmit`, the
119
+ prompt-injection guard, the token-budget enforcer. The compound-memory
120
+ behaviour described in the docs could not have worked, because the hook that
121
+ implements it was never registered.
122
+
123
+ Correct merge logic already existed in `scripts/lib/install/apply.js` — but the
124
+ CLI shells out to `install.sh` and never reaches it. Instrumenting the function
125
+ during a real install confirmed zero calls.
126
+
127
+ ### The fix
128
+
129
+ `install_hooks()` now copies the whole `hooks/` tree and calls a new registrar,
130
+ `scripts/install/register-hooks.js`, which merges the manifest into
131
+ `settings.json` through that same tested merge:
132
+
133
+ - **Idempotent** — three consecutive installs give 45, 45, 45 entries.
134
+ - **Non-destructive** — RTK's hook and the user's own survive; verified.
135
+ - **Scoped** — only the `hooks` key changes; `model`, `theme` and `permissions`
136
+ are left alone.
137
+ - **Atomic** — temp file plus rename, so an interrupted install cannot leave a
138
+ truncated `settings.json` and a broken editor.
139
+ - **Permission-preserving** — an existing file keeps its mode; a new one is
140
+ `0600`, because `settings.json` can carry tokens.
141
+ - **Degrades honestly** — no Node, or a corrupt `settings.json`, prints what to
142
+ run rather than failing the install or losing the hooks.
143
+
144
+ `doctor` now reports `8 hook events wired` instead of warning that three are
145
+ missing.
146
+
147
+ ### Corrected
148
+
149
+ The installer banner claimed **194 skills, 97 commands, 22+ hooks**. Actual:
150
+ 196, 103, and 44 hook entries across 8 events. It is the first thing every user
151
+ sees, and all three numbers were wrong. Fixed in `install.sh`, `install.ps1`
152
+ and the CLI header.
153
+
154
+ One of those tests then broke Windows CI on its own terms: it asserted the
155
+ substituted root by searching `JSON.stringify(settings)` for the path. A Windows
156
+ root is `D:\a\repo`, which stringify escapes to `D:\\a\\repo`, so the match
157
+ failed even though the substitution was correct. It now asserts on the parsed
158
+ command strings. Reproduced locally with a Windows-shaped path before and after:
159
+ old assertion `false`, new assertion `true`.
160
+
161
+ **647 tests passing** — 14 new, covering idempotency, third-party hook
162
+ preservation, unrelated-key preservation, corrupt-config recovery, file modes,
163
+ and two that guard the regression directly: every event in the shipped manifest
164
+ must register, and every script it references must exist in the repo.
165
+
5
166
  ## v2.20.0 — 2.19.0 shipped 21 MB instead of 5; here is the guard (September 2026)
6
167
 
7
168
  **Upgrade from 2.19.0.** That release is 4× the size it should be and carries
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/ → 633 passing tests across 41 test files
29
+ tests/ → 647 passing tests across 42 test files
30
30
  ```
31
31
 
32
32
  ## Running Tests
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.20.0
1
+ 2.21.1
@@ -141,7 +141,7 @@ if (args[1] === '--help' || args[1] === '-h') {
141
141
 
142
142
  // ── Subcommand: mcp (start MCP server over stdio) ────────────────────────────
143
143
  // Usage: npx kodelyth-ecc mcp
144
- // Exposes 70 agents, 194 skills, 97 commands, the routing rule, and the local
144
+ // Exposes 70 agents, 196 skills, 103 commands, the routing rule, and the local
145
145
  // memory store to any MCP-compatible client (Claude Desktop, LangGraph, etc.).
146
146
  if (args[0] === 'mcp') {
147
147
  // Hand off entirely to the MCP server — it owns stdio from here on.
package/install.ps1 CHANGED
@@ -61,7 +61,7 @@ if ($Bundle) {
61
61
  # -- Banner --------------------------------------------------------------------
62
62
  Write-Host ""
63
63
  Write-Host " Kodelyth ECC -- Production-grade AI coding agent toolkit" -ForegroundColor Cyan
64
- Write-Host " 70 agents (8 devil-mode) | 194 skills | 97 commands | 22+ hooks | intent routing | local memory" -ForegroundColor Gray
64
+ Write-Host " 70 agents (8 devil-mode) | 196 skills | 103 commands | 44 hooks | intent routing | local memory" -ForegroundColor Gray
65
65
  Write-Host ""
66
66
 
67
67
  # $PSScriptRoot is always the directory containing this .ps1 file,
@@ -344,7 +344,7 @@ Powered by Kodelyth ECC -- github.com/sifxprime/kodelyth-ecc
344
344
  $geminiText = @'
345
345
  # Kodelyth ECC -- Gemini CLI context
346
346
 
347
- This project uses Kodelyth ECC -- 70 specialist agents, 194 skills, 97 commands, intent routing, and self-learning memory.
347
+ This project uses Kodelyth ECC -- 70 specialist agents, 196 skills, 103 commands, intent routing, and self-learning memory.
348
348
 
349
349
  The full toolkit is installed under this directory:
350
350
 
package/install.sh CHANGED
@@ -47,7 +47,7 @@ echo -e "${RED}${BOLD} ║ DANGER LEVEL: GOD TIER · NOT FOR JUNIOR DEV
47
47
  echo -e "${RED}${BOLD} ╚══════════════════════════════════════════════════════════════╝${RESET}"
48
48
  echo ""
49
49
  echo -e "${BOLD} Kodelyth ECC — The most dangerous AI coding toolkit on the planet${RESET}"
50
- echo -e "${CYAN} 70 specialist agents (8 devil-mode) · 194 skills · 97 commands · 22+ hooks · intent routing · compound memory${RESET}"
50
+ echo -e "${CYAN} 70 specialist agents (8 devil-mode) · 196 skills · 103 commands · 44 hooks · intent routing · compound memory${RESET}"
51
51
  echo -e "${CYAN} Any language · Any framework · Any scale · 300B-level quality${RESET}"
52
52
  echo ""
53
53
  echo -e " github.com/sifxprime/kodelyth-ecc"
@@ -435,7 +435,8 @@ install_flat() {
435
435
  }
436
436
 
437
437
  install_hooks() {
438
- local src="$SCRIPT_DIR/hooks/hooks.json"
438
+ local src_dir="$SCRIPT_DIR/hooks"
439
+ local src="$src_dir/hooks.json"
439
440
  local dest="$1"
440
441
 
441
442
  if [[ -z "$dest" ]] || [[ ! -f "$src" ]]; then
@@ -443,8 +444,39 @@ install_hooks() {
443
444
  fi
444
445
 
445
446
  mkdir -p "$dest"
446
- cp "$src" "$dest/hooks.json"
447
- echo -e " ${GREEN}✓${RESET} Hooks ${BLUE}(→ $dest/hooks.json)${RESET}"
447
+
448
+ # Copy the whole tree, not just hooks.json. Every command inside hooks.json
449
+ # points at ${CLAUDE_PLUGIN_ROOT}/hooks/{memory,safety}/*.js — copying the
450
+ # manifest alone registered hooks whose scripts were never installed.
451
+ cp -r "$src_dir"/. "$dest/"
452
+ local script_count
453
+ script_count=$(find "$dest" -name '*.js' -type f 2>/dev/null | wc -l | tr -d ' ')
454
+
455
+ echo -e " ${GREEN}✓${RESET} Hooks ${BLUE}(→ $dest, $script_count scripts)${RESET}"
456
+
457
+ # Registering into settings.json is the step that actually activates them.
458
+ # Claude Code reads hooks from settings.json; a hooks.json sitting on disk
459
+ # does nothing. Node ships with this package, but install.sh can also be run
460
+ # standalone, so degrade with a clear message rather than failing the install.
461
+ local registrar="$SCRIPT_DIR/scripts/install/register-hooks.js"
462
+ local target_root="${dest%/hooks}"
463
+
464
+ if [[ ! -f "$registrar" ]]; then
465
+ return
466
+ fi
467
+ if ! command -v node >/dev/null 2>&1; then
468
+ echo -e " ${YELLOW}!${RESET} Hooks copied but not registered — node not found"
469
+ echo -e " ${BLUE}Run after installing Node:${RESET} node \"$registrar\" \"$target_root\""
470
+ return
471
+ fi
472
+
473
+ local summary
474
+ if summary=$(node "$registrar" "$target_root" 2>&1); then
475
+ echo -e " ${GREEN}✓${RESET} Hooks registered ${BLUE}($summary)${RESET}"
476
+ else
477
+ echo -e " ${YELLOW}!${RESET} Hook registration failed: $summary"
478
+ echo -e " ${BLUE}Retry with:${RESET} node \"$registrar\" \"$target_root\""
479
+ fi
448
480
  }
449
481
 
450
482
  # Generate a single .windsurfrules file from all common rule .md files
@@ -621,7 +653,7 @@ AIDER_EOF
621
653
  cat > "$GEMINI_FILE" <<'GEMINI_EOF'
622
654
  # Kodelyth ECC — Gemini CLI context
623
655
 
624
- This project uses Kodelyth ECC — 70 specialist agents, 194 skills, 97 commands, intent routing, and self-learning memory.
656
+ This project uses Kodelyth ECC — 70 specialist agents, 196 skills, 103 commands, intent routing, and self-learning memory.
625
657
 
626
658
  The full toolkit is installed under this directory:
627
659
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.20.0",
3
+ "version": "2.21.1",
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",
@@ -28,6 +28,7 @@ Every row below was checked on a stock macOS shell. These are not theoretical.
28
28
  | `grep -c` under `set -e` | **any OS** — exits 1 on zero matches | `grep -c x f \|\| echo 0` |
29
29
  | `for x in $VAR` | **macOS/zsh** — no word splitting, loops once | list items literally, or `for x in "${ARR[@]}"` |
30
30
  | `execFileSync('npm', …)` | **Windows** — ENOENT bare (no PATHEXT), EINVAL on `.cmd` since Node 18.20.2 | `execSync` with one literal command string |
31
+ | `JSON.stringify(x).includes(winPath)` | **Windows** — stringify escapes `\\` to `\\\\` | assert on parsed values, never on JSON text |
31
32
 
32
33
  ## Measured, not assumed
33
34
 
@@ -28,6 +28,7 @@ Every row below was checked on a stock macOS shell. These are not theoretical.
28
28
  | `grep -c` under `set -e` | **any OS** — exits 1 on zero matches | `grep -c x f \|\| echo 0` |
29
29
  | `for x in $VAR` | **macOS/zsh** — no word splitting, loops once | list items literally, or `for x in "${ARR[@]}"` |
30
30
  | `execFileSync('npm', …)` | **Windows** — ENOENT bare (no PATHEXT), EINVAL on `.cmd` since Node 18.20.2 | `execSync` with one literal command string |
31
+ | `JSON.stringify(x).includes(winPath)` | **Windows** — stringify escapes `\\` to `\\\\` | assert on parsed values, never on JSON text |
31
32
 
32
33
  ## Measured, not assumed
33
34
 
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Register ECC's hooks into a Claude-compatible settings.json.
5
+ *
6
+ * Why this exists
7
+ * ---------------
8
+ * `install.sh` copied `hooks/hooks.json` into `<target>/hooks/hooks.json` and
9
+ * stopped there. Claude Code does not read that file — it reads `hooks` out of
10
+ * `settings.json`. So every hook ECC ships was placed on disk and never
11
+ * activated: memory inject (SessionStart), memory capture and correction
12
+ * encoding (Stop), the prompt-injection guard, the token-budget enforcer.
13
+ *
14
+ * The only entry that ever appeared in settings.json came from RTK's own
15
+ * installer, which writes that file for its own hook.
16
+ *
17
+ * A correct merge already existed in scripts/lib/install/apply.js, but the CLI
18
+ * shells out to install.sh and never reaches it. This script is the bridge:
19
+ * install.sh calls it, and it uses that same tested merge.
20
+ *
21
+ * Guarantees
22
+ * ----------
23
+ * - Idempotent. Re-running adds nothing; entries dedupe by content signature.
24
+ * - Non-destructive. Hooks already in settings.json are kept, including
25
+ * another tool's (RTK's) and the user's own.
26
+ * - Scoped. Only the `hooks` key is touched; every other setting is preserved.
27
+ * - Atomic. Written via a temp file and rename, so an interrupted install
28
+ * cannot leave a truncated settings.json — which would break the editor.
29
+ *
30
+ * Usage: node scripts/install/register-hooks.js <targetRoot> [hooksJsonPath]
31
+ */
32
+
33
+ const fs = require('fs');
34
+ const path = require('path');
35
+
36
+ const { mergeHookEntries } = require('../lib/install/apply.js');
37
+ const { replaceFilePreservingMode } = require('../lib/safe-fs.js');
38
+
39
+ function readJsonOrNull(file) {
40
+ try {
41
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
42
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * @param {string} targetRoot e.g. ~/.claude
50
+ * @param {string} [hooksJsonPath] defaults to <targetRoot>/hooks/hooks.json
51
+ * @returns {{status:string, events:number, added:number, total:number, settingsPath:string}}
52
+ */
53
+ function registerHooks(targetRoot, hooksJsonPath) {
54
+ if (!targetRoot) throw new Error('registerHooks: targetRoot is required');
55
+
56
+ const hooksPath = hooksJsonPath || path.join(targetRoot, 'hooks', 'hooks.json');
57
+ const config = readJsonOrNull(hooksPath);
58
+ if (!config) {
59
+ return { status: 'no-hooks-config', events: 0, added: 0, total: 0, settingsPath: '' };
60
+ }
61
+
62
+ const incoming = config.hooks && typeof config.hooks === 'object' && !Array.isArray(config.hooks)
63
+ ? config.hooks
64
+ : null;
65
+ if (!incoming) {
66
+ return { status: 'invalid-hooks-config', events: 0, added: 0, total: 0, settingsPath: '' };
67
+ }
68
+
69
+ const settingsPath = path.join(targetRoot, 'settings.json');
70
+ const settings = readJsonOrNull(settingsPath) || {};
71
+ const existing = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
72
+ ? settings.hooks
73
+ : {};
74
+
75
+ const before = countEntries(existing);
76
+ const merged = { ...existing };
77
+
78
+ for (const [event, entries] of Object.entries(incoming)) {
79
+ const current = Array.isArray(existing[event]) ? existing[event] : [];
80
+ const next = Array.isArray(entries) ? entries : [];
81
+ // targetRoot is the plugin root: hooks.json references
82
+ // ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/... which resolves under it.
83
+ merged[event] = mergeHookEntries(current, next, targetRoot);
84
+ }
85
+
86
+ const after = countEntries(merged);
87
+ const body = JSON.stringify({ ...settings, hooks: merged }, null, 2) + '\n';
88
+
89
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
90
+ // Keeps the file's existing permissions when it is already there — this is a
91
+ // user config and the installer has no business widening or narrowing it.
92
+ // 0o600 for a new one: settings.json can carry tokens.
93
+ replaceFilePreservingMode(settingsPath, body, 0o600);
94
+
95
+ return {
96
+ status: 'ok',
97
+ events: Object.keys(merged).length,
98
+ added: after - before,
99
+ total: after,
100
+ settingsPath,
101
+ };
102
+ }
103
+
104
+ function countEntries(hooksObject) {
105
+ let n = 0;
106
+ for (const entries of Object.values(hooksObject || {})) {
107
+ if (Array.isArray(entries)) {
108
+ for (const entry of entries) n += Array.isArray(entry.hooks) ? entry.hooks.length : 1;
109
+ }
110
+ }
111
+ return n;
112
+ }
113
+
114
+ module.exports = { registerHooks, countEntries };
115
+
116
+ if (require.main === module) {
117
+ const [targetRoot, hooksJsonPath] = process.argv.slice(2);
118
+ if (!targetRoot) {
119
+ console.error('usage: node scripts/install/register-hooks.js <targetRoot> [hooksJsonPath]');
120
+ process.exit(2);
121
+ }
122
+ try {
123
+ const r = registerHooks(targetRoot, hooksJsonPath);
124
+ if (r.status !== 'ok') {
125
+ // Not fatal: a target without a hooks config is a valid configuration.
126
+ console.error(`hooks not registered (${r.status})`);
127
+ process.exit(0);
128
+ }
129
+ console.log(`${r.total} hook entries across ${r.events} events (+${r.added} new)`);
130
+ } catch (err) {
131
+ // An install must not die here. Report loudly and let the rest complete —
132
+ // but exit non-zero so a caller that checks can surface it.
133
+ console.error(`hook registration failed: ${err.message}`);
134
+ process.exit(1);
135
+ }
136
+ }
@@ -261,5 +261,10 @@ function applyInstallPlan(plan) {
261
261
  }
262
262
 
263
263
  module.exports = {
264
+ // Exported so the shell installer can register hooks through the same tested
265
+ // merge as the JS install path — install.sh copied hooks.json but never wrote
266
+ // settings.json, so none of the hooks were ever active (fixed in 2.21.0).
267
+ mergeHookEntries,
268
+ replacePluginRootPlaceholders,
264
269
  applyInstallPlan,
265
270
  };