@chappibunny/repolens 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  RepoLens scans your repository, generates living architecture documentation, and publishes it to Notion, Confluence, GitHub Wiki, or Markdown — automatically on every push. Engineers get technical docs. Stakeholders get readable system overviews. Nobody writes a word.
19
19
 
20
- > Stable as of v1.0 — [API guarantees](STABILITY.md) · [Security hardened](SECURITY.md) · v1.8.1
20
+ > Stable as of v1.0 — [API guarantees](STABILITY.md) · [Security hardened](SECURITY.md) · v1.9.0
21
21
 
22
22
  ---
23
23
 
@@ -29,6 +29,14 @@ RepoLens scans your repository, generates living architecture documentation, and
29
29
 
30
30
  ▶️ *Click to watch on YouTube*
31
31
 
32
+ <details>
33
+ <summary>🔍 <strong>Supported Languages</strong> (16 auto-detected)</summary>
34
+
35
+ `JavaScript` `TypeScript` `Python` `Go` `Rust` `Java` `C` `C++` `C#` `Ruby` `PHP` `Swift` `Kotlin` `Scala` `Shell` `SQL`
36
+
37
+ Plus framework detection: **Django** · **FastAPI** · **Flask** · **Gin** · **Echo** · **Fiber** · **Actix** · **Rocket** — and all major JS frameworks (React, Next.js, Vue, Angular, Express, NestJS, Svelte, etc.)
38
+ </details>
39
+
32
40
  ---
33
41
 
34
42
  ## 🚀 Quick Start (60 seconds)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chappibunny/repolens",
3
- "version": "1.8.2",
3
+ "version": "1.9.1",
4
4
  "description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -21,7 +21,7 @@ import { upsertPrComment } from "./delivery/comment.js";
21
21
  import { runInit } from "./init.js";
22
22
  import { runMigrate } from "./migrate.js";
23
23
  import { runWatch } from "./watch.js";
24
- import { info, error, warn } from "./utils/logger.js";
24
+ import { info, error, warn, fmt } from "./utils/logger.js";
25
25
  import { formatError } from "./utils/errors.js";
26
26
  import { checkForUpdates } from "./utils/update-check.js";
27
27
  import { generateDocumentSet } from "./docs/generate-doc-set.js";
@@ -520,23 +520,25 @@ async function main() {
520
520
  info("\nTo publish to Notion, Confluence, or GitHub Wiki, run: repolens publish");
521
521
 
522
522
  if (aiAutoEnabled) {
523
- info("\n🤖 AI-enhanced docs were generated using GitHub Models (free tier)");
523
+ info(`\n🤖 AI-enhanced docs were generated using ${fmt.boldGreen("GitHub Models (FREE)")}`);
524
524
  info(" To keep AI enabled permanently, run: repolens init --interactive");
525
525
  } else if (!cfg.ai?.enabled && process.env.REPOLENS_AI_ENABLED !== "true") {
526
- info("\n─────────────────────────────────────────────────────────────────");
527
- info("💡 Your docs are missing AI-enhanced sections:");
528
- info(" • Executive Summary — plain language overview for leadership");
529
- info(" Business Domains what the system does for stakeholders");
530
- info(" • Architecture Overviewdeeper narrative for architects");
531
- info(" • Data Flowshow information moves through your system");
532
- info(" • Developer Onboardinggetting started guide for new hires");
533
- info("");
534
- info(" To enable for FREE with GitHub Models:");
535
- info(" export GITHUB_TOKEN=<your-token>");
536
- info(" repolens demo");
537
- info("");
538
- info(" Or run: repolens init --interactive → select GitHub Models");
539
- info("─────────────────────────────────────────────────────────────────");
526
+ info(`\n${fmt.cyan("┌──────────────────────────────────────────────────────────────────┐")}`);
527
+ info(`${fmt.cyan("│")} ${fmt.boldYellow("✨ Unlock AI-Enhanced Documentation")} ${fmt.cyan("│")}`);
528
+ info(`${fmt.cyan("├──────────────────────────────────────────────────────────────────┤")}`);
529
+ info(`${fmt.cyan("│")} Your docs are missing these AI-powered sections: ${fmt.cyan("│")}`);
530
+ info(`${fmt.cyan("│")} ${fmt.yellow("")} Executive Summaryplain language overview for leadership ${fmt.cyan("│")}`);
531
+ info(`${fmt.cyan("│")} ${fmt.yellow("")} Business Domainswhat the system does for stakeholders ${fmt.cyan("│")}`);
532
+ info(`${fmt.cyan("│")} ${fmt.yellow("")} Architecture Overviewdeeper narrative for architects ${fmt.cyan("│")}`);
533
+ info(`${fmt.cyan("")} ${fmt.yellow("•")} Data Flows — how information moves through your system ${fmt.cyan("│")}`);
534
+ info(`${fmt.cyan("│")} ${fmt.yellow("•")} Developer Onboarding getting started guide for new hires ${fmt.cyan("│")}`);
535
+ info(`${fmt.cyan("")} ${fmt.cyan("│")}`);
536
+ info(`${fmt.cyan("│")} ${fmt.boldGreen("🆓 Enable for FREE with GitHub Models:")} ${fmt.cyan("│")}`);
537
+ info(`${fmt.cyan("")} ${fmt.green("export GITHUB_TOKEN=<your-token>")} ${fmt.cyan("│")}`);
538
+ info(`${fmt.cyan("│")} ${fmt.green("repolens demo")} ${fmt.cyan("│")}`);
539
+ info(`${fmt.cyan("")} ${fmt.cyan("│")}`);
540
+ info(`${fmt.cyan("│")} Or run: ${fmt.brightCyan("repolens init --interactive")} → select GitHub Models ${fmt.cyan("│")}`);
541
+ info(`${fmt.cyan("└──────────────────────────────────────────────────────────────────┘")}`);
540
542
  }
541
543
 
542
544
  printPerformanceSummary();
package/src/core/scan.js CHANGED
@@ -136,7 +136,70 @@ function routePathFromFile(file) {
136
136
  return file;
137
137
  }
138
138
 
139
- async function extractRepoMetadata(repoRoot) {
139
+ /**
140
+ * Detect languages from file extensions
141
+ */
142
+ function detectLanguagesFromFiles(files) {
143
+ const languages = new Set();
144
+
145
+ // Extension to language mapping
146
+ const extensionMap = {
147
+ // JavaScript/TypeScript
148
+ ".js": "JavaScript",
149
+ ".mjs": "JavaScript",
150
+ ".cjs": "JavaScript",
151
+ ".jsx": "JavaScript",
152
+ ".ts": "TypeScript",
153
+ ".tsx": "TypeScript",
154
+ // Python
155
+ ".py": "Python",
156
+ ".pyw": "Python",
157
+ ".pyi": "Python",
158
+ // Go
159
+ ".go": "Go",
160
+ // Rust
161
+ ".rs": "Rust",
162
+ // Java
163
+ ".java": "Java",
164
+ // C/C++
165
+ ".c": "C",
166
+ ".h": "C",
167
+ ".cpp": "C++",
168
+ ".hpp": "C++",
169
+ ".cc": "C++",
170
+ ".cxx": "C++",
171
+ // C#
172
+ ".cs": "C#",
173
+ // Ruby
174
+ ".rb": "Ruby",
175
+ // PHP
176
+ ".php": "PHP",
177
+ // Swift
178
+ ".swift": "Swift",
179
+ // Kotlin
180
+ ".kt": "Kotlin",
181
+ ".kts": "Kotlin",
182
+ // Scala
183
+ ".scala": "Scala",
184
+ // Shell
185
+ ".sh": "Shell",
186
+ ".bash": "Shell",
187
+ ".zsh": "Shell",
188
+ // SQL
189
+ ".sql": "SQL",
190
+ };
191
+
192
+ for (const file of files) {
193
+ const ext = path.extname(file).toLowerCase();
194
+ if (extensionMap[ext]) {
195
+ languages.add(extensionMap[ext]);
196
+ }
197
+ }
198
+
199
+ return languages;
200
+ }
201
+
202
+ async function extractRepoMetadata(repoRoot, files = []) {
140
203
  const metadata = {
141
204
  hasPackageJson: false,
142
205
  frameworks: [],
@@ -191,12 +254,9 @@ async function extractRepoMetadata(repoRoot) {
191
254
  if (allDeps["swc"] || allDeps["@swc/core"]) metadata.buildTools.push("SWC");
192
255
  if (allDeps["parcel"]) metadata.buildTools.push("Parcel");
193
256
 
194
- // Detect languages
257
+ // Detect TypeScript from dependencies (supplements file detection)
195
258
  if (allDeps["typescript"]) metadata.languages.add("TypeScript");
196
259
 
197
- // Infer JavaScript if package.json exists (any npm project uses JS/Node)
198
- metadata.languages.add("JavaScript");
199
-
200
260
  // Detect Node.js runtime indicators
201
261
  const hasNodeEngines = pkg.engines && pkg.engines.node;
202
262
  const hasBin = pkg.bin != null;
@@ -210,6 +270,82 @@ async function extractRepoMetadata(repoRoot) {
210
270
  // No package.json or invalid JSON
211
271
  }
212
272
 
273
+ // Detect Python frameworks and tools
274
+ try {
275
+ // Check for pyproject.toml (modern Python)
276
+ const pyprojectPath = path.join(repoRoot, "pyproject.toml");
277
+ const pyprojectContent = await fs.readFile(pyprojectPath, "utf8");
278
+ metadata.languages.add("Python");
279
+
280
+ // Detect Python frameworks from pyproject.toml
281
+ if (/django/i.test(pyprojectContent)) metadata.frameworks.push("Django");
282
+ if (/fastapi/i.test(pyprojectContent)) metadata.frameworks.push("FastAPI");
283
+ if (/flask/i.test(pyprojectContent)) metadata.frameworks.push("Flask");
284
+ if (/pytest/i.test(pyprojectContent)) metadata.testFrameworks.push("pytest");
285
+ if (/poetry/i.test(pyprojectContent)) metadata.buildTools.push("Poetry");
286
+ } catch {
287
+ // No pyproject.toml
288
+ }
289
+
290
+ try {
291
+ // Check for requirements.txt
292
+ const reqPath = path.join(repoRoot, "requirements.txt");
293
+ const reqContent = await fs.readFile(reqPath, "utf8");
294
+ metadata.languages.add("Python");
295
+
296
+ if (/django/i.test(reqContent)) metadata.frameworks.push("Django");
297
+ if (/fastapi/i.test(reqContent)) metadata.frameworks.push("FastAPI");
298
+ if (/flask/i.test(reqContent)) metadata.frameworks.push("Flask");
299
+ if (/pytest/i.test(reqContent)) metadata.testFrameworks.push("pytest");
300
+ } catch {
301
+ // No requirements.txt
302
+ }
303
+
304
+ try {
305
+ // Check for setup.py (legacy Python)
306
+ await fs.access(path.join(repoRoot, "setup.py"));
307
+ metadata.languages.add("Python");
308
+ } catch {
309
+ // No setup.py
310
+ }
311
+
312
+ // Detect Go modules
313
+ try {
314
+ const goModPath = path.join(repoRoot, "go.mod");
315
+ const goModContent = await fs.readFile(goModPath, "utf8");
316
+ metadata.languages.add("Go");
317
+
318
+ if (/gin-gonic/i.test(goModContent)) metadata.frameworks.push("Gin");
319
+ if (/echo/i.test(goModContent)) metadata.frameworks.push("Echo");
320
+ if (/fiber/i.test(goModContent)) metadata.frameworks.push("Fiber");
321
+ } catch {
322
+ // No go.mod
323
+ }
324
+
325
+ // Detect Rust via Cargo.toml
326
+ try {
327
+ const cargoPath = path.join(repoRoot, "Cargo.toml");
328
+ const cargoContent = await fs.readFile(cargoPath, "utf8");
329
+ metadata.languages.add("Rust");
330
+
331
+ if (/actix/i.test(cargoContent)) metadata.frameworks.push("Actix");
332
+ if (/rocket/i.test(cargoContent)) metadata.frameworks.push("Rocket");
333
+ if (/tokio/i.test(cargoContent)) metadata.frameworks.push("Tokio");
334
+ } catch {
335
+ // No Cargo.toml
336
+ }
337
+
338
+ // Merge languages detected from file extensions
339
+ const fileLanguages = detectLanguagesFromFiles(files);
340
+ for (const lang of fileLanguages) {
341
+ metadata.languages.add(lang);
342
+ }
343
+
344
+ // De-duplicate frameworks (in case detected from multiple sources)
345
+ metadata.frameworks = [...new Set(metadata.frameworks)];
346
+ metadata.testFrameworks = [...new Set(metadata.testFrameworks)];
347
+ metadata.buildTools = [...new Set(metadata.buildTools)];
348
+
213
349
  return metadata;
214
350
  }
215
351
 
@@ -435,8 +571,8 @@ export async function scanRepo(cfg) {
435
571
  }
436
572
  }
437
573
 
438
- // Extract repository metadata
439
- const metadata = await extractRepoMetadata(repoRoot);
574
+ // Extract repository metadata (pass files for language detection)
575
+ const metadata = await extractRepoMetadata(repoRoot, files);
440
576
 
441
577
  // Detect external API integrations
442
578
  const externalApis = await detectExternalApis(files, repoRoot);
@@ -3,6 +3,53 @@ import { sanitizeSecrets } from "./secrets.js";
3
3
  const isVerbose = process.argv.includes("--verbose");
4
4
  const isTest = process.env.NODE_ENV === "test";
5
5
 
6
+ // Terminal color support detection
7
+ const supportsColor = !process.env.NO_COLOR &&
8
+ (process.env.FORCE_COLOR ||
9
+ (process.stdout.isTTY && process.env.TERM !== "dumb"));
10
+
11
+ // ANSI color codes
12
+ const colors = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ green: "\x1b[32m",
17
+ yellow: "\x1b[33m",
18
+ cyan: "\x1b[36m",
19
+ magenta: "\x1b[35m",
20
+ brightGreen: "\x1b[92m",
21
+ brightCyan: "\x1b[96m",
22
+ brightYellow: "\x1b[93m",
23
+ };
24
+
25
+ /**
26
+ * Apply color formatting to text (respects NO_COLOR)
27
+ */
28
+ export function colorize(text, ...styles) {
29
+ if (!supportsColor) return text;
30
+ const codes = styles.map(s => colors[s] || "").join("");
31
+ return `${codes}${text}${colors.reset}`;
32
+ }
33
+
34
+ /**
35
+ * Shorthand color helpers
36
+ */
37
+ export const fmt = {
38
+ bold: (text) => colorize(text, "bold"),
39
+ green: (text) => colorize(text, "green"),
40
+ brightGreen: (text) => colorize(text, "brightGreen"),
41
+ cyan: (text) => colorize(text, "cyan"),
42
+ brightCyan: (text) => colorize(text, "brightCyan"),
43
+ yellow: (text) => colorize(text, "yellow"),
44
+ brightYellow: (text) => colorize(text, "brightYellow"),
45
+ magenta: (text) => colorize(text, "magenta"),
46
+ dim: (text) => colorize(text, "dim"),
47
+ // Combined styles
48
+ boldGreen: (text) => colorize(text, "bold", "brightGreen"),
49
+ boldCyan: (text) => colorize(text, "bold", "brightCyan"),
50
+ boldYellow: (text) => colorize(text, "bold", "brightYellow"),
51
+ };
52
+
6
53
  /**
7
54
  * Sanitize arguments for logging
8
55
  */