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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Estimates token counts using a simple heuristic:
3
+ * ~4 characters per token for English text/code (GPT/Claude average)
4
+ * This avoids requiring tiktoken as a dependency.
5
+ */
6
+ function estimateTokens(text) {
7
+ return Math.ceil(text.length / 4);
8
+ }
9
+ /**
10
+ * Calculates token stats: how many tokens the output uses
11
+ * vs. how many an AI would spend exploring the same info manually
12
+ */
13
+ export function calculateTokenStats(result, outputContent, fileCount) {
14
+ const outputTokens = estimateTokens(outputContent);
15
+ // Estimate exploration cost:
16
+ // - Each file read costs ~150 tokens (path + content snippet)
17
+ // - Each route discovery requires reading the route file (~400 tokens avg)
18
+ // - Each schema model requires reading the schema file (~300 tokens avg)
19
+ // - Each component discovery requires reading the file (~250 tokens avg)
20
+ // - Each grep/glob operation costs ~50 tokens for the command + results
21
+ // - AI typically needs 3-5 exploration rounds to map a project
22
+ const routeExplorationTokens = result.routes.length * 400;
23
+ const schemaExplorationTokens = result.schemas.length * 300;
24
+ const componentExplorationTokens = result.components.length * 250;
25
+ const libExplorationTokens = result.libs.length * 200;
26
+ const configExplorationTokens = result.config.envVars.length * 100;
27
+ const middlewareExplorationTokens = result.middleware.length * 200;
28
+ const graphExplorationTokens = result.graph.hotFiles.length * 150;
29
+ // Add overhead for glob/grep operations to find files (typically 10-20 searches)
30
+ const searchOverhead = Math.min(fileCount, 50) * 80;
31
+ // Add overhead for multiple exploration rounds (AI often revisits files)
32
+ const revisitMultiplier = 1.3;
33
+ const estimatedExplorationTokens = Math.round((routeExplorationTokens +
34
+ schemaExplorationTokens +
35
+ componentExplorationTokens +
36
+ libExplorationTokens +
37
+ configExplorationTokens +
38
+ middlewareExplorationTokens +
39
+ graphExplorationTokens +
40
+ searchOverhead) *
41
+ revisitMultiplier);
42
+ return {
43
+ outputTokens,
44
+ estimatedExplorationTokens,
45
+ saved: Math.max(0, estimatedExplorationTokens - outputTokens),
46
+ fileCount,
47
+ };
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScanResult } from "./types.js";
2
+ export declare function writeOutput(result: ScanResult, outputDir: string): Promise<string>;
@@ -0,0 +1,268 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export async function writeOutput(result, outputDir) {
4
+ await mkdir(outputDir, { recursive: true });
5
+ const sections = [];
6
+ if (result.routes.length > 0) {
7
+ const content = formatRoutes(result);
8
+ sections.push({ name: "routes", content });
9
+ await writeFile(join(outputDir, "routes.md"), content);
10
+ }
11
+ if (result.schemas.length > 0) {
12
+ const content = formatSchema(result);
13
+ sections.push({ name: "schema", content });
14
+ await writeFile(join(outputDir, "schema.md"), content);
15
+ }
16
+ if (result.components.length > 0) {
17
+ const content = formatComponents(result);
18
+ sections.push({ name: "components", content });
19
+ await writeFile(join(outputDir, "components.md"), content);
20
+ }
21
+ if (result.libs.length > 0) {
22
+ const content = formatLibs(result);
23
+ sections.push({ name: "libs", content });
24
+ await writeFile(join(outputDir, "libs.md"), content);
25
+ }
26
+ const configContent = formatConfig(result);
27
+ if (configContent) {
28
+ sections.push({ name: "config", content: configContent });
29
+ await writeFile(join(outputDir, "config.md"), configContent);
30
+ }
31
+ if (result.middleware.length > 0) {
32
+ const content = formatMiddleware(result);
33
+ sections.push({ name: "middleware", content });
34
+ await writeFile(join(outputDir, "middleware.md"), content);
35
+ }
36
+ if (result.graph.hotFiles.length > 0) {
37
+ const content = formatGraph(result);
38
+ sections.push({ name: "graph", content });
39
+ await writeFile(join(outputDir, "graph.md"), content);
40
+ }
41
+ const combined = formatCombined(result, sections);
42
+ await writeFile(join(outputDir, "CODESIGHT.md"), combined);
43
+ return combined;
44
+ }
45
+ function formatRoutes(result) {
46
+ const lines = ["# Routes", ""];
47
+ const byFramework = new Map();
48
+ for (const route of result.routes) {
49
+ const fw = route.framework;
50
+ if (!byFramework.has(fw))
51
+ byFramework.set(fw, []);
52
+ byFramework.get(fw).push(route);
53
+ }
54
+ for (const [fw, routes] of byFramework) {
55
+ if (byFramework.size > 1) {
56
+ lines.push(`## ${fw}`, "");
57
+ }
58
+ for (const route of routes) {
59
+ const tags = route.tags.length > 0 ? ` [${route.tags.join(", ")}]` : "";
60
+ const params = route.params ? ` params(${route.params.join(", ")})` : "";
61
+ const contract = [];
62
+ if (route.requestType)
63
+ contract.push(`in: ${route.requestType}`);
64
+ if (route.responseType)
65
+ contract.push(`out: ${route.responseType}`);
66
+ const contractStr = contract.length > 0 ? ` → ${contract.join(", ")}` : "";
67
+ lines.push(`- \`${route.method}\` \`${route.path}\`${params}${contractStr}${tags}`);
68
+ }
69
+ lines.push("");
70
+ }
71
+ return lines.join("\n");
72
+ }
73
+ function formatSchema(result) {
74
+ const lines = ["# Schema", ""];
75
+ const byOrm = new Map();
76
+ for (const model of result.schemas) {
77
+ if (!byOrm.has(model.orm))
78
+ byOrm.set(model.orm, []);
79
+ byOrm.get(model.orm).push(model);
80
+ }
81
+ for (const [orm, models] of byOrm) {
82
+ if (byOrm.size > 1)
83
+ lines.push(`## ${orm}`, "");
84
+ for (const model of models) {
85
+ if (model.name.startsWith("enum:")) {
86
+ const enumName = model.name.replace("enum:", "");
87
+ const values = model.fields.map((f) => f.name).join(" | ");
88
+ lines.push(`### enum ${enumName}: ${values}`, "");
89
+ continue;
90
+ }
91
+ lines.push(`### ${model.name}`);
92
+ for (const field of model.fields) {
93
+ const flags = field.flags.length > 0 ? ` (${field.flags.join(", ")})` : "";
94
+ lines.push(`- ${field.name}: ${field.type}${flags}`);
95
+ }
96
+ if (model.relations.length > 0) {
97
+ lines.push(`- _relations_: ${model.relations.join(", ")}`);
98
+ }
99
+ lines.push("");
100
+ }
101
+ }
102
+ return lines.join("\n");
103
+ }
104
+ function formatComponents(result) {
105
+ const lines = ["# Components", ""];
106
+ for (const comp of result.components) {
107
+ const markers = [];
108
+ if (comp.isClient)
109
+ markers.push("client");
110
+ if (comp.isServer)
111
+ markers.push("server");
112
+ const markerStr = markers.length > 0 ? ` [${markers.join(", ")}]` : "";
113
+ if (comp.props.length > 0) {
114
+ lines.push(`- **${comp.name}**${markerStr} — props: ${comp.props.join(", ")} — \`${comp.file}\``);
115
+ }
116
+ else {
117
+ lines.push(`- **${comp.name}**${markerStr} — \`${comp.file}\``);
118
+ }
119
+ }
120
+ lines.push("");
121
+ return lines.join("\n");
122
+ }
123
+ function formatLibs(result) {
124
+ const lines = ["# Libraries", ""];
125
+ for (const lib of result.libs) {
126
+ const maxExports = 6;
127
+ const shown = lib.exports.slice(0, maxExports);
128
+ const remaining = lib.exports.length - maxExports;
129
+ if (lib.exports.length <= 2) {
130
+ const exps = shown
131
+ .map((e) => {
132
+ const sig = e.signature ? `: ${e.signature}` : "";
133
+ return `${e.kind} ${e.name}${sig}`;
134
+ })
135
+ .join(", ");
136
+ lines.push(`- \`${lib.file}\` — ${exps}`);
137
+ }
138
+ else {
139
+ lines.push(`- \`${lib.file}\``);
140
+ for (const exp of shown) {
141
+ const sig = exp.signature ? `: ${exp.signature}` : "";
142
+ lines.push(` - ${exp.kind} ${exp.name}${sig}`);
143
+ }
144
+ if (remaining > 0)
145
+ lines.push(` - _...${remaining} more_`);
146
+ }
147
+ }
148
+ lines.push("");
149
+ return lines.join("\n");
150
+ }
151
+ function formatConfig(result) {
152
+ const lines = ["# Config", ""];
153
+ if (result.config.envVars.length > 0) {
154
+ lines.push("## Environment Variables", "");
155
+ for (const env of result.config.envVars) {
156
+ const status = env.hasDefault ? "(has default)" : "**required**";
157
+ lines.push(`- \`${env.name}\` ${status} — ${env.source}`);
158
+ }
159
+ lines.push("");
160
+ }
161
+ if (result.config.configFiles.length > 0) {
162
+ lines.push("## Config Files", "");
163
+ for (const f of result.config.configFiles) {
164
+ lines.push(`- \`${f}\``);
165
+ }
166
+ lines.push("");
167
+ }
168
+ const notableDeps = filterNotableDeps(result.config.dependencies);
169
+ if (notableDeps.length > 0) {
170
+ lines.push("## Key Dependencies", "");
171
+ for (const [name, version] of notableDeps) {
172
+ lines.push(`- ${name}: ${version}`);
173
+ }
174
+ lines.push("");
175
+ }
176
+ if (lines.length <= 2)
177
+ return null;
178
+ return lines.join("\n");
179
+ }
180
+ function formatMiddleware(result) {
181
+ const lines = ["# Middleware", ""];
182
+ const byType = new Map();
183
+ for (const mw of result.middleware) {
184
+ if (!byType.has(mw.type))
185
+ byType.set(mw.type, []);
186
+ byType.get(mw.type).push(mw);
187
+ }
188
+ for (const [type, mws] of byType) {
189
+ lines.push(`## ${type}`);
190
+ for (const mw of mws) {
191
+ lines.push(`- ${mw.name} — \`${mw.file}\``);
192
+ }
193
+ lines.push("");
194
+ }
195
+ return lines.join("\n");
196
+ }
197
+ function formatGraph(result) {
198
+ const lines = ["# Dependency Graph", ""];
199
+ lines.push("## Most Imported Files (change these carefully)", "");
200
+ for (const hf of result.graph.hotFiles) {
201
+ lines.push(`- \`${hf.file}\` — imported by **${hf.importedBy}** files`);
202
+ }
203
+ lines.push("");
204
+ // Show top import edges grouped by target
205
+ const edgesByTarget = new Map();
206
+ for (const edge of result.graph.edges) {
207
+ if (!edgesByTarget.has(edge.to))
208
+ edgesByTarget.set(edge.to, []);
209
+ edgesByTarget.get(edge.to).push(edge.from);
210
+ }
211
+ const topTargets = Array.from(edgesByTarget.entries())
212
+ .sort((a, b) => b[1].length - a[1].length)
213
+ .slice(0, 10);
214
+ if (topTargets.length > 0) {
215
+ lines.push("## Import Map (who imports what)", "");
216
+ for (const [target, importers] of topTargets) {
217
+ const shown = importers.slice(0, 5);
218
+ const more = importers.length > 5 ? ` +${importers.length - 5} more` : "";
219
+ lines.push(`- \`${target}\` ← ${shown.map((i) => `\`${i}\``).join(", ")}${more}`);
220
+ }
221
+ lines.push("");
222
+ }
223
+ return lines.join("\n");
224
+ }
225
+ function formatCombined(result, sections) {
226
+ const lines = [];
227
+ lines.push(`# ${result.project.name} — AI Context Map`);
228
+ lines.push("");
229
+ const fw = result.project.frameworks.join(", ") || "unknown";
230
+ const orm = result.project.orms.join(", ") || "none";
231
+ const lang = result.project.language;
232
+ const compFw = result.project.componentFramework;
233
+ lines.push(`> **Stack:** ${fw} | ${orm} | ${compFw} | ${lang}`);
234
+ if (result.project.isMonorepo) {
235
+ const wsNames = result.project.workspaces.map((w) => w.name).join(", ");
236
+ lines.push(`> **Monorepo:** ${wsNames}`);
237
+ }
238
+ lines.push("");
239
+ // Token stats
240
+ const ts = result.tokenStats;
241
+ lines.push(`> ${result.routes.length} routes | ${result.schemas.length} models | ${result.components.length} components | ${result.libs.length} lib files | ${result.config.envVars.length} env vars | ${result.middleware.length} middleware | ${result.graph.edges.length} import links`);
242
+ lines.push(`> **Token savings:** this file is ~${ts.outputTokens.toLocaleString()} tokens. Without it, AI exploration would cost ~${ts.estimatedExplorationTokens.toLocaleString()} tokens. **Saves ~${ts.saved.toLocaleString()} tokens per conversation.**`);
243
+ lines.push("");
244
+ lines.push("---");
245
+ lines.push("");
246
+ for (const section of sections) {
247
+ lines.push(section.content);
248
+ lines.push("---");
249
+ lines.push("");
250
+ }
251
+ lines.push(`_Generated by [codesight](https://github.com/Houseofmvps/codesight) — see your codebase clearly_`);
252
+ return lines.join("\n");
253
+ }
254
+ function filterNotableDeps(deps) {
255
+ const notable = new Set([
256
+ "next", "react", "vue", "svelte", "hono", "express", "fastify", "koa",
257
+ "drizzle-orm", "prisma", "@prisma/client", "typeorm", "tailwindcss",
258
+ "stripe", "@polar-sh/sdk", "resend", "bullmq", "redis", "ioredis",
259
+ "zod", "trpc", "@trpc/server", "better-auth", "@clerk/nextjs",
260
+ "next-auth", "lucia", "passport", "@anthropic-ai/sdk", "openai",
261
+ "ai", "langchain", "supabase", "@supabase/supabase-js", "mongoose",
262
+ "pg", "mysql2", "better-sqlite3", "playwright", "puppeteer",
263
+ "socket.io", "graphql", "@apollo/server",
264
+ ]);
265
+ return Object.entries(deps)
266
+ .filter(([name]) => notable.has(name))
267
+ .sort(([a], [b]) => a.localeCompare(b));
268
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScanResult } from "../types.js";
2
+ export declare function generateAIConfigs(result: ScanResult, root: string): Promise<string[]>;
@@ -0,0 +1,137 @@
1
+ import { writeFile, readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ async function fileExists(path) {
4
+ try {
5
+ await stat(path);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ function generateContext(result) {
13
+ const { project, routes, schemas, components, middleware, config, graph } = result;
14
+ const lines = [];
15
+ // Stack overview
16
+ const fw = project.frameworks.join(", ") || "generic";
17
+ const orm = project.orms.join(", ") || "none";
18
+ lines.push(`This is a ${project.language} project using ${fw}${orm !== "none" ? ` with ${orm}` : ""}.`);
19
+ if (project.isMonorepo) {
20
+ lines.push(`It is a monorepo with workspaces: ${project.workspaces.map((w) => `${w.name} (${w.path})`).join(", ")}.`);
21
+ }
22
+ lines.push("");
23
+ // Key architecture facts
24
+ if (routes.length > 0) {
25
+ lines.push(`The API has ${routes.length} routes. See .codesight/routes.md for the full route map with methods, paths, and tags.`);
26
+ }
27
+ if (schemas.length > 0) {
28
+ lines.push(`The database has ${schemas.length} models. See .codesight/schema.md for the full schema with fields, types, and relations.`);
29
+ }
30
+ if (components.length > 0) {
31
+ lines.push(`The UI has ${components.length} components. See .codesight/components.md for the full list with props.`);
32
+ }
33
+ if (middleware.length > 0) {
34
+ const types = [...new Set(middleware.map((m) => m.type))].join(", ");
35
+ lines.push(`Middleware includes: ${types}.`);
36
+ }
37
+ lines.push("");
38
+ // Hot files (most imported = most impactful to change)
39
+ if (graph.hotFiles.length > 0) {
40
+ lines.push("High-impact files (most imported, changes here affect many other files):");
41
+ for (const hf of graph.hotFiles.slice(0, 8)) {
42
+ lines.push(`- ${hf.file} (imported by ${hf.importedBy} files)`);
43
+ }
44
+ lines.push("");
45
+ }
46
+ // Required env vars
47
+ const requiredEnvs = config.envVars.filter((e) => !e.hasDefault);
48
+ if (requiredEnvs.length > 0) {
49
+ lines.push("Required environment variables (no defaults):");
50
+ for (const env of requiredEnvs.slice(0, 15)) {
51
+ lines.push(`- ${env.name} (${env.source})`);
52
+ }
53
+ lines.push("");
54
+ }
55
+ lines.push("Read .codesight/CODESIGHT.md for the complete AI context map including all routes, schema, components, libraries, config, middleware, and dependency graph.");
56
+ return lines.join("\n");
57
+ }
58
+ export async function generateAIConfigs(result, root) {
59
+ const generated = [];
60
+ const context = generateContext(result);
61
+ // --- CLAUDE.md ---
62
+ const claudePath = join(root, "CLAUDE.md");
63
+ const claudeExists = await fileExists(claudePath);
64
+ if (!claudeExists) {
65
+ const claudeContent = `# Project Context
66
+
67
+ ${context}
68
+ `;
69
+ await writeFile(claudePath, claudeContent);
70
+ generated.push("CLAUDE.md");
71
+ }
72
+ else {
73
+ // Check if it already references codesight
74
+ const existing = await readFile(claudePath, "utf-8");
75
+ if (!existing.includes("codesight") && !existing.includes("CODESIGHT")) {
76
+ // Append context section
77
+ const appendSection = `
78
+
79
+ # AI Context (auto-generated by codesight)
80
+
81
+ ${context}
82
+ `;
83
+ await writeFile(claudePath, existing + appendSection);
84
+ generated.push("CLAUDE.md (appended)");
85
+ }
86
+ }
87
+ // --- .cursorrules ---
88
+ const cursorPath = join(root, ".cursorrules");
89
+ const cursorExists = await fileExists(cursorPath);
90
+ if (!cursorExists) {
91
+ const cursorContent = `# Project Context
92
+
93
+ ${context}
94
+ `;
95
+ await writeFile(cursorPath, cursorContent);
96
+ generated.push(".cursorrules");
97
+ }
98
+ // --- .github/copilot-instructions.md ---
99
+ const copilotDir = join(root, ".github");
100
+ const copilotPath = join(copilotDir, "copilot-instructions.md");
101
+ const copilotExists = await fileExists(copilotPath);
102
+ if (!copilotExists) {
103
+ // Only create if .github dir exists (don't create it for non-GitHub projects)
104
+ const ghDirExists = await fileExists(copilotDir);
105
+ if (ghDirExists) {
106
+ const copilotContent = `# Project Context
107
+
108
+ ${context}
109
+ `;
110
+ await writeFile(copilotPath, copilotContent);
111
+ generated.push(".github/copilot-instructions.md");
112
+ }
113
+ }
114
+ // --- codex.md (OpenAI Codex CLI) ---
115
+ const codexPath = join(root, "codex.md");
116
+ const codexExists = await fileExists(codexPath);
117
+ if (!codexExists) {
118
+ const codexContent = `# Project Context
119
+
120
+ ${context}
121
+ `;
122
+ await writeFile(codexPath, codexContent);
123
+ generated.push("codex.md");
124
+ }
125
+ // --- AGENTS.md (OpenAI Codex) ---
126
+ const agentsPath = join(root, "AGENTS.md");
127
+ const agentsExists = await fileExists(agentsPath);
128
+ if (!agentsExists) {
129
+ const agentsContent = `# Project Context
130
+
131
+ ${context}
132
+ `;
133
+ await writeFile(agentsPath, agentsContent);
134
+ generated.push("AGENTS.md");
135
+ }
136
+ return generated;
137
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScanResult } from "../types.js";
2
+ export declare function generateHtmlReport(result: ScanResult, outputDir: string): Promise<string>;
@@ -0,0 +1,200 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ function escapeHtml(str) {
4
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5
+ }
6
+ export async function generateHtmlReport(result, outputDir) {
7
+ const filePath = join(outputDir, "report.html");
8
+ const html = buildHtml(result);
9
+ await writeFile(filePath, html);
10
+ return filePath;
11
+ }
12
+ function buildHtml(r) {
13
+ const { project, routes, schemas, components, libs, config, middleware, graph, tokenStats } = r;
14
+ const fw = project.frameworks.join(", ") || "generic";
15
+ const orm = project.orms.join(", ") || "none";
16
+ return `<!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
+ <title>${escapeHtml(project.name)} — codesight report</title>
22
+ <style>
23
+ *{margin:0;padding:0;box-sizing:border-box}
24
+ :root{--bg:#0a0a0f;--card:#12121a;--border:#1e1e2e;--text:#e0e0e8;--muted:#6b6b80;--accent:#6366f1;--accent2:#22d3ee;--green:#22c55e;--orange:#f59e0b;--red:#ef4444;--pink:#ec4899}
25
+ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:2rem;max-width:1400px;margin:0 auto;line-height:1.6}
26
+ h1{font-size:2.5rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:.25rem}
27
+ .subtitle{color:var(--muted);font-size:1rem;margin-bottom:2rem}
28
+ .stack-badge{display:inline-block;background:var(--card);border:1px solid var(--border);border-radius:6px;padding:2px 10px;font-size:.85rem;color:var(--accent2);margin:0 4px 4px 0}
29
+ .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin:2rem 0}
30
+ .stat{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;text-align:center}
31
+ .stat-value{font-size:2rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
32
+ .stat-label{color:var(--muted);font-size:.85rem;margin-top:.25rem}
33
+ .token-hero{background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid var(--accent);border-radius:16px;padding:2rem;margin:2rem 0;text-align:center}
34
+ .token-saved{font-size:3rem;font-weight:900;color:var(--green)}
35
+ .token-detail{color:var(--muted);font-size:.9rem;margin-top:.5rem}
36
+ .section{margin:2.5rem 0}
37
+ .section h2{font-size:1.4rem;font-weight:700;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid var(--border)}
38
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}
39
+ .card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1rem;transition:border-color .2s}
40
+ .card:hover{border-color:var(--accent)}
41
+ .card-title{font-weight:700;font-size:1rem;margin-bottom:.5rem}
42
+ .card-meta{color:var(--muted);font-size:.8rem}
43
+ .tag{display:inline-block;background:rgba(99,102,241,.15);color:var(--accent);border-radius:4px;padding:1px 6px;font-size:.75rem;margin:1px}
44
+ .tag-auth{background:rgba(239,68,68,.15);color:var(--red)}
45
+ .tag-db{background:rgba(34,211,238,.15);color:var(--accent2)}
46
+ .tag-ai{background:rgba(236,72,153,.15);color:var(--pink)}
47
+ .tag-payment{background:rgba(245,158,11,.15);color:var(--orange)}
48
+ .tag-email{background:rgba(34,197,94,.15);color:var(--green)}
49
+ .tag-queue{background:rgba(168,85,247,.15);color:#a855f7}
50
+ .tag-cache{background:rgba(245,158,11,.15);color:var(--orange)}
51
+ .method{font-weight:700;font-size:.8rem;padding:2px 6px;border-radius:4px;margin-right:6px}
52
+ .method-GET{background:rgba(34,197,94,.2);color:var(--green)}
53
+ .method-POST{background:rgba(99,102,241,.2);color:var(--accent)}
54
+ .method-PUT{background:rgba(245,158,11,.2);color:var(--orange)}
55
+ .method-PATCH{background:rgba(245,158,11,.2);color:var(--orange)}
56
+ .method-DELETE{background:rgba(239,68,68,.2);color:var(--red)}
57
+ .method-ALL{background:rgba(107,107,128,.2);color:var(--muted)}
58
+ .route-path{font-family:'Fira Code',monospace;font-size:.9rem}
59
+ .route-contract{color:var(--muted);font-size:.8rem;font-style:italic;margin-left:.5rem}
60
+ .field{display:flex;gap:.5rem;padding:3px 0;font-size:.9rem}
61
+ .field-name{font-family:monospace;color:var(--accent2)}
62
+ .field-type{color:var(--muted);font-family:monospace}
63
+ .field-flags{display:flex;gap:3px}
64
+ .flag{font-size:.7rem;padding:0 4px;border-radius:3px;background:rgba(99,102,241,.1);color:var(--accent)}
65
+ .flag-pk{background:rgba(245,158,11,.2);color:var(--orange)}
66
+ .flag-fk{background:rgba(34,211,238,.2);color:var(--accent2)}
67
+ .flag-unique{background:rgba(236,72,153,.2);color:var(--pink)}
68
+ .hot-bar{height:8px;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:4px;margin-top:4px}
69
+ .component-props{color:var(--muted);font-size:.85rem}
70
+ .badge-client{background:rgba(34,197,94,.15);color:var(--green);font-size:.75rem;padding:1px 6px;border-radius:4px}
71
+ .badge-server{background:rgba(99,102,241,.15);color:var(--accent);font-size:.75rem;padding:1px 6px;border-radius:4px}
72
+ .env-required{color:var(--red);font-weight:600;font-size:.8rem}
73
+ .env-default{color:var(--green);font-size:.8rem}
74
+ .footer{text-align:center;color:var(--muted);margin-top:4rem;padding-top:2rem;border-top:1px solid var(--border);font-size:.85rem}
75
+ .footer a{color:var(--accent);text-decoration:none}
76
+ table{width:100%;border-collapse:collapse}
77
+ table td,table th{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border);font-size:.9rem}
78
+ table th{color:var(--muted);font-size:.8rem;font-weight:600;text-transform:uppercase}
79
+ </style>
80
+ </head>
81
+ <body>
82
+
83
+ <h1>${escapeHtml(project.name)}</h1>
84
+ <div class="subtitle">AI Context Map — generated by codesight</div>
85
+
86
+ <div>
87
+ ${project.frameworks.map((f) => `<span class="stack-badge">${escapeHtml(f)}</span>`).join("")}
88
+ ${project.orms.map((o) => `<span class="stack-badge">${escapeHtml(o)}</span>`).join("")}
89
+ <span class="stack-badge">${escapeHtml(project.componentFramework)}</span>
90
+ <span class="stack-badge">${escapeHtml(project.language)}</span>
91
+ ${project.isMonorepo ? '<span class="stack-badge">monorepo</span>' : ""}
92
+ </div>
93
+
94
+ <div class="token-hero">
95
+ <div class="token-saved">~${tokenStats.saved.toLocaleString()} tokens saved</div>
96
+ <div class="token-detail">
97
+ Output: ${tokenStats.outputTokens.toLocaleString()} tokens — Exploration cost without codesight: ~${tokenStats.estimatedExplorationTokens.toLocaleString()} tokens — ${tokenStats.fileCount} files scanned
98
+ </div>
99
+ </div>
100
+
101
+ <div class="stats">
102
+ <div class="stat"><div class="stat-value">${routes.length}</div><div class="stat-label">Routes</div></div>
103
+ <div class="stat"><div class="stat-value">${schemas.length}</div><div class="stat-label">Models</div></div>
104
+ <div class="stat"><div class="stat-value">${components.length}</div><div class="stat-label">Components</div></div>
105
+ <div class="stat"><div class="stat-value">${libs.length}</div><div class="stat-label">Libraries</div></div>
106
+ <div class="stat"><div class="stat-value">${config.envVars.length}</div><div class="stat-label">Env Vars</div></div>
107
+ <div class="stat"><div class="stat-value">${middleware.length}</div><div class="stat-label">Middleware</div></div>
108
+ <div class="stat"><div class="stat-value">${graph.edges.length}</div><div class="stat-label">Import Links</div></div>
109
+ </div>
110
+
111
+ ${routes.length > 0 ? `
112
+ <div class="section">
113
+ <h2>Routes</h2>
114
+ <table>
115
+ <tr><th>Method</th><th>Path</th><th>Contract</th><th>Tags</th><th>File</th></tr>
116
+ ${routes.map((r) => `<tr>
117
+ <td><span class="method method-${escapeHtml(r.method)}">${escapeHtml(r.method)}</span></td>
118
+ <td class="route-path">${escapeHtml(r.path)}${r.params ? ` <span class="route-contract">params: ${escapeHtml(r.params.join(", "))}</span>` : ""}</td>
119
+ <td>${r.requestType ? `<span class="route-contract">in: ${escapeHtml(r.requestType)}</span>` : ""}${r.responseType ? `<span class="route-contract">out: ${escapeHtml(r.responseType)}</span>` : ""}</td>
120
+ <td>${r.tags.map((t) => `<span class="tag tag-${escapeHtml(t)}">${escapeHtml(t)}</span>`).join("")}</td>
121
+ <td class="card-meta">${escapeHtml(r.file)}</td>
122
+ </tr>`).join("\n")}
123
+ </table>
124
+ </div>` : ""}
125
+
126
+ ${schemas.length > 0 ? `
127
+ <div class="section">
128
+ <h2>Schema</h2>
129
+ <div class="grid">
130
+ ${schemas.map((m) => `<div class="card">
131
+ <div class="card-title">${escapeHtml(m.name)} <span class="card-meta">${escapeHtml(m.orm)}</span></div>
132
+ ${m.fields.map((f) => `<div class="field">
133
+ <span class="field-name">${escapeHtml(f.name)}</span>
134
+ <span class="field-type">${escapeHtml(f.type)}</span>
135
+ <span class="field-flags">${f.flags.map((fl) => `<span class="flag flag-${escapeHtml(fl)}">${escapeHtml(fl)}</span>`).join("")}</span>
136
+ </div>`).join("\n")}
137
+ ${m.relations.length > 0 ? `<div class="card-meta" style="margin-top:6px">Relations: ${escapeHtml(m.relations.join(", "))}</div>` : ""}
138
+ </div>`).join("\n")}
139
+ </div>
140
+ </div>` : ""}
141
+
142
+ ${components.length > 0 ? `
143
+ <div class="section">
144
+ <h2>Components</h2>
145
+ <div class="grid">
146
+ ${components.map((c) => `<div class="card">
147
+ <div class="card-title">${escapeHtml(c.name)} ${c.isClient ? '<span class="badge-client">client</span>' : ""}${c.isServer ? '<span class="badge-server">server</span>' : ""}</div>
148
+ ${c.props.length > 0 ? `<div class="component-props">props: ${escapeHtml(c.props.join(", "))}</div>` : ""}
149
+ <div class="card-meta">${escapeHtml(c.file)}</div>
150
+ </div>`).join("\n")}
151
+ </div>
152
+ </div>` : ""}
153
+
154
+ ${graph.hotFiles.length > 0 ? `
155
+ <div class="section">
156
+ <h2>Dependency Hot Files</h2>
157
+ <div class="grid">
158
+ ${graph.hotFiles.slice(0, 12).map((h) => {
159
+ const maxImports = graph.hotFiles[0]?.importedBy || 1;
160
+ const pct = Math.round((h.importedBy / maxImports) * 100);
161
+ return `<div class="card">
162
+ <div class="card-title" style="font-size:.9rem">${escapeHtml(h.file)}</div>
163
+ <div class="card-meta">imported by ${h.importedBy} files</div>
164
+ <div class="hot-bar" style="width:${pct}%"></div>
165
+ </div>`;
166
+ }).join("\n")}
167
+ </div>
168
+ </div>` : ""}
169
+
170
+ ${config.envVars.length > 0 ? `
171
+ <div class="section">
172
+ <h2>Environment Variables</h2>
173
+ <table>
174
+ <tr><th>Variable</th><th>Status</th><th>Source</th></tr>
175
+ ${config.envVars.map((e) => `<tr>
176
+ <td><code>${escapeHtml(e.name)}</code></td>
177
+ <td>${e.hasDefault ? '<span class="env-default">has default</span>' : '<span class="env-required">required</span>'}</td>
178
+ <td class="card-meta">${escapeHtml(e.source)}</td>
179
+ </tr>`).join("\n")}
180
+ </table>
181
+ </div>` : ""}
182
+
183
+ ${middleware.length > 0 ? `
184
+ <div class="section">
185
+ <h2>Middleware</h2>
186
+ <div class="grid">
187
+ ${middleware.map((m) => `<div class="card">
188
+ <div class="card-title">${escapeHtml(m.name)} <span class="tag tag-${escapeHtml(m.type)}">${escapeHtml(m.type)}</span></div>
189
+ <div class="card-meta">${escapeHtml(m.file)}</div>
190
+ </div>`).join("\n")}
191
+ </div>
192
+ </div>` : ""}
193
+
194
+ <div class="footer">
195
+ Generated by <a href="https://github.com/Houseofmvps/codesight">codesight</a> — see your codebase clearly
196
+ </div>
197
+
198
+ </body>
199
+ </html>`;
200
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};