kodingo-cli 1.0.12 → 1.0.13

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.js CHANGED
@@ -13,6 +13,7 @@ const ignore_1 = require("./commands/ignore");
13
13
  const deny_1 = require("./commands/deny");
14
14
  const scan_git_1 = require("./commands/scan-git");
15
15
  const scan_claude_1 = require("./commands/scan-claude");
16
+ const scan_repo_1 = require("./commands/scan-repo");
16
17
  const init_1 = require("./commands/init");
17
18
  const install_hook_1 = require("./commands/install-hook");
18
19
  const update_1 = require("./commands/update");
@@ -214,4 +215,15 @@ program
214
215
  payload.repo = String(options.repo);
215
216
  await (0, scan_claude_1.scanClaudeCommand)(payload);
216
217
  });
218
+ // ── scan-repo ─────────────────────────────────────────────────────────────────
219
+ program
220
+ .command("scan-repo")
221
+ .description("Bulk scan existing codebase and capture memory for functions not yet in Kortex")
222
+ .option("--repo <path>", "repo path to scan")
223
+ .action(async (options) => {
224
+ const payload = {};
225
+ if (options.repo)
226
+ payload.repo = String(options.repo);
227
+ await (0, scan_repo_1.scanRepoCommand)(payload);
228
+ });
217
229
  program.parse(process.argv);
@@ -78,3 +78,6 @@ async function readStdin() {
78
78
  }
79
79
  // review test
80
80
  // review test
81
+ function testKodingoSaveWatcher() {
82
+ return false;
83
+ }
@@ -47,6 +47,7 @@ exports.registerInitCommand = registerInitCommand;
47
47
  const readline = __importStar(require("node:readline"));
48
48
  const persistence_config_1 = require("../utils/persistence-config");
49
49
  const path_1 = __importDefault(require("path"));
50
+ const scan_repo_1 = require("./scan-repo");
50
51
  const fs_1 = __importDefault(require("fs"));
51
52
  function findRepoRoot(startPath) {
52
53
  let current = startPath;
@@ -85,6 +86,18 @@ function ensureGitignoreEntry(repoRoot, entry) {
85
86
  }
86
87
  }
87
88
  // ── Command ───────────────────────────────────────────────────────────────────
89
+ async function runInitialScan(repoRoot) {
90
+ try {
91
+ console.log("\n🔍 Scanning existing codebase for functions without memory...");
92
+ const captured = await (0, scan_repo_1.scanRepoCommand)({ repo: repoRoot, silent: false });
93
+ if (captured === 0) {
94
+ console.log(" No existing functions found — Kortex will capture them as you write and commit code.");
95
+ }
96
+ }
97
+ catch {
98
+ // Never fail init because of a scan error
99
+ }
100
+ }
88
101
  function registerInitCommand(program) {
89
102
  program
90
103
  .command("init")
@@ -117,6 +130,8 @@ function registerInitCommand(program) {
117
130
  const repoRoot = findRepoRoot(process.cwd());
118
131
  ensureGitignoreEntry(repoRoot, ".kortex/");
119
132
  console.log(`✔ Cloud mode configured. Config saved to ${persistence_config_1.CONFIG_PATH}`);
133
+ // Auto-scan existing codebase silently
134
+ await runInitialScan(repoRoot);
120
135
  return;
121
136
  }
122
137
  // ── Interactive flow ──────────────────────────────────────────────────
@@ -161,6 +176,8 @@ function registerInitCommand(program) {
161
176
  console.log(` API URL : ${apiUrl}`);
162
177
  console.log(` Config : ${persistence_config_1.CONFIG_PATH}`);
163
178
  console.log("\nYou're ready to use kodingo with the cloud API.");
179
+ // Auto-scan existing codebase for functions without memory
180
+ await runInitialScan(repoRoot);
164
181
  }
165
182
  catch (err) {
166
183
  console.error(`\n✖ Init failed: ${err.message}`);
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * kodingo scan-repo
4
+ *
5
+ * Bulk scans an entire repository for functions that have no Kortex memory yet.
6
+ * Designed to be run once on an existing codebase after `kodingo init`.
7
+ * Also triggered automatically at the end of `kodingo init` if existing code is found.
8
+ *
9
+ * Walks all supported files, extracts symbols, checks which ones have no memory,
10
+ * and submits them to the inference endpoint in batches.
11
+ *
12
+ * Caps at 50 symbols per run to avoid overwhelming the API.
13
+ * Safe to run multiple times — skips symbols that already have memory.
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.scanRepoCommand = scanRepoCommand;
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const persistence_config_1 = require("../utils/persistence-config");
23
+ const API_BASE = "https://kodingo-api.onrender.com";
24
+ const SUPPORTED_EXTENSIONS = [
25
+ ".ts", ".tsx", ".js", ".jsx",
26
+ ".py", ".go", ".rs", ".php",
27
+ ".java", ".kt", ".rb", ".c", ".cpp",
28
+ ];
29
+ const SYMBOL_PATTERNS = [
30
+ /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
31
+ /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
32
+ /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/,
33
+ /^def\s+(\w+)\s*\(/,
34
+ /^func\s+(\w+)\s*\(/,
35
+ /^(?:pub\s+)?fn\s+(\w+)\s*\(/,
36
+ ];
37
+ const IGNORED_DIRS = [
38
+ "node_modules", ".git", ".kortex", "dist", "build",
39
+ "out", ".next", "coverage", "__pycache__", "vendor",
40
+ ];
41
+ const MAX_SYMBOLS = 50;
42
+ const BATCH_SIZE = 5;
43
+ const BATCH_DELAY_MS = 1200;
44
+ function findRepoRoot(startPath) {
45
+ let current = startPath;
46
+ while (true) {
47
+ if (fs_1.default.existsSync(path_1.default.join(current, ".git")))
48
+ return current;
49
+ const parent = path_1.default.dirname(current);
50
+ if (parent === current)
51
+ return startPath;
52
+ current = parent;
53
+ }
54
+ }
55
+ function walkFiles(dir) {
56
+ const results = [];
57
+ try {
58
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ if (IGNORED_DIRS.includes(entry.name))
61
+ continue;
62
+ const fullPath = path_1.default.join(dir, entry.name);
63
+ if (entry.isDirectory()) {
64
+ results.push(...walkFiles(fullPath));
65
+ }
66
+ else if (entry.isFile() && SUPPORTED_EXTENSIONS.includes(path_1.default.extname(entry.name))) {
67
+ results.push(fullPath);
68
+ }
69
+ }
70
+ }
71
+ catch { }
72
+ return results;
73
+ }
74
+ function extractSymbols(filePath) {
75
+ try {
76
+ const text = fs_1.default.readFileSync(filePath, "utf-8");
77
+ const lines = text.split("\n");
78
+ const entries = [];
79
+ for (let i = 0; i < lines.length; i++) {
80
+ const trimmed = lines[i].trim();
81
+ for (const pat of SYMBOL_PATTERNS) {
82
+ const m = trimmed.match(pat);
83
+ if (m?.[1]) {
84
+ entries.push({
85
+ name: m[1],
86
+ code: lines.slice(i, i + 40).join("\n"),
87
+ file: filePath,
88
+ });
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ return entries;
94
+ }
95
+ catch {
96
+ return [];
97
+ }
98
+ }
99
+ async function hasExistingMemory(token, symbol, repo) {
100
+ try {
101
+ const res = await fetch(`${API_BASE}/memory?symbol=${encodeURIComponent(symbol)}&repo=${encodeURIComponent(repo)}&limit=1`, { headers: { "X-Kodingo-Token": token } });
102
+ if (!res.ok)
103
+ return false;
104
+ const data = await res.json();
105
+ return (data.total ?? 0) > 0;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ async function inferAndCapture(token, symbol, repo) {
112
+ try {
113
+ const inferRes = await fetch(`${API_BASE}/infer`, {
114
+ method: "POST",
115
+ headers: { "Content-Type": "application/json", "X-Kodingo-Token": token },
116
+ body: JSON.stringify({ symbol: symbol.name, code: symbol.code }),
117
+ });
118
+ if (!inferRes.ok)
119
+ return false;
120
+ const inferred = await inferRes.json();
121
+ const saveRes = await fetch(`${API_BASE}/memory`, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/json", "X-Kodingo-Token": token },
124
+ body: JSON.stringify({
125
+ type: inferred.type ?? "context",
126
+ title: inferred.title ?? `${symbol.name} — scanned`,
127
+ content: inferred.content ?? `Function \`${symbol.name}\` detected during repo scan.`,
128
+ symbol: symbol.name,
129
+ repo,
130
+ tags: [...(inferred.tags ?? []), "scan-repo"],
131
+ status: "proposed",
132
+ confidence: 0.35,
133
+ }),
134
+ });
135
+ return saveRes.ok;
136
+ }
137
+ catch {
138
+ return false;
139
+ }
140
+ }
141
+ async function scanRepoCommand(options = {}) {
142
+ const config = (0, persistence_config_1.readConfig)();
143
+ if (config.mode !== "cloud" || !config.token) {
144
+ if (!options.silent)
145
+ console.error("✖ scan-repo requires cloud mode. Run `kodingo init` first.");
146
+ return 0;
147
+ }
148
+ const repoRoot = options.repo
149
+ ? path_1.default.resolve(options.repo)
150
+ : findRepoRoot(process.cwd());
151
+ const repoName = path_1.default.basename(repoRoot);
152
+ if (!options.silent) {
153
+ console.log(`\n🔍 Kortex — scanning ${repoName} for existing functions...\n`);
154
+ }
155
+ // Walk all files and extract symbols
156
+ const files = walkFiles(repoRoot);
157
+ const allSymbols = [];
158
+ for (const file of files) {
159
+ allSymbols.push(...extractSymbols(file));
160
+ }
161
+ if (allSymbols.length === 0) {
162
+ if (!options.silent)
163
+ console.log(" No functions found — repo appears empty. Nothing to scan.");
164
+ return 0;
165
+ }
166
+ if (!options.silent) {
167
+ console.log(` Found ${allSymbols.length} functions across ${files.length} files`);
168
+ console.log(` Checking which ones need memory...\n`);
169
+ }
170
+ // Filter to symbols without existing memory, cap at MAX_SYMBOLS
171
+ const toCapture = [];
172
+ for (const sym of allSymbols) {
173
+ if (toCapture.length >= MAX_SYMBOLS)
174
+ break;
175
+ const exists = await hasExistingMemory(config.token, sym.name, repoName);
176
+ if (!exists)
177
+ toCapture.push(sym);
178
+ }
179
+ if (toCapture.length === 0) {
180
+ if (!options.silent)
181
+ console.log(" ✔ All functions already have Kortex memory. Nothing to capture.");
182
+ return 0;
183
+ }
184
+ if (!options.silent) {
185
+ console.log(` Capturing memory for ${toCapture.length} functions...\n`);
186
+ }
187
+ // Process in batches
188
+ let captured = 0;
189
+ for (let i = 0; i < toCapture.length; i += BATCH_SIZE) {
190
+ const batch = toCapture.slice(i, i + BATCH_SIZE);
191
+ const results = await Promise.all(batch.map(sym => inferAndCapture(config.token, sym, repoName)));
192
+ const batchCaptured = results.filter(Boolean).length;
193
+ captured += batchCaptured;
194
+ if (!options.silent) {
195
+ const progress = Math.min(i + BATCH_SIZE, toCapture.length);
196
+ process.stdout.write(`\r Progress: ${progress}/${toCapture.length} functions processed`);
197
+ }
198
+ if (i + BATCH_SIZE < toCapture.length) {
199
+ await new Promise(r => setTimeout(r, BATCH_DELAY_MS));
200
+ }
201
+ }
202
+ if (!options.silent) {
203
+ console.log(`\n\n✔ Scan complete — ${captured} memories captured for ${repoName}`);
204
+ if (allSymbols.length > MAX_SYMBOLS) {
205
+ console.log(` Note: ${allSymbols.length - MAX_SYMBOLS} additional functions will be captured automatically as you open files.`);
206
+ }
207
+ }
208
+ return captured;
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,