codecortex-ai 0.3.2 → 0.4.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/dist/cli/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
writeManifest,
|
|
25
25
|
writeModuleDoc,
|
|
26
26
|
writeSession
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-OJAMBNHJ.js";
|
|
28
28
|
|
|
29
29
|
// src/cli/index.ts
|
|
30
30
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -2317,9 +2317,324 @@ function pad3(str, len) {
|
|
|
2317
2317
|
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
2318
2318
|
}
|
|
2319
2319
|
|
|
2320
|
+
// src/cli/commands/hook.ts
|
|
2321
|
+
import { resolve as resolve9, join as join3 } from "path";
|
|
2322
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2323
|
+
import { readFile as readFile4, writeFile as writeFile2, chmod, unlink } from "fs/promises";
|
|
2324
|
+
import simpleGit3 from "simple-git";
|
|
2325
|
+
var HOOK_START = "# --- codecortex-hook-start ---";
|
|
2326
|
+
var HOOK_END = "# --- codecortex-hook-end ---";
|
|
2327
|
+
var HOOK_TYPES = ["post-commit", "post-merge"];
|
|
2328
|
+
var HOOK_SCRIPT = `
|
|
2329
|
+
${HOOK_START}
|
|
2330
|
+
# Auto-update CodeCortex knowledge after git operations
|
|
2331
|
+
# Installed by: codecortex hook install
|
|
2332
|
+
(
|
|
2333
|
+
if command -v codecortex >/dev/null 2>&1; then
|
|
2334
|
+
codecortex update >/dev/null 2>&1
|
|
2335
|
+
elif command -v npx >/dev/null 2>&1; then
|
|
2336
|
+
npx codecortex-ai update >/dev/null 2>&1
|
|
2337
|
+
fi
|
|
2338
|
+
) &
|
|
2339
|
+
${HOOK_END}
|
|
2340
|
+
`.trimStart();
|
|
2341
|
+
function hasCodeCortexHook(content) {
|
|
2342
|
+
return content.includes(HOOK_START);
|
|
2343
|
+
}
|
|
2344
|
+
function removeCodeCortexHook(content) {
|
|
2345
|
+
const startIdx = content.indexOf(HOOK_START);
|
|
2346
|
+
if (startIdx === -1) return content;
|
|
2347
|
+
const endIdx = content.indexOf(HOOK_END);
|
|
2348
|
+
if (endIdx === -1) return content;
|
|
2349
|
+
const before = content.slice(0, startIdx);
|
|
2350
|
+
const after = content.slice(endIdx + HOOK_END.length);
|
|
2351
|
+
return before + after.replace(/^\n/, "");
|
|
2352
|
+
}
|
|
2353
|
+
function isEmptyHook(content) {
|
|
2354
|
+
const stripped = content.split("\n").filter((line) => {
|
|
2355
|
+
const trimmed = line.trim();
|
|
2356
|
+
return trimmed !== "" && !trimmed.startsWith("#!") && !trimmed.startsWith("#");
|
|
2357
|
+
}).join("");
|
|
2358
|
+
return stripped.length === 0;
|
|
2359
|
+
}
|
|
2360
|
+
async function getGitHooksDir(root) {
|
|
2361
|
+
const git = simpleGit3(root);
|
|
2362
|
+
const gitDir = (await git.revparse(["--git-dir"])).trim();
|
|
2363
|
+
const resolved = resolve9(root, gitDir);
|
|
2364
|
+
return join3(resolved, "hooks");
|
|
2365
|
+
}
|
|
2366
|
+
async function isCodeCortexIgnored(root) {
|
|
2367
|
+
const gitignorePath = join3(root, ".gitignore");
|
|
2368
|
+
if (!existsSync11(gitignorePath)) return false;
|
|
2369
|
+
try {
|
|
2370
|
+
const content = await readFile4(gitignorePath, "utf-8");
|
|
2371
|
+
return content.split("\n").some((line) => {
|
|
2372
|
+
const trimmed = line.trim();
|
|
2373
|
+
return trimmed === ".codecortex" || trimmed === ".codecortex/" || trimmed === ".codecortex/**";
|
|
2374
|
+
});
|
|
2375
|
+
} catch {
|
|
2376
|
+
return false;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
async function hookInstallCommand(opts) {
|
|
2380
|
+
const root = resolve9(opts.root);
|
|
2381
|
+
if (!await isGitRepo(root)) {
|
|
2382
|
+
console.error("Error: not a git repository.");
|
|
2383
|
+
process.exitCode = 1;
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
if (process.platform === "win32") {
|
|
2387
|
+
console.warn("Warning: shell-based git hooks may not work natively on Windows.");
|
|
2388
|
+
console.warn("Consider using WSL or Git Bash.");
|
|
2389
|
+
}
|
|
2390
|
+
const hooksDir = await getGitHooksDir(root);
|
|
2391
|
+
if (await isCodeCortexIgnored(root)) {
|
|
2392
|
+
console.warn("Warning: .codecortex is in .gitignore \u2014 knowledge won't be shared.");
|
|
2393
|
+
console.warn("Remove .codecortex from .gitignore to commit it alongside code.");
|
|
2394
|
+
console.log("");
|
|
2395
|
+
}
|
|
2396
|
+
for (const hookType of HOOK_TYPES) {
|
|
2397
|
+
const hookPath = join3(hooksDir, hookType);
|
|
2398
|
+
let status;
|
|
2399
|
+
if (existsSync11(hookPath)) {
|
|
2400
|
+
const content = await readFile4(hookPath, "utf-8");
|
|
2401
|
+
if (hasCodeCortexHook(content)) {
|
|
2402
|
+
status = "already installed";
|
|
2403
|
+
} else {
|
|
2404
|
+
const newContent = content.trimEnd() + "\n\n" + HOOK_SCRIPT;
|
|
2405
|
+
await writeFile2(hookPath, newContent, "utf-8");
|
|
2406
|
+
await chmod(hookPath, 493);
|
|
2407
|
+
status = "installed";
|
|
2408
|
+
}
|
|
2409
|
+
} else {
|
|
2410
|
+
const content = "#!/bin/sh\n\n" + HOOK_SCRIPT;
|
|
2411
|
+
await writeFile2(hookPath, content, "utf-8");
|
|
2412
|
+
await chmod(hookPath, 493);
|
|
2413
|
+
status = "installed";
|
|
2414
|
+
}
|
|
2415
|
+
console.log(` ${hookType}: ${status}`);
|
|
2416
|
+
}
|
|
2417
|
+
const allNew = true;
|
|
2418
|
+
console.log("");
|
|
2419
|
+
console.log("Knowledge will auto-update after every commit and merge.");
|
|
2420
|
+
}
|
|
2421
|
+
async function hookUninstallCommand(opts) {
|
|
2422
|
+
const root = resolve9(opts.root);
|
|
2423
|
+
if (!await isGitRepo(root)) {
|
|
2424
|
+
console.error("Error: not a git repository.");
|
|
2425
|
+
process.exitCode = 1;
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
const hooksDir = await getGitHooksDir(root);
|
|
2429
|
+
for (const hookType of HOOK_TYPES) {
|
|
2430
|
+
const hookPath = join3(hooksDir, hookType);
|
|
2431
|
+
let status;
|
|
2432
|
+
if (!existsSync11(hookPath)) {
|
|
2433
|
+
status = "not installed";
|
|
2434
|
+
} else {
|
|
2435
|
+
const content = await readFile4(hookPath, "utf-8");
|
|
2436
|
+
if (!hasCodeCortexHook(content)) {
|
|
2437
|
+
status = "not installed";
|
|
2438
|
+
} else {
|
|
2439
|
+
const cleaned = removeCodeCortexHook(content);
|
|
2440
|
+
if (isEmptyHook(cleaned)) {
|
|
2441
|
+
await unlink(hookPath);
|
|
2442
|
+
} else {
|
|
2443
|
+
await writeFile2(hookPath, cleaned, "utf-8");
|
|
2444
|
+
}
|
|
2445
|
+
status = "removed";
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
console.log(` ${hookType}: ${status}`);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
async function hookStatusCommand(opts) {
|
|
2452
|
+
const root = resolve9(opts.root);
|
|
2453
|
+
if (!await isGitRepo(root)) {
|
|
2454
|
+
console.error("Error: not a git repository.");
|
|
2455
|
+
process.exitCode = 1;
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
const hooksDir = await getGitHooksDir(root);
|
|
2459
|
+
for (const hookType of HOOK_TYPES) {
|
|
2460
|
+
const hookPath = join3(hooksDir, hookType);
|
|
2461
|
+
let installed = false;
|
|
2462
|
+
if (existsSync11(hookPath)) {
|
|
2463
|
+
const content = await readFile4(hookPath, "utf-8");
|
|
2464
|
+
installed = hasCodeCortexHook(content);
|
|
2465
|
+
}
|
|
2466
|
+
console.log(` ${hookType}: ${installed ? "installed" : "not installed"}`);
|
|
2467
|
+
}
|
|
2468
|
+
const manifest = await readManifest(root);
|
|
2469
|
+
if (manifest) {
|
|
2470
|
+
const lastUpdated = new Date(manifest.lastUpdated);
|
|
2471
|
+
const ageHours = Math.floor((Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60));
|
|
2472
|
+
const ageLabel = ageHours < 1 ? "just now" : ageHours < 24 ? `${ageHours} hour${ageHours === 1 ? "" : "s"} ago` : `${Math.floor(ageHours / 24)} day${Math.floor(ageHours / 24) === 1 ? "" : "s"} ago`;
|
|
2473
|
+
console.log(` Knowledge: last updated ${ageLabel}`);
|
|
2474
|
+
} else {
|
|
2475
|
+
console.log(" Knowledge: not initialized (run codecortex init)");
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/cli/commands/upgrade.ts
|
|
2480
|
+
import { execSync as execSync2 } from "child_process";
|
|
2481
|
+
|
|
2482
|
+
// src/cli/utils/version-check.ts
|
|
2483
|
+
import { tmpdir } from "os";
|
|
2484
|
+
import { join as join4 } from "path";
|
|
2485
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
2486
|
+
var CACHE_FILE = join4(tmpdir(), "codecortex-update-check.json");
|
|
2487
|
+
var CACHE_TTL = 24 * 60 * 60 * 1e3;
|
|
2488
|
+
var REGISTRY_URL = "https://registry.npmjs.org/codecortex-ai/latest";
|
|
2489
|
+
var FETCH_TIMEOUT = 3e3;
|
|
2490
|
+
function readCache() {
|
|
2491
|
+
try {
|
|
2492
|
+
const raw = readFileSync(CACHE_FILE, "utf-8");
|
|
2493
|
+
const data = JSON.parse(raw);
|
|
2494
|
+
if (typeof data === "object" && data !== null && "latest" in data && typeof data.latest === "string" && "lastCheck" in data && typeof data.lastCheck === "number") {
|
|
2495
|
+
return data;
|
|
2496
|
+
}
|
|
2497
|
+
return null;
|
|
2498
|
+
} catch {
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function writeCache(latest) {
|
|
2503
|
+
try {
|
|
2504
|
+
writeFileSync(CACHE_FILE, JSON.stringify({ latest, lastCheck: Date.now() }));
|
|
2505
|
+
} catch {
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
function compareVersions(current, latest) {
|
|
2509
|
+
const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
2510
|
+
const c = parse(current);
|
|
2511
|
+
const l = parse(latest);
|
|
2512
|
+
const cMaj = c[0] ?? 0, cMin = c[1] ?? 0, cPatch = c[2] ?? 0;
|
|
2513
|
+
const lMaj = l[0] ?? 0, lMin = l[1] ?? 0, lPatch = l[2] ?? 0;
|
|
2514
|
+
if (lMaj !== cMaj) return lMaj > cMaj;
|
|
2515
|
+
if (lMin !== cMin) return lMin > cMin;
|
|
2516
|
+
return lPatch > cPatch;
|
|
2517
|
+
}
|
|
2518
|
+
async function fetchLatestVersion() {
|
|
2519
|
+
try {
|
|
2520
|
+
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
|
|
2521
|
+
if (!res.ok) return null;
|
|
2522
|
+
const data = await res.json();
|
|
2523
|
+
return data.version ?? null;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
async function checkForUpdate(currentVersion) {
|
|
2529
|
+
const cached = readCache();
|
|
2530
|
+
if (cached && Date.now() - cached.lastCheck < CACHE_TTL) {
|
|
2531
|
+
return {
|
|
2532
|
+
current: currentVersion,
|
|
2533
|
+
latest: cached.latest,
|
|
2534
|
+
isOutdated: compareVersions(currentVersion, cached.latest)
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
const latest = await fetchLatestVersion();
|
|
2538
|
+
if (!latest) return null;
|
|
2539
|
+
writeCache(latest);
|
|
2540
|
+
return {
|
|
2541
|
+
current: currentVersion,
|
|
2542
|
+
latest,
|
|
2543
|
+
isOutdated: compareVersions(currentVersion, latest)
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function detectPackageManager() {
|
|
2547
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
2548
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
2549
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
2550
|
+
if (ua.startsWith("bun")) return "bun";
|
|
2551
|
+
const bin = process.argv[1] ?? "";
|
|
2552
|
+
if (bin.includes("yarn")) return "yarn";
|
|
2553
|
+
if (bin.includes("pnpm")) return "pnpm";
|
|
2554
|
+
if (bin.includes("bun")) return "bun";
|
|
2555
|
+
return "npm";
|
|
2556
|
+
}
|
|
2557
|
+
function getUpgradeCommand() {
|
|
2558
|
+
const pm = detectPackageManager();
|
|
2559
|
+
switch (pm) {
|
|
2560
|
+
case "yarn":
|
|
2561
|
+
return "yarn global add codecortex-ai@latest";
|
|
2562
|
+
case "pnpm":
|
|
2563
|
+
return "pnpm add -g codecortex-ai@latest";
|
|
2564
|
+
case "bun":
|
|
2565
|
+
return "bun add -g codecortex-ai@latest";
|
|
2566
|
+
default:
|
|
2567
|
+
return "npm install -g codecortex-ai@latest";
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
function shouldNotify() {
|
|
2571
|
+
if (process.env.CI || process.env.BUILD_NUMBER || process.env.RUN_ID) return false;
|
|
2572
|
+
if (process.env.NO_UPDATE_NOTIFIER) return false;
|
|
2573
|
+
if (process.env.NODE_ENV === "test") return false;
|
|
2574
|
+
if (!process.stderr.isTTY) return false;
|
|
2575
|
+
return true;
|
|
2576
|
+
}
|
|
2577
|
+
function renderUpdateNotification(current, latest) {
|
|
2578
|
+
const cmd = getUpgradeCommand();
|
|
2579
|
+
const yellow = "\x1B[33m";
|
|
2580
|
+
const cyan = "\x1B[36m";
|
|
2581
|
+
const dim = "\x1B[2m";
|
|
2582
|
+
const reset = "\x1B[0m";
|
|
2583
|
+
const bold = "\x1B[1m";
|
|
2584
|
+
const line1Text = `Update available: ${current} \u2192 ${latest}`;
|
|
2585
|
+
const line2Text = `Run ${cmd}`;
|
|
2586
|
+
const contentWidth = Math.max(line1Text.length, line2Text.length);
|
|
2587
|
+
const innerWidth = contentWidth + 6;
|
|
2588
|
+
const rpad = (text) => " ".repeat(Math.max(0, contentWidth - text.length + 3));
|
|
2589
|
+
const msg = [
|
|
2590
|
+
"",
|
|
2591
|
+
`${dim}\u256D${"\u2500".repeat(innerWidth)}\u256E${reset}`,
|
|
2592
|
+
`${dim}\u2502${reset}${" ".repeat(innerWidth)}${dim}\u2502${reset}`,
|
|
2593
|
+
`${dim}\u2502${reset} ${yellow}Update available:${reset} ${dim}${current}${reset} \u2192 ${cyan}${bold}${latest}${reset}${rpad(line1Text)}${dim}\u2502${reset}`,
|
|
2594
|
+
`${dim}\u2502${reset} Run ${cyan}${cmd}${reset}${rpad(line2Text)}${dim}\u2502${reset}`,
|
|
2595
|
+
`${dim}\u2502${reset}${" ".repeat(innerWidth)}${dim}\u2502${reset}`,
|
|
2596
|
+
`${dim}\u2570${"\u2500".repeat(innerWidth)}\u256F${reset}`,
|
|
2597
|
+
""
|
|
2598
|
+
].join("\n");
|
|
2599
|
+
process.stderr.write(msg);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// src/cli/commands/upgrade.ts
|
|
2603
|
+
async function upgradeCommand(currentVersion) {
|
|
2604
|
+
console.log("Checking for updates...");
|
|
2605
|
+
console.log("");
|
|
2606
|
+
const result = await checkForUpdate(currentVersion);
|
|
2607
|
+
if (!result) {
|
|
2608
|
+
console.log("Could not reach the npm registry. Check your internet connection.");
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (!result.isOutdated) {
|
|
2612
|
+
console.log(`Already on the latest version (${result.current}).`);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const cmd = getUpgradeCommand();
|
|
2616
|
+
console.log(`Update available: ${result.current} \u2192 ${result.latest}`);
|
|
2617
|
+
console.log("");
|
|
2618
|
+
console.log(`Running: ${cmd}`);
|
|
2619
|
+
console.log("");
|
|
2620
|
+
try {
|
|
2621
|
+
execSync2(cmd, { stdio: "inherit" });
|
|
2622
|
+
console.log("");
|
|
2623
|
+
console.log(`Successfully upgraded to ${result.latest}!`);
|
|
2624
|
+
} catch {
|
|
2625
|
+
console.error("");
|
|
2626
|
+
console.error("Upgrade failed. Try running manually:");
|
|
2627
|
+
console.error(` ${cmd}`);
|
|
2628
|
+
console.error("");
|
|
2629
|
+
console.error("If you get a permission error, try:");
|
|
2630
|
+
console.error(` sudo ${cmd}`);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2320
2634
|
// src/cli/index.ts
|
|
2321
2635
|
var require3 = createRequire2(import.meta.url);
|
|
2322
2636
|
var { version } = require3("../../package.json");
|
|
2637
|
+
var updateCheckPromise = shouldNotify() ? checkForUpdate(version).catch(() => null) : Promise.resolve(null);
|
|
2323
2638
|
var program = new Command();
|
|
2324
2639
|
program.name("codecortex").description("Persistent, AI-powered codebase knowledge layer").version(version);
|
|
2325
2640
|
program.command("init").description("Initialize codebase knowledge: discover files, extract symbols, analyze git history").option("-r, --root <path>", "Project root directory", process.cwd()).option("-d, --days <number>", "Days of git history to analyze", "90").action(initCommand);
|
|
@@ -2330,5 +2645,21 @@ program.command("symbols [query]").description("Browse and filter the symbol ind
|
|
|
2330
2645
|
program.command("search <query>").description("Search across all CodeCortex knowledge files").option("-r, --root <path>", "Project root directory", process.cwd()).option("-l, --limit <number>", "Max results", "20").action((query, opts) => searchCommand(query, opts));
|
|
2331
2646
|
program.command("modules [name]").description("List modules or deep-dive into a specific module").option("-r, --root <path>", "Project root directory", process.cwd()).action((name, opts) => modulesCommand(name, opts));
|
|
2332
2647
|
program.command("hotspots").description("Show files ranked by risk: churn + coupling + bug history").option("-r, --root <path>", "Project root directory", process.cwd()).option("-l, --limit <number>", "Number of files to show", "15").action(hotspotsCommand);
|
|
2333
|
-
program.
|
|
2648
|
+
var hook = program.command("hook").description("Manage git hooks for auto-updating knowledge");
|
|
2649
|
+
hook.command("install").description("Install post-commit and post-merge hooks").option("-r, --root <path>", "Project root directory", process.cwd()).action(hookInstallCommand);
|
|
2650
|
+
hook.command("uninstall").description("Remove CodeCortex hooks (preserves other hooks)").option("-r, --root <path>", "Project root directory", process.cwd()).action(hookUninstallCommand);
|
|
2651
|
+
hook.command("status").description("Show hook installation state and knowledge freshness").option("-r, --root <path>", "Project root directory", process.cwd()).action(hookStatusCommand);
|
|
2652
|
+
program.command("upgrade").description("Check for and install the latest version of CodeCortex").action(() => upgradeCommand(version));
|
|
2653
|
+
async function main() {
|
|
2654
|
+
await program.parseAsync();
|
|
2655
|
+
const commandName = program.args[0];
|
|
2656
|
+
if (commandName !== "serve" && commandName !== "upgrade") {
|
|
2657
|
+
const result = await updateCheckPromise;
|
|
2658
|
+
if (result?.isOutdated) {
|
|
2659
|
+
renderUpdateNotification(result.current, result.latest);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
main().catch(() => {
|
|
2664
|
+
});
|
|
2334
2665
|
//# sourceMappingURL=index.js.map
|