openwolf 1.0.0
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/LICENSE +663 -0
- package/README.md +232 -0
- package/dist/bin/openwolf.js +10 -0
- package/dist/bin/openwolf.js.map +1 -0
- package/dist/dashboard/assets/AISuggestions-DzE-DQkR.js +1 -0
- package/dist/dashboard/assets/ActivityTimeline-DGVjujnt.js +1 -0
- package/dist/dashboard/assets/AnatomyBrowser-S-2rmYtw.js +1 -0
- package/dist/dashboard/assets/BugLog-CG2zDHJc.js +1 -0
- package/dist/dashboard/assets/CerebrumViewer-Dlgoy69U.js +1 -0
- package/dist/dashboard/assets/CronStatus-DxUF1iW_.js +1 -0
- package/dist/dashboard/assets/DesignQC-BGXn_aq8.js +1 -0
- package/dist/dashboard/assets/MemoryViewer-CGqkTyvQ.js +1 -0
- package/dist/dashboard/assets/ProjectOverview-DlFhu69i.js +1 -0
- package/dist/dashboard/assets/TokenUsage-DDsQiVIq.js +68 -0
- package/dist/dashboard/assets/index-CzK9GUjV.css +1 -0
- package/dist/dashboard/assets/index-PYeNGjkN.js +52 -0
- package/dist/dashboard/index.html +16 -0
- package/dist/hooks/post-read.js +68 -0
- package/dist/hooks/post-write.js +502 -0
- package/dist/hooks/pre-read.js +79 -0
- package/dist/hooks/pre-write.js +120 -0
- package/dist/hooks/session-start.js +76 -0
- package/dist/hooks/shared.js +613 -0
- package/dist/hooks/stop.js +146 -0
- package/dist/src/buglog/bug-matcher.js +3 -0
- package/dist/src/buglog/bug-matcher.js.map +1 -0
- package/dist/src/buglog/bug-tracker.js +81 -0
- package/dist/src/buglog/bug-tracker.js.map +1 -0
- package/dist/src/cli/bug-cmd.js +28 -0
- package/dist/src/cli/bug-cmd.js.map +1 -0
- package/dist/src/cli/cron-cmd.js +106 -0
- package/dist/src/cli/cron-cmd.js.map +1 -0
- package/dist/src/cli/daemon-cmd.js +177 -0
- package/dist/src/cli/daemon-cmd.js.map +1 -0
- package/dist/src/cli/dashboard.js +84 -0
- package/dist/src/cli/dashboard.js.map +1 -0
- package/dist/src/cli/designqc-cmd.js +31 -0
- package/dist/src/cli/designqc-cmd.js.map +1 -0
- package/dist/src/cli/index.js +149 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/init.js +506 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/registry.js +93 -0
- package/dist/src/cli/registry.js.map +1 -0
- package/dist/src/cli/scan.js +39 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/status.js +85 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/update.js +414 -0
- package/dist/src/cli/update.js.map +1 -0
- package/dist/src/daemon/cron-engine.js +300 -0
- package/dist/src/daemon/cron-engine.js.map +1 -0
- package/dist/src/daemon/file-watcher.js +53 -0
- package/dist/src/daemon/file-watcher.js.map +1 -0
- package/dist/src/daemon/health.js +23 -0
- package/dist/src/daemon/health.js.map +1 -0
- package/dist/src/daemon/wolf-daemon.js +294 -0
- package/dist/src/daemon/wolf-daemon.js.map +1 -0
- package/dist/src/designqc/designqc-capture.js +235 -0
- package/dist/src/designqc/designqc-capture.js.map +1 -0
- package/dist/src/designqc/designqc-engine.js +141 -0
- package/dist/src/designqc/designqc-engine.js.map +1 -0
- package/dist/src/designqc/designqc-types.js +5 -0
- package/dist/src/designqc/designqc-types.js.map +1 -0
- package/dist/src/hooks/post-read.js +69 -0
- package/dist/src/hooks/post-read.js.map +1 -0
- package/dist/src/hooks/post-write.js +503 -0
- package/dist/src/hooks/post-write.js.map +1 -0
- package/dist/src/hooks/pre-read.js +80 -0
- package/dist/src/hooks/pre-read.js.map +1 -0
- package/dist/src/hooks/pre-write.js +121 -0
- package/dist/src/hooks/pre-write.js.map +1 -0
- package/dist/src/hooks/session-start.js +77 -0
- package/dist/src/hooks/session-start.js.map +1 -0
- package/dist/src/hooks/shared.js +614 -0
- package/dist/src/hooks/shared.js.map +1 -0
- package/dist/src/hooks/stop.js +147 -0
- package/dist/src/hooks/stop.js.map +1 -0
- package/dist/src/scanner/anatomy-scanner.js +260 -0
- package/dist/src/scanner/anatomy-scanner.js.map +1 -0
- package/dist/src/scanner/description-extractor.js +1007 -0
- package/dist/src/scanner/description-extractor.js.map +1 -0
- package/dist/src/scanner/project-root.js +42 -0
- package/dist/src/scanner/project-root.js.map +1 -0
- package/dist/src/tracker/token-estimator.js +20 -0
- package/dist/src/tracker/token-estimator.js.map +1 -0
- package/dist/src/tracker/token-ledger.js +45 -0
- package/dist/src/tracker/token-ledger.js.map +1 -0
- package/dist/src/tracker/waste-detector.js +101 -0
- package/dist/src/tracker/waste-detector.js.map +1 -0
- package/dist/src/utils/fs-safe.js +74 -0
- package/dist/src/utils/fs-safe.js.map +1 -0
- package/dist/src/utils/logger.js +48 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/paths.js +23 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/platform.js +14 -0
- package/dist/src/utils/platform.js.map +1 -0
- package/package.json +77 -0
- package/src/templates/OPENWOLF.md +135 -0
- package/src/templates/anatomy.md +5 -0
- package/src/templates/buglog.json +4 -0
- package/src/templates/cerebrum.md +22 -0
- package/src/templates/claude-md-snippet.md +5 -0
- package/src/templates/claude-rules-openwolf.md +15 -0
- package/src/templates/config.json +73 -0
- package/src/templates/cron-manifest.json +97 -0
- package/src/templates/cron-state.json +7 -0
- package/src/templates/identity.md +9 -0
- package/src/templates/memory.md +4 -0
- package/src/templates/reframe-frameworks.md +597 -0
- package/src/templates/token-ledger.json +21 -0
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
// ─── Known files ─────────────────────────────────────────────
|
|
4
|
+
const KNOWN_FILES = {
|
|
5
|
+
// JS/TS ecosystem
|
|
6
|
+
"package.json": "Node.js package manifest",
|
|
7
|
+
"package-lock.json": "npm lock file",
|
|
8
|
+
"pnpm-lock.yaml": "pnpm lock file",
|
|
9
|
+
"yarn.lock": "Yarn lock file",
|
|
10
|
+
"bun.lockb": "Bun lock file",
|
|
11
|
+
"tsconfig.json": "TypeScript configuration",
|
|
12
|
+
"tsconfig.build.json": "TypeScript build configuration",
|
|
13
|
+
"tsconfig.hooks.json": "TypeScript hooks build configuration",
|
|
14
|
+
".eslintrc.json": "ESLint configuration",
|
|
15
|
+
".eslintrc.js": "ESLint configuration",
|
|
16
|
+
".eslintrc.cjs": "ESLint configuration",
|
|
17
|
+
"eslint.config.js": "ESLint flat configuration",
|
|
18
|
+
"eslint.config.mjs": "ESLint flat configuration",
|
|
19
|
+
"biome.json": "Biome linter/formatter configuration",
|
|
20
|
+
".prettierrc": "Prettier configuration",
|
|
21
|
+
".prettierrc.json": "Prettier configuration",
|
|
22
|
+
"prettier.config.js": "Prettier configuration",
|
|
23
|
+
// Build tools
|
|
24
|
+
"vite.config.ts": "Vite build configuration",
|
|
25
|
+
"vite.config.js": "Vite build configuration",
|
|
26
|
+
"next.config.js": "Next.js configuration",
|
|
27
|
+
"next.config.mjs": "Next.js configuration",
|
|
28
|
+
"next.config.ts": "Next.js configuration",
|
|
29
|
+
"nuxt.config.ts": "Nuxt configuration",
|
|
30
|
+
"nuxt.config.js": "Nuxt configuration",
|
|
31
|
+
"svelte.config.js": "SvelteKit configuration",
|
|
32
|
+
"astro.config.mjs": "Astro configuration",
|
|
33
|
+
"astro.config.ts": "Astro configuration",
|
|
34
|
+
"remix.config.js": "Remix configuration",
|
|
35
|
+
"tailwind.config.js": "Tailwind CSS configuration",
|
|
36
|
+
"tailwind.config.ts": "Tailwind CSS configuration",
|
|
37
|
+
"postcss.config.js": "PostCSS configuration",
|
|
38
|
+
"postcss.config.cjs": "PostCSS configuration",
|
|
39
|
+
".babelrc": "Babel configuration",
|
|
40
|
+
"babel.config.js": "Babel configuration",
|
|
41
|
+
"webpack.config.js": "Webpack configuration",
|
|
42
|
+
"rollup.config.js": "Rollup configuration",
|
|
43
|
+
"turbo.json": "Turborepo configuration",
|
|
44
|
+
// Test
|
|
45
|
+
"jest.config.js": "Jest test configuration",
|
|
46
|
+
"jest.config.ts": "Jest test configuration",
|
|
47
|
+
"vitest.config.ts": "Vitest test configuration",
|
|
48
|
+
"playwright.config.ts": "Playwright test configuration",
|
|
49
|
+
"cypress.config.ts": "Cypress test configuration",
|
|
50
|
+
// Git
|
|
51
|
+
".gitignore": "Git ignore rules",
|
|
52
|
+
".gitattributes": "Git attributes",
|
|
53
|
+
// Docker
|
|
54
|
+
"Dockerfile": "Docker container definition",
|
|
55
|
+
"docker-compose.yml": "Docker Compose services",
|
|
56
|
+
"docker-compose.yaml": "Docker Compose services",
|
|
57
|
+
".dockerignore": "Docker ignore rules",
|
|
58
|
+
// CI/CD
|
|
59
|
+
".github/workflows/ci.yml": "GitHub Actions CI pipeline",
|
|
60
|
+
".gitlab-ci.yml": "GitLab CI pipeline",
|
|
61
|
+
"Jenkinsfile": "Jenkins pipeline",
|
|
62
|
+
// Rust
|
|
63
|
+
"Cargo.toml": "Rust package manifest",
|
|
64
|
+
"Cargo.lock": "Rust dependency lock file",
|
|
65
|
+
// Go
|
|
66
|
+
"go.mod": "Go module definition",
|
|
67
|
+
"go.sum": "Go dependency checksums",
|
|
68
|
+
// Python
|
|
69
|
+
"pyproject.toml": "Python project configuration",
|
|
70
|
+
"setup.py": "Python package setup",
|
|
71
|
+
"setup.cfg": "Python package configuration",
|
|
72
|
+
"requirements.txt": "Python dependencies",
|
|
73
|
+
"Pipfile": "Pipenv dependencies",
|
|
74
|
+
"poetry.lock": "Poetry lock file",
|
|
75
|
+
// Ruby
|
|
76
|
+
"Gemfile": "Ruby dependencies",
|
|
77
|
+
"Gemfile.lock": "Ruby dependency lock",
|
|
78
|
+
"Rakefile": "Ruby make-like build tasks",
|
|
79
|
+
// PHP
|
|
80
|
+
"composer.json": "PHP package manifest",
|
|
81
|
+
"composer.lock": "PHP dependency lock",
|
|
82
|
+
"artisan": "Laravel CLI entry point",
|
|
83
|
+
// Java/Kotlin
|
|
84
|
+
"build.gradle": "Gradle build configuration",
|
|
85
|
+
"build.gradle.kts": "Gradle Kotlin build configuration",
|
|
86
|
+
"pom.xml": "Maven project configuration",
|
|
87
|
+
"settings.gradle": "Gradle settings",
|
|
88
|
+
"settings.gradle.kts": "Gradle Kotlin settings",
|
|
89
|
+
// C#/.NET
|
|
90
|
+
"Program.cs": "Application entry point",
|
|
91
|
+
"Startup.cs": "ASP.NET startup configuration",
|
|
92
|
+
"appsettings.json": ".NET application settings",
|
|
93
|
+
"global.json": ".NET SDK configuration",
|
|
94
|
+
// Swift
|
|
95
|
+
"Package.swift": "Swift package manifest",
|
|
96
|
+
// Dart/Flutter
|
|
97
|
+
"pubspec.yaml": "Dart/Flutter package manifest",
|
|
98
|
+
"pubspec.lock": "Dart dependency lock",
|
|
99
|
+
// DB/Schema
|
|
100
|
+
"schema.sql": "Database schema",
|
|
101
|
+
"schema.prisma": "Prisma database schema",
|
|
102
|
+
"drizzle.config.ts": "Drizzle ORM configuration",
|
|
103
|
+
// Server
|
|
104
|
+
".htaccess": "Apache configuration",
|
|
105
|
+
"nginx.conf": "Nginx configuration",
|
|
106
|
+
// Misc
|
|
107
|
+
".editorconfig": "Editor configuration",
|
|
108
|
+
".env.example": "Environment variable template",
|
|
109
|
+
"LICENSE": "Project license",
|
|
110
|
+
"README.md": "Project documentation",
|
|
111
|
+
"CHANGELOG.md": "Change log",
|
|
112
|
+
"deno.json": "Deno configuration",
|
|
113
|
+
"Makefile": "Make build targets",
|
|
114
|
+
"CMakeLists.txt": "CMake build configuration",
|
|
115
|
+
};
|
|
116
|
+
const MAX_DESC = 150;
|
|
117
|
+
const READ_BYTES = 12288; // 12KB for better analysis
|
|
118
|
+
// ─── Main entry ──────────────────────────────────────────────
|
|
119
|
+
export function extractDescription(filePath) {
|
|
120
|
+
const basename = path.basename(filePath);
|
|
121
|
+
if (KNOWN_FILES[basename])
|
|
122
|
+
return KNOWN_FILES[basename];
|
|
123
|
+
let content;
|
|
124
|
+
try {
|
|
125
|
+
const fd = fs.openSync(filePath, "r");
|
|
126
|
+
const buf = Buffer.alloc(READ_BYTES);
|
|
127
|
+
const bytesRead = fs.readSync(fd, buf, 0, READ_BYTES, 0);
|
|
128
|
+
fs.closeSync(fd);
|
|
129
|
+
content = buf.subarray(0, bytesRead).toString("utf-8");
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
if (!content.trim())
|
|
135
|
+
return "";
|
|
136
|
+
const ext = path.extname(basename).toLowerCase();
|
|
137
|
+
// package.json with description
|
|
138
|
+
if (basename === "package.json") {
|
|
139
|
+
try {
|
|
140
|
+
const pkg = JSON.parse(content);
|
|
141
|
+
return pkg.description || pkg.name || "Node.js package";
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return "Node.js package manifest";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Markdown heading
|
|
148
|
+
if (ext === ".md" || ext === ".mdx") {
|
|
149
|
+
const m = content.match(/^#{1,2}\s+(.+)$/m);
|
|
150
|
+
if (m)
|
|
151
|
+
return m[1].trim();
|
|
152
|
+
}
|
|
153
|
+
// HTML title
|
|
154
|
+
if (ext === ".html" || ext === ".htm") {
|
|
155
|
+
const m = content.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
156
|
+
if (m)
|
|
157
|
+
return m[1].trim();
|
|
158
|
+
}
|
|
159
|
+
// Docblock (JSDoc, PHPDoc, Rustdoc, Javadoc, C# XML doc)
|
|
160
|
+
const doc = extractDocblock(content, ext);
|
|
161
|
+
if (doc)
|
|
162
|
+
return doc;
|
|
163
|
+
// Header comment
|
|
164
|
+
const hdr = extractHeaderComment(content, ext);
|
|
165
|
+
if (hdr)
|
|
166
|
+
return hdr;
|
|
167
|
+
// Language-specific smart extraction
|
|
168
|
+
const smart = extractSmart(content, ext, basename, filePath);
|
|
169
|
+
if (smart)
|
|
170
|
+
return smart;
|
|
171
|
+
// Generic fallback
|
|
172
|
+
return extractGenericFallback(content);
|
|
173
|
+
}
|
|
174
|
+
// ─── Docblock extraction ─────────────────────────────────────
|
|
175
|
+
function extractDocblock(content, ext) {
|
|
176
|
+
// JSDoc / PHPDoc / Javadoc / Kotlin KDoc: /** ... */
|
|
177
|
+
const jsdoc = content.match(/\/\*\*\s*\n?\s*\*?\s*(.+)/);
|
|
178
|
+
if (jsdoc) {
|
|
179
|
+
const line = jsdoc[1].replace(/\*\/$/, "").trim();
|
|
180
|
+
if (line && !line.startsWith("@") && line.length > 5)
|
|
181
|
+
return line;
|
|
182
|
+
}
|
|
183
|
+
// Python docstring
|
|
184
|
+
if (ext === ".py") {
|
|
185
|
+
const dm = content.match(/^(?:#[^\n]*\n)*\s*(?:"""(.+?)"""|'''(.+?)''')/s);
|
|
186
|
+
if (dm) {
|
|
187
|
+
const first = (dm[1] || dm[2]).split("\n")[0].trim();
|
|
188
|
+
if (first && first.length > 3)
|
|
189
|
+
return first;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Rust doc comments: /// or //!
|
|
193
|
+
if (ext === ".rs") {
|
|
194
|
+
const lines = content.split("\n");
|
|
195
|
+
for (const line of lines.slice(0, 20)) {
|
|
196
|
+
const m = line.match(/^\s*(?:\/\/\/|\/\/!)\s*(.+)/);
|
|
197
|
+
if (m && m[1].length > 5)
|
|
198
|
+
return m[1].trim();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Go package comment
|
|
202
|
+
if (ext === ".go") {
|
|
203
|
+
const m = content.match(/\/\/\s*Package\s+\w+\s+(.*)/);
|
|
204
|
+
if (m)
|
|
205
|
+
return m[1].trim();
|
|
206
|
+
}
|
|
207
|
+
// C# XML doc: /// <summary>...</summary>
|
|
208
|
+
if (ext === ".cs") {
|
|
209
|
+
const m = content.match(/<summary>\s*([\s\S]*?)\s*<\/summary>/);
|
|
210
|
+
if (m) {
|
|
211
|
+
const text = m[1].replace(/\/\/\/\s*/g, "").replace(/\s+/g, " ").trim();
|
|
212
|
+
if (text.length > 5)
|
|
213
|
+
return text;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Elixir @moduledoc
|
|
217
|
+
if (ext === ".ex" || ext === ".exs") {
|
|
218
|
+
const m = content.match(/@moduledoc\s+"""\s*\n\s*(.*)/);
|
|
219
|
+
if (m)
|
|
220
|
+
return m[1].trim();
|
|
221
|
+
}
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
224
|
+
// ─── Header comment ──────────────────────────────────────────
|
|
225
|
+
function extractHeaderComment(content, ext) {
|
|
226
|
+
const lines = content.split("\n");
|
|
227
|
+
for (const line of lines.slice(0, 15)) {
|
|
228
|
+
const t = line.trim();
|
|
229
|
+
if (!t || t === "<?php" || t.startsWith("#!") || t.startsWith("package ") ||
|
|
230
|
+
t.startsWith("namespace") || t.startsWith("use ") || t.startsWith("import ") ||
|
|
231
|
+
t.startsWith("from ") || t.startsWith("require") || t.startsWith("module "))
|
|
232
|
+
continue;
|
|
233
|
+
const cm = t.match(/^(?:\/\/|#|--)\s*(.+)/);
|
|
234
|
+
if (cm) {
|
|
235
|
+
const text = cm[1].trim();
|
|
236
|
+
if (text.length > 5 && !isGenericComment(text))
|
|
237
|
+
return text;
|
|
238
|
+
}
|
|
239
|
+
if (!t.startsWith("//") && !t.startsWith("#") && !t.startsWith("/*") &&
|
|
240
|
+
!t.startsWith("*") && !t.startsWith("--"))
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
function isGenericComment(text) {
|
|
246
|
+
const l = text.toLowerCase();
|
|
247
|
+
return l.startsWith("strict") || l.startsWith("copyright") || l.startsWith("license") ||
|
|
248
|
+
l.startsWith("@author") || l.startsWith("@package") || l.startsWith("@license") ||
|
|
249
|
+
l.startsWith("generated") || l.startsWith("auto-generated") || l === "use strict" ||
|
|
250
|
+
l.startsWith("eslint-") || l.startsWith("prettier-") || l.startsWith("nolint") ||
|
|
251
|
+
/^[-=]{3,}$/.test(text);
|
|
252
|
+
}
|
|
253
|
+
// ─── Smart extraction router ────────────────────────────────
|
|
254
|
+
function extractSmart(content, ext, basename, filePath) {
|
|
255
|
+
switch (ext) {
|
|
256
|
+
case ".php": return extractPhp(content, basename, filePath);
|
|
257
|
+
case ".ts":
|
|
258
|
+
case ".tsx":
|
|
259
|
+
case ".js":
|
|
260
|
+
case ".jsx":
|
|
261
|
+
case ".mjs":
|
|
262
|
+
case ".cjs":
|
|
263
|
+
return extractTsJs(content, basename, ext);
|
|
264
|
+
case ".py": return extractPython(content, basename);
|
|
265
|
+
case ".go": return extractGo(content);
|
|
266
|
+
case ".rs": return extractRust(content);
|
|
267
|
+
case ".java": return extractJava(content, basename);
|
|
268
|
+
case ".kt":
|
|
269
|
+
case ".kts": return extractKotlin(content, basename);
|
|
270
|
+
case ".cs": return extractCSharp(content, basename);
|
|
271
|
+
case ".rb": return extractRuby(content, basename);
|
|
272
|
+
case ".swift": return extractSwift(content);
|
|
273
|
+
case ".dart": return extractDart(content, basename);
|
|
274
|
+
case ".vue": return extractVue(content);
|
|
275
|
+
case ".svelte": return extractSvelte(content, basename);
|
|
276
|
+
case ".astro": return extractAstro(content, basename);
|
|
277
|
+
case ".css":
|
|
278
|
+
case ".scss":
|
|
279
|
+
case ".less": return extractCss(content);
|
|
280
|
+
case ".sql": return extractSql(content);
|
|
281
|
+
case ".proto": return extractProto(content);
|
|
282
|
+
case ".graphql":
|
|
283
|
+
case ".gql": return extractGraphQL(content);
|
|
284
|
+
case ".yaml":
|
|
285
|
+
case ".yml": return extractYaml(content, basename);
|
|
286
|
+
case ".toml": return extractToml(content, basename);
|
|
287
|
+
case ".ex":
|
|
288
|
+
case ".exs": return extractElixir(content);
|
|
289
|
+
case ".lua": return extractLua(content);
|
|
290
|
+
case ".zig": return extractZig(content);
|
|
291
|
+
default: return "";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ─── PHP / Laravel ───────────────────────────────────────────
|
|
295
|
+
function extractPhp(content, basename, filePath) {
|
|
296
|
+
if (basename.endsWith(".blade.php")) {
|
|
297
|
+
const ext = content.match(/@extends\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
298
|
+
const sections = (content.match(/@section\(\s*['"](\w+)['"]/g) || []).map(s => s.match(/['"](\w+)['"]/)?.[1]).filter(Boolean);
|
|
299
|
+
const forms = (content.match(/<form/gi) || []).length;
|
|
300
|
+
const tables = (content.match(/<table/gi) || []).length;
|
|
301
|
+
const comps = (content.match(/<x-/gi) || []).length;
|
|
302
|
+
const parts = [];
|
|
303
|
+
if (ext)
|
|
304
|
+
parts.push(`extends ${ext[1]}`);
|
|
305
|
+
if (sections.length)
|
|
306
|
+
parts.push(`sections: ${sections.join(", ")}`);
|
|
307
|
+
if (forms)
|
|
308
|
+
parts.push(`${forms} form(s)`);
|
|
309
|
+
if (tables)
|
|
310
|
+
parts.push(`${tables} table(s)`);
|
|
311
|
+
if (comps)
|
|
312
|
+
parts.push(`${comps} component(s)`);
|
|
313
|
+
return parts.length ? `Blade: ${parts.join(", ")}` : `Blade: ${basename.replace(".blade.php", "")}`;
|
|
314
|
+
}
|
|
315
|
+
const dirName = path.basename(path.dirname(filePath));
|
|
316
|
+
const classM = content.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/);
|
|
317
|
+
const className = classM?.[1] || "";
|
|
318
|
+
const parent = classM?.[2] || "";
|
|
319
|
+
// Public methods with docblock summaries
|
|
320
|
+
const methods = [];
|
|
321
|
+
const mRegex = /(?:\/\*\*\s*([\s\S]*?)\*\/\s*)?public\s+(?:static\s+)?function\s+(\w+)/g;
|
|
322
|
+
let mm;
|
|
323
|
+
while ((mm = mRegex.exec(content)) !== null) {
|
|
324
|
+
const doc = mm[1] || "";
|
|
325
|
+
const name = mm[2];
|
|
326
|
+
if (name === "__construct" || name === "middleware")
|
|
327
|
+
continue;
|
|
328
|
+
const docLines = doc.split("\n").map(l => l.replace(/^\s*\*\s?/, "").trim()).filter(Boolean);
|
|
329
|
+
const summary = docLines.find(l => !l.startsWith("@") && l.length > 3)?.slice(0, 50) || "";
|
|
330
|
+
methods.push({ name, summary });
|
|
331
|
+
}
|
|
332
|
+
const methodList = (items, max = 5) => {
|
|
333
|
+
const display = items.slice(0, max).map(m => m.summary || m.name).join(", ");
|
|
334
|
+
return items.length > max ? `${display} + ${items.length - max} more` : display;
|
|
335
|
+
};
|
|
336
|
+
// Controller
|
|
337
|
+
if (basename.endsWith("Controller.php") || parent === "Controller") {
|
|
338
|
+
return methods.length ? methodList(methods) : `Controller: ${className}`;
|
|
339
|
+
}
|
|
340
|
+
// Model
|
|
341
|
+
if (parent === "Model" || parent === "Authenticatable" || dirName === "Models") {
|
|
342
|
+
const parts = [];
|
|
343
|
+
const tbl = content.match(/\$table\s*=\s*['"]([^'"]+)['"]/);
|
|
344
|
+
if (tbl)
|
|
345
|
+
parts.push(`table: ${tbl[1]}`);
|
|
346
|
+
const fill = content.match(/\$fillable\s*=\s*\[([^\]]*)\]/s);
|
|
347
|
+
if (fill) {
|
|
348
|
+
const c = (fill[1].match(/['"]/g) || []).length / 2;
|
|
349
|
+
parts.push(`${Math.floor(c)} fields`);
|
|
350
|
+
}
|
|
351
|
+
const casts = content.match(/\$casts\s*=\s*\[([^\]]*)\]/s);
|
|
352
|
+
if (casts) {
|
|
353
|
+
const c = (casts[1].match(/['"]/g) || []).length / 2;
|
|
354
|
+
parts.push(`${Math.floor(c)} casts`);
|
|
355
|
+
}
|
|
356
|
+
const rels = (content.match(/\$this->(hasMany|hasOne|belongsTo|belongsToMany|morphMany|morphTo|morphOne|hasManyThrough)\(/g) || []).length;
|
|
357
|
+
if (rels)
|
|
358
|
+
parts.push(`${rels} rels`);
|
|
359
|
+
const scopes = (content.match(/public\s+function\s+scope(\w+)/g) || []).length;
|
|
360
|
+
if (scopes)
|
|
361
|
+
parts.push(`${scopes} scopes`);
|
|
362
|
+
return parts.length ? `Model — ${parts.join(", ")}` : `Model: ${className}`;
|
|
363
|
+
}
|
|
364
|
+
// Migration
|
|
365
|
+
if (basename.match(/^\d{4}_\d{2}_\d{2}/)) {
|
|
366
|
+
const create = content.match(/Schema::create\(\s*['"]([^'"]+)['"]/);
|
|
367
|
+
if (create)
|
|
368
|
+
return `Migration: create ${create[1]} table`;
|
|
369
|
+
const alter = content.match(/Schema::table\(\s*['"]([^'"]+)['"]/);
|
|
370
|
+
if (alter)
|
|
371
|
+
return `Migration: alter ${alter[1]} table`;
|
|
372
|
+
return "Database migration";
|
|
373
|
+
}
|
|
374
|
+
// Laravel types
|
|
375
|
+
const types = {
|
|
376
|
+
ServiceProvider: "Service provider", FormRequest: "Form validation",
|
|
377
|
+
ShouldQueue: "Queued job", Notification: "Notification", Mailable: "Mail",
|
|
378
|
+
Event: "Event", Listener: "Event listener", Command: "Artisan command",
|
|
379
|
+
Seeder: "Database seeder", Factory: "Model factory", Resource: "API resource",
|
|
380
|
+
Policy: "Authorization policy", Observer: "Model observer", Rule: "Validation rule",
|
|
381
|
+
Cast: "Attribute cast", Scope: "Query scope",
|
|
382
|
+
};
|
|
383
|
+
for (const [p, label] of Object.entries(types)) {
|
|
384
|
+
if (parent === p || basename.endsWith(`${p}.php`))
|
|
385
|
+
return `${label}: ${className}`;
|
|
386
|
+
}
|
|
387
|
+
// Interface / Trait
|
|
388
|
+
const iface = content.match(/interface\s+(\w+)/);
|
|
389
|
+
if (iface) {
|
|
390
|
+
const mc = (content.match(/public\s+function\s+\w+/g) || []).length;
|
|
391
|
+
return `Interface: ${iface[1]} (${mc} methods)`;
|
|
392
|
+
}
|
|
393
|
+
const trait = content.match(/trait\s+(\w+)/);
|
|
394
|
+
if (trait)
|
|
395
|
+
return `Trait: ${trait[1]}`;
|
|
396
|
+
// Generic class
|
|
397
|
+
if (className && methods.length)
|
|
398
|
+
return `${className}: ${methodList(methods, 4)}`;
|
|
399
|
+
return "";
|
|
400
|
+
}
|
|
401
|
+
// ─── TypeScript / JavaScript ─────────────────────────────────
|
|
402
|
+
function extractTsJs(content, basename, ext) {
|
|
403
|
+
// React/Preact component
|
|
404
|
+
if (ext === ".tsx" || ext === ".jsx") {
|
|
405
|
+
const comp = content.match(/(?:export\s+(?:default\s+)?)?(?:function|const)\s+(\w+)/);
|
|
406
|
+
const parts = [];
|
|
407
|
+
if (comp)
|
|
408
|
+
parts.push(comp[1]);
|
|
409
|
+
// What it renders
|
|
410
|
+
const renders = [];
|
|
411
|
+
if (/<(?:form|Form)/i.test(content))
|
|
412
|
+
renders.push("form");
|
|
413
|
+
if (/<(?:table|Table|DataTable)/i.test(content))
|
|
414
|
+
renders.push("table");
|
|
415
|
+
if (/(?:Chart|Recharts|Victory|<canvas)/i.test(content))
|
|
416
|
+
renders.push("chart");
|
|
417
|
+
if (/<(?:dialog|Dialog|Modal|Drawer)/i.test(content))
|
|
418
|
+
renders.push("modal");
|
|
419
|
+
if (/<(?:map|Map|MapContainer)/i.test(content))
|
|
420
|
+
renders.push("map");
|
|
421
|
+
if (renders.length)
|
|
422
|
+
parts.push(`renders ${renders.join(", ")}`);
|
|
423
|
+
// Key hooks
|
|
424
|
+
const hooks = new Set();
|
|
425
|
+
const hr = /use(\w+)\(/g;
|
|
426
|
+
let hm;
|
|
427
|
+
while ((hm = hr.exec(content)) !== null) {
|
|
428
|
+
const name = hm[1];
|
|
429
|
+
if (["State", "Effect", "Ref", "Memo", "Callback", "Context", "Reducer", "Query", "Mutation",
|
|
430
|
+
"Router", "Params", "Navigate", "SearchParams", "Form", "Fetcher"].includes(name)) {
|
|
431
|
+
hooks.add(`use${name}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (hooks.size > 0 && hooks.size <= 4)
|
|
435
|
+
parts.push(`uses ${[...hooks].join(", ")}`);
|
|
436
|
+
// Data fetching
|
|
437
|
+
if (content.includes("getServerSideProps") || content.includes("getStaticProps"))
|
|
438
|
+
parts.push("SSR");
|
|
439
|
+
if (content.includes("loader") && content.includes("useLoaderData"))
|
|
440
|
+
parts.push("Remix loader");
|
|
441
|
+
if (parts.length)
|
|
442
|
+
return parts.join(" — ");
|
|
443
|
+
}
|
|
444
|
+
// Next.js app router conventions
|
|
445
|
+
if (basename === "page.tsx" || basename === "page.js")
|
|
446
|
+
return "Next.js page component";
|
|
447
|
+
if (basename === "layout.tsx" || basename === "layout.js")
|
|
448
|
+
return "Next.js layout";
|
|
449
|
+
if (basename === "loading.tsx")
|
|
450
|
+
return "Next.js loading UI";
|
|
451
|
+
if (basename === "error.tsx")
|
|
452
|
+
return "Next.js error boundary";
|
|
453
|
+
if (basename === "not-found.tsx")
|
|
454
|
+
return "Next.js 404 page";
|
|
455
|
+
if (basename === "route.ts" || basename === "route.js") {
|
|
456
|
+
const methods = [...new Set((content.match(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/g) || [])
|
|
457
|
+
.map(m => m.match(/(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/)?.[1]))].filter(Boolean);
|
|
458
|
+
return methods.length ? `Next.js API route: ${methods.join(", ")}` : "Next.js API route";
|
|
459
|
+
}
|
|
460
|
+
// Express/Fastify/Hono routes
|
|
461
|
+
const routeHits = content.match(/\.(get|post|put|patch|delete)\s*\(\s*['"`]/g);
|
|
462
|
+
if (routeHits && routeHits.length > 0) {
|
|
463
|
+
const methods = [...new Set(routeHits.map(r => r.match(/\.(get|post|put|patch|delete)/)?.[1]?.toUpperCase()))];
|
|
464
|
+
return `API routes: ${methods.join(", ")} (${routeHits.length} endpoints)`;
|
|
465
|
+
}
|
|
466
|
+
// tRPC router
|
|
467
|
+
if (content.includes("createTRPCRouter") || content.includes("publicProcedure") || content.includes("protectedProcedure")) {
|
|
468
|
+
const procs = (content.match(/\.(query|mutation|subscription)\s*\(/g) || []).length;
|
|
469
|
+
return procs ? `tRPC router: ${procs} procedures` : "tRPC router";
|
|
470
|
+
}
|
|
471
|
+
// Zustand / Redux store
|
|
472
|
+
if (content.includes("create(") && content.includes("set("))
|
|
473
|
+
return "Zustand store";
|
|
474
|
+
if (content.includes("createSlice")) {
|
|
475
|
+
const name = content.match(/name:\s*['"](\w+)['"]/);
|
|
476
|
+
return name ? `Redux slice: ${name[1]}` : "Redux slice";
|
|
477
|
+
}
|
|
478
|
+
// Zod schemas
|
|
479
|
+
if (content.includes("z.object") || content.includes("z.string") || content.includes("z.number")) {
|
|
480
|
+
const schemas = (content.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*z\./g) || [])
|
|
481
|
+
.map(s => s.match(/(?:const|let)\s+(\w+)/)?.[1]).filter(Boolean);
|
|
482
|
+
if (schemas.length)
|
|
483
|
+
return `Zod schemas: ${schemas.slice(0, 4).join(", ")}${schemas.length > 4 ? ` + ${schemas.length - 4} more` : ""}`;
|
|
484
|
+
}
|
|
485
|
+
// Prisma client usage
|
|
486
|
+
if (content.includes("prisma.") && content.includes("findMany")) {
|
|
487
|
+
return "Prisma data access layer";
|
|
488
|
+
}
|
|
489
|
+
// Exports summary
|
|
490
|
+
const exports = (content.match(/export\s+(?:async\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/g) || [])
|
|
491
|
+
.map(e => e.match(/(\w+)$/)?.[1]).filter(Boolean);
|
|
492
|
+
if (exports.length > 0 && exports.length <= 5)
|
|
493
|
+
return `Exports ${exports.join(", ")}`;
|
|
494
|
+
if (exports.length > 5)
|
|
495
|
+
return `Exports ${exports.slice(0, 4).join(", ")} + ${exports.length - 4} more`;
|
|
496
|
+
return "";
|
|
497
|
+
}
|
|
498
|
+
// ─── Python ──────────────────────────────────────────────────
|
|
499
|
+
function extractPython(content, basename) {
|
|
500
|
+
// Django view
|
|
501
|
+
if (content.includes("def get(self") || content.includes("def post(self") || content.includes("@api_view") || content.includes("APIView")) {
|
|
502
|
+
const viewFuncs = (content.match(/def\s+(get|post|put|patch|delete|list|retrieve|create|update|destroy|perform_create)\s*\(/g) || [])
|
|
503
|
+
.map(m => m.match(/def\s+(\w+)/)?.[1]).filter(Boolean);
|
|
504
|
+
if (viewFuncs.length)
|
|
505
|
+
return `View: ${viewFuncs.join(", ")}`;
|
|
506
|
+
}
|
|
507
|
+
// Django model
|
|
508
|
+
if (content.includes("models.Model")) {
|
|
509
|
+
const cls = content.match(/class\s+(\w+)\(.*models\.Model\)/);
|
|
510
|
+
const fields = (content.match(/^\s+\w+\s*=\s*models\.\w+/gm) || []).length;
|
|
511
|
+
const meta = content.match(/class\s+Meta:[\s\S]*?db_table\s*=\s*['"](\w+)['"]/);
|
|
512
|
+
const parts = [];
|
|
513
|
+
if (cls)
|
|
514
|
+
parts.push(cls[1]);
|
|
515
|
+
if (meta)
|
|
516
|
+
parts.push(`table: ${meta[1]}`);
|
|
517
|
+
parts.push(`${fields} fields`);
|
|
518
|
+
return `Model: ${parts.join(", ")}`;
|
|
519
|
+
}
|
|
520
|
+
// Django serializer
|
|
521
|
+
if (content.includes("serializers.") || content.includes("Serializer)")) {
|
|
522
|
+
const cls = content.match(/class\s+(\w+).*Serializer/);
|
|
523
|
+
return cls ? `Serializer: ${cls[1]}` : "DRF serializer";
|
|
524
|
+
}
|
|
525
|
+
// Django URL patterns
|
|
526
|
+
if (content.includes("urlpatterns") || content.includes("path(")) {
|
|
527
|
+
const paths = (content.match(/path\s*\(\s*['"]([^'"]*)['"]/g) || []).length;
|
|
528
|
+
return paths ? `URL patterns: ${paths} routes` : "URL configuration";
|
|
529
|
+
}
|
|
530
|
+
// FastAPI / Starlette router
|
|
531
|
+
if (content.includes("@router.") || content.includes("@app.")) {
|
|
532
|
+
const routes = (content.match(/@(?:router|app)\.(get|post|put|patch|delete)\s*\(/g) || []);
|
|
533
|
+
const paths = routes.map(r => r.match(/\.(get|post|put|patch|delete)/)?.[1]?.toUpperCase()).filter(Boolean);
|
|
534
|
+
return routes.length ? `API: ${[...new Set(paths)].join(", ")} (${routes.length} endpoints)` : "API router";
|
|
535
|
+
}
|
|
536
|
+
// Flask
|
|
537
|
+
if (content.includes("@app.route") || content.includes("@blueprint.route") || content.includes("Blueprint(")) {
|
|
538
|
+
const routes = (content.match(/@(?:app|blueprint|\w+)\.route\s*\(/g) || []).length;
|
|
539
|
+
return routes ? `Flask routes: ${routes} endpoints` : "Flask blueprint";
|
|
540
|
+
}
|
|
541
|
+
// Pydantic model
|
|
542
|
+
if (content.includes("BaseModel") && content.includes("Field(")) {
|
|
543
|
+
const cls = content.match(/class\s+(\w+)\(.*BaseModel\)/);
|
|
544
|
+
const fields = (content.match(/^\s+\w+\s*:\s*\w+/gm) || []).length;
|
|
545
|
+
return cls ? `Pydantic: ${cls[1]} (${fields} fields)` : `Pydantic model (${fields} fields)`;
|
|
546
|
+
}
|
|
547
|
+
// SQLAlchemy model
|
|
548
|
+
if (content.includes("declarative_base") || content.includes("mapped_column") || content.includes("Column(")) {
|
|
549
|
+
const cls = content.match(/class\s+(\w+)/);
|
|
550
|
+
const table = content.match(/__tablename__\s*=\s*['"](\w+)['"]/);
|
|
551
|
+
return cls ? `SQLAlchemy: ${cls[1]}${table ? ` (${table[1]})` : ""}` : "SQLAlchemy model";
|
|
552
|
+
}
|
|
553
|
+
// Celery task
|
|
554
|
+
if (content.includes("@shared_task") || content.includes("@app.task") || content.includes("@celery.task")) {
|
|
555
|
+
const tasks = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
556
|
+
return tasks.length ? `Celery tasks: ${tasks.join(", ")}` : "Celery task";
|
|
557
|
+
}
|
|
558
|
+
// Pytest
|
|
559
|
+
if (basename.startsWith("test_") || basename.endsWith("_test.py")) {
|
|
560
|
+
const tests = (content.match(/def\s+test_(\w+)/g) || []).map(m => m.match(/test_(\w+)/)?.[1]).filter(Boolean);
|
|
561
|
+
return tests.length ? `Tests: ${tests.slice(0, 4).join(", ")}${tests.length > 4 ? ` + ${tests.length - 4} more` : ""}` : "Test file";
|
|
562
|
+
}
|
|
563
|
+
// Generic class + functions
|
|
564
|
+
const cls = content.match(/class\s+(\w+)/);
|
|
565
|
+
const funcs = (content.match(/def\s+(\w+)/g) || [])
|
|
566
|
+
.map(f => f.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
567
|
+
if (cls && funcs.length) {
|
|
568
|
+
const display = funcs.slice(0, 4).join(", ");
|
|
569
|
+
return funcs.length > 4 ? `${cls[1]}: ${display} + ${funcs.length - 4} more` : `${cls[1]}: ${display}`;
|
|
570
|
+
}
|
|
571
|
+
if (funcs.length) {
|
|
572
|
+
return funcs.length > 4 ? `${funcs.slice(0, 4).join(", ")} + ${funcs.length - 4} more` : funcs.join(", ");
|
|
573
|
+
}
|
|
574
|
+
return "";
|
|
575
|
+
}
|
|
576
|
+
// ─── Go ──────────────────────────────────────────────────────
|
|
577
|
+
function extractGo(content) {
|
|
578
|
+
// HTTP handlers
|
|
579
|
+
const handlers = (content.match(/func\s+(\w+)\s*\(\s*\w+\s+http\.ResponseWriter/g) || [])
|
|
580
|
+
.map(m => m.match(/func\s+(\w+)/)?.[1]).filter(Boolean);
|
|
581
|
+
if (handlers.length)
|
|
582
|
+
return `HTTP handlers: ${handlers.slice(0, 5).join(", ")}${handlers.length > 5 ? ` + ${handlers.length - 5} more` : ""}`;
|
|
583
|
+
// Interface
|
|
584
|
+
const iface = content.match(/type\s+(\w+)\s+interface\s*\{/);
|
|
585
|
+
if (iface) {
|
|
586
|
+
const methods = (content.match(/^\s+(\w+)\s*\(/gm) || []).length;
|
|
587
|
+
return `Interface: ${iface[1]} (${methods} methods)`;
|
|
588
|
+
}
|
|
589
|
+
// Struct
|
|
590
|
+
const structMatch = content.match(/type\s+(\w+)\s+struct\s*\{/);
|
|
591
|
+
if (structMatch) {
|
|
592
|
+
const fields = (content.match(/^\s+\w+\s+\w+/gm) || []).length;
|
|
593
|
+
const methods = (content.match(/func\s+\(\w+\s+\*?\w+\)\s+(\w+)/g) || [])
|
|
594
|
+
.map(m => m.match(/\)\s+(\w+)/)?.[1]).filter(n => n && n[0] === n[0].toUpperCase());
|
|
595
|
+
const parts = [`${structMatch[1]} (${fields} fields)`];
|
|
596
|
+
if (methods.length)
|
|
597
|
+
parts.push(`methods: ${methods.slice(0, 4).join(", ")}`);
|
|
598
|
+
return parts.join("; ");
|
|
599
|
+
}
|
|
600
|
+
// Package functions
|
|
601
|
+
const funcs = (content.match(/^func\s+(\w+)/gm) || [])
|
|
602
|
+
.map(m => m.match(/func\s+(\w+)/)?.[1]).filter(n => n && n[0] === n[0].toUpperCase());
|
|
603
|
+
if (funcs.length)
|
|
604
|
+
return funcs.length > 5 ? `${funcs.slice(0, 4).join(", ")} + ${funcs.length - 4} more` : funcs.join(", ");
|
|
605
|
+
return "";
|
|
606
|
+
}
|
|
607
|
+
// ─── Rust ────────────────────────────────────────────────────
|
|
608
|
+
function extractRust(content) {
|
|
609
|
+
// Struct + impl
|
|
610
|
+
const structM = content.match(/pub\s+struct\s+(\w+)/);
|
|
611
|
+
if (structM) {
|
|
612
|
+
const methods = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || [])
|
|
613
|
+
.map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
614
|
+
if (methods.length)
|
|
615
|
+
return `${structM[1]}: ${methods.slice(0, 4).join(", ")}${methods.length > 4 ? ` + ${methods.length - 4} more` : ""}`;
|
|
616
|
+
return `Struct: ${structM[1]}`;
|
|
617
|
+
}
|
|
618
|
+
// Trait
|
|
619
|
+
const traitM = content.match(/pub\s+trait\s+(\w+)/);
|
|
620
|
+
if (traitM) {
|
|
621
|
+
const fns = (content.match(/fn\s+(\w+)/g) || []).length;
|
|
622
|
+
return `Trait: ${traitM[1]} (${fns} methods)`;
|
|
623
|
+
}
|
|
624
|
+
// Enum
|
|
625
|
+
const enumM = content.match(/pub\s+enum\s+(\w+)/);
|
|
626
|
+
if (enumM) {
|
|
627
|
+
const variants = (content.match(/^\s+(\w+)[\s({,]/gm) || []).length;
|
|
628
|
+
return `Enum: ${enumM[1]} (${variants} variants)`;
|
|
629
|
+
}
|
|
630
|
+
// Actix/Axum handlers
|
|
631
|
+
const handlers = (content.match(/#\[(?:get|post|put|patch|delete)\s*\("/g) || []).length;
|
|
632
|
+
if (handlers)
|
|
633
|
+
return `Web handlers: ${handlers} endpoints`;
|
|
634
|
+
// Public functions
|
|
635
|
+
const fns = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || [])
|
|
636
|
+
.map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
637
|
+
if (fns.length)
|
|
638
|
+
return fns.length > 5 ? `${fns.slice(0, 4).join(", ")} + ${fns.length - 4} more` : fns.join(", ");
|
|
639
|
+
return "";
|
|
640
|
+
}
|
|
641
|
+
// ─── Java ────────────────────────────────────────────────────
|
|
642
|
+
function extractJava(content, basename) {
|
|
643
|
+
const cls = content.match(/(?:public\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
|
|
644
|
+
const className = cls?.[1] || basename.replace(".java", "");
|
|
645
|
+
const parent = cls?.[2] || "";
|
|
646
|
+
// Spring annotations
|
|
647
|
+
const annotations = (content.match(/@(RestController|Controller|Service|Repository|Component|Entity|Configuration)/g) || [])
|
|
648
|
+
.map(a => a.slice(1));
|
|
649
|
+
// Spring endpoints
|
|
650
|
+
const mappings = (content.match(/@(?:Get|Post|Put|Patch|Delete|Request)Mapping/g) || []).length;
|
|
651
|
+
if (mappings)
|
|
652
|
+
return `${annotations[0] || "Spring"}: ${className} (${mappings} endpoints)`;
|
|
653
|
+
if (annotations.length)
|
|
654
|
+
return `${annotations[0]}: ${className}`;
|
|
655
|
+
// JPA Entity
|
|
656
|
+
if (content.includes("@Entity") || content.includes("@Table")) {
|
|
657
|
+
const table = content.match(/@Table\s*\(\s*name\s*=\s*"(\w+)"/);
|
|
658
|
+
return table ? `Entity: ${className} (table: ${table[1]})` : `Entity: ${className}`;
|
|
659
|
+
}
|
|
660
|
+
// Public methods
|
|
661
|
+
const methods = (content.match(/public\s+(?:static\s+)?(?:\w+(?:<[\w,\s]+>)?)\s+(\w+)\s*\(/g) || [])
|
|
662
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== className);
|
|
663
|
+
if (methods.length)
|
|
664
|
+
return `${className}: ${methods.slice(0, 4).join(", ")}${methods.length > 4 ? ` + ${methods.length - 4} more` : ""}`;
|
|
665
|
+
return className ? `Class: ${className}` : "";
|
|
666
|
+
}
|
|
667
|
+
// ─── Kotlin ──────────────────────────────────────────────────
|
|
668
|
+
function extractKotlin(content, basename) {
|
|
669
|
+
const cls = content.match(/(?:data\s+)?class\s+(\w+)/);
|
|
670
|
+
const className = cls?.[1] || basename.replace(/\.kts?$/, "");
|
|
671
|
+
// Data class (brief)
|
|
672
|
+
if (content.match(/data\s+class/)) {
|
|
673
|
+
const props = (content.match(/val\s+\w+:/g) || []).length + (content.match(/var\s+\w+:/g) || []).length;
|
|
674
|
+
return `Data class: ${className} (${props} properties)`;
|
|
675
|
+
}
|
|
676
|
+
// Ktor / Spring
|
|
677
|
+
if (content.includes("routing {") || content.includes("route(")) {
|
|
678
|
+
const routes = (content.match(/(?:get|post|put|patch|delete)\s*\(\s*"/g) || []).length;
|
|
679
|
+
return routes ? `Ktor routes: ${routes} endpoints` : "Ktor routing";
|
|
680
|
+
}
|
|
681
|
+
// Functions
|
|
682
|
+
const fns = (content.match(/fun\s+(\w+)/g) || [])
|
|
683
|
+
.map(m => m.match(/fun\s+(\w+)/)?.[1]).filter(Boolean);
|
|
684
|
+
if (cls && fns.length)
|
|
685
|
+
return `${className}: ${fns.slice(0, 4).join(", ")}${fns.length > 4 ? ` + ${fns.length - 4} more` : ""}`;
|
|
686
|
+
if (fns.length)
|
|
687
|
+
return fns.slice(0, 5).join(", ");
|
|
688
|
+
return "";
|
|
689
|
+
}
|
|
690
|
+
// ─── C# / .NET ───────────────────────────────────────────────
|
|
691
|
+
function extractCSharp(content, basename) {
|
|
692
|
+
const cls = content.match(/(?:public\s+)?(?:partial\s+)?class\s+(\w+)(?:\s*:\s*(\w+))?/);
|
|
693
|
+
const className = cls?.[1] || basename.replace(".cs", "");
|
|
694
|
+
const parent = cls?.[2] || "";
|
|
695
|
+
// ASP.NET Controller
|
|
696
|
+
if (parent === "Controller" || parent === "ControllerBase" || content.includes("[ApiController]")) {
|
|
697
|
+
const actions = (content.match(/\[Http(Get|Post|Put|Patch|Delete)\]/g) || [])
|
|
698
|
+
.map(a => a.match(/Http(\w+)/)?.[1]).filter(Boolean);
|
|
699
|
+
return actions.length ? `API Controller: ${className} (${[...new Set(actions)].join(", ")})` : `Controller: ${className}`;
|
|
700
|
+
}
|
|
701
|
+
// EF DbContext
|
|
702
|
+
if (parent === "DbContext" || content.includes("DbSet<")) {
|
|
703
|
+
const sets = (content.match(/DbSet<(\w+)>/g) || []).map(s => s.match(/<(\w+)>/)?.[1]).filter(Boolean);
|
|
704
|
+
return sets.length ? `DbContext: ${sets.join(", ")}` : `DbContext: ${className}`;
|
|
705
|
+
}
|
|
706
|
+
// EF Entity
|
|
707
|
+
if (content.includes("[Table(") || content.includes("[Key]")) {
|
|
708
|
+
return `Entity: ${className}`;
|
|
709
|
+
}
|
|
710
|
+
// Interface
|
|
711
|
+
if (content.match(/interface\s+I\w+/)) {
|
|
712
|
+
const methods = (content.match(/\w+\s+\w+\s*\(/g) || []).length;
|
|
713
|
+
return `Interface: ${className} (${methods} members)`;
|
|
714
|
+
}
|
|
715
|
+
// Public methods
|
|
716
|
+
const methods = (content.match(/public\s+(?:async\s+)?(?:static\s+)?(?:virtual\s+)?(?:override\s+)?(?:\w+(?:<[\w,\s]+>)?)\s+(\w+)\s*\(/g) || [])
|
|
717
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== className);
|
|
718
|
+
if (methods.length)
|
|
719
|
+
return `${className}: ${methods.slice(0, 4).join(", ")}${methods.length > 4 ? ` + ${methods.length - 4} more` : ""}`;
|
|
720
|
+
return className ? `Class: ${className}` : "";
|
|
721
|
+
}
|
|
722
|
+
// ─── Ruby / Rails ────────────────────────────────────────────
|
|
723
|
+
function extractRuby(content, basename) {
|
|
724
|
+
const cls = content.match(/class\s+(\w+)(?:\s*<\s*(\w+(?:::\w+)?))?/);
|
|
725
|
+
const className = cls?.[1] || "";
|
|
726
|
+
const parent = cls?.[2] || "";
|
|
727
|
+
// Rails controller
|
|
728
|
+
if (parent?.includes("Controller") || basename.endsWith("_controller.rb")) {
|
|
729
|
+
const actions = (content.match(/def\s+(index|show|new|create|edit|update|destroy|search|\w+)/g) || [])
|
|
730
|
+
.map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
731
|
+
return actions.length ? `Controller: ${actions.join(", ")}` : `Controller: ${className}`;
|
|
732
|
+
}
|
|
733
|
+
// Rails model
|
|
734
|
+
if (parent === "ApplicationRecord" || parent === "ActiveRecord::Base") {
|
|
735
|
+
const assocs = (content.match(/(?:has_many|has_one|belongs_to|has_and_belongs_to_many)\s+:(\w+)/g) || [])
|
|
736
|
+
.map(m => m.match(/:(\w+)/)?.[1]).filter(Boolean);
|
|
737
|
+
const validations = (content.match(/validates\s/g) || []).length;
|
|
738
|
+
const scopes = (content.match(/scope\s+:(\w+)/g) || []).length;
|
|
739
|
+
const parts = [];
|
|
740
|
+
if (assocs.length)
|
|
741
|
+
parts.push(`assocs: ${assocs.join(", ")}`);
|
|
742
|
+
if (validations)
|
|
743
|
+
parts.push(`${validations} validations`);
|
|
744
|
+
if (scopes)
|
|
745
|
+
parts.push(`${scopes} scopes`);
|
|
746
|
+
return parts.length ? `Model: ${className} — ${parts.join(", ")}` : `Model: ${className}`;
|
|
747
|
+
}
|
|
748
|
+
// Rails migration
|
|
749
|
+
if (basename.match(/^\d{14}_/)) {
|
|
750
|
+
const create = content.match(/create_table\s+:(\w+)/);
|
|
751
|
+
if (create)
|
|
752
|
+
return `Migration: create ${create[1]}`;
|
|
753
|
+
const change = content.match(/(?:add|remove|rename)_column\s+:(\w+)/);
|
|
754
|
+
if (change)
|
|
755
|
+
return `Migration: alter ${change[1]}`;
|
|
756
|
+
return "Database migration";
|
|
757
|
+
}
|
|
758
|
+
// Methods
|
|
759
|
+
const methods = (content.match(/def\s+(\w+)/g) || [])
|
|
760
|
+
.map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
761
|
+
if (cls && methods.length)
|
|
762
|
+
return `${className}: ${methods.slice(0, 4).join(", ")}${methods.length > 4 ? ` + ${methods.length - 4} more` : ""}`;
|
|
763
|
+
if (methods.length)
|
|
764
|
+
return methods.slice(0, 5).join(", ");
|
|
765
|
+
return "";
|
|
766
|
+
}
|
|
767
|
+
// ─── Swift ───────────────────────────────────────────────────
|
|
768
|
+
function extractSwift(content) {
|
|
769
|
+
// SwiftUI View
|
|
770
|
+
if (content.includes(": View") || content.includes("some View")) {
|
|
771
|
+
const name = content.match(/struct\s+(\w+)\s*:\s*View/);
|
|
772
|
+
return name ? `SwiftUI view: ${name[1]}` : "SwiftUI view";
|
|
773
|
+
}
|
|
774
|
+
const struct = content.match(/(?:public\s+)?struct\s+(\w+)/);
|
|
775
|
+
const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
|
|
776
|
+
const proto = content.match(/protocol\s+(\w+)/);
|
|
777
|
+
if (proto) {
|
|
778
|
+
const reqs = (content.match(/func\s+(\w+)/g) || []).length;
|
|
779
|
+
return `Protocol: ${proto[1]} (${reqs} requirements)`;
|
|
780
|
+
}
|
|
781
|
+
const name = struct?.[1] || cls?.[1] || "";
|
|
782
|
+
const funcs = (content.match(/func\s+(\w+)/g) || [])
|
|
783
|
+
.map(m => m.match(/func\s+(\w+)/)?.[1]).filter(Boolean);
|
|
784
|
+
if (name && funcs.length)
|
|
785
|
+
return `${name}: ${funcs.slice(0, 4).join(", ")}${funcs.length > 4 ? ` + ${funcs.length - 4} more` : ""}`;
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
// ─── Dart / Flutter ──────────────────────────────────────────
|
|
789
|
+
function extractDart(content, basename) {
|
|
790
|
+
// Flutter widget
|
|
791
|
+
if (content.includes("StatefulWidget") || content.includes("StatelessWidget")) {
|
|
792
|
+
const name = content.match(/class\s+(\w+)\s+extends\s+(?:Stateful|Stateless)Widget/);
|
|
793
|
+
const type = content.includes("StatefulWidget") ? "Stateful" : "Stateless";
|
|
794
|
+
return name ? `${type} widget: ${name[1]}` : `${type} widget`;
|
|
795
|
+
}
|
|
796
|
+
// Riverpod/Provider
|
|
797
|
+
if (content.includes("@riverpod") || content.includes("Provider(")) {
|
|
798
|
+
return "Riverpod provider";
|
|
799
|
+
}
|
|
800
|
+
const cls = content.match(/class\s+(\w+)/);
|
|
801
|
+
const methods = (content.match(/(?:void|Future|String|int|bool|dynamic|Widget)\s+(\w+)\s*\(/g) || [])
|
|
802
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(Boolean);
|
|
803
|
+
if (cls && methods.length)
|
|
804
|
+
return `${cls[1]}: ${methods.slice(0, 4).join(", ")}`;
|
|
805
|
+
return "";
|
|
806
|
+
}
|
|
807
|
+
// ─── Vue ─────────────────────────────────────────────────────
|
|
808
|
+
function extractVue(content) {
|
|
809
|
+
const name = content.match(/name:\s*['"]([^'"]+)['"]/);
|
|
810
|
+
const setup = content.includes("<script setup");
|
|
811
|
+
const ts = content.includes('lang="ts"');
|
|
812
|
+
// Props
|
|
813
|
+
const propsMatch = content.match(/defineProps<\{([^}]+)\}>/s) || content.match(/props:\s*\{([^}]+)\}/s);
|
|
814
|
+
const propCount = propsMatch ? (propsMatch[1].match(/\w+\s*[:\?]/g) || []).length : 0;
|
|
815
|
+
// Emits
|
|
816
|
+
const emits = (content.match(/defineEmits|emit\s*\(/g) || []).length;
|
|
817
|
+
const parts = [];
|
|
818
|
+
if (name)
|
|
819
|
+
parts.push(name[1]);
|
|
820
|
+
if (setup)
|
|
821
|
+
parts.push("setup");
|
|
822
|
+
if (ts)
|
|
823
|
+
parts.push("TS");
|
|
824
|
+
if (propCount)
|
|
825
|
+
parts.push(`${propCount} props`);
|
|
826
|
+
if (emits)
|
|
827
|
+
parts.push(`emits`);
|
|
828
|
+
return parts.length ? `Vue: ${parts.join(", ")}` : "Vue component";
|
|
829
|
+
}
|
|
830
|
+
// ─── Svelte ──────────────────────────────────────────────────
|
|
831
|
+
function extractSvelte(content, basename) {
|
|
832
|
+
const ts = content.includes('lang="ts"');
|
|
833
|
+
const props = (content.match(/export\s+let\s+(\w+)/g) || []).length;
|
|
834
|
+
const stores = (content.match(/\$\w+/g) || []).length;
|
|
835
|
+
const parts = [basename.replace(".svelte", "")];
|
|
836
|
+
if (ts)
|
|
837
|
+
parts.push("TS");
|
|
838
|
+
if (props)
|
|
839
|
+
parts.push(`${props} props`);
|
|
840
|
+
if (stores)
|
|
841
|
+
parts.push(`${stores} stores`);
|
|
842
|
+
return `Svelte: ${parts.join(", ")}`;
|
|
843
|
+
}
|
|
844
|
+
// ─── Astro ───────────────────────────────────────────────────
|
|
845
|
+
function extractAstro(content, basename) {
|
|
846
|
+
const imports = (content.match(/import\s+\w+\s+from/g) || []).length;
|
|
847
|
+
const slots = (content.match(/<slot/g) || []).length;
|
|
848
|
+
const parts = [basename.replace(".astro", "")];
|
|
849
|
+
if (slots)
|
|
850
|
+
parts.push(`${slots} slot(s)`);
|
|
851
|
+
if (imports > 3)
|
|
852
|
+
parts.push(`${imports} imports`);
|
|
853
|
+
return `Astro: ${parts.join(", ")}`;
|
|
854
|
+
}
|
|
855
|
+
// ─── CSS / SCSS / Less ───────────────────────────────────────
|
|
856
|
+
function extractCss(content) {
|
|
857
|
+
const rules = (content.match(/^[.#@][^\n{]+/gm) || []).length;
|
|
858
|
+
const media = (content.match(/@media/g) || []).length;
|
|
859
|
+
const animations = (content.match(/@keyframes\s+(\w+)/g) || []).length;
|
|
860
|
+
const vars = (content.match(/--[\w-]+\s*:/g) || []).length;
|
|
861
|
+
const layers = (content.match(/@layer/g) || []).length;
|
|
862
|
+
const parts = [];
|
|
863
|
+
if (rules)
|
|
864
|
+
parts.push(`${rules} rules`);
|
|
865
|
+
if (vars)
|
|
866
|
+
parts.push(`${vars} vars`);
|
|
867
|
+
if (media)
|
|
868
|
+
parts.push(`${media} media queries`);
|
|
869
|
+
if (animations)
|
|
870
|
+
parts.push(`${animations} animations`);
|
|
871
|
+
if (layers)
|
|
872
|
+
parts.push(`${layers} layers`);
|
|
873
|
+
return parts.length ? `Styles: ${parts.join(", ")}` : "";
|
|
874
|
+
}
|
|
875
|
+
// ─── SQL ─────────────────────────────────────────────────────
|
|
876
|
+
function extractSql(content) {
|
|
877
|
+
const creates = (content.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)/gi) || [])
|
|
878
|
+
.map(m => m.match(/(?:TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?)([`"']?\w+)/i)?.[1]?.replace(/[`"']/g, "")).filter(Boolean);
|
|
879
|
+
const alters = (content.match(/ALTER\s+TABLE\s+[`"']?(\w+)/gi) || []).length;
|
|
880
|
+
const views = (content.match(/CREATE\s+(?:OR\s+REPLACE\s+)?VIEW/gi) || []).length;
|
|
881
|
+
const functions = (content.match(/CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION/gi) || []).length;
|
|
882
|
+
const parts = [];
|
|
883
|
+
if (creates.length)
|
|
884
|
+
parts.push(`tables: ${creates.slice(0, 4).join(", ")}`);
|
|
885
|
+
if (alters)
|
|
886
|
+
parts.push(`${alters} alter(s)`);
|
|
887
|
+
if (views)
|
|
888
|
+
parts.push(`${views} view(s)`);
|
|
889
|
+
if (functions)
|
|
890
|
+
parts.push(`${functions} function(s)`);
|
|
891
|
+
return parts.length ? `SQL: ${parts.join(", ")}` : "";
|
|
892
|
+
}
|
|
893
|
+
// ─── Protocol Buffers ────────────────────────────────────────
|
|
894
|
+
function extractProto(content) {
|
|
895
|
+
const msgs = (content.match(/message\s+(\w+)/g) || []).map(m => m.match(/message\s+(\w+)/)?.[1]).filter(Boolean);
|
|
896
|
+
const services = (content.match(/service\s+(\w+)/g) || []).map(m => m.match(/service\s+(\w+)/)?.[1]).filter(Boolean);
|
|
897
|
+
const parts = [];
|
|
898
|
+
if (msgs.length)
|
|
899
|
+
parts.push(`messages: ${msgs.slice(0, 3).join(", ")}`);
|
|
900
|
+
if (services.length)
|
|
901
|
+
parts.push(`services: ${services.join(", ")}`);
|
|
902
|
+
return parts.length ? `Proto: ${parts.join(", ")}` : "";
|
|
903
|
+
}
|
|
904
|
+
// ─── GraphQL ─────────────────────────────────────────────────
|
|
905
|
+
function extractGraphQL(content) {
|
|
906
|
+
const types = (content.match(/type\s+(\w+)/g) || []).map(m => m.match(/type\s+(\w+)/)?.[1]).filter(Boolean);
|
|
907
|
+
const queries = (content.match(/(?:query|mutation|subscription)\s+(\w+)/g) || []).length;
|
|
908
|
+
const parts = [];
|
|
909
|
+
if (types.length)
|
|
910
|
+
parts.push(`types: ${types.slice(0, 4).join(", ")}`);
|
|
911
|
+
if (queries)
|
|
912
|
+
parts.push(`${queries} operations`);
|
|
913
|
+
return parts.length ? `GraphQL: ${parts.join(", ")}` : "";
|
|
914
|
+
}
|
|
915
|
+
// ─── YAML ────────────────────────────────────────────────────
|
|
916
|
+
function extractYaml(content, basename) {
|
|
917
|
+
// GitHub Actions
|
|
918
|
+
if (content.includes("runs-on:") || content.includes("uses:")) {
|
|
919
|
+
const name = content.match(/^name:\s*(.+)$/m);
|
|
920
|
+
return name ? `CI: ${name[1].trim()}` : "GitHub Actions workflow";
|
|
921
|
+
}
|
|
922
|
+
// Kubernetes
|
|
923
|
+
if (content.includes("apiVersion:") && content.includes("kind:")) {
|
|
924
|
+
const kind = content.match(/kind:\s*(\w+)/);
|
|
925
|
+
const name = content.match(/name:\s*(\S+)/);
|
|
926
|
+
return kind ? `K8s ${kind[1]}${name ? `: ${name[1]}` : ""}` : "Kubernetes manifest";
|
|
927
|
+
}
|
|
928
|
+
// Docker Compose
|
|
929
|
+
if (content.includes("services:") && (basename.includes("docker") || basename.includes("compose"))) {
|
|
930
|
+
const services = (content.match(/^\s{2}\w+:/gm) || []).length;
|
|
931
|
+
return `Docker Compose: ${services} services`;
|
|
932
|
+
}
|
|
933
|
+
return "";
|
|
934
|
+
}
|
|
935
|
+
// ─── TOML ────────────────────────────────────────────────────
|
|
936
|
+
function extractToml(content, basename) {
|
|
937
|
+
if (basename === "Cargo.toml") {
|
|
938
|
+
const name = content.match(/^name\s*=\s*"([^"]+)"/m);
|
|
939
|
+
const desc = content.match(/^description\s*=\s*"([^"]+)"/m);
|
|
940
|
+
return desc ? desc[1] : name ? `Rust crate: ${name[1]}` : "Rust package manifest";
|
|
941
|
+
}
|
|
942
|
+
if (basename === "pyproject.toml") {
|
|
943
|
+
const name = content.match(/^name\s*=\s*"([^"]+)"/m);
|
|
944
|
+
const desc = content.match(/^description\s*=\s*"([^"]+)"/m);
|
|
945
|
+
return desc ? desc[1] : name ? `Python project: ${name[1]}` : "Python project configuration";
|
|
946
|
+
}
|
|
947
|
+
return "";
|
|
948
|
+
}
|
|
949
|
+
// ─── Elixir ──────────────────────────────────────────────────
|
|
950
|
+
function extractElixir(content) {
|
|
951
|
+
const mod = content.match(/defmodule\s+([\w.]+)/);
|
|
952
|
+
const fns = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(Boolean);
|
|
953
|
+
// Phoenix controller/live view
|
|
954
|
+
if (content.includes("use") && content.includes("Controller")) {
|
|
955
|
+
return mod ? `Phoenix controller: ${mod[1]}` : "Phoenix controller";
|
|
956
|
+
}
|
|
957
|
+
if (content.includes("Phoenix.LiveView")) {
|
|
958
|
+
return mod ? `LiveView: ${mod[1]}` : "Phoenix LiveView";
|
|
959
|
+
}
|
|
960
|
+
if (mod && fns.length)
|
|
961
|
+
return `${mod[1]}: ${fns.slice(0, 4).join(", ")}`;
|
|
962
|
+
return mod ? mod[1] : "";
|
|
963
|
+
}
|
|
964
|
+
// ─── Lua ─────────────────────────────────────────────────────
|
|
965
|
+
function extractLua(content) {
|
|
966
|
+
const fns = (content.match(/function\s+(?:\w+[.:])?(\w+)/g) || [])
|
|
967
|
+
.map(m => m.match(/(\w+)\s*$/)?.[1]).filter(Boolean);
|
|
968
|
+
if (fns.length)
|
|
969
|
+
return fns.length > 5 ? `${fns.slice(0, 4).join(", ")} + ${fns.length - 4} more` : fns.join(", ");
|
|
970
|
+
return "";
|
|
971
|
+
}
|
|
972
|
+
// ─── Zig ─────────────────────────────────────────────────────
|
|
973
|
+
function extractZig(content) {
|
|
974
|
+
const fns = (content.match(/pub\s+fn\s+(\w+)/g) || [])
|
|
975
|
+
.map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
976
|
+
if (fns.length)
|
|
977
|
+
return fns.length > 5 ? `${fns.slice(0, 4).join(", ")} + ${fns.length - 4} more` : fns.join(", ");
|
|
978
|
+
return "";
|
|
979
|
+
}
|
|
980
|
+
// ─── Generic fallback ────────────────────────────────────────
|
|
981
|
+
function extractGenericFallback(content) {
|
|
982
|
+
// Try exports
|
|
983
|
+
const exp = content.match(/export\s+(?:default\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/);
|
|
984
|
+
if (exp)
|
|
985
|
+
return `Exports ${exp[1]}`;
|
|
986
|
+
// Try class with methods
|
|
987
|
+
const cls = content.match(/(?:function|class|const|interface|type|enum)\s+(\w+)/);
|
|
988
|
+
if (cls) {
|
|
989
|
+
const name = cls[1];
|
|
990
|
+
const methods = (content.match(/(?:public\s+)?(?:async\s+)?(?:function\s+|(?:get|set)\s+)(\w+)\s*\(/g) || [])
|
|
991
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1])
|
|
992
|
+
.filter(n => n && n !== name && n !== "__construct" && n !== "constructor");
|
|
993
|
+
if (methods.length > 0 && methods.length <= 5)
|
|
994
|
+
return `${name}: ${methods.join(", ")}`;
|
|
995
|
+
if (methods.length > 5)
|
|
996
|
+
return `${name}: ${methods.slice(0, 3).join(", ")} + ${methods.length - 3} more`;
|
|
997
|
+
return `Declares ${name}`;
|
|
998
|
+
}
|
|
999
|
+
return "";
|
|
1000
|
+
}
|
|
1001
|
+
// ─── Utility ─────────────────────────────────────────────────
|
|
1002
|
+
export function capDescription(desc, max = MAX_DESC) {
|
|
1003
|
+
if (desc.length <= max)
|
|
1004
|
+
return desc;
|
|
1005
|
+
return desc.slice(0, max - 3) + "...";
|
|
1006
|
+
}
|
|
1007
|
+
//# sourceMappingURL=description-extractor.js.map
|