kodelyth-ecc 2.21.2 → 2.23.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,152 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.23.0 — Windows has no manual step left (October 2026)
6
+
7
+ 2.22.0 made the Windows install work but left two components needing a manual
8
+ download: RTK and the codebase graph. That turned out to be a gap in ECC, not a
9
+ limitation upstream — **both projects publish Windows builds** and ECC simply
10
+ never fetched them:
11
+
12
+ ```
13
+ rtk-x86_64-pc-windows-msvc.zip
14
+ codebase-memory-mcp-windows-amd64.zip / -arm64.zip
15
+ ```
16
+
17
+ Both now install automatically. Windows is at parity with macOS and Linux.
18
+
19
+ ### How it works
20
+
21
+ A shared `scripts/lib/win-install.js`, used by both. It pulls from GitHub's
22
+ `releases/latest/download/<asset>` URL, which always redirects to the newest
23
+ release — no API call, so no token and no rate limit.
24
+
25
+ Download and extraction use `curl.exe` and `tar.exe`. Both ship with Windows 10
26
+ 1803 and later, and `tar` there is bsdtar, which handles zip. That avoids
27
+ PowerShell quoting entirely. Both are probed before use, so an older machine
28
+ gets a clear message instead of a spawn error.
29
+
30
+ Architecture comes from `process.arch`: the codebase graph has a native arm64
31
+ build, while RTK publishes x86_64 only — fine on arm64 Windows, which runs x64
32
+ under emulation.
33
+
34
+ ### PATH is yours, not ours
35
+
36
+ Binaries land in `%LOCALAPPDATA%\Kodelyth\bin`. ECC does **not** edit your PATH.
37
+ It prints the one command that does:
38
+
39
+ ```powershell
40
+ setx PATH "%PATH%;%LOCALAPPDATA%\Kodelyth\bin"
41
+ ```
42
+
43
+ This mirrors the POSIX path, which reports `~/.local/bin` rather than writing to
44
+ a shell rc. Whether the directory is already persistent is read from
45
+ `HKCU\Environment`, not from `process.env` — the latter would always say yes
46
+ right after the installer prepends it for its own process, and would wrongly
47
+ tell you there is nothing left to do.
48
+
49
+ ### Verified, not asserted
50
+
51
+ The Windows CI job now installs for real and asserts both binaries exist and
52
+ run:
53
+
54
+ ```
55
+ install dir: C:\Users\runneradmin\AppData\Local\Kodelyth\bin
56
+ rtk.exe 10,285,056 bytes
57
+ rtk 0.51.0
58
+ codebase-memory-mcp 0.11.0
59
+ ```
60
+
61
+ `doctor`'s Windows hints changed too. They previously said "no Windows installer
62
+ yet" and pointed at a release page; the install command is now correct on every
63
+ platform, and the hint names the PATH step rather than implying the command
64
+ alone finishes the job.
65
+
66
+ **647 tests passing.**
67
+
68
+ ## v2.22.0 — Windows actually works now (September 2026)
69
+
70
+ The Windows install was broken in three separate ways. Windows CI had been green
71
+ throughout, because the test suite only ever exercised library functions — it
72
+ never ran the installer, which takes a completely different path there
73
+ (`install.ps1` through PowerShell rather than `install.sh` through bash).
74
+
75
+ Every finding below was measured on a real `windows-latest` runner, not inferred
76
+ from reading the code.
77
+
78
+ ### The documented command failed outright
79
+
80
+ `install.sh` matches `claude-home|claude-code` in every case arm, so both
81
+ spellings work on macOS and Linux. `install.ps1` had three separate
82
+ `switch ($Target)` blocks that each matched only `claude-home`, so the other
83
+ spelling hit the default arm:
84
+
85
+ ```
86
+ Unknown target: claude-code
87
+ Valid targets: claude-home, windsurf-project, ...
88
+ ```
89
+
90
+ `claude-code` is what the README, the docs and the website install page all tell
91
+ people to use. **Every Windows user following the documented instructions got a
92
+ hard failure.** Aliases are now normalised once at the top of the file, which
93
+ covers all three switches and keeps the accepted set identical to `install.sh`
94
+ rather than merely similar.
95
+
96
+ ### None of the post-install wiring ever ran
97
+
98
+ The Windows branch called `process.exit()` immediately after `install.ps1`, while
99
+ the POSIX branch continued into MCP registration, RTK, Terse mode and the
100
+ codebase graph. A Windows user got agents, skills and commands on disk and
101
+ silently none of those, with no message explaining why.
102
+
103
+ That block is now a shared `runPostInstall()` called by both branches.
104
+
105
+ ### Hooks shipped without their scripts, and were never registered
106
+
107
+ `install.ps1` copied only `hooks/hooks.json` — the same defect `install.sh` had
108
+ before 2.21.0, where Windows was missed. So the manifest landed with none of the
109
+ scripts it points at, and `settings.json` was never written at all. Claude Code
110
+ reads hooks from `settings.json`, so every hook was inert.
111
+
112
+ ### Before and after, measured
113
+
114
+ ```
115
+ before after
116
+ --target claude-code hard fail works
117
+ hook files 1 12
118
+ settings.json ABSENT present, 8 events
119
+ Terse mode absent present
120
+ MCP registration absent present
121
+ total files installed — 502
122
+ ```
123
+
124
+ ### Guarded so it cannot regress
125
+
126
+ A `Windows install` workflow now runs on every push to main: it packs the
127
+ package as npm would, installs it globally on `windows-latest`, runs a real
128
+ install into a scratch HOME, and asserts on what lands — counts per module,
129
+ whether `settings.json` was written and with how many events, whether Terse and
130
+ MCP registration happened, then runs `doctor`. Green CI now means the Windows
131
+ install works, which it never did before.
132
+
133
+ ### Also
134
+
135
+ `doctor` was telling Windows users to run `kodelythecc rtk install` and
136
+ `kodelythecc codebase install`. Neither has a Windows installer, so both
137
+ commands report that they cannot proceed and the user follows a dead end. Both
138
+ hints now point at the release page on Windows. Remediation that fails on the
139
+ reader's platform is worse than none.
140
+
141
+ RTK and the codebase graph remain manual on Windows — they are native binaries
142
+ without a Windows installer script. The difference is that ECC now says so
143
+ during install rather than the feature being silently absent.
144
+
145
+ README gains a Windows section covering what is automatic, what is not, and why
146
+ no execution-policy change is needed. Two stale claims corrected there: 373
147
+ tests became 647, and 22 hooks became 44 hook entries.
148
+
149
+ **647 tests passing.**
150
+
5
151
  ## v2.21.2 — same content, with a GitHub release attached (September 2026)
6
152
 
7
153
  Identical to 2.21.1 in every shipped file. It exists because 2.21.1 could not be
package/README.md CHANGED
@@ -96,7 +96,7 @@ You never typed `use debug-detective`. You didn't have to. The toolkit read the
96
96
  | Quality hooks | ✅ 22+ | Some | ❌ | ❌ |
97
97
  | IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
98
98
  | Telemetry | ❌ none | Varies | ❌ | Varies |
99
- | Test coverage | ✅ 373 tests | ❌ | ❌ | ❌ |
99
+ | Test coverage | ✅ 647 tests | ❌ | ❌ | ❌ |
100
100
  | Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
101
101
 
102
102
  ---
@@ -118,7 +118,7 @@ kodelythecc --target claude-code --codebase-graph
118
118
  That's it. This single flow:
119
119
 
120
120
  1. Installs both binaries (`kodelyth-ecc` and short-form `kodelythecc`) to your PATH
121
- 2. Copies 70 agents + 196 skills + 103 commands + 22 hooks + 15 rules into your AI tool's config dir
121
+ 2. Copies 70 agents + 196 skills + 103 commands + 44 hook entries + 15 rules into your AI tool's config dir
122
122
  3. Auto-installs **RTK** binary and wires its PreToolUse hook (input compression starts on next AI restart)
123
123
  4. Installs **Terse mode** skill + `/terse` and `/terse-compress` slash commands (dormant — user types `/terse` to activate)
124
124
  5. Auto-installs **codebase-memory-mcp** and registers its MCP entries in your AI tool (with `--codebase-graph`)
@@ -190,6 +190,48 @@ export KODELYTH_EXTRA_IDE_WATCH="$HOME/my-agent-logs,$HOME/other-tool/state"
190
190
  npx kodelyth-ecc dashboard
191
191
  ```
192
192
 
193
+ ### Windows
194
+
195
+ The same command works. `npx kodelyth-ecc` detects Windows and runs `install.ps1`
196
+ through PowerShell for you — there is nothing separate to download.
197
+
198
+ ```powershell
199
+ npx kodelyth-ecc # Claude Code (default)
200
+ npx kodelyth-ecc --target cursor-project # any target works the same way
201
+ ```
202
+
203
+ **Requirements.** Node.js 18+ and either PowerShell 7 (`pwsh`) or the Windows
204
+ PowerShell 5.1 that ships with the OS. The installer tries `pwsh` first and falls
205
+ back automatically, and it passes `-ExecutionPolicy Bypass` for its own process
206
+ only, so you do not need to change your machine's execution policy.
207
+
208
+ **Everything installs automatically**, same as macOS and Linux: the 70 agents,
209
+ 196 skills, 103 commands, 15 rules, all 44 hook entries registered into
210
+ `settings.json`, the ECC MCP server (16 tools), Terse mode, **RTK**, and the
211
+ **codebase graph**.
212
+
213
+ The last two are native binaries. ECC downloads the published Windows builds
214
+ straight from GitHub releases and puts them in `%LOCALAPPDATA%\Kodelyth\bin`.
215
+ That directory is not on `PATH` by default, so the installer prints the one
216
+ command that fixes it:
217
+
218
+ ```powershell
219
+ setx PATH "%PATH%;%LOCALAPPDATA%\Kodelyth\bin"
220
+ ```
221
+
222
+ Run it once, reopen your terminal, done. ECC does not edit `PATH` for you —
223
+ that is your environment to change.
224
+
225
+ **Verify:**
226
+
227
+ ```powershell
228
+ kodelythecc doctor
229
+ ```
230
+
231
+ Every install is exercised on a real `windows-latest` runner on each push — it
232
+ packs the package, installs it globally, runs a real install, and asserts on
233
+ what lands. See the `Windows install` workflow.
234
+
193
235
  ### Option 2 — npx from GitHub (always latest commit)
194
236
 
195
237
  ```bash
@@ -973,7 +1015,9 @@ npx kodelyth-ecc typescript python golang rust java kotlin php swift cpp dart ru
973
1015
  | Google Antigravity | Full | `antigravity` | Agents → skills, commands → workflows, rules |
974
1016
  | OpenCode | Rules only | `opencode` | Rules (agents + skills not yet supported by OpenCode) |
975
1017
 
976
- **OS support:** macOS, Linux (`install.sh`), Windows (`install.ps1`), or any OS with Node.js 18+ (`npx`).
1018
+ **OS support:** macOS, Linux and Windows. `npx kodelyth-ecc` is the same command on all
1019
+ three — it dispatches to `install.sh` on macOS/Linux and `install.ps1` on Windows for you.
1020
+ See [Windows](#windows) for the two pieces that still need a manual step there.
977
1021
 
978
1022
  ---
979
1023
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.21.2
1
+ 2.23.0
@@ -1806,79 +1806,20 @@ if (args.includes('--version') || args.includes('-v')) {
1806
1806
  }
1807
1807
 
1808
1808
  // ── Run installer ─────────────────────────────────────────────────────────────
1809
- if (isWin) {
1810
- const ps1 = path.join(ROOT, 'install.ps1');
1811
- if (!fs.existsSync(ps1)) {
1812
- console.error('Error: install.ps1 not found. Please clone the repo and run install.ps1 manually.');
1813
- process.exit(1);
1814
- }
1815
-
1816
- // Map CLI args (--target X, --bundle X, --profile X) → PowerShell named params (-Target X, -Bundle X)
1817
- // Positional language args (e.g. "typescript python") → collected for -Languages
1818
- const psArgs = [];
1819
- const languages = [];
1820
- for (let i = 0; i < args.length; i++) {
1821
- const a = args[i];
1822
- if ((a === '--target' || a === '-target') && args[i + 1]) {
1823
- psArgs.push('-Target', args[++i]); continue;
1824
- }
1825
- if ((a === '--bundle' || a === '-bundle') && args[i + 1]) {
1826
- psArgs.push('-Bundle', args[++i]); continue;
1827
- }
1828
- if ((a === '--profile' || a === '-profile') && args[i + 1]) {
1829
- // profile maps to languages in ps1
1830
- languages.push(args[++i]); continue;
1831
- }
1832
- if (!a.startsWith('-')) {
1833
- // positional = language module
1834
- languages.push(a); continue;
1835
- }
1836
- // pass through any other flags unmapped
1837
- psArgs.push(a);
1838
- }
1839
- if (languages.length > 0) {
1840
- psArgs.push('-Languages', languages.join(','));
1841
- }
1842
-
1843
- // Try pwsh (PowerShell 7+) first, then fall back to powershell (5.1)
1844
- function trySpawn(bin) {
1845
- return spawnSync(bin, ['-NoLogo', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...psArgs], {
1846
- stdio: 'inherit',
1847
- shell: false,
1848
- env: { ...process.env, KODELYTH_NONINTERACTIVE: '1' },
1849
- });
1850
- }
1851
-
1852
- let result = trySpawn('pwsh');
1853
- if (result.error && result.error.code === 'ENOENT') {
1854
- // pwsh not found — try legacy powershell.exe
1855
- result = trySpawn('powershell');
1856
- }
1857
-
1858
- if (result.error) {
1859
- console.error(
1860
- '\nError: Could not launch PowerShell to run the installer.\n' +
1861
- 'Make sure PowerShell is installed and accessible in your PATH.\n' +
1862
- ' • Try: pwsh --version\n' +
1863
- ' • Or: powershell -Version\n' +
1864
- '\nAlternatively, run the installer manually:\n' +
1865
- ` powershell -ExecutionPolicy Bypass -File "${ps1}"\n`
1866
- );
1867
- process.exit(1);
1868
- }
1869
- process.exit(result.status ?? 1);
1870
- } else {
1871
- try { require(path.join(ROOT, 'scripts', 'migrate-legacy.js')).main(); } catch {}
1872
- const sh = path.join(ROOT, 'install.sh');
1873
- if (!fs.existsSync(sh)) {
1874
- console.error('Error: install.sh not found in package root:', ROOT);
1875
- process.exit(1);
1876
- }
1877
- fs.chmodSync(sh, 0o755);
1878
- const result = spawnSync('bash', [sh, ...args], { stdio: 'inherit', shell: false });
1879
-
1809
+ // Post-install wiring: MCP registration, RTK, Terse mode, codebase graph.
1810
+ //
1811
+ // This lived inside the POSIX branch, and the Windows branch called
1812
+ // process.exit() straight after install.ps1 — so none of it ran on Windows. A
1813
+ // Windows user got agents, skills and commands on disk and silently none of:
1814
+ // the MCP server (16 tools), RTK token savings, /terse and /terse-compress, or
1815
+ // the codebase graph. Five advertised features, absent with no message.
1816
+ //
1817
+ // Every block below already guards itself and reports a reason when it cannot
1818
+ // proceed, which is what makes sharing it safe: on Windows the parts that work
1819
+ // now run, and the parts that cannot say so instead of vanishing.
1820
+ function runPostInstall(status, args) {
1880
1821
  // Post-install: auto-register ECC's own MCP server in Claude Code + Desktop.
1881
- if (result.status === 0 && !args.includes('--no-mcp-register')) {
1822
+ if (status === 0 && !args.includes('--no-mcp-register')) {
1882
1823
  try {
1883
1824
  const reg = require(path.join(ROOT, 'scripts', 'mcp', 'register-self.js'));
1884
1825
  process.stdout.write('\n━ ECC MCP server registration ' + '─'.repeat(31) + '\n');
@@ -1894,7 +1835,7 @@ if (isWin) {
1894
1835
  }
1895
1836
 
1896
1837
  // Post-install: auto-install + wire RTK for the target IDE (opt-out via --no-rtk).
1897
- if (result.status === 0 && !args.includes('--no-rtk')) {
1838
+ if (status === 0 && !args.includes('--no-rtk')) {
1898
1839
  try {
1899
1840
  const rtk = require(path.join(ROOT, 'scripts', 'rtk', 'index.js'));
1900
1841
  const targetIdx = args.indexOf('--target');
@@ -1909,6 +1850,13 @@ if (isWin) {
1909
1850
  const en = rtk.enableFor(target, { log: () => {} });
1910
1851
  if (en.enabled) {
1911
1852
  w(` ✓ RTK ${(rtk.getVersion() || '').replace(/^rtk /,'')} — wired for ${target}`);
1853
+ // On Windows the binary goes to %LOCALAPPDATA%\Kodelyth\bin. ECC does
1854
+ // not edit PATH for the user, so say plainly what is left to do —
1855
+ // otherwise rtk works during this install and vanishes next shell.
1856
+ if (inst.pathHint) {
1857
+ w(` ! ${inst.dir} is not on your PATH yet`);
1858
+ w(` run once, then reopen your terminal: ${inst.pathHint}`);
1859
+ }
1912
1860
  w(` ✓ Restart your AI tool to activate. 60-90% token savings on shell commands.`);
1913
1861
  } else {
1914
1862
  w(` · skipped: ${en.reason}`);
@@ -1972,5 +1920,80 @@ if (isWin) {
1972
1920
  }
1973
1921
  }
1974
1922
 
1975
- process.exit(result.status ?? 1);
1976
1923
  }
1924
+
1925
+ if (isWin) {
1926
+ const ps1 = path.join(ROOT, 'install.ps1');
1927
+ if (!fs.existsSync(ps1)) {
1928
+ console.error('Error: install.ps1 not found. Please clone the repo and run install.ps1 manually.');
1929
+ process.exit(1);
1930
+ }
1931
+
1932
+ // Map CLI args (--target X, --bundle X, --profile X) → PowerShell named params (-Target X, -Bundle X)
1933
+ // Positional language args (e.g. "typescript python") → collected for -Languages
1934
+ const psArgs = [];
1935
+ const languages = [];
1936
+ for (let i = 0; i < args.length; i++) {
1937
+ const a = args[i];
1938
+ if ((a === '--target' || a === '-target') && args[i + 1]) {
1939
+ psArgs.push('-Target', args[++i]); continue;
1940
+ }
1941
+ if ((a === '--bundle' || a === '-bundle') && args[i + 1]) {
1942
+ psArgs.push('-Bundle', args[++i]); continue;
1943
+ }
1944
+ if ((a === '--profile' || a === '-profile') && args[i + 1]) {
1945
+ // profile maps to languages in ps1
1946
+ languages.push(args[++i]); continue;
1947
+ }
1948
+ if (!a.startsWith('-')) {
1949
+ // positional = language module
1950
+ languages.push(a); continue;
1951
+ }
1952
+ // pass through any other flags unmapped
1953
+ psArgs.push(a);
1954
+ }
1955
+ if (languages.length > 0) {
1956
+ psArgs.push('-Languages', languages.join(','));
1957
+ }
1958
+
1959
+ // Try pwsh (PowerShell 7+) first, then fall back to powershell (5.1)
1960
+ function trySpawn(bin) {
1961
+ return spawnSync(bin, ['-NoLogo', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...psArgs], {
1962
+ stdio: 'inherit',
1963
+ shell: false,
1964
+ env: { ...process.env, KODELYTH_NONINTERACTIVE: '1' },
1965
+ });
1966
+ }
1967
+
1968
+ let result = trySpawn('pwsh');
1969
+ if (result.error && result.error.code === 'ENOENT') {
1970
+ // pwsh not found — try legacy powershell.exe
1971
+ result = trySpawn('powershell');
1972
+ }
1973
+
1974
+ if (result.error) {
1975
+ console.error(
1976
+ '\nError: Could not launch PowerShell to run the installer.\n' +
1977
+ 'Make sure PowerShell is installed and accessible in your PATH.\n' +
1978
+ ' • Try: pwsh --version\n' +
1979
+ ' • Or: powershell -Version\n' +
1980
+ '\nAlternatively, run the installer manually:\n' +
1981
+ ` powershell -ExecutionPolicy Bypass -File "${ps1}"\n`
1982
+ );
1983
+ process.exit(1);
1984
+ }
1985
+ runPostInstall(result.status, args);
1986
+ process.exit(result.status ?? 1);
1987
+ } else {
1988
+ try { require(path.join(ROOT, 'scripts', 'migrate-legacy.js')).main(); } catch {}
1989
+ const sh = path.join(ROOT, 'install.sh');
1990
+ if (!fs.existsSync(sh)) {
1991
+ console.error('Error: install.sh not found in package root:', ROOT);
1992
+ process.exit(1);
1993
+ }
1994
+ fs.chmodSync(sh, 0o755);
1995
+ const result = spawnSync('bash', [sh, ...args], { stdio: 'inherit', shell: false });
1996
+
1997
+ runPostInstall(result.status, args);
1998
+ process.exit(result.status ?? 1);
1999
+ }
package/install.ps1 CHANGED
@@ -32,6 +32,33 @@ $ErrorActionPreference = "Stop"
32
32
  # Non-interactive mode: set by npx launcher (KODELYTH_NONINTERACTIVE=1) or --yes flag
33
33
  $NonInteractive = ($env:KODELYTH_NONINTERACTIVE -eq '1')
34
34
 
35
+ # -- Target aliases -----------------------------------------------------------
36
+ # install.sh matches "claude-home|claude-code" in every one of its case arms, so
37
+ # both spellings work on macOS and Linux. This file had three separate
38
+ # switch ($Target) blocks that each matched only "claude-home", so
39
+ # "--target claude-code" fell through to the default arm and exited 1.
40
+ #
41
+ # That is the spelling the README, the docs and the website install page all
42
+ # tell people to use, so the documented primary command failed outright on
43
+ # Windows while working everywhere else. Measured on a windows-latest runner:
44
+ #
45
+ # Unknown target: claude-code
46
+ # Valid targets: claude-home, windsurf-project, ...
47
+ #
48
+ # Normalising once here fixes all three switches at the same time, and keeps the
49
+ # accepted set identical to install.sh rather than merely similar. Matching is
50
+ # case-insensitive because PowerShell users reasonably type -Target Claude-Code.
51
+ $TargetAliases = @{
52
+ 'claude-code' = 'claude-home'
53
+ 'claude' = 'claude-home'
54
+ }
55
+ $targetKey = $Target.ToLower()
56
+ if ($TargetAliases.ContainsKey($targetKey)) {
57
+ $Target = $TargetAliases[$targetKey]
58
+ } else {
59
+ $Target = $targetKey
60
+ }
61
+
35
62
  # -- Bundle handling (audience-tailored cheat sheets) -------------------------
36
63
  if ($Bundle) {
37
64
  switch ($Bundle.ToLower()) {
@@ -233,10 +260,38 @@ switch ($Target) {
233
260
  Install-Dir "$ScriptDir\commands" $CommandsDest "Commands (79)"
234
261
 
235
262
  # Hooks
263
+ #
264
+ # Copy the whole tree, not just the manifest. Every command inside
265
+ # hooks.json points at ${CLAUDE_PLUGIN_ROOT}/hooks/{memory,safety}/*.js,
266
+ # so copying hooks.json alone registers hooks whose scripts are absent.
267
+ #
268
+ # Then register them into settings.json. Claude Code reads hooks from
269
+ # settings.json, NOT from hooks.json — a manifest sitting on disk does
270
+ # nothing. install.sh was fixed for this in 2.21.0; this file was missed,
271
+ # so Windows installs shipped 1 hook file and no settings.json at all
272
+ # (measured on a windows-latest runner).
236
273
  if ($HooksDest) {
237
274
  if (-not (Test-Path $HooksDest)) { New-Item -ItemType Directory -Path $HooksDest -Force | Out-Null }
238
- Copy-Item "$ScriptDir\hooks\hooks.json" "$HooksDest\hooks.json" -Force
239
- Write-Host " [OK] Hooks -> $HooksDest" -ForegroundColor Green
275
+ Copy-Item "$ScriptDir\hooks\*" $HooksDest -Recurse -Force
276
+ $hookScripts = (Get-ChildItem -Path $HooksDest -Recurse -Filter "*.js" -ErrorAction SilentlyContinue).Count
277
+ Write-Host " [OK] Hooks -> $HooksDest ($hookScripts scripts)" -ForegroundColor Green
278
+
279
+ $registrar = Join-Path $ScriptDir "scripts\install\register-hooks.js"
280
+ if (Test-Path $registrar) {
281
+ $nodeCmd = Get-Command node -ErrorAction SilentlyContinue
282
+ if ($nodeCmd) {
283
+ $summary = & node $registrar $Dest 2>&1
284
+ if ($LASTEXITCODE -eq 0) {
285
+ Write-Host " [OK] Hooks registered ($summary)" -ForegroundColor Green
286
+ } else {
287
+ Write-Host " [!] Hook registration failed: $summary" -ForegroundColor Yellow
288
+ Write-Host " Retry: node `"$registrar`" `"$Dest`"" -ForegroundColor Gray
289
+ }
290
+ } else {
291
+ Write-Host " [!] Hooks copied but not registered -- node not found" -ForegroundColor Yellow
292
+ Write-Host " Run after installing Node: node `"$registrar`" `"$Dest`"" -ForegroundColor Gray
293
+ }
294
+ }
240
295
  }
241
296
 
242
297
  # Rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.21.2",
3
+ "version": "2.23.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",
@@ -25,7 +25,32 @@ function getVersion() {
25
25
  function install({ log = () => {} } = {}) {
26
26
  if (isInstalled()) return { installed: false, skipped: true, reason: 'already installed', version: getVersion() };
27
27
  if (os.platform() === 'win32') {
28
- return { installed: false, skipped: true, reason: 'windows requires manual install: https://github.com/DeusData/codebase-memory-mcp/releases' };
28
+ // This project publishes Windows builds for both architectures; ECC just
29
+ // never downloaded them. Pick by process.arch so an arm64 machine gets the
30
+ // native build rather than running x64 under emulation.
31
+ const win = require('../lib/win-install.js');
32
+ const asset = process.arch === 'arm64'
33
+ ? 'codebase-memory-mcp-windows-arm64.zip'
34
+ : 'codebase-memory-mcp-windows-amd64.zip';
35
+ const r = win.installFromRelease({
36
+ repo: 'DeusData/codebase-memory-mcp',
37
+ asset,
38
+ binName: `${BIN}.exe`,
39
+ log,
40
+ });
41
+ if (!r.installed) {
42
+ return { installed: false, skipped: true, reason: `${r.reason} — or install by hand: https://github.com/DeusData/codebase-memory-mcp/releases` };
43
+ }
44
+ if (!isInstalled()) {
45
+ return { installed: false, skipped: true, reason: `installed to ${r.dir} but not runnable from PATH` };
46
+ }
47
+ return {
48
+ installed: true,
49
+ method: 'github-release',
50
+ version: getVersion(),
51
+ dir: r.dir,
52
+ pathHint: r.onPath ? null : win.setxHint(r.dir),
53
+ };
29
54
  }
30
55
  log('[codebase] installing codebase-memory-mcp via official curl script…');
31
56
  const script = 'curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash';
@@ -115,8 +115,21 @@ function checkPromptInjectionGuard() {
115
115
  return { status: PASS, detail: `prompt-injection guard active (mode=${mode})` };
116
116
  }
117
117
 
118
+ // Both tools DO publish Windows builds, and since 2.23.0 ECC downloads them, so
119
+ // the install command is now the right advice on every platform. What differs is
120
+ // what happens afterwards: on Windows the binary lands in
121
+ // %LOCALAPPDATA%\Kodelyth\bin, which is not on PATH by default, so the hint
122
+ // mentions that rather than implying the command alone finishes the job.
123
+ const IS_WIN = process.platform === 'win32';
124
+
118
125
  function checkRtk() {
119
- if (!onPath('rtk')) return { status: WARN, detail: 'RTK not installed (input savings inactive)', fix: 'kodelythecc rtk install' };
126
+ if (!onPath('rtk')) return {
127
+ status: WARN,
128
+ detail: 'RTK not installed (input savings inactive)',
129
+ fix: IS_WIN
130
+ ? 'kodelythecc rtk install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
131
+ : 'kodelythecc rtk install',
132
+ };
120
133
  try {
121
134
  const v = execFileSync('rtk', ['--version'], { encoding: 'utf8' }).trim();
122
135
  return { status: PASS, detail: `${v} installed`, };
@@ -130,7 +143,13 @@ function checkTerse() {
130
143
  }
131
144
 
132
145
  function checkCodebaseGraph() {
133
- if (!onPath('codebase-memory-mcp')) return { status: WARN, detail: 'codebase graph not installed (optional)', fix: 'kodelythecc codebase install' };
146
+ if (!onPath('codebase-memory-mcp')) return {
147
+ status: WARN,
148
+ detail: 'codebase graph not installed (optional)',
149
+ fix: IS_WIN
150
+ ? 'kodelythecc codebase install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
151
+ : 'kodelythecc codebase install',
152
+ };
134
153
  return { status: PASS, detail: 'codebase-memory-mcp installed' };
135
154
  }
136
155
 
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Install a released binary on Windows.
5
+ *
6
+ * Both RTK and codebase-memory-mcp publish Windows builds, but ECC never
7
+ * implemented the download path and reported "windows requires manual install"
8
+ * instead. That was a gap here, not a limitation upstream:
9
+ *
10
+ * rtk-x86_64-pc-windows-msvc.zip
11
+ * codebase-memory-mcp-windows-amd64.zip / -arm64.zip
12
+ *
13
+ * Uses GitHub's `releases/latest/download/<asset>` URL, which always redirects
14
+ * to the newest release's asset — no API call, so no token and no rate limit.
15
+ *
16
+ * curl.exe and tar.exe both ship with Windows 10 1803 and later, and tar there
17
+ * is bsdtar, which extracts zip. That avoids PowerShell quoting entirely. Both
18
+ * are probed before use so an older machine gets a clear message rather than a
19
+ * spawn error.
20
+ *
21
+ * PATH is deliberately NOT modified. The POSIX path installs to ~/.local/bin and
22
+ * tells the user to add it; this mirrors that, reporting the exact setx command
23
+ * rather than silently editing a user's environment.
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const os = require('os');
28
+ const path = require('path');
29
+ const { execFileSync, spawnSync } = require('child_process');
30
+
31
+ /** Where ECC puts binaries it installs on Windows. User-writable, no admin. */
32
+ function installDir() {
33
+ const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
34
+ return path.join(base, 'Kodelyth', 'bin');
35
+ }
36
+
37
+ function have(bin) {
38
+ try {
39
+ execFileSync(bin, ['--version'], { stdio: 'ignore' });
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @param {object} opts
48
+ * @param {string} opts.repo "owner/name"
49
+ * @param {string} opts.asset asset filename in the latest release
50
+ * @param {string} opts.binName executable to find after extraction (with .exe)
51
+ * @param {function} [opts.log]
52
+ * @returns {{installed:boolean, reason?:string, dir?:string, binPath?:string, onPath?:boolean}}
53
+ */
54
+ function installFromRelease({ repo, asset, binName, log = () => {} }) {
55
+ if (process.platform !== 'win32') {
56
+ return { installed: false, reason: 'installFromRelease is Windows-only' };
57
+ }
58
+ if (!have('curl')) {
59
+ return { installed: false, reason: 'curl.exe not found (needs Windows 10 1803 or later)' };
60
+ }
61
+ if (!have('tar')) {
62
+ return { installed: false, reason: 'tar.exe not found (needs Windows 10 1803 or later)' };
63
+ }
64
+
65
+ const dir = installDir();
66
+ const url = `https://github.com/${repo}/releases/latest/download/${asset}`;
67
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-win-'));
68
+ const zip = path.join(tmp, asset);
69
+
70
+ try {
71
+ fs.mkdirSync(dir, { recursive: true });
72
+
73
+ log(`[win] downloading ${asset}…`);
74
+ // -L to follow the release redirect, -f so a 404 is an error rather than
75
+ // an HTML error page written to disk and then "extracted".
76
+ const dl = spawnSync('curl', ['-fsSL', '-o', zip, url], { stdio: 'inherit' });
77
+ if (dl.status !== 0) {
78
+ return { installed: false, reason: `download failed (curl exit ${dl.status}) — ${url}` };
79
+ }
80
+ if (!fs.existsSync(zip) || fs.statSync(zip).size === 0) {
81
+ return { installed: false, reason: `downloaded file is empty — ${url}` };
82
+ }
83
+
84
+ log('[win] extracting…');
85
+ const ex = spawnSync('tar', ['-xf', zip, '-C', dir], { stdio: 'inherit' });
86
+ if (ex.status !== 0) {
87
+ return { installed: false, reason: `extract failed (tar exit ${ex.status})` };
88
+ }
89
+
90
+ // Archives vary: some put the binary at the root, some inside a folder.
91
+ const binPath = findBinary(dir, binName);
92
+ if (!binPath) {
93
+ return { installed: false, reason: `${binName} not found in ${asset} after extraction` };
94
+ }
95
+ // Flatten so the binary sits directly in dir and one PATH entry covers it.
96
+ const finalPath = path.join(dir, binName);
97
+ if (binPath !== finalPath) fs.copyFileSync(binPath, finalPath);
98
+
99
+ // Make it usable in THIS process so a follow-up isInstalled() check passes
100
+ // without the user restarting their shell.
101
+ const sep = path.delimiter;
102
+ if (!(process.env.PATH || '').split(sep).includes(dir)) {
103
+ process.env.PATH = `${dir}${sep}${process.env.PATH || ''}`;
104
+ }
105
+
106
+ return { installed: true, dir, binPath: finalPath, onPath: isOnUserPath(dir) };
107
+ } finally {
108
+ try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* temp dir */ }
109
+ }
110
+ }
111
+
112
+ /** Depth-limited search — these archives are shallow; this avoids walking a tree. */
113
+ function findBinary(root, binName, depth = 0) {
114
+ if (depth > 3) return null;
115
+ let entries;
116
+ try {
117
+ entries = fs.readdirSync(root, { withFileTypes: true });
118
+ } catch {
119
+ return null;
120
+ }
121
+ for (const e of entries) {
122
+ const p = path.join(root, e.name);
123
+ if (e.isFile() && e.name.toLowerCase() === binName.toLowerCase()) return p;
124
+ }
125
+ for (const e of entries) {
126
+ if (e.isDirectory()) {
127
+ const found = findBinary(path.join(root, e.name), binName, depth + 1);
128
+ if (found) return found;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Whether the directory is on the user's PERSISTENT PATH, not just this
136
+ * process's. Checking process.env would always say yes right after we prepend
137
+ * it, which would wrongly tell the user there is nothing left to do.
138
+ */
139
+ function isOnUserPath(dir) {
140
+ try {
141
+ const out = execFileSync(
142
+ 'reg',
143
+ ['query', 'HKCU\\Environment', '/v', 'Path'],
144
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
145
+ );
146
+ return out.toLowerCase().includes(dir.toLowerCase());
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /** The exact command to persist the directory on PATH, for the user to run. */
153
+ function setxHint(dir) {
154
+ return `setx PATH "%PATH%;${dir}"`;
155
+ }
156
+
157
+ module.exports = { installFromRelease, installDir, isOnUserPath, setxHint, findBinary };
@@ -80,13 +80,37 @@ function getVersion() {
80
80
  // ── Install RTK binary ───────────────────────────────────────────────────────
81
81
  // Mac: prefer `brew install rtk` if brew is on PATH (fastest, cached).
82
82
  // Otherwise: pipe the official install script through sh (installs to ~/.local/bin).
83
- // Windows: skipped (needs manual .zip download from releases).
83
+ // Windows: downloads the published .zip from GitHub releases.
84
84
  function install({ log = () => {} } = {}) {
85
85
  if (isInstalled()) {
86
86
  return { installed: false, skipped: true, reason: 'already installed', version: getVersion() };
87
87
  }
88
88
  if (os.platform() === 'win32') {
89
- return { installed: false, skipped: true, reason: 'windows requires manual install: https://github.com/rtk-ai/rtk/releases' };
89
+ // RTK publishes a Windows build; ECC just never downloaded it and told the
90
+ // user to do it by hand. Only x86_64 is published, which is fine on arm64
91
+ // Windows because it runs x64 binaries under emulation.
92
+ const win = require('../lib/win-install.js');
93
+ const r = win.installFromRelease({
94
+ repo: 'rtk-ai/rtk',
95
+ asset: 'rtk-x86_64-pc-windows-msvc.zip',
96
+ binName: 'rtk.exe',
97
+ log,
98
+ });
99
+ if (!r.installed) {
100
+ return { installed: false, skipped: true, reason: `${r.reason} — or install by hand: https://github.com/rtk-ai/rtk/releases` };
101
+ }
102
+ if (!isInstalled()) {
103
+ return { installed: false, skipped: true, reason: `installed to ${r.dir} but not runnable from PATH` };
104
+ }
105
+ return {
106
+ installed: true,
107
+ method: 'github-release',
108
+ version: getVersion(),
109
+ dir: r.dir,
110
+ // Reported so the caller can tell the user what is still needed. PATH is
111
+ // not edited for them — that is their environment to change.
112
+ pathHint: r.onPath ? null : win.setxHint(r.dir),
113
+ };
90
114
  }
91
115
 
92
116
  // Try Homebrew first on macOS.