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.
Files changed (112) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +232 -0
  3. package/dist/bin/openwolf.js +10 -0
  4. package/dist/bin/openwolf.js.map +1 -0
  5. package/dist/dashboard/assets/AISuggestions-DzE-DQkR.js +1 -0
  6. package/dist/dashboard/assets/ActivityTimeline-DGVjujnt.js +1 -0
  7. package/dist/dashboard/assets/AnatomyBrowser-S-2rmYtw.js +1 -0
  8. package/dist/dashboard/assets/BugLog-CG2zDHJc.js +1 -0
  9. package/dist/dashboard/assets/CerebrumViewer-Dlgoy69U.js +1 -0
  10. package/dist/dashboard/assets/CronStatus-DxUF1iW_.js +1 -0
  11. package/dist/dashboard/assets/DesignQC-BGXn_aq8.js +1 -0
  12. package/dist/dashboard/assets/MemoryViewer-CGqkTyvQ.js +1 -0
  13. package/dist/dashboard/assets/ProjectOverview-DlFhu69i.js +1 -0
  14. package/dist/dashboard/assets/TokenUsage-DDsQiVIq.js +68 -0
  15. package/dist/dashboard/assets/index-CzK9GUjV.css +1 -0
  16. package/dist/dashboard/assets/index-PYeNGjkN.js +52 -0
  17. package/dist/dashboard/index.html +16 -0
  18. package/dist/hooks/post-read.js +68 -0
  19. package/dist/hooks/post-write.js +502 -0
  20. package/dist/hooks/pre-read.js +79 -0
  21. package/dist/hooks/pre-write.js +120 -0
  22. package/dist/hooks/session-start.js +76 -0
  23. package/dist/hooks/shared.js +613 -0
  24. package/dist/hooks/stop.js +146 -0
  25. package/dist/src/buglog/bug-matcher.js +3 -0
  26. package/dist/src/buglog/bug-matcher.js.map +1 -0
  27. package/dist/src/buglog/bug-tracker.js +81 -0
  28. package/dist/src/buglog/bug-tracker.js.map +1 -0
  29. package/dist/src/cli/bug-cmd.js +28 -0
  30. package/dist/src/cli/bug-cmd.js.map +1 -0
  31. package/dist/src/cli/cron-cmd.js +106 -0
  32. package/dist/src/cli/cron-cmd.js.map +1 -0
  33. package/dist/src/cli/daemon-cmd.js +177 -0
  34. package/dist/src/cli/daemon-cmd.js.map +1 -0
  35. package/dist/src/cli/dashboard.js +84 -0
  36. package/dist/src/cli/dashboard.js.map +1 -0
  37. package/dist/src/cli/designqc-cmd.js +31 -0
  38. package/dist/src/cli/designqc-cmd.js.map +1 -0
  39. package/dist/src/cli/index.js +149 -0
  40. package/dist/src/cli/index.js.map +1 -0
  41. package/dist/src/cli/init.js +506 -0
  42. package/dist/src/cli/init.js.map +1 -0
  43. package/dist/src/cli/registry.js +93 -0
  44. package/dist/src/cli/registry.js.map +1 -0
  45. package/dist/src/cli/scan.js +39 -0
  46. package/dist/src/cli/scan.js.map +1 -0
  47. package/dist/src/cli/status.js +85 -0
  48. package/dist/src/cli/status.js.map +1 -0
  49. package/dist/src/cli/update.js +414 -0
  50. package/dist/src/cli/update.js.map +1 -0
  51. package/dist/src/daemon/cron-engine.js +300 -0
  52. package/dist/src/daemon/cron-engine.js.map +1 -0
  53. package/dist/src/daemon/file-watcher.js +53 -0
  54. package/dist/src/daemon/file-watcher.js.map +1 -0
  55. package/dist/src/daemon/health.js +23 -0
  56. package/dist/src/daemon/health.js.map +1 -0
  57. package/dist/src/daemon/wolf-daemon.js +294 -0
  58. package/dist/src/daemon/wolf-daemon.js.map +1 -0
  59. package/dist/src/designqc/designqc-capture.js +235 -0
  60. package/dist/src/designqc/designqc-capture.js.map +1 -0
  61. package/dist/src/designqc/designqc-engine.js +141 -0
  62. package/dist/src/designqc/designqc-engine.js.map +1 -0
  63. package/dist/src/designqc/designqc-types.js +5 -0
  64. package/dist/src/designqc/designqc-types.js.map +1 -0
  65. package/dist/src/hooks/post-read.js +69 -0
  66. package/dist/src/hooks/post-read.js.map +1 -0
  67. package/dist/src/hooks/post-write.js +503 -0
  68. package/dist/src/hooks/post-write.js.map +1 -0
  69. package/dist/src/hooks/pre-read.js +80 -0
  70. package/dist/src/hooks/pre-read.js.map +1 -0
  71. package/dist/src/hooks/pre-write.js +121 -0
  72. package/dist/src/hooks/pre-write.js.map +1 -0
  73. package/dist/src/hooks/session-start.js +77 -0
  74. package/dist/src/hooks/session-start.js.map +1 -0
  75. package/dist/src/hooks/shared.js +614 -0
  76. package/dist/src/hooks/shared.js.map +1 -0
  77. package/dist/src/hooks/stop.js +147 -0
  78. package/dist/src/hooks/stop.js.map +1 -0
  79. package/dist/src/scanner/anatomy-scanner.js +260 -0
  80. package/dist/src/scanner/anatomy-scanner.js.map +1 -0
  81. package/dist/src/scanner/description-extractor.js +1007 -0
  82. package/dist/src/scanner/description-extractor.js.map +1 -0
  83. package/dist/src/scanner/project-root.js +42 -0
  84. package/dist/src/scanner/project-root.js.map +1 -0
  85. package/dist/src/tracker/token-estimator.js +20 -0
  86. package/dist/src/tracker/token-estimator.js.map +1 -0
  87. package/dist/src/tracker/token-ledger.js +45 -0
  88. package/dist/src/tracker/token-ledger.js.map +1 -0
  89. package/dist/src/tracker/waste-detector.js +101 -0
  90. package/dist/src/tracker/waste-detector.js.map +1 -0
  91. package/dist/src/utils/fs-safe.js +74 -0
  92. package/dist/src/utils/fs-safe.js.map +1 -0
  93. package/dist/src/utils/logger.js +48 -0
  94. package/dist/src/utils/logger.js.map +1 -0
  95. package/dist/src/utils/paths.js +23 -0
  96. package/dist/src/utils/paths.js.map +1 -0
  97. package/dist/src/utils/platform.js +14 -0
  98. package/dist/src/utils/platform.js.map +1 -0
  99. package/package.json +77 -0
  100. package/src/templates/OPENWOLF.md +135 -0
  101. package/src/templates/anatomy.md +5 -0
  102. package/src/templates/buglog.json +4 -0
  103. package/src/templates/cerebrum.md +22 -0
  104. package/src/templates/claude-md-snippet.md +5 -0
  105. package/src/templates/claude-rules-openwolf.md +15 -0
  106. package/src/templates/config.json +73 -0
  107. package/src/templates/cron-manifest.json +97 -0
  108. package/src/templates/cron-state.json +7 -0
  109. package/src/templates/identity.md +9 -0
  110. package/src/templates/memory.md +4 -0
  111. package/src/templates/reframe-frameworks.md +597 -0
  112. 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