@davstack/peek 0.1.2 → 0.1.4
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-spec.js +67 -3
- package/dist/cli-spec.js.map +1 -1
- package/dist/cli.js +67 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.js +67 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli-spec.js
CHANGED
|
@@ -175,11 +175,17 @@ function extractTypeScriptSymbols(text) {
|
|
|
175
175
|
for (const [pattern, label] of patterns) {
|
|
176
176
|
for (const match of source.matchAll(pattern)) {
|
|
177
177
|
const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);
|
|
178
|
-
items.push(
|
|
178
|
+
items.push({
|
|
179
|
+
start: range.start,
|
|
180
|
+
value: `${formatLineRange(range)} ${label} ${match[1]}`
|
|
181
|
+
});
|
|
182
|
+
if (label === "class") {
|
|
183
|
+
items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));
|
|
184
|
+
}
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
items.push(...extractTypeScriptTestCalls(source));
|
|
182
|
-
return
|
|
188
|
+
return uniqueSortedItems(items);
|
|
183
189
|
}
|
|
184
190
|
function extractTypeScriptTestCalls(text) {
|
|
185
191
|
const items = [];
|
|
@@ -189,10 +195,60 @@ function extractTypeScriptTestCalls(text) {
|
|
|
189
195
|
const quote = match[2];
|
|
190
196
|
const title = match[3];
|
|
191
197
|
const range = lineRangeForCallExpression(text, match.index ?? 0);
|
|
192
|
-
items.push(
|
|
198
|
+
items.push({
|
|
199
|
+
start: range.start,
|
|
200
|
+
value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return items;
|
|
204
|
+
}
|
|
205
|
+
function extractTypeScriptClassMethods(text, classIndex) {
|
|
206
|
+
const openingBrace = text.indexOf("{", classIndex);
|
|
207
|
+
if (openingBrace === -1) return [];
|
|
208
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
209
|
+
if (closingBrace === -1) return [];
|
|
210
|
+
const classBody = text.slice(openingBrace + 1, closingBrace);
|
|
211
|
+
const bodyOffset = openingBrace + 1;
|
|
212
|
+
const items = [];
|
|
213
|
+
const methodPattern = /^([ \t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\s+)*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
214
|
+
const matches = Array.from(classBody.matchAll(methodPattern));
|
|
215
|
+
const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));
|
|
216
|
+
for (const match of matches) {
|
|
217
|
+
if ((match[1]?.length ?? 0) !== memberIndent) continue;
|
|
218
|
+
const name = match[2];
|
|
219
|
+
if (!name || ["if", "for", "while", "switch", "catch"].includes(name)) continue;
|
|
220
|
+
const index = bodyOffset + (match.index ?? 0);
|
|
221
|
+
const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);
|
|
222
|
+
items.push({
|
|
223
|
+
start: blockRange.start,
|
|
224
|
+
value: `${formatLineRange(blockRange)} method ${name}`
|
|
225
|
+
});
|
|
193
226
|
}
|
|
194
227
|
return items;
|
|
195
228
|
}
|
|
229
|
+
function lineRangeForTypeScriptMethod(text, index, classClosingBrace) {
|
|
230
|
+
const start = lineNumberAt(text, index);
|
|
231
|
+
const openingParen = text.indexOf("(", index);
|
|
232
|
+
if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };
|
|
233
|
+
const closingParen = findMatchingParen(text, openingParen);
|
|
234
|
+
if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };
|
|
235
|
+
const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);
|
|
236
|
+
if (openingBrace === -1 || openingBrace > classClosingBrace) {
|
|
237
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
238
|
+
}
|
|
239
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
240
|
+
if (closingBrace === -1 || closingBrace > classClosingBrace) {
|
|
241
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
242
|
+
}
|
|
243
|
+
return { start, end: lineNumberAt(text, closingBrace) };
|
|
244
|
+
}
|
|
245
|
+
function findMethodBodyOpeningBrace(text, closingParen, classClosingBrace) {
|
|
246
|
+
const lineEnd = text.indexOf("\n", closingParen);
|
|
247
|
+
const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);
|
|
248
|
+
const sameLineBrace = text.lastIndexOf("{", searchEnd);
|
|
249
|
+
if (sameLineBrace > closingParen) return sameLineBrace;
|
|
250
|
+
return text.indexOf("{", closingParen);
|
|
251
|
+
}
|
|
196
252
|
function extractPythonSymbols(text) {
|
|
197
253
|
const items = [];
|
|
198
254
|
const lines = text.split(/\r?\n/);
|
|
@@ -228,6 +284,14 @@ function stripBlockComments(text) {
|
|
|
228
284
|
function unique(items) {
|
|
229
285
|
return Array.from(new Set(items));
|
|
230
286
|
}
|
|
287
|
+
function uniqueSortedItems(items) {
|
|
288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
289
|
+
return items.sort((a, b) => a.start - b.start || a.value.localeCompare(b.value)).filter((item) => {
|
|
290
|
+
if (seen.has(item.value)) return false;
|
|
291
|
+
seen.add(item.value);
|
|
292
|
+
return true;
|
|
293
|
+
}).map((item) => item.value);
|
|
294
|
+
}
|
|
231
295
|
function createLimiter(maxConcurrent) {
|
|
232
296
|
let active = 0;
|
|
233
297
|
const queue = [];
|
package/dist/cli-spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cli-spec.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: string[] = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${label} ${match[1]}`);\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return unique(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): string[] {\n const items: string[] = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${callName}(${quote}${title}${quote})`);\n }\n\n return items;\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n","import type { CliSpec } from '@davstack/cli-utils';\nimport type { PeekFilePathMode, PeekOutputPresetName, ScanOptions } from './index.js';\n\nconst outputFlags = {\n human: {\n type: 'boolean',\n default: false,\n description: 'Use human-readable output defaults: indented XML and full repo-relative file paths',\n },\n agent: {\n type: 'boolean',\n default: false,\n description: 'Use token-optimized output defaults: no indentation and concise file paths',\n },\n indent: {\n type: 'boolean',\n description: 'Indent nested folder output',\n },\n file_paths: {\n type: 'string',\n values: ['concise', 'full'],\n description: 'File path style: concise or full',\n },\n 'include-lines-count': {\n type: 'boolean',\n description: 'Include total line counts on file tags',\n },\n} as const;\n\nfunction resolveCliScanOptions(flags: Record<string, unknown>): ScanOptions {\n const human = flags.human === true;\n const agent = flags.agent === true;\n if (human && agent) throw new Error('Use only one output preset: --human or --agent');\n\n const preset: PeekOutputPresetName | undefined = agent ? 'agent' : human ? 'human' : undefined;\n const filePaths = flags.file_paths;\n if (filePaths !== undefined && filePaths !== 'concise' && filePaths !== 'full') {\n throw new Error('--file_paths must be \"concise\" or \"full\"');\n }\n\n return {\n deep: flags.deep as boolean,\n preset,\n indent: flags.indent as boolean | undefined,\n filePaths: filePaths as PeekFilePathMode | undefined,\n includeLinesCount: flags['include-lines-count'] as boolean | undefined,\n };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\n examples: [\n 'peek .',\n 'peek packages/context-compactor --agent',\n 'peek . --human',\n 'peek . --file_paths=full --no-include-lines-count',\n ],\n defaults: [\n '--human behavior: deep scan, indented output, full file paths, line counts',\n '--agent behavior: deep scan, no indentation, concise file paths, line counts',\n ],\n positionals: [{ name: 'path', required: true, description: 'Folder to peek at' }],\n flags: {\n deep: {\n type: 'boolean',\n description: 'Recursively include child folders',\n },\n ...outputFlags,\n },\n run: async (ctx) => {\n const { peekFolder } = await import('./index.js');\n console.log(await peekFolder(ctx.positionals[0], resolveCliScanOptions(ctx.flags)));\n },\n};\n"],"mappings":"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","names":["peekFolder"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cli-spec.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: Array<{ start: number; value: string }> = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${label} ${match[1]}`,\n });\n if (label === 'class') {\n items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));\n }\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return uniqueSortedItems(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): Array<{ start: number; value: string }> {\n const items: Array<{ start: number; value: string }> = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`,\n });\n }\n\n return items;\n}\n\nfunction extractTypeScriptClassMethods(\n text: string,\n classIndex: number,\n): Array<{ start: number; value: string }> {\n const openingBrace = text.indexOf('{', classIndex);\n if (openingBrace === -1) return [];\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1) return [];\n\n const classBody = text.slice(openingBrace + 1, closingBrace);\n const bodyOffset = openingBrace + 1;\n const items: Array<{ start: number; value: string }> = [];\n const methodPattern =\n /^([ \\t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\\s+)*([A-Za-z_$][\\w$]*)\\s*\\(/gm;\n const matches = Array.from(classBody.matchAll(methodPattern));\n const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));\n\n for (const match of matches) {\n if ((match[1]?.length ?? 0) !== memberIndent) continue;\n const name = match[2];\n if (!name || ['if', 'for', 'while', 'switch', 'catch'].includes(name)) continue;\n const index = bodyOffset + (match.index ?? 0);\n const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);\n items.push({\n start: blockRange.start,\n value: `${formatLineRange(blockRange)} method ${name}`,\n });\n }\n\n return items;\n}\n\nfunction lineRangeForTypeScriptMethod(\n text: string,\n index: number,\n classClosingBrace: number,\n): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };\n\n const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);\n if (openingBrace === -1 || openingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1 || closingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n return { start, end: lineNumberAt(text, closingBrace) };\n}\n\nfunction findMethodBodyOpeningBrace(\n text: string,\n closingParen: number,\n classClosingBrace: number,\n): number {\n const lineEnd = text.indexOf('\\n', closingParen);\n const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);\n const sameLineBrace = text.lastIndexOf('{', searchEnd);\n if (sameLineBrace > closingParen) return sameLineBrace;\n return text.indexOf('{', closingParen);\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction uniqueSortedItems(items: Array<{ start: number; value: string }>): string[] {\n const seen = new Set<string>();\n return items\n .sort((a, b) => a.start - b.start || a.value.localeCompare(b.value))\n .filter((item) => {\n if (seen.has(item.value)) return false;\n seen.add(item.value);\n return true;\n })\n .map((item) => item.value);\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n","import type { CliSpec } from '@davstack/cli-utils';\nimport type { PeekFilePathMode, PeekOutputPresetName, ScanOptions } from './index.js';\n\nconst outputFlags = {\n human: {\n type: 'boolean',\n default: false,\n description: 'Use human-readable output defaults: indented XML and full repo-relative file paths',\n },\n agent: {\n type: 'boolean',\n default: false,\n description: 'Use token-optimized output defaults: no indentation and concise file paths',\n },\n indent: {\n type: 'boolean',\n description: 'Indent nested folder output',\n },\n file_paths: {\n type: 'string',\n values: ['concise', 'full'],\n description: 'File path style: concise or full',\n },\n 'include-lines-count': {\n type: 'boolean',\n description: 'Include total line counts on file tags',\n },\n} as const;\n\nfunction resolveCliScanOptions(flags: Record<string, unknown>): ScanOptions {\n const human = flags.human === true;\n const agent = flags.agent === true;\n if (human && agent) throw new Error('Use only one output preset: --human or --agent');\n\n const preset: PeekOutputPresetName | undefined = agent ? 'agent' : human ? 'human' : undefined;\n const filePaths = flags.file_paths;\n if (filePaths !== undefined && filePaths !== 'concise' && filePaths !== 'full') {\n throw new Error('--file_paths must be \"concise\" or \"full\"');\n }\n\n return {\n deep: flags.deep as boolean,\n preset,\n indent: flags.indent as boolean | undefined,\n filePaths: filePaths as PeekFilePathMode | undefined,\n includeLinesCount: flags['include-lines-count'] as boolean | undefined,\n };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\n examples: [\n 'peek .',\n 'peek packages/context-compactor --agent',\n 'peek . --human',\n 'peek . --file_paths=full --no-include-lines-count',\n ],\n defaults: [\n '--human behavior: deep scan, indented output, full file paths, line counts',\n '--agent behavior: deep scan, no indentation, concise file paths, line counts',\n ],\n positionals: [{ name: 'path', required: true, description: 'Folder to peek at' }],\n flags: {\n deep: {\n type: 'boolean',\n description: 'Recursively include child folders',\n },\n ...outputFlags,\n },\n run: async (ctx) => {\n const { peekFolder } = await import('./index.js');\n console.log(await peekFolder(ctx.positionals[0], resolveCliScanOptions(ctx.flags)));\n },\n};\n"],"mappings":"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","names":["peekFolder"]}
|
package/dist/cli.js
CHANGED
|
@@ -175,11 +175,17 @@ function extractTypeScriptSymbols(text) {
|
|
|
175
175
|
for (const [pattern, label] of patterns) {
|
|
176
176
|
for (const match of source.matchAll(pattern)) {
|
|
177
177
|
const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);
|
|
178
|
-
items.push(
|
|
178
|
+
items.push({
|
|
179
|
+
start: range.start,
|
|
180
|
+
value: `${formatLineRange(range)} ${label} ${match[1]}`
|
|
181
|
+
});
|
|
182
|
+
if (label === "class") {
|
|
183
|
+
items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));
|
|
184
|
+
}
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
items.push(...extractTypeScriptTestCalls(source));
|
|
182
|
-
return
|
|
188
|
+
return uniqueSortedItems(items);
|
|
183
189
|
}
|
|
184
190
|
function extractTypeScriptTestCalls(text) {
|
|
185
191
|
const items = [];
|
|
@@ -189,10 +195,60 @@ function extractTypeScriptTestCalls(text) {
|
|
|
189
195
|
const quote = match[2];
|
|
190
196
|
const title = match[3];
|
|
191
197
|
const range = lineRangeForCallExpression(text, match.index ?? 0);
|
|
192
|
-
items.push(
|
|
198
|
+
items.push({
|
|
199
|
+
start: range.start,
|
|
200
|
+
value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return items;
|
|
204
|
+
}
|
|
205
|
+
function extractTypeScriptClassMethods(text, classIndex) {
|
|
206
|
+
const openingBrace = text.indexOf("{", classIndex);
|
|
207
|
+
if (openingBrace === -1) return [];
|
|
208
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
209
|
+
if (closingBrace === -1) return [];
|
|
210
|
+
const classBody = text.slice(openingBrace + 1, closingBrace);
|
|
211
|
+
const bodyOffset = openingBrace + 1;
|
|
212
|
+
const items = [];
|
|
213
|
+
const methodPattern = /^([ \t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\s+)*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
214
|
+
const matches = Array.from(classBody.matchAll(methodPattern));
|
|
215
|
+
const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));
|
|
216
|
+
for (const match of matches) {
|
|
217
|
+
if ((match[1]?.length ?? 0) !== memberIndent) continue;
|
|
218
|
+
const name = match[2];
|
|
219
|
+
if (!name || ["if", "for", "while", "switch", "catch"].includes(name)) continue;
|
|
220
|
+
const index = bodyOffset + (match.index ?? 0);
|
|
221
|
+
const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);
|
|
222
|
+
items.push({
|
|
223
|
+
start: blockRange.start,
|
|
224
|
+
value: `${formatLineRange(blockRange)} method ${name}`
|
|
225
|
+
});
|
|
193
226
|
}
|
|
194
227
|
return items;
|
|
195
228
|
}
|
|
229
|
+
function lineRangeForTypeScriptMethod(text, index, classClosingBrace) {
|
|
230
|
+
const start = lineNumberAt(text, index);
|
|
231
|
+
const openingParen = text.indexOf("(", index);
|
|
232
|
+
if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };
|
|
233
|
+
const closingParen = findMatchingParen(text, openingParen);
|
|
234
|
+
if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };
|
|
235
|
+
const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);
|
|
236
|
+
if (openingBrace === -1 || openingBrace > classClosingBrace) {
|
|
237
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
238
|
+
}
|
|
239
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
240
|
+
if (closingBrace === -1 || closingBrace > classClosingBrace) {
|
|
241
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
242
|
+
}
|
|
243
|
+
return { start, end: lineNumberAt(text, closingBrace) };
|
|
244
|
+
}
|
|
245
|
+
function findMethodBodyOpeningBrace(text, closingParen, classClosingBrace) {
|
|
246
|
+
const lineEnd = text.indexOf("\n", closingParen);
|
|
247
|
+
const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);
|
|
248
|
+
const sameLineBrace = text.lastIndexOf("{", searchEnd);
|
|
249
|
+
if (sameLineBrace > closingParen) return sameLineBrace;
|
|
250
|
+
return text.indexOf("{", closingParen);
|
|
251
|
+
}
|
|
196
252
|
function extractPythonSymbols(text) {
|
|
197
253
|
const items = [];
|
|
198
254
|
const lines = text.split(/\r?\n/);
|
|
@@ -228,6 +284,14 @@ function stripBlockComments(text) {
|
|
|
228
284
|
function unique(items) {
|
|
229
285
|
return Array.from(new Set(items));
|
|
230
286
|
}
|
|
287
|
+
function uniqueSortedItems(items) {
|
|
288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
289
|
+
return items.sort((a, b) => a.start - b.start || a.value.localeCompare(b.value)).filter((item) => {
|
|
290
|
+
if (seen.has(item.value)) return false;
|
|
291
|
+
seen.add(item.value);
|
|
292
|
+
return true;
|
|
293
|
+
}).map((item) => item.value);
|
|
294
|
+
}
|
|
231
295
|
function createLimiter(maxConcurrent) {
|
|
232
296
|
let active = 0;
|
|
233
297
|
const queue = [];
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cli.ts","../src/cli-spec.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: string[] = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${label} ${match[1]}`);\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return unique(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): string[] {\n const items: string[] = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${callName}(${quote}${title}${quote})`);\n }\n\n return items;\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n","import { defineCli } from '@davstack/cli-utils';\nimport { cliSpec } from './cli-spec.js';\n\nconst code = await defineCli(cliSpec).run(process.argv.slice(2));\nprocess.exit(code);\n","import type { CliSpec } from '@davstack/cli-utils';\nimport type { PeekFilePathMode, PeekOutputPresetName, ScanOptions } from './index.js';\n\nconst outputFlags = {\n human: {\n type: 'boolean',\n default: false,\n description: 'Use human-readable output defaults: indented XML and full repo-relative file paths',\n },\n agent: {\n type: 'boolean',\n default: false,\n description: 'Use token-optimized output defaults: no indentation and concise file paths',\n },\n indent: {\n type: 'boolean',\n description: 'Indent nested folder output',\n },\n file_paths: {\n type: 'string',\n values: ['concise', 'full'],\n description: 'File path style: concise or full',\n },\n 'include-lines-count': {\n type: 'boolean',\n description: 'Include total line counts on file tags',\n },\n} as const;\n\nfunction resolveCliScanOptions(flags: Record<string, unknown>): ScanOptions {\n const human = flags.human === true;\n const agent = flags.agent === true;\n if (human && agent) throw new Error('Use only one output preset: --human or --agent');\n\n const preset: PeekOutputPresetName | undefined = agent ? 'agent' : human ? 'human' : undefined;\n const filePaths = flags.file_paths;\n if (filePaths !== undefined && filePaths !== 'concise' && filePaths !== 'full') {\n throw new Error('--file_paths must be \"concise\" or \"full\"');\n }\n\n return {\n deep: flags.deep as boolean,\n preset,\n indent: flags.indent as boolean | undefined,\n filePaths: filePaths as PeekFilePathMode | undefined,\n includeLinesCount: flags['include-lines-count'] as boolean | undefined,\n };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\n examples: [\n 'peek .',\n 'peek packages/context-compactor --agent',\n 'peek . --human',\n 'peek . --file_paths=full --no-include-lines-count',\n ],\n defaults: [\n '--human behavior: deep scan, indented output, full file paths, line counts',\n '--agent behavior: deep scan, no indentation, concise file paths, line counts',\n ],\n positionals: [{ name: 'path', required: true, description: 'Folder to peek at' }],\n flags: {\n deep: {\n type: 'boolean',\n description: 'Recursively include child folders',\n },\n ...outputFlags,\n },\n run: async (ctx) => {\n const { peekFolder } = await import('./index.js');\n console.log(await peekFolder(ctx.positionals[0], resolveCliScanOptions(ctx.flags)));\n },\n};\n"],"mappings":"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","names":["code","peekFolder"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cli.ts","../src/cli-spec.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: Array<{ start: number; value: string }> = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${label} ${match[1]}`,\n });\n if (label === 'class') {\n items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));\n }\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return uniqueSortedItems(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): Array<{ start: number; value: string }> {\n const items: Array<{ start: number; value: string }> = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`,\n });\n }\n\n return items;\n}\n\nfunction extractTypeScriptClassMethods(\n text: string,\n classIndex: number,\n): Array<{ start: number; value: string }> {\n const openingBrace = text.indexOf('{', classIndex);\n if (openingBrace === -1) return [];\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1) return [];\n\n const classBody = text.slice(openingBrace + 1, closingBrace);\n const bodyOffset = openingBrace + 1;\n const items: Array<{ start: number; value: string }> = [];\n const methodPattern =\n /^([ \\t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\\s+)*([A-Za-z_$][\\w$]*)\\s*\\(/gm;\n const matches = Array.from(classBody.matchAll(methodPattern));\n const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));\n\n for (const match of matches) {\n if ((match[1]?.length ?? 0) !== memberIndent) continue;\n const name = match[2];\n if (!name || ['if', 'for', 'while', 'switch', 'catch'].includes(name)) continue;\n const index = bodyOffset + (match.index ?? 0);\n const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);\n items.push({\n start: blockRange.start,\n value: `${formatLineRange(blockRange)} method ${name}`,\n });\n }\n\n return items;\n}\n\nfunction lineRangeForTypeScriptMethod(\n text: string,\n index: number,\n classClosingBrace: number,\n): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };\n\n const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);\n if (openingBrace === -1 || openingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1 || closingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n return { start, end: lineNumberAt(text, closingBrace) };\n}\n\nfunction findMethodBodyOpeningBrace(\n text: string,\n closingParen: number,\n classClosingBrace: number,\n): number {\n const lineEnd = text.indexOf('\\n', closingParen);\n const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);\n const sameLineBrace = text.lastIndexOf('{', searchEnd);\n if (sameLineBrace > closingParen) return sameLineBrace;\n return text.indexOf('{', closingParen);\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction uniqueSortedItems(items: Array<{ start: number; value: string }>): string[] {\n const seen = new Set<string>();\n return items\n .sort((a, b) => a.start - b.start || a.value.localeCompare(b.value))\n .filter((item) => {\n if (seen.has(item.value)) return false;\n seen.add(item.value);\n return true;\n })\n .map((item) => item.value);\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n","import { defineCli } from '@davstack/cli-utils';\nimport { cliSpec } from './cli-spec.js';\n\nconst code = await defineCli(cliSpec).run(process.argv.slice(2));\nprocess.exit(code);\n","import type { CliSpec } from '@davstack/cli-utils';\nimport type { PeekFilePathMode, PeekOutputPresetName, ScanOptions } from './index.js';\n\nconst outputFlags = {\n human: {\n type: 'boolean',\n default: false,\n description: 'Use human-readable output defaults: indented XML and full repo-relative file paths',\n },\n agent: {\n type: 'boolean',\n default: false,\n description: 'Use token-optimized output defaults: no indentation and concise file paths',\n },\n indent: {\n type: 'boolean',\n description: 'Indent nested folder output',\n },\n file_paths: {\n type: 'string',\n values: ['concise', 'full'],\n description: 'File path style: concise or full',\n },\n 'include-lines-count': {\n type: 'boolean',\n description: 'Include total line counts on file tags',\n },\n} as const;\n\nfunction resolveCliScanOptions(flags: Record<string, unknown>): ScanOptions {\n const human = flags.human === true;\n const agent = flags.agent === true;\n if (human && agent) throw new Error('Use only one output preset: --human or --agent');\n\n const preset: PeekOutputPresetName | undefined = agent ? 'agent' : human ? 'human' : undefined;\n const filePaths = flags.file_paths;\n if (filePaths !== undefined && filePaths !== 'concise' && filePaths !== 'full') {\n throw new Error('--file_paths must be \"concise\" or \"full\"');\n }\n\n return {\n deep: flags.deep as boolean,\n preset,\n indent: flags.indent as boolean | undefined,\n filePaths: filePaths as PeekFilePathMode | undefined,\n includeLinesCount: flags['include-lines-count'] as boolean | undefined,\n };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\n examples: [\n 'peek .',\n 'peek packages/context-compactor --agent',\n 'peek . --human',\n 'peek . --file_paths=full --no-include-lines-count',\n ],\n defaults: [\n '--human behavior: deep scan, indented output, full file paths, line counts',\n '--agent behavior: deep scan, no indentation, concise file paths, line counts',\n ],\n positionals: [{ name: 'path', required: true, description: 'Folder to peek at' }],\n flags: {\n deep: {\n type: 'boolean',\n description: 'Recursively include child folders',\n },\n ...outputFlags,\n },\n run: async (ctx) => {\n const { peekFolder } = await import('./index.js');\n console.log(await peekFolder(ctx.positionals[0], resolveCliScanOptions(ctx.flags)));\n },\n};\n"],"mappings":"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","names":["code","peekFolder"]}
|
package/dist/index.js
CHANGED
|
@@ -182,11 +182,17 @@ function extractTypeScriptSymbols(text) {
|
|
|
182
182
|
for (const [pattern, label] of patterns) {
|
|
183
183
|
for (const match of source.matchAll(pattern)) {
|
|
184
184
|
const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);
|
|
185
|
-
items.push(
|
|
185
|
+
items.push({
|
|
186
|
+
start: range.start,
|
|
187
|
+
value: `${formatLineRange(range)} ${label} ${match[1]}`
|
|
188
|
+
});
|
|
189
|
+
if (label === "class") {
|
|
190
|
+
items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));
|
|
191
|
+
}
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
194
|
items.push(...extractTypeScriptTestCalls(source));
|
|
189
|
-
return
|
|
195
|
+
return uniqueSortedItems(items);
|
|
190
196
|
}
|
|
191
197
|
function extractTypeScriptTestCalls(text) {
|
|
192
198
|
const items = [];
|
|
@@ -196,10 +202,60 @@ function extractTypeScriptTestCalls(text) {
|
|
|
196
202
|
const quote = match[2];
|
|
197
203
|
const title = match[3];
|
|
198
204
|
const range = lineRangeForCallExpression(text, match.index ?? 0);
|
|
199
|
-
items.push(
|
|
205
|
+
items.push({
|
|
206
|
+
start: range.start,
|
|
207
|
+
value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return items;
|
|
211
|
+
}
|
|
212
|
+
function extractTypeScriptClassMethods(text, classIndex) {
|
|
213
|
+
const openingBrace = text.indexOf("{", classIndex);
|
|
214
|
+
if (openingBrace === -1) return [];
|
|
215
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
216
|
+
if (closingBrace === -1) return [];
|
|
217
|
+
const classBody = text.slice(openingBrace + 1, closingBrace);
|
|
218
|
+
const bodyOffset = openingBrace + 1;
|
|
219
|
+
const items = [];
|
|
220
|
+
const methodPattern = /^([ \t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\s+)*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
221
|
+
const matches = Array.from(classBody.matchAll(methodPattern));
|
|
222
|
+
const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));
|
|
223
|
+
for (const match of matches) {
|
|
224
|
+
if ((match[1]?.length ?? 0) !== memberIndent) continue;
|
|
225
|
+
const name = match[2];
|
|
226
|
+
if (!name || ["if", "for", "while", "switch", "catch"].includes(name)) continue;
|
|
227
|
+
const index = bodyOffset + (match.index ?? 0);
|
|
228
|
+
const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);
|
|
229
|
+
items.push({
|
|
230
|
+
start: blockRange.start,
|
|
231
|
+
value: `${formatLineRange(blockRange)} method ${name}`
|
|
232
|
+
});
|
|
200
233
|
}
|
|
201
234
|
return items;
|
|
202
235
|
}
|
|
236
|
+
function lineRangeForTypeScriptMethod(text, index, classClosingBrace) {
|
|
237
|
+
const start = lineNumberAt(text, index);
|
|
238
|
+
const openingParen = text.indexOf("(", index);
|
|
239
|
+
if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };
|
|
240
|
+
const closingParen = findMatchingParen(text, openingParen);
|
|
241
|
+
if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };
|
|
242
|
+
const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);
|
|
243
|
+
if (openingBrace === -1 || openingBrace > classClosingBrace) {
|
|
244
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
245
|
+
}
|
|
246
|
+
const closingBrace = findMatchingBrace(text, openingBrace);
|
|
247
|
+
if (closingBrace === -1 || closingBrace > classClosingBrace) {
|
|
248
|
+
return { start, end: lineNumberAt(text, closingParen) };
|
|
249
|
+
}
|
|
250
|
+
return { start, end: lineNumberAt(text, closingBrace) };
|
|
251
|
+
}
|
|
252
|
+
function findMethodBodyOpeningBrace(text, closingParen, classClosingBrace) {
|
|
253
|
+
const lineEnd = text.indexOf("\n", closingParen);
|
|
254
|
+
const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);
|
|
255
|
+
const sameLineBrace = text.lastIndexOf("{", searchEnd);
|
|
256
|
+
if (sameLineBrace > closingParen) return sameLineBrace;
|
|
257
|
+
return text.indexOf("{", closingParen);
|
|
258
|
+
}
|
|
203
259
|
function extractPythonSymbols(text) {
|
|
204
260
|
const items = [];
|
|
205
261
|
const lines = text.split(/\r?\n/);
|
|
@@ -235,6 +291,14 @@ function stripBlockComments(text) {
|
|
|
235
291
|
function unique(items) {
|
|
236
292
|
return Array.from(new Set(items));
|
|
237
293
|
}
|
|
294
|
+
function uniqueSortedItems(items) {
|
|
295
|
+
const seen = /* @__PURE__ */ new Set();
|
|
296
|
+
return items.sort((a, b) => a.start - b.start || a.value.localeCompare(b.value)).filter((item) => {
|
|
297
|
+
if (seen.has(item.value)) return false;
|
|
298
|
+
seen.add(item.value);
|
|
299
|
+
return true;
|
|
300
|
+
}).map((item) => item.value);
|
|
301
|
+
}
|
|
238
302
|
function createLimiter(maxConcurrent) {
|
|
239
303
|
let active = 0;
|
|
240
304
|
const queue = [];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: string[] = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${label} ${match[1]}`);\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return unique(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): string[] {\n const items: string[] = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push(`${formatLineRange(range)} ${callName}(${quote}${title}${quote})`);\n }\n\n return items;\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nexport const GENERATED_PEEK_FILE = '.folder-peek.generated.md';\nconst execFileAsync = promisify(execFile);\n\nexport type ScanOptions = {\n deep?: boolean;\n preset?: PeekOutputPresetName;\n indent?: boolean;\n filePaths?: PeekFilePathMode;\n file_paths?: PeekFilePathMode;\n includeLinesCount?: boolean;\n include_lines_count?: boolean;\n};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\n lines: number;\n items: string[];\n};\n\nexport type PeekFilePathMode = 'concise' | 'full';\nexport type PeekOutputPresetName = 'agent' | 'human';\n\nexport type PeekOutputConfig = {\n deep: boolean;\n indent: boolean;\n filePaths: PeekFilePathMode;\n includeLinesCount: boolean;\n};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n includeLinesCount: true,\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\n includeLinesCount: true,\n },\n};\n\ntype FolderSummary = {\n path: string;\n files: FileSummary[];\n folders: FolderSummary[];\n omittedFiles: string[];\n};\n\nconst SKIP_DIRS = new Set([\n '.git',\n '.next',\n 'coverage',\n 'dist',\n 'dist-ssr',\n 'node_modules',\n]);\n\nconst SKIP_FILES = new Set([GENERATED_PEEK_FILE]);\n\nexport async function scanFolderPeek(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const repoRoot = await findRepoRoot(root);\n const outputConfig = resolveOutputConfig(options);\n const fileLimit = createLimiter(64);\n const summary = await collectFolderSummary(\n root,\n repoRoot,\n root,\n outputConfig.deep,\n outputConfig,\n fileLimit,\n );\n\n return `${renderFolderSummary(summary, outputConfig)}\\n`;\n}\n\nexport async function generateFolderPeek(\n folder: string,\n options: ScanOptions = {},\n): Promise<{ path: string; content: string }> {\n const root = path.resolve(folder);\n const content = await scanFolderPeek(root, options);\n const outPath = path.join(root, GENERATED_PEEK_FILE);\n await mkdir(root, { recursive: true });\n await writeFile(outPath, content, 'utf8');\n return { path: outPath, content };\n}\n\nexport async function peekFolder(folder: string, options: ScanOptions = {}): Promise<string> {\n const root = path.resolve(folder);\n const generatedPath = path.join(root, GENERATED_PEEK_FILE);\n try {\n if (!options.deep && !hasOutputOverrides(options)) return await readFile(generatedPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n return (await generateFolderPeek(root, options)).content;\n}\n\nasync function collectFolderSummary(\n root: string,\n repoRoot: string,\n dir: string,\n deep: boolean,\n outputConfig: PeekOutputConfig,\n fileLimit: <T>(task: () => Promise<T>) => Promise<T>,\n): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const fileTasks: Array<Promise<{ name: string; summary: FileSummary | null }>> = [];\n const folderTasks: Array<Promise<FolderSummary>> = [];\n const omittedFiles: string[] = [];\n const ignoredPaths = await checkIgnoredPaths(root, entries.map((entry) => path.join(dir, entry.name)));\n\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = path.join(dir, entry.name);\n if (ignoredPaths.has(toPosix(path.relative(root, fullPath)))) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folderTasks.push(collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig, fileLimit));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n fileTasks.push(\n fileLimit(async () => ({\n name: entry.name,\n summary: await summarizeFile(root, repoRoot, fullPath, outputConfig),\n })),\n );\n }\n\n const fileResults = await Promise.all(fileTasks);\n const files = fileResults\n .map((result) => result.summary)\n .filter((summary): summary is FileSummary => summary !== null);\n omittedFiles.push(\n ...fileResults\n .filter((result) => result.summary === null)\n .map((result) => result.name),\n );\n const folders = await Promise.all(folderTasks);\n\n return {\n path: formatFolderPath(root, repoRoot, dir),\n files: files.sort((a, b) => a.path.localeCompare(b.path)),\n folders: folders.sort((a, b) => a.path.localeCompare(b.path)),\n omittedFiles: omittedFiles.sort((a, b) => a.localeCompare(b)),\n };\n}\n\nfunction formatFolderPath(root: string, repoRoot: string, dir: string): string {\n const base = isInsidePath(repoRoot, dir) ? repoRoot : root;\n const relativePath = toPosix(path.relative(base, dir));\n return relativePath || '.';\n}\n\nfunction renderFolderSummary(\n summary: FolderSummary,\n outputConfig: PeekOutputConfig,\n depth = 0,\n): string {\n const indent = outputConfig.indent ? '\\t'.repeat(Math.max(0, depth - 1)) : '';\n const childIndent = outputConfig.indent ? '\\t'.repeat(depth) : '';\n const lines = [`${indent}<folder path=\"${escapeAttribute(summary.path)}\">`];\n\n for (const file of summary.files) {\n const lineCount = outputConfig.includeLinesCount ? ` lines=\"${file.lines}\"` : '';\n lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\"${lineCount}>`);\n for (const item of file.items) {\n const prefix = item.startsWith('[ln ') ? '' : '- ';\n lines.push(`${childIndent}${prefix}${item}`);\n }\n lines.push(`${childIndent}</file>`);\n }\n\n for (const folder of summary.folders) {\n lines.push(renderFolderSummary(folder, outputConfig, depth + 1));\n }\n\n if (summary.omittedFiles.length > 0) {\n lines.push(`${childIndent}<omitted_files>`);\n for (const omittedFile of summary.omittedFiles) {\n lines.push(`${childIndent}- ${escapeText(omittedFile)}`);\n }\n lines.push(`${childIndent}</omitted_files>`);\n }\n\n lines.push(`${indent}</folder>`);\n return lines.join('\\n');\n}\n\nasync function summarizeFile(\n root: string,\n repoRoot: string,\n fullPath: string,\n outputConfig: PeekOutputConfig,\n): Promise<FileSummary | null> {\n const extension = path.extname(fullPath);\n const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);\n\n if (extension === '.md' || extension === '.mdx') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'markdown',\n lines: countLines(text),\n items: extractMarkdownHeadings(text),\n };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'typescript',\n lines: countLines(text),\n items: extractTypeScriptSymbols(text),\n };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return {\n path: relativePath,\n kind: 'python',\n lines: countLines(text),\n items: extractPythonSymbols(text),\n };\n }\n return null;\n}\n\nfunction isTypeScriptLikeFile(extension: string, fullPath: string): boolean {\n return ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs'].includes(extension) && !fullPath.endsWith('.d.ts');\n}\n\nfunction extractMarkdownHeadings(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => /^(#{1,6})\\s+(.+?)\\s*$/.exec(line))\n .filter((match): match is RegExpExecArray => match !== null)\n .map((match) => `h${match[1].length} ${match[2].replace(/\\s+#+$/, '').trim()}`);\n}\n\nfunction extractTypeScriptSymbols(text: string): string[] {\n const items: Array<{ start: number; value: string }> = [];\n const source = stripBlockComments(text);\n const patterns: Array<[RegExp, string]> = [\n [/^(?:export\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/gm, 'class'],\n [/^(?:export\\s+)?interface\\s+([A-Za-z_$][\\w$]*)/gm, 'interface'],\n [/^(?:export\\s+)?type\\s+([A-Za-z_$][\\w$]*)\\s*=/gm, 'type'],\n [/^(?:export\\s+)?(?:declare\\s+)?(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm, 'const'],\n [\n /^(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_$][\\w$]*)\\s*\\(/gm,\n 'function',\n ],\n ];\n\n for (const [pattern, label] of patterns) {\n for (const match of source.matchAll(pattern)) {\n const range = lineRangeForTypeScriptSymbol(source, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${label} ${match[1]}`,\n });\n if (label === 'class') {\n items.push(...extractTypeScriptClassMethods(source, match.index ?? 0));\n }\n }\n }\n items.push(...extractTypeScriptTestCalls(source));\n return uniqueSortedItems(items);\n}\n\nfunction extractTypeScriptTestCalls(text: string): Array<{ start: number; value: string }> {\n const items: Array<{ start: number; value: string }> = [];\n const pattern = /^\\s*(describe|test|it)\\s*\\(\\s*(['\"`])((?:\\\\.|(?!\\2)[\\s\\S])*?)\\2/gm;\n\n for (const match of text.matchAll(pattern)) {\n const callName = match[1];\n const quote = match[2];\n const title = match[3];\n const range = lineRangeForCallExpression(text, match.index ?? 0);\n items.push({\n start: range.start,\n value: `${formatLineRange(range)} ${callName}(${quote}${title}${quote})`,\n });\n }\n\n return items;\n}\n\nfunction extractTypeScriptClassMethods(\n text: string,\n classIndex: number,\n): Array<{ start: number; value: string }> {\n const openingBrace = text.indexOf('{', classIndex);\n if (openingBrace === -1) return [];\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1) return [];\n\n const classBody = text.slice(openingBrace + 1, closingBrace);\n const bodyOffset = openingBrace + 1;\n const items: Array<{ start: number; value: string }> = [];\n const methodPattern =\n /^([ \\t]+)(?:(?:public|private|protected|static|async|override|readonly|get|set)\\s+)*([A-Za-z_$][\\w$]*)\\s*\\(/gm;\n const matches = Array.from(classBody.matchAll(methodPattern));\n const memberIndent = Math.min(...matches.map((match) => match[1]?.length ?? 0));\n\n for (const match of matches) {\n if ((match[1]?.length ?? 0) !== memberIndent) continue;\n const name = match[2];\n if (!name || ['if', 'for', 'while', 'switch', 'catch'].includes(name)) continue;\n const index = bodyOffset + (match.index ?? 0);\n const blockRange = lineRangeForTypeScriptMethod(text, index, closingBrace);\n items.push({\n start: blockRange.start,\n value: `${formatLineRange(blockRange)} method ${name}`,\n });\n }\n\n return items;\n}\n\nfunction lineRangeForTypeScriptMethod(\n text: string,\n index: number,\n classClosingBrace: number,\n): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1 || openingParen > classClosingBrace) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1 || closingParen > classClosingBrace) return { start, end: start };\n\n const openingBrace = findMethodBodyOpeningBrace(text, closingParen, classClosingBrace);\n if (openingBrace === -1 || openingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace === -1 || closingBrace > classClosingBrace) {\n return { start, end: lineNumberAt(text, closingParen) };\n }\n\n return { start, end: lineNumberAt(text, closingBrace) };\n}\n\nfunction findMethodBodyOpeningBrace(\n text: string,\n closingParen: number,\n classClosingBrace: number,\n): number {\n const lineEnd = text.indexOf('\\n', closingParen);\n const searchEnd = lineEnd === -1 ? classClosingBrace : Math.min(lineEnd, classClosingBrace);\n const sameLineBrace = text.lastIndexOf('{', searchEnd);\n if (sameLineBrace > closingParen) return sameLineBrace;\n return text.indexOf('{', closingParen);\n}\n\nfunction extractPythonSymbols(text: string): string[] {\n const items: string[] = [];\n const lines = text.split(/\\r?\\n/);\n for (const [index, line] of lines.entries()) {\n const lineNumber = index + 1;\n let match = /^class\\s+([A-Za-z_]\\w*)\\s*[:(]/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} class ${match[1]}`);\n continue;\n }\n match = /^(?:async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(line);\n if (match) {\n items.push(`${formatLineRange(lineRangeForPythonBlock(lines, index))} function ${match[1]}`);\n continue;\n }\n match = /^([A-Z][A-Z0-9_]*)\\s*[:=]/.exec(line);\n if (match) items.push(`${formatLineRange({ start: lineNumber, end: lineNumber })} const ${match[1]}`);\n }\n return unique(items);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n return SKIP_DIRS.has(name);\n}\n\nfunction shouldSkipFile(name: string): boolean {\n return SKIP_FILES.has(name) || name.endsWith('.map');\n}\n\nfunction stripBlockComments(text: string): string {\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (comment) =>\n comment.replace(/[^\\r\\n]/g, ' '),\n );\n}\n\nfunction unique(items: string[]): string[] {\n return Array.from(new Set(items));\n}\n\nfunction uniqueSortedItems(items: Array<{ start: number; value: string }>): string[] {\n const seen = new Set<string>();\n return items\n .sort((a, b) => a.start - b.start || a.value.localeCompare(b.value))\n .filter((item) => {\n if (seen.has(item.value)) return false;\n seen.add(item.value);\n return true;\n })\n .map((item) => item.value);\n}\n\nfunction createLimiter(maxConcurrent: number): <T>(task: () => Promise<T>) => Promise<T> {\n let active = 0;\n const queue: Array<() => void> = [];\n\n return async function limit<T>(task: () => Promise<T>): Promise<T> {\n if (active >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n queue.shift()?.();\n }\n };\n}\n\nfunction lineNumberAt(text: string, index: number): number {\n let lineNumber = 1;\n for (let offset = 0; offset < index; offset += 1) {\n if (text.charCodeAt(offset) === 10) lineNumber += 1;\n }\n return lineNumber;\n}\n\nfunction lineRangeForTypeScriptSymbol(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const lineEnd = text.indexOf('\\n', index);\n const declarationEnd = lineEnd === -1 ? text.length : lineEnd;\n const openingBrace = text.indexOf('{', index);\n\n if (openingBrace !== -1 && openingBrace < declarationEnd) {\n const closingBrace = findMatchingBrace(text, openingBrace);\n if (closingBrace !== -1) return { start, end: lineNumberAt(text, closingBrace) };\n }\n\n const semicolon = text.indexOf(';', index);\n if (semicolon !== -1 && semicolon < declarationEnd) {\n return { start, end: lineNumberAt(text, semicolon) };\n }\n\n return { start, end: start };\n}\n\nfunction findMatchingBrace(text: string, openingBrace: number): number {\n let depth = 0;\n for (let index = openingBrace; index < text.length; index += 1) {\n const char = text[index];\n if (char === '{') depth += 1;\n if (char === '}') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForCallExpression(text: string, index: number): { start: number; end: number } {\n const start = lineNumberAt(text, index);\n const openingParen = text.indexOf('(', index);\n if (openingParen === -1) return { start, end: start };\n\n const closingParen = findMatchingParen(text, openingParen);\n if (closingParen === -1) return { start, end: start };\n return { start, end: lineNumberAt(text, closingParen) };\n}\n\nfunction findMatchingParen(text: string, openingParen: number): number {\n let depth = 0;\n let quote: string | null = null;\n\n for (let index = openingParen; index < text.length; index += 1) {\n const char = text[index];\n if (quote) {\n if (char === '\\\\') {\n index += 1;\n continue;\n }\n if (char === quote) quote = null;\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n quote = char;\n continue;\n }\n if (char === '(') depth += 1;\n if (char === ')') {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction lineRangeForPythonBlock(\n lines: string[],\n startIndex: number,\n): { start: number; end: number } {\n const startIndent = indentationLength(lines[startIndex] ?? '');\n let endIndex = startIndex;\n\n for (let index = startIndex + 1; index < lines.length; index += 1) {\n const line = lines[index] ?? '';\n if (line.trim() === '') {\n endIndex = index;\n continue;\n }\n if (indentationLength(line) <= startIndent) break;\n endIndex = index;\n }\n\n while (endIndex > startIndex && (lines[endIndex] ?? '').trim() === '') endIndex -= 1;\n return { start: startIndex + 1, end: endIndex + 1 };\n}\n\nfunction indentationLength(line: string): number {\n return line.match(/^\\s*/)?.[0].length ?? 0;\n}\n\nfunction formatLineRange(range: { start: number; end: number }): string {\n return range.start === range.end ? `[ln ${range.start}]` : `[ln ${range.start}-${range.end}]`;\n}\n\nfunction formatFilePath(\n root: string,\n repoRoot: string,\n fullPath: string,\n filePaths: PeekFilePathMode,\n): string {\n if (filePaths === 'concise') return `/${path.basename(fullPath)}`;\n const base = isInsidePath(repoRoot, fullPath) ? repoRoot : root;\n return toPosix(path.relative(base, fullPath));\n}\n\nfunction resolveOutputConfig(options: ScanOptions): PeekOutputConfig {\n const presetName = options.preset ?? 'human';\n const preset = PEEK_OUTPUT_PRESETS[presetName];\n if (!preset) throw new Error(`Unknown peek output preset: ${presetName}`);\n\n return {\n deep: options.deep ?? preset.deep,\n indent: options.indent ?? preset.indent,\n filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),\n includeLinesCount:\n options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount,\n };\n}\n\nfunction normalizeFilePathMode(value: PeekFilePathMode): PeekFilePathMode {\n if (value === 'concise' || value === 'full') return value;\n throw new Error(`Unknown peek file path mode: ${String(value)}`);\n}\n\nfunction countLines(text: string): number {\n if (text.length === 0) return 0;\n const newlineCount = text.match(/\\r\\n|\\r|\\n/g)?.length ?? 0;\n return newlineCount + (/(?:\\r\\n|\\r|\\n)$/.test(text) ? 0 : 1);\n}\n\nfunction hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== undefined ||\n options.includeLinesCount !== undefined ||\n options.include_lines_count !== undefined\n );\n}\n\nfunction isInsidePath(parent: string, child: string): boolean {\n const relativePath = path.relative(parent, child);\n return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<');\n}\n\nasync function checkIgnoredPaths(root: string, fullPaths: string[]): Promise<Set<string>> {\n const relativePaths = fullPaths\n .map((fullPath) => toPosix(path.relative(root, fullPath)))\n .filter((relativePath) => relativePath && !relativePath.startsWith('..'));\n\n if (relativePaths.length === 0) return new Set();\n\n return await new Promise((resolve) => {\n const child = spawn('git', ['-C', root, 'check-ignore', '--stdin'], {\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n let stdout = '';\n\n child.stdout.setEncoding('utf8');\n child.stdout.on('data', (chunk) => {\n stdout += chunk;\n });\n child.on('error', () => resolve(new Set()));\n child.on('close', (code) => {\n if (code !== 0 && code !== 1) {\n resolve(new Set());\n return;\n }\n resolve(new Set(stdout.split(/\\r?\\n/).filter(Boolean)));\n });\n\n child.stdin.end(relativePaths.join('\\n'));\n });\n}\n\nasync function findRepoRoot(root: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', ['-C', root, 'rev-parse', '--show-toplevel']);\n return path.resolve(stdout.trim());\n } catch {\n return root;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davstack/peek",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Peek at concise, agent-friendly folder summaries.",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=20"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@davstack/cli-utils": "
|
|
34
|
+
"@davstack/cli-utils": "^1.3.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.9.1",
|