@davstack/peek 0.1.0 → 0.1.1
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/bin/davstack-peek.mjs +0 -0
- package/dist/cli-spec.js +38 -9
- package/dist/cli-spec.js.map +1 -1
- package/dist/cli.js +38 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +32 -8
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/bin/davstack-peek.mjs
CHANGED
|
File without changes
|
package/dist/cli-spec.js
CHANGED
|
@@ -85,7 +85,8 @@ function renderFolderSummary(summary, outputConfig, depth = 0) {
|
|
|
85
85
|
const childIndent = outputConfig.indent ? " ".repeat(depth) : "";
|
|
86
86
|
const lines = [`${indent}<folder path="${escapeAttribute(summary.path)}">`];
|
|
87
87
|
for (const file of summary.files) {
|
|
88
|
-
|
|
88
|
+
const lineCount = outputConfig.includeLinesCount ? ` lines="${file.lines}"` : "";
|
|
89
|
+
lines.push(`${childIndent}<file path="${escapeAttribute(file.path)}"${lineCount}>`);
|
|
89
90
|
for (const item of file.items) {
|
|
90
91
|
const prefix = item.startsWith("[ln ") ? "" : "- ";
|
|
91
92
|
lines.push(`${childIndent}${prefix}${item}`);
|
|
@@ -110,15 +111,30 @@ async function summarizeFile(root, repoRoot, fullPath, outputConfig) {
|
|
|
110
111
|
const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);
|
|
111
112
|
if (extension === ".md" || extension === ".mdx") {
|
|
112
113
|
const text = await readFile(fullPath, "utf8");
|
|
113
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
path: relativePath,
|
|
116
|
+
kind: "markdown",
|
|
117
|
+
lines: countLines(text),
|
|
118
|
+
items: extractMarkdownHeadings(text)
|
|
119
|
+
};
|
|
114
120
|
}
|
|
115
121
|
if (isTypeScriptLikeFile(extension, fullPath)) {
|
|
116
122
|
const text = await readFile(fullPath, "utf8");
|
|
117
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
path: relativePath,
|
|
125
|
+
kind: "typescript",
|
|
126
|
+
lines: countLines(text),
|
|
127
|
+
items: extractTypeScriptSymbols(text)
|
|
128
|
+
};
|
|
118
129
|
}
|
|
119
130
|
if (extension === ".py") {
|
|
120
131
|
const text = await readFile(fullPath, "utf8");
|
|
121
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
path: relativePath,
|
|
134
|
+
kind: "python",
|
|
135
|
+
lines: countLines(text),
|
|
136
|
+
items: extractPythonSymbols(text)
|
|
137
|
+
};
|
|
122
138
|
}
|
|
123
139
|
return null;
|
|
124
140
|
}
|
|
@@ -297,15 +313,21 @@ function resolveOutputConfig(options) {
|
|
|
297
313
|
return {
|
|
298
314
|
deep: options.deep ?? preset.deep,
|
|
299
315
|
indent: options.indent ?? preset.indent,
|
|
300
|
-
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths)
|
|
316
|
+
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),
|
|
317
|
+
includeLinesCount: options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount
|
|
301
318
|
};
|
|
302
319
|
}
|
|
303
320
|
function normalizeFilePathMode(value) {
|
|
304
321
|
if (value === "concise" || value === "full") return value;
|
|
305
322
|
throw new Error(`Unknown peek file path mode: ${String(value)}`);
|
|
306
323
|
}
|
|
324
|
+
function countLines(text) {
|
|
325
|
+
if (text.length === 0) return 0;
|
|
326
|
+
const newlineCount = text.match(/\r\n|\r|\n/g)?.length ?? 0;
|
|
327
|
+
return newlineCount + (/(?:\r\n|\r|\n)$/.test(text) ? 0 : 1);
|
|
328
|
+
}
|
|
307
329
|
function hasOutputOverrides(options) {
|
|
308
|
-
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0;
|
|
330
|
+
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0 || options.includeLinesCount !== void 0 || options.include_lines_count !== void 0;
|
|
309
331
|
}
|
|
310
332
|
function isInsidePath(parent, child) {
|
|
311
333
|
const relativePath = path.relative(parent, child);
|
|
@@ -350,12 +372,14 @@ var init_src = __esm({
|
|
|
350
372
|
human: {
|
|
351
373
|
deep: true,
|
|
352
374
|
indent: true,
|
|
353
|
-
filePaths: "full"
|
|
375
|
+
filePaths: "full",
|
|
376
|
+
includeLinesCount: true
|
|
354
377
|
},
|
|
355
378
|
agent: {
|
|
356
379
|
deep: true,
|
|
357
380
|
indent: false,
|
|
358
|
-
filePaths: "concise"
|
|
381
|
+
filePaths: "concise",
|
|
382
|
+
includeLinesCount: true
|
|
359
383
|
}
|
|
360
384
|
};
|
|
361
385
|
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -389,6 +413,10 @@ var outputFlags = {
|
|
|
389
413
|
file_paths: {
|
|
390
414
|
type: "string",
|
|
391
415
|
description: "File path style: concise or full"
|
|
416
|
+
},
|
|
417
|
+
"include-lines-count": {
|
|
418
|
+
type: "boolean",
|
|
419
|
+
description: "Include total line counts on file tags"
|
|
392
420
|
}
|
|
393
421
|
};
|
|
394
422
|
function resolveCliScanOptions(flags) {
|
|
@@ -404,7 +432,8 @@ function resolveCliScanOptions(flags) {
|
|
|
404
432
|
deep: flags.deep,
|
|
405
433
|
preset,
|
|
406
434
|
indent: flags.indent,
|
|
407
|
-
filePaths
|
|
435
|
+
filePaths,
|
|
436
|
+
includeLinesCount: flags["include-lines-count"]
|
|
408
437
|
};
|
|
409
438
|
}
|
|
410
439
|
var cliSpec = {
|
package/dist/cli-spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cli-spec.ts"],"sourcesContent":["import { execFile } 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};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\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};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\">`);\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 { path: relativePath, kind: 'markdown', items: extractMarkdownHeadings(text) };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'typescript', items: extractTypeScriptSymbols(text) };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'python', items: extractPythonSymbols(text) };\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 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 };\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 hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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 description: 'File path style: concise or full',\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 };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\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 } 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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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 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 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":";;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,gBAAgB;AACzB,SAAS,OAAO,SAAS,UAAU,iBAAiB;AACpD,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAiE1B,eAAsB,eAAe,QAAgB,UAAuB,CAAC,GAAoB;AAC/F,QAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAM,WAAW,MAAM,aAAa,IAAI;AACxC,QAAM,eAAe,oBAAoB,OAAO;AAChD,QAAM,UAAU,MAAM,qBAAqB,MAAM,UAAU,MAAM,aAAa,MAAM,YAAY;AAEhG,SAAO,GAAG,oBAAoB,SAAS,YAAY,CAAC;AAAA;AACtD;AAEA,eAAsB,mBACpB,QACA,UAAuB,CAAC,GACoB;AAC5C,QAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAM,UAAU,MAAM,eAAe,MAAM,OAAO;AAClD,QAAM,UAAU,KAAK,KAAK,MAAM,mBAAmB;AACnD,QAAM,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,UAAU,SAAS,SAAS,MAAM;AACxC,SAAO,EAAE,MAAM,SAAS,QAAQ;AAClC;AAEA,eAAsB,WAAW,QAAgB,UAAuB,CAAC,GAAoB;AAC3F,QAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAM,gBAAgB,KAAK,KAAK,MAAM,mBAAmB;AACzD,MAAI;AACF,QAAI,CAAC,QAAQ,QAAQ,CAAC,mBAAmB,OAAO,EAAG,QAAO,MAAM,SAAS,eAAe,MAAM;AAAA,EAChG,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,SAAU,OAAM;AAAA,EAChE;AACA,UAAQ,MAAM,mBAAmB,MAAM,OAAO,GAAG;AACnD;AAEA,eAAe,qBACb,MACA,UACA,KACA,MACA,cACwB;AACxB,QAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,QAAM,QAAuB,CAAC;AAC9B,QAAM,UAA2B,CAAC;AAClC,QAAM,eAAyB,CAAC;AAEhC,aAAW,SAAS,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,GAAG;AACxE,UAAM,WAAW,KAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,QAAI,MAAM,aAAa,MAAM,QAAQ,EAAG;AAExC,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,CAAC,QAAQ,oBAAoB,MAAM,IAAI,EAAG;AAC9C,cAAQ,KAAK,MAAM,qBAAqB,MAAM,UAAU,UAAU,MAAM,YAAY,CAAC;AACrF;AAAA,IACF;AACA,QAAI,CAAC,MAAM,OAAO,KAAK,eAAe,MAAM,IAAI,EAAG;AAEnD,UAAM,UAAU,MAAM,cAAc,MAAM,UAAU,UAAU,YAAY;AAC1E,QAAI,SAAS;AACX,YAAM,KAAK,OAAO;AAAA,IACpB,OAAO;AACL,mBAAa,KAAK,MAAM,IAAI;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,iBAAiB,MAAM,UAAU,GAAG;AAAA,IAC1C,OAAO,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,IACxD,SAAS,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,IAC5D,cAAc,aAAa,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EAC9D;AACF;AAEA,SAAS,iBAAiB,MAAc,UAAkB,KAAqB;AAC7E,QAAM,OAAO,aAAa,UAAU,GAAG,IAAI,WAAW;AACtD,QAAM,eAAe,QAAQ,KAAK,SAAS,MAAM,GAAG,CAAC;AACrD,SAAO,gBAAgB;AACzB;AAEA,SAAS,oBACP,SACA,cACA,QAAQ,GACA;AACR,QAAM,SAAS,aAAa,SAAS,IAAK,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,IAAI;AAC3E,QAAM,cAAc,aAAa,SAAS,IAAK,OAAO,KAAK,IAAI;AAC/D,QAAM,QAAQ,CAAC,GAAG,MAAM,iBAAiB,gBAAgB,QAAQ,IAAI,CAAC,IAAI;AAE1E,aAAW,QAAQ,QAAQ,OAAO;AAChC,UAAM,YAAY,aAAa,oBAAoB,WAAW,KAAK,KAAK,MAAM;AAC9E,UAAM,KAAK,GAAG,WAAW,eAAe,gBAAgB,KAAK,IAAI,CAAC,IAAI,SAAS,GAAG;AAClF,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,SAAS,KAAK,WAAW,MAAM,IAAI,KAAK;AAC9C,YAAM,KAAK,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,EAAE;AAAA,IAC7C;AACA,UAAM,KAAK,GAAG,WAAW,SAAS;AAAA,EACpC;AAEA,aAAW,UAAU,QAAQ,SAAS;AACpC,UAAM,KAAK,oBAAoB,QAAQ,cAAc,QAAQ,CAAC,CAAC;AAAA,EACjE;AAEA,MAAI,QAAQ,aAAa,SAAS,GAAG;AACnC,UAAM,KAAK,GAAG,WAAW,iBAAiB;AAC1C,eAAW,eAAe,QAAQ,cAAc;AAC9C,YAAM,KAAK,GAAG,WAAW,KAAK,WAAW,WAAW,CAAC,EAAE;AAAA,IACzD;AACA,UAAM,KAAK,GAAG,WAAW,kBAAkB;AAAA,EAC7C;AAEA,QAAM,KAAK,GAAG,MAAM,WAAW;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAe,cACb,MACA,UACA,UACA,cAC6B;AAC7B,QAAM,YAAY,KAAK,QAAQ,QAAQ;AACvC,QAAM,eAAe,eAAe,MAAM,UAAU,UAAU,aAAa,SAAS;AAEpF,MAAI,cAAc,SAAS,cAAc,QAAQ;AAC/C,UAAM,OAAO,MAAM,SAAS,UAAU,MAAM;AAC5C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,WAAW,IAAI;AAAA,MACtB,OAAO,wBAAwB,IAAI;AAAA,IACrC;AAAA,EACF;AACA,MAAI,qBAAqB,WAAW,QAAQ,GAAG;AAC7C,UAAM,OAAO,MAAM,SAAS,UAAU,MAAM;AAC5C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,WAAW,IAAI;AAAA,MACtB,OAAO,yBAAyB,IAAI;AAAA,IACtC;AAAA,EACF;AACA,MAAI,cAAc,OAAO;AACvB,UAAM,OAAO,MAAM,SAAS,UAAU,MAAM;AAC5C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,WAAW,IAAI;AAAA,MACtB,OAAO,qBAAqB,IAAI;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,WAAmB,UAA2B;AAC1E,SAAO,CAAC,OAAO,QAAQ,QAAQ,QAAQ,OAAO,MAAM,EAAE,SAAS,SAAS,KAAK,CAAC,SAAS,SAAS,OAAO;AACzG;AAEA,SAAS,wBAAwB,MAAwB;AACvD,SAAO,KACJ,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,wBAAwB,KAAK,IAAI,CAAC,EAChD,OAAO,CAAC,UAAoC,UAAU,IAAI,EAC1D,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,EAAE,MAAM,IAAI,MAAM,CAAC,EAAE,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC,EAAE;AAClF;AAEA,SAAS,yBAAyB,MAAwB;AACxD,QAAM,QAAkB,CAAC;AACzB,QAAM,SAAS,mBAAmB,IAAI;AACtC,QAAM,WAAoC;AAAA,IACxC,CAAC,+DAA+D,OAAO;AAAA,IACvE,CAAC,mDAAmD,WAAW;AAAA,IAC/D,CAAC,kDAAkD,MAAM;AAAA,IACzD,CAAC,0EAA0E,OAAO;AAAA,IAClF;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,aAAW,CAAC,SAAS,KAAK,KAAK,UAAU;AACvC,eAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAC5C,YAAM,QAAQ,6BAA6B,QAAQ,MAAM,SAAS,CAAC;AACnE,YAAM,KAAK,GAAG,gBAAgB,KAAK,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,EAAE;AAAA,IAC7D;AAAA,EACF;AACA,QAAM,KAAK,GAAG,2BAA2B,MAAM,CAAC;AAChD,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,2BAA2B,MAAwB;AAC1D,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAU;AAEhB,aAAW,SAAS,KAAK,SAAS,OAAO,GAAG;AAC1C,UAAM,WAAW,MAAM,CAAC;AACxB,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,QAAQ,2BAA2B,MAAM,MAAM,SAAS,CAAC;AAC/D,UAAM,KAAK,GAAG,gBAAgB,KAAK,CAAC,IAAI,QAAQ,IAAI,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG;AAAA,EAC9E;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAwB;AACpD,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC3C,UAAM,aAAa,QAAQ;AAC3B,QAAI,QAAQ,iCAAiC,KAAK,IAAI;AACtD,QAAI,OAAO;AACT,YAAM,KAAK,GAAG,gBAAgB,wBAAwB,OAAO,KAAK,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,EAAE;AACxF;AAAA,IACF;AACA,YAAQ,0CAA0C,KAAK,IAAI;AAC3D,QAAI,OAAO;AACT,YAAM,KAAK,GAAG,gBAAgB,wBAAwB,OAAO,KAAK,CAAC,CAAC,aAAa,MAAM,CAAC,CAAC,EAAE;AAC3F;AAAA,IACF;AACA,YAAQ,4BAA4B,KAAK,IAAI;AAC7C,QAAI,MAAO,OAAM,KAAK,GAAG,gBAAgB,EAAE,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,EAAE;AAAA,EACtG;AACA,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,oBAAoB,MAAuB;AAClD,SAAO,UAAU,IAAI,IAAI;AAC3B;AAEA,SAAS,eAAe,MAAuB;AAC7C,SAAO,WAAW,IAAI,IAAI,KAAK,KAAK,SAAS,MAAM;AACrD;AAEA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KAAK;AAAA,IAAQ;AAAA,IAAqB,CAAC,YACxC,QAAQ,QAAQ,YAAY,GAAG;AAAA,EACjC;AACF;AAEA,SAAS,OAAO,OAA2B;AACzC,SAAO,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC;AAClC;AAEA,SAAS,aAAa,MAAc,OAAuB;AACzD,MAAI,aAAa;AACjB,WAAS,SAAS,GAAG,SAAS,OAAO,UAAU,GAAG;AAChD,QAAI,KAAK,WAAW,MAAM,MAAM,GAAI,eAAc;AAAA,EACpD;AACA,SAAO;AACT;AAEA,SAAS,6BAA6B,MAAc,OAA+C;AACjG,QAAM,QAAQ,aAAa,MAAM,KAAK;AACtC,QAAM,UAAU,KAAK,QAAQ,MAAM,KAAK;AACxC,QAAM,iBAAiB,YAAY,KAAK,KAAK,SAAS;AACtD,QAAM,eAAe,KAAK,QAAQ,KAAK,KAAK;AAE5C,MAAI,iBAAiB,MAAM,eAAe,gBAAgB;AACxD,UAAM,eAAe,kBAAkB,MAAM,YAAY;AACzD,QAAI,iBAAiB,GAAI,QAAO,EAAE,OAAO,KAAK,aAAa,MAAM,YAAY,EAAE;AAAA,EACjF;AAEA,QAAM,YAAY,KAAK,QAAQ,KAAK,KAAK;AACzC,MAAI,cAAc,MAAM,YAAY,gBAAgB;AAClD,WAAO,EAAE,OAAO,KAAK,aAAa,MAAM,SAAS,EAAE;AAAA,EACrD;AAEA,SAAO,EAAE,OAAO,KAAK,MAAM;AAC7B;AAEA,SAAS,kBAAkB,MAAc,cAA8B;AACrE,MAAI,QAAQ;AACZ,WAAS,QAAQ,cAAc,QAAQ,KAAK,QAAQ,SAAS,GAAG;AAC9D,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,SAAS,IAAK,UAAS;AAC3B,QAAI,SAAS,KAAK;AAChB,eAAS;AACT,UAAI,UAAU,EAAG,QAAO;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,2BAA2B,MAAc,OAA+C;AAC/F,QAAM,QAAQ,aAAa,MAAM,KAAK;AACtC,QAAM,eAAe,KAAK,QAAQ,KAAK,KAAK;AAC5C,MAAI,iBAAiB,GAAI,QAAO,EAAE,OAAO,KAAK,MAAM;AAEpD,QAAM,eAAe,kBAAkB,MAAM,YAAY;AACzD,MAAI,iBAAiB,GAAI,QAAO,EAAE,OAAO,KAAK,MAAM;AACpD,SAAO,EAAE,OAAO,KAAK,aAAa,MAAM,YAAY,EAAE;AACxD;AAEA,SAAS,kBAAkB,MAAc,cAA8B;AACrE,MAAI,QAAQ;AACZ,MAAI,QAAuB;AAE3B,WAAS,QAAQ,cAAc,QAAQ,KAAK,QAAQ,SAAS,GAAG;AAC9D,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,OAAO;AACT,UAAI,SAAS,MAAM;AACjB,iBAAS;AACT;AAAA,MACF;AACA,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,KAAK;AAChD,cAAQ;AACR;AAAA,IACF;AACA,QAAI,SAAS,IAAK,UAAS;AAC3B,QAAI,SAAS,KAAK;AAChB,eAAS;AACT,UAAI,UAAU,EAAG,QAAO;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,wBACP,OACA,YACgC;AAChC,QAAM,cAAc,kBAAkB,MAAM,UAAU,KAAK,EAAE;AAC7D,MAAI,WAAW;AAEf,WAAS,QAAQ,aAAa,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACjE,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,KAAK,MAAM,IAAI;AACtB,iBAAW;AACX;AAAA,IACF;AACA,QAAI,kBAAkB,IAAI,KAAK,YAAa;AAC5C,eAAW;AAAA,EACb;AAEA,SAAO,WAAW,eAAe,MAAM,QAAQ,KAAK,IAAI,KAAK,MAAM,GAAI,aAAY;AACnF,SAAO,EAAE,OAAO,aAAa,GAAG,KAAK,WAAW,EAAE;AACpD;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,KAAK,MAAM,MAAM,IAAI,CAAC,EAAE,UAAU;AAC3C;AAEA,SAAS,gBAAgB,OAA+C;AACtE,SAAO,MAAM,UAAU,MAAM,MAAM,OAAO,MAAM,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,MAAM,GAAG;AAC5F;AAEA,SAAS,eACP,MACA,UACA,UACA,WACQ;AACR,MAAI,cAAc,UAAW,QAAO,IAAI,KAAK,SAAS,QAAQ,CAAC;AAC/D,QAAM,OAAO,aAAa,UAAU,QAAQ,IAAI,WAAW;AAC3D,SAAO,QAAQ,KAAK,SAAS,MAAM,QAAQ,CAAC;AAC9C;AAEA,SAAS,oBAAoB,SAAwC;AACnE,QAAM,aAAa,QAAQ,UAAU;AACrC,QAAM,SAAS,oBAAoB,UAAU;AAC7C,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,+BAA+B,UAAU,EAAE;AAExE,SAAO;AAAA,IACL,MAAM,QAAQ,QAAQ,OAAO;AAAA,IAC7B,QAAQ,QAAQ,UAAU,OAAO;AAAA,IACjC,WAAW,sBAAsB,QAAQ,aAAa,QAAQ,cAAc,OAAO,SAAS;AAAA,IAC5F,mBACE,QAAQ,qBAAqB,QAAQ,uBAAuB,OAAO;AAAA,EACvE;AACF;AAEA,SAAS,sBAAsB,OAA2C;AACxE,MAAI,UAAU,aAAa,UAAU,OAAQ,QAAO;AACpD,QAAM,IAAI,MAAM,gCAAgC,OAAO,KAAK,CAAC,EAAE;AACjE;AAEA,SAAS,WAAW,MAAsB;AACxC,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,eAAe,KAAK,MAAM,aAAa,GAAG,UAAU;AAC1D,SAAO,gBAAgB,kBAAkB,KAAK,IAAI,IAAI,IAAI;AAC5D;AAEA,SAAS,mBAAmB,SAA+B;AACzD,SACE,QAAQ,WAAW,UACnB,QAAQ,WAAW,UACnB,QAAQ,cAAc,UACtB,QAAQ,eAAe,UACvB,QAAQ,sBAAsB,UAC9B,QAAQ,wBAAwB;AAEpC;AAEA,SAAS,aAAa,QAAgB,OAAwB;AAC5D,QAAM,eAAe,KAAK,SAAS,QAAQ,KAAK;AAChD,SAAO,iBAAiB,MAAO,CAAC,aAAa,WAAW,IAAI,KAAK,CAAC,KAAK,WAAW,YAAY;AAChG;AAEA,SAAS,QAAQ,OAAuB;AACtC,SAAO,MAAM,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AACvC;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,MAAM,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,QAAQ;AAC5D;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,MAAM,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM;AAC1D;AAEA,eAAe,aAAa,MAAc,UAAoC;AAC5E,QAAM,eAAe,QAAQ,KAAK,SAAS,MAAM,QAAQ,CAAC;AAC1D,MAAI,CAAC,gBAAgB,aAAa,WAAW,IAAI,EAAG,QAAO;AAE3D,MAAI;AACF,UAAM,cAAc,OAAO,CAAC,MAAM,MAAM,gBAAgB,WAAW,MAAM,YAAY,CAAC;AACtF,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAQ,MAAqC;AACnD,QAAI,SAAS,KAAK,SAAS,OAAO,SAAS,SAAU,QAAO;AAC5D,WAAO;AAAA,EACT;AACF;AAEA,eAAe,aAAa,MAA+B;AACzD,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc,OAAO,CAAC,MAAM,MAAM,aAAa,iBAAiB,CAAC;AAC1F,WAAO,KAAK,QAAQ,OAAO,KAAK,CAAC;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AApfA,IAKa,qBACP,eA6BO,qBAsBP,WASA;AAlEN;AAAA;AAAA;AAKO,IAAM,sBAAsB;AACnC,IAAM,gBAAgB,UAAU,QAAQ;AA6BjC,IAAM,sBAAsE;AAAA,MACjF,OAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,mBAAmB;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,mBAAmB;AAAA,MACrB;AAAA,IACF;AASA,IAAM,YAAY,oBAAI,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,IAAM,aAAa,oBAAI,IAAI,CAAC,mBAAmB,CAAC;AAAA;AAAA;;;AC/DhD,IAAM,cAAc;AAAA,EAClB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,YAAY;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,uBAAuB;AAAA,IACrB,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAEA,SAAS,sBAAsB,OAA6C;AAC1E,QAAM,QAAQ,MAAM,UAAU;AAC9B,QAAM,QAAQ,MAAM,UAAU;AAC9B,MAAI,SAAS,MAAO,OAAM,IAAI,MAAM,gDAAgD;AAEpF,QAAM,SAA2C,QAAQ,UAAU,QAAQ,UAAU;AACrF,QAAM,YAAY,MAAM;AACxB,MAAI,cAAc,UAAa,cAAc,aAAa,cAAc,QAAQ;AAC9E,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,QAAQ,MAAM;AAAA,IACd;AAAA,IACA,mBAAmB,MAAM,qBAAqB;AAAA,EAChD;AACF;AAEO,IAAM,UAAmB;AAAA,EAC9B,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa,CAAC,EAAE,MAAM,QAAQ,UAAU,MAAM,aAAa,oBAAoB,CAAC;AAAA,EAChF,OAAO;AAAA,IACL,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EACL;AAAA,EACA,KAAK,OAAO,QAAQ;AAClB,UAAM,EAAE,YAAAA,YAAW,IAAI,MAAM;AAC7B,YAAQ,IAAI,MAAMA,YAAW,IAAI,YAAY,CAAC,GAAG,sBAAsB,IAAI,KAAK,CAAC,CAAC;AAAA,EACpF;AACF;","names":["peekFolder"]}
|
package/dist/cli.js
CHANGED
|
@@ -85,7 +85,8 @@ function renderFolderSummary(summary, outputConfig, depth = 0) {
|
|
|
85
85
|
const childIndent = outputConfig.indent ? " ".repeat(depth) : "";
|
|
86
86
|
const lines = [`${indent}<folder path="${escapeAttribute(summary.path)}">`];
|
|
87
87
|
for (const file of summary.files) {
|
|
88
|
-
|
|
88
|
+
const lineCount = outputConfig.includeLinesCount ? ` lines="${file.lines}"` : "";
|
|
89
|
+
lines.push(`${childIndent}<file path="${escapeAttribute(file.path)}"${lineCount}>`);
|
|
89
90
|
for (const item of file.items) {
|
|
90
91
|
const prefix = item.startsWith("[ln ") ? "" : "- ";
|
|
91
92
|
lines.push(`${childIndent}${prefix}${item}`);
|
|
@@ -110,15 +111,30 @@ async function summarizeFile(root, repoRoot, fullPath, outputConfig) {
|
|
|
110
111
|
const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);
|
|
111
112
|
if (extension === ".md" || extension === ".mdx") {
|
|
112
113
|
const text = await readFile(fullPath, "utf8");
|
|
113
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
path: relativePath,
|
|
116
|
+
kind: "markdown",
|
|
117
|
+
lines: countLines(text),
|
|
118
|
+
items: extractMarkdownHeadings(text)
|
|
119
|
+
};
|
|
114
120
|
}
|
|
115
121
|
if (isTypeScriptLikeFile(extension, fullPath)) {
|
|
116
122
|
const text = await readFile(fullPath, "utf8");
|
|
117
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
path: relativePath,
|
|
125
|
+
kind: "typescript",
|
|
126
|
+
lines: countLines(text),
|
|
127
|
+
items: extractTypeScriptSymbols(text)
|
|
128
|
+
};
|
|
118
129
|
}
|
|
119
130
|
if (extension === ".py") {
|
|
120
131
|
const text = await readFile(fullPath, "utf8");
|
|
121
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
path: relativePath,
|
|
134
|
+
kind: "python",
|
|
135
|
+
lines: countLines(text),
|
|
136
|
+
items: extractPythonSymbols(text)
|
|
137
|
+
};
|
|
122
138
|
}
|
|
123
139
|
return null;
|
|
124
140
|
}
|
|
@@ -297,15 +313,21 @@ function resolveOutputConfig(options) {
|
|
|
297
313
|
return {
|
|
298
314
|
deep: options.deep ?? preset.deep,
|
|
299
315
|
indent: options.indent ?? preset.indent,
|
|
300
|
-
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths)
|
|
316
|
+
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),
|
|
317
|
+
includeLinesCount: options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount
|
|
301
318
|
};
|
|
302
319
|
}
|
|
303
320
|
function normalizeFilePathMode(value) {
|
|
304
321
|
if (value === "concise" || value === "full") return value;
|
|
305
322
|
throw new Error(`Unknown peek file path mode: ${String(value)}`);
|
|
306
323
|
}
|
|
324
|
+
function countLines(text) {
|
|
325
|
+
if (text.length === 0) return 0;
|
|
326
|
+
const newlineCount = text.match(/\r\n|\r|\n/g)?.length ?? 0;
|
|
327
|
+
return newlineCount + (/(?:\r\n|\r|\n)$/.test(text) ? 0 : 1);
|
|
328
|
+
}
|
|
307
329
|
function hasOutputOverrides(options) {
|
|
308
|
-
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0;
|
|
330
|
+
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0 || options.includeLinesCount !== void 0 || options.include_lines_count !== void 0;
|
|
309
331
|
}
|
|
310
332
|
function isInsidePath(parent, child) {
|
|
311
333
|
const relativePath = path.relative(parent, child);
|
|
@@ -350,12 +372,14 @@ var init_src = __esm({
|
|
|
350
372
|
human: {
|
|
351
373
|
deep: true,
|
|
352
374
|
indent: true,
|
|
353
|
-
filePaths: "full"
|
|
375
|
+
filePaths: "full",
|
|
376
|
+
includeLinesCount: true
|
|
354
377
|
},
|
|
355
378
|
agent: {
|
|
356
379
|
deep: true,
|
|
357
380
|
indent: false,
|
|
358
|
-
filePaths: "concise"
|
|
381
|
+
filePaths: "concise",
|
|
382
|
+
includeLinesCount: true
|
|
359
383
|
}
|
|
360
384
|
};
|
|
361
385
|
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -392,6 +416,10 @@ var outputFlags = {
|
|
|
392
416
|
file_paths: {
|
|
393
417
|
type: "string",
|
|
394
418
|
description: "File path style: concise or full"
|
|
419
|
+
},
|
|
420
|
+
"include-lines-count": {
|
|
421
|
+
type: "boolean",
|
|
422
|
+
description: "Include total line counts on file tags"
|
|
395
423
|
}
|
|
396
424
|
};
|
|
397
425
|
function resolveCliScanOptions(flags) {
|
|
@@ -407,7 +435,8 @@ function resolveCliScanOptions(flags) {
|
|
|
407
435
|
deep: flags.deep,
|
|
408
436
|
preset,
|
|
409
437
|
indent: flags.indent,
|
|
410
|
-
filePaths
|
|
438
|
+
filePaths,
|
|
439
|
+
includeLinesCount: flags["include-lines-count"]
|
|
411
440
|
};
|
|
412
441
|
}
|
|
413
442
|
var cliSpec = {
|
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 } 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};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\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};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\">`);\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 { path: relativePath, kind: 'markdown', items: extractMarkdownHeadings(text) };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'typescript', items: extractTypeScriptSymbols(text) };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'python', items: extractPythonSymbols(text) };\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 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 };\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 hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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 description: 'File path style: concise or full',\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 };\n}\n\nexport const cliSpec: CliSpec = {\n name: 'peek',\n description: 'Print concise folder summaries for agents.',\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 } 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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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 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 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.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ type ScanOptions = {
|
|
|
5
5
|
indent?: boolean;
|
|
6
6
|
filePaths?: PeekFilePathMode;
|
|
7
7
|
file_paths?: PeekFilePathMode;
|
|
8
|
+
includeLinesCount?: boolean;
|
|
9
|
+
include_lines_count?: boolean;
|
|
8
10
|
};
|
|
9
11
|
type PeekFilePathMode = 'concise' | 'full';
|
|
10
12
|
type PeekOutputPresetName = 'agent' | 'human';
|
|
@@ -12,6 +14,7 @@ type PeekOutputConfig = {
|
|
|
12
14
|
deep: boolean;
|
|
13
15
|
indent: boolean;
|
|
14
16
|
filePaths: PeekFilePathMode;
|
|
17
|
+
includeLinesCount: boolean;
|
|
15
18
|
};
|
|
16
19
|
declare const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig>;
|
|
17
20
|
declare function scanFolderPeek(folder: string, options?: ScanOptions): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,14 @@ var PEEK_OUTPUT_PRESETS = {
|
|
|
9
9
|
human: {
|
|
10
10
|
deep: true,
|
|
11
11
|
indent: true,
|
|
12
|
-
filePaths: "full"
|
|
12
|
+
filePaths: "full",
|
|
13
|
+
includeLinesCount: true
|
|
13
14
|
},
|
|
14
15
|
agent: {
|
|
15
16
|
deep: true,
|
|
16
17
|
indent: false,
|
|
17
|
-
filePaths: "concise"
|
|
18
|
+
filePaths: "concise",
|
|
19
|
+
includeLinesCount: true
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
22
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -90,7 +92,8 @@ function renderFolderSummary(summary, outputConfig, depth = 0) {
|
|
|
90
92
|
const childIndent = outputConfig.indent ? " ".repeat(depth) : "";
|
|
91
93
|
const lines = [`${indent}<folder path="${escapeAttribute(summary.path)}">`];
|
|
92
94
|
for (const file of summary.files) {
|
|
93
|
-
|
|
95
|
+
const lineCount = outputConfig.includeLinesCount ? ` lines="${file.lines}"` : "";
|
|
96
|
+
lines.push(`${childIndent}<file path="${escapeAttribute(file.path)}"${lineCount}>`);
|
|
94
97
|
for (const item of file.items) {
|
|
95
98
|
const prefix = item.startsWith("[ln ") ? "" : "- ";
|
|
96
99
|
lines.push(`${childIndent}${prefix}${item}`);
|
|
@@ -115,15 +118,30 @@ async function summarizeFile(root, repoRoot, fullPath, outputConfig) {
|
|
|
115
118
|
const relativePath = formatFilePath(root, repoRoot, fullPath, outputConfig.filePaths);
|
|
116
119
|
if (extension === ".md" || extension === ".mdx") {
|
|
117
120
|
const text = await readFile(fullPath, "utf8");
|
|
118
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
path: relativePath,
|
|
123
|
+
kind: "markdown",
|
|
124
|
+
lines: countLines(text),
|
|
125
|
+
items: extractMarkdownHeadings(text)
|
|
126
|
+
};
|
|
119
127
|
}
|
|
120
128
|
if (isTypeScriptLikeFile(extension, fullPath)) {
|
|
121
129
|
const text = await readFile(fullPath, "utf8");
|
|
122
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
path: relativePath,
|
|
132
|
+
kind: "typescript",
|
|
133
|
+
lines: countLines(text),
|
|
134
|
+
items: extractTypeScriptSymbols(text)
|
|
135
|
+
};
|
|
123
136
|
}
|
|
124
137
|
if (extension === ".py") {
|
|
125
138
|
const text = await readFile(fullPath, "utf8");
|
|
126
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
path: relativePath,
|
|
141
|
+
kind: "python",
|
|
142
|
+
lines: countLines(text),
|
|
143
|
+
items: extractPythonSymbols(text)
|
|
144
|
+
};
|
|
127
145
|
}
|
|
128
146
|
return null;
|
|
129
147
|
}
|
|
@@ -302,15 +320,21 @@ function resolveOutputConfig(options) {
|
|
|
302
320
|
return {
|
|
303
321
|
deep: options.deep ?? preset.deep,
|
|
304
322
|
indent: options.indent ?? preset.indent,
|
|
305
|
-
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths)
|
|
323
|
+
filePaths: normalizeFilePathMode(options.filePaths ?? options.file_paths ?? preset.filePaths),
|
|
324
|
+
includeLinesCount: options.includeLinesCount ?? options.include_lines_count ?? preset.includeLinesCount
|
|
306
325
|
};
|
|
307
326
|
}
|
|
308
327
|
function normalizeFilePathMode(value) {
|
|
309
328
|
if (value === "concise" || value === "full") return value;
|
|
310
329
|
throw new Error(`Unknown peek file path mode: ${String(value)}`);
|
|
311
330
|
}
|
|
331
|
+
function countLines(text) {
|
|
332
|
+
if (text.length === 0) return 0;
|
|
333
|
+
const newlineCount = text.match(/\r\n|\r|\n/g)?.length ?? 0;
|
|
334
|
+
return newlineCount + (/(?:\r\n|\r|\n)$/.test(text) ? 0 : 1);
|
|
335
|
+
}
|
|
312
336
|
function hasOutputOverrides(options) {
|
|
313
|
-
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0;
|
|
337
|
+
return options.preset !== void 0 || options.indent !== void 0 || options.filePaths !== void 0 || options.file_paths !== void 0 || options.includeLinesCount !== void 0 || options.include_lines_count !== void 0;
|
|
314
338
|
}
|
|
315
339
|
function isInsidePath(parent, child) {
|
|
316
340
|
const relativePath = path.relative(parent, child);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { execFile } 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};\n\ntype FileSummary = {\n path: string;\n kind: 'markdown' | 'typescript' | 'python';\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};\n\nexport const PEEK_OUTPUT_PRESETS: Record<PeekOutputPresetName, PeekOutputConfig> = {\n human: {\n deep: true,\n indent: true,\n filePaths: 'full',\n },\n agent: {\n deep: true,\n indent: false,\n filePaths: 'concise',\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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 lines.push(`${childIndent}<file path=\"${escapeAttribute(file.path)}\">`);\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 { path: relativePath, kind: 'markdown', items: extractMarkdownHeadings(text) };\n }\n if (isTypeScriptLikeFile(extension, fullPath)) {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'typescript', items: extractTypeScriptSymbols(text) };\n }\n if (extension === '.py') {\n const text = await readFile(fullPath, 'utf8');\n return { path: relativePath, kind: 'python', items: extractPythonSymbols(text) };\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 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 };\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 hasOutputOverrides(options: ScanOptions): boolean {\n return (\n options.preset !== undefined ||\n options.indent !== undefined ||\n options.filePaths !== undefined ||\n options.file_paths !== 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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 } 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 summary = await collectFolderSummary(root, repoRoot, root, outputConfig.deep, outputConfig);\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): Promise<FolderSummary> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: FileSummary[] = [];\n const folders: FolderSummary[] = [];\n const omittedFiles: string[] = [];\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 (await isGitIgnored(root, fullPath)) continue;\n\n if (entry.isDirectory()) {\n if (!deep || shouldSkipDirectory(entry.name)) continue;\n folders.push(await collectFolderSummary(root, repoRoot, fullPath, deep, outputConfig));\n continue;\n }\n if (!entry.isFile() || shouldSkipFile(entry.name)) continue;\n\n const summary = await summarizeFile(root, repoRoot, fullPath, outputConfig);\n if (summary) {\n files.push(summary);\n } else {\n omittedFiles.push(entry.name);\n }\n }\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 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 isGitIgnored(root: string, fullPath: string): Promise<boolean> {\n const relativePath = toPosix(path.relative(root, fullPath));\n if (!relativePath || relativePath.startsWith('..')) return false;\n\n try {\n await execFileAsync('git', ['-C', root, 'check-ignore', '--quiet', '--', relativePath]);\n return true;\n } catch (error) {\n const code = (error as { code?: number | string }).code;\n if (code === 1 || code === 128 || code === 'ENOENT') return false;\n return false;\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,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davstack/peek",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Peek at concise, agent-friendly folder summaries.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"prepublishOnly": "tsup",
|
|
10
|
+
"test": "pnpm -w exec vitest run --project peek"
|
|
11
|
+
},
|
|
7
12
|
"bin": {
|
|
8
|
-
"peek": "
|
|
13
|
+
"peek": "bin/davstack-peek.mjs"
|
|
9
14
|
},
|
|
10
15
|
"exports": {
|
|
11
16
|
".": {
|
|
@@ -26,7 +31,7 @@
|
|
|
26
31
|
"node": ">=20"
|
|
27
32
|
},
|
|
28
33
|
"dependencies": {
|
|
29
|
-
"@davstack/cli-utils": "
|
|
34
|
+
"@davstack/cli-utils": "workspace:*"
|
|
30
35
|
},
|
|
31
36
|
"devDependencies": {
|
|
32
37
|
"@types/node": "^25.9.1",
|
|
@@ -35,9 +40,5 @@
|
|
|
35
40
|
},
|
|
36
41
|
"publishConfig": {
|
|
37
42
|
"access": "public"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsup",
|
|
41
|
-
"test": "pnpm -w exec vitest run --project peek"
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
}
|