scai 0.1.112 → 0.1.114

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/CHANGELOG.md CHANGED
@@ -189,4 +189,8 @@ Type handling with the module pipeline
189
189
  4. Updated Edge/Table schema for better query performance.
190
190
  5. Update package-lock.json to caniuse-lite@1.0.30001741.
191
191
  6. Enable execution of as an executable file in the scripts.
192
- 7. Remove context failure if models not installed. Add ability to set global model.
192
+ 7. Remove context failure if models not installed. Add ability to set global model.
193
+
194
+ ## 2025-09-13
195
+
196
+ • Improve robustness of context update logic
package/dist/config.js CHANGED
@@ -151,7 +151,7 @@ export const Config = {
151
151
  ? chalk.green(`✅ ${key} (active)`)
152
152
  : chalk.white(` ${key}`);
153
153
  console.log(`- ${label}`);
154
- console.log(` ↳ indexDir: ${r.indexDir}`);
154
+ console.log(` ↳ indexDir: ${isActive ? chalk.yellow(r.indexDir) : r.indexDir}`);
155
155
  }
156
156
  },
157
157
  getGitHubToken() {
package/dist/context.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // context.ts
2
2
  import { readConfig, writeConfig } from "./config.js";
3
3
  import { normalizePath } from "./utils/contentUtils.js";
4
- import { getHashedRepoKey } from "./utils/repoKey.js";
5
4
  import { getDbForRepo, getDbPathForRepo } from "./db/client.js";
6
5
  import fs from "fs";
7
6
  import chalk from "chalk";
@@ -23,32 +22,39 @@ function modelExists(model) {
23
22
  export async function updateContext() {
24
23
  const cwd = normalizePath(process.cwd());
25
24
  const cfg = readConfig();
25
+ // 🔎 Look up existing repo by indexDir
26
26
  let repoKey = Object.keys(cfg.repos || {}).find((key) => normalizePath(cfg.repos[key]?.indexDir || "") === cwd);
27
- let isNewRepo = false;
28
27
  if (!repoKey) {
29
- repoKey = getHashedRepoKey(cwd);
30
- if (!cfg.repos[repoKey])
31
- cfg.repos[repoKey] = {};
32
- cfg.repos[repoKey].indexDir = cwd;
33
- isNewRepo = true;
28
+ const err = new Error();
29
+ err.message =
30
+ chalk.red("❌ Current directory is not in your list of indexed repos. See the 'scai index list' command.") +
31
+ "\n" +
32
+ chalk.blueBright(" ↳ To get the full benefit of an indexed repo, navigate to your repository root folder.") +
33
+ "\n\n" +
34
+ chalk.greenBright(" Run ") +
35
+ chalk.cyan("scai index set") +
36
+ chalk.greenBright(" command. This enables Scai to index the repo, find git credentials etc.\n");
37
+ throw err;
34
38
  }
35
39
  const activeRepoChanged = cfg.activeRepo !== repoKey;
36
40
  cfg.activeRepo = repoKey;
37
41
  writeConfig(cfg);
38
42
  const repoCfg = cfg.repos[repoKey];
39
43
  let ok = true;
40
- if (isNewRepo || activeRepoChanged) {
44
+ if (activeRepoChanged) {
41
45
  console.log(chalk.yellow("\n🔁 Updating context...\n"));
42
46
  console.log(`✅ Active repo: ${chalk.green(repoKey)}`);
43
47
  console.log(`✅ Index dir: ${chalk.cyan(repoCfg.indexDir || cwd)}`);
44
48
  }
49
+ // 🔑 Token check
45
50
  const token = repoCfg.githubToken || cfg.githubToken;
46
51
  if (!token) {
47
52
  console.log(`ℹ️ No GitHub token found. You can set one with: ${chalk.bold(chalk.bgGreen("scai auth set"))}`);
48
53
  }
49
- else if (isNewRepo || activeRepoChanged) {
54
+ else if (activeRepoChanged) {
50
55
  console.log(`✅ GitHub token present`);
51
56
  }
57
+ // 💾 DB check
52
58
  const dbPath = getDbPathForRepo();
53
59
  if (!fs.existsSync(dbPath)) {
54
60
  console.log(chalk.yellow(`📦 Initializing DB at ${dbPath}`));
@@ -59,7 +65,7 @@ export async function updateContext() {
59
65
  ok = false;
60
66
  }
61
67
  }
62
- else if (isNewRepo || activeRepoChanged) {
68
+ else if (activeRepoChanged) {
63
69
  console.log(chalk.green("✅ Database present"));
64
70
  }
65
71
  // 🧠 Model check
package/dist/index.js CHANGED
@@ -232,10 +232,10 @@ index
232
232
  index
233
233
  .command('set [dir]')
234
234
  .description('Set and activate index directory')
235
- .action(async (dir = process.cwd()) => await withContext(async () => {
236
- Config.setIndexDir(dir);
235
+ .action(async (dir = process.cwd()) => {
236
+ await Config.setIndexDir(dir);
237
237
  Config.show();
238
- }));
238
+ });
239
239
  index
240
240
  .command('list')
241
241
  .description('List all indexed repositories')
@@ -336,9 +336,13 @@ cmd.addHelpText('after', `
336
336
 
337
337
  💡 Use with caution and expect possible changes or instability.
338
338
  `);
339
- cmd.parse(process.argv);
340
339
  async function withContext(action) {
341
340
  const ok = await updateContext();
342
341
  //if (!ok) process.exit(1);
343
342
  await action();
344
343
  }
344
+ // 👇 this should be the very last line
345
+ cmd.parseAsync(process.argv).catch((err) => {
346
+ console.error(err.message); // only show our styled message
347
+ process.exit(1);
348
+ });
@@ -6,9 +6,6 @@ export async function generate(input) {
6
6
  const model = Config.getModel();
7
7
  const contextLength = readConfig().contextLength ?? 8192;
8
8
  let prompt = input.content;
9
- if (prompt.length > contextLength) {
10
- console.warn(`⚠️ Warning: Input prompt length (${prompt.length}) exceeds model context length (${contextLength}).`);
11
- }
12
9
  const spinner = new Spinner(`🧠 Thinking with ${model}...`);
13
10
  spinner.start();
14
11
  try {
@@ -6,13 +6,14 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
6
6
  const log = (...args) => console.log("[buildContextualPrompt]", ...args);
7
7
  const promptSections = [];
8
8
  const seenPaths = new Set();
9
- function summarizeForPrompt(summary, maxLines = 5) {
9
+ // ✂️ Word-based summarizer with progressive shortening
10
+ function summarizeForPrompt(summary, maxWords = 30) {
10
11
  if (!summary)
11
12
  return undefined;
12
- const lines = summary.split("\n").map(l => l.trim()).filter(Boolean);
13
- if (lines.length <= maxLines)
14
- return lines.join(" ");
15
- return lines.slice(0, maxLines).join(" ") + " …";
13
+ const words = summary.split(/\s+/);
14
+ if (words.length <= maxWords)
15
+ return summary.trim();
16
+ return words.slice(0, maxWords).join(" ") + " …";
16
17
  }
17
18
  // --- Step 1: Top file summary ---
18
19
  if (topFile.summary) {
@@ -62,21 +63,27 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
62
63
  }
63
64
  function buildFileTree(file, depth, visited = new Set()) {
64
65
  log(`buildFileTree - file=${file.path}, depth=${depth}`);
65
- if (depth === 0 || visited.has(file.id)) {
66
- return { id: file.id.toString(), path: file.path, summary: summarizeForPrompt(file.summary) };
66
+ if (visited.has(file.id)) {
67
+ return { id: file.id.toString(), path: file.path };
67
68
  }
68
69
  visited.add(file.id);
69
- const relatedFiles = getRelatedKGFiles(file.id, visited)
70
- .map(f => ({ id: f.id, path: f.path, summary: f.summary }))
71
- .slice(0, 5); // limit max 5 children per node
72
- log(`File ${file.path} has ${relatedFiles.length} related files`);
73
- const relatedNodes = relatedFiles.map(f => buildFileTree(f, depth - 1, visited));
74
- return {
70
+ // progressively shorten summaries, drop at depth <= 1
71
+ const maxWordsByDepth = depth >= 3 ? 30 : depth === 2 ? 15 : 0;
72
+ const node = {
75
73
  id: file.id.toString(),
76
74
  path: file.path,
77
- summary: summarizeForPrompt(file.summary),
78
- related: relatedNodes.length ? relatedNodes : undefined,
75
+ summary: maxWordsByDepth > 0 ? summarizeForPrompt(file.summary, maxWordsByDepth) : undefined,
79
76
  };
77
+ if (depth > 1) {
78
+ const relatedFiles = getRelatedKGFiles(file.id, visited)
79
+ .map(f => ({ id: f.id, path: f.path, summary: f.summary }))
80
+ .slice(0, 5); // cap children
81
+ log(`File ${file.path} has ${relatedFiles.length} related files`);
82
+ const relatedNodes = relatedFiles.map(f => buildFileTree(f, depth - 1, visited));
83
+ if (relatedNodes.length)
84
+ node.related = relatedNodes;
85
+ }
86
+ return node;
80
87
  }
81
88
  const kgTree = buildFileTree({ id: topFile.id, path: topFile.path, summary: topFile.summary }, kgDepth);
82
89
  const kgJson = JSON.stringify(kgTree, null, 2);
@@ -93,8 +100,10 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
93
100
  console.warn("⚠️ Could not generate file tree:", e);
94
101
  }
95
102
  // --- Step 5: Code snippet ---
103
+ // Only include raw code if no summary exists, or if the query explicitly asks for it
96
104
  const MAX_LINES = 50;
97
- if (topFile.code) {
105
+ const queryNeedsCode = /\b(code|implementation|function|snippet)\b/i.test(query);
106
+ if ((!topFile.summary || queryNeedsCode) && topFile.code) {
98
107
  const lines = topFile.code.split("\n").slice(0, MAX_LINES);
99
108
  let snippet = lines.join("\n");
100
109
  if (topFile.code.split("\n").length > MAX_LINES) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.112",
3
+ "version": "0.1.114",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"