@ngommans/codefocus 0.1.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.

Potentially problematic release.


This version of @ngommans/codefocus might be problematic. Click here for more details.

Files changed (40) hide show
  1. package/README.md +124 -0
  2. package/dist/benchmark-43DOYNYR.js +465 -0
  3. package/dist/benchmark-43DOYNYR.js.map +1 -0
  4. package/dist/chunk-6XH2ZLP6.js +127 -0
  5. package/dist/chunk-6XH2ZLP6.js.map +1 -0
  6. package/dist/chunk-7RYHZOYF.js +27 -0
  7. package/dist/chunk-7RYHZOYF.js.map +1 -0
  8. package/dist/chunk-ITVAEU6K.js +250 -0
  9. package/dist/chunk-ITVAEU6K.js.map +1 -0
  10. package/dist/chunk-Q6DOBQ4F.js +231 -0
  11. package/dist/chunk-Q6DOBQ4F.js.map +1 -0
  12. package/dist/chunk-X7DRJUEX.js +543 -0
  13. package/dist/chunk-X7DRJUEX.js.map +1 -0
  14. package/dist/cli.js +111 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/commands-ICBN54MT.js +64 -0
  17. package/dist/commands-ICBN54MT.js.map +1 -0
  18. package/dist/config-OCBWYENF.js +12 -0
  19. package/dist/config-OCBWYENF.js.map +1 -0
  20. package/dist/extended-benchmark-5RUXDG3D.js +323 -0
  21. package/dist/extended-benchmark-5RUXDG3D.js.map +1 -0
  22. package/dist/find-W5UDE4US.js +63 -0
  23. package/dist/find-W5UDE4US.js.map +1 -0
  24. package/dist/graph-DZNBEATA.js +189 -0
  25. package/dist/graph-DZNBEATA.js.map +1 -0
  26. package/dist/map-6WOMDLCP.js +131 -0
  27. package/dist/map-6WOMDLCP.js.map +1 -0
  28. package/dist/mcp-7WYTXIQS.js +354 -0
  29. package/dist/mcp-7WYTXIQS.js.map +1 -0
  30. package/dist/mcp-server.js +369 -0
  31. package/dist/mcp-server.js.map +1 -0
  32. package/dist/query-DJNWYYJD.js +427 -0
  33. package/dist/query-DJNWYYJD.js.map +1 -0
  34. package/dist/query-PS6QVPXP.js +538 -0
  35. package/dist/query-PS6QVPXP.js.map +1 -0
  36. package/dist/root-ODTOXM2J.js +10 -0
  37. package/dist/root-ODTOXM2J.js.map +1 -0
  38. package/dist/watcher-LFBZAM5E.js +73 -0
  39. package/dist/watcher-LFBZAM5E.js.map +1 -0
  40. package/package.json +61 -0
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveRoot
4
+ } from "./chunk-7RYHZOYF.js";
5
+ import {
6
+ IndexDatabase
7
+ } from "./chunk-Q6DOBQ4F.js";
8
+
9
+ // src/commands/graph.ts
10
+ import { createRequire } from "module";
11
+ import { resolve } from "path";
12
+ import { existsSync } from "fs";
13
+ var require2 = createRequire(import.meta.url);
14
+ var { DirectedGraph } = require2("graphology");
15
+ function isFilePath(target) {
16
+ return target.includes("/") || target.includes("\\") || /\.\w+$/.test(target);
17
+ }
18
+ function renderTree(heading, nodes) {
19
+ const lines = [heading];
20
+ for (let i = 0; i < nodes.length; i++) {
21
+ const isLast = i === nodes.length - 1;
22
+ const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
23
+ lines.push(`${prefix}${nodes[i].label}`);
24
+ if (nodes[i].detail) {
25
+ const cont = isLast ? " " : "\u2502 ";
26
+ lines.push(`${cont}${nodes[i].detail}`);
27
+ }
28
+ }
29
+ return lines.join("\n");
30
+ }
31
+ function buildFileGraph(db) {
32
+ const graph = new DirectedGraph();
33
+ for (const file of db.getAllFiles()) {
34
+ graph.addNode(file.path);
35
+ }
36
+ for (const edge of db.getFileImportEdges()) {
37
+ const key = `${edge.source_file}->${edge.target_file}`;
38
+ if (graph.hasEdge(key)) {
39
+ const existing = graph.getEdgeAttributes(key);
40
+ graph.setEdgeAttribute(
41
+ key,
42
+ "specifiers",
43
+ `${existing.specifiers}, ${edge.specifiers}`
44
+ );
45
+ } else {
46
+ graph.addEdgeWithKey(key, edge.source_file, edge.target_file, {
47
+ specifiers: edge.specifiers
48
+ });
49
+ }
50
+ }
51
+ return graph;
52
+ }
53
+ function showFileGraph(db, target, direction) {
54
+ const graph = buildFileGraph(db);
55
+ if (!graph.hasNode(target)) {
56
+ console.error(`Error: file "${target}" not found in the index`);
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+ const sections = [];
61
+ if (direction === "outgoing" || direction === "both") {
62
+ const outEdges = [];
63
+ graph.forEachOutEdge(
64
+ target,
65
+ (_edge, attrs, _src, tgt) => {
66
+ outEdges.push({ label: tgt, detail: `imports: ${attrs.specifiers}` });
67
+ }
68
+ );
69
+ if (outEdges.length > 0) {
70
+ sections.push(renderTree("Dependencies (outgoing):", outEdges));
71
+ } else {
72
+ sections.push("Dependencies (outgoing): (none)");
73
+ }
74
+ }
75
+ if (direction === "incoming" || direction === "both") {
76
+ const inEdges = [];
77
+ graph.forEachInEdge(
78
+ target,
79
+ (_edge, attrs, src) => {
80
+ inEdges.push({ label: src, detail: `imports: ${attrs.specifiers}` });
81
+ }
82
+ );
83
+ if (inEdges.length > 0) {
84
+ sections.push(renderTree("Dependents (incoming):", inEdges));
85
+ } else {
86
+ sections.push("Dependents (incoming): (none)");
87
+ }
88
+ }
89
+ console.log(`
90
+ ${target}
91
+ `);
92
+ console.log(sections.join("\n\n"));
93
+ }
94
+ function showSymbolGraph(db, target, direction) {
95
+ const symbols = db.findSymbolsByName(target);
96
+ const exactMatch = symbols.find((s) => s.name === target);
97
+ const sym = exactMatch ?? symbols[0];
98
+ if (!sym || !sym.id) {
99
+ console.error(`Error: symbol "${target}" not found in the index`);
100
+ process.exitCode = 1;
101
+ return;
102
+ }
103
+ const heading = `${sym.name} (${sym.kind}) \u2014 ${sym.file_path}:${sym.start_line}`;
104
+ const sections = [];
105
+ if (direction === "outgoing" || direction === "both") {
106
+ const refs = db.getOutgoingReferences(sym.id);
107
+ const outNodes = refs.map((r) => ({
108
+ label: `${r.target_name} (${r.target_kind}) \u2014 ${r.target_file}:${r.target_line}`,
109
+ detail: `ref: ${r.ref_type}`
110
+ }));
111
+ if (outNodes.length > 0) {
112
+ sections.push(renderTree("Dependencies (outgoing):", outNodes));
113
+ } else {
114
+ sections.push("Dependencies (outgoing): (none)");
115
+ }
116
+ }
117
+ if (direction === "incoming" || direction === "both") {
118
+ const refs = db.getIncomingReferences(sym.id);
119
+ const inNodes = refs.map((r) => ({
120
+ label: `${r.source_name} (${r.source_kind}) \u2014 ${r.source_file}:${r.source_line}`,
121
+ detail: `ref: ${r.ref_type}`
122
+ }));
123
+ if (inNodes.length > 0) {
124
+ sections.push(renderTree("Dependents (incoming):", inNodes));
125
+ } else {
126
+ sections.push("Dependents (incoming): (none)");
127
+ }
128
+ }
129
+ console.log(`
130
+ ${heading}
131
+ `);
132
+ console.log(sections.join("\n\n"));
133
+ }
134
+ async function runGraph(positional, flags) {
135
+ if (flags.help) {
136
+ console.log(`codefocus graph \u2014 Show dependency graph
137
+
138
+ Usage: codefocus graph <file-or-symbol> [options]
139
+
140
+ Options:
141
+ --direction <dir> Graph direction: both, incoming, outgoing (default: both)
142
+ --root <path> Root directory of indexed project (default: auto-detect)
143
+ --help Show this help message
144
+
145
+ Examples:
146
+ codefocus graph src/cli.ts
147
+ codefocus graph src/cli.ts --direction outgoing
148
+ codefocus graph Calculator --direction incoming`);
149
+ return;
150
+ }
151
+ const target = positional[0];
152
+ if (!target) {
153
+ console.error("Error: graph requires a file path or symbol name");
154
+ process.exitCode = 2;
155
+ return;
156
+ }
157
+ const direction = flags.direction || "both";
158
+ if (!["both", "incoming", "outgoing"].includes(direction)) {
159
+ console.error(
160
+ `Error: invalid direction "${direction}". Use both, incoming, or outgoing`
161
+ );
162
+ process.exitCode = 2;
163
+ return;
164
+ }
165
+ const root = resolveRoot(flags.root);
166
+ const dbPath = resolve(root, ".codefocus", "index.db");
167
+ if (!existsSync(dbPath)) {
168
+ console.error(
169
+ `Error: no index found at ${dbPath}
170
+ Run 'codefocus index --root ${root}' first.`
171
+ );
172
+ process.exitCode = 1;
173
+ return;
174
+ }
175
+ const db = new IndexDatabase(dbPath);
176
+ try {
177
+ if (isFilePath(target)) {
178
+ showFileGraph(db, target, direction);
179
+ } else {
180
+ showSymbolGraph(db, target, direction);
181
+ }
182
+ } finally {
183
+ db.close();
184
+ }
185
+ }
186
+ export {
187
+ runGraph
188
+ };
189
+ //# sourceMappingURL=graph-DZNBEATA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/graph.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { IndexDatabase } from \"../db.js\";\nimport { resolveRoot } from \"../root.js\";\n\nconst require = createRequire(import.meta.url);\nconst { DirectedGraph } = require(\"graphology\");\n\ntype Direction = \"both\" | \"incoming\" | \"outgoing\";\n\n// ── helpers ────────────────────────────────────────────────────────────\n\n/** Determine whether the target string looks like a file path. */\nfunction isFilePath(target: string): boolean {\n return (\n target.includes(\"/\") || target.includes(\"\\\\\") || /\\.\\w+$/.test(target)\n );\n}\n\n// ── tree-rendering helpers ─────────────────────────────────────────────\n\ninterface TreeNode {\n label: string;\n detail?: string;\n}\n\nfunction renderTree(heading: string, nodes: TreeNode[]): string {\n const lines: string[] = [heading];\n for (let i = 0; i < nodes.length; i++) {\n const isLast = i === nodes.length - 1;\n const prefix = isLast ? \"└── \" : \"├── \";\n lines.push(`${prefix}${nodes[i].label}`);\n if (nodes[i].detail) {\n const cont = isLast ? \" \" : \"│ \";\n lines.push(`${cont}${nodes[i].detail}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n// ── file-level graph ───────────────────────────────────────────────────\n\nfunction buildFileGraph(\n db: IndexDatabase,\n): InstanceType<typeof DirectedGraph> {\n const graph = new DirectedGraph();\n\n for (const file of db.getAllFiles()) {\n graph.addNode(file.path);\n }\n\n for (const edge of db.getFileImportEdges()) {\n const key = `${edge.source_file}->${edge.target_file}`;\n if (graph.hasEdge(key)) {\n const existing = graph.getEdgeAttributes(key);\n graph.setEdgeAttribute(\n key,\n \"specifiers\",\n `${existing.specifiers}, ${edge.specifiers}`,\n );\n } else {\n graph.addEdgeWithKey(key, edge.source_file, edge.target_file, {\n specifiers: edge.specifiers,\n });\n }\n }\n\n return graph;\n}\n\nfunction showFileGraph(\n db: IndexDatabase,\n target: string,\n direction: Direction,\n): void {\n const graph = buildFileGraph(db);\n\n if (!graph.hasNode(target)) {\n console.error(`Error: file \"${target}\" not found in the index`);\n process.exitCode = 1;\n return;\n }\n\n const sections: string[] = [];\n\n if (direction === \"outgoing\" || direction === \"both\") {\n const outEdges: TreeNode[] = [];\n graph.forEachOutEdge(\n target,\n (\n _edge: string,\n attrs: { specifiers: string },\n _src: string,\n tgt: string,\n ) => {\n outEdges.push({ label: tgt, detail: `imports: ${attrs.specifiers}` });\n },\n );\n if (outEdges.length > 0) {\n sections.push(renderTree(\"Dependencies (outgoing):\", outEdges));\n } else {\n sections.push(\"Dependencies (outgoing): (none)\");\n }\n }\n\n if (direction === \"incoming\" || direction === \"both\") {\n const inEdges: TreeNode[] = [];\n graph.forEachInEdge(\n target,\n (\n _edge: string,\n attrs: { specifiers: string },\n src: string,\n ) => {\n inEdges.push({ label: src, detail: `imports: ${attrs.specifiers}` });\n },\n );\n if (inEdges.length > 0) {\n sections.push(renderTree(\"Dependents (incoming):\", inEdges));\n } else {\n sections.push(\"Dependents (incoming): (none)\");\n }\n }\n\n console.log(`\\n${target}\\n`);\n console.log(sections.join(\"\\n\\n\"));\n}\n\n// ── symbol-level graph ─────────────────────────────────────────────────\n\nfunction showSymbolGraph(\n db: IndexDatabase,\n target: string,\n direction: Direction,\n): void {\n const symbols = db.findSymbolsByName(target);\n const exactMatch = symbols.find((s) => s.name === target);\n const sym = exactMatch ?? symbols[0];\n\n if (!sym || !sym.id) {\n console.error(`Error: symbol \"${target}\" not found in the index`);\n process.exitCode = 1;\n return;\n }\n\n const heading = `${sym.name} (${sym.kind}) — ${sym.file_path}:${sym.start_line}`;\n const sections: string[] = [];\n\n if (direction === \"outgoing\" || direction === \"both\") {\n const refs = db.getOutgoingReferences(sym.id);\n const outNodes: TreeNode[] = refs.map((r) => ({\n label: `${r.target_name} (${r.target_kind}) — ${r.target_file}:${r.target_line}`,\n detail: `ref: ${r.ref_type}`,\n }));\n if (outNodes.length > 0) {\n sections.push(renderTree(\"Dependencies (outgoing):\", outNodes));\n } else {\n sections.push(\"Dependencies (outgoing): (none)\");\n }\n }\n\n if (direction === \"incoming\" || direction === \"both\") {\n const refs = db.getIncomingReferences(sym.id);\n const inNodes: TreeNode[] = refs.map((r) => ({\n label: `${r.source_name} (${r.source_kind}) — ${r.source_file}:${r.source_line}`,\n detail: `ref: ${r.ref_type}`,\n }));\n if (inNodes.length > 0) {\n sections.push(renderTree(\"Dependents (incoming):\", inNodes));\n } else {\n sections.push(\"Dependents (incoming): (none)\");\n }\n }\n\n console.log(`\\n${heading}\\n`);\n console.log(sections.join(\"\\n\\n\"));\n}\n\n// ── command entry point ────────────────────────────────────────────────\n\nexport async function runGraph(\n positional: string[],\n flags: Record<string, string | boolean>,\n): Promise<void> {\n if (flags.help) {\n console.log(`codefocus graph — Show dependency graph\n\nUsage: codefocus graph <file-or-symbol> [options]\n\nOptions:\n --direction <dir> Graph direction: both, incoming, outgoing (default: both)\n --root <path> Root directory of indexed project (default: auto-detect)\n --help Show this help message\n\nExamples:\n codefocus graph src/cli.ts\n codefocus graph src/cli.ts --direction outgoing\n codefocus graph Calculator --direction incoming`);\n return;\n }\n\n const target = positional[0];\n if (!target) {\n console.error(\"Error: graph requires a file path or symbol name\");\n process.exitCode = 2;\n return;\n }\n\n const direction = (flags.direction as Direction) || \"both\";\n if (![\"both\", \"incoming\", \"outgoing\"].includes(direction)) {\n console.error(\n `Error: invalid direction \"${direction}\". Use both, incoming, or outgoing`,\n );\n process.exitCode = 2;\n return;\n }\n\n const root = resolveRoot(flags.root);\n const dbPath = resolve(root, \".codefocus\", \"index.db\");\n\n if (!existsSync(dbPath)) {\n console.error(\n `Error: no index found at ${dbPath}\\nRun 'codefocus index --root ${root}' first.`,\n );\n process.exitCode = 1;\n return;\n }\n\n const db = new IndexDatabase(dbPath);\n try {\n if (isFilePath(target)) {\n showFileGraph(db, target, direction);\n } else {\n showSymbolGraph(db, target, direction);\n }\n } finally {\n db.close();\n }\n}\n"],"mappings":"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","names":["require"]}
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveRoot
4
+ } from "./chunk-7RYHZOYF.js";
5
+ import {
6
+ IndexDatabase
7
+ } from "./chunk-Q6DOBQ4F.js";
8
+
9
+ // src/commands/map.ts
10
+ import { createRequire } from "module";
11
+ import { resolve } from "path";
12
+ import { existsSync } from "fs";
13
+ var require2 = createRequire(import.meta.url);
14
+ var { DirectedGraph } = require2("graphology");
15
+ var pagerank = require2("graphology-metrics/centrality/pagerank");
16
+ var { getEncoding } = require2("js-tiktoken");
17
+ function rankFilesByPagerank(db) {
18
+ const graph = new DirectedGraph();
19
+ for (const file of db.getAllFiles()) {
20
+ graph.addNode(file.path);
21
+ }
22
+ for (const edge of db.getFileImportEdges()) {
23
+ const key = `${edge.source_file}->${edge.target_file}`;
24
+ if (!graph.hasEdge(key)) {
25
+ graph.addEdgeWithKey(key, edge.source_file, edge.target_file);
26
+ }
27
+ }
28
+ const ranks = graph.order > 0 ? pagerank(graph, { getEdgeWeight: null }) : {};
29
+ return new Map(Object.entries(ranks));
30
+ }
31
+ function renderFileBlock(filePath, db) {
32
+ const symbols = db.getSymbolsByFile(filePath);
33
+ symbols.sort((a, b) => a.start_line - b.start_line);
34
+ const lines = [filePath];
35
+ for (const sym of symbols) {
36
+ if (sym.kind === "variable" && !sym.signature) continue;
37
+ const label = sym.signature ?? `${sym.kind} ${sym.name}`;
38
+ lines.push(` ${label}`);
39
+ }
40
+ return lines.join("\n");
41
+ }
42
+ async function runMap(positional, flags) {
43
+ if (flags.help) {
44
+ console.log(`codefocus map \u2014 High-level codebase overview
45
+
46
+ Usage: codefocus map [options]
47
+
48
+ Options:
49
+ --budget <tokens> Token budget for output (default: 2000)
50
+ --root <path> Root directory of indexed project (default: auto-detect)
51
+ --help Show this help message
52
+
53
+ Examples:
54
+ codefocus map
55
+ codefocus map --budget 4000
56
+ codefocus map --root ./my-project`);
57
+ return;
58
+ }
59
+ const budget = parseInt(String(flags.budget || "2000"), 10);
60
+ if (isNaN(budget) || budget <= 0) {
61
+ console.error("Error: --budget must be a positive integer");
62
+ process.exitCode = 2;
63
+ return;
64
+ }
65
+ const root = resolveRoot(flags.root);
66
+ const dbPath = resolve(root, ".codefocus", "index.db");
67
+ if (!existsSync(dbPath)) {
68
+ console.error(
69
+ `Error: no index found at ${dbPath}
70
+ Run 'codefocus index --root ${root}' first.`
71
+ );
72
+ process.exitCode = 1;
73
+ return;
74
+ }
75
+ const db = new IndexDatabase(dbPath);
76
+ try {
77
+ const files = db.getAllFiles();
78
+ if (files.length === 0) {
79
+ console.log("[map] Index is empty \u2014 no files to map.");
80
+ return;
81
+ }
82
+ const ranks = rankFilesByPagerank(db);
83
+ const rankedFiles = files.map((f) => ({ path: f.path, rank: ranks.get(f.path) ?? 0 })).sort((a, b) => b.rank - a.rank || a.path.localeCompare(b.path));
84
+ const enc = getEncoding("cl100k_base");
85
+ const blocks = [];
86
+ let tokenCount = 0;
87
+ let truncated = false;
88
+ for (const file of rankedFiles) {
89
+ const block = renderFileBlock(file.path, db);
90
+ const blockTokens = enc.encode(block).length;
91
+ if (tokenCount + blockTokens <= budget) {
92
+ blocks.push(block);
93
+ tokenCount += blockTokens;
94
+ continue;
95
+ }
96
+ const blockLines = block.split("\n");
97
+ let partial = blockLines[0];
98
+ let partialTokens = enc.encode(partial).length;
99
+ if (tokenCount + partialTokens > budget) {
100
+ truncated = true;
101
+ break;
102
+ }
103
+ for (let i = 1; i < blockLines.length; i++) {
104
+ const candidate = partial + "\n" + blockLines[i];
105
+ const candidateTokens = enc.encode(candidate).length;
106
+ if (tokenCount + candidateTokens > budget) break;
107
+ partial = candidate;
108
+ partialTokens = candidateTokens;
109
+ }
110
+ blocks.push(partial);
111
+ tokenCount += partialTokens;
112
+ truncated = true;
113
+ break;
114
+ }
115
+ console.log(blocks.join("\n\n"));
116
+ const shown = blocks.length;
117
+ const total = rankedFiles.length;
118
+ const parts = [`[map] ${shown}/${total} files, ~${tokenCount} tokens`];
119
+ if (truncated) {
120
+ parts.push(`(budget: ${budget}, truncated)`);
121
+ }
122
+ console.log(`
123
+ ${parts.join(" ")}`);
124
+ } finally {
125
+ db.close();
126
+ }
127
+ }
128
+ export {
129
+ runMap
130
+ };
131
+ //# sourceMappingURL=map-6WOMDLCP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/map.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { IndexDatabase } from \"../db.js\";\nimport { resolveRoot } from \"../root.js\";\n\nconst require = createRequire(import.meta.url);\nconst { DirectedGraph } = require(\"graphology\");\nconst pagerank = require(\"graphology-metrics/centrality/pagerank\");\nconst { getEncoding } = require(\"js-tiktoken\");\n\n/**\n * Build a directed file-level graph and compute PageRank scores.\n * Returns a map from file path to its PageRank score.\n */\nfunction rankFilesByPagerank(\n db: IndexDatabase,\n): Map<string, number> {\n const graph = new DirectedGraph();\n\n for (const file of db.getAllFiles()) {\n graph.addNode(file.path);\n }\n\n for (const edge of db.getFileImportEdges()) {\n const key = `${edge.source_file}->${edge.target_file}`;\n if (!graph.hasEdge(key)) {\n graph.addEdgeWithKey(key, edge.source_file, edge.target_file);\n }\n }\n\n const ranks: Record<string, number> =\n graph.order > 0 ? pagerank(graph, { getEdgeWeight: null }) : {};\n\n return new Map(Object.entries(ranks));\n}\n\n/**\n * Render a single file's section: file path followed by its symbols.\n * Filters out low-value variable declarations (e.g. `require`, `const`\n * boilerplate) that lack signatures and add noise to the map.\n */\nfunction renderFileBlock(\n filePath: string,\n db: IndexDatabase,\n): string {\n const symbols = db.getSymbolsByFile(filePath);\n symbols.sort((a, b) => a.start_line - b.start_line);\n\n const lines: string[] = [filePath];\n for (const sym of symbols) {\n // Skip variables without signatures — these are typically require()\n // boilerplate, module-level constants, etc. that don't help orientation.\n if (sym.kind === \"variable\" && !sym.signature) continue;\n const label = sym.signature ?? `${sym.kind} ${sym.name}`;\n lines.push(` ${label}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport async function runMap(\n positional: string[],\n flags: Record<string, string | boolean>,\n): Promise<void> {\n if (flags.help) {\n console.log(`codefocus map — High-level codebase overview\n\nUsage: codefocus map [options]\n\nOptions:\n --budget <tokens> Token budget for output (default: 2000)\n --root <path> Root directory of indexed project (default: auto-detect)\n --help Show this help message\n\nExamples:\n codefocus map\n codefocus map --budget 4000\n codefocus map --root ./my-project`);\n return;\n }\n\n const budget = parseInt(String(flags.budget || \"2000\"), 10);\n if (isNaN(budget) || budget <= 0) {\n console.error(\"Error: --budget must be a positive integer\");\n process.exitCode = 2;\n return;\n }\n\n const root = resolveRoot(flags.root);\n const dbPath = resolve(root, \".codefocus\", \"index.db\");\n\n if (!existsSync(dbPath)) {\n console.error(\n `Error: no index found at ${dbPath}\\nRun 'codefocus index --root ${root}' first.`,\n );\n process.exitCode = 1;\n return;\n }\n\n const db = new IndexDatabase(dbPath);\n try {\n const files = db.getAllFiles();\n\n if (files.length === 0) {\n console.log(\"[map] Index is empty — no files to map.\");\n return;\n }\n\n // Rank files by PageRank connectivity\n const ranks = rankFilesByPagerank(db);\n\n const rankedFiles = files\n .map((f) => ({ path: f.path, rank: ranks.get(f.path) ?? 0 }))\n .sort((a, b) => b.rank - a.rank || a.path.localeCompare(b.path));\n\n // Token-budget enforcement via cl100k_base (GPT-4 family tokenizer)\n const enc = getEncoding(\"cl100k_base\");\n const blocks: string[] = [];\n let tokenCount = 0;\n let truncated = false;\n\n for (const file of rankedFiles) {\n const block = renderFileBlock(file.path, db);\n const blockTokens = enc.encode(block).length;\n\n if (tokenCount + blockTokens <= budget) {\n blocks.push(block);\n tokenCount += blockTokens;\n continue;\n }\n\n // Block doesn't fit in full — try trimming symbols to partial fit.\n // Always include at least the file path line.\n const blockLines = block.split(\"\\n\");\n let partial = blockLines[0]; // file path heading\n let partialTokens = enc.encode(partial).length;\n\n if (tokenCount + partialTokens > budget) {\n truncated = true;\n break;\n }\n\n for (let i = 1; i < blockLines.length; i++) {\n const candidate = partial + \"\\n\" + blockLines[i];\n const candidateTokens = enc.encode(candidate).length;\n if (tokenCount + candidateTokens > budget) break;\n partial = candidate;\n partialTokens = candidateTokens;\n }\n\n blocks.push(partial);\n tokenCount += partialTokens;\n truncated = true;\n break;\n }\n\n // Output the map\n console.log(blocks.join(\"\\n\\n\"));\n\n // Summary footer\n const shown = blocks.length;\n const total = rankedFiles.length;\n const parts = [`[map] ${shown}/${total} files, ~${tokenCount} tokens`];\n if (truncated) {\n parts.push(`(budget: ${budget}, truncated)`);\n }\n console.log(`\\n${parts.join(\" \")}`);\n } finally {\n db.close();\n }\n}\n"],"mappings":"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","names":["require"]}