Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/autocomplete/context/ContextRetrievalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export class ContextRetrievalService {
const { imports } = fileInfo;
// Look for imports of any symbols around the current range
const textAroundCursor =
helper.fullPrefix.split("\n").slice(-5).join("\n") +
helper.fullSuffix.split("\n").slice(0, 3).join("\n");
helper.fullPrefixLines.slice(-5).join("\n") +
helper.fullSuffixLines.slice(0, 3).join("\n");
const symbols = Array.from(getSymbolsForSnippet(textAroundCursor)).filter(
(symbol) => !helper.lang.topLevelKeywords.includes(symbol),
);
Expand Down
35 changes: 22 additions & 13 deletions core/autocomplete/context/ImportDefinitionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ export class ImportDefinitionsService {
);

constructor(private readonly ide: IDE) {
ide.onDidChangeActiveTextEditor((filepath) => {
this.cache
.initKey(filepath)
.catch((e) =>
console.warn(
`Failed to initialize ImportDefinitionService: ${e.message}`,
),
);
});
console.log("new import definitions service");
// ide.onDidChangeActiveTextEditor((filepath) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting out the onDidChangeActiveTextEditor handler stops warming this cache when the user switches files, so import definition lookups now return undefined instead of the cached definitions.

Prompt for AI agents
Address the following comment on core/autocomplete/context/ImportDefinitionsService.ts at line 26:

<comment>Commenting out the onDidChangeActiveTextEditor handler stops warming this cache when the user switches files, so import definition lookups now return undefined instead of the cached definitions.</comment>

<file context>
@@ -21,15 +22,16 @@ export class ImportDefinitionsService {
-        );
-    });
+    console.log(&quot;new import definitions service&quot;);
+    //  ide.onDidChangeActiveTextEditor((filepath) =&gt; {
+    //   this.cache
+    //     .initKey(filepath)
</file context>
Fix with Cubic

// this.cache
// .initKey(filepath)
// .catch((e) =>
// console.warn(
// `Failed to initialize ImportDefinitionService: ${e.message}`,
// ),
// );
// });
}

get(filepath: string): FileInfo | undefined {
Expand Down Expand Up @@ -58,6 +59,9 @@ export class ImportDefinitionsService {
if (!foundInDir) {
return null;
} else {
console.log(
`read file - Import definition service _getFileInfo - ${filepath}`,
);
fileContents = await this.ide.readFile(filepath);
}
} catch (err) {
Expand Down Expand Up @@ -101,10 +105,15 @@ export class ImportDefinitionsService {
},
});
fileInfo.imports[match.captures[0].node.text] = await Promise.all(
defs.map(async (def) => ({
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
})),
defs.map(async (def) => {
console.log(
`read file - ImportDefinitionsService readRangeInFile - ${def.filepath}`,
);
return {
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
};
}),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,15 @@ export class RootPathContextService {

return !isIgnoredPath;
})
.map(async (def) => ({
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
})),
.map(async (def) => {
console.log(
`read file - RootPathContextService readRangeInFile - ${def.filepath}`,
);
return {
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
};
}),
);

return newSnippets;
Expand Down
80 changes: 0 additions & 80 deletions core/autocomplete/context/static-context/tree-sitter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,86 +78,6 @@ export async function extractTopLevelDecls(
return query.matches(ast.rootNode);
}

export async function extractTopLevelDeclsWithFormatting(
currentFile: string,
givenParser?: Parser,
) {
const ast = await getAst(currentFile, await fs.readFile(currentFile, "utf8"));
if (!ast) {
throw new Error(`failed to get ast for file ${currentFile}`);
}
let language;
if (givenParser) {
language = givenParser.getLanguage();
} else {
language = getFullLanguageName(currentFile);
}

const query = await getQueryForFile(
currentFile,
`static-context-queries/relevant-headers-queries/${language}-get-toplevel-headers.scm`,
);
if (!query) {
throw new Error(
`failed to get query for file ${currentFile} and language ${language}`,
);
}
const matches = query.matches(ast.rootNode);

const results = [];

for (const match of matches) {
const item: {
declaration: string;
nodeType: string;
name: string;
declaredType: string;
returnType?: string;
} = {
declaration: "",
nodeType: "",
name: "",
declaredType: "",
};

for (const { name, node } of match.captures) {
if (name === "top.var.decl") {
item.nodeType = "variable";
item.declaration = node.text;

// Attempt to get the declared type (e.g., const x: string = ...)
const typeNode = node.descendantsOfType("type_annotation")[0];
if (typeNode) {
item.declaredType = typeNode.text.replace(/^:\s*/, "");
}
} else if (name === "top.var.name" || name === "top.fn.name") {
item.name = node.text;
} else if (name === "top.fn.decl") {
item.nodeType = "function";
item.declaration = node.text;

// Get the return type (e.g., function foo(): string)
const returnTypeNode = node.childForFieldName("return_type");
if (returnTypeNode) {
item.returnType = returnTypeNode.text.replace(/^:\s*/, "");
}

// Get declaredType if needed (TypeScript style)
const nameNode = node.childForFieldName("name");
if (nameNode && nameNode.nextSibling?.type === "type_annotation") {
item.declaredType = nameNode.nextSibling.text.replace(/^:\s*/, "");
}
}
}

if (item.name && item.declaration) {
results.push(item);
}
}

return results;
}

export function extractFunctionTypeFromDecl(match: Parser.QueryMatch): string {
let paramsNode: Parser.SyntaxNode | undefined = undefined;
let returnNode: Parser.SyntaxNode | undefined = undefined;
Expand Down
3 changes: 3 additions & 0 deletions core/autocomplete/snippets/getAllSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
// Create a promise that resolves to a snippet or null
const readPromise = new Promise<AutocompleteCodeSnippet | null>(
(resolve) => {
console.log(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This debug console.log runs on every file read from the recently opened cache, so autocomplete will emit a log per file on each invocation; this adds synchronous logging overhead and floods the console, undermining the performance improvements you're targeting. Please drop or gate the log before merging.

Prompt for AI agents
Address the following comment on core/autocomplete/snippets/getAllSnippets.ts at line 131:

<comment>This debug console.log runs on every file read from the recently opened cache, so autocomplete will emit a log per file on each invocation; this adds synchronous logging overhead and floods the console, undermining the performance improvements you&#39;re targeting. Please drop or gate the log before merging.</comment>

<file context>
@@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
       // Create a promise that resolves to a snippet or null
       const readPromise = new Promise&lt;AutocompleteCodeSnippet | null&gt;(
         (resolve) =&gt; {
+          console.log(
+            `read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
+          );
</file context>
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new console.log will fire for every recently opened file read, spamming the console and adding overhead in production usages. Please remove it or gate it behind the existing logging/instrumentation mechanism.

Prompt for AI agents
Address the following comment on core/autocomplete/snippets/getAllSnippets.ts at line 131:

<comment>This new console.log will fire for every recently opened file read, spamming the console and adding overhead in production usages. Please remove it or gate it behind the existing logging/instrumentation mechanism.</comment>

<file context>
@@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
       // Create a promise that resolves to a snippet or null
       const readPromise = new Promise&lt;AutocompleteCodeSnippet | null&gt;(
         (resolve) =&gt; {
+          console.log(
+            `read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
+          );
</file context>
Fix with Cubic

`read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
);
ide
.readFile(fileUri)
.then((fileContent) => {
Expand Down
51 changes: 30 additions & 21 deletions core/autocomplete/templating/constructPrefixSuffix.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IDE } from "../..";
import { getRangeInString } from "../../util/ranges";
import { languageForFilepath } from "../constants/AutocompleteLanguageInfo";
import { AutocompleteInput } from "../util/types";
Expand All @@ -9,35 +8,45 @@ import { AutocompleteInput } from "../util/types";
*/
export async function constructInitialPrefixSuffix(
input: AutocompleteInput,
ide: IDE,
lines: string[],
): Promise<{
prefix: string;
suffix: string;
prefixLines: string[];
suffixLines: string[];
}> {
const lang = languageForFilepath(input.filepath);

const fileContents =
input.manuallyPassFileContents ?? (await ide.readFile(input.filepath));
const fileLines = fileContents.split("\n");
let prefix =
getRangeInString(fileContents, {
start: { line: 0, character: 0 },
end: input.selectedCompletionInfo?.range.start ?? input.pos,
}) + (input.selectedCompletionInfo?.text ?? "");
const prefixLines = getRangeInString(lines, {
start: { line: 0, character: 0 },
end: input.selectedCompletionInfo?.range.start ?? input.pos,
});
const selectedCompletionInfoText = input.selectedCompletionInfo?.text ?? "";
const selectedCompletionInfoLines = selectedCompletionInfoText.split("\n");
let i = 0;
for (const line of selectedCompletionInfoLines) {
if (i === 0) {
prefixLines[prefixLines.length - 1] =
prefixLines[prefixLines.length - 1] + line;
} else {
prefixLines.push(line);
}
i++;
}

let prefix: string;
if (input.injectDetails) {
const lines = prefix.split("\n");
prefix = `${lines.slice(0, -1).join("\n")}\n${
lang.singleLineComment
} ${input.injectDetails
const lastLine = prefixLines.pop();
const detailsLines = input.injectDetails
.split("\n")
.join(`\n${lang.singleLineComment} `)}\n${lines[lines.length - 1]}`;
.map((line) => `${lang.singleLineComment} ${line}`);
prefixLines.push(...detailsLines);
if (lastLine !== undefined) {
prefixLines.push(lastLine);
}
}

const suffix = getRangeInString(fileContents, {
const suffixLines = getRangeInString(lines, {
start: input.pos,
end: { line: fileLines.length - 1, character: Number.MAX_SAFE_INTEGER },
end: { line: lines.length - 1, character: Number.MAX_SAFE_INTEGER },
});

return { prefix, suffix };
return { prefixLines, suffixLines };
}
60 changes: 44 additions & 16 deletions core/autocomplete/util/HelperVars.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { IDE, TabAutocompleteOptions } from "../..";
import {
countTokens,
pruneLinesFromBottom,
pruneLinesFromTop,
} from "../../llm/countTokens";
import { pruneFromBottom, pruneFromTop } from "../../llm/countTokens";
import {
AutocompleteLanguageInfo,
languageForFilepath,
Expand All @@ -26,6 +22,8 @@ export class HelperVars {
private _fileLines: string[] | undefined;
private _fullPrefix: string | undefined;
private _fullSuffix: string | undefined;
private _fullSuffixLines: string[] | undefined;
private _fullPrefixLines: string[] | undefined;
private _prunedPrefix: string | undefined;
private _prunedSuffix: string | undefined;

Expand All @@ -45,21 +43,32 @@ export class HelperVars {
}
this.workspaceUris = await this.ide.getWorkspaceDirs();

console.log(`read file - HelperVars init - ${this.filepath}`);
this._fileContents =
this.input.manuallyPassFileContents ??
(await this.ide.readFile(this.filepath));

this._fileLines = this._fileContents.split("\n");

// Construct full prefix/suffix (a few edge cases handled in here)
const { prefix: fullPrefix, suffix: fullSuffix } =
await constructInitialPrefixSuffix(this.input, this.ide);
const { prefixLines, suffixLines } = await constructInitialPrefixSuffix(
this.input,
this._fileLines,
);
this._fullPrefixLines = prefixLines;
const fullPrefix = prefixLines.join("\n");
this._fullPrefix = fullPrefix;

this._fullSuffixLines = suffixLines;
const fullSuffix = suffixLines.join("\n");
this._fullSuffix = fullSuffix;

const { prunedPrefix, prunedSuffix } = this.prunePrefixSuffix();
this._prunedPrefix = prunedPrefix;
this._prunedSuffix = prunedSuffix;
const { prunedPrefix, prunedSuffix } = this.prunePrefixSuffix(
prefixLines,
suffixLines,
);
this._prunedPrefix = prunedPrefix.join("\n");
this._prunedSuffix = prunedSuffix.join("\n");

try {
const ast = await getAst(this.filepath, fullPrefix + fullSuffix);
Expand All @@ -82,23 +91,24 @@ export class HelperVars {
return instance;
}

prunePrefixSuffix() {
prunePrefixSuffix(prefixLines: string[], suffixLines: string[]) {
// Construct basic prefix
const maxPrefixTokens =
this.options.maxPromptTokens * this.options.prefixPercentage;
const prunedPrefix = pruneLinesFromTop(
this.fullPrefix,

const { pruned: prunedPrefix, totalTokens: prefixTokens } = pruneFromTop(
prefixLines,
maxPrefixTokens,
this.modelName,
);

// Construct suffix
const maxSuffixTokens = Math.min(
this.options.maxPromptTokens - countTokens(prunedPrefix, this.modelName),
this.options.maxPromptTokens - prefixTokens,
this.options.maxSuffixPercentage * this.options.maxPromptTokens,
);
const prunedSuffix = pruneLinesFromBottom(
this.fullSuffix,
const { pruned: prunedSuffix } = pruneFromBottom(
suffixLines,
maxSuffixTokens,
this.modelName,
);
Expand Down Expand Up @@ -131,6 +141,24 @@ export class HelperVars {
return this._fileContents;
}

get fullSuffixLines(): string[] {
if (this._fullSuffixLines === undefined) {
throw new Error(
"HelperVars must be initialized before accessing fullSuffixLines",
);
}
return this.fullSuffixLines;
}

get fullPrefixLines(): string[] {
if (this._fullPrefixLines === undefined) {
throw new Error(
"HelperVars must be initialized before accessing fullPrefixLines",
);
}
return this._fullPrefixLines;
}

get fileLines(): string[] {
if (this._fileLines === undefined) {
throw new Error(
Expand Down
3 changes: 0 additions & 3 deletions core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,9 +746,6 @@ declare global {
getSignatureHelp(location: Location): Promise<SignatureHelp | null>;
getReferences(location: Location): Promise<RangeInFile[]>;
getDocumentSymbols(textDocumentIdentifier: string): Promise<DocumentSymbol[]>;

// Callbacks
onDidChangeActiveTextEditor(callback: (filepath: string) => void): void;
}

// Slash Commands
Expand Down
6 changes: 3 additions & 3 deletions core/context/retrieval/pipelines/BaseRetrievalPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ export default class BaseRetrievalPipeline implements IRetrievalPipeline {
return;
}

this.lanceDbIndex = await LanceDbIndex.create(embedModel, (uri) =>
this.options.ide.readFile(uri),
);
this.lanceDbIndex = await LanceDbIndex.create(embedModel, (uri) => {
return this.options.ide.readFile(uri);
});
}

protected async ensureLanceDbInitialized(): Promise<boolean> {
Expand Down
Loading
Loading