Skip to content

Commit 8f19721

Browse files
committed
feat: v1.0.3 - reindex, self-update, hardened IDE detection, installer PATH, dark UI
1 parent 9fd5418 commit 8f19721

17 files changed

Lines changed: 1739 additions & 154 deletions

File tree

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131
target: x86_64-pc-windows-msvc
3232
artifact: cortex-win32-x64
3333
ext: ".exe"
34+
- os: windows-latest
35+
target: i686-pc-windows-msvc
36+
artifact: cortex-win32-ia32
37+
ext: ".exe"
3438

3539
runs-on: ${{ matrix.os }}
3640

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
All notable changes to Cortex are documented here.
44

5+
## [1.0.3] - 2026-06-01
6+
7+
### Added
8+
- `cortex update` self-update command: downloads latest release from GitHub, verifies SHA-256 checksum, replaces binary (with Windows rename-then-replace pattern), and triggers reindex
9+
- `cortex reindex` command: deletes and rebuilds the graph database from scratch
10+
- Default exclusions for `.serena`, `.cursor`, `.kiro`, `.agent` directories and lock files (`pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `Cargo.lock`)
11+
- Renamed `.cortex-ignore` to `.cortexignore` for consistency
12+
- Installer PATH configuration (Windows `setx`, Unix shell profile export)
13+
- Post-install automatic reindex with 120s timeout
14+
- Windows x86 (`win32-ia32`) binary support in release workflow and npm installer
15+
16+
### Changed
17+
- Unified UI fully dark-themed (`#1e1e2e` background) with corner-positioned icon navigation replacing the nav bar
18+
- Hotspots table uses `overflow-x: auto` to prevent horizontal overflow
19+
- Statistics overlay uses CSS Grid with `tabular-nums` for numeric alignment
20+
521
## [1.0.2] - 2026-05-25
622

723
### Added

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cortex"
3-
version = "1.0.2"
3+
version = "1.0.3"
44
edition = "2024"
55
license = "MIT"
66

docs/cli-reference.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,41 @@ title: "CLI Reference"
33
description: "Complete reference for all Cortex CLI commands and options."
44
order: 3
55
category: "reference"
6-
lastModified: "2025-07-14"
6+
lastModified: "2026-06-01"
77
---
88

99
# CLI reference
1010

1111
Complete reference for all `cortex` commands.
1212

13+
## cortex update
14+
15+
Self-update to the latest release.
16+
17+
```sh
18+
cortex update
19+
```
20+
21+
Checks GitHub Releases for a newer version, downloads the platform-specific archive, verifies the SHA-256 checksum, replaces the running binary, and triggers `cortex reindex`. On Windows, uses the rename-then-replace pattern to handle locked executables.
22+
23+
Behavior:
24+
- Prints "already up to date" if the current version matches the latest release
25+
- Aborts with a checksum mismatch error if the SHA-256 does not match (possible tampering)
26+
- Exits non-zero with a network error if GitHub is unreachable
27+
- Prints `Updated cortex: {old} → {new}` on success
28+
29+
## cortex reindex
30+
31+
Delete and rebuild the graph database from scratch.
32+
33+
```sh
34+
cortex reindex
35+
```
36+
37+
Removes `graph.db`, `graph.db-wal`, and `graph.db-shm` from `.cortex-data/`, recreates the database with schema migrations, re-indexes the entire repository, and prints an `IndexStats` summary (files parsed, nodes created, edges created, duration).
38+
39+
Use this after a schema change, a corrupted database, or when switching branches with significantly different code.
40+
1341
## cortex status
1442

1543
Print graph statistics for the current repository.

npm/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@1337xcode/cortex",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "Code intelligence MCP server. Indexes your repository into a queryable call graph for AI coding agents.",
55
"bin": {
66
"cortex": "./bin/cortex.js"
@@ -25,7 +25,7 @@
2525
"author": "1337Xcode",
2626
"license": "MIT",
2727
"os": ["darwin", "linux", "win32"],
28-
"cpu": ["x64", "arm64"],
28+
"cpu": ["x64", "arm64", "ia32"],
2929
"engines": {
3030
"node": ">=16"
3131
}

npm/scripts/install.js

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
* Cortex installer script.
55
*
66
* Downloads the correct platform binary from GitHub releases, places it in
7-
* ~/.cortex/bin/, and runs `cortex install` to configure detected AI agents.
7+
* ~/.cortex/bin/, configures PATH, and runs `cortex reindex` to build the
8+
* code graph.
89
*
910
* Handles fresh installs and updates: overwrites any existing binary in place.
1011
*
@@ -30,6 +31,7 @@ function getPlatformTarget() {
3031
"darwin-arm64": "cortex-darwin-arm64.tar.gz",
3132
"linux-x64": "cortex-linux-x64.tar.gz",
3233
"win32-x64": "cortex-win32-x64.tar.gz",
34+
"win32-ia32": "cortex-win32-ia32.tar.gz",
3335
};
3436

3537
const key = `${platform}-${arch}`;
@@ -105,14 +107,56 @@ async function extractTarGz(buffer, destDir) {
105107
}
106108
}
107109

108-
function runCortexInstall(binaryPath) {
109-
console.log("\nConfiguring AI agents...");
110+
function configurePath(installDir) {
111+
if (process.platform === "win32") {
112+
configurePathWindows(installDir);
113+
} else {
114+
configurePathUnix(installDir);
115+
}
116+
}
117+
118+
function configurePathWindows(installDir) {
119+
const currentPath = process.env.PATH || "";
120+
if (currentPath.toLowerCase().includes(installDir.toLowerCase())) {
121+
return; // Already on PATH
122+
}
110123
try {
111-
execFileSync(binaryPath, ["install"], { stdio: "inherit" });
124+
// Prepend so new binary takes precedence
125+
execFileSync("setx", ["PATH", `${installDir};%PATH%`], { stdio: "ignore" });
126+
console.log(`Added ${installDir} to PATH (via setx).`);
112127
} catch (e) {
113-
// Non-fatal: binary is installed even if agent config fails
114-
console.warn("Warning: `cortex install` exited with an error.");
115-
console.warn("You can run it manually later: cortex install");
128+
console.warn(`Warning: Could not update PATH via setx: ${e.message}`);
129+
console.warn(` Manually add to PATH: ${installDir}`);
130+
}
131+
}
132+
133+
function configurePathUnix(installDir) {
134+
const exportLine = `export PATH="${installDir}:$PATH"`;
135+
const shellFiles = [".bashrc", ".zshrc"]
136+
.map((f) => path.join(os.homedir(), f))
137+
.filter((f) => fs.existsSync(f));
138+
139+
if (shellFiles.length === 0) {
140+
console.log(`Add to your shell config: ${exportLine}`);
141+
return;
142+
}
143+
144+
for (const file of shellFiles) {
145+
const content = fs.readFileSync(file, "utf8");
146+
if (content.includes(installDir)) {
147+
continue; // Already configured
148+
}
149+
fs.appendFileSync(file, `\n# Added by cortex installer\n${exportLine}\n`);
150+
console.log(`Updated ${path.basename(file)} with PATH entry.`);
151+
}
152+
}
153+
154+
function runReindex(binaryPath) {
155+
console.log("\nRebuilding code graph...");
156+
try {
157+
execFileSync(binaryPath, ["reindex"], { stdio: "inherit", timeout: 120000 });
158+
} catch (e) {
159+
console.warn("Warning: `cortex reindex` failed. You can run it manually later: cortex reindex");
116160
}
117161
}
118162

@@ -171,29 +215,11 @@ async function main() {
171215

172216
console.log(`\nInstalled cortex to ${binaryPath}`);
173217

174-
// Add PATH hint if not already on PATH
175-
const pathDirs = (process.env.PATH || "").split(path.delimiter);
176-
const onPath = pathDirs.some((dir) => {
177-
try {
178-
return fs.realpathSync(dir) === fs.realpathSync(INSTALL_DIR);
179-
} catch {
180-
return dir === INSTALL_DIR;
181-
}
182-
});
183-
184-
if (!onPath) {
185-
console.log("");
186-
console.log("Add cortex to your PATH:");
187-
if (platform === "win32") {
188-
console.log(` setx PATH "%PATH%;${INSTALL_DIR}"`);
189-
} else {
190-
console.log(` export PATH="${INSTALL_DIR}:$PATH"`);
191-
console.log(` # Add to ~/.bashrc or ~/.zshrc to persist`);
192-
}
193-
}
218+
// Configure PATH
219+
configurePath(INSTALL_DIR);
194220

195-
// Run cortex install to configure agents
196-
runCortexInstall(binaryPath);
221+
// Run cortex reindex to build the code graph
222+
runReindex(binaryPath);
197223

198224
console.log("\nDone. Run `cortex serve` to start the MCP server.");
199225
}

npm/scripts/install.test.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Property test for PATH modification idempotency.
4+
*
5+
* Property 9: For any shell configuration file content that already contains
6+
* the ~/.cortex/bin path string, the PATH configuration function SHALL not
7+
* modify the file (no duplicate entries). Conversely, for content that does
8+
* not contain the path, the function SHALL prepend (not append) the export line.
9+
*
10+
* Validates: Requirements 6.3, 6.4, 6.5
11+
*/
12+
13+
const assert = require("node:assert");
14+
const os = require("node:os");
15+
const path = require("node:path");
16+
17+
const INSTALL_DIR = path.join(os.homedir(), ".cortex", "bin");
18+
const EXPORT_LINE = `export PATH="${INSTALL_DIR}:$PATH"`;
19+
20+
/**
21+
* Simulate configurePathUnix logic for a single file.
22+
* This mirrors the actual logic in install.js:
23+
* if (content.includes(installDir)) { continue; }
24+
* fs.appendFileSync(file, `\n# Added by cortex installer\n${exportLine}\n`);
25+
*
26+
* Returns the new content (or same content if no modification needed).
27+
*/
28+
function simulateConfigurePathUnix(existingContent, installDir) {
29+
if (existingContent.includes(installDir)) {
30+
return existingContent; // No modification — already configured
31+
}
32+
const exportLine = `export PATH="${installDir}:$PATH"`;
33+
return existingContent + `\n# Added by cortex installer\n${exportLine}\n`;
34+
}
35+
36+
/**
37+
* Generate random shell config content for property testing.
38+
* @param {boolean} includeInstallDir - Whether to embed the install dir in the content
39+
*/
40+
function generateRandomShellContent(includeInstallDir) {
41+
const lines = [];
42+
const numLines = Math.floor(Math.random() * 20) + 1;
43+
44+
for (let i = 0; i < numLines; i++) {
45+
const lineType = Math.floor(Math.random() * 6);
46+
switch (lineType) {
47+
case 0:
48+
lines.push(`# Comment ${Math.random().toString(36).slice(2)}`);
49+
break;
50+
case 1:
51+
lines.push(`export PATH="/usr/local/bin:$PATH"`);
52+
break;
53+
case 2:
54+
lines.push(`alias ll='ls -la'`);
55+
break;
56+
case 3:
57+
lines.push(`export EDITOR=vim`);
58+
break;
59+
case 4:
60+
lines.push("");
61+
break;
62+
case 5:
63+
lines.push(`source ~/.nvm/nvm.sh`);
64+
break;
65+
}
66+
}
67+
68+
if (includeInstallDir) {
69+
// Insert the install dir reference at a random position
70+
const pos = Math.floor(Math.random() * (lines.length + 1));
71+
lines.splice(pos, 0, `export PATH="${INSTALL_DIR}:$PATH"`);
72+
}
73+
74+
return lines.join("\n");
75+
}
76+
77+
// ---------------------------------------------------------------------------
78+
// Property Tests
79+
// ---------------------------------------------------------------------------
80+
81+
const NUM_ITERATIONS = 100;
82+
83+
console.log("Property 9: PATH modification idempotency");
84+
console.log(`Running ${NUM_ITERATIONS} iterations per sub-property...\n`);
85+
86+
// Sub-property A: Content already containing install dir should NOT be modified
87+
let passCount = 0;
88+
for (let i = 0; i < NUM_ITERATIONS; i++) {
89+
const content = generateRandomShellContent(true);
90+
const result = simulateConfigurePathUnix(content, INSTALL_DIR);
91+
assert.strictEqual(
92+
result,
93+
content,
94+
`Iteration ${i}: Content was modified when install dir already present`
95+
);
96+
passCount++;
97+
}
98+
console.log(` [PASS] ${passCount}/${NUM_ITERATIONS}: No modification when path already present`);
99+
100+
// Sub-property B: Content NOT containing install dir should have export line added
101+
passCount = 0;
102+
for (let i = 0; i < NUM_ITERATIONS; i++) {
103+
const content = generateRandomShellContent(false);
104+
const result = simulateConfigurePathUnix(content, INSTALL_DIR);
105+
106+
// The result must contain the install dir
107+
assert(
108+
result.includes(INSTALL_DIR),
109+
`Iteration ${i}: Result does not contain install dir after modification`
110+
);
111+
112+
// The export line should appear exactly once
113+
const escapedDir = INSTALL_DIR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
114+
const occurrences = (result.match(new RegExp(escapedDir, "g")) || []).length;
115+
assert.strictEqual(
116+
occurrences,
117+
1,
118+
`Iteration ${i}: Install dir appears ${occurrences} times (expected 1)`
119+
);
120+
121+
// The PATH entry should prepend (installDir comes before $PATH in the export)
122+
assert(
123+
result.includes(`"${INSTALL_DIR}:$PATH"`),
124+
`Iteration ${i}: PATH not prepended correctly — expected "${INSTALL_DIR}:$PATH"`
125+
);
126+
127+
passCount++;
128+
}
129+
console.log(` [PASS] ${passCount}/${NUM_ITERATIONS}: Export line correctly added when path not present`);
130+
131+
// Sub-property C: Idempotency — applying the function twice yields the same result
132+
passCount = 0;
133+
for (let i = 0; i < NUM_ITERATIONS; i++) {
134+
const content = generateRandomShellContent(false);
135+
const firstPass = simulateConfigurePathUnix(content, INSTALL_DIR);
136+
const secondPass = simulateConfigurePathUnix(firstPass, INSTALL_DIR);
137+
138+
assert.strictEqual(
139+
firstPass,
140+
secondPass,
141+
`Iteration ${i}: Second application modified the content (not idempotent)`
142+
);
143+
144+
// Count occurrences — must be exactly 1 after any number of applications
145+
const escapedDir = INSTALL_DIR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
146+
const occurrences = (secondPass.match(new RegExp(escapedDir, "g")) || []).length;
147+
assert.strictEqual(
148+
occurrences,
149+
1,
150+
`Iteration ${i}: Duplicate entries after second pass (found ${occurrences})`
151+
);
152+
153+
passCount++;
154+
}
155+
console.log(` [PASS] ${passCount}/${NUM_ITERATIONS}: Idempotent — no duplicates on repeated application`);
156+
157+
// Sub-property D: The added content is appended (not inserted before existing content)
158+
passCount = 0;
159+
for (let i = 0; i < NUM_ITERATIONS; i++) {
160+
const content = generateRandomShellContent(false);
161+
const result = simulateConfigurePathUnix(content, INSTALL_DIR);
162+
163+
// Original content should still be at the start of the result
164+
assert(
165+
result.startsWith(content),
166+
`Iteration ${i}: Original content was not preserved at the start of the file`
167+
);
168+
169+
passCount++;
170+
}
171+
console.log(` [PASS] ${passCount}/${NUM_ITERATIONS}: Original file content preserved (export appended to end)`);
172+
173+
console.log("\nAll property tests passed.");

0 commit comments

Comments
 (0)