This file is a merged representation of the entire codebase, combined into a single document by Repomix.

<file_summary>
This section contains a summary of this file.

<purpose>
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
</purpose>

<file_format>
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files (if enabled)
5. Multiple file entries, each consisting of:
  - File path as an attribute
  - Full contents of the file
</file_format>

<usage_guidelines>
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.
</usage_guidelines>

<notes>
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Files are sorted by Git change count (files with more changes are at the bottom)
</notes>

</file_summary>

<directory_structure>
.github/
  workflows/
    nodejs.yml
.local/
  state/
    replit/
      agent/
        .latest.json
.upm/
  store.json
attached_assets/
  Pasted--directory-test-git-main-hexdump-C-scopeTest-js-00000000-2f-2f-20-54-65-73-74-20-66-69-6c-65--1753168145351_1753168145352.txt
  Pasted--workspace-npm-test-loqatevars-1-0-4-test-node-experimental-vm-modules-node-modules-jest-bi-1753165878508_1753165878509.txt
config/
  localVars.js
  summary.md
directory_test/
  AGENTS.md
  badConst.js
  badEnvOnly.js
  badScope.js
  functionCaller.js
  functionDefault.js
  ifStatement.js
  importModule.js
  letVariable.js
  localVars.js
  mockEnv.js
  requireImport.js
  scopeTest.js
  summary.md
  useLocalVars.js
ignoreDirTest/
  public/
    file.js
  index.js
lib/
  errors.js
  logger.js
  summary.md
  utils.js
test_dir/
  indirect-env.js
test_samples/
  app.js
  config.js
  constants.js
  database.js
  localVars.js
  server.js
  summary.md
tests/
  __mocks__/
    globby.js
  asyncPool.cleanup.test.js
  asyncPool.test.js
  cli.default.test.js
  cli.helpCommand.test.js
  cli.invalidCommand.test.js
  cli.multipleOptions.test.js
  cli.run.test.js
  cli.test.js
  errors.test.js
  findMatchingFiles.invalid.test.js
  findMatchingFiles.single.test.js
  findMatchingFilesDetailed.test.js
  function-scope.test.js
  glob-patterns.test.js
  index.test.js
  indirect-env.test.js
  integration.test.js
  localVars.test.js
  searchFiles.defaultStreamFallback.test.js
  searchFiles.invalid-extension.test.js
  searchFiles.invalidExtensionType.test.js
  searchFiles.test.js
  utils.test.js
  validateDirectory.invalid.test.js
  validateDirectory.success.test.js
  validateDirectory.test.js
.gitignore
.replit
AGENTS.md
ARCHITECTURE.md
cli.js
DB_SCHEMA.md
DEPENDENCY_GRAPH.md
index.js
MODULES.md
package.json
README.md
replit.md
WORKFLOWS.md
</directory_structure>

<files>
This section contains the contents of the repository's files.

<file path=".local/state/replit/agent/.latest.json">
{"latest": "main"}
</file>

<file path=".upm/store.json">
{"version":2,"languages":{"nodejs-npm":{"specfileHash":"0a520387f417e4526881cc107797bbf2","lockfileHash":"b4695602750c8a916ef78523524eb39d"}}}
</file>

<file path="config/summary.md">
# Config Directory

This directory contains configuration files for the `loqatevars` application.

*   **[`localVars.js`](localVars.js)**: This is the central place for defining and exporting configuration variables. It includes a list of directories to be ignored during scans (`ignoreDirs`) and handles loading environment variables using `dotenv`. Centralizing configuration here makes the application easier to manage and maintain.
</file>

<file path="directory_test/functionCaller.js">
// File 7: imports function and invokes it with different string literal
const { greetUser } = require('./functionDefault');

console.log(greetUser("custom user"));
</file>

<file path="directory_test/functionDefault.js">
// File 6: function declaration with string literal as default parameter
function greetUser(name = "default user") {
  return `Hello, ${name}!`;
}

module.exports = { greetUser };
</file>

<file path="directory_test/ifStatement.js">
// File 8: if statement with string literal in conditional
if (production === "development") {
  console.log("This will never execute");
} else {
  console.log("Running in production mode");
}
</file>

<file path="directory_test/importModule.js">
// File 10: imports node module using import
import fs from 'fs';
import path from 'path';

console.log('File system module loaded with import');
</file>

<file path="directory_test/letVariable.js">
// File 4: let declaration for "let variable"
let letVariable = "let variable";

console.log(letVariable);
</file>

<file path="directory_test/localVars.js">
// File 2: mock localVars.js with const variable and imported MOCK_ENV
const { MOCK_ENV } = require('./mockEnv');
const testValue = "test value";
const mockEnv = MOCK_ENV;

module.exports = {
  testValue,
  mockEnv
};
</file>

<file path="directory_test/mockEnv.js">
// File 1: mockEnv file with let declaration for process.env.MOCK_ENV
const localVars = require('../config/localVars');
let MOCK_ENV = localVars.MOCK_ENV;
MOCK_ENV = "test env";

module.exports = { MOCK_ENV };
</file>

<file path="directory_test/requireImport.js">
// File 9: imports node module using require
const fs = require('fs');
const path = require('path');

console.log('File system module loaded');
</file>

<file path="directory_test/scopeTest.js">
// Test file for top-level scope detection
const localVars = require('../config/localVars');

function myFunction() {
  const insideFunction = "should NOT be flagged";
  if (true) {
    const insideBlock = "should NOT be flagged";
  }
}

class MyClass {
  constructor() {
    const insideClass = "should NOT be flagged";
  }
}

if (true) {
  const insideIfBlock = "should NOT be flagged";
}
</file>

<file path="directory_test/useLocalVars.js">
// File 11: imports exports of mock localVars and uses them
const { testValue, mockEnv } = require('./localVars');

console.log('Test value:', testValue);
console.log('Mock environment:', mockEnv);

// Use the imported values in some logic
if (mockEnv === "test env") {
  console.log('Environment is correctly set to test');
}
</file>

<file path="ignoreDirTest/public/file.js">
// ignored file
</file>

<file path="ignoreDirTest/index.js">
// root file
</file>

<file path="lib/summary.md">
# Lib Directory

This directory contains the core logic for the `loqatevars` application.

*   **[`utils.js`](utils.js)**: This file contains all the main functions for scanning and analyzing the codebase. It is responsible for finding files, parsing them using `acorn` to create an Abstract Syntax Tree (AST), and then analyzing the AST to find `const` declarations and `process.env` usage.
*   **[`errors.js`](errors.js)**: This file defines a custom error class for the application.
</file>

<file path="test_samples/app.js">
const express = require('express');
const app = express();
const { Router } = require('express');
</file>

<file path="test_samples/config.js">
const localVars = require('../config/localVars');

var config = {
  apiKey: localVars.API_KEY,
  env: localVars.NODE_ENV
};
</file>

<file path="test_samples/constants.js">
const localVars = require('../config/localVars');
</file>

<file path="test_samples/database.js">
const localVars = require('../config/localVars');
const database = require('./db');
</file>

<file path="test_samples/localVars.js">
const localConfig = {
  secret: process.env.SECRET_KEY
};
</file>

<file path="test_samples/server.js">
const localVars = require('../config/localVars');

const config = {
  port: localVars.PORT,
  database: localVars.DATABASE_URL
};

const server = require('express')();
console.log('Server starting...');
</file>

<file path="test_samples/summary.md">
# Test Samples Directory

This directory contains a set of sample JavaScript files that are likely used for more comprehensive testing of the `loqatevars` tool. The file names suggest they represent different components of a sample application:

*   `app.js`
*   `config.js`
*   `constants.js`
*   `database.js`
*   `localVars.js`
*   `server.js`

These files probably provide more realistic and complex test cases than the ones in the `directory_test` folder, allowing for testing the tool against a simulated project structure.
</file>

<file path="tests/errors.test.js">
const { AppError } = require('../lib/errors');
const path = require('path');

describe('AppError', () => {
  test('stores message and code', () => {
    const err = new AppError('fail', 'FAIL_CODE');
    expect(err.message).toBe('fail');
    expect(err.code).toBe('FAIL_CODE');
  });

  test('captures stack information', () => {
    const err = new AppError('fail', 'FAIL');
    expect(err.stack).toBeDefined();
    expect(err.stack.startsWith('AppError:')).toBe(true);
    expect(err.stack).toContain('AppError');
    expect(err.stack).toContain(path.basename(__filename));
  });
});
</file>

<file path="tests/findMatchingFiles.invalid.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { findMatchingFiles } = require('../lib/utils');

describe('findMatchingFiles invalid directory', () => {
  test('rejects for non-existent directory', async () => {
    await expect(findMatchingFiles('./nonexistent')).rejects.toMatchObject({ code: 'DIRECTORY_NOT_FOUND' });
  });
});
</file>

<file path="tests/index.test.js">
// Mock globby to avoid ESM issues in utils
jest.mock('globby');
const lib = require('../index');

describe('index exports', () => {
  test('re-exports findMatchingFiles', () => {
    expect(typeof lib.findMatchingFiles).toBe('function');
  });

  test('re-exports findMatchingFilesDetailed', () => {
    expect(typeof lib.findMatchingFilesDetailed).toBe('function');
  });
});
</file>

<file path="tests/validateDirectory.invalid.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { validateDirectory } = require('../lib/utils');

describe('validateDirectory invalid parameter', () => {
  test('rejects for non-string directory argument', async () => {
    await expect(validateDirectory(123)).rejects.toHaveProperty('code', 'INVALID_DIRECTORY');
  });
});
</file>

<file path="tests/validateDirectory.success.test.js">
// No fs-extra mocking, use actual fs-extra
const { validateDirectory } = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');

describe('validateDirectory success path', () => {
  let tempDir;

  beforeAll(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'valid-dir-')); // create temp directory for test
  });

  afterAll(() => {
    fs.removeSync(tempDir); // cleanup temporary directory after tests
  });

  test('resolves for existing directory', async () => {
    await expect(validateDirectory(tempDir)).resolves.toBeUndefined(); // should resolve with undefined for valid directory
  });
});
</file>

<file path=".gitignore">
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage
.grunt

# Bower dependency directory
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons
build/Release

# Dependency directories
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache
.cache
.parcel-cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# MacOS
.DS_Store

# Windows
Thumbs.db
ehthumbs.db

# Editor directories and files
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
</file>

<file path=".replit">
modules = ["nodejs-20"]

[nix]
channel = "stable-24_05"

[workflows]
runButton = "Project"

[[workflows.workflow]]
name = "Project"
mode = "parallel"
author = "agent"

[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Test Module"

[[workflows.workflow]]
name = "Test Module"
author = "agent"

[[workflows.workflow.tasks]]
task = "shell.exec"
args = "npm test"

[deployment]
run = ["sh", "-c", "npm test"]
</file>

<file path="AGENTS.md">
# AGENTS.md

## VISION

This project, `loqatevars`, enforces an architectural pattern where environment variables and constants are centralized. The core business logic is to prevent the proliferation of scattered `process.env` calls and `const` declarations in large, especially AI-generated, codebases. This tool was created to automate the detection of non-compliance with this pattern, as manual auditing is error-prone and time-consuming. The intended end-state for a codebase analyzed by this tool is to have a single, clearly-defined configuration file (e.g., `config.js` or `localVars.js`) from which all other modules import their configuration.

## FUNCTIONALITY

No undocumented items.

## SCOPE

**In-Scope:**
*   Identifying `.js` files containing `const` or `process.env`.
*   Providing command-line and programmatic interfaces for file scanning.
*   Ignoring specified files and directories to narrow the search.

**Out-of-Scope:**
*   Automatically refactoring or modifying the identified files. The tool is for detection only.
*   Analyzing file types other than JavaScript by default (though configurable).
*   Supporting languages other than JavaScript.

## CONSTRAINTS

*   All tests reside in the `tests/` directory and are executed via Jest.
*   The `directory_test/` and `test_samples/` directories contain files specifically designed for testing various scenarios. Do not add, remove, or modify files in these directories unless you are intentionally changing the test cases.

## POLICY

No undocumented items.
</file>

<file path="ARCHITECTURE.md">
# Architecture

This project, `loqatevars`, is a Node.js command-line tool designed to analyze JavaScript codebases. Its primary function is to identify files that contain `const` variable declarations and `process.env` usage.

The architecture is composed of three main layers:

1.  **Command-Line Interface (CLI)**: The entry point for user interaction is [`cli.js`](cli.js). It is responsible for parsing command-line arguments, handling user commands (`scan`, `detailed`, `help`), and displaying the final output to the console.

2.  **Core Logic Library**: The core functionality resides in the `lib/` directory, specifically within [`lib/utils.js`](lib/utils.js). This module contains the functions responsible for:
    *   Traversing the file system to find relevant files.
    *   Reading file contents.
    *   Parsing JavaScript code into an Abstract Syntax Tree (AST) using the `acorn` library.
    *   Analyzing the AST to detect `const` declarations (while intelligently ignoring those used for `require` statements) and `process.env` access.

3.  **Module Entry Point**: The [`index.js`](index.js) file serves as the main entry point for the `npm` package. It exposes the core scanning functions (`findMatchingFiles` and `findMatchingFilesDetailed`) for programmatic use in other projects.

## Data Flow

The typical data flow is as follows:
1.  A user executes a command via the `loqatevars` CLI.
2.  [`cli.js`](cli.js) parses the arguments and invokes the corresponding function from [`lib/utils.js`](lib/utils.js).
3.  The function in [`lib/utils.js`](lib/utils.js) scans the target directory, reads files, and analyzes their content.
4.  The results are returned to [`cli.js`](cli.js), which then formats and prints the output to the user.

Configuration, such as directories to ignore, is managed in [`config/localVars.js`](config/localVars.js).
</file>

<file path="DB_SCHEMA.md">
# Database Schema

This project, `loqatevars`, does not utilize a database. It is a command-line tool that operates directly on the file system to analyze JavaScript source code. All data is processed in memory during the execution of the tool, and no persistent data storage is required.
</file>

<file path="index.js">
/**
 * @file Main entry point for the loqatevars npm module.
 * @description This file serves as the public interface for the `loqatevars` package.
 * It exports the core scanning functions, making them available for programmatic
 * use in other Node.js projects.
 */

const {
  findMatchingFiles,
  findMatchingFilesDetailed
} = require('./lib/utils.js');

module.exports = {
  findMatchingFiles,
  findMatchingFilesDetailed
};
</file>

<file path="MODULES.md">
# Modules

This document provides a summary of the key modules in the `loqatevars` project.

*   **[`index.js`](index.js)**
    *   **Purpose**: The main entry point for the `npm` package.
    *   **Functionality**: It exports the core functions `findMatchingFiles` and `findMatchingFilesDetailed` from [`lib/utils.js`](lib/utils.js), making them available for other Node.js projects to use programmatically.

*   **[`cli.js`](cli.js)**
    *   **Purpose**: The command-line interface for the application.
    *   **Functionality**: This module handles parsing of command-line arguments, executes the appropriate scanning functions based on user commands (`scan`, `detailed`), and displays the results in a user-friendly format in the console. It also provides a `help` command.

*   **[`lib/utils.js`](lib/utils.js)**
    *   **Purpose**: The core logic of the application.
    *   **Functionality**: This is where the main work of finding and analyzing files happens.
        *   `searchFiles`: Uses `globby` to find all relevant files in a directory, respecting ignore patterns.
        *   `analyzeConstUsage`: Uses `acorn` to perform Abstract Syntax Tree (AST) analysis on file content to identify `const` declarations (excluding `require` statements) and `process.env` usage.
        *   `findMatchingFiles` and `findMatchingFilesDetailed`: These functions orchestrate the file search and analysis, returning a simple or detailed list of matching files.

*   **[`config/localVars.js`](config/localVars.js)**
    *   **Purpose**: Centralized configuration for the application.
    *   **Functionality**: This file defines and exports configuration variables, such as `ignoreDirs` (a list of directories to exclude from scans) and any environment variables used in the project. This makes it easy to manage and modify configuration without changing the core logic.

*   **[`lib/errors.js`](lib/errors.js)**
    *   **Purpose**: Defines a custom error class for the application.
    *   **Functionality**: This file contains the `AppError` class, which is used to create custom errors with unique codes. This is useful for handling specific error cases in the application.
</file>

<file path=".github/workflows/nodejs.yml">
name: Node.js CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - uses: actions/upload-artifact@v3
        with:
          name: coverage
          path: coverage
          if-no-files-found: ignore
</file>

<file path="attached_assets/Pasted--directory-test-git-main-hexdump-C-scopeTest-js-00000000-2f-2f-20-54-65-73-74-20-66-69-6c-65--1753168145351_1753168145352.txt">
➜  directory_test git:(main) hexdump -C scopeTest.js
00000000  2f 2f 20 54 65 73 74 20  66 69 6c 65 20 66 6f 72  |// Test file for|
00000010  20 74 6f 70 2d 6c 65 76  65 6c 20 73 63 6f 70 65  | top-level scope|
00000020  20 64 65 74 65 63 74 69  6f 6e 0a 63 6f 6e 73 74  | detection.const|
00000030  20 6c 6f 63 61 6c 56 61  72 73 20 3d 20 72 65 71  | localVars = req|
00000040  75 69 72 65 28 27 2e 2e  2f 63 6f 6e 66 69 67 2f  |uire('../config/|
00000050  6c 6f 63 61 6c 56 61 72  73 27 29 3b 0a 0a 66 75  |localVars');..fu|
00000060  6e 63 74 69 6f 6e 20 6d  79 46 75 6e 63 74 69 6f  |nction myFunctio|
00000070  6e 28 29 20 7b 0a 20 20  63 6f 6e 73 74 20 69 6e  |n() {.  const in|
00000080  73 69 64 65 46 75 6e 63  74 69 6f 6e 20 3d 20 22  |sideFunction = "|
00000090  73 68 6f 75 6c 64 20 4e  4f 54 20 62 65 20 66 6c  |should NOT be fl|
000000a0  61 67 67 65 64 22 3b 0a  20 20 69 66 20 28 74 72  |agged";.  if (tr|
000000b0  75 65 29 20 7b 0a 20 20  20 20 63 6f 6e 73 74 20  |ue) {.    const |
000000c0  69 6e 73 69 64 65 42 6c  6f 63 6b 20 3d 20 22 73  |insideBlock = "s|
000000d0  68 6f 75 6c 64 20 4e 4f  54 20 62 65 20 66 6c 61  |hould NOT be fla|
000000e0  67 67 65 64 22 3b 0a 20  20 7d 0a 7d 0a 0a 63 6c  |gged";.  }.}..cl|
000000f0  61 73 73 20 4d 79 43 6c  61 73 73 20 7b 0a 20 20  |ass MyClass {.  |
00000100  63 6f 6e 73 74 72 75 63  74 6f 72 28 29 20 7b 0a  |constructor() {.|
00000110  20 20 20 20 63 6f 6e 73  74 20 69 6e 73 69 64 65  |    const inside|
00000120  43 6c 61 73 73 20 3d 20  22 73 68 6f 75 6c 64 20  |Class = "should |
00000130  4e 4f 54 20 62 65 20 66  6c 61 67 67 65 64 22 3b  |NOT be flagged";|
00000140  0a 20 20 7d 0a 7d 0a 0a  69 66 20 28 74 72 75 65  |.  }.}..if (true|
00000150  29 20 7b 0a 20 20 63 6f  6e 73 74 20 69 6e 73 69  |) {.  const insi|
00000160  64 65 49 66 42 6c 6f 63  6b 20 3d 20 22 73 68 6f  |deIfBlock = "sho|
00000170  75 6c 64 20 4e 4f 54 20  62 65 20 66 6c 61 67 67  |uld NOT be flagg|
00000180  65 64 22 3b 0a 7d                                 |ed";.}|
00000186
➜  directory_test git:(main) loqatevars --debug
badConst.js badEnvOnly.js mockEnv.js scopeTest.js

Found 4 files
➜  directory_test git:(main) which loqatevars
npm list -g loqatevars
/Users/q/.nvm/versions/node/v22.17.0/bin/loqatevars
/Users/q/.nvm/versions/node/v22.17.0/lib
└── loqatevars@1.0.4

➜  directory_test git:(main) loqatevars detailed

=== loqatevars Analysis ===
Directory: /Users/q/code/loqatevars/directory_test
Total JS files: 13
Scanned files: 12
Matching files: 4
Ignored files: localVars.js

Files containing const or process.env:
  badConst.js ()
  badEnvOnly.js (process.env)
  mockEnv.js (process.env)
  scopeTest.js ()

Concatenated result:
badConst.js badEnvOnly.js mockEnv.js scopeTest.js
➜  directory_test git:(main)
</file>

<file path="attached_assets/Pasted--workspace-npm-test-loqatevars-1-0-4-test-node-experimental-vm-modules-node-modules-jest-bi-1753165878508_1753165878509.txt">
~/workspace$ npm test

> loqatevars@1.0.4 test
> node --experimental-vm-modules node_modules/jest/bin/jest.js tests

 PASS  tests/integration.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🛠️  run anywhere with `dotenvx run -- yourcommand`)

      at _log (node_modules/dotenv/lib/main.js:136:11)

(node:686) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/cli.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/utils.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  override existing env vars with { override: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

(node:693) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/glob-patterns.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🛠️  run anywhere with `dotenvx run -- yourcommand`)

      at _log (node_modules/dotenv/lib/main.js:136:11)

(node:687) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/indirect-env.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent building .env in docker: https://dotenvx.com/prebuild)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/findMatchingFilesDetailed.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/cli.default.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  specify custom .env file path with { path: '/custom/path/.env' })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/cli.multipleOptions.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.defaultStreamFallback.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/localVars.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/cli.invalidCommand.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  load multiple .env files with { path: ['.env.local', '.env'] })

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/asyncPool.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent building .env in docker: https://dotenvx.com/prebuild)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/cli.run.test.js
 PASS  tests/cli.helpCommand.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.invalid-extension.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/findMatchingFiles.single.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.invalidExtensionType.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.success.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  override existing env vars with { override: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/errors.test.js
 PASS  tests/asyncPool.cleanup.test.js
 PASS  tests/function-scope.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/index.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/findMatchingFiles.invalid.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  load multiple .env files with { path: ['.env.local', '.env'] })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.invalid.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)


Test Suites: 26 passed, 26 total
Tests:       64 passed, 64 total
Snapshots:   0 total
Time:        3.486 s
Ran all test suites matching /tests/i.
~/workspace$ npm i
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported

added 55 packages, removed 16 packages, changed 64 packages, and audited 329 packages in 3s

55 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
~/workspace$ npm test

> loqatevars@1.0.4 test
> node --experimental-vm-modules node_modules/jest/bin/jest.js tests

 FAIL  tests/integration.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/integration.test.js:11:18)

 FAIL  tests/cli.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.test.js:1:18)

(node:865) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/utils.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🛠️  run anywhere with `dotenvx run -- yourcommand`)

      at _log (node_modules/dotenv/lib/main.js:136:11)

(node:866) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  tests/glob-patterns.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/cli.run.test.js
 FAIL  tests/cli.invalidCommand.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.invalidCommand.test.js:1:18)

 PASS  tests/findMatchingFilesDetailed.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.defaultStreamFallback.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 FAIL  tests/cli.multipleOptions.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.multipleOptions.test.js:1:18)

 FAIL  tests/cli.default.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.default.test.js:7:18)

 PASS  tests/searchFiles.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/asyncPool.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/indirect-env.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 encrypt with dotenvx: https://dotenvx.com)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/findMatchingFiles.single.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  specify custom .env file path with { path: '/custom/path/.env' })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  enable debug logging with { debug: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 FAIL  tests/cli.helpCommand.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.helpCommand.test.js:1:18)

 PASS  tests/localVars.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent committing .env to code: https://dotenvx.com/precommit)

      at _log (node_modules/dotenv/lib/main.js:136:11)

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  suppress all logs with { quiet: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.invalidExtensionType.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: 🔐 prevent building .env in docker: https://dotenvx.com/prebuild)

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/index.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  specify custom .env file path with { path: '/custom/path/.env' })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/findMatchingFiles.invalid.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  write to custom object with { processEnv: myObject })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/searchFiles.invalid-extension.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  load multiple .env files with { path: ['.env.local', '.env'] })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.success.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  specify custom .env file path with { path: '/custom/path/.env' })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/errors.test.js
 PASS  tests/function-scope.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  override existing env vars with { override: true })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/validateDirectory.invalid.test.js
  ● Console

    console.log
      [dotenv@17.2.0] injecting env (0) from .env (tip: ⚙️  specify custom .env file path with { path: '/custom/path/.env' })

      at _log (node_modules/dotenv/lib/main.js:136:11)

 PASS  tests/asyncPool.cleanup.test.js

Summary of all failing tests
 FAIL  tests/integration.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/integration.test.js:11:18)

 FAIL  tests/cli.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.test.js:1:18)

 FAIL  tests/cli.invalidCommand.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.invalidCommand.test.js:1:18)

 FAIL  tests/cli.multipleOptions.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.multipleOptions.test.js:1:18)

 FAIL  tests/cli.default.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.default.test.js:7:18)

 FAIL  tests/cli.helpCommand.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/runner/workspace/node_modules/yargs/index.mjs

       8 | // Import scanning functions directly from lib to avoid circular dependency when
       9 | // index.js also requires this module
    > 10 | const yargs = require('yargs/yargs');
         |               ^
      11 | const { hideBin } = require('yargs/helpers');
      12 | const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
      13 | const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:799:21)
      at Object.require (cli.js:10:15)
      at Object.require (tests/cli.helpCommand.test.js:1:18)


Test Suites: 6 failed, 20 passed, 26 total
Tests:       49 passed, 49 total
Snapshots:   0 total
Time:        2.377 s, estimated 4 s
Ran all test suites matching tests.
</file>

<file path="directory_test/AGENTS.md">
Do not modify the files in this folder.
</file>

<file path="directory_test/badConst.js">
// File 3: const variable declaration for "bad variable"
const pig = "pink";
</file>

<file path="directory_test/badEnvOnly.js">
// File 5: uses process.env.BAD_ENV without using const


if (process.env.BAD_ENV) {
  console.log('BAD_ENV is set to:', process.env.BAD_ENV);
} else {
  console.log('BAD_ENV is not set');
}
</file>

<file path="directory_test/summary.md">
# Directory Test Directory

This directory contains a collection of JavaScript files used for testing the `loqatevars` tool. 
The files are designed to test specific scenarios, such as:

*   `badConst.js`
*   `badScope.js`
*   `functionCaller.js`
*   `ifStatement.js`
*   `importModule.js`
*   `letVariable.js`
*   `badEnvOnly.js`
*   `requireImport.js`

These files likely serve as test cases to ensure the tool correctly identifies or 
ignores different patterns of `const` and `process.env` usage.
</file>

<file path="test_dir/indirect-env.js">
// Test file for indirect process.env access
const env = process.env;
console.log(env.TEST_VAR);

// Test destructuring
const { env: destructuredEnv } = process;
console.log(destructuredEnv.TEST_VAR2);
</file>

<file path="tests/__mocks__/globby.js">
const fs = require('fs');
const path = require('path');

function walk(dir) {
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  let files = [];
  for (const entry of entries) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      files = files.concat(walk(full));
    } else {
      files.push(full);
    }
  }
  return files;
}

function globbySync(patterns, options = {}) {
  const cwd = options.cwd || process.cwd();
  const ignore = options.ignore || [];
  const exts = patterns.map(p => p.replace('**/*', ''));
  const allFiles = walk(cwd).filter(f => exts.some(ext => f.endsWith(ext)));
  return allFiles.filter(f => !ignore.some(pat => {
    const clean = pat.replace(/\*\*\//g, '').replace(/\*\*/g, '');
    return f.includes(clean);
  }));
}

function globbyStream(patterns, options = {}) {
  const files = globbySync(patterns, options);
  async function* generator() {
    for (const file of files) { yield file; }
  }
  return generator();
}

function globby() {}

globby.stream = jest.fn(globbyStream);

module.exports = {
  __esModule: true,
  default: globby,
  globbySync,
  stream: globby.stream,
};
</file>

<file path="tests/asyncPool.cleanup.test.js">
describe('asyncPool cleanup safety', () => {
  test('does not splice when promise not present', () => {
    const executing = [Promise.resolve('a'), Promise.resolve('b')];
    const e = Promise.resolve('c');
    const cleanup = () => {
      const idx = executing.indexOf(e);
      if (idx > -1) executing.splice(idx, 1);
    };
    const beforeLen = executing.length;
    cleanup();
    expect(executing.length).toBe(beforeLen);
  });
});
</file>

<file path="tests/cli.helpCommand.test.js">
const { main } = require('../cli.js');
const utils = require('../lib/utils.js');

jest.mock('../lib/utils.js');

describe('CLI help command', () => {
  let logSpy;
  let exitSpy;

  beforeEach(() => {
    logSpy = jest.spyOn(console, 'log').mockImplementation();
    exitSpy = jest.spyOn(process, 'exit').mockImplementation();
  });

  afterEach(() => {
    logSpy.mockRestore();
    exitSpy.mockRestore();
    jest.clearAllMocks();
  });

  test('loqatevars help shows usage info', async () => {
    process.argv = ['node', 'cli.js', 'help'];
    await main();
    expect(utils.findMatchingFiles).not.toHaveBeenCalled();
    const output = logSpy.mock.calls.map(c => c.join(' ')).join('\n');
    expect(output).toMatch(/Commands:/); // confirm help output
    expect(exitSpy).not.toHaveBeenCalled();
  });
});
</file>

<file path="tests/cli.invalidCommand.test.js">
const { main } = require('../cli.js');
const utils = require('../lib/utils.js');

jest.mock('../lib/utils.js');

describe('CLI invalid command', () => {
  let logSpy;
  let exitSpy;

  beforeEach(() => {
    logSpy = jest.spyOn(console, 'log').mockImplementation();
    exitSpy = jest.spyOn(process, 'exit').mockImplementation();
  });

  afterEach(() => {
    logSpy.mockRestore();
    exitSpy.mockRestore();
    jest.clearAllMocks();
  });

  test('prints help and does not execute scan for unknown command', async () => {
    process.argv = ['node', 'cli.js', 'unknown'];
    await main();
    expect(utils.findMatchingFiles).not.toHaveBeenCalled();
    expect(logSpy).toHaveBeenCalled();
    const output = logSpy.mock.calls.map(c => c.join(' ')).join('\n');
    expect(output).toMatch(/Unknown command: unknown/);
    expect(output).toMatch(/Commands:/);
  });
});
</file>

<file path="tests/cli.multipleOptions.test.js">
const { main } = require('../cli.js');
const { findMatchingFiles } = require('../lib/utils.js');

jest.mock('../lib/utils.js');

describe('CLI multiple option parsing', () => {
  let logSpy;
  let exitSpy;

  beforeEach(() => {
    logSpy = jest.spyOn(console, 'log').mockImplementation();
    exitSpy = jest.spyOn(process, 'exit').mockImplementation();
  });

  afterEach(() => {
    logSpy.mockRestore();
    exitSpy.mockRestore();
    jest.clearAllMocks();
  });

  test('multiple --ignore options combine correctly', async () => {
    process.argv = ['node', 'cli.js', 'scan', './src', '--ignore', 'one.js', '--ignore', 'two.js'];
    findMatchingFiles.mockResolvedValue([]);
    await main();
    expect(findMatchingFiles).toHaveBeenCalledWith('./src', ['one.js', 'two.js'], ['.js']);
  });

  test('multiple --extensions options combine correctly', async () => {
    process.argv = ['node', 'cli.js', 'scan', './src', '--extensions', '.js', '--extensions', '.ts'];
    findMatchingFiles.mockResolvedValue([]);
    await main();
    expect(findMatchingFiles).toHaveBeenCalledWith('./src', ['**/localVars.js'], ['.js', '.ts']);
  });
});
</file>

<file path="tests/cli.run.test.js">
const path = require('path');
const fs = require('fs-extra');
const os = require('os');
const { execSync } = require('child_process');

// Verify that executing `node cli.js` scans the current working directory

describe('CLI execution from shell', () => {
  let testDir;
  let originalDir;

  beforeEach(() => {
    originalDir = process.cwd();
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-run-'));
    fs.writeFileSync(path.join(testDir, 'a.js'), 'const a = 1;');
  });

  afterEach(() => {
    process.chdir(originalDir);
    fs.removeSync(testDir);
  });

  test('node cli.js scans current working directory', () => {
    process.chdir(testDir); // run CLI from temp directory
    const cliPath = path.resolve(__dirname, '..', 'cli.js');
    const output = execSync(`node ${cliPath}`).toString();
    expect(output).toContain('a.js');
  });
});
</file>

<file path="tests/findMatchingFiles.single.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { findMatchingFiles } = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');

describe('findMatchingFiles with single JS file', () => {
  let tempDir;

  beforeAll(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'single-match-')); // create isolated temp directory
    fs.writeFileSync(path.join(tempDir, 'one.js'), 'const demo = 123;'); // create file with const to be flagged
  });

  afterAll(() => {
    fs.removeSync(tempDir); // clean up temporary directory
  });

  test('should report exactly one matching file', async () => {
    const result = await findMatchingFiles(tempDir, []);
    expect(result).toEqual(['one.js']);
  });
});
</file>

<file path="tests/function-scope.test.js">
const { analyzeConstUsage } = require('../lib/utils');

describe('function scope process.env', () => {
  test('parses function with process.env without ANALYSIS_ERROR', () => {
    const code = `function example() { console.log(process.env.TEST_VAR); }`;
    expect(() => analyzeConstUsage(code, 'func.js')).not.toThrow();
    const result = analyzeConstUsage(code, 'func.js');
    expect(result.hasProcessEnv).toBe(true);
  });
});
</file>

<file path="tests/indirect-env.test.js">
const { analyzeConstUsage } = require('../lib/utils');
const fs = require('fs-extra');

describe('indirect-env tests', () => {
  it('should detect direct process.env access', async () => {
    const code = `const value = process.env.TEST_VAR;`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  it('should detect const env = process.env pattern', async () => {
    const code = `
      const env = process.env;
      console.log(env.TEST_VAR);
    `;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  it('should detect destructured env access', async () => {
    const code = `
      const { env } = process;
      console.log(env.TEST_VAR);
    `;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  it('should detect deep destructured env access', async () => {
    const code = `
      const { env: myEnv } = process;
      console.log(myEnv.TEST_VAR);
    `;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  it('should detect dynamic access process["env"]', async () => {
    const code = `const value = process['env'].TEST_VAR;`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  it('should detect ES module imports as importConst', async () => {
    const code = `
      import fs from 'fs';
      import { something } from 'module';
    `;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.importConst).toBe(2);
    expect(result.variableConst).toBe(0);
  });
});
</file>

<file path="tests/localVars.test.js">
// Tests for config/localVars environment handling

const originalEnv = { ...process.env };

afterEach(() => {
  process.env = { ...originalEnv }; // restore environment after each test
  jest.resetModules(); // ensure localVars re-reads env vars
});

test('loads environment variables correctly', () => {
  process.env.API_KEY = 'abc123';
  process.env.DEBUG_MODE = 'true';
  process.env.PORT = '8080';

  const localVars = require('../config/localVars');

  expect(localVars.API_KEY).toBe('abc123');
  expect(localVars.DEBUG_MODE).toBe(true);
  expect(localVars.PORT).toBe('8080');
});

test('uses default PORT when not provided', () => {
  process.env.API_KEY = 'xyz789';
  process.env.DEBUG_MODE = 'true';
  delete process.env.PORT; // ensure PORT is undefined

  const localVars = require('../config/localVars');

  expect(localVars.API_KEY).toBe('xyz789');
  expect(localVars.DEBUG_MODE).toBe(true);
  expect(localVars.PORT).toBe('3000');
});
</file>

<file path="tests/searchFiles.defaultStreamFallback.test.js">
// Custom mock to test default.stream fallback
jest.mock('globby');

const { searchFiles } = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const globbyModule = require('globby');

describe('searchFiles default.stream fallback', () => {
  let tempDir;

  beforeAll(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'search-default-stream-'));
    fs.writeFileSync(path.join(tempDir, 'one.js'), 'const demo = 1;');
    globbyModule.stream = undefined; // ensure top-level stream is missing
    globbyModule.default.stream.mockImplementation(async function* () {
      yield path.join(tempDir, 'one.js');
    });
  });

  afterAll(() => {
    fs.removeSync(tempDir);
  });

  test('uses globby.default.stream when globby.stream is undefined', async () => {
    const result = await searchFiles(tempDir, ['.js']);
    expect(result).toEqual([path.join(tempDir, 'one.js')]);
  });
});
</file>

<file path="tests/searchFiles.invalid-extension.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { searchFiles } = require('../lib/utils');

const path = require('path');

describe('searchFiles invalid extensions', () => {
  const dir = __dirname;
  const ignore = [];

  test('rejects empty extension string', async () => {
    await expect(searchFiles(dir, [''], ignore)).rejects.toHaveProperty('code', 'INVALID_EXTENSION');
  });

  test('rejects extension consisting only of a dot', async () => {
    await expect(searchFiles(dir, ['.'], ignore)).rejects.toHaveProperty('code', 'INVALID_EXTENSION');
  });

  test('rejects extension containing path separators', async () => {
    await expect(searchFiles(dir, ['bad/ext'], ignore)).rejects.toHaveProperty('code', 'INVALID_EXTENSION');
  });
});
</file>

<file path="tests/searchFiles.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { searchFiles } = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const globby = require('globby').default;

describe('searchFiles without ignoreFiles', () => {
  let tempDir;

  beforeAll(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'search-default-'));
    fs.writeFileSync(path.join(tempDir, 'one.js'), 'const demo = 1;');
  });

  afterAll(() => {
    fs.removeSync(tempDir);
  });

  test('returns files when ignoreFiles is omitted', async () => {
    const mockStream = async function* () {
      yield path.join(tempDir, 'one.js');
    };
    globby.stream.mockReturnValue(mockStream());

    const result = await searchFiles(tempDir, ['.js']);
    expect(result).toEqual([path.join(tempDir, 'one.js')]);
  });
});
</file>

<file path="tests/validateDirectory.test.js">
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const { validateDirectory } = require('../lib/utils');

describe('validateDirectory', () => {
  let tempFile;

  afterEach(() => {
    if (tempFile && fs.existsSync(tempFile)) {
      fs.removeSync(tempFile); // cleanup created test file
    }
    tempFile = null;
  });

  test('rejects for non-string directory argument', async () => {
    await expect(validateDirectory(123)).rejects.toMatchObject({ code: 'INVALID_DIRECTORY' });
  });

  test('rejects when path is an existing file', async () => {
    tempFile = path.join(os.tmpdir(), `temp-file-${Date.now()}`);
    fs.writeFileSync(tempFile, 'test'); // create real file
    await expect(validateDirectory(tempFile)).rejects.toMatchObject({ code: 'PATH_NOT_DIRECTORY' });
  });
});
</file>

<file path="DEPENDENCY_GRAPH.md">
Processed 55 files (709ms) (1 warning)

3 cli.js
3 lib/utils.js
3 tests/cli.test.js
2 tests/cli.default.test.js
2 tests/cli.helpCommand.test.js
2 tests/cli.invalidCommand.test.js
2 tests/cli.multipleOptions.test.js
2 tests/integration.test.js
1 directory_test/badScope.js
1 directory_test/functionCaller.js
1 directory_test/localVars.js
1 directory_test/mockEnv.js
1 directory_test/scopeTest.js
1 directory_test/useLocalVars.js
1 index.js
1 test_samples/config.js
1 test_samples/constants.js
1 test_samples/database.js
1 test_samples/server.js
1 tests/asyncPool.test.js
1 tests/errors.test.js
1 tests/findMatchingFiles.invalid.test.js
1 tests/findMatchingFiles.single.test.js
1 tests/findMatchingFilesDetailed.test.js
1 tests/function-scope.test.js
1 tests/glob-patterns.test.js
1 tests/index.test.js
1 tests/indirect-env.test.js
1 tests/localVars.test.js
1 tests/searchFiles.defaultStreamFallback.test.js
1 tests/searchFiles.invalid-extension.test.js
1 tests/searchFiles.invalidExtensionType.test.js
1 tests/searchFiles.test.js
1 tests/utils.test.js
1 tests/validateDirectory.invalid.test.js
1 tests/validateDirectory.success.test.js
1 tests/validateDirectory.test.js
0 config/localVars.js
0 directory_test/badConst.js
0 directory_test/badEnvOnly.js
0 directory_test/functionDefault.js
0 directory_test/ifStatement.js
0 directory_test/importModule.js
0 directory_test/letVariable.js
0 directory_test/requireImport.js
0 ignoreDirTest/index.js
0 ignoreDirTest/public/file.js
0 lib/errors.js
0 lib/logger.js
0 test_dir/indirect-env.js
0 test_samples/app.js
0 test_samples/localVars.js
0 tests/__mocks__/globby.js
0 tests/asyncPool.cleanup.test.js
0 tests/cli.run.test.js
</file>

<file path="WORKFLOWS.md">
# Workflows

This document outlines the primary workflows for the `loqatevars` tool.

## 1. Standard Scan Workflow

This is the default workflow for identifying files with `const` or `process.env` usage.

1.  **Execution**: The user runs the `loqatevars scan` command from their terminal, optionally providing a directory path.
    ```bash
    loqatevars scan ./my-project
    ```
2.  **Parsing**: [`cli.js`](cli.js) parses the command-line arguments to determine the target directory and any other options (like files to ignore).
3.  **File Search**: The `findMatchingFiles` function in [`lib/utils.js`](lib/utils.js) is called. It uses `globby` to recursively find all `.js` files in the target directory, excluding those in ignored directories (e.g., `node_modules`).
4.  **Analysis**: For each found file, `findMatchingFiles` reads its content and passes it to `analyzeConstUsage`.
5.  **AST Parsing**: `analyzeConstUsage` uses `acorn` to parse the file content into an Abstract Syntax Tree (AST). It traverses the AST to find `const` declarations (that are not `require` statements) and `process.env` usage.
6.  **Result Aggregation**: If a file contains either, its path is added to a list of matches.
7.  **Output**: The list of matching file paths is returned to [`cli.js`](cli.js), which then prints the array of file paths to the console.

## 2. Detailed Scan Workflow

This workflow provides a more detailed analysis of the scanned files.

1.  **Execution**: The user runs the `loqatevars detailed` command.
    ```bash
    loqatevars detailed ./my-project
    ```
2.  **Parsing**: Same as the standard workflow.
3.  **File Search**: The `findMatchingFilesDetailed` function in [`lib/utils.js`](lib/utils.js) is called.
4.  **Analysis**: The analysis process is the same, but `findMatchingFilesDetailed` collects more information for each match, including the counts of different types of `const` declarations.
5.  **Detailed Result Aggregation**: The function returns an object containing a list of detailed match objects and a summary object with statistics (total files scanned, number of matches, etc.).
6.  **Output**: [`cli.js`](cli.js) formats this detailed information into a human-readable summary and prints it to the console.

## 3. Programmatic Usage Workflow

The tool can also be used as a library in other Node.js projects.

1.  **Import**: A developer imports the desired function into their project:
    ```javascript
    const { findMatchingFiles } = require('loqatevars');
    ```
2.  **Execution**: They call the function with the desired directory and options, using `async/await` to handle the returned Promise.
3.  **Return Value**: The function returns a Promise that resolves with the results, which can then be used by the calling code.
</file>

<file path="directory_test/badScope.js">
// Test file for top-level scope detection
const localVars = require('../config/localVars');
//const pig = "pink";

function myFunction() {
	let insideFunction = "should NOT be flagged";
	if (true) {
		let insideBlock = "should NOT be flagged";
	}
}

class MyClass {
	constructor() {
		let insideClass = "should NOT be flagged";
	}
}

if (true) {
	let insideIfBlock = "should NOT be flagged";
}
</file>

<file path="tests/asyncPool.test.js">
const { asyncPool } = require('../lib/utils');

jest.setTimeout(10000);

describe('asyncPool error handling', () => {
  test('continues processing when a task rejects', async () => {
    const tasks = [
      () => Promise.resolve('first'),
      () => Promise.reject(new Error('fail')),
      () => new Promise(res => setTimeout(() => res('last'), 10))
    ];

    const results = await asyncPool(2, tasks);

    expect(results.length).toBe(3);
    expect(results[0].status).toBe('fulfilled');
    expect(results[1].status).toBe('rejected');
    expect(results[2].status).toBe('fulfilled');
    expect(results[2].value).toBe('last');
  });

  test('rejects for non-positive limit', async () => {
    await expect(asyncPool(0, [() => Promise.resolve()]))
      .rejects.toHaveProperty('code', 'INVALID_POOL_LIMIT');
  });
});
</file>

<file path="tests/findMatchingFilesDetailed.test.js">
// Mock globby to avoid ESM loading issues
jest.mock('globby');
const { findMatchingFilesDetailed } = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');

describe('findMatchingFilesDetailed', () => {
  const testDir = path.join(__dirname, 'detailed-data');

  beforeAll(() => {
    fs.ensureDirSync(testDir);
    fs.writeFileSync(path.join(testDir, 'file1.js'), 'const x = 1;');
    fs.writeFileSync(path.join(testDir, 'file2.js'), 'process.env.USER;');
  });

  afterAll(() => {
    fs.removeSync(testDir);
  });

  test('should return details for matching files', async () => {
    const result = await findMatchingFilesDetailed(testDir, []);
    expect(result.matches.length).toBe(2);
    const paths = result.matches.map(m => m.relativePath);
    expect(paths).toContain('file1.js');
    expect(paths).toContain('file2.js');
    expect(result.summary.scannedFiles).toBe(2);
    expect(result.summary.matchingFiles).toBe(2);
  });

  test('should throw error for invalid directory', async () => {
    await expect(findMatchingFilesDetailed('./nonexistent')).rejects.toThrow('DIRECTORY_NOT_FOUND');
  });

  test('should classify reasons for each file case', async () => {
    const caseDir = path.join(__dirname, 'reason-data');
    try {
      fs.ensureDirSync(caseDir); // ensure test directory
      fs.writeFileSync(path.join(caseDir, 'envOnly.js'), 'console.log(process.env.API_KEY);');
      fs.writeFileSync(path.join(caseDir, 'constOnly.js'), 'const a = 1;');
      fs.writeFileSync(path.join(caseDir, 'both.js'), 'const b = 2; console.log(process.env.NODE_ENV);');

      const result = await findMatchingFilesDetailed(caseDir, []);
      expect(result.matches.length).toBe(3);

      const reasons = {};
      result.matches.forEach(m => { reasons[m.relativePath] = m.reason; });

      expect(reasons['envOnly.js']).toBe('process.env');
      expect(reasons['constOnly.js']).toBe('variables');
      expect(reasons['both.js']).toBe('both');
    } finally {
      fs.removeSync(caseDir); // cleanup
    }
  });
});
</file>

<file path="tests/searchFiles.invalidExtensionType.test.js">
// Test for invalid extension types
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const { searchFiles } = require('../lib/utils');

describe('searchFiles invalid extension type', () => {
  test('throws INVALID_EXTENSION_TYPE for non-string extension', async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ext-type-'));
    fs.writeFileSync(path.join(tempDir, 'file.js'), 'const a = 1;');
    await expect(searchFiles(tempDir, [123], [])).rejects.toThrow('INVALID_EXTENSION_TYPE');
    await expect(searchFiles(tempDir, [123], [])).rejects.toHaveProperty('code', 'INVALID_EXTENSION_TYPE');
    fs.removeSync(tempDir);
  });
});
</file>

<file path="config/localVars.js">
/**
 * @file This file serves as a centralized configuration hub for the application.
 * It contains all hardcoded values, environment variables, and constants, providing a single
 * source of truth for configuration. This approach simplifies maintenance and reduces the
 * risk of inconsistencies.
 *
 * The file is organized into categories to improve readability and maintainability.
 * When adding new values, please adhere to the existing structure and guidelines.
 *
 * Rationale for Centralized Configuration:
 * - **Single Source of Truth**: Avoids scattering configuration values across the codebase,
 *   making it easier to manage and update them.
 * - **Consistency**: Ensures that all parts of the application use the same configuration values.
 * - **Security**: Keeps sensitive information, such as API keys, in one place, making it
 *   easier to secure and audit.
 * - **Maintainability**: Simplifies the process of changing configuration values, as they
 *   only need to be updated in one location.
 */

/**
 * The `dotenv` library is used to load environment variables from a `.env` file
 * into `process.env`. This is a common practice for managing environment-specific
 * configurations in a development environment.
 */
require('dotenv').config();

/**
 * @section Directory Scanning Constants
 *
 * This section defines constants related to directory scanning and file searching.
 * These values are used to configure the behavior of the `loqatevars` tool when
 * it scans the codebase.
 */

/**
 * `ignoreDirs` is an array of directory names that should be ignored during the scan.
 * This list includes common directories that are not relevant for code analysis,
 * such as `node_modules`, build artifacts, and asset directories.
 *
 * Rationale:
 * - Excluding these directories significantly improves the performance of the scan
 *   by reducing the number of files that need to be processed.
 * - It also prevents the tool from flagging irrelevant files, which would create
 *   unnecessary noise in the results.
 */
const ignoreDirs = [
  'node_modules', '.git', '.replit', 'attached_assets',
  'public', 'static', 'assets', 'dist', 'build', 'out',
  'client', 'frontend', 'web', 'www', 'app/assets',
  'src/assets', 'src/public', 'resources/views', 'templates',
  'views', 'pages', 'components', 'styles', 'css', 'scss',
  'images', 'img', 'fonts', 'media'
];

/**
 * @section Test Variables
 *
 * This section contains variables that are used for testing purposes.
 * These variables are not used in the production application but are essential
 * for verifying the correctness of the `loqatevars` tool.
 */
const badVariable = "bad variable";
const topLevelVar = "should be flagged";
const anotherTopLevel = "should be flagged";
const finalTopLevel = "should be flagged";

// App constants
const timeout = 5000;
const maxRetries = 3;
const defaultConfig = { theme: 'dark' };


module.exports = {
  ignoreDirs,
  API_KEY: process.env.API_KEY || '',
  DEBUG_MODE: process.env.DEBUG_MODE === 'true',
  MOCK_ENV: process.env.MOCK_ENV,
  BAD_ENV: process.env.BAD_ENV,
  NODE_ENV: process.env.NODE_ENV,
  DB_CONNECTION: process.env.DB_CONNECTION || 'localhost',
  PORT: String(process.env.PORT || 3000), // always return port as string
  DATABASE_URL: process.env.DATABASE_URL,
  badVariable,
  topLevelVar,
  anotherTopLevel,
  finalTopLevel,
  timeout,
  maxRetries,
  defaultConfig
};
</file>

<file path="lib/errors.js">
/**
 * @file This file defines a hierarchy of custom error classes for the application.
 * By creating a structured error hierarchy, we can handle different types of errors
 * more effectively and provide more meaningful feedback to the user.
 *
 * The error classes are designed to distinguish between operational errors (expected issues)
 * and programmer errors (bugs), which is a best practice for robust error handling.
 */

/**
 * `AppError` is the base class for all custom errors in the application.
 * It extends the built-in `Error` class and adds support for a `code` property,
 * which allows for more specific error handling based on a unique identifier.
 *
 * This class serves as the foundation for creating more specialized error types,
 * such as `OperationalError` and `ProgrammerError`.
 *
 * @class AppError
 * @extends {Error}
 */
class AppError extends Error {
  /**
   * Creates an instance of AppError.
   * @param {string} message - A human-readable description of the error.
   * @param {string} code - A unique, machine-readable code for the error (e.g., 'INVALID_DIRECTORY').
   * @memberof AppError
   */
  constructor(message, code) {
    super(message);
    // this.constructor.name ensures that the error name is always the name of the class
    this.name = this.constructor.name;
    // The 'code' property provides a stable, machine-readable identifier for the error.
    this.code = code;
    // `Error.captureStackTrace` creates a .stack property on `this` instance, which is a string
    // representing the point in the code at which the error was instantiated.
    Error.captureStackTrace(this, this.constructor);
  }
}

/**
 * `OperationalError` is used for runtime errors that are expected to occur under
 * certain conditions, such as invalid user input or external service failures.
 * These are not bugs in the code but rather predictable issues that need to be handled gracefully.
 *
 * The `isOperational` flag is a key property that allows us to distinguish these errors
 * from programmer errors and handle them differently (e.g., by showing a friendly message to the user).
 *
 * @class OperationalError
 * @extends {AppError}
 */
class OperationalError extends AppError {
  /**
   * Creates an instance of OperationalError.
   * @param {string} message - A human-readable description of the error.
   * @param {string} code - A unique, machine-readable code for the error.
   * @memberof OperationalError
   */
  constructor(message, code) {
    super(message, code);
    // This flag indicates that the error is an operational error, which can be useful
    // for centralized error handling logic.
    this.isOperational = true;
  }
}

/**
 * `ProgrammerError` is used for bugs and other unexpected issues in the code.
 * These are errors that should ideally never happen and indicate a flaw in the program's logic.
 *
 * The `isProgrammerError` flag helps in identifying these errors so that they can be
 * logged with more detail (e.g., with a full stack trace) for debugging purposes.
 *
 * @class ProgrammerError
 * @extends {AppError}
 */
class ProgrammerError extends AppError {
  /**
   * Creates an instance of ProgrammerError.
   * @param {string} message - A human-readable description of the error.
   * @param {string} code - A unique, machine-readable code for the error.
   * @memberof ProgrammerError
   */
  constructor(message, code) {
    super(message, code);
    // This flag indicates that the error is a programmer error, which helps in
    // distinguishing it from operational errors.
    this.isProgrammerError = true;
  }
}

/**
 * The custom error classes are exported so that they can be used throughout the application.
 * This allows for consistent and structured error handling across different modules.
 */
module.exports = {
  AppError,
  OperationalError,
  ProgrammerError
};
</file>

<file path="lib/logger.js">
/**
 * @file Configures and exports debug instances for the application.
 * @description This module uses the `debug` library to create namespaces for
 * different parts of the application, allowing for more organized and

 * readable logging.
 */

const debug = require('debug');

/**
 * Exports the debug instances for different application modules.
 * - `cli`: Used for logging within the command-line interface module.
 * - `utils`: Used for logging within the utility functions module.
 *
 * This centralized approach to logger configuration ensures consistency
 * and makes it easy to add new logging namespaces as the application grows.
 */
module.exports = {
  cli: debug('loqatevars:cli'),
  utils: debug('loqatevars:utils')
};
</file>

<file path="tests/integration.test.js">
const fs = require('fs-extra');
const path = require('path');
const os = require('os');

jest.mock('../lib/utils.js', () => ({
    findMatchingFiles: jest.fn(),
    findMatchingFilesDetailed: jest.fn(),
    validateDirectory: jest.fn().mockResolvedValue()
}));

const { main } = require('../cli');

const utils = require('../lib/utils.js');

describe('Integration Tests', () => {
  let testDir;
  let mockLog;
  let mockError;
  let originalArgv;

  beforeEach(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'integration-'));
    mockLog = jest.spyOn(console, 'log').mockImplementation();
    mockError = jest.spyOn(console, 'error').mockImplementation();
    originalArgv = process.argv;
  });

  afterEach(() => {
    fs.removeSync(testDir);
    mockLog.mockRestore();
    mockError.mockRestore();
    process.argv = originalArgv;
  });

    test('should run scan command successfully', async () => {
        utils.findMatchingFiles.mockResolvedValue(['file1.js', 'file2.js']);
        
        process.argv = ['node', 'cli.js', 'scan', testDir, '--ignore', 'ignore.js'];
        await main();
        
        const output = mockLog.mock.calls.join('\\n');
        expect(output).toContain('file1.js');
        expect(output).toContain('file2.js');
        expect(output).not.toContain('ignore.js');
    });

    test('should run detailed command successfully', async () => {
        utils.findMatchingFilesDetailed.mockResolvedValue({
          matches: [{ relativePath: 'file1.js', reason: 'variables' }],
          summary: { scannedFiles: 1, matchingFiles: 1 }
        });

    process.argv = ['node', 'cli.js', 'detailed', testDir];
    await main();

    const output = mockLog.mock.calls.map(c => c.join(' ')).join('\\n');
    expect(output).toContain('Scanned files');
    expect(output).toContain('Matching files');
  });

    test('detailed command classifies files', async () => {
        const caseDir = path.join(testDir, 'reason-data');
        try {
          utils.findMatchingFilesDetailed.mockResolvedValue({
            matches: [
              { relativePath: 'envOnly.js', reason: 'process.env' },
              { relativePath: 'constOnly.js', reason: 'variables' },
              { relativePath: 'both.js', reason: 'both' }
            ],
            summary: { scannedFiles: 3, matchingFiles: 3 }
          });
      process.argv = ['node', 'cli.js', 'detailed', caseDir];
      await main();
        const output = mockLog.mock.calls.map(c => c.join(' ')).join('\\n');
        expect(output).toContain('Matching files');
    } finally {
      fs.removeSync(caseDir);
    }
  });
});
</file>

<file path="tests/utils.test.js">
const {
  analyzeConstUsage,
  findMatchingFiles,
  findMatchingFilesDetailed,
  validateDirectory
} = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const globby = require('globby').default;

jest.mock('globby', () => {
  // Create the main globby function mock
  const mockGlobby = jest.fn();

  // Attach stream method directly to the function
  mockGlobby.stream = jest.fn();

  return {
    __esModule: true,
    default: mockGlobby,
    globby: mockGlobby,
    stream: mockGlobby.stream,
  };
});

describe('analyzeConstUsage', () => {
  test('should detect const variable declarations', () => {
    const code = `const x = 10;`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.variableConst).toBe(1);
    expect(result.shouldFlag).toBe(true);
  });

  test('should ignore require imports', () => {
    const code = `const fs = require('fs');`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.importConst).toBe(1);
    expect(result.variableConst).toBe(0);
    expect(result.shouldFlag).toBe(false);
  });

  test('should detect process.env usage', () => {
    const code = `console.log(process.env.USER);`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
    expect(result.shouldFlag).toBe(true);
  });

  test('detects env alias usage', () => {
    const code = `const env = process.env; console.log(env.MY_VAR);`;
    const result = analyzeConstUsage(code, 'test.js');
    expect(result.hasProcessEnv).toBe(true);
  });

  test('should correctly parse files with ES6 import statements', () => {
    const importPath = path.join(__dirname, '..', 'directory_test', 'importModule.js');
    const code = fs.readFileSync(importPath, 'utf8');
    const result = analyzeConstUsage(code, 'importModule.js');
   expect(result.importConst).toBe(2);
  });
});


describe('findMatchingFiles', () => {
  let testDir;

  beforeAll(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'find-match-'));
    fs.writeFileSync(path.join(testDir, 'file1.js'), 'const x = 1;');
    fs.writeFileSync(path.join(testDir, 'file2.js'), 'process.env.USER;');
    fs.writeFileSync(path.join(testDir, 'ignore.js'), '// ignored');
  });

  afterAll(() => {
    fs.removeSync(testDir);
  });

  test('should find files with const or process.env', async () => {
    // Mock globby.stream to return test files
    const mockStream = async function* () {
      yield path.join(testDir, 'file1.js');
      yield path.join(testDir, 'file2.js');
    };
    globby.stream.mockReturnValue(mockStream());
    
    const result = await findMatchingFiles(testDir, ['ignore.js']);
    expect(result).toEqual(expect.arrayContaining(['file1.js', 'file2.js']));
  });

  test('should return an empty array when no matching files', async () => {
    const noMatchDir = path.join(__dirname, 'no-match-data');
    fs.ensureDirSync(noMatchDir);
    fs.writeFileSync(path.join(noMatchDir, 'a.js'), 'let a = 1;');
    fs.writeFileSync(path.join(noMatchDir, 'b.js'), 'var b = 2;');
    
    // Mock globby.stream to return test files
    const mockStream = async function* () {
      yield path.join(noMatchDir, 'a.js');
      yield path.join(noMatchDir, 'b.js');
    };
    globby.stream.mockReturnValue(mockStream());

    const result = await findMatchingFiles(noMatchDir, []);
    expect(result).toEqual([]);

    fs.removeSync(noMatchDir);
  });
});

describe('findMatchingFilesDetailed', () => {
  let testDir;

  beforeAll(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'detailed-match-'));
    fs.writeFileSync(path.join(testDir, 'file1.js'), 'const x = 1;');
    fs.writeFileSync(path.join(testDir, 'file2.js'), 'process.env.USER;');
    fs.writeFileSync(path.join(testDir, 'ignore.js'), '// ignored');
  });

  afterAll(() => {
    fs.removeSync(testDir);
  });

  test('should return detailed results', async () => {
    // Mock globby.stream to return test files
    const mockStream = async function* () {
      yield path.join(testDir, 'file1.js');
      yield path.join(testDir, 'file2.js');
    };
    globby.stream.mockReturnValue(mockStream());
    
    const result = await findMatchingFilesDetailed(testDir, ['ignore.js']);
    expect(result.summary.scannedFiles).toBe(2);
    expect(result.summary.matchingFiles).toBe(2);
    const paths = result.matches.map(m => m.relativePath);
    expect(paths).toContain('file1.js');
    expect(paths).toContain('file2.js');
  });

  test('should report zero matches when none found', async () => {
    const emptyDir = path.join(__dirname, 'no-match-detailed');
    fs.ensureDirSync(emptyDir);
    fs.writeFileSync(path.join(emptyDir, 'a.js'), 'function t() { return 1; }');
    fs.writeFileSync(path.join(emptyDir, 'b.js'), 'var x = 3;');
    
    // Mock globby.stream to return test files
    const mockStream = async function* () {
      yield path.join(emptyDir, 'a.js');
      yield path.join(emptyDir, 'b.js');
    };
    globby.stream.mockReturnValue(mockStream());

    const result = await findMatchingFilesDetailed(emptyDir, []);
    expect(result.matches.length).toBe(0);
    expect(result.summary.matchingFiles).toBe(0);
    expect(result.summary.scannedFiles).toBe(2);

    fs.removeSync(emptyDir);
  });
});

describe('validateDirectory additional cases', () => {
  test('should reject when path is a file', async () => {
    const filePath = path.join(__dirname, 'tempfile');
    fs.writeFileSync(filePath, 'data');
    await expect(validateDirectory(filePath)).rejects.toThrow('PATH_NOT_DIRECTORY');
    fs.removeSync(filePath);
  });

  test('should reject for non-string path', async () => {
    await expect(validateDirectory(42)).rejects.toThrow('INVALID_DIRECTORY');
  });
});

describe('analyzeConstUsage error handling', () => {
  test('should throw AppError for invalid JavaScript', () => {
    const badCode = 'const =';
    expect(() => analyzeConstUsage(badCode, 'bad.js')).toThrow(
      "Failed to parse bad.js: Both module and script parsing failed. Module error: Unexpected token (1:6), Script error: Unexpected token (1:6)"
    );
  });
});

describe('concurrency detection', () => {
  test('defaults to 1 when os.cpus returns undefined', () => {
    jest.resetModules();
    const os = require('os');
    const original = os.cpus;
    os.cpus = () => undefined;
    const { concurrency } = require('../lib/utils');
    os.cpus = original;
    expect(concurrency).toBe(1);
  });
});
</file>

<file path="replit.md">
# loqatevars

## Overview

This is an npm module named "loqatevars" designed to help locate errant variable use in LLM-created applications. The module scans JavaScript codebases to identify files that mix variable declarations (const) with environment variable access (process.env), enabling developers to centralize variable declarations and environment variable naming instead of having them scattered and mutated throughout the codebase. It provides both programmatic API and CLI interface for code quality auditing and refactoring guidance.

## System Architecture

### Frontend Architecture
- **Type**: N/A (This is a library module, not a frontend application)

### Backend Architecture
- **Type**: Node.js library module
- **Entry Point**: `index.js` serves as the main export file
- **Organization**: Modular structure with separate library directory
- **Export Strategy**: CommonJS module exports with centralized export management

### Key Design Decisions
1. **Modular Structure**: Separates library code into `lib/` directory for better organization
2. **Centralized Exports**: Uses `index.js` to consolidate and expose public API
3. **Error Handling**: Implements comprehensive input validation and error throwing
4. **Documentation**: JSDoc comments for all public functions
5. **CLI Support**: Provides command-line interface with flexible options
6. **Recursive Scanning**: Walks directory trees while respecting ignore patterns

## Key Components

### Core Files
- **`index.js`**: Main entry point that re-exports scanner functions from `lib/utils.js`
- **`lib/utils.js`**: Core scanner implementation with file system operations
- **`cli.js`**: Command-line interface for terminal usage
- **Tests**: Automated test suite executed with Jest via `npm test`

### Scanner Functions
1. **`findMatchingFiles(dir, ignoreFiles, extensions)`**: Identifies files with meaningful variable declarations or process.env usage, intelligently excluding files that only contain import/require const declarations
2. **`findMatchingFilesDetailed(dir, ignoreFiles, extensions)`**: Enhanced analysis providing detailed const usage breakdown, distinguishing between import const and variable const declarations to help prioritize refactoring efforts

### Error Handling Strategy
- Input type validation for all functions
- Descriptive error messages
- Defensive programming practices
- Empty input validation

## Data Flow

```
External Consumer
    ↓
index.js (main entry) ←→ cli.js (CLI interface)
↓
lib/utils.js (scanner implementation)
↓
File System (recursive directory walking)
```

1. Consumer imports scanner functions from main `index.js` or uses CLI via `cli.js`
2. `index.js` re-exports scanner functions from `lib/utils.js`
3. Scanner implementation walks file system recursively
4. Returns matching files as arrays of file paths or detailed objects

## External Dependencies

### Runtime Dependencies
- **None**: Pure Node.js implementation using only built-in modules

### Development Dependencies
- **None**: No external testing frameworks or build tools required

### Built-in Node.js Modules Used
- **fs**: File system operations for reading files and directories
- **path**: Path manipulation and resolution utilities

## Recent Changes

### January 2025 - Critical Bug Fixes
- **Scope Tracking Bug Fix**: Fixed logic in `analyzeConstUsage()` to properly distinguish between top-level and nested const declarations. Only top-level const declarations are now considered for flagging, correctly ignoring const declarations inside functions, classes, and blocks.
- **Package Compatibility**: Resolved yargs ES module compatibility issue by downgrading from v18+ to v17.7.2 and pinning the exact version (removed caret) to prevent future compatibility breaks.
- **Test Suite**: All 26 test suites with 64 tests now pass successfully after fixes.

## Deployment Strategy

### Package Distribution
- **Type**: NPM package template
- **Installation**: Git-based installation with remote URL setup
- **Structure**: Standard npm module layout ready for publication

### Development Workflow
1. Clone template repository
2. Update git remote to target repository
3. Implement custom utility functions
4. Run tests with `npm test`
5. Publish to npm registry

## User Preferences

Preferred communication style: Simple, everyday language.

## Changelog

```
Changelog:
- June 29, 2025. Initial setup as npm module template
- June 29, 2025. Complete transformation to loqatevars functionality
  - Replaced utility functions with codebase scanning capabilities
  - Added findMatchingFiles() and findMatchingFilesDetailed() functions
  - Implemented CLI interface with flexible options
  - Added comprehensive test suite with sample file generation
  - Enhanced error handling for file system operations
- June 29, 2025. Updated module name to "loqatevars"
  - Updated all documentation and CLI references
  - Clarified module purpose as variable locator for LLM-generated code
  - Added comprehensive documentation explaining refactoring patterns
  - Established clear use case: helping centralize scattered variable declarations
- June 30, 2025. Fixed scanning logic and implemented proper CLI commands
  - Changed from AND to OR logic (finds files with either const OR process.env)
  - Restructured CLI with command-based interface (scan, detailed, help)
  - Created proper npm package with bin configuration
  - Successfully implemented "loqatevars" command with npm link
  - Updated all documentation to use proper command syntax
- July 1, 2025. Enhanced const analysis to exclude import/require declarations
  - Added smart const analysis that distinguishes import const from variable const
  - Files with only import/require const declarations are no longer flagged
  - Enhanced detailed analysis shows breakdown of const usage types
  - Improved accuracy in identifying files that need variable centralization
  - Added comprehensive test cases demonstrating the enhanced logic
- July 1, 2025. Added file count display to basic scan output
  - Basic scan now shows file count after blank line (e.g., "Found 5 files")
  - Enhanced user feedback for both CLI and programmatic usage
  - Maintains clean output format with proper spacing
- July 1, 2025. Added frontend directory exclusion
  - Scanner now ignores common frontend directories (public, static, assets, dist, build, etc.)
  - Excludes framework-specific frontend folders (components, views, pages, styles, etc.)
  - Focuses scanning on backend/server code where variable centralization is most relevant
  - Improves scan accuracy by avoiding frontend-specific JavaScript patterns
```

## Usage Examples

### Programmatic Usage
```javascript
const { findMatchingFiles, findMatchingFilesDetailed } = require('loqatevars');

// Basic usage - returns an array of file paths
const matches = findMatchingFiles('./src');
console.log(matches); // ["src/config.js", "src/server.js"]

// Detailed usage - returns object with statistics
const detailed = findMatchingFilesDetailed('./src');
console.log(detailed.summary.matchingFiles); // 2
console.log(detailed.matches[0].relativePath); // "config.js"
```

### CLI Usage
```bash
# Basic scan
node cli.js ./src

# Detailed output
node cli.js ./src --detailed

# Custom ignore files
node cli.js ./src --ignore "spec.js,localVars.js"

# Multiple file extensions
node cli.js ./src --extensions ".js,.ts"
```

## Primary Use Case

loqatevars addresses a common problem in LLM-generated code: scattered variable declarations and inconsistent environment variable usage. When AI generates code, it often creates multiple files that directly access `process.env` while also declaring local constants, making configuration management difficult and error-prone.

### Problem Pattern (What loqatevars identifies)
Files that mix local variable declarations with direct environment variable access:
```javascript
// database.js - problematic pattern
const dbHost = process.env.DB_HOST || 'localhost';
const timeout = 5000;

// server.js - problematic pattern
const port = process.env.PORT || 3000;
const maxConnections = 100;
```

### Solution Pattern (Refactoring goal)
Centralized configuration with clean separation:
```javascript
// localVars.js - centralized config (ignored by scanner)
module.exports = {
  database: { host: process.env.DB_HOST || 'localhost' },
  server: { port: process.env.PORT || 3000 }
};

// database.js - clean consumer
const config = require('./localVars');
const timeout = 5000; // local constants only
```

## Notes

- Module follows CommonJS pattern for maximum compatibility
- Recursive directory scanning with intelligent ignore patterns
- Comprehensive error handling for file system operations
- CLI provides both simple and detailed output modes
- Default ignore pattern assumes `localVars.js` as intended centralized config file
- Helps identify refactoring opportunities in LLM-generated codebases
</file>

<file path="tests/cli.default.test.js">
const fs = require('fs-extra');
const path = require('path');
const os = require('os');

jest.mock('../lib/utils.js');

const { main } = require('../cli.js');
const utils = require('../lib/utils.js');

describe('CLI default behavior', () => {
  let mockLog;
  let mockExit;
  let testDir;
  let originalArgv;

  beforeEach(() => {
    mockLog = jest.spyOn(console, 'log').mockImplementation();
    mockExit = jest.spyOn(process, 'exit').mockImplementation();
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-default-'));
    originalArgv = process.argv;
  });

  afterEach(() => {
    mockLog.mockRestore();
    mockExit.mockRestore();
    fs.removeSync(testDir);
    process.argv = originalArgv;
  });

  test('main uses defaults and outputs matches', async () => {
    process.argv = ['node', 'cli.js'];
    const originalCwd = process.cwd;
    process.cwd = () => testDir;

    fs.writeFileSync(path.join(testDir, 'a.js'), 'const a = 1;');
    fs.writeFileSync(path.join(testDir, 'b.js'), 'process.env.B');

    utils.findMatchingFiles.mockResolvedValue(['a.js', 'b.js']);

    await main();

   expect(utils.findMatchingFiles).toHaveBeenCalledWith(testDir, ['**/localVars.js'], ['.js']);
    const output = mockLog.mock.calls.join('\\n');
    expect(output).toContain('a.js');
    expect(output).toContain('b.js');
    expect(mockExit).not.toHaveBeenCalled();

    process.cwd = originalCwd;
  });
});
</file>

<file path="tests/glob-patterns.test.js">
const { findMatchingFiles } = require('../lib/utils');
const globby = require('globby').default;
const fs = require('fs-extra');
const path = require('path');
const os = require('os');

jest.mock('globby', () => {
  const mockGlobby = jest.fn();
  mockGlobby.stream = jest.fn();
  return { __esModule: true, default: mockGlobby, globby: mockGlobby, stream: mockGlobby.stream };
});

describe('Glob pattern tests', () => {
 let tempDir;

 beforeAll(() => {
   tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'glob-test-'));
   fs.writeFileSync(path.join(tempDir, 'file1.js'), 'const a = 1;');
   fs.writeFileSync(path.join(tempDir, 'file2.ts'), 'const b = 2;');
   fs.writeFileSync(path.join(tempDir, 'file3.js'), 'const c = 3;');
 });

 afterAll(() => {
   fs.removeSync(tempDir);
 });

 it('should find .js files only', async () => {
   const mockStream = async function* () {
     yield path.join(tempDir, 'file1.js');
     yield path.join(tempDir, 'file3.js');
   };
   globby.stream.mockReturnValue(mockStream());
   const matches = await findMatchingFiles(tempDir, 'ignore.txt', ['.js']);
   expect(matches).toEqual(['file1.js', 'file3.js']);
 });

 it('should find .ts files only', async () => {
   const mockStream = async function* () { yield path.join(tempDir, 'file2.ts'); };
   globby.stream.mockReturnValue(mockStream());
   const matches = await findMatchingFiles(tempDir, 'ignore.txt', ['.ts']);
   expect(matches).toEqual(['file2.ts']);
 });

 it('should find both .js and .ts files', async () => {
   const mockStream = async function* () {
     yield path.join(tempDir, 'file1.js');
     yield path.join(tempDir, 'file2.ts');
     yield path.join(tempDir, 'file3.js');
   };
   globby.stream.mockReturnValue(mockStream());
   const matches = await findMatchingFiles(tempDir, 'ignore.txt', ['.js', '.ts']);
   expect(matches).toEqual(['file1.js', 'file2.ts', 'file3.js']);
 });

 it('should ignore specified files', async () => {
   const mockStream = async function* () { yield path.join(tempDir, 'file3.js'); };
   globby.stream.mockReturnValue(mockStream());
   const matches = await findMatchingFiles(tempDir, ['file1.js', 'ignore.txt'], ['.js']);
   expect(matches).toEqual(['file3.js']);
 });
});
</file>

<file path="tests/cli.test.js">
const { main } = require('../cli.js');
const { findMatchingFiles, findMatchingFilesDetailed } = require('../lib/utils.js');
const { AppError } = require('../lib/errors');

jest.mock('../lib/utils.js');

describe('CLI Execution', () => {
    let consoleSpy;
    let exitSpy;

    beforeEach(() => {
        consoleSpy = jest.spyOn(console, 'log').mockImplementation();
        exitSpy = jest.spyOn(process, 'exit').mockImplementation();
    });

    afterEach(() => {
        consoleSpy.mockRestore();
        exitSpy.mockRestore();
        jest.clearAllMocks();
    });

    test('main should call findMatchingFiles with correct arguments for scan command', async () => {
        process.argv = ['node', 'cli.js', 'scan', './src', '--ignore', 'ignore.js', '--extensions', '.ts'];
        findMatchingFiles.mockResolvedValue(['file1.ts']);
        await main();
        expect(findMatchingFiles).toHaveBeenCalledWith('./src', ['ignore.js'], ['.ts']);
        expect(consoleSpy).toHaveBeenNthCalledWith(1, 'file1.ts');
        expect(consoleSpy).toHaveBeenNthCalledWith(2, '\nFound 1 file');
    });

    test('main handles trailing commas in options', async () => {
        process.argv = ['node', 'cli.js', 'scan', './src', '--ignore', 'ignore.js,', '--extensions', '.ts,'];
        findMatchingFiles.mockResolvedValue(['file1.ts']);
        await main();
        expect(findMatchingFiles).toHaveBeenCalledWith('./src', ['ignore.js'], ['.ts']);
    });

    test('main should call findMatchingFilesDetailed with correct arguments for detailed command', async () => {
        process.argv = ['node', 'cli.js', 'detailed', './src'];
        findMatchingFilesDetailed.mockResolvedValue({
            matches: [{ relativePath: 'file1.js', reason: 'const' }],
            summary: { scannedFiles: 1, matchingFiles: 1, ignoredFiles: [] }
        });
        await main();
        expect(findMatchingFilesDetailed).toHaveBeenCalledWith('./src', ['**/localVars.js'], ['.js']);
        expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('loqatevars Detailed Analysis'));
    });

    test('main should handle no matches found for scan command', async () => {
        process.argv = ['node', 'cli.js', 'scan'];
        findMatchingFiles.mockResolvedValue([]);
        await main();
        expect(consoleSpy).toHaveBeenCalledWith('No files found containing const or process.env');
    });

    test('main should handle no matches found for detailed command', async () => {
        process.argv = ['node', 'cli.js', 'detailed'];
        findMatchingFilesDetailed.mockResolvedValue({
            matches: [],
            summary: { scannedFiles: 0, matchingFiles: 0, ignoredFiles: [] }
        });
        await main();
        expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No files found containing "const" variables or "process.env".'));
    });

    test('main should handle errors gracefully', async () => {
        process.argv = ['node', 'cli.js', 'scan'];
        const error = new Error('Test Error');
        findMatchingFiles.mockRejectedValue(error);
      const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
      await main();
      expect(consoleErrorSpy).toHaveBeenCalledWith('Unexpected Error:', 'Test Error');
      expect(exitSpy).not.toHaveBeenCalled();
      consoleErrorSpy.mockRestore();
    });

    test('main should handle AppError as operational', async () => {
        process.argv = ['node', 'cli.js', 'scan'];
        const error = new AppError('App fail', 'FAIL');
        findMatchingFiles.mockRejectedValue(error);
        const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
        await main();
        expect(consoleErrorSpy).toHaveBeenCalledWith('Operational Error:', 'App fail');
        expect(exitSpy).not.toHaveBeenCalled();
        consoleErrorSpy.mockRestore();
    });
});
</file>

<file path="package.json">
{
  "name": "loqatevars",
  "version": "1.0.5",
  "description": "Locate JavaScript files with 'const' or 'process.env' usage in LLM-generated codebases",
  "main": "index.js",
  "bin": {
    "loqatevars": "./cli.js"
  },
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests"
  },
  "keywords": [
    "scanner",
    "const",
    "process.env",
    "codebase",
    "analysis",
    "javascript",
    "llm",
    "variables"
  ],
  "author": "",
  "license": "MIT",
  "engines": {
    "node": ">=14.0.0"
  },
  "dependencies": {
    "acorn": "^8.15.0",
    "acorn-walk": "^8.3.4",
    "debug": "^4.4.1",
    "dotenv": "^17.2.0",
    "fs-extra": "^11.3.0",
    "globby": "^14.1.0",
    "repomix": "^1.2.0",
    "yargs": "17.7.2"
  },
  "devDependencies": {
    "jest": "^30.0.5"
  }
}
</file>

<file path="README.md">
# loqatevars

A Node.js module designed to help locate errant variable use in LLM-created applications. It scans JavaScript codebases to find files containing both 'const' declarations and 'process.env' usage, enabling developers to centralize variable declarations and environment variable naming instead of having them scattered and mutated throughout the codebase.

## Why loqatevars?

When LLMs generate code, they often create scattered variable declarations and inconsistent environment variable usage across multiple files. This makes it difficult to:
- Track where variables are defined
- Maintain consistent naming conventions
- Centralize configuration management
- Refactor variable usage

loqatevars helps you identify these files so you can consolidate variable declarations and environment variable access into a centralized location.

## Features

- Recursive directory scanning to find problematic files
- Broad detection: identifies files containing:
  - `const` declarations (excluding imports)
  - `process.env` usage (including direct, indirect, and dynamic access)
  - ES module imports (`import` statements)
- Smart frontend directory exclusion (ignores public, assets, components, etc.)
- Configurable file ignore patterns (defaults to `**/localVars.js`)
- Detailed analysis with statistics for large codebases
- CLI interface with file count display for quick audits
- Lightweight with minimal dependencies

## Detection Limitations

The tool may not detect:
- `process.env` access via computed properties (`process[someKey]`)
- Environment variables accessed through proxy objects
- Variables that are dynamically constructed
- `process.env` access in minified/obfuscated code

## Installation


```bash
# install as dependency
npm install loqatevars
```

## Development Setup

To run the test suite locally you must install the project's dev dependencies.
Running `npm install` without the `--production` flag ensures Jest and other
packages in `devDependencies` are available.
Skipping this step will cause `MODULE_NOT_FOUND` errors when executing `npm test`.

## Usage

### Programmatic API

```javascript
const { findMatchingFiles, findMatchingFilesDetailed } = require('loqatevars');

async function run() {
  // Basic usage - returns an array of file paths
  const matches = await findMatchingFiles('./src');
  console.log(matches); // ["src/config.js", "src/server.js"]

  // Detailed analysis with statistics
  const detailed = await findMatchingFilesDetailed('./src');
  console.log(`Found ${detailed.summary.matchingFiles} matching files`);
  detailed.matches.forEach(match => {
    console.log(`- ${match.relativePath}`);
  });
}

run();
```

### CLI Usage

```bash
# Scan current directory (default command)
loqatevars scan

# Scan specific directory
loqatevars scan ./src

# Detailed analysis with statistics
loqatevars detailed ./src

# Custom ignore files (comma-separated or repeated)
loqatevars scan ./src --ignore "localVars.js,config.js"
# Equivalent repeated usage
loqatevars scan ./src --ignore localVars.js --ignore config.js

# Scan multiple file types
loqatevars scan ./src --extensions ".js,.ts"
# Equivalent repeated usage
loqatevars scan ./src --extensions .js --extensions .ts

# Enable debug logging
loqatevars scan ./src --debug

# Help
loqatevars help
# Or
loqatevars --help
# Or
loqatevars -h
```

Both `--ignore` and `--extensions` accept comma-separated values or can be repeated.

## How it works

loqatevars scans your codebase to identify files that contain variable declarations or environment variable access:

1. **Detects `const` declarations** - Files defining variables locally
2. **Detects `process.env` usage** - Files accessing environment variables
3. **Finds files with either** - All candidates for centralization

The goal is to help you move toward a pattern where:
- Environment variables are accessed in one centralized location (like `config.js`)
- Other files import from that central config instead of directly accessing `process.env`
- Variable naming becomes consistent across your application

By default, it ignores `**/localVars.js` (assuming this is your intended centralized config file).

## API Reference

### `findMatchingFiles(dir, ignoreFiles, extensions)`

Scans directory for files containing either 'const' declarations or 'process.env' usage.

**Parameters:**
- `dir` (string): Directory to scan (default: `process.cwd()`)
- `ignoreFiles` (string|Array): Files to ignore (default: '**/localVars.js')
- `extensions` (string|Array): File extensions to scan (default: ['.js'])

**Returns:** `Promise<string[]>` - A promise that resolves to an array of matching file paths.

### `findMatchingFilesDetailed(dir, ignoreFiles, extensions)`

Provides detailed analysis with statistics and file information for each match.

**Returns:** `Promise<object>` - A promise that resolves to an object with a `matches` array and `summary` statistics.

### `analyzeConstUsage(content)` (Internal Function)

Analyze a single file's contents to determine how `const` is used and whether
`process.env` appears. This function is not part of the public API but is documented here for completeness.

**Parameters:**
- `content` (string): File contents to inspect

**Returns:** Object with `totalConst`, `importConst`, `variableConst`,
`hasProcessEnv`, and `shouldFlag` fields

### CLI Entry Point

You can import the `main` function from `cli.js` to build a custom
command-line interface if needed. This is useful if you want to integrate `loqatevars` into other tools.

**Example:**
```javascript
const { main } = require('loqatevars/cli');

// To run the 'scan' command on the current directory programmatically:
process.argv.push('scan', '.');
main();
```

## Example Output

### Basic scan
```bash
$ loqatevars scan ./src
src/config.js
src/server.js
src/database.js
src/utils.js

Found 4 files
```

These files contain either variable declarations or environment variable access - all candidates for centralization.

### Detailed analysis
```bash
$ loqatevars detailed ./src

=== loqatevars Detailed Analysis ===
Directory: /project/src
Scanned files: 13
Matching files: 4
Ignored file patterns: **/localVars.js

--- Matching Files ---
- config.js (Reason: process.env)
- database.js (Reason: const, process.env)
- server.js (Reason: const, process.env)
- utils.js (Reason: const)

## Error Handling

The CLI tool is designed to exit with a non-zero status code upon encountering an error. The errors are categorized to help distinguish between user-correctable issues and internal problems:

- **`OperationalError`**: This error occurs when there is a problem with the environment or the inputs provided by the user. Examples include:
  - The specified directory does not exist.
  - Invalid file extensions are provided.
  - The user provides an invalid command.

- **`ProgrammerError`**: This error indicates a bug or an issue within the tool's source code. These are unexpected errors that should be reported for investigation.

When an error occurs, a descriptive message is printed to the console to help with troubleshooting.
--- Concatenated Paths ---
/project/src/config.js
/project/src/database.js
/project/src/server.js
/project/src/utils.js
```

## Refactoring Strategy

Once you've identified problematic files, consider this refactoring approach:

**Before (scattered variables):**
```javascript
// database.js
const dbHost = process.env.DB_HOST || 'localhost';
const dbPort = process.env.DB_PORT || 5432;

// server.js  
const port = process.env.PORT || 3000;
const apiKey = process.env.API_KEY;
```

**After (centralized config):**
```javascript
// config.js or localVars.js
module.exports = {
  database: {
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 5432
  },
  server: {
    port: process.env.PORT || 3000,
    apiKey: process.env.API_KEY
  }
};

// database.js
const config = require('./config');
const { host, port } = config.database;

// server.js
const config = require('./config');
const { port, apiKey } = config.server;
```

## Testing

Install dependencies first, then run the Jest test suite:

```bash
npm install
npm test
```

Skipping the installation step results in `MODULE_NOT_FOUND` errors for the Jest runtime. This command runs all unit tests in the `tests/` folder.

All tests reside in the `tests/` directory and are executed via Jest.

## License

MIT
</file>

<file path="cli.js">
#!/usr/bin/env node

/**
 * @fileoverview This script provides a command-line interface (CLI) for the loqatevars tool.
 * It allows users to scan directories for specific file patterns, identify files containing
 * 'const' variables or 'process.env' references, and view detailed analysis reports.
 *
 * @author Your Name
 * @version 1.0.0
 */

/**
 * The `loqatevars` CLI is designed to be a powerful tool for code analysis and maintenance.
 * It helps developers quickly locate files that may contain environment-specific configurations
 * or hard-coded constants, which can be critical in large-scale applications.
 *
 * The CLI supports two main commands:
 * - `scan`: Performs a basic scan and returns a list of matching files.
 * - `detailed`: Provides a more comprehensive analysis, including statistics and reasons for each match.
 *
 * The script is built using `yargs` for command-line argument parsing and provides flexible
 * options for ignoring files, specifying extensions, and enabling debug logging.
 *
 * Scalability Considerations:
 * - The current implementation reads file paths into memory, which may not be ideal for
 *   extremely large directories. Future optimizations could involve streaming file paths
 *   to reduce memory consumption.
 * - The `detailed` command, in particular, could be memory-intensive if a large number of
 *   files are matched. A more scalable approach might involve processing files in smaller
 *   batches or using a more efficient data structure for storing results.
 */

// Import scanning functions directly from lib to avoid circular dependency when
// index.js also requires this module
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js');
const { AppError, OperationalError, ProgrammerError } = require('./lib/errors');
const localVars = require('./config/localVars');

/**
 * The main entry point of the CLI application. This function is responsible for:
 * 1.  Parsing command-line arguments using `yargs`.
 * 2.  Configuring the available commands (`scan`, `detailed`, `help`) and options (`--ignore`, `--extensions`, `--debug`).
 * 3.  Executing the requested command based on user input.
 * 4.  Handling and logging any errors that occur during execution, with specific handling for different error types.
 *
 * The function is designed to be asynchronous to support non-blocking I/O operations,
 * which is crucial for performance in a Node.js environment.
 */
async function main() {
  const rawArgs = hideBin(process.argv);
  const isHelpCommand = rawArgs[0] === 'help';
  const yargsInstance = yargs(rawArgs)
    // set up CLI commands and options
    /**
     * The 'scan' command is the default command of the CLI.
     * It scans the specified directory for files that match the given criteria
     * and prints a list of matching file paths to the console.
     * This command is designed for quick and simple analysis.
     */
    .command('scan [directory]', 'Scan directory for files (default command)', (yargs) => {
      return yargs.positional('directory', {
        describe: 'Directory to scan',
        default: process.cwd()
      });
    })
    /**
     * The 'detailed' command provides a more in-depth analysis of the scanned files.
     * It includes statistics such as the number of scanned files, matching files,
     * and a list of ignored file patterns. It also provides the reason for each match,
     * which can be helpful for understanding why a particular file was flagged.
     */
    .command('detailed [directory]', 'Show detailed analysis with statistics', (yargs) => {
      return yargs.positional('directory', {
        describe: 'Directory to scan',
        default: process.cwd()
      });
    })
    .command('help', 'Show help')
    /**
     * The '--ignore' option allows users to specify a comma-separated list of file patterns to ignore during the scan.
     * This is useful for excluding irrelevant files, such as configuration files or third-party libraries.
     * The default value ignores 'localVars.js' files at any depth, which is a common convention for local environment variables.
     */
    .option('ignore', {
      alias: 'i',
      describe: 'Comma-separated list of files to ignore',
      default: '**/localVars.js', // default pattern skips localVars.js at any path depth
      type: 'string'
    })
    .option('extensions', {
      alias: 'e',
      describe: 'Comma-separated list of extensions to scan',
      default: '.js',
      type: 'string'
    })
    .option('debug', {
      describe: 'Enable debug logging',
      type: 'boolean',
      default: false
    })
    .help()
    .alias('help', 'h')
    .exitProcess(false);

  const argv = yargsInstance.argv;

  if (isHelpCommand || argv.help) { // show help and exit
    yargsInstance.showHelp('log');
    if (localVars.NODE_ENV !== 'test') process.exit(0); // exit success outside tests
    return;
  }

  const command = argv._[0] || 'scan';
  if (!['scan', 'detailed', 'help'].includes(command)) {
    console.log(`Unknown command: ${command}`);
    yargsInstance.showHelp('log');
    return;
  }
  // Resolve directory argument after parsing to ensure we have a usable path
  const directory = argv.directory || process.cwd(); // ensure we have a directory to scan

  // Support repeated --ignore options or comma separated list
  const ignoreInput = Array.isArray(argv.ignore) ? argv.ignore.join(',') : String(argv.ignore);
  const ignoreFiles = ignoreInput
    .split(',')
    .map(f => String(f).trim())
    .filter(f => f); // remove any empty items

  // Support repeated --extensions options or comma separated list
  const extInput = Array.isArray(argv.extensions) ? argv.extensions.join(',') : String(argv.extensions);
  const extensions = extInput
    .split(',')
    .map(ext => String(ext).trim())
    .filter(ext => ext)
    .map(ext => ext.startsWith('.') ? ext : `.${ext}`); // normalise extension format

  if (argv.debug) {
    require('debug').enable('loqatevars:*');
  }

  try {
    if (command === 'detailed') {
      const result = await findMatchingFilesDetailed(directory, ignoreFiles, extensions);
      
      console.log('\n=== loqatevars Detailed Analysis ===');
      console.log(`Directory: ${directory}`);
      console.log(`Scanned files: ${result.summary.scannedFiles}`);
      console.log(`Matching files: ${result.summary.matchingFiles}`);
      
     if (result.summary.ignoredFiles.length > 0) {
       console.log(`Ignored file patterns: ${result.summary.ignoredFiles.join(', ')}`);
     }
      
      console.log('\n--- Matching Files ---');
      
      if (result.matches.length === 0) {
        console.log('No files found containing "const" variables or "process.env".');
      } else {
        result.matches.forEach(match => {
          const classification = match.reason;
          console.log(`- ${match.relativePath} (Reason: ${classification})`);
        });
        
        console.log('\n--- Concatenated Paths ---');
        console.log(result.matches.map(m => m.relativePath).join('\n'));
      }
    } else {
      const matches = await findMatchingFiles(directory, ignoreFiles, extensions);
      if (matches.length > 0) {
        console.log(matches.join('\n'));
        console.log(`\nFound ${matches.length} file${matches.length === 1 ? '' : 's'}`);
      } else {
        console.log('No files found containing const or process.env');
      }
    }
  } catch (error) {
    if (error instanceof OperationalError) {
      console.error('Operational Error:', error.message);
      if (localVars.NODE_ENV !== 'test') process.exit(1);
    } else if (error instanceof ProgrammerError) {
      console.error('Programmer Error:', error.message);
      console.error(error.stack);
      if (localVars.NODE_ENV !== 'test') process.exit(1);
    } else if (error instanceof AppError) {
      console.error('Operational Error:', error.message);
      if (localVars.NODE_ENV !== 'test') process.exit(1);
    } else {
      console.error('Unexpected Error:', error.message);
      console.error(error.stack);
      if (localVars.NODE_ENV !== 'test') process.exit(1);
    }
  }
}

module.exports = {
  main
};

if (require.main === module) {
  main();
}
</file>

<file path="lib/utils.js">
/**
 * @file This file contains the core utilities for the `loqatevars` tool.
 * It includes functions for scanning directories, analyzing JavaScript files for specific
 * code patterns (`const` variables and `process.env` usage), and managing concurrent
 * operations. The utilities are designed to be efficient and scalable, using techniques
 * like AST parsing and asynchronous processing.
 *
 * The main functions exported are:
 * - `findMatchingFiles`: Performs a basic scan and returns a list of matching file paths.
 * - `findMatchingFilesDetailed`: Provides a more comprehensive analysis with detailed statistics.
 * - `analyzeConstUsage`: The core analysis function that uses `acorn` to parse JavaScript
 *   and identify relevant code patterns.
 *
 * Scalability Considerations:
 * - The `asyncPool` function is used to limit concurrency and prevent the application
 *   from overwhelming the system with too many simultaneous file operations.
 * - The use of `globby` streams for file searching is a memory-efficient approach,
 *   especially for large directories.
 * - The AST parsing in `analyzeConstUsage` can be CPU-intensive, so the concurrency
 *   limit is based on the number of CPU cores to optimize performance.
 */

const fs = require('fs-extra');
const path = require('path');
const localVars = require('../config/localVars');
const os = require('os');
const acorn = require('acorn');
const walk = require('acorn-walk');
const { AppError } = require('./errors');
const { utils: d } = require('./logger');

/**
 * `asyncPool` is a utility function for running a collection of asynchronous tasks
 * with a specified concurrency limit. This is crucial for managing system resources
 * and preventing performance degradation when dealing with a large number of I/O-bound
 * or CPU-bound operations.
 *
 * The function works by maintaining a pool of executing promises and using `Promise.race`
 * to wait for the next available slot in the pool. This ensures that no more than `limit`
 * tasks are running at any given time.
 *
 * @param {number} limit - The maximum number of tasks to run concurrently.
 * @param {Array<Function>} tasks - An array of functions that return promises.
 * @returns {Promise<Array<object>>} A promise that resolves to an array of settlement objects,
 *                                    similar to `Promise.allSettled`.
 */
async function asyncPool(limit, tasks) {
  if (!Number.isInteger(limit) || limit <= 0) { // reject invalid limit early
    throw new AppError('Invalid concurrency limit', 'INVALID_POOL_LIMIT');
  }
  const executing = [];
  const results = [];

  for (const task of tasks) {
    const p = Promise.resolve().then(task); // ensure task is a promise
    results.push(p);
    const e = p.finally(() => { // cleanup regardless of outcome
      const idx = executing.indexOf(e); // ensure promise is in the list
      if (idx > -1) executing.splice(idx, 1); // remove only if present
    });
    executing.push(e);
    if (executing.length >= limit) {
      await Promise.race(executing); // wait for the fastest task to finish
    }
  }

  return Promise.allSettled(results);
}

/**
 * The concurrency level is determined based on the number of available CPU cores.
 * This is a common strategy for optimizing the performance of parallel tasks,
 * as it allows the application to take full advantage of the available processing power.
 *
 * In environments where the number of CPU cores cannot be determined, a default
 * concurrency of 1 is used to ensure stability.
 */
const cpuInfo = os.cpus();
const concurrency = cpuInfo ? Math.max(cpuInfo.length, 1) : 1;


/**
 * Analyzes the provided JavaScript code content to identify usage of `const` declarations
 * and `process.env`. It uses AST (Abstract Syntax Tree) parsing for accuracy.
 *
 * The function distinguishes between `const` declarations used for module imports (`require`)
 * and those used for defining variables. This is important because we only want to flag
 * variable declarations.
 *
 * @param {string} content - The JavaScript code to analyze.
 * @returns {object} An object containing the analysis results, including counts of
 *                   different `const` types and a flag indicating if `process.env` is used.
 */
/**
 * `createScopeTracker` is a factory function that creates a scope tracking utility.
 * This utility is essential for accurately analyzing variable declarations and references
 * within the AST. It uses a stack of maps to manage lexical scopes, allowing for
 * correct resolution of variable bindings.
 *
 * The tracker provides methods for entering and exiting scopes, adding new bindings,
 * and finding existing bindings. This is particularly important for distinguishing
 * between top-level `const` declarations and those within nested scopes.
 *
 * @returns {object} A scope tracker object with methods for scope management.
 */
function createScopeTracker() {
  const scopeStack = [new Map()];

  return {
    enterScope() {
      scopeStack.push(new Map());
    },
    exitScope() {
      scopeStack.pop();
    },
    addBinding(name, value) {
      scopeStack[scopeStack.length - 1].set(name, value);
    },
    findBinding(name) {
      for (let i = scopeStack.length - 1; i >= 0; i--) {
        if (scopeStack[i].has(name)) {
          return scopeStack[i].get(name);
        }
      }
      return undefined;
    },
    isTopLevel() {
      return scopeStack.length === 1;
    }
  };
}

// Helper to identify `env` property access
function isEnvProperty(prop) {
  return (
    (prop.type === 'Identifier' && prop.name === 'env') ||
    (prop.type === 'Literal' && prop.value === 'env')
  );
}

/**
 * `analyzeConstUsage` is the core analysis function of the `loqatevars` tool.
 * It takes JavaScript code as input and uses AST (Abstract Syntax Tree) parsing
 * to identify `const` declarations and `process.env` usage.
 *
 * The function is designed to be highly accurate, distinguishing between `const`
 * declarations for module imports and those for regular variables. It also handles
 * various forms of `process.env` access, including direct and indirect references.
 *
 * @param {string} content - The JavaScript code to analyze.
 * @param {string} filePath - The path to the file being analyzed (used for logging).
 * @returns {object} An object containing the analysis results.
 */
function analyzeConstUsage(content, filePath) {
  let totalConst = 0;
  let importConst = 0;
  let variableConst = 0;
  let hasProcessEnv = false;

  try {
    let ast;
    try {
      // First, try parsing the code as an ES module. This is the modern standard
      // and should be the default for most new JavaScript code.
      ast = acorn.parse(content, {
        ecmaVersion: 'latest',
        sourceType: 'module',
        locations: true,
      });
    } catch (parseError) {
      try {
        // If module parsing fails, fall back to parsing as a script. This is
        // necessary for older codebases or files that are not ES modules.
        ast = acorn.parse(content, {
          ecmaVersion: 'latest',
          sourceType: 'script',
          locations: true,
        });
      } catch (scriptParseError) {
        // If both parsing attempts fail, throw a detailed error to help with debugging.
        throw new AppError(
          `Failed to parse ${filePath}: Both module and script parsing failed. Module error: ${parseError.message}, Script error: ${scriptParseError.message}`,
          'AST_PARSING_ERROR'
        );
      }
    }

    const scope = createScopeTracker();

    // `walk.recursive` traverses the AST, allowing us to inspect each node.
    // We provide custom visitor functions for the node types we're interested in.
    walk.recursive(ast, null, {
      BlockStatement(node, state, c) {
        scope.enterScope();
        walk.base.BlockStatement(node, state, c); // traverse inner nodes
        scope.exitScope();
      },
      Function(node, state, c) {
        scope.enterScope();
        walk.base.Function(node, state, c); // traverse function body
        scope.exitScope();
      },
      ImportDeclaration(node) {
        // `import` statements are a form of `const` declaration, but we want to
        // distinguish them from variable declarations.
        importConst += node.specifiers.length;
      },
      VariableDeclaration(node, state, c) {
        if (node.kind === 'const') {
          node.declarations.forEach(decl => {
            totalConst++;
            // We only flag `const` declarations at the top level of the module,
            // as these are more likely to be environment-specific configurations.
            if (scope.isTopLevel()) {
              if (decl.init && decl.init.type === 'CallExpression' && decl.init.callee.name === 'require') {
                importConst++;
              } else {
                variableConst++;
              }
            }
            // Track variables that are assigned the `process` object, so we can
            // detect indirect `process.env` access later.
            if (decl.id.type === 'Identifier' && decl.init) {
              if (decl.init.type === 'Identifier' && decl.init.name === 'process') {
                scope.addBinding(decl.id.name, 'process');
              }
            }
          });
        }
        walk.base.VariableDeclaration(node, state, c);
      },
      MemberExpression(node, state, c) {
        // This visitor function detects direct and indirect access to `process.env`.
        if (
          node.object.type === 'Identifier' &&
          node.object.name === 'process' &&
          isEnvProperty(node.property)
        ) {
          hasProcessEnv = true;
          d('Found process.env at %s:%d:%d', filePath, node.loc.start.line, node.loc.start.column);
        } else if (node.object.type === 'Identifier') {
          const binding = scope.findBinding(node.object.name);
          if (isEnvProperty(node.property) && binding === 'process') {
            hasProcessEnv = true;
            d('Found indirect env access through %s at %s:%d:%d',
              node.object.name, filePath, node.loc.start.line, node.loc.start.column);
          } else if (binding === 'process.env') {
            hasProcessEnv = true;
            d('Found indirect env variable %s at %s:%d:%d',
              node.object.name, filePath, node.loc.start.line, node.loc.start.column);
          }
        }
        walk.base.MemberExpression(node, state, c);
      },
      VariableDeclarator(node, state, c) {
        // This visitor handles cases where `process` or `process.env` are
        // assigned to other variables, which is a form of indirect access.
        if (node.id.type === "Identifier" && node.init) {
          if (node.init.type === "Identifier") {
            if (node.init.name === "process") {
              scope.addBinding(node.id.name, "process");
            } else {
              const ref = scope.findBinding(node.init.name);
              if (ref === "process" || ref === "process.env") {
                scope.addBinding(node.id.name, ref);
              }
            }
          } else if (node.init.type === "MemberExpression" && node.init.object.type === "Identifier" && node.init.object.name === "process" && isEnvProperty(node.init.property)) {
            scope.addBinding(node.id.name, "process.env");
          }
        }
        // This handles destructuring assignments from `process`, such as
        // `const { env } = process;`.
        if (node.id.type === 'ObjectPattern' && node.init && node.init.type === 'Identifier' && node.init.name === 'process') {
          const hasEnv = node.id.properties.some(
            prop => (prop.key.name === 'env' || (prop.value && prop.value.name === 'env'))
          );
          if (hasEnv) {
            hasProcessEnv = true;
            d('Found process destructuring at %s:%d:%d', filePath, node.loc.start.line, node.loc.start.column);
          }
        }
        walk.base.VariableDeclarator(node, state, c);
      }
    });
  } catch (error) {
    // Re-throw AppError instances directly
    if (error instanceof AppError) {
      throw error;
    }
    // Wrap all other errors in AppError with consistent formatting
    throw new AppError(`ANALYSIS_ERROR: Failed to analyze ${filePath} - ${error.message}`, 'ANALYSIS_ERROR');
  }

  return {
    totalConst,
    importConst,
    variableConst,
    hasProcessEnv,
    shouldFlag: hasProcessEnv || variableConst > 0
  };
}

/**
 * Uses `globby` to find all files in a directory that match the given extensions,
 * while respecting a list of ignored files and directories. This function is a wrapper
 * around `globby` to simplify its usage in this application.
 *
 * @param {string} dir - The directory to search in.
 * @param {string[]} extensions - An array of file extensions to look for (e.g., ['.js']).
 * @param {string|string[]} [ignoreFiles=[]] - A single file or an array of files/patterns to ignore.
 * @returns {string[]} An array of absolute file paths that match the criteria.
 */
async function searchFiles(dir, extensions, ignoreFiles = []) {
 const ignoreList = ignoreFiles
   ? (Array.isArray(ignoreFiles) ? ignoreFiles : [ignoreFiles])
   : []; // default to empty array when ignoreFiles omitted
 
  // Validate and normalize extensions
  extensions = extensions.map(ext => {
    if (typeof ext !== 'string') { // ensure each extension is a string before further checks
      throw new AppError('INVALID_EXTENSION_TYPE', 'INVALID_EXTENSION_TYPE');
    }
    if (ext === '') { // reject empty strings early
      throw new AppError('Extension cannot be empty', 'INVALID_EXTENSION');
    }
    if (!ext.startsWith('.')) {
      ext = '.' + ext;
    }
    if (ext === '.') { // '.' alone is not a valid extension
      throw new AppError('Extension cannot be just a dot', 'INVALID_EXTENSION');
    }
    if (ext.includes('/') || ext.includes('\\')) { // no path characters allowed
      throw new AppError(`Extension cannot contain path separators: ${ext}`, 'INVALID_EXTENSION');
    }
    return ext;
  });
 
 // Use multiple patterns instead of brace expansion
 const patterns = extensions.map(ext => `**/*${ext}`);
 
 const ignorePatterns = [
   ...ignoreList,
   ...localVars.ignoreDirs.map(d => `**/${d}/**`)
 ];

  // Use dynamic import so we can load ESM globby within CommonJS
  let streamFn;
  try {
    const globbyModule = await import('globby');
    // Determine the stream function regardless of where it is exported from
    streamFn =
      globbyModule.stream ||
      (globbyModule.default && globbyModule.default.stream) ||
      globbyModule.globbyStream;
    if (typeof streamFn !== 'function') {
      throw new Error('Stream function not found');
    }
  } catch (err) {
    throw new AppError(`GLOBBY_IMPORT_ERROR: ${err.message}`, 'GLOBBY_IMPORT_ERROR');
  }
 const fileStream = streamFn(patterns, {
    cwd: dir,
    ignore: ignorePatterns,
    onlyFiles: true,
    absolute: true,
  });

 const files = [];
 for await (const entry of fileStream) { files.push(entry); }

 return files;
}

/**
 * Asynchronously validates that a given path is a valid, existing directory.
 * This is a crucial step before attempting to scan a directory, as it prevents
 * errors from occurring later in the process.
 *
 * @param {string} dir - The directory path to validate.
 * @throws {AppError} If the directory is invalid, not found, or not a directory.
 */
async function validateDirectory(dir) {
  if (typeof dir !== 'string') {
    throw new AppError(`INVALID_DIRECTORY: The directory '${dir}' is not a valid string.`, 'INVALID_DIRECTORY');
  }

  d('Validating directory: %s', dir);

  try {
    const stat = await fs.stat(dir);
    d('Is directory: %s', stat.isDirectory());
    if (!stat.isDirectory()) {
      throw new AppError(`PATH_NOT_DIRECTORY: The path '${dir}' is not a directory.`, 'PATH_NOT_DIRECTORY');
    }
  } catch (error) {
    if (error.code === 'ENOENT') {
      throw new AppError(`DIRECTORY_NOT_FOUND: The directory '${dir}' does not exist.`, 'DIRECTORY_NOT_FOUND');
    }
    // Re-throw AppError instances directly
    if (error instanceof AppError) {
      throw error;
    }
    // Wrap all other errors in AppError with consistent formatting
    throw new AppError(`VALIDATION_ERROR: Failed to validate directory '${dir}' - ${error.message}`, 'VALIDATION_ERROR');
  }
}

/**
 * Scans a directory for JavaScript files that contain either `const` variable declarations
 * (excluding `require` imports) or `process.env` usage. This is the main "simple" scan
 * function of the tool.
 *
 * It orchestrates the process of validating the directory, searching for files,
 * and analyzing them. The results are returned as an array of file paths.
 *
 * @param {string} dir - The directory to scan.
 * @param {string|string[]} ignoreFiles - Files/patterns to ignore.
 * @param {string[]} extensions - File extensions to scan.
 * @returns {Promise<string[]>} A promise that resolves to an array of matching file paths.
 */
// '**/localVars.js' ensures the default ignore pattern matches any directory level
async function findMatchingFiles(dir = process.cwd(), ignoreFiles = '**/localVars.js', extensions = ['.js']) {
  await validateDirectory(dir);

  const files = await searchFiles(dir, extensions, ignoreFiles);
  const matches = [];
  const tasks = files.map(fullPath => async () => {
    try {
      const content = await fs.readFile(fullPath, 'utf8');
      const analysis = analyzeConstUsage(content, fullPath);
      if (analysis.shouldFlag) {
        return path.relative(dir, fullPath);
      }
    } catch (error) {
      d('Skipping file due to error: %s', fullPath, error);
      if (!(error instanceof AppError)) {
        console.error(`FILE_PROCESSING_ERROR: Failed to process ${fullPath} - ${error.message}`);
      }
    }
    return null;
  });

  const results = await asyncPool(concurrency, tasks);

  results.forEach(result => {
    if (result.status === 'fulfilled' && result.value) {
      matches.push(result.value);
    } else if (result.status === 'rejected') {
      // This part should ideally not be reached if errors are caught inside the limit wrapper
      d('An unexpected error occurred: %s', result.reason);
    }
  });

  return matches;
}

/**
 * Scans a directory and returns a detailed report of files that contain `const`
 * variable declarations or `process.env` usage. This is the "detailed" scan function.
 *
 * Similar to `findMatchingFiles`, but it returns a much richer set of data, including
 * statistics about the scan and detailed information about each matching file.
 *
 * @param {string} dir - The directory to scan.
 * @param {string|string[]} ignoreFiles - Files/patterns to ignore.
 * @param {string[]} extensions - File extensions to scan.
 * @returns {Promise<object>} A promise that resolves to an object containing the detailed
 *                            scan results.
 */
// '**/localVars.js' ensures all localVars.js files are skipped regardless of directory depth
async function findMatchingFilesDetailed(dir = process.cwd(), ignoreFiles = '**/localVars.js', extensions = ['.js']) {
  await validateDirectory(dir);

  const files = await searchFiles(dir, extensions, ignoreFiles);
  const matches = [];
  const tasks = files.map(fullPath => async () => {
    try {
      const content = await fs.readFile(fullPath, 'utf8');
      const analysis = analyzeConstUsage(content, fullPath);
      d('Scanned file: %s, Analysis result: %o', fullPath, analysis);

      if (analysis.shouldFlag) {
        const relativePath = path.relative(dir, fullPath);
        return {
          relativePath: relativePath,
          hasProcessEnv: analysis.hasProcessEnv,
          totalConst: analysis.totalConst,
          importConst: analysis.importConst,
          variableConst: analysis.variableConst,
          reason: analysis.hasProcessEnv && analysis.variableConst > 0 ? 'both' :
                 analysis.hasProcessEnv ? 'process.env' : 'variables'
        };
      }
    } catch (error) {
      d('Skipping file due to error: %s', fullPath, error);
      if (!(error instanceof AppError)) {
        console.error(`FILE_PROCESSING_ERROR: Failed to process ${fullPath} - ${error.message}`);
      }
    }
    return null;
  });

  const results = await asyncPool(concurrency, tasks);

  results.forEach(result => {
    if (result.status === 'fulfilled' && result.value) {
      matches.push(result.value);
      d('Flagged file: %s', result.value.relativePath);
    } else if (result.status === 'rejected') {
      d('An unexpected error occurred: %s', result.reason);
    }
  });

  return {
    matches,
    summary: {
      scannedFiles: files.length,
      matchingFiles: matches.length,
      ignoredFiles: Array.isArray(ignoreFiles) ? ignoreFiles : [ignoreFiles]
    }
  };
}

module.exports = {
  analyzeConstUsage,
  findMatchingFiles,
  findMatchingFilesDetailed,
  searchFiles,
  validateDirectory,
  asyncPool,
  concurrency
};
</file>

</files>
