@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
93 lines • 3.01 kB
JavaScript
import { resolveFilePath as resolveFilePathSafe, isValidFilePath as validateFilePath, } from './path-validation.js';
/**
* Regex pattern to match @file mentions
* Matches:
* - @file.ts
* - @src/path/to/file.tsx
* - @file.ts:10
* - @file.ts:10-20
*/
const FILE_MENTION_REGEX = /@([^\s:]+)(?::(\d+)(?:-(\d+))?)?/g;
/**
* Parse all @mentions from user input
*/
export function parseFileMentions(input) {
const mentions = [];
let match;
// Reset regex state
FILE_MENTION_REGEX.lastIndex = 0;
while ((match = FILE_MENTION_REGEX.exec(input)) !== null) {
const rawText = match[0]; // Full match: "@src/app.tsx:10-20"
const filePath = match[1]; // Captured group 1: "src/app.tsx"
const lineStart = match[2]; // Captured group 2: "10"
const lineEnd = match[3]; // Captured group 3: "20"
// Skip if the file path is empty or invalid
if (!filePath || !isValidFilePath(filePath)) {
continue;
}
const mention = {
rawText,
filePath,
startIndex: match.index,
endIndex: match.index + rawText.length,
};
// Parse line range if present
if (lineStart) {
const start = parseInt(lineStart, 10);
const end = lineEnd ? parseInt(lineEnd, 10) : undefined;
// Validate line numbers
if (start > 0 && (!end || end >= start)) {
mention.lineRange = { start, end };
}
}
mentions.push(mention);
}
return mentions;
}
/**
* Validate file path to prevent directory traversal attacks
* and ensure it's within the project directory
*
* This is a re-export of the shared validation function from path-validation.ts
* to maintain backward compatibility with existing code.
*/
export function isValidFilePath(filePath) {
return validateFilePath(filePath);
}
/**
* Resolve a relative file path to an absolute path within the project
*
* This is a re-export of the shared validation function from path-validation.ts
* to maintain backward compatibility with existing code.
*/
export function resolveFilePath(filePath, cwd) {
return resolveFilePathSafe(filePath, cwd);
}
/**
* Parse line range from a string like "10-20" or "10"
*/
export function parseLineRange(rangeStr) {
if (!rangeStr) {
return null;
}
const parts = rangeStr.split('-');
if (parts.length === 1) {
// Single line: "10"
const line = parseInt(parts[0], 10);
if (isNaN(line) || line <= 0) {
return null;
}
return { start: line, end: undefined };
}
else if (parts.length === 2) {
// Range: "10-20"
const start = parseInt(parts[0], 10);
const end = parseInt(parts[1], 10);
if (isNaN(start) || isNaN(end) || start <= 0 || end < start) {
return null;
}
return { start, end };
}
return null;
}
//# sourceMappingURL=file-mention-parser.js.map