@chappibunny/repolens 1.8.1 β†’ 1.9.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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to RepoLens will be documented in this file.
4
4
 
5
+ ## 1.8.2
6
+
7
+ ### πŸ› Bug Fixes
8
+
9
+ - **Uninstall crash fixed**: `repolens uninstall` crashed with `Cannot read properties of undefined (reading 'trim')` when run via `npx`. The readline prompt used the callback-based `node:readline` API incorrectly with `await`. Fixed by wrapping in a proper Promise with null guard.
10
+
11
+ ### ✨ AI Auto-Detection
12
+
13
+ - **GITHUB_TOKEN auto-enables AI**: `demo`, `publish`, and `init` commands now detect `GITHUB_TOKEN` in the environment and automatically enable AI-enhanced docs via GitHub Models (free). No manual configuration needed.
14
+ - **Workflow template enables AI by default**: The generated `.github/workflows/repolens.yml` now has `REPOLENS_AI_ENABLED`, `REPOLENS_AI_PROVIDER`, and `GITHUB_TOKEN` enabled (not commented out) since GitHub Models is free.
15
+ - **Init auto-configures AI**: Non-interactive `repolens init` writes `ai.enabled: true` and `ai.provider: github` to `.repolens.yml` when `GITHUB_TOKEN` is detected.
16
+ - **Better AI messaging**: Demo and publish commands show prominent status about AI mode (enabled/deterministic) with specific guidance on what AI adds.
17
+ - **Sharper deterministic upsell**: All 6 fallback document types now explain exactly what AI adds (e.g., "adds plain-language explanations, strategic recommendations, and risk assessments") and mention `GITHUB_TOKEN` auto-detection.
18
+
19
+ ### πŸ“– Documentation
20
+
21
+ - **Scoped package name everywhere**: All user-facing docs now use `npx @chappibunny/repolens` instead of bare `repolens`. Added warning about the squatted `repolens@0.0.1` placeholder package on npm.
22
+ - Updated: README.md (commands table + warning), TROUBLESHOOTING.md, MIGRATION.md (6 instances), KNOWN_ISSUES.md (2 instances), bug report template.
23
+
5
24
  ## 1.8.1
6
25
 
7
26
  ### πŸ“– Documentation Refresh
package/README.md CHANGED
@@ -148,16 +148,19 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
148
148
 
149
149
  | Command | Description |
150
150
  |---|---|
151
- | `repolens init` | Scaffold config + GitHub Actions workflow |
152
- | `repolens init --interactive` | Step-by-step configuration wizard |
153
- | `repolens publish` | Scan, generate, and publish documentation |
154
- | `repolens demo` | Quick local preview β€” no API keys needed |
155
- | `repolens doctor` | Validate your setup |
156
- | `repolens watch` | Auto-regenerate docs on file changes |
157
- | `repolens migrate` | Upgrade from v0.3.0 workflows ([details](MIGRATION.md)) |
158
- | `repolens uninstall` | Remove all RepoLens files (config, docs, workflow) |
159
- | `repolens uninstall --force` | Remove without confirmation prompt |
160
- | `repolens feedback` | Send feedback to the team |
151
+ | `npx @chappibunny/repolens init` | Scaffold config + GitHub Actions workflow |
152
+ | `npx @chappibunny/repolens init --interactive` | Step-by-step configuration wizard |
153
+ | `npx @chappibunny/repolens publish` | Scan, generate, and publish documentation |
154
+ | `npx @chappibunny/repolens demo` | Quick local preview β€” no API keys needed |
155
+ | `npx @chappibunny/repolens doctor` | Validate your setup |
156
+ | `npx @chappibunny/repolens watch` | Auto-regenerate docs on file changes |
157
+ | `npx @chappibunny/repolens migrate` | Upgrade from v0.3.0 workflows ([details](MIGRATION.md)) |
158
+ | `npx @chappibunny/repolens uninstall` | Remove all RepoLens files (config, docs, workflow) |
159
+ | `npx @chappibunny/repolens uninstall --force` | Remove without confirmation prompt |
160
+ | `npx @chappibunny/repolens feedback` | Send feedback to the team |
161
+
162
+ > **Note:** If you installed globally with `npm install -g @chappibunny/repolens`, you can use the shorter `repolens <command>` form.
163
+ > **Warning:** Do not run bare `npx repolens` β€” there is an unrelated `repolens@0.0.1` placeholder package on npm. Always use the scoped name `@chappibunny/repolens`.
161
164
 
162
165
  ---
163
166
 
@@ -255,7 +258,7 @@ When you open a pull request, RepoLens posts:
255
258
  - **Report issues**: Share bugs, edge cases, or UX friction
256
259
  - **Request features**: Tell us what's missing
257
260
  - **Build plugins**: Extend RepoLens with custom renderers and publishers
258
- - **Share feedback**: `repolens feedback`
261
+ - **Share feedback**: `npx @chappibunny/repolens feedback`
259
262
 
260
263
  ---
261
264
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chappibunny/repolens",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -245,7 +245,7 @@ The codebase follows ${context.patterns.length > 0 ? context.patterns.join(", ")
245
245
 
246
246
  ---
247
247
 
248
- *This summary is generated deterministically from repository structure. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language insights tailored to non-technical readers.*`;
248
+ *This summary was generated from repository structure analysis. Set \`GITHUB_TOKEN\` to auto-enable AI-enhanced summaries via GitHub Models (free) \u2014 adds plain-language explanations for non-technical readers, strategic recommendations, and risk assessments.*`;
249
249
 
250
250
  return output;
251
251
  }
@@ -353,7 +353,7 @@ ${context.domains.slice(0, 5).map((d, i) => `| ${i + 1} | ${d.name} | ${d.fileCo
353
353
 
354
354
  output += `---
355
355
 
356
- *This overview is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for richer contextual explanations.*`;
356
+ *This overview was generated from repository structure analysis. Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 adds narrative context, architectural rationale, and technology trade-off analysis.*`;
357
357
 
358
358
  return output;
359
359
  }
@@ -412,7 +412,7 @@ function getFallbackBusinessDomains(context, enrichment = {}) {
412
412
  output += "\n";
413
413
  }
414
414
 
415
- output += `---\n\n*Domain mapping is based on directory naming conventions. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language descriptions aimed at non-technical stakeholders.*`;
415
+ output += `---\n\n*Domain mapping is based on directory naming conventions. Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 adds business-language descriptions, stakeholder impact analysis, and cross-domain relationship narratives.*`;
416
416
 
417
417
  return output;
418
418
  }
@@ -533,7 +533,7 @@ The repository comprises **${context.project.filesScanned} files** organized int
533
533
 
534
534
  output += `---
535
535
 
536
- *This architecture overview is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for deeper architectural narratives.*`;
536
+ *This architecture overview was generated from repository structure analysis. Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 adds architectural narratives, design rationale, scalability analysis, and refactoring recommendations.*`;
537
537
 
538
538
  return output;
539
539
  }
@@ -559,7 +559,7 @@ function getFallbackDataFlows(flows, context, enrichment = {}) {
559
559
 
560
560
  if (allFlows.length === 0) {
561
561
  output += `No data flows were detected. This typically means the system uses straightforward request–response patterns without distinct multi-step pipelines.\n\n`;
562
- output += `---\n\n*Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for heuristic-based flow descriptions.*`;
562
+ output += `---\n\n*Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 AI can infer data flows from import patterns even when heuristics find none.*`;
563
563
  return output;
564
564
  }
565
565
 
@@ -603,7 +603,7 @@ function getFallbackDataFlows(flows, context, enrichment = {}) {
603
603
  }
604
604
  }
605
605
 
606
- output += `---\n\n*Flow detection is based on naming conventions and import patterns. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language flow narratives.*`;
606
+ output += `---\n\n*Flow detection is based on naming conventions and import patterns. Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 adds end-to-end flow narratives, failure mode analysis, and data transformation descriptions.*`;
607
607
 
608
608
  return output;
609
609
  }
@@ -715,7 +715,7 @@ ${context.topModules.slice(0, 10).map(m => `| \`${m.key}\` | ${m.fileCount} | ${
715
715
 
716
716
  output += `\n\n---
717
717
 
718
- *This onboarding guide is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for a narrative-style guide with tips and common pitfalls.*`;
718
+ *This onboarding guide was generated from repository structure analysis. Set \`GITHUB_TOKEN\` to auto-enable AI via GitHub Models (free) \u2014 adds narrative walkthroughs, common pitfalls, debugging tips, and contribution guidelines.*`;
719
719
 
720
720
  return output;
721
721
  }
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";
@@ -338,6 +338,14 @@ async function main() {
338
338
  process.exit(EXIT_VALIDATION);
339
339
  }
340
340
 
341
+ // Auto-detect GITHUB_TOKEN and enable AI with GitHub Models (free)
342
+ if (!cfg.ai?.enabled && process.env.REPOLENS_AI_ENABLED !== "true" && process.env.GITHUB_TOKEN) {
343
+ info("✨ GITHUB_TOKEN detected β€” enabling AI-enhanced docs via GitHub Models (free)");
344
+ cfg.ai = { enabled: true, provider: "github" };
345
+ process.env.REPOLENS_AI_ENABLED = "true";
346
+ process.env.REPOLENS_AI_PROVIDER = "github";
347
+ }
348
+
341
349
  // Load plugins
342
350
  let pluginManager;
343
351
  try {
@@ -472,6 +480,22 @@ async function main() {
472
480
  };
473
481
  }
474
482
 
483
+ // Auto-detect GITHUB_TOKEN and enable AI with GitHub Models (free)
484
+ const aiAlreadyConfigured = cfg.ai?.enabled || process.env.REPOLENS_AI_ENABLED === "true";
485
+ let aiAutoEnabled = false;
486
+ if (!aiAlreadyConfigured && process.env.GITHUB_TOKEN) {
487
+ info("\n✨ GITHUB_TOKEN detected β€” enabling AI-enhanced docs via GitHub Models (free)");
488
+ cfg.ai = { enabled: true, provider: "github" };
489
+ process.env.REPOLENS_AI_ENABLED = "true";
490
+ process.env.REPOLENS_AI_PROVIDER = "github";
491
+ aiAutoEnabled = true;
492
+ } else if (aiAlreadyConfigured) {
493
+ info("\nπŸ€– AI-enhanced documentation enabled");
494
+ } else {
495
+ info("\nπŸ“„ Running in deterministic mode (no GITHUB_TOKEN detected)");
496
+ info(" Set GITHUB_TOKEN to auto-enable free AI-enhanced docs via GitHub Models");
497
+ }
498
+
475
499
  try {
476
500
  info("Scanning repository...");
477
501
  const scanTimer = startTimer("scan");
@@ -495,10 +519,26 @@ async function main() {
495
519
  info("Browse your docs: open the .repolens/ directory");
496
520
  info("\nTo publish to Notion, Confluence, or GitHub Wiki, run: repolens publish");
497
521
 
498
- // Upsell AI enhancement when not already enabled
499
- if (!cfg.ai?.enabled && process.env.REPOLENS_AI_ENABLED !== "true") {
500
- info("\nπŸ’‘ Want richer, AI-enhanced docs? Run: repolens init --interactive");
501
- info(" Select GitHub Models (free) β€” uses your existing GITHUB_TOKEN, no extra keys needed.");
522
+ if (aiAutoEnabled) {
523
+ info(`\nπŸ€– AI-enhanced docs were generated using ${fmt.boldGreen("GitHub Models (FREE)")}`);
524
+ info(" To keep AI enabled permanently, run: repolens init --interactive");
525
+ } else if (!cfg.ai?.enabled && process.env.REPOLENS_AI_ENABLED !== "true") {
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 Summary β€” plain language overview for leadership ${fmt.cyan("β”‚")}`);
531
+ info(`${fmt.cyan("β”‚")} ${fmt.yellow("β€’")} Business Domains β€” what the system does for stakeholders ${fmt.cyan("β”‚")}`);
532
+ info(`${fmt.cyan("β”‚")} ${fmt.yellow("β€’")} Architecture Overview β€” deeper 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("β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜")}`);
502
542
  }
503
543
 
504
544
  printPerformanceSummary();
@@ -575,10 +615,12 @@ async function main() {
575
615
  info("\nCI/test environment detected β€” skipping confirmation (use --force to suppress this message).");
576
616
  } else {
577
617
  const rl = createInterface({ input: process.stdin, output: process.stdout });
578
- const answer = await rl.question(`\nRemove ${found.length} item${found.length === 1 ? "" : "s"}? This cannot be undone. (y/N): `);
618
+ const answer = await new Promise((resolve) => {
619
+ rl.question(`\nRemove ${found.length} item${found.length === 1 ? "" : "s"}? This cannot be undone. (y/N): `, resolve);
620
+ });
579
621
  rl.close();
580
622
 
581
- if (answer.trim().toLowerCase() !== "y") {
623
+ if (!answer || answer.trim().toLowerCase() !== "y") {
582
624
  info("Uninstall cancelled.");
583
625
  await closeTelemetry();
584
626
  return;
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);
package/src/init.js CHANGED
@@ -89,10 +89,10 @@ jobs:
89
89
  CONFLUENCE_API_TOKEN: \${{ secrets.CONFLUENCE_API_TOKEN }}
90
90
  CONFLUENCE_SPACE_KEY: \${{ secrets.CONFLUENCE_SPACE_KEY }}
91
91
  CONFLUENCE_PARENT_PAGE_ID: \${{ secrets.CONFLUENCE_PARENT_PAGE_ID }}
92
- # Uncomment to enable free AI-enhanced docs via GitHub Models:
93
- # REPOLENS_AI_ENABLED: true
94
- # REPOLENS_AI_PROVIDER: github
95
- # GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
92
+ # AI-enhanced docs via GitHub Models (free)
93
+ REPOLENS_AI_ENABLED: true
94
+ REPOLENS_AI_PROVIDER: github
95
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
96
96
  run: npx @chappibunny/repolens@latest publish
97
97
  `;
98
98
 
@@ -324,9 +324,10 @@ function buildIgnorePatterns() {
324
324
  ];
325
325
  }
326
326
 
327
- function buildRepoLensConfig(projectName, detectedRoots) {
327
+ function buildRepoLensConfig(projectName, detectedRoots, options = {}) {
328
328
  const includePatterns = buildIncludePatterns(detectedRoots);
329
329
  const ignorePatterns = buildIgnorePatterns();
330
+ const enableAI = options.enableAI || false;
330
331
 
331
332
  const lines = [
332
333
  `configVersion: 1`,
@@ -358,10 +359,35 @@ function buildRepoLensConfig(projectName, detectedRoots) {
358
359
  `# owner: "your-username"`,
359
360
  `# repo: "your-repo-name"`,
360
361
  ``,
361
- `scan:`,
362
- ` include:`
363
362
  ];
364
363
 
364
+ // AI configuration
365
+ if (enableAI) {
366
+ lines.push(`# AI-enhanced documentation (auto-detected GITHUB_TOKEN)`);
367
+ lines.push(`ai:`);
368
+ lines.push(` enabled: true`);
369
+ lines.push(` provider: github`);
370
+ lines.push(``);
371
+ lines.push(`features:`);
372
+ lines.push(` executive_summary: true`);
373
+ lines.push(` business_domains: true`);
374
+ lines.push(` architecture_overview: true`);
375
+ lines.push(` data_flows: true`);
376
+ lines.push(` developer_onboarding: true`);
377
+ lines.push(` change_impact: true`);
378
+ lines.push(``);
379
+ } else {
380
+ lines.push(`# AI-enhanced documentation (free via GitHub Models)`);
381
+ lines.push(`# Uncomment to enable β€” or set GITHUB_TOKEN and it auto-activates:`);
382
+ lines.push(`# ai:`);
383
+ lines.push(`# enabled: true`);
384
+ lines.push(`# provider: github`);
385
+ lines.push(``);
386
+ }
387
+
388
+ lines.push(`scan:`);
389
+ lines.push(` include:`);
390
+
365
391
  if (includePatterns.length) {
366
392
  for (const pattern of includePatterns) {
367
393
  lines.push(` - "${pattern}"`);
@@ -725,9 +751,12 @@ export async function runInit(targetDir = process.cwd(), options = {}) {
725
751
 
726
752
  const projectName = wizardAnswers?.projectName || detectProjectName(repoRoot);
727
753
  const detectedRoots = wizardAnswers ? [] : await detectRepoStructure(repoRoot);
754
+
755
+ // Auto-detect GITHUB_TOKEN for AI enablement
756
+ const hasGitHubToken = !!process.env.GITHUB_TOKEN;
728
757
  const configContent = wizardAnswers
729
758
  ? buildWizardConfig(wizardAnswers)
730
- : buildRepoLensConfig(projectName, detectedRoots);
759
+ : buildRepoLensConfig(projectName, detectedRoots, { enableAI: hasGitHubToken });
731
760
 
732
761
  info(`Detected project name: ${projectName}`);
733
762
 
@@ -786,8 +815,11 @@ NOTION_VERSION=2022-06-28
786
815
  }
787
816
 
788
817
  info("\n✨ RepoLens initialization complete!\n");
789
-
790
- if (notionCredentials) {
818
+ if (hasGitHubToken && !wizardAnswers) {
819
+ info("πŸ€– Detected GITHUB_TOKEN β€” AI-enhanced docs enabled via GitHub Models (free)");
820
+ info(" Your workflow and config are pre-configured. No extra setup needed.\n");
821
+ }
822
+ if (notionCredentials) {
791
823
  info("πŸŽ‰ Notion publishing is ready!");
792
824
  info(" Your credentials are stored in .env (gitignored)\n");
793
825
  info("Next steps:");
@@ -812,16 +844,18 @@ NOTION_VERSION=2022-06-28
812
844
  info(" 2. To enable Notion publishing:");
813
845
  info(" - Copy .env.example to .env and add your credentials, OR");
814
846
  info(" - Add GitHub secrets: NOTION_TOKEN, NOTION_PARENT_PAGE_ID");
815
- info(" 3. (Optional) Enable AI features:");
816
- info(" ── FREE: GitHub Models (recommended for GitHub Actions) ──");
817
- info(" REPOLENS_AI_ENABLED=true");
818
- info(" REPOLENS_AI_PROVIDER=github");
819
- info(" (Uses your GITHUB_TOKEN automatically β€” no API key signup needed)");
820
- info(" ── Or: OpenAI / Anthropic / Google ──");
821
- info(" REPOLENS_AI_ENABLED=true");
822
- info(" REPOLENS_AI_API_KEY=sk-...");
823
- info(" See: https://github.com/CHAPIBUNNY/repolens/blob/main/AI.md");
824
- info(" 4. Run 'npx @chappibunny/repolens publish' to test locally");
825
- info(" 5. Commit the generated files");
847
+ if (!hasGitHubToken) {
848
+ info(" 3. (Optional) Enable AI features:");
849
+ info(" \u2500\u2500 FREE: GitHub Models (recommended for GitHub Actions) \u2500\u2500");
850
+ info(" REPOLENS_AI_ENABLED=true");
851
+ info(" REPOLENS_AI_PROVIDER=github");
852
+ info(" (Uses your GITHUB_TOKEN automatically \u2014 no API key signup needed)");
853
+ info(" \u2500\u2500 Or: OpenAI / Anthropic / Google \u2500\u2500");
854
+ info(" REPOLENS_AI_ENABLED=true");
855
+ info(" REPOLENS_AI_API_KEY=sk-...");
856
+ info(" See: https://github.com/CHAPIBUNNY/repolens/blob/main/AI.md");
857
+ }
858
+ info(` ${hasGitHubToken ? "3" : "4"}. Run 'npx @chappibunny/repolens publish' to test locally`);
859
+ info(` ${hasGitHubToken ? "4" : "5"}. Commit the generated files`);
826
860
  }
827
861
  }
@@ -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
  */