(args: z.infer<typeof ProjectOverviewArgs>, ctx: ToolContext)
| 159 | argsSchema = ProjectOverviewArgs; |
| 160 | |
| 161 | async execute(args: z.infer<typeof ProjectOverviewArgs>, ctx: ToolContext): Promise<ToolResult> { |
| 162 | const root = args.path ? path.resolve(ctx.cwd, args.path) : ctx.cwd; |
| 163 | const maxFiles = args.max_files ?? 10_000; |
| 164 | const { files, truncated } = await walkDir(root, maxFiles); |
| 165 | |
| 166 | const lines: string[] = []; |
| 167 | lines.push(`# Project Overview: ${path.basename(root)}`); |
| 168 | lines.push(`Path: ${root}`); |
| 169 | lines.push(`Files scanned: ${files.length}${truncated ? ' (truncated)' : ''}`); |
| 170 | lines.push(''); |
| 171 | |
| 172 | // Language stats |
| 173 | const langs = detectLanguages(files); |
| 174 | const langList = Object.entries(langs) |
| 175 | .sort((a, b) => b[1].lines - a[1].lines) |
| 176 | .slice(0, 10); |
| 177 | lines.push('## Languages (by total lines)'); |
| 178 | for (const [lang, s] of langList) { |
| 179 | lines.push(` ${lang.padEnd(15)} ${s.files.toLocaleString().padStart(6)} files, ${s.lines.toLocaleString()} lines`); |
| 180 | } |
| 181 | lines.push(''); |
| 182 | |
| 183 | // package.json info |
| 184 | const unparseable: string[] = []; |
| 185 | const pkg = await readJsonOrNull(path.join(root, 'package.json'), unparseable); |
| 186 | if (unparseable.length > 0) { |
| 187 | lines.push('## ⚠ Unparseable config files'); |
| 188 | lines.push(' The following config files exist but could not be parsed; tech-stack detection below may be incomplete:'); |
| 189 | for (const f of unparseable) lines.push(` - ${path.relative(root, f) || path.basename(f)}`); |
| 190 | lines.push(''); |
| 191 | } |
| 192 | if (pkg) { |
| 193 | lines.push('## Node.js Project (package.json)'); |
| 194 | lines.push(` Name: ${pkg.name ?? '(unnamed)'}`); |
| 195 | lines.push(` Version: ${pkg.version ?? '?'}`); |
| 196 | if (pkg.description) lines.push(` Desc: ${pkg.description}`); |
| 197 | if (pkg.main) lines.push(` Main: ${pkg.main}`); |
| 198 | if (pkg.bin) lines.push(` Bin: ${typeof pkg.bin === 'string' ? pkg.bin : Object.keys(pkg.bin).join(', ')}`); |
| 199 | if (pkg.scripts) { |
| 200 | lines.push(` Scripts:`); |
| 201 | for (const [n, cmd] of Object.entries(pkg.scripts)) lines.push(` ${n}: ${cmd}`); |
| 202 | } |
| 203 | const depCount = Object.keys(pkg.dependencies ?? {}).length; |
| 204 | const devDepCount = Object.keys(pkg.devDependencies ?? {}).length; |
| 205 | const peerCount = Object.keys(pkg.peerDependencies ?? {}).length; |
| 206 | const optCount = Object.keys(pkg.optionalDependencies ?? {}).length; |
| 207 | lines.push(` Deps: ${depCount} runtime, ${devDepCount} dev${peerCount ? `, ${peerCount} peer` : ''}${optCount ? `, ${optCount} optional` : ''}`); |
| 208 | if (pkg.workspaces) { |
| 209 | lines.push(` Workspaces: ${Array.isArray(pkg.workspaces) ? pkg.workspaces.join(', ') : JSON.stringify(pkg.workspaces)}`); |
| 210 | } |
| 211 | lines.push(''); |
| 212 | } |
| 213 | |
| 214 | // Other config files |
| 215 | const configs = files.filter(f => { |
| 216 | const base = path.basename(f.path); |
| 217 | return CONFIG_PATTERNS.some(p => p.test(base)); |
| 218 | }); |
nothing calls this directly
no test coverage detected