mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
fix: Fix issue with ambiguous references (#393)
This commit is contained in:
parent
da8d49f8d9
commit
dbd8ef7fdb
10 changed files with 191 additions and 136 deletions
|
|
@ -200,19 +200,19 @@ When you have sufficient context, output your answer as a structured markdown re
|
|||
**Required Response Format:**
|
||||
- **CRITICAL**: You MUST always prefix your answer with a \`${ANSWER_TAG}\` tag at the very top of your response
|
||||
- **CRITICAL**: You MUST provide your complete response in markdown format with embedded code references
|
||||
- **CODE REFERENCE REQUIREMENT**: Whenever you mention, discuss, or refer to ANY specific part of the code (files, functions, variables, methods, classes, imports, etc.), you MUST immediately follow with a code reference using the format \`${fileReferenceToString({ fileName: 'filename'})}\` or \`${fileReferenceToString({ fileName: 'filename', range: { startLine: 1, endLine: 10 } })}\` (where the numbers are the start and end line numbers of the code snippet). This includes:
|
||||
- Files (e.g., "The \`auth.ts\` file" → must include \`${fileReferenceToString({ fileName: 'auth.ts' })}\`)
|
||||
- Function names (e.g., "The \`getRepos()\` function" → must include \`${fileReferenceToString({ fileName: 'auth.ts', range: { startLine: 15, endLine: 20 } })}\`)
|
||||
- Variable names (e.g., "The \`suggestionQuery\` variable" → must include \`${fileReferenceToString({ fileName: 'search.ts', range: { startLine: 42, endLine: 42 } })}\`)
|
||||
- Code patterns (e.g., "using \`file:\${suggestionQuery}\` pattern" → must include \`${fileReferenceToString({ fileName: 'search.ts', range: { startLine: 10, endLine: 15 } })}\`)
|
||||
- **CODE REFERENCE REQUIREMENT**: Whenever you mention, discuss, or refer to ANY specific part of the code (files, functions, variables, methods, classes, imports, etc.), you MUST immediately follow with a code reference using the format \`${fileReferenceToString({ repo: 'repository', path: 'filename'})}\` or \`${fileReferenceToString({ repo: 'repository', path: 'filename', range: { startLine: 1, endLine: 10 } })}\` (where the numbers are the start and end line numbers of the code snippet). This includes:
|
||||
- Files (e.g., "The \`auth.ts\` file" → must include \`${fileReferenceToString({ repo: 'repository', path: 'auth.ts' })}\`)
|
||||
- Function names (e.g., "The \`getRepos()\` function" → must include \`${fileReferenceToString({ repo: 'repository', path: 'auth.ts', range: { startLine: 15, endLine: 20 } })}\`)
|
||||
- Variable names (e.g., "The \`suggestionQuery\` variable" → must include \`${fileReferenceToString({ repo: 'repository', path: 'search.ts', range: { startLine: 42, endLine: 42 } })}\`)
|
||||
- Any code snippet or line you're explaining
|
||||
- Class names, method calls, imports, etc.
|
||||
- Some examples of both correct and incorrect code references:
|
||||
- Correct: @file:{path/to/file.ts}
|
||||
- Correct: @file:{path/to/file.ts:10-15}
|
||||
- Incorrect: @file{path/to/file.ts} (missing colon)
|
||||
- Incorrect: @file:path/to/file.ts (missing curly braces)
|
||||
- Incorrect: @file:{path/to/file.ts:10-25,30-35} (multiple ranges not supported)
|
||||
- Correct: @file:{repository::path/to/file.ts}
|
||||
- Correct: @file:{repository::path/to/file.ts:10-15}
|
||||
- Incorrect: @file{repository::path/to/file.ts} (missing colon)
|
||||
- Incorrect: @file:repository::path/to/file.ts (missing curly braces)
|
||||
- Incorrect: @file:{repository::path/to/file.ts:10-25,30-35} (multiple ranges not supported)
|
||||
- Incorrect: @file:{path/to/file.ts} (missing repository)
|
||||
- Be clear and very concise. Use bullet points where appropriate
|
||||
- Do NOT explain code without providing the exact location reference. Every code mention requires a corresponding \`${FILE_REFERENCE_PREFIX}\` reference
|
||||
- If you cannot provide a code reference for something you're discussing, do not mention that specific code element
|
||||
|
|
@ -221,7 +221,7 @@ When you have sufficient context, output your answer as a structured markdown re
|
|||
**Example answer structure:**
|
||||
\`\`\`markdown
|
||||
${ANSWER_TAG}
|
||||
Authentication in Sourcebot is built on NextAuth.js with a session-based approach using JWT tokens and Prisma as the database adapter ${fileReferenceToString({ fileName: 'auth.ts', range: { startLine: 135, endLine: 140 } })}. The system supports multiple authentication providers and implements organization-based authorization with role-defined permissions.
|
||||
Authentication in Sourcebot is built on NextAuth.js with a session-based approach using JWT tokens and Prisma as the database adapter ${fileReferenceToString({ repo: 'github.com/sourcebot-dev/sourcebot', path: 'auth.ts', range: { startLine: 135, endLine: 140 } })}. The system supports multiple authentication providers and implements organization-based authorization with role-defined permissions.
|
||||
\`\`\`
|
||||
|
||||
</answer_instructions>
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@ describe('calculateVisibleRanges', () => {
|
|||
test('applies padding to a single range', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: {
|
||||
startLine: 10,
|
||||
endLine: 15
|
||||
}
|
||||
},
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -38,16 +39,18 @@ describe('calculateVisibleRanges', () => {
|
|||
test('merges overlapping ranges', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 12, endLine: 20 }
|
||||
range: { startLine: 12, endLine: 20 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -62,16 +65,18 @@ describe('calculateVisibleRanges', () => {
|
|||
test('merges adjacent ranges (including padding)', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 19, endLine: 25 }
|
||||
range: { startLine: 19, endLine: 25 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -88,16 +93,18 @@ describe('calculateVisibleRanges', () => {
|
|||
test('keeps separate ranges when they dont overlap', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 25, endLine: 30 }
|
||||
range: { startLine: 25, endLine: 30 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -112,10 +119,11 @@ describe('calculateVisibleRanges', () => {
|
|||
test('respects file boundaries - start of file', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 1, endLine: 5 }
|
||||
range: { startLine: 1, endLine: 5 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -130,10 +138,11 @@ describe('calculateVisibleRanges', () => {
|
|||
test('respects file boundaries - end of file', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 95, endLine: 100 }
|
||||
range: { startLine: 95, endLine: 100 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -148,28 +157,32 @@ describe('calculateVisibleRanges', () => {
|
|||
test('handles multiple ranges with complex overlaps', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 20, endLine: 25 }
|
||||
range: { startLine: 20, endLine: 25 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '3',
|
||||
type: 'file',
|
||||
range: { startLine: 22, endLine: 30 }
|
||||
range: { startLine: 22, endLine: 30 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '4',
|
||||
type: 'file',
|
||||
range: { startLine: 50, endLine: 55 }
|
||||
range: { startLine: 50, endLine: 55 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -195,16 +208,18 @@ describe('calculateVisibleRanges', () => {
|
|||
test('ignores references without ranges', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
// No range property
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -219,10 +234,11 @@ describe('calculateVisibleRanges', () => {
|
|||
test('works with zero padding', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -237,10 +253,11 @@ describe('calculateVisibleRanges', () => {
|
|||
test('handles single line ranges', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 10 }
|
||||
range: { startLine: 10, endLine: 10 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -255,22 +272,25 @@ describe('calculateVisibleRanges', () => {
|
|||
test('sorts ranges by start line', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 50, endLine: 55 }
|
||||
range: { startLine: 50, endLine: 55 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '3',
|
||||
type: 'file',
|
||||
range: { startLine: 30, endLine: 35 }
|
||||
range: { startLine: 30, endLine: 35 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -380,16 +400,18 @@ describe('StateField Integration', () => {
|
|||
test('initial state calculation with references', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 25, endLine: 30 }
|
||||
range: { startLine: 25, endLine: 30 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -457,7 +479,7 @@ describe('StateField Integration', () => {
|
|||
// Update references
|
||||
const newReferences: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
|
|
@ -482,10 +504,11 @@ describe('StateField Integration', () => {
|
|||
test('expandRegionEffect expands hidden region up', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 20, endLine: 25 }
|
||||
range: { startLine: 20, endLine: 25 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -528,10 +551,11 @@ describe('StateField Integration', () => {
|
|||
test('expandRegionEffect expands hidden region down', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 20, endLine: 25 }
|
||||
range: { startLine: 20, endLine: 25 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -564,10 +588,11 @@ describe('StateField Integration', () => {
|
|||
test('document changes recalculate state', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -606,10 +631,11 @@ describe('StateField Integration', () => {
|
|||
test('action creators work correctly', () => {
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 10, endLine: 15 }
|
||||
range: { startLine: 10, endLine: 15 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -647,16 +673,18 @@ describe('StateField Integration', () => {
|
|||
// Add references
|
||||
const references: FileReference[] = [
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '1',
|
||||
type: 'file',
|
||||
range: { startLine: 20, endLine: 25 }
|
||||
range: { startLine: 20, endLine: 25 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
},
|
||||
{
|
||||
fileName: 'test.ts',
|
||||
path: 'test.ts',
|
||||
id: '2',
|
||||
type: 'file',
|
||||
range: { startLine: 60, endLine: 65 }
|
||||
range: { startLine: 60, endLine: 65 },
|
||||
repo: 'github.com/sourcebot-dev/sourcebot'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -45,12 +45,13 @@ function remarkReferencesPlugin() {
|
|||
return function (tree: Nodes) {
|
||||
findAndReplace(tree, [
|
||||
FILE_REFERENCE_REGEX,
|
||||
(_, fileName: string, startLine?: string, endLine?: string) => {
|
||||
(_, repo: string, fileName: string, startLine?: string, endLine?: string) => {
|
||||
// Create display text
|
||||
let displayText = fileName.split('/').pop() ?? fileName;
|
||||
|
||||
const fileReference = createFileReference({
|
||||
fileName,
|
||||
repo: repo,
|
||||
path: fileName,
|
||||
startLine,
|
||||
endLine,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ interface ReferencedSourcesListViewProps {
|
|||
}
|
||||
|
||||
const resolveFileReference = (reference: FileReference, sources: FileSource[]): FileSource | undefined => {
|
||||
return sources.find((source) => source.path.endsWith(reference.fileName));
|
||||
return sources.find(
|
||||
(source) => source.repo.endsWith(reference.repo) &&
|
||||
source.path.endsWith(reference.path)
|
||||
);
|
||||
}
|
||||
|
||||
const getFileId = (fileSource: FileSource) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
|
||||
export const FILE_REFERENCE_PREFIX = '@file:';
|
||||
export const FILE_REFERENCE_REGEX = new RegExp(`${FILE_REFERENCE_PREFIX}\\{([^:}]+)(?::(\\d+)(?:-(\\d+))?)?\\}`, 'g');
|
||||
export const FILE_REFERENCE_REGEX = new RegExp(
|
||||
// @file:{repoName::fileName:startLine-endLine}
|
||||
`${FILE_REFERENCE_PREFIX}\\{([^:}]+)::([^:}]+)(?::(\\d+)(?:-(\\d+))?)?\\}`,
|
||||
'g'
|
||||
);
|
||||
|
||||
export const ANSWER_TAG = '<!--answer-->';
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ export type Source = z.infer<typeof sourceSchema>;
|
|||
const fileReferenceSchema = z.object({
|
||||
type: z.literal('file'),
|
||||
id: z.string(),
|
||||
fileName: z.string(),
|
||||
repo: z.string(),
|
||||
path: z.string(),
|
||||
range: z.object({
|
||||
startLine: z.number(),
|
||||
endLine: z.number(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ test('useExtractReferences extracts file references from text content', () => {
|
|||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The auth flow is implemented in @file:{auth.ts} and uses sessions @file:{auth.ts:45-60}.'
|
||||
text: 'The auth flow is implemented in @file:{github.com/sourcebot-dev/sourcebot::auth.ts} and uses sessions @file:{github.com/sourcebot-dev/sourcebot::auth.ts:45-60}.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -20,15 +20,18 @@ test('useExtractReferences extracts file references from text content', () => {
|
|||
|
||||
expect(result.current).toHaveLength(2);
|
||||
expect(result.current[0]).toMatchObject({
|
||||
fileName: 'auth.ts',
|
||||
id: getFileReferenceId({ fileName: 'auth.ts' }),
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
id: getFileReferenceId({ repo: 'github.com/sourcebot-dev/sourcebot', path: 'auth.ts' }),
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
expect(result.current[1]).toMatchObject({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
id: getFileReferenceId({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
range: {
|
||||
startLine: 45,
|
||||
endLine: 60,
|
||||
|
|
@ -49,7 +52,7 @@ test('useExtractReferences extracts file references from reasoning content', ()
|
|||
parts: [
|
||||
{
|
||||
type: 'reasoning',
|
||||
text: 'The auth flow is implemented in @file:{auth.ts} and uses sessions @file:{auth.ts:45-60}.'
|
||||
text: 'The auth flow is implemented in @file:{github.com/sourcebot-dev/sourcebot::auth.ts} and uses sessions @file:{github.com/sourcebot-dev/sourcebot::auth.ts:45-60}.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -58,15 +61,18 @@ test('useExtractReferences extracts file references from reasoning content', ()
|
|||
|
||||
expect(result.current).toHaveLength(2);
|
||||
expect(result.current[0]).toMatchObject({
|
||||
fileName: 'auth.ts',
|
||||
id: getFileReferenceId({ fileName: 'auth.ts' }),
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
id: getFileReferenceId({ repo: 'github.com/sourcebot-dev/sourcebot', path: 'auth.ts' }),
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
expect(result.current[1]).toMatchObject({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
id: getFileReferenceId({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
range: {
|
||||
startLine: 45,
|
||||
endLine: 60,
|
||||
|
|
@ -87,15 +93,15 @@ test('useExtractReferences extracts file references from multi-part', () => {
|
|||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The auth flow is implemented in @file:{auth.ts}.'
|
||||
text: 'The auth flow is implemented in @file:{github.com/sourcebot-dev/sourcebot::auth.ts}.'
|
||||
},
|
||||
{
|
||||
type: 'reasoning',
|
||||
text: 'We need to check the session handling in @file:{session.ts:10-20}.'
|
||||
text: 'We need to check the session handling in @file:{github.com/sourcebot-dev/sourcebot::session.ts:10-20}.'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The configuration is stored in @file:{config.json} and @file:{utils.ts:5}.'
|
||||
text: 'The configuration is stored in @file:{github.com/sourcebot-dev/sourcebot::config.json} and @file:{github.com/sourcebot-dev/sourcebot::utils.ts:5}.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -106,16 +112,19 @@ test('useExtractReferences extracts file references from multi-part', () => {
|
|||
|
||||
// From text part
|
||||
expect(result.current[0]).toMatchObject({
|
||||
fileName: 'auth.ts',
|
||||
id: getFileReferenceId({ fileName: 'auth.ts' }),
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
id: getFileReferenceId({ repo: 'github.com/sourcebot-dev/sourcebot', path: 'auth.ts' }),
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
// From reasoning part
|
||||
expect(result.current[1]).toMatchObject({
|
||||
fileName: 'session.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'session.ts',
|
||||
id: getFileReferenceId({
|
||||
fileName: 'session.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'session.ts',
|
||||
range: {
|
||||
startLine: 10,
|
||||
endLine: 20,
|
||||
|
|
@ -129,15 +138,18 @@ test('useExtractReferences extracts file references from multi-part', () => {
|
|||
});
|
||||
|
||||
expect(result.current[2]).toMatchObject({
|
||||
fileName: 'config.json',
|
||||
id: getFileReferenceId({ fileName: 'config.json' }),
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'config.json',
|
||||
id: getFileReferenceId({ repo: 'github.com/sourcebot-dev/sourcebot', path: 'config.json' }),
|
||||
type: 'file',
|
||||
});
|
||||
|
||||
expect(result.current[3]).toMatchObject({
|
||||
fileName: 'utils.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'utils.ts',
|
||||
id: getFileReferenceId({
|
||||
fileName: 'utils.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'utils.ts',
|
||||
range: {
|
||||
startLine: 5,
|
||||
endLine: 5,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ export const useExtractReferences = (message?: SBChatMessage) => {
|
|||
|
||||
let match;
|
||||
while ((match = FILE_REFERENCE_REGEX.exec(content ?? '')) !== null && match !== null) {
|
||||
const [_, fileName, startLine, endLine] = match;
|
||||
const [_, repo, fileName, startLine, endLine] = match;
|
||||
|
||||
const fileReference = createFileReference({
|
||||
fileName,
|
||||
repo: repo,
|
||||
path: fileName,
|
||||
startLine,
|
||||
endLine,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,26 +13,30 @@ vi.mock('@/env.mjs', () => ({
|
|||
|
||||
test('fileReferenceToString formats file references correctly', () => {
|
||||
expect(fileReferenceToString({
|
||||
fileName: 'auth.ts'
|
||||
})).toBe('@file:{auth.ts}');
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts'
|
||||
})).toBe('@file:{github.com/sourcebot-dev/sourcebot::auth.ts}');
|
||||
|
||||
expect(fileReferenceToString({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
range: {
|
||||
startLine: 45,
|
||||
endLine: 60,
|
||||
}
|
||||
})).toBe('@file:{auth.ts:45-60}');
|
||||
})).toBe('@file:{github.com/sourcebot-dev/sourcebot::auth.ts:45-60}');
|
||||
});
|
||||
|
||||
test('fileReferenceToString matches FILE_REFERENCE_REGEX', () => {
|
||||
expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({
|
||||
fileName: 'auth.ts'
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts'
|
||||
}))).toBe(true);
|
||||
|
||||
FILE_REFERENCE_REGEX.lastIndex = 0;
|
||||
expect(FILE_REFERENCE_REGEX.test(fileReferenceToString({
|
||||
fileName: 'auth.ts',
|
||||
repo: 'github.com/sourcebot-dev/sourcebot',
|
||||
path: 'auth.ts',
|
||||
range: {
|
||||
startLine: 45,
|
||||
endLine: 60,
|
||||
|
|
@ -240,55 +244,55 @@ test('getAnswerPartFromAssistantMessage returns undefined when streaming and no
|
|||
});
|
||||
|
||||
test('repairCitations fixes missing colon after @file', () => {
|
||||
const input = 'See the function in @file{auth.ts} for details.';
|
||||
const expected = 'See the function in @file:{auth.ts} for details.';
|
||||
const input = 'See the function in @file{github.com/sourcebot-dev/sourcebot::auth.ts} for details.';
|
||||
const expected = 'See the function in @file:{github.com/sourcebot-dev/sourcebot::auth.ts} for details.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations fixes missing colon with range', () => {
|
||||
const input = 'Check @file{config.ts:15-20} for the configuration.';
|
||||
const expected = 'Check @file:{config.ts:15-20} for the configuration.';
|
||||
const input = 'Check @file{github.com/sourcebot-dev/sourcebot::config.ts:15-20} for the configuration.';
|
||||
const expected = 'Check @file:{github.com/sourcebot-dev/sourcebot::config.ts:15-20} for the configuration.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations fixes missing braces around filename', () => {
|
||||
const input = 'The logic is in @file:utils.js and handles validation.';
|
||||
const expected = 'The logic is in @file:{utils.js} and handles validation.';
|
||||
const input = 'The logic is in @file:github.com/sourcebot-dev/sourcebot::utils.js and handles validation.';
|
||||
const expected = 'The logic is in @file:{github.com/sourcebot-dev/sourcebot::utils.js} and handles validation.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations fixes missing braces with path', () => {
|
||||
const input = 'Look at @file:src/components/Button.tsx for the component.';
|
||||
const expected = 'Look at @file:{src/components/Button.tsx} for the component.';
|
||||
const input = 'Look at @file:github.com/sourcebot-dev/sourcebot::src/components/Button.tsx for the component.';
|
||||
const expected = 'Look at @file:{github.com/sourcebot-dev/sourcebot::src/components/Button.tsx} for the component.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations removes multiple ranges keeping only first', () => {
|
||||
const input = 'See @file:{service.ts:10-15,20-25,30-35} for implementation.';
|
||||
const expected = 'See @file:{service.ts:10-15} for implementation.';
|
||||
const input = 'See @file:{github.com/sourcebot-dev/sourcebot::service.ts:10-15,20-25,30-35} for implementation.';
|
||||
const expected = 'See @file:{github.com/sourcebot-dev/sourcebot::service.ts:10-15} for implementation.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations fixes malformed triple number ranges', () => {
|
||||
const input = 'Check @file:{handler.ts:5-10-15} for the logic.';
|
||||
const expected = 'Check @file:{handler.ts:5-10} for the logic.';
|
||||
const input = 'Check @file:{github.com/sourcebot-dev/sourcebot::handler.ts:5-10-15} for the logic.';
|
||||
const expected = 'Check @file:{github.com/sourcebot-dev/sourcebot::handler.ts:5-10} for the logic.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations handles multiple citations in same text', () => {
|
||||
const input = 'See @file{auth.ts} and @file:config.js for setup details.';
|
||||
const expected = 'See @file:{auth.ts} and @file:{config.js} for setup details.';
|
||||
const input = 'See @file{github.com/sourcebot-dev/sourcebot::auth.ts} and @file:github.com/sourcebot-dev/sourcebot::config.js for setup details.';
|
||||
const expected = 'See @file:{github.com/sourcebot-dev/sourcebot::auth.ts} and @file:{github.com/sourcebot-dev/sourcebot::config.js} for setup details.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations leaves correctly formatted citations unchanged', () => {
|
||||
const input = 'The function @file:{utils.ts:42-50} handles validation correctly.';
|
||||
const input = 'The function @file:{github.com/sourcebot-dev/sourcebot::utils.ts:42-50} handles validation correctly.';
|
||||
expect(repairCitations(input)).toBe(input);
|
||||
});
|
||||
|
||||
test('repairCitations handles edge cases with spaces and punctuation', () => {
|
||||
const input = 'Functions like @file:helper.ts, @file{main.js}, and @file:{app.ts:1-5,10-15} work.';
|
||||
const expected = 'Functions like @file:{helper.ts}, @file:{main.js}, and @file:{app.ts:1-5} work.';
|
||||
const input = 'Functions like @file:github.com/sourcebot-dev/sourcebot::helper.ts, @file{github.com/sourcebot-dev/sourcebot::main.js}, and @file:{github.com/sourcebot-dev/sourcebot::app.ts:1-5,10-15} work.';
|
||||
const expected = 'Functions like @file:{github.com/sourcebot-dev/sourcebot::helper.ts}, @file:{github.com/sourcebot-dev/sourcebot::main.js}, and @file:{github.com/sourcebot-dev/sourcebot::app.ts:1-5} work.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
|
|
@ -302,24 +306,24 @@ test('repairCitations returns text without citations unchanged', () => {
|
|||
});
|
||||
|
||||
test('repairCitations handles complex file paths correctly', () => {
|
||||
const input = 'Check @file:src/components/ui/Button/index.tsx for implementation.';
|
||||
const expected = 'Check @file:{src/components/ui/Button/index.tsx} for implementation.';
|
||||
const input = 'Check @file:github.com/sourcebot-dev/sourcebot::src/components/ui/Button/index.tsx for implementation.';
|
||||
const expected = 'Check @file:{github.com/sourcebot-dev/sourcebot::src/components/ui/Button/index.tsx} for implementation.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations handles files with numbers and special characters', () => {
|
||||
const input = 'See @file{utils-v2.0.1.ts} and @file:config_2024.json for setup.';
|
||||
const expected = 'See @file:{utils-v2.0.1.ts} and @file:{config_2024.json} for setup.';
|
||||
const input = 'See @file{github.com/sourcebot-dev/sourcebot::utils-v2.0.1.ts} and @file:github.com/sourcebot-dev/sourcebot::config_2024.json for setup.';
|
||||
const expected = 'See @file:{github.com/sourcebot-dev/sourcebot::utils-v2.0.1.ts} and @file:{github.com/sourcebot-dev/sourcebot::config_2024.json} for setup.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations handles citation at end of sentence', () => {
|
||||
const input = 'The implementation is in @file:helper.ts.';
|
||||
const expected = 'The implementation is in @file:{helper.ts}.';
|
||||
const input = 'The implementation is in @file:github.com/sourcebot-dev/sourcebot::helper.ts.';
|
||||
const expected = 'The implementation is in @file:{github.com/sourcebot-dev/sourcebot::helper.ts}.';
|
||||
expect(repairCitations(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('repairCitations preserves already correct citations with ranges', () => {
|
||||
const input = 'The function @file:{utils.ts:10-20} and variable @file:{config.js:5} work correctly.';
|
||||
const input = 'The function @file:{github.com/sourcebot-dev/sourcebot::utils.ts:10-20} and variable @file:{github.com/sourcebot-dev/sourcebot::config.js:5} work correctly.';
|
||||
expect(repairCitations(input)).toBe(input);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export const slateContentToString = (children: Descendant[]): string => {
|
|||
|
||||
switch (type) {
|
||||
case 'file':
|
||||
return `${fileReferenceToString({ fileName: child.data.name })} `;
|
||||
return `${fileReferenceToString({ repo: child.data.repo, path: child.data.path })} `;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,15 +210,15 @@ export const createUIMessage = (text: string, mentions: MentionData[], selectedR
|
|||
}
|
||||
}
|
||||
|
||||
export const getFileReferenceId = ({ fileName, range }: Omit<FileReference, 'type' | 'id'>) => {
|
||||
return `file-reference-${fileName}${range ? `-${range.startLine}-${range.endLine}` : ''}`;
|
||||
export const getFileReferenceId = ({ repo, path, range }: Omit<FileReference, 'type' | 'id'>) => {
|
||||
return `file-reference-${repo}::${path}${range ? `-${range.startLine}-${range.endLine}` : ''}`;
|
||||
}
|
||||
|
||||
export const fileReferenceToString = ({ fileName, range }: Omit<FileReference, 'type' | 'id'>) => {
|
||||
return `${FILE_REFERENCE_PREFIX}{${fileName}${range ? `:${range.startLine}-${range.endLine}` : ''}}`;
|
||||
export const fileReferenceToString = ({ repo, path, range }: Omit<FileReference, 'type' | 'id'>) => {
|
||||
return `${FILE_REFERENCE_PREFIX}{${repo}::${path}${range ? `:${range.startLine}-${range.endLine}` : ''}}`;
|
||||
}
|
||||
|
||||
export const createFileReference = ({ fileName, startLine, endLine }: { fileName: string, startLine?: string, endLine?: string }): FileReference => {
|
||||
export const createFileReference = ({ repo, path, startLine, endLine }: { repo: string, path: string, startLine?: string, endLine?: string }): FileReference => {
|
||||
const range = startLine && endLine ? {
|
||||
startLine: parseInt(startLine),
|
||||
endLine: parseInt(endLine),
|
||||
|
|
@ -229,8 +229,9 @@ export const createFileReference = ({ fileName, startLine, endLine }: { fileName
|
|||
|
||||
return {
|
||||
type: 'file',
|
||||
id: getFileReferenceId({ fileName, range }),
|
||||
fileName,
|
||||
id: getFileReferenceId({ repo, path, range }),
|
||||
repo,
|
||||
path,
|
||||
range,
|
||||
}
|
||||
}
|
||||
|
|
@ -241,7 +242,7 @@ export const createFileReference = ({ fileName, startLine, endLine }: { fileName
|
|||
* links.
|
||||
*/
|
||||
export const convertLLMOutputToPortableMarkdown = (text: string): string => {
|
||||
return text.replace(FILE_REFERENCE_REGEX, (_, fileName, startLine, endLine) => {
|
||||
return text.replace(FILE_REFERENCE_REGEX, (_, _repo, fileName, startLine, endLine) => {
|
||||
const displayName = fileName.split('/').pop() || fileName;
|
||||
|
||||
let linkText = displayName;
|
||||
|
|
@ -294,9 +295,9 @@ export const repairCitations = (text: string): string => {
|
|||
// Fix missing braces: @file:filename -> @file:{filename}
|
||||
.replace(/@file:([^\s{]\S*?)(\s|[,;!?](?:\s|$)|\.(?:\s|$)|$)/g, '@file:{$1}$2')
|
||||
// Fix multiple ranges: keep only first range
|
||||
.replace(/@file:\{([^:}]+):(\d+-\d+),[\d,-]+\}/g, '@file:{$1:$2}')
|
||||
.replace(/@file:\{(.+?):(\d+-\d+),[\d,-]+\}/g, '@file:{$1:$2}')
|
||||
// Fix malformed ranges
|
||||
.replace(/@file:\{([^:}]+):(\d+)-(\d+)-(\d+)\}/g, '@file:{$1:$2-$3}');
|
||||
.replace(/@file:\{(.+?):(\d+)-(\d+)-(\d+)\}/g, '@file:{$1:$2-$3}');
|
||||
};
|
||||
|
||||
// Attempts to find the part of the assistant's message
|
||||
|
|
|
|||
Loading…
Reference in a new issue