kodelyth-ecc 2.24.5 → 2.24.6

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.24.6 — brand assets advertised the wrong version and stale counts (October 2026)
6
+
7
+ Assets and documentation only. No code changes to the toolkit.
8
+
9
+ ### What was wrong
10
+
11
+ Every social and brand SVG carries its version and headline counts as literal
12
+ artwork, and nothing kept them in step with releases.
13
+
14
+ ECC's 23 SVGs said `v2.24.1`, four releases behind. The website's copies said
15
+ `v2.18.0` — seven behind — and the two sets had drifted apart, so the marketing
16
+ images on the site advertised a different version from the ones in the README.
17
+
18
+ The counts were worse, and were nearly missed: grepping for the *correct* values
19
+ (70 agents, 196 skills) found them and suggested the counts were fine. Rendering
20
+ `github-social-preview.png` showed otherwise — **97 COMMANDS** and **22+ HOOKS**
21
+ against an actual 103 and 44, on the flagship image used for the repository's
22
+ social card. Fixed across 11 SVGs per repo: 97 to 103 commands, 22+/20+ to 44
23
+ hooks, 384 to 672 tests, 14 to 15 rules.
24
+
25
+ The PNG exports were worse still, at `v2.7.0`.
26
+
27
+ ### Root cause
28
+
29
+ `.gitignore` names `node brand/convert.js` as the way to regenerate the exports.
30
+ That script covers only the `brand/` lockups from a hardcoded list, never the 49
31
+ `social/` exports — and it cannot run at all, because `puppeteer-core` is neither
32
+ installed nor declared in `package.json`. The documented regeneration path was
33
+ broken, and because the PNGs are gitignored build artifacts the drift was
34
+ invisible.
35
+
36
+ ### What ships
37
+
38
+ Two scripts, so this cannot recur silently:
39
+
40
+ - `scripts/brand/sync-asset-version.js` stamps the version from `package.json`
41
+ into every SVG. Takes `--root` so the website's copies are driven from the same
42
+ source of truth, and `--check` exits non-zero when anything is stale.
43
+ - `scripts/brand/export-assets.js` renders all 49 exports at 7680px, taking each
44
+ height from the SVG's own viewBox. `--check` compares mtimes and fails when a
45
+ PNG is older than its source.
46
+
47
+ Counts are deliberately NOT stamped by script. They are laid out as artwork, and
48
+ some numbers that look like catalog counts are not: "20+ rules stacked" counts
49
+ accumulated lessons, and "4 agents" / "8 agents" describe how many a given
50
+ parallel command fires. A blanket replace would rewrite those into nonsense, so
51
+ each count fix here was read in context first and those were left alone.
52
+
53
+ ### Also
54
+
55
+ The README now asks for a star. The post-install CLI asked once; the README —
56
+ which is also the npm package page, and the first thing most people see — never
57
+ did.
58
+
59
+ **672 tests across 43 files.**
60
+
5
61
  ## v2.24.5 — fix path traversal in session bundles (October 2026)
6
62
 
7
63
  **Security fix.** A session bundle is a portable, shareable artifact — that is the
package/README.md CHANGED
@@ -1157,6 +1157,20 @@ command you ran, the full error, and your `kodelythecc doctor` output.
1157
1157
 
1158
1158
  <div align="center">
1159
1159
 
1160
+ ### If ECC saved you time
1161
+
1162
+ Starring the repo is the main way other developers find it — the project has no
1163
+ marketing budget and no telemetry, so visibility is the only signal that it is
1164
+ worth anyone's time.
1165
+
1166
+ [![Star on GitHub](https://img.shields.io/github/stars/sifxprime/kodelyth-ecc?style=social)](https://github.com/sifxprime/kodelyth-ecc)
1167
+
1168
+ </div>
1169
+
1170
+ ---
1171
+
1172
+ <div align="center">
1173
+
1160
1174
  [![npm](https://img.shields.io/badge/npm-kodelyth--ecc-red.svg)](https://www.npmjs.com/package/kodelyth-ecc)
1161
1175
  [![GitHub](https://img.shields.io/badge/GitHub-sifxprime%2Fkodelyth--ecc-181717.svg)](https://github.com/sifxprime/kodelyth-ecc)
1162
1176
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.24.5",
3
+ "version": "2.24.6",
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",
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Render every social SVG to its 8K PNG export.
6
+ *
7
+ * The PNGs under social/exports/8k/ are rasterised copies of the SVGs, used where
8
+ * an SVG will not do — GitHub's social preview, and most social platforms, accept
9
+ * only raster uploads. Nothing regenerated them, so they drifted badly: by v2.24.5
10
+ * the exports still read v2.7.0, 97 commands and 22+ hooks, against an actual
11
+ * 103 and 44. The SVGs had been corrected several times; the PNGs never were.
12
+ *
13
+ * Every existing export is 7680px wide with the aspect ratio taken from the SVG's
14
+ * own viewBox, so that is what this reproduces.
15
+ *
16
+ * node scripts/brand/export-assets.js # render all
17
+ * node scripts/brand/export-assets.js --check # report stale, change nothing
18
+ * node scripts/brand/export-assets.js card-main # render one, by name
19
+ *
20
+ * --check compares mtimes and exits non-zero when any PNG is older than its SVG,
21
+ * so a release step can catch exports that no longer match their source.
22
+ *
23
+ * Requires rsvg-convert (librsvg). It is not installed by default anywhere, so its
24
+ * absence is reported as a clear instruction rather than a spawn error.
25
+ *
26
+ * On the choice of renderer: brand/convert.js already renders the brand/ lockups
27
+ * with puppeteer-core, chosen there for font fidelity, and consistency would argue
28
+ * for using it here too. It is not used because it cannot run — puppeteer-core is
29
+ * neither installed nor declared in package.json, so `node brand/convert.js` fails
30
+ * on a clean checkout. That is most likely why these exports went stale in the
31
+ * first place: the tool named in .gitignore for regenerating them does not work.
32
+ *
33
+ * rsvg-convert was checked against the output it replaces rather than assumed
34
+ * equivalent: re-rendering github-social-preview.png produced 491,233 bytes against
35
+ * the previous Chrome-rendered 491,564 at identical dimensions, and the two were
36
+ * compared side by side — same fonts, same geometry, differing only in the text
37
+ * that was actually corrected. If puppeteer-core is ever added as a real
38
+ * dependency, moving this to the same renderer would be a reasonable tidy-up.
39
+ */
40
+
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const { execFileSync, spawnSync } = require('child_process');
44
+
45
+ const ROOT = path.join(__dirname, '..', '..');
46
+ const SVG_DIR = path.join(ROOT, 'social');
47
+ const PNG_DIR = path.join(SVG_DIR, 'exports', '8k');
48
+ const WIDTH = 7680;
49
+
50
+ function haveRenderer() {
51
+ try {
52
+ execFileSync('rsvg-convert', ['--version'], { stdio: 'ignore' });
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function listPairs(filter) {
60
+ if (!fs.existsSync(SVG_DIR)) return [];
61
+ return fs
62
+ .readdirSync(SVG_DIR)
63
+ .filter((f) => f.endsWith('.svg'))
64
+ .map((f) => path.basename(f, '.svg'))
65
+ .filter((name) => !filter || name === filter)
66
+ .sort()
67
+ .map((name) => ({
68
+ name,
69
+ svg: path.join(SVG_DIR, `${name}.svg`),
70
+ png: path.join(PNG_DIR, `${name}.png`),
71
+ }));
72
+ }
73
+
74
+ /** A PNG is stale when it is missing or older than the SVG it came from. */
75
+ function isStale({ svg, png }) {
76
+ if (!fs.existsSync(png)) return true;
77
+ return fs.statSync(png).mtimeMs < fs.statSync(svg).mtimeMs;
78
+ }
79
+
80
+ function run({ check = false, only = null } = {}) {
81
+ const pairs = listPairs(only);
82
+ if (pairs.length === 0) {
83
+ console.error(only ? `no such SVG: social/${only}.svg` : `no SVGs in ${SVG_DIR}`);
84
+ return 1;
85
+ }
86
+
87
+ const stale = pairs.filter(isStale);
88
+
89
+ if (check) {
90
+ for (const p of stale) console.log(` STALE ${p.name}.png`);
91
+ console.log(
92
+ stale.length === 0
93
+ ? ` all ${pairs.length} exports are newer than their SVG`
94
+ : ` ${stale.length} of ${pairs.length} export(s) stale`
95
+ );
96
+ return stale.length === 0 ? 0 : 1;
97
+ }
98
+
99
+ if (!haveRenderer()) {
100
+ console.error(' rsvg-convert not found. Install librsvg:');
101
+ console.error(' macOS: brew install librsvg');
102
+ console.error(' Debian: apt-get install librsvg2-bin');
103
+ return 1;
104
+ }
105
+
106
+ fs.mkdirSync(PNG_DIR, { recursive: true });
107
+
108
+ let ok = 0;
109
+ const failed = [];
110
+ for (const p of pairs) {
111
+ // Width only — rsvg-convert derives the height from the viewBox, so each
112
+ // card keeps its own aspect ratio without hardcoding 49 different heights.
113
+ const r = spawnSync('rsvg-convert', ['-w', String(WIDTH), p.svg, '-o', p.png], {
114
+ stdio: ['ignore', 'ignore', 'pipe'],
115
+ });
116
+ if (r.status === 0 && fs.existsSync(p.png) && fs.statSync(p.png).size > 0) {
117
+ ok += 1;
118
+ } else {
119
+ failed.push(`${p.name}: ${String(r.stderr || '').trim() || `exit ${r.status}`}`);
120
+ }
121
+ }
122
+
123
+ console.log(` rendered ${ok}/${pairs.length} at ${WIDTH}px wide`);
124
+ for (const f of failed) console.error(` FAILED ${f}`);
125
+ return failed.length === 0 ? 0 : 1;
126
+ }
127
+
128
+ if (require.main === module) {
129
+ const args = process.argv.slice(2);
130
+ const check = args.includes('--check');
131
+ const only = args.find((a) => !a.startsWith('--')) || null;
132
+ process.exit(run({ check, only }));
133
+ }
134
+
135
+ module.exports = { run, listPairs, isStale, WIDTH };
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Stamp the current package version into every brand and social SVG.
6
+ *
7
+ * These assets carry a literal "v2.24.1" in their artwork, and nothing kept them
8
+ * in step with releases. By 2.24.5 the ECC copies were four releases behind and
9
+ * the website's copies were SEVEN behind, at v2.18.0 — the two sets had drifted
10
+ * apart as well, so the marketing images on the site advertised a different
11
+ * version from the ones in the README.
12
+ *
13
+ * Only the version token is touched. Counts (70 agents, 196 skills, …) are laid
14
+ * out as artwork and change rarely, so they stay a deliberate edit; a script that
15
+ * silently reflowed them would do more harm than the staleness it fixed.
16
+ *
17
+ * node scripts/brand/sync-asset-version.js # ECC's own assets
18
+ * node scripts/brand/sync-asset-version.js --check # report, change nothing
19
+ * node scripts/brand/sync-asset-version.js --root ../ecc-web/public
20
+ *
21
+ * --check exits non-zero when anything is stale, so a release step can refuse to
22
+ * ship assets that advertise the wrong version.
23
+ */
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const ROOT = path.join(__dirname, '..', '..');
29
+ const VERSION = require(path.join(ROOT, 'package.json')).version;
30
+
31
+ // A version token in artwork: "v" followed by semver, as a whole word. Anchored
32
+ // on the leading "v" so it cannot match an IP address — section-dashboard.svg
33
+ // contains "127.0.0.1", which a bare semver pattern happily mangles.
34
+ const VERSION_TOKEN = /\bv\d+\.\d+\.\d+\b/g;
35
+
36
+ function collectSvgs(root) {
37
+ const out = [];
38
+ for (const dir of ['social', 'brand']) {
39
+ const full = path.join(root, dir);
40
+ if (!fs.existsSync(full)) continue;
41
+ for (const name of fs.readdirSync(full)) {
42
+ if (name.endsWith('.svg')) out.push(path.join(full, name));
43
+ }
44
+ }
45
+ return out.sort();
46
+ }
47
+
48
+ function run({ root, check }) {
49
+ const files = collectSvgs(root);
50
+ if (files.length === 0) {
51
+ console.error(`no social/ or brand/ SVGs under ${root}`);
52
+ return 1;
53
+ }
54
+
55
+ const stale = [];
56
+ let changedFiles = 0;
57
+ let changedTokens = 0;
58
+
59
+ for (const file of files) {
60
+ const before = fs.readFileSync(file, 'utf8');
61
+ const found = before.match(VERSION_TOKEN);
62
+ if (!found) continue;
63
+
64
+ const outdated = found.filter((t) => t !== `v${VERSION}`);
65
+ if (outdated.length === 0) continue;
66
+
67
+ stale.push({ file, versions: [...new Set(outdated)], count: outdated.length });
68
+ changedTokens += outdated.length;
69
+ changedFiles += 1;
70
+
71
+ if (!check) {
72
+ fs.writeFileSync(file, before.replace(VERSION_TOKEN, `v${VERSION}`), 'utf8');
73
+ }
74
+ }
75
+
76
+ const rel = (f) => path.relative(root, f);
77
+ for (const s of stale) {
78
+ console.log(` ${check ? 'STALE' : 'updated'} ${rel(s.file).padEnd(40)} ${s.versions.join(', ')} -> v${VERSION}`);
79
+ }
80
+
81
+ if (stale.length === 0) {
82
+ console.log(` all ${files.length} SVGs already at v${VERSION}`);
83
+ return 0;
84
+ }
85
+
86
+ console.log(
87
+ ` ${check ? 'stale' : 'synced'}: ${changedFiles} file(s), ${changedTokens} token(s) -> v${VERSION}`
88
+ );
89
+ return check ? 1 : 0;
90
+ }
91
+
92
+ if (require.main === module) {
93
+ const args = process.argv.slice(2);
94
+ const check = args.includes('--check');
95
+ const rootFlag = args.indexOf('--root');
96
+ const root = rootFlag >= 0 ? path.resolve(args[rootFlag + 1]) : ROOT;
97
+ process.exit(run({ root, check }));
98
+ }
99
+
100
+ module.exports = { run, collectSvgs, VERSION_TOKEN };