codesight 1.0.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/index.js ADDED
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ import { resolve, join } from "node:path";
3
+ import { writeFile, stat, mkdir } from "node:fs/promises";
4
+ import { collectFiles, detectProject } from "./scanner.js";
5
+ import { detectRoutes } from "./detectors/routes.js";
6
+ import { detectSchemas } from "./detectors/schema.js";
7
+ import { detectComponents } from "./detectors/components.js";
8
+ import { detectLibs } from "./detectors/libs.js";
9
+ import { detectConfig } from "./detectors/config.js";
10
+ import { detectMiddleware } from "./detectors/middleware.js";
11
+ import { detectDependencyGraph } from "./detectors/graph.js";
12
+ import { enrichRouteContracts } from "./detectors/contracts.js";
13
+ import { calculateTokenStats } from "./detectors/tokens.js";
14
+ import { writeOutput } from "./formatter.js";
15
+ import { generateAIConfigs } from "./generators/ai-config.js";
16
+ import { generateHtmlReport } from "./generators/html-report.js";
17
+ const VERSION = "1.0.0";
18
+ const BRAND = "codesight";
19
+ function printHelp() {
20
+ console.log(`
21
+ ${BRAND} v${VERSION} — See your codebase clearly
22
+
23
+ Usage: ${BRAND} [options] [directory]
24
+
25
+ Options:
26
+ -o, --output <dir> Output directory (default: .codesight)
27
+ -d, --depth <n> Max directory depth (default: 10)
28
+ --init Generate AI config files (CLAUDE.md, .cursorrules, etc.)
29
+ --watch Re-scan on file changes
30
+ --hook Install git pre-commit hook
31
+ --html Generate interactive HTML report
32
+ --open Generate HTML report and open in browser
33
+ --mcp Start as MCP server (for Claude Code, Cursor)
34
+ --json Output JSON instead of markdown
35
+ -v, --version Show version
36
+ -h, --help Show this help
37
+
38
+ Examples:
39
+ npx ${BRAND} # Scan current directory
40
+ npx ${BRAND} --init # Scan + generate AI config files
41
+ npx ${BRAND} --open # Scan + open visual report
42
+ npx ${BRAND} --watch # Watch mode, re-scan on changes
43
+ npx ${BRAND} --mcp # Start MCP server
44
+ npx ${BRAND} --hook # Install git pre-commit hook
45
+ npx ${BRAND} ./my-project # Scan specific directory
46
+ `);
47
+ }
48
+ async function fileExists(path) {
49
+ try {
50
+ await stat(path);
51
+ return true;
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ async function scan(root, outputDirName, maxDepth) {
58
+ const outputDir = join(root, outputDirName);
59
+ console.log(`\n ${BRAND} v${VERSION}`);
60
+ console.log(` Scanning: ${root}\n`);
61
+ const startTime = Date.now();
62
+ // Step 1: Detect project
63
+ process.stdout.write(" Detecting project...");
64
+ const project = await detectProject(root);
65
+ console.log(` ${project.frameworks.length > 0 ? project.frameworks.join(", ") : "generic"} | ${project.orms.length > 0 ? project.orms.join(", ") : "no ORM"} | ${project.language}`);
66
+ if (project.isMonorepo) {
67
+ console.log(` Monorepo: ${project.workspaces.map((w) => w.name).join(", ")}`);
68
+ }
69
+ // Step 2: Collect files
70
+ process.stdout.write(" Collecting files...");
71
+ const files = await collectFiles(root, maxDepth);
72
+ console.log(` ${files.length} files`);
73
+ // Step 3: Run all detectors in parallel
74
+ process.stdout.write(" Analyzing...");
75
+ const [rawRoutes, schemas, components, libs, config, middleware, graph] = await Promise.all([
76
+ detectRoutes(files, project),
77
+ detectSchemas(files, project),
78
+ detectComponents(files, project),
79
+ detectLibs(files, project),
80
+ detectConfig(files, project),
81
+ detectMiddleware(files, project),
82
+ detectDependencyGraph(files, project),
83
+ ]);
84
+ // Step 4: Enrich routes with contract info
85
+ const routes = await enrichRouteContracts(rawRoutes, project);
86
+ console.log(" done");
87
+ // Step 5: Write output
88
+ process.stdout.write(" Writing output...");
89
+ // Temporary result without token stats to generate output
90
+ const tempResult = {
91
+ project,
92
+ routes,
93
+ schemas,
94
+ components,
95
+ libs,
96
+ config,
97
+ middleware,
98
+ graph,
99
+ tokenStats: { outputTokens: 0, estimatedExplorationTokens: 0, saved: 0, fileCount: files.length },
100
+ };
101
+ const outputContent = await writeOutput(tempResult, outputDir);
102
+ // Step 6: Calculate real token stats
103
+ const tokenStats = calculateTokenStats(tempResult, outputContent, files.length);
104
+ const result = { ...tempResult, tokenStats };
105
+ // Re-write with accurate token stats
106
+ await writeOutput(result, outputDir);
107
+ console.log(` ${outputDirName}/`);
108
+ const elapsed = Date.now() - startTime;
109
+ // Stats
110
+ console.log(`
111
+ Results:
112
+ Routes: ${routes.length}
113
+ Models: ${schemas.length}
114
+ Components: ${components.length}
115
+ Libraries: ${libs.length}
116
+ Env vars: ${config.envVars.length}
117
+ Middleware: ${middleware.length}
118
+ Import links: ${graph.edges.length}
119
+ Hot files: ${graph.hotFiles.length}
120
+
121
+ Tokens:
122
+ Output size: ~${tokenStats.outputTokens.toLocaleString()} tokens
123
+ Exploration cost: ~${tokenStats.estimatedExplorationTokens.toLocaleString()} tokens
124
+ Saved: ~${tokenStats.saved.toLocaleString()} tokens per conversation
125
+
126
+ Done in ${elapsed}ms
127
+ `);
128
+ return result;
129
+ }
130
+ async function installGitHook(root, outputDirName) {
131
+ const hooksDir = join(root, ".git", "hooks");
132
+ const hookPath = join(hooksDir, "pre-commit");
133
+ if (!(await fileExists(join(root, ".git")))) {
134
+ console.log(" No .git directory found. Initialize a git repo first.");
135
+ return;
136
+ }
137
+ await mkdir(hooksDir, { recursive: true });
138
+ let existingContent = "";
139
+ try {
140
+ const { readFile } = await import("node:fs/promises");
141
+ existingContent = await readFile(hookPath, "utf-8");
142
+ }
143
+ catch { }
144
+ const hookCommand = `\n# codesight: regenerate AI context\nnpx codesight -o ${outputDirName}\ngit add ${outputDirName}/\n`;
145
+ if (existingContent.includes("codesight")) {
146
+ console.log(" Git hook already installed.");
147
+ return;
148
+ }
149
+ if (existingContent) {
150
+ await writeFile(hookPath, existingContent + hookCommand);
151
+ }
152
+ else {
153
+ await writeFile(hookPath, `#!/bin/sh\n${hookCommand}`);
154
+ }
155
+ // Make executable
156
+ const { chmod } = await import("node:fs/promises");
157
+ await chmod(hookPath, 0o755);
158
+ console.log(` Git pre-commit hook installed at .git/hooks/pre-commit`);
159
+ }
160
+ async function watchMode(root, outputDirName, maxDepth) {
161
+ console.log(` Watching for changes... (Ctrl+C to stop)\n`);
162
+ let debounceTimer = null;
163
+ let isScanning = false;
164
+ const runScan = async () => {
165
+ if (isScanning)
166
+ return;
167
+ isScanning = true;
168
+ try {
169
+ console.log("\n Changes detected, re-scanning...\n");
170
+ await scan(root, outputDirName, maxDepth);
171
+ }
172
+ catch (err) {
173
+ console.error(" Scan error:", err.message);
174
+ }
175
+ isScanning = false;
176
+ };
177
+ // Use polling approach for cross-platform compatibility
178
+ const { watch } = await import("node:fs");
179
+ const watcher = watch(root, { recursive: true }, (_event, filename) => {
180
+ if (!filename)
181
+ return;
182
+ // Skip output directory and hidden files
183
+ if (filename.startsWith(outputDirName) || filename.startsWith(".git"))
184
+ return;
185
+ if (filename.includes("node_modules"))
186
+ return;
187
+ if (debounceTimer)
188
+ clearTimeout(debounceTimer);
189
+ debounceTimer = setTimeout(runScan, 500);
190
+ });
191
+ // Keep process alive
192
+ process.on("SIGINT", () => {
193
+ watcher.close();
194
+ console.log("\n Watch mode stopped.");
195
+ process.exit(0);
196
+ });
197
+ // Wait forever
198
+ await new Promise(() => { });
199
+ }
200
+ async function main() {
201
+ const args = process.argv.slice(2);
202
+ if (args.includes("--help") || args.includes("-h")) {
203
+ printHelp();
204
+ process.exit(0);
205
+ }
206
+ if (args.includes("--version") || args.includes("-v")) {
207
+ console.log(`${BRAND} v${VERSION}`);
208
+ process.exit(0);
209
+ }
210
+ // Parse args
211
+ let targetDir = process.cwd();
212
+ let outputDirName = ".codesight";
213
+ let maxDepth = 10;
214
+ let jsonOutput = false;
215
+ let doInit = false;
216
+ let doWatch = false;
217
+ let doHook = false;
218
+ let doHtml = false;
219
+ let doOpen = false;
220
+ let doMcp = false;
221
+ for (let i = 0; i < args.length; i++) {
222
+ const arg = args[i];
223
+ if ((arg === "-o" || arg === "--output") && args[i + 1]) {
224
+ outputDirName = args[++i];
225
+ }
226
+ else if ((arg === "-d" || arg === "--depth") && args[i + 1]) {
227
+ maxDepth = parseInt(args[++i], 10);
228
+ }
229
+ else if (arg === "--json") {
230
+ jsonOutput = true;
231
+ }
232
+ else if (arg === "--init") {
233
+ doInit = true;
234
+ }
235
+ else if (arg === "--watch") {
236
+ doWatch = true;
237
+ }
238
+ else if (arg === "--hook") {
239
+ doHook = true;
240
+ }
241
+ else if (arg === "--html") {
242
+ doHtml = true;
243
+ }
244
+ else if (arg === "--open") {
245
+ doHtml = true;
246
+ doOpen = true;
247
+ }
248
+ else if (arg === "--mcp") {
249
+ doMcp = true;
250
+ }
251
+ else if (!arg.startsWith("-")) {
252
+ targetDir = resolve(arg);
253
+ }
254
+ }
255
+ // MCP server mode (blocks, no other output)
256
+ if (doMcp) {
257
+ const { startMCPServer } = await import("./mcp-server.js");
258
+ await startMCPServer();
259
+ return;
260
+ }
261
+ const root = resolve(targetDir);
262
+ // Install git hook
263
+ if (doHook) {
264
+ await installGitHook(root, outputDirName);
265
+ }
266
+ // Run scan
267
+ const result = await scan(root, outputDirName, maxDepth);
268
+ // JSON output
269
+ if (jsonOutput) {
270
+ console.log(JSON.stringify(result, null, 2));
271
+ }
272
+ // Generate AI config files
273
+ if (doInit) {
274
+ process.stdout.write(" Generating AI configs...");
275
+ const generated = await generateAIConfigs(result, root);
276
+ if (generated.length > 0) {
277
+ console.log(` ${generated.join(", ")}`);
278
+ }
279
+ else {
280
+ console.log(" all configs already exist");
281
+ }
282
+ }
283
+ // Generate HTML report
284
+ if (doHtml) {
285
+ const outputDir = join(root, outputDirName);
286
+ process.stdout.write(" Generating HTML report...");
287
+ const reportPath = await generateHtmlReport(result, outputDir);
288
+ console.log(` ${outputDirName}/report.html`);
289
+ if (doOpen) {
290
+ const { exec } = await import("node:child_process");
291
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
292
+ exec(`${cmd} "${reportPath}"`);
293
+ console.log(" Opening in browser...");
294
+ }
295
+ }
296
+ // Watch mode (blocks)
297
+ if (doWatch) {
298
+ await watchMode(root, outputDirName, maxDepth);
299
+ }
300
+ }
301
+ main().catch((err) => {
302
+ console.error("Error:", err.message);
303
+ process.exit(1);
304
+ });
@@ -0,0 +1 @@
1
+ export declare function startMCPServer(): Promise<void>;
@@ -0,0 +1,171 @@
1
+ import { resolve } from "node:path";
2
+ import { collectFiles, detectProject } from "./scanner.js";
3
+ import { detectRoutes } from "./detectors/routes.js";
4
+ import { detectSchemas } from "./detectors/schema.js";
5
+ import { detectComponents } from "./detectors/components.js";
6
+ import { detectLibs } from "./detectors/libs.js";
7
+ import { detectConfig } from "./detectors/config.js";
8
+ import { detectMiddleware } from "./detectors/middleware.js";
9
+ import { detectDependencyGraph } from "./detectors/graph.js";
10
+ import { enrichRouteContracts } from "./detectors/contracts.js";
11
+ import { calculateTokenStats } from "./detectors/tokens.js";
12
+ import { writeOutput } from "./formatter.js";
13
+ function send(msg) {
14
+ const json = JSON.stringify(msg);
15
+ const header = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n`;
16
+ process.stdout.write(header + json);
17
+ }
18
+ async function runScan(directory) {
19
+ const root = resolve(directory || process.cwd());
20
+ const project = await detectProject(root);
21
+ const files = await collectFiles(root, 10);
22
+ const [rawRoutes, schemas, components, libs, config, middleware, graph] = await Promise.all([
23
+ detectRoutes(files, project),
24
+ detectSchemas(files, project),
25
+ detectComponents(files, project),
26
+ detectLibs(files, project),
27
+ detectConfig(files, project),
28
+ detectMiddleware(files, project),
29
+ detectDependencyGraph(files, project),
30
+ ]);
31
+ const routes = await enrichRouteContracts(rawRoutes, project);
32
+ const tempResult = {
33
+ project,
34
+ routes,
35
+ schemas,
36
+ components,
37
+ libs,
38
+ config,
39
+ middleware,
40
+ graph,
41
+ tokenStats: { outputTokens: 0, estimatedExplorationTokens: 0, saved: 0, fileCount: files.length },
42
+ };
43
+ const outputContent = await writeOutput(tempResult, resolve(root, ".codesight"));
44
+ const tokenStats = calculateTokenStats(tempResult, outputContent, files.length);
45
+ return outputContent.replace(/Saves ~\d[\d,]* tokens/, `Saves ~${tokenStats.saved.toLocaleString()} tokens`);
46
+ }
47
+ async function handleRequest(req) {
48
+ // MCP initialize
49
+ if (req.method === "initialize") {
50
+ send({
51
+ jsonrpc: "2.0",
52
+ id: req.id ?? null,
53
+ result: {
54
+ protocolVersion: "2024-11-05",
55
+ capabilities: { tools: {} },
56
+ serverInfo: { name: "codesight", version: "1.0.0" },
57
+ },
58
+ });
59
+ return;
60
+ }
61
+ // MCP initialized notification
62
+ if (req.method === "notifications/initialized") {
63
+ return; // no response for notifications
64
+ }
65
+ // List tools
66
+ if (req.method === "tools/list") {
67
+ send({
68
+ jsonrpc: "2.0",
69
+ id: req.id ?? null,
70
+ result: {
71
+ tools: [
72
+ {
73
+ name: "codesight_scan",
74
+ description: "Scans a codebase and returns a complete AI context map including routes, database schema, components, libraries, config, middleware, and dependency graph. Saves thousands of tokens vs manual exploration.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ directory: {
79
+ type: "string",
80
+ description: "Directory to scan (defaults to current working directory)",
81
+ },
82
+ },
83
+ },
84
+ },
85
+ ],
86
+ },
87
+ });
88
+ return;
89
+ }
90
+ // Call tool
91
+ if (req.method === "tools/call") {
92
+ const toolName = req.params?.name;
93
+ const args = req.params?.arguments || {};
94
+ if (toolName === "codesight_scan") {
95
+ try {
96
+ const result = await runScan(args.directory || process.cwd());
97
+ send({
98
+ jsonrpc: "2.0",
99
+ id: req.id ?? null,
100
+ result: {
101
+ content: [{ type: "text", text: result }],
102
+ },
103
+ });
104
+ }
105
+ catch (err) {
106
+ send({
107
+ jsonrpc: "2.0",
108
+ id: req.id ?? null,
109
+ result: {
110
+ content: [{ type: "text", text: `Error scanning: ${err.message}` }],
111
+ isError: true,
112
+ },
113
+ });
114
+ }
115
+ return;
116
+ }
117
+ send({
118
+ jsonrpc: "2.0",
119
+ id: req.id ?? null,
120
+ error: { code: -32601, message: `Unknown tool: ${toolName}` },
121
+ });
122
+ return;
123
+ }
124
+ // Unknown method
125
+ if (req.id !== undefined) {
126
+ send({
127
+ jsonrpc: "2.0",
128
+ id: req.id,
129
+ error: { code: -32601, message: `Method not found: ${req.method}` },
130
+ });
131
+ }
132
+ }
133
+ export async function startMCPServer() {
134
+ // Read Content-Length delimited JSON-RPC messages from stdin
135
+ let buffer = "";
136
+ process.stdin.setEncoding("utf-8");
137
+ process.stdin.on("data", async (chunk) => {
138
+ buffer += chunk;
139
+ while (true) {
140
+ // Parse Content-Length header
141
+ const headerEnd = buffer.indexOf("\r\n\r\n");
142
+ if (headerEnd === -1)
143
+ break;
144
+ const header = buffer.substring(0, headerEnd);
145
+ const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
146
+ if (!lengthMatch) {
147
+ buffer = buffer.substring(headerEnd + 4);
148
+ continue;
149
+ }
150
+ const contentLength = parseInt(lengthMatch[1], 10);
151
+ const bodyStart = headerEnd + 4;
152
+ if (buffer.length < bodyStart + contentLength)
153
+ break;
154
+ const body = buffer.substring(bodyStart, bodyStart + contentLength);
155
+ buffer = buffer.substring(bodyStart + contentLength);
156
+ try {
157
+ const req = JSON.parse(body);
158
+ await handleRequest(req);
159
+ }
160
+ catch (err) {
161
+ send({
162
+ jsonrpc: "2.0",
163
+ id: null,
164
+ error: { code: -32700, message: "Parse error" },
165
+ });
166
+ }
167
+ }
168
+ });
169
+ // Keep alive
170
+ await new Promise(() => { });
171
+ }
@@ -0,0 +1,4 @@
1
+ import type { ProjectInfo } from "./types.js";
2
+ export declare function collectFiles(root: string, maxDepth?: number): Promise<string[]>;
3
+ export declare function readFileSafe(path: string): Promise<string>;
4
+ export declare function detectProject(root: string): Promise<ProjectInfo>;