kodelyth-ecc 2.23.2 → 2.24.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 +101 -0
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +17 -1
- package/package.json +1 -1
- package/scripts/cli/menu.js +3 -1
- package/scripts/cli/update-check.js +38 -2
- package/scripts/doctor-health.js +29 -2
- package/scripts/lib/win-install.js +63 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.24.1 — verify Windows downloads before running them (October 2026)
|
|
6
|
+
|
|
7
|
+
**Security fix.** The Windows installer added in 2.23.0 downloaded a release zip
|
|
8
|
+
over HTTPS and extracted it straight into the user's PATH directory **with no
|
|
9
|
+
integrity check at all**.
|
|
10
|
+
|
|
11
|
+
This repository ships a `supply-chain-auditor` agent whose entire job is catching
|
|
12
|
+
"downloads and executes a binary without verifying it". The code doing it was
|
|
13
|
+
ours. Both upstream projects publish a `checksums.txt` in the same release, in
|
|
14
|
+
standard `sha256sum` format, and nothing was reading it.
|
|
15
|
+
|
|
16
|
+
### What it does now
|
|
17
|
+
|
|
18
|
+
Fetches `checksums.txt` from the release, finds the line for the asset it just
|
|
19
|
+
downloaded, hashes the file with Node's `crypto`, and compares.
|
|
20
|
+
|
|
21
|
+
- **Mismatch is fatal.** It refuses to install and reports both hashes.
|
|
22
|
+
- **Missing or unlisted warns and proceeds.** The fetch is HTTPS from
|
|
23
|
+
github.com so transport tampering is already hard, and a release that stops
|
|
24
|
+
publishing checksums should not make ECC look broken. The case this control
|
|
25
|
+
exists for is a mismatch, and that stays fatal.
|
|
26
|
+
|
|
27
|
+
Hashing uses Node's `crypto` rather than shelling out, because `certutil`,
|
|
28
|
+
`sha256sum` and `shasum` differ in availability and output format across the
|
|
29
|
+
machines this has to run on.
|
|
30
|
+
|
|
31
|
+
### Proven, not asserted
|
|
32
|
+
|
|
33
|
+
Checked against the real artifact before a single test was written: the genuine
|
|
34
|
+
RTK Windows asset hashes to its published sha256, and flipping one byte produces
|
|
35
|
+
a mismatch.
|
|
36
|
+
|
|
37
|
+
Nine tests cover a matching checksum, a single changed byte, an unlisted asset,
|
|
38
|
+
a malformed entry, picking the right line out of a full file, uppercase hex, and
|
|
39
|
+
that a filename merely *ending* with the asset name does not satisfy the lookup
|
|
40
|
+
(`evil-asset.zip` must not pass as `asset.zip`).
|
|
41
|
+
|
|
42
|
+
The Windows CI job asserts the gate is exercised on every push — a security
|
|
43
|
+
control nobody runs is decoration. From a real `windows-latest` runner:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
[win] checksum verified (sha256 1623e9b45d28…)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**656 tests across 43 files.**
|
|
50
|
+
|
|
51
|
+
## v2.24.0 — the update notice now tells you why (October 2026)
|
|
52
|
+
|
|
53
|
+
The CLI has always polled npm for new versions and cached the answer for 24
|
|
54
|
+
hours. That monthly touchpoint was spent on almost nothing: the result surfaced
|
|
55
|
+
only in the interactive menu, as a version number and an install command.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Update to v2.23.2
|
|
59
|
+
npm i -g kodelyth-ecc (installs new version)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A version number is a nag. It states a fact and gives no reason to act on it.
|
|
63
|
+
|
|
64
|
+
### It now links what changed
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Update to v2.24.0
|
|
68
|
+
what changed: github.com/sifxprime/kodelyth-ecc/releases/tag/v2.24.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`check()` returns a `releaseNotes` URL alongside the version, so every consumer
|
|
72
|
+
gets it without doing its own string building.
|
|
73
|
+
|
|
74
|
+
### doctor surfaces it too
|
|
75
|
+
|
|
76
|
+
A second natural touchpoint — `doctor` is run deliberately, usually when
|
|
77
|
+
somebody is already paying attention:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
! version — v2.23.2 installed, v2.24.0 available
|
|
81
|
+
→ npm i -g kodelyth-ecc what changed: .../releases/tag/v2.24.0
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Cache-only, on purpose
|
|
85
|
+
|
|
86
|
+
The new `cachedCheck()` reads the 24-hour cache and never touches the network.
|
|
87
|
+
`doctor` is the command people reach for when something is **already** wrong, so
|
|
88
|
+
it must not wait on the registry — and an unreachable npm is not a fact about
|
|
89
|
+
the health of your install. A cold or stale cache yields nothing and the row is
|
|
90
|
+
simply absent, rather than reported as a warning. A check that fires on "I could
|
|
91
|
+
not reach the network" teaches people to ignore the output.
|
|
92
|
+
|
|
93
|
+
`run()` stays synchronous and takes the result as a parameter, so it remains
|
|
94
|
+
testable and `doctor` adds no latency.
|
|
95
|
+
|
|
96
|
+
Verified across every state: a newer version warns with the link, being current
|
|
97
|
+
passes, a stale cache is ignored, a corrupt cache leaves `doctor` working on its
|
|
98
|
+
11 checks, and a cold cache omits the row.
|
|
99
|
+
|
|
100
|
+
### What this does not do
|
|
101
|
+
|
|
102
|
+
No nagging on every command. Nothing auto-updates. Nothing blocks.
|
|
103
|
+
|
|
104
|
+
**647 tests passing.**
|
|
105
|
+
|
|
5
106
|
## v2.23.2 — the README leads with the product, not the badges (October 2026)
|
|
6
107
|
|
|
7
108
|
No change to the toolkit. The README **is** the npm landing page, and npm only
|
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/ → 656 passing tests across 43 test files
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Running Tests
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Google Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi and Gemini CLI.
|
|
|
44
44
|
[](https://www.npmjs.com/package/kodelyth-ecc)
|
|
45
45
|
[](https://github.com/sifxprime/kodelyth-ecc/stargazers)
|
|
46
46
|
[](LICENSE)
|
|
47
|
-

|
|
48
48
|

|
|
49
49
|

|
|
50
50
|

|
|
@@ -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 | ❌ | Varies |
|
|
121
|
-
| Test coverage | ✅
|
|
121
|
+
| Test coverage | ✅ 656 tests | ❌ | ❌ | ❌ |
|
|
122
122
|
| Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
|
|
123
123
|
|
|
124
124
|
---
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.24.1
|
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -707,7 +707,23 @@ if (args[0] === 'god' || args[0] === 'evil' || args[0] === 'arena') {
|
|
|
707
707
|
|
|
708
708
|
if (args[0] === 'doctor') {
|
|
709
709
|
const { run, PASS, WARN, FAIL } = require(path.join(ROOT, 'scripts', 'doctor-health.js'));
|
|
710
|
-
|
|
710
|
+
|
|
711
|
+
// Cache-only, deliberately. doctor is what people run when something is
|
|
712
|
+
// already wrong, so it must not wait on the registry — and an unreachable npm
|
|
713
|
+
// is not a fact about the health of their install. The cache is warmed by the
|
|
714
|
+
// interactive menu; a cold one yields null and the version row is simply
|
|
715
|
+
// absent.
|
|
716
|
+
let update;
|
|
717
|
+
try {
|
|
718
|
+
const { cachedCheck } = require(path.join(ROOT, 'scripts', 'cli', 'update-check.js'));
|
|
719
|
+
const vf = path.join(ROOT, 'VERSION');
|
|
720
|
+
const current = fs.existsSync(vf)
|
|
721
|
+
? fs.readFileSync(vf, 'utf8').trim()
|
|
722
|
+
: require(path.join(ROOT, 'package.json')).version;
|
|
723
|
+
update = cachedCheck(current) || undefined;
|
|
724
|
+
} catch { /* never let a version hint break the health check */ }
|
|
725
|
+
|
|
726
|
+
const report = run({ update });
|
|
711
727
|
if (args.includes('--json')) {
|
|
712
728
|
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
713
729
|
process.exit(report.summary.fail > 0 ? 1 : 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.1",
|
|
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",
|
package/scripts/cli/menu.js
CHANGED
|
@@ -47,7 +47,9 @@ async function buildOptions({ ROOT }) {
|
|
|
47
47
|
if (update.updateAvailable) {
|
|
48
48
|
opts.push({
|
|
49
49
|
label: `Update to v${update.latest}`,
|
|
50
|
-
|
|
50
|
+
// Say what it is AND where to read what changed. A version number alone
|
|
51
|
+
// is a nag; a reason is an invitation.
|
|
52
|
+
hint: `what changed: ${update.releaseNotes || 'github.com/sifxprime/kodelyth-ecc/releases'}`,
|
|
51
53
|
run: () => runUpdate(),
|
|
52
54
|
badge: 'NEW',
|
|
53
55
|
});
|
|
@@ -54,7 +54,19 @@ function cmpVersion(a, b) {
|
|
|
54
54
|
return 0;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Where to read what actually changed in a given version.
|
|
59
|
+
*
|
|
60
|
+
* "update available: v2.23.2" is a nag — it tells you a number and no reason.
|
|
61
|
+
* A link to the release notes turns the same notice into something worth acting
|
|
62
|
+
* on, which is the whole point of owning a monthly touchpoint.
|
|
63
|
+
*/
|
|
64
|
+
function releaseNotesUrl(version) {
|
|
65
|
+
if (!version) return null;
|
|
66
|
+
return `https://github.com/sifxprime/kodelyth-ecc/releases/tag/v${version}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Public: returns { current, latest, updateAvailable, cached, releaseNotes } or nulls on failure.
|
|
58
70
|
async function check({ current, force = false } = {}) {
|
|
59
71
|
if (!force) {
|
|
60
72
|
const c = readCache();
|
|
@@ -63,6 +75,7 @@ async function check({ current, force = false } = {}) {
|
|
|
63
75
|
current, latest: c.latest,
|
|
64
76
|
updateAvailable: c.latest && cmpVersion(c.latest, current) > 0,
|
|
65
77
|
cached: true,
|
|
78
|
+
releaseNotes: releaseNotesUrl(c.latest),
|
|
66
79
|
};
|
|
67
80
|
}
|
|
68
81
|
}
|
|
@@ -72,7 +85,30 @@ async function check({ current, force = false } = {}) {
|
|
|
72
85
|
current, latest,
|
|
73
86
|
updateAvailable: !!(latest && cmpVersion(latest, current) > 0),
|
|
74
87
|
cached: false,
|
|
88
|
+
releaseNotes: releaseNotesUrl(latest),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Cache-only read. No network, no promise, no timeout — safe to call from a
|
|
94
|
+
* synchronous command.
|
|
95
|
+
*
|
|
96
|
+
* doctor uses this rather than check(): it is the command people run when
|
|
97
|
+
* something is already wrong, so it must never wait on the registry. The cache
|
|
98
|
+
* is warmed by the interactive menu, which does hit the network. A cold cache
|
|
99
|
+
* simply yields null and the version row does not appear, which is correct —
|
|
100
|
+
* "I could not reach npm" is not a fact about the health of your install.
|
|
101
|
+
*/
|
|
102
|
+
function cachedCheck(current) {
|
|
103
|
+
const c = readCache();
|
|
104
|
+
if (!c || !c.latest) return null;
|
|
105
|
+
return {
|
|
106
|
+
current,
|
|
107
|
+
latest: c.latest,
|
|
108
|
+
updateAvailable: cmpVersion(c.latest, current) > 0,
|
|
109
|
+
cached: true,
|
|
110
|
+
releaseNotes: releaseNotesUrl(c.latest),
|
|
75
111
|
};
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
module.exports = { check, cmpVersion };
|
|
114
|
+
module.exports = { check, cachedCheck, cmpVersion, releaseNotesUrl };
|
package/scripts/doctor-health.js
CHANGED
|
@@ -155,7 +155,29 @@ function checkCodebaseGraph() {
|
|
|
155
155
|
|
|
156
156
|
// ── Runner ───────────────────────────────────────────────────────────────────
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Whether the installed version is current.
|
|
160
|
+
*
|
|
161
|
+
* Takes a pre-fetched result rather than doing its own network call, so run()
|
|
162
|
+
* stays synchronous and testable, and doctor never blocks on the registry. When
|
|
163
|
+
* nothing was fetched — offline, timed out, caller did not bother — this is
|
|
164
|
+
* silently absent rather than a warning. "Could not reach npm" is not a health
|
|
165
|
+
* problem with the user's install, and reporting it as one trains people to
|
|
166
|
+
* ignore the output.
|
|
167
|
+
*/
|
|
168
|
+
function checkVersion(update) {
|
|
169
|
+
if (!update || !update.latest) return null;
|
|
170
|
+
if (!update.updateAvailable) {
|
|
171
|
+
return { status: PASS, detail: `v${update.current} is the latest` };
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
status: WARN,
|
|
175
|
+
detail: `v${update.current} installed, v${update.latest} available`,
|
|
176
|
+
fix: `npm i -g kodelyth-ecc what changed: ${update.releaseNotes || 'github.com/sifxprime/kodelyth-ecc/releases'}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function run({ update } = {}) {
|
|
159
181
|
const checks = [
|
|
160
182
|
check('binaries', checkBinaries),
|
|
161
183
|
check('install-target', checkInstallTarget),
|
|
@@ -169,6 +191,11 @@ function run() {
|
|
|
169
191
|
check('terse', checkTerse),
|
|
170
192
|
check('codebase-graph', checkCodebaseGraph),
|
|
171
193
|
];
|
|
194
|
+
|
|
195
|
+
// Appended rather than inlined: it is the only check that depends on the
|
|
196
|
+
// network, and it drops out entirely when no result was supplied.
|
|
197
|
+
const version = checkVersion(update);
|
|
198
|
+
if (version) checks.push({ id: 'version', ...version });
|
|
172
199
|
const summary = {
|
|
173
200
|
total: checks.length,
|
|
174
201
|
pass: checks.filter(c => c.status === PASS).length,
|
|
@@ -178,4 +205,4 @@ function run() {
|
|
|
178
205
|
return { checks, summary };
|
|
179
206
|
}
|
|
180
207
|
|
|
181
|
-
module.exports = { run, PASS, WARN, FAIL };
|
|
208
|
+
module.exports = { run, checkVersion, PASS, WARN, FAIL };
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const os = require('os');
|
|
28
28
|
const path = require('path');
|
|
29
|
+
const crypto = require('crypto');
|
|
29
30
|
const { execFileSync, spawnSync } = require('child_process');
|
|
30
31
|
|
|
31
32
|
/** Where ECC puts binaries it installs on Windows. User-writable, no admin. */
|
|
@@ -81,6 +82,29 @@ function installFromRelease({ repo, asset, binName, log = () => {} }) {
|
|
|
81
82
|
return { installed: false, reason: `downloaded file is empty — ${url}` };
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// Verify the download before extracting, let alone running it.
|
|
86
|
+
//
|
|
87
|
+
// ECC ships a supply-chain-auditor agent whose whole job is catching
|
|
88
|
+
// "downloads and executes a binary with no integrity check" — and the first
|
|
89
|
+
// version of this file did exactly that. Both projects publish a
|
|
90
|
+
// checksums.txt in the same release, so there is no excuse.
|
|
91
|
+
const verdict = verifyChecksum({ repo, asset, file: zip, log });
|
|
92
|
+
if (verdict.status === 'mismatch') {
|
|
93
|
+
return {
|
|
94
|
+
installed: false,
|
|
95
|
+
reason: `checksum mismatch for ${asset} — expected ${verdict.expected}, got ${verdict.actual}. Refusing to install.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (verdict.status === 'unavailable') {
|
|
99
|
+
// Warn rather than fail. The fetch is HTTPS from github.com, so transport
|
|
100
|
+
// tampering is already hard, and a release that simply stops publishing
|
|
101
|
+
// checksums should not make ECC look broken. A MISMATCH is still fatal —
|
|
102
|
+
// that is the case this check exists for.
|
|
103
|
+
log(`[win] WARNING: no checksum available for ${asset} (${verdict.reason}) — proceeding on HTTPS alone`);
|
|
104
|
+
} else {
|
|
105
|
+
log(`[win] checksum verified (sha256 ${verdict.expected.slice(0, 12)}…)`);
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
log('[win] extracting…');
|
|
85
109
|
const ex = spawnSync('tar', ['-xf', zip, '-C', dir], { stdio: 'inherit' });
|
|
86
110
|
if (ex.status !== 0) {
|
|
@@ -154,4 +178,42 @@ function setxHint(dir) {
|
|
|
154
178
|
return `setx PATH "%PATH%;${dir}"`;
|
|
155
179
|
}
|
|
156
180
|
|
|
157
|
-
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check a downloaded asset against the release's checksums.txt.
|
|
184
|
+
*
|
|
185
|
+
* Both upstreams publish the standard sha256sum format:
|
|
186
|
+
* <64 hex> <filename>
|
|
187
|
+
*
|
|
188
|
+
* Returns { status: 'ok' | 'mismatch' | 'unavailable' }. Hashing is done with
|
|
189
|
+
* Node's crypto rather than shelling out, because certutil, sha256sum and
|
|
190
|
+
* shasum all differ in availability and output format across the machines this
|
|
191
|
+
* has to run on.
|
|
192
|
+
*/
|
|
193
|
+
function verifyChecksum({ repo, asset, file, log = () => {}, checksumText }) {
|
|
194
|
+
// checksumText is injectable so the parsing and comparison can be tested
|
|
195
|
+
// without reaching the network. Production callers never pass it.
|
|
196
|
+
let text = checksumText;
|
|
197
|
+
if (text === undefined) {
|
|
198
|
+
const url = `https://github.com/${repo}/releases/latest/download/checksums.txt`;
|
|
199
|
+
try {
|
|
200
|
+
text = execFileSync('curl', ['-fsSL', url], { encoding: 'utf8', timeout: 20000 });
|
|
201
|
+
} catch {
|
|
202
|
+
return { status: 'unavailable', reason: 'checksums.txt could not be fetched' };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const line = text.split('\n').find((l) => l.trim().endsWith(` ${asset}`) || l.trim().endsWith(` ${asset}`));
|
|
207
|
+
if (!line) return { status: 'unavailable', reason: `${asset} not listed in checksums.txt` };
|
|
208
|
+
|
|
209
|
+
const expected = (line.trim().split(/\s+/)[0] || '').toLowerCase();
|
|
210
|
+
if (!/^[0-9a-f]{64}$/.test(expected)) {
|
|
211
|
+
return { status: 'unavailable', reason: 'malformed checksum entry' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
215
|
+
if (actual !== expected) return { status: 'mismatch', expected, actual };
|
|
216
|
+
return { status: 'ok', expected };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = { installFromRelease, installDir, isOnUserPath, setxHint, findBinary, verifyChecksum };
|