thuban 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ "use strict";module.exports={version:"0.4.6",errors:{MODULE_NOT_FOUND:{symptom:"Error: Cannot find module 'thuban' or one of its dependencies",cause:"Thuban or a required dependency is not installed, or node_modules is corrupted.",fix:"Run `npm install -g thuban` (global) or `npm install thuban` (local). If already installed, delete node_modules and reinstall.",severity:"critical"},ENOENT:{symptom:"ENOENT: no such file or directory",cause:"The specified file or directory does not exist. The path may be misspelled or the target was moved/deleted.",fix:"Verify the path exists. Use absolute paths to avoid ambiguity. On Windows, check for backslash vs forward-slash issues.",severity:"high"},EACCES:{symptom:"EACCES: permission denied",cause:"The current user lacks read or write permissions on the target file or directory.",fix:"On macOS/Linux run with sudo or fix ownership with `chown`. On Windows, run the terminal as Administrator or adjust folder security settings.",severity:"high"},EPERM:{symptom:"EPERM: operation not permitted",cause:"The OS blocked the operation. Common on Windows when files are locked by another process or antivirus.",fix:"Close any editors or processes using the files. Disable real-time antivirus scanning for the project directory temporarily. On Windows, run as Administrator.",severity:"high"},SHEBANG_ERROR:{symptom:"SyntaxError: Invalid or unexpected token at line 1 (#!/usr/bin/env node)",cause:"Duplicate or malformed shebang line in the CLI entry point. This was a packaging bug.",fix:"Upgrade to Thuban 0.4.6 or later — this is fixed. Run `npm install -g thuban@latest`.",severity:"critical"},BABEL_PARSE_ERROR:{symptom:"SyntaxError: Unexpected token (babel parser)",cause:"The scanned file contains syntax errors or uses an unsupported syntax extension that the parser cannot handle.",fix:"The scanner will skip unparseable files automatically. Fix the syntax error in your source, or add the file to .thubanignore.",severity:"medium"},TIMEOUT:{symptom:"Scan timed out after 120 seconds",cause:"The scan target contains too many files or very large files, exceeding the default timeout.",fix:"Use `--ignore` to skip large directories (e.g., `--ignore dist,build,vendor`). Increase timeout with `--timeout=300`. Split into smaller scans.",severity:"medium"},OUT_OF_MEMORY:{symptom:"FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed — JavaScript heap out of memory",cause:"The project has too many files or extremely large files for the default Node.js memory limit.",fix:'Increase Node memory: `NODE_OPTIONS="--max-old-space-size=4096" thuban scan .` or ignore large directories with `--ignore`.',severity:"high"},GIT_NOT_FOUND:{symptom:"Error: git is not installed or not in PATH",cause:"Git is required for diff, gate, and history commands but is not installed or not accessible from the terminal.",fix:'Install Git from https://git-scm.com. On Windows, ensure "Git from the command line" was selected during install. Restart your terminal after installing.',severity:"high"},NO_FILES_FOUND:{symptom:"No scannable files found in the target directory",cause:"The target directory is empty, all files are ignored, or the path points to a non-project directory.",fix:"Check your .thubanignore file. Verify the target path. Run `thuban scan . --verbose` to see which files are being skipped and why.",severity:"medium"},INVALID_LICENSE:{symptom:"License key is invalid or has expired",cause:"The activation key is incorrect, expired, or has been used on too many machines.",fix:"Run `thuban activate` with a valid key. Check your email for the correct key. Contact support@thuban.dev for license issues.",severity:"high"},CRLF_MISMATCH:{symptom:"Unexpected line ending differences or garbled output on Windows",cause:"Windows CRLF line endings conflicting with Unix LF expectations in the scanner.",fix:"Upgrade to Thuban 0.4.6 — CRLF handling is fixed. Alternatively, configure Git: `git config core.autocrlf true`.",severity:"medium"},NPM_INSTALL_EACCES:{symptom:"npm ERR! Error: EACCES: permission denied during global install",cause:"npm global directory is owned by root or another user.",fix:"On macOS/Linux: `sudo npm install -g thuban` or fix npm permissions (https://docs.npmjs.com/resolving-eacces-permissions-errors). On Windows: run terminal as Administrator.",severity:"high"},NPM_INSTALL_NETWORK:{symptom:"npm ERR! network request failed or ETIMEDOUT",cause:"Network connectivity issue, corporate proxy, or npm registry is unreachable.",fix:"Check internet connection. If behind a proxy: `npm config set proxy http://proxy:port`. Try `npm install -g thuban --registry https://registry.npmjs.org`.",severity:"high"},NPM_INSTALL_ENGINE:{symptom:"npm ERR! engine Unsupported engine — required node >= 16",cause:"Your Node.js version is too old. Thuban requires Node.js 16.0.0 or later.",fix:"Upgrade Node.js from https://nodejs.org. Use nvm (macOS/Linux) or nvm-windows to manage versions: `nvm install 18 && nvm use 18`.",severity:"critical"},NPM_INSTALL_CONFLICT:{symptom:"npm ERR! ERESOLVE unable to resolve dependency tree",cause:"Conflicting peer dependencies in the project.",fix:"Run `npm install -g thuban --legacy-peer-deps` or use `npm install -g thuban --force`.",severity:"medium"},PATH_NOT_FOUND:{symptom:"'thuban' is not recognized as an internal or external command",cause:"The npm global bin directory is not in your system PATH.",fix:"Run `npm config get prefix` to find the install location. Add that path's bin directory to your PATH. On Windows, restart the terminal after changing PATH.",severity:"high"},PATH_SPACES_WINDOWS:{symptom:"Error parsing path or unexpected token in path on Windows",cause:"The project path contains spaces (e.g., C:\\Users\\My User\\project), which can break some operations.",fix:'Move the project to a path without spaces, or wrap the path in double quotes: `thuban scan "C:\\My Projects\\app"`.',severity:"medium"},LONG_PATH_WINDOWS:{symptom:"ENAMETOOLONG or path too long error on Windows",cause:"Windows has a 260-character path limit by default. Deep node_modules nesting can exceed this.",fix:"Enable long paths in Windows: run `reg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f` as Administrator, then restart.",severity:"medium"},ANTIVIRUS_BLOCK:{symptom:"Scan hangs, files are inaccessible, or random EPERM errors on Windows",cause:"Windows Defender or another antivirus is scanning/locking files that Thuban is trying to read.",fix:"Add your project directory and the Thuban install directory to your antivirus exclusion list. For Windows Defender: Settings → Virus & threat protection → Exclusions.",severity:"medium"},NODE_VERSION_OLD:{symptom:"SyntaxError: Unexpected token ??= or optional chaining errors",cause:"Node.js version is below 16. Thuban uses modern JavaScript syntax requiring Node 16+.",fix:"Upgrade Node.js to version 16 or later. Download from https://nodejs.org or use nvm.",severity:"critical"},NODE_VERSION_MISMATCH:{symptom:"Native module compilation errors or N-API version mismatch",cause:"Node.js major version changed after Thuban was installed, invalidating native bindings.",fix:"Reinstall Thuban: `npm install -g thuban`. If using nvm, reinstall after switching Node versions.",severity:"high"},EISDIR:{symptom:"EISDIR: illegal operation on a directory",cause:"A file operation was attempted on a directory path.",fix:"Check that you are pointing to a directory for scan commands, not a file. For single-file scans, use the exact file path.",severity:"low"},EMFILE:{symptom:"EMFILE: too many open files",cause:"The OS file descriptor limit has been reached. Common on macOS with large projects.",fix:"On macOS/Linux: `ulimit -n 10240`. On macOS permanently: add `limit maxfiles 10240 10240` to /etc/launchd.conf. Use `--ignore` to reduce file count.",severity:"high"},ENOSPC:{symptom:"ENOSPC: no space left on device or ENOSPC: System limit for file watchers reached",cause:"Disk is full, or the OS inotify watcher limit is exhausted (Linux).",fix:"Free disk space. On Linux for watcher limit: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`.",severity:"high"},CONFIG_PARSE_ERROR:{symptom:"Error parsing .thubanrc or thuban.config.js",cause:"The configuration file has a syntax error (invalid JSON or JS).",fix:"Validate your config file. For JSON, use a JSON validator. For JS, ensure it exports a valid object. Delete the config to use defaults.",severity:"medium"},REPORT_WRITE_FAILED:{symptom:"Failed to write report file",cause:"Cannot write to the output directory due to permissions or disk space.",fix:"Check write permissions on the output directory. Specify a different output path with `--output /path/to/dir`.",severity:"medium"},UNSUPPORTED_FILE_TYPE:{symptom:"Skipping unsupported file type: .xyz",cause:"The file extension is not in Thuban's supported language list.",fix:"This is informational — Thuban supports JS, TS, Python, Go, Rust, Java, Kotlin, C#, PHP, Ruby, Swift, and Dart. Other file types are skipped.",severity:"low"},SCANNER_CRASH:{symptom:"Unhandled exception in scanner worker thread",cause:"A file caused the scanner to crash, usually due to extremely unusual syntax or encoding.",fix:"Run with `--verbose` to identify the problematic file. Add it to .thubanignore. Report the issue at https://github.com/thuban-dev/thuban/issues.",severity:"high"},GIT_DIRTY_TREE:{symptom:"Cannot run gate: working tree has uncommitted changes",cause:"The gate command requires a clean git working tree to compare against.",fix:"Commit or stash your changes before running `thuban gate`. Use `git stash` to temporarily save changes.",severity:"medium"},DASHBOARD_PORT_IN_USE:{symptom:"EADDRINUSE: address already in use (port 3000)",cause:"Another process is using the dashboard port.",fix:"Specify a different port: `thuban dashboard . --port 3001`. Or stop the other process using port 3000.",severity:"low"},WEBHOOK_FAILED:{symptom:"Failed to send notification to webhook URL",cause:"The webhook URL is unreachable, returned an error, or the payload was rejected.",fix:"Verify the webhook URL is correct and accessible. Check firewall rules. Test the URL with curl first.",severity:"medium"},BASELINE_NOT_FOUND:{symptom:"No baseline found. Create one first with thuban baseline . --create",cause:"A comparison was attempted but no baseline snapshot exists yet.",fix:"Create a baseline first: `thuban baseline . --create`. Then run your comparison.",severity:"medium"},HISTORY_EMPTY:{symptom:"No scan history found for this project",cause:"No previous scans have been recorded. History is stored in the local .thuban/ directory.",fix:"Run at least one scan first: `thuban scan .`. History is stored locally in .thuban/ — do not delete that directory.",severity:"low"},DIFF_NO_GIT:{symptom:"Cannot run diff: not a git repository",cause:"The diff command requires a git repository to determine changed files.",fix:'Initialize a git repo: `git init && git add . && git commit -m "initial"`. Then run `thuban diff .`.',severity:"medium"},CRUCIBLE_TIMEOUT:{symptom:"Crucible deep analysis timed out",cause:"The crucible analysis is computationally intensive and exceeded the time limit on a large codebase.",fix:"Run crucible on smaller subsets: `thuban crucible src/`. Increase timeout: `--timeout=600`. Ignore test directories.",severity:"medium"},MCP_CONNECTION_REFUSED:{symptom:"MCP server connection refused or ECONNREFUSED",cause:"The MCP server is not running or the port/socket configuration is incorrect.",fix:"Start the MCP server first: `thuban mcp-server`. Check that your editor's MCP client config points to the correct address.",severity:"high"},TELEMETRY_DISABLED:{symptom:"Telemetry data not available — telemetry is disabled",cause:"Telemetry collection was turned off in settings.",fix:"Enable telemetry: `thuban telemetry --enable`. Telemetry is anonymous and helps improve Thuban.",severity:"low"},ACTIVATION_FAILED:{symptom:"Activation failed: unable to verify license key",cause:"Network issue reaching the license server, or the key is invalid.",fix:"Check internet connection. Verify the key is correct (check email). Try again in a few minutes. Contact support@thuban.dev if persistent.",severity:"high"},SYMLINK_LOOP:{symptom:"ELOOP: too many levels of symbolic links",cause:"Circular symbolic links in the project directory.",fix:"Identify and remove circular symlinks. Add symlinked directories to .thubanignore.",severity:"medium"},ENCODING_ERROR:{symptom:"Unable to read file: unsupported encoding",cause:"The file uses an encoding other than UTF-8 or ASCII.",fix:"Convert the file to UTF-8, or add it to .thubanignore. Most modern projects should use UTF-8.",severity:"low"},WORKER_THREAD_CRASH:{symptom:"Worker thread exited unexpectedly with code 1",cause:"A scanner worker thread crashed, usually due to a problematic file or low memory.",fix:"Run with `--verbose` to identify the file. Increase memory with NODE_OPTIONS. Report persistent crashes to support.",severity:"high"},JSON_PARSE_ERROR:{symptom:"SyntaxError: Unexpected token in JSON at position N",cause:"A JSON configuration file (package.json, tsconfig.json, etc.) has invalid syntax.",fix:"Validate the JSON file. Common issues: trailing commas, missing quotes, comments in JSON (not allowed).",severity:"medium"},NETWORK_OFFLINE:{symptom:"Network request failed: getaddrinfo ENOTFOUND",cause:"No internet connection. Required for activation, updates, and webhook notifications.",fix:"Check your internet connection. Thuban scanning works fully offline — only activation and notifications need network.",severity:"low"},CONCURRENT_SCAN:{symptom:"Another scan is already running for this project",cause:"A previous scan process is still active or a lock file was not cleaned up.",fix:"Wait for the current scan to finish. If it crashed, delete .thuban/.lock in the project directory.",severity:"medium"},GLOB_PATTERN_ERROR:{symptom:"Invalid glob pattern in ignore configuration",cause:"The .thubanignore file or --ignore flag contains an invalid glob pattern.",fix:"Check your .thubanignore syntax. Use standard glob patterns: `*.log`, `dist/**`, `**/test/**`. Avoid regex syntax.",severity:"low"},PASSPORT_GENERATION_FAILED:{symptom:"Failed to generate code passport",cause:"Insufficient scan data or the project has no analyzable files.",fix:"Run a full scan first: `thuban scan .`. Ensure the project has supported source files. Check scan results for errors.",severity:"medium"},INVESTOR_REPORT_NO_DATA:{symptom:"Cannot generate investor report: insufficient scan data",cause:"The investor report requires a complete scan with history data.",fix:"Run at least two scans over time to generate trend data. Create a baseline first: `thuban baseline . --create`.",severity:"medium"},CI_TOKEN_MISSING:{symptom:"CI mode requires THUBAN_TOKEN environment variable",cause:"Running in CI/CD without providing an authentication token.",fix:"Set the THUBAN_TOKEN environment variable in your CI config. Generate a token with `thuban activate --ci`.",severity:"high"},CLONE_DETECTION_OOM:{symptom:"Clone detection ran out of memory on large codebase",cause:"Clone detection is O(n^2) and can exhaust memory on very large projects.",fix:"Run clone detection on subdirectories: `thuban clones src/`. Use `--ignore` to exclude generated code and vendor directories.",severity:"medium"}},diagnostics:[{name:"node-version",description:"Check that Node.js is installed and meets the minimum version requirement (>= 16.0.0).",command:"node --version",validator:"Parse semver output; major version must be >= 16.",fixIfFailed:"Install or upgrade Node.js from https://nodejs.org. Use nvm for version management."},{name:"npm-version",description:"Check that npm is installed and accessible.",command:"npm --version",validator:"Any valid semver output indicates npm is available.",fixIfFailed:"npm ships with Node.js. Reinstall Node.js from https://nodejs.org if npm is missing."},{name:"git-available",description:"Check that Git is installed (required for diff, gate, and history commands).",command:"git --version",validator:'Any output containing "git version" indicates success.',fixIfFailed:'Install Git from https://git-scm.com. On Windows, select "Git from the command line" during setup. Restart terminal after install.'},{name:"thuban-version",description:"Check that Thuban is installed and report the current version.",command:"thuban --version",validator:"Any valid semver output. Compare against latest known version.",fixIfFailed:"Install Thuban: `npm install -g thuban`. If installed but not found, check your PATH."},{name:"disk-space",description:"Check available disk space on the current drive.",command:"Platform-specific: Windows `wmic logicaldisk get freespace`, macOS/Linux `df -h .`",validator:"At least 500 MB free space recommended for scan output and history.",fixIfFailed:"Free up disk space. Clear old scan history with `thuban history --clear`. Remove unused node_modules."},{name:"path-spaces",description:"Check if the current working directory path contains spaces (common source of errors on Windows).",command:"Inspect process.cwd() for space characters.",validator:"Path should not contain spaces for maximum compatibility.",fixIfFailed:"Move the project to a path without spaces, or always wrap paths in double quotes."},{name:"node-modules",description:"Check if node_modules directory exists in the target project.",command:"Check for existence of ./node_modules directory.",validator:"Directory exists (for Node.js projects). Not required for non-Node projects.",fixIfFailed:"Run `npm install` in the project directory to install dependencies."},{name:"package-json",description:"Check if the target directory contains a package.json file.",command:"Check for existence of ./package.json.",validator:"File exists and is valid JSON.",fixIfFailed:"Run `npm init -y` to create a package.json, or verify you are in the correct project directory."},{name:"write-permissions",description:"Check if Thuban can write to the output directory (.thuban/).",command:"Attempt to create/write a temp file in .thuban/ directory.",validator:"File creation succeeds without EACCES or EPERM.",fixIfFailed:"Fix directory permissions. On Windows, run as Administrator. On macOS/Linux, check ownership with `ls -la`."},{name:"os-info",description:"Report operating system, architecture, and Node.js version for diagnostics.",command:"Read process.platform, process.arch, process.version.",validator:"Informational — always passes. Used for support context.",fixIfFailed:"N/A — this check is informational only."},{name:"file-watcher-limit",description:"Check OS file watcher limit (important for monitor mode on Linux).",command:"Linux: `cat /proc/sys/fs/inotify/max_user_watches`. macOS/Windows: N/A.",validator:"Linux: value should be >= 65536 for medium projects, >= 524288 for large projects.",fixIfFailed:"Linux: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`."},{name:"global-install-path",description:"Verify the npm global bin directory is in the system PATH.",command:"npm config get prefix",validator:"The prefix/bin directory should appear in the PATH environment variable.",fixIfFailed:"Add the npm global bin directory to your PATH. On Windows: System Properties → Environment Variables → PATH."}],faq:[{question:"How do I install Thuban?",answer:"Run `npm install -g thuban` to install globally. Requires Node.js 16 or later. On macOS/Linux you may need `sudo`. On Windows, run your terminal as Administrator if you get permission errors.",keywords:["install","setup","npm","getting started"],category:"installation"},{question:"How do I ignore node_modules?",answer:"Thuban ignores node_modules by default. To customize ignores, create a .thubanignore file in your project root (same syntax as .gitignore), or use the `--ignore` flag: `thuban scan . --ignore node_modules,dist,build`.",keywords:["ignore","node_modules","exclude","skip"],category:"configuration"},{question:"What languages does Thuban support?",answer:"Thuban deeply analyzes 12 languages: JavaScript, TypeScript, Python, Go, Rust, Java, Kotlin, C#, PHP, Ruby, Swift, and Dart. Each language has a dedicated parser for accurate AST-level analysis. Other file types are skipped during scanning.",keywords:["languages","support","javascript","python","typescript","go","rust","java"],category:"scanning"},{question:"What does the trust score mean?",answer:"The trust score (0-100) measures overall code health across multiple dimensions: dependency freshness, code quality patterns, security posture, test coverage signals, documentation presence, and AI-generated code detection. Higher is better. Scores above 80 are considered healthy.",keywords:["trust","score","rating","health","meaning"],category:"results"},{question:"How do I run Thuban in CI/CD?",answer:"Add `npx thuban scan . --ci --exit-code` to your CI pipeline. The `--ci` flag outputs machine-readable results and `--exit-code` returns non-zero if the trust score is below the threshold (default 70, configurable with `--threshold`). Set THUBAN_TOKEN env var for authentication.",keywords:["ci","cd","pipeline","github actions","jenkins","continuous integration"],category:"configuration"},{question:"How do I use Thuban with VS Code?",answer:"Install the Thuban VS Code extension from the marketplace, or use the MCP server integration. The extension shows inline trust scores, highlights issues, and provides quick-fix suggestions. Run `thuban mcp-server` to start the MCP server for editor integration.",keywords:["vscode","vs code","editor","extension","ide"],category:"configuration"},{question:"How do I use the MCP server?",answer:"Run `thuban mcp-server` to start the Model Context Protocol server. Configure your AI editor (Cursor, VS Code, etc.) to connect to it. The MCP server provides real-time code analysis, trust scores, and suggestions directly in your editor.",keywords:["mcp","model context protocol","server","cursor","editor"],category:"configuration"},{question:"What is Mother Code DNA?",answer:"Mother Code DNA is Thuban's analysis of the foundational patterns in your codebase — the core architectural decisions, coding conventions, and structural patterns that define your project's identity. It helps detect drift from established patterns and identifies when AI-generated code diverges from your project's DNA.",keywords:["mother code","dna","patterns","architecture","identity"],category:"results"},{question:"What is Crucible?",answer:"Crucible is Thuban's deep analysis mode that stress-tests your code against advanced scenarios: edge cases, security vulnerabilities, performance bottlenecks, and architectural weaknesses. Run it with `thuban crucible .`. It takes longer than a standard scan but provides deeper insights.",keywords:["crucible","deep analysis","stress test","advanced"],category:"scanning"},{question:"How do I set up always-on monitoring?",answer:"Run `thuban monitor . --frequency=daily` to start continuous monitoring. Supported frequencies: hourly, daily, weekly. View results with `thuban history .` and trends with `thuban trend .`. Add `--notify=slack` or `--notify=webhook` for alerts.",keywords:["monitor","always-on","continuous","watching","automatic"],category:"monitor"},{question:"How do I view scan history?",answer:"Run `thuban history .` to see all previous scan results for the current project. History is stored locally in the .thuban/ directory. Use `thuban trend .` to see score trends over time.",keywords:["history","previous","past","scans","results"],category:"results"},{question:"How do I generate a PDF report?",answer:"Run `thuban executive .` to generate a rich HTML executive report. Open it in your browser and use Ctrl+P (Cmd+P on Mac) to print/save as PDF. The executive report includes charts, scores, and actionable recommendations.",keywords:["pdf","report","export","print","executive"],category:"reports"},{question:"How do I scan only changed files?",answer:"Run `thuban diff .` to scan only files that have changed since the last git commit. This is much faster than a full scan and is ideal for pre-commit checks. Requires git.",keywords:["diff","changed","modified","delta","incremental"],category:"scanning"},{question:"What is hallucination detection?",answer:"Thuban's hallucination detection identifies code that appears to be AI-generated but references non-existent APIs, packages, or patterns. This catches cases where an AI model \"hallucinated\" a function or library that doesn't exist. Run `thuban hallucinate .` for a focused hallucination scan.",keywords:["hallucination","ai","generated","fake","non-existent","detection"],category:"scanning"},{question:"How do I set a baseline?",answer:"Run `thuban baseline . --create` to snapshot the current state of your codebase. Future scans can compare against this baseline to show improvements or regressions. Use `thuban compare . --baseline` to see the diff.",keywords:["baseline","snapshot","benchmark","compare","reference"],category:"scanning"},{question:"Does Thuban send my code anywhere?",answer:"No. Thuban runs 100% locally on your machine. Your source code never leaves your computer. No code is uploaded, transmitted, or shared. The only network calls are for license activation and optional webhook notifications.",keywords:["privacy","security","data","upload","send","local","offline"],category:"security"},{question:"How much does Thuban cost?",answer:"Thuban has four tiers: Free (£0 — one-off checkups, up to 100 files), Pro (£29/month — unlimited scans, monitoring, auto-fix, dashboards), Team (£99/month — CI integration, team dashboards, up to 10 users), and Enterprise (£299/month — SSO, audit logs, priority support, unlimited users). All tiers include local-only scanning.",keywords:["price","cost","pricing","free","pro","team","enterprise","plan"],category:"pricing"},{question:"How do I scan a private repo?",answer:"Clone the repository locally with `git clone`, then run `thuban scan .` in the cloned directory. Thuban scans local files only — it never accesses remote repositories directly. Your code stays on your machine.",keywords:["private","repo","repository","clone","local"],category:"scanning"},{question:"How do I fix issues automatically?",answer:"Run `thuban fix . --fix` to automatically apply safe fixes for detected issues. Thuban will fix formatting, remove unused imports, update deprecated patterns, and apply security patches where safe. Always review changes with `git diff` after auto-fixing.",keywords:["fix","auto","automatic","repair","resolve"],category:"scanning"},{question:"What is the pre-commit gate?",answer:"The gate command (`thuban gate --fix`) runs as a pre-commit hook to prevent problematic code from being committed. It scans changed files, applies auto-fixes, and blocks the commit if the trust score drops below the threshold. Install with `thuban gate --install`.",keywords:["gate","pre-commit","hook","block","prevent","commit"],category:"configuration"},{question:"How do I get an investor report?",answer:"Run `thuban investor-report .` to generate a comprehensive report designed for investors and stakeholders. It includes code health trends, risk assessment, technical debt analysis, and team velocity metrics. Requires scan history for trend data.",keywords:["investor","report","stakeholder","due diligence","assessment"],category:"reports"},{question:"How do I update Thuban?",answer:"Run `npm install -g thuban@latest` to update to the latest version. Check your current version with `thuban --version`. Run `thuban upgrade` for guided upgrade with changelog.",keywords:["update","upgrade","latest","version","new"],category:"installation"},{question:"How do I configure Thuban?",answer:"Create a .thubanrc file (JSON) or thuban.config.js file in your project root. You can set ignore patterns, thresholds, output format, and more. Run `thuban scan . --verbose` to see the active configuration.",keywords:["config","configure","settings","options","thubanrc"],category:"configuration"},{question:"What is the dependency health check?",answer:"Run `thuban deps .` to analyze your project's dependencies for known vulnerabilities, outdated packages, abandoned projects, and license issues. It checks npm, PyPI, and other registries for the latest information.",keywords:["deps","dependencies","packages","vulnerabilities","outdated"],category:"scanning"},{question:"How do I detect code clones?",answer:"Run `thuban clones .` to find duplicated code blocks across your project. It detects exact clones, near-clones (renamed variables), and structural clones (same logic, different syntax). Results include similarity percentage and refactoring suggestions.",keywords:["clones","duplicates","copy paste","duplication","similar"],category:"scanning"},{question:"What is the code passport?",answer:"Run `thuban passport .` to generate a portable code health certificate for your project. The passport summarizes trust score, key metrics, and verification status in a shareable format. Useful for open-source projects and vendor assessments.",keywords:["passport","certificate","verification","shareable","badge"],category:"reports"},{question:"How do I check for ghost dependencies?",answer:"Run `thuban ghosts .` to find ghost dependencies — packages that are imported in code but not listed in package.json, or listed in package.json but never imported. Both cases are problematic and should be resolved.",keywords:["ghosts","ghost","phantom","missing","unused","dependencies"],category:"scanning"},{question:"How do I use the dashboard?",answer:"Run `thuban dashboard .` to generate an interactive HTML dashboard with charts, trends, and detailed breakdowns. It opens in your default browser. The dashboard shows trust score history, issue categories, and file-level details.",keywords:["dashboard","visual","charts","graphs","html","browser"],category:"reports"},{question:"How do I check code drift?",answer:"Run `thuban drift .` to detect how much your codebase has drifted from its original patterns (Mother Code DNA). High drift indicates inconsistent coding styles, possibly from multiple AI tools or contributors with different conventions.",keywords:["drift","consistency","patterns","divergence","style"],category:"scanning"},{question:"How do I check for code injection risks?",answer:"Run `thuban inject .` to scan for potential code injection vulnerabilities: SQL injection, XSS, command injection, path traversal, and more. Results include severity ratings and remediation guidance.",keywords:["inject","injection","sql","xss","security","vulnerability"],category:"security"},{question:"Can I use Thuban offline?",answer:"Yes. All scanning, analysis, and reporting works completely offline. The only features requiring internet are: initial installation (npm), license activation, update checks, and webhook notifications. Your code never leaves your machine.",keywords:["offline","internet","network","air-gapped","local"],category:"security"},{question:"How do I get the AI score for my code?",answer:"Run `thuban ai-score .` to get a detailed breakdown of how much of your code appears to be AI-generated, which AI patterns were detected, and confidence levels. This helps teams understand their AI code composition.",keywords:["ai","score","generated","artificial","detection","composition"],category:"scanning"},{question:"How do I estimate AI cost savings?",answer:"Run `thuban cost .` to estimate the development cost of your codebase and how much AI-assisted development has saved. It factors in lines of code, complexity, language, and market rates.",keywords:["cost","savings","estimate","value","money","roi"],category:"reports"},{question:"How do I compare two scans?",answer:"Run `thuban compare . --baseline` to compare the current scan against a saved baseline. Or use `thuban compare . --with=<scan-id>` to compare against any previous scan from history.",keywords:["compare","diff","difference","before","after","regression"],category:"results"},{question:"How do I get support?",answer:"Run `thuban support` for built-in diagnostics and troubleshooting. For human support, email support@thuban.dev or visit https://github.com/thuban-dev/thuban/issues. Enterprise customers have priority support channels.",keywords:["support","help","contact","issue","bug","question"],category:"installation"},{question:"How do I submit feedback?",answer:"Run `thuban feedback` to submit feedback directly from the CLI. You can also open issues at https://github.com/thuban-dev/thuban/issues or email feedback@thuban.dev.",keywords:["feedback","suggestion","feature request","improvement"],category:"installation"},{question:"How do I uninstall Thuban?",answer:"Run `npm uninstall -g thuban` to remove the global installation. To clean up project-level data, delete the .thuban/ directory in your project root. To remove configuration, delete .thubanrc or thuban.config.js.",keywords:["uninstall","remove","delete","cleanup"],category:"installation"},{question:"What is telemetry and can I disable it?",answer:"Thuban collects anonymous usage telemetry (command frequency, scan duration, error rates) to improve the product. No code or file contents are ever collected. Disable with `thuban telemetry --disable`. View current status with `thuban telemetry --status`.",keywords:["telemetry","analytics","tracking","privacy","disable"],category:"security"},{question:"How do I use the watch mode?",answer:"Run `thuban watch .` to start file-watching mode. Thuban will automatically re-scan when files change. This is lighter than monitor mode — it watches for file system events rather than running on a schedule. Press Ctrl+C to stop.",keywords:["watch","live","realtime","auto","file changes"],category:"scanning"},{question:"How do I check my project health?",answer:"Run `thuban health .` for a quick health check that covers dependency freshness, security vulnerabilities, code quality signals, and configuration issues. It is faster than a full scan and gives a high-level overview.",keywords:["health","check","quick","overview","status"],category:"scanning"}],commands:{scan:{usage:"thuban scan <path> [options]",description:"Run a full code analysis scan on the target directory or file. This is the primary command.",examples:["thuban scan .","thuban scan src/","thuban scan . --ignore dist,build","thuban scan . --verbose --output report.json"],flags:["--ignore <dirs>","--verbose","--output <path>","--format <json|text|html>","--timeout <seconds>","--ci","--exit-code","--threshold <number>"],commonIssues:["No files found — check ignore patterns","Timeout on large projects — use --ignore","Permission denied — check file permissions"]},deps:{usage:"thuban deps <path> [options]",description:"Analyze project dependencies for vulnerabilities, outdated packages, and license issues.",examples:["thuban deps .","thuban deps . --verbose"],flags:["--verbose","--format <json|text>"],commonIssues:["No package.json found","Network needed for vulnerability database updates"]},drift:{usage:"thuban drift <path> [options]",description:"Detect code pattern drift from the project's Mother Code DNA baseline.",examples:["thuban drift .","thuban drift src/ --threshold 20"],flags:["--threshold <percent>","--verbose"],commonIssues:["Needs baseline — run thuban baseline . --create first","High drift in new projects is normal"]},inject:{usage:"thuban inject <path> [options]",description:"Scan for code injection vulnerabilities (SQL injection, XSS, command injection, etc.).",examples:["thuban inject .","thuban inject src/api/"],flags:["--severity <critical|high|medium|low>","--verbose","--format <json|text>"],commonIssues:["False positives in template strings — add to .thubanignore","Sanitized inputs may still flag — review manually"]},health:{usage:"thuban health <path> [options]",description:"Quick health check covering dependencies, security, quality, and configuration.",examples:["thuban health ."],flags:["--verbose"],commonIssues:["Requires package.json for dependency checks"]},report:{usage:"thuban report <path> [options]",description:"Generate a detailed scan report in various formats.",examples:["thuban report .","thuban report . --format html --output report.html"],flags:["--format <json|text|html>","--output <path>","--verbose"],commonIssues:["Run a scan first — report uses cached scan data","Write permission needed for output path"]},hallucinate:{usage:"thuban hallucinate <path> [options]",description:"Detect AI hallucinations — references to non-existent APIs, packages, or patterns.",examples:["thuban hallucinate .","thuban hallucinate src/ --verbose"],flags:["--verbose","--format <json|text>"],commonIssues:["May flag legitimate but uncommon APIs — review results manually"]},watch:{usage:"thuban watch <path> [options]",description:"Watch for file changes and automatically re-scan. Lighter than monitor mode.",examples:["thuban watch .","thuban watch src/ --ignore tests"],flags:["--ignore <dirs>","--verbose"],commonIssues:["ENOSPC on Linux — increase inotify watchers","High CPU on large projects — use --ignore"]},monitor:{usage:"thuban monitor <path> [options]",description:"Start always-on scheduled monitoring with configurable frequency and notifications.",examples:["thuban monitor . --frequency=daily","thuban monitor . --frequency=hourly --notify=slack","thuban monitor --stop"],flags:["--frequency <hourly|daily|weekly>","--notify <slack|webhook>","--webhook-url <url>","--stop"],commonIssues:["Process must stay running — use a process manager like pm2","Webhook URL must be accessible"]},history:{usage:"thuban history <path> [options]",description:"View all previous scan results for a project.",examples:["thuban history .","thuban history . --limit 10","thuban history --clear"],flags:["--limit <number>","--format <json|text>","--clear"],commonIssues:["No history — run at least one scan first",".thuban/ directory was deleted"]},trend:{usage:"thuban trend <path> [options]",description:"Show trust score trends over time with visual charts.",examples:["thuban trend .","thuban trend . --days 30"],flags:["--days <number>","--format <json|text|chart>"],commonIssues:["Needs multiple scans over time for meaningful trends"]},dashboard:{usage:"thuban dashboard <path> [options]",description:"Generate and open an interactive HTML dashboard in the browser.",examples:["thuban dashboard .","thuban dashboard . --port 3001"],flags:["--port <number>","--no-open"],commonIssues:["Port already in use — specify different port","Browser does not open — manually navigate to the URL"]},diff:{usage:"thuban diff <path> [options]",description:"Scan only files changed since the last git commit.",examples:["thuban diff .","thuban diff . --base main"],flags:["--base <branch|commit>","--verbose"],commonIssues:["Not a git repository","No changes detected — all files match HEAD"]},fix:{usage:"thuban fix <path> [options]",description:"Automatically apply safe fixes for detected issues.",examples:["thuban fix . --fix","thuban fix . --dry-run"],flags:["--fix","--dry-run","--verbose"],commonIssues:["Always review changes with git diff after auto-fixing","Some issues require manual intervention"]},gate:{usage:"thuban gate [options]",description:"Pre-commit quality gate. Blocks commits if trust score drops below threshold.",examples:["thuban gate --fix","thuban gate --install","thuban gate --threshold 75"],flags:["--fix","--install","--uninstall","--threshold <number>"],commonIssues:["Git hooks not running — check .git/hooks/ permissions","Working tree must be clean"]},cost:{usage:"thuban cost <path> [options]",description:"Estimate development cost and AI-assisted savings for the codebase.",examples:["thuban cost .","thuban cost . --rate 150"],flags:["--rate <hourly-rate>","--currency <USD|EUR|GBP>","--verbose"],commonIssues:["Estimates are approximate — based on industry averages"]},ghosts:{usage:"thuban ghosts <path> [options]",description:"Find ghost dependencies — imported but unlisted, or listed but unused.",examples:["thuban ghosts .","thuban ghosts . --verbose"],flags:["--verbose","--format <json|text>"],commonIssues:["Dynamic imports may not be detected","Peer dependencies may show as ghosts"]},"ai-score":{usage:"thuban ai-score <path> [options]",description:"Analyze AI-generated code composition and confidence levels.",examples:["thuban ai-score .","thuban ai-score src/ --verbose"],flags:["--verbose","--format <json|text>","--threshold <percent>"],commonIssues:["Detection is probabilistic — not 100% accurate","Boilerplate code may flag as AI-generated"]},clones:{usage:"thuban clones <path> [options]",description:"Detect duplicated code blocks (exact, near, and structural clones).",examples:["thuban clones .","thuban clones src/ --min-lines 10"],flags:["--min-lines <number>","--min-similarity <percent>","--verbose"],commonIssues:["Memory intensive on large codebases — scan subdirectories","Generated code may produce many clones"]},passport:{usage:"thuban passport <path> [options]",description:"Generate a portable code health certificate for sharing.",examples:["thuban passport .","thuban passport . --output passport.json"],flags:["--output <path>","--format <json|html>"],commonIssues:["Requires a completed scan","Passport is a snapshot — re-generate after changes"]},executive:{usage:"thuban executive <path> [options]",description:"Generate a rich executive summary report with charts and recommendations.",examples:["thuban executive .","thuban executive . --output exec-report.html"],flags:["--output <path>","--format <html|json>"],commonIssues:["Use Ctrl+P in browser to save as PDF","Requires scan data — run a scan first"]},baseline:{usage:"thuban baseline <path> [options]",description:"Create or manage baseline snapshots for comparison.",examples:["thuban baseline . --create","thuban baseline . --show","thuban baseline . --delete"],flags:["--create","--show","--delete","--name <label>"],commonIssues:["Only one active baseline per project by default","Use --name to manage multiple baselines"]},telemetry:{usage:"thuban telemetry [options]",description:"Manage anonymous usage telemetry settings.",examples:["thuban telemetry --status","thuban telemetry --enable","thuban telemetry --disable"],flags:["--enable","--disable","--status"],commonIssues:["Telemetry is anonymous — no code is ever collected"]},compare:{usage:"thuban compare <path> [options]",description:"Compare current scan results against a baseline or previous scan.",examples:["thuban compare . --baseline","thuban compare . --with=scan-abc123"],flags:["--baseline","--with <scan-id>","--verbose"],commonIssues:["Requires a baseline or scan history","Large diffs may be truncated — use --verbose"]},crucible:{usage:"thuban crucible <path> [options]",description:"Deep analysis mode — stress-tests code for edge cases, security, and performance.",examples:["thuban crucible .","thuban crucible src/ --timeout=600"],flags:["--timeout <seconds>","--verbose","--format <json|text|html>"],commonIssues:["Slow on large codebases — scan subdirectories","May timeout — increase with --timeout"]},activate:{usage:"thuban activate [options]",description:"Activate a Thuban license key for Pro, Team, or Enterprise features.",examples:["thuban activate","thuban activate --key YOUR-LICENSE-KEY","thuban activate --ci"],flags:["--key <license-key>","--ci"],commonIssues:["Requires internet connection","Key is tied to machine — contact support for transfers"]},status:{usage:"thuban status",description:"Show current Thuban installation status, license, and configuration.",examples:["thuban status"],flags:[],commonIssues:[]},upgrade:{usage:"thuban upgrade",description:"Check for and install the latest version of Thuban with changelog.",examples:["thuban upgrade"],flags:["--check"],commonIssues:["Requires internet connection","May need sudo on macOS/Linux"]},support:{usage:"thuban support",description:"Run built-in diagnostics and get troubleshooting help.",examples:["thuban support"],flags:["--verbose"],commonIssues:[]},feedback:{usage:"thuban feedback [message]",description:"Submit feedback or feature requests directly from the CLI.",examples:["thuban feedback",'thuban feedback "Love the new dashboard feature!"'],flags:[],commonIssues:["Requires internet connection to submit"]},"investor-report":{usage:"thuban investor-report <path> [options]",description:"Generate a comprehensive report for investors with health trends, risk assessment, and technical debt analysis.",examples:["thuban investor-report .","thuban investor-report . --output investor.html"],flags:["--output <path>","--format <html|json|pdf>"],commonIssues:["Requires scan history for trend data","Create a baseline first for best results"]}},troubleshooting:{"scan-crashes":{title:"Scan crashes or exits unexpectedly",symptoms:["Unhandled exception error","Process exits with non-zero code","Scanner worker thread crash"],steps:["Run with --verbose flag to identify the problematic file: `thuban scan . --verbose`.","Check Node.js version: `node --version` (must be >= 16).",'Increase memory if needed: `NODE_OPTIONS="--max-old-space-size=4096" thuban scan .`.',"Add problematic files to .thubanignore.","Update Thuban to latest: `npm install -g thuban@latest`.","If the issue persists, report it with the verbose output at https://github.com/thuban-dev/thuban/issues."]},"empty-results":{title:"Scan returns no results or empty report",symptoms:["No files found message","Report has zero issues","Trust score shows N/A"],steps:["Verify the target path exists and contains source files: `ls` or `dir` the directory.","Check .thubanignore — it may be excluding all files.","Run with --verbose to see which files are being scanned: `thuban scan . --verbose`.","Ensure the project uses a supported language (JS, TS, Python, Go, Rust, Java, Kotlin, C#, PHP, Ruby, Swift, Dart).","Check if the path has special characters or spaces — wrap in quotes.","Try scanning a specific subdirectory: `thuban scan src/`."]},"slow-scan":{title:"Scan is very slow or seems to hang",symptoms:["Scan takes more than 5 minutes","Progress bar stuck","High CPU usage"],steps:["Use --ignore to skip large directories: `thuban scan . --ignore node_modules,dist,build,.git,vendor`.","Check if node_modules is being scanned (should be ignored by default).","Scan a smaller subdirectory first to verify it works: `thuban scan src/`.","Increase timeout: `thuban scan . --timeout=300`.","Check disk I/O — slow disks significantly impact scan speed.","On Windows, add the project to antivirus exclusions."]},"windows-path-errors":{title:"Path-related errors on Windows",symptoms:["ENOENT errors with correct paths","Backslash/forward-slash confusion","Path too long errors"],steps:["Use forward slashes or escaped backslashes in paths.",'Wrap paths with spaces in double quotes: `thuban scan "C:\\My Projects\\app"`.',"Enable long paths: run as Admin `reg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f`.","Move the project to a shorter path (e.g., C:\\dev\\project).","Avoid special characters in directory names (parentheses, brackets, etc.).","Restart the terminal after making PATH changes."]},"permission-denied":{title:"Permission denied errors",symptoms:["EACCES error","EPERM error","Access is denied (Windows)"],steps:["On Windows: right-click terminal → Run as Administrator.","On macOS/Linux: use `sudo thuban scan .` or fix ownership with `sudo chown -R $USER:$USER .`.","Check if files are locked by another process (editor, antivirus).","On Windows, check folder security properties (right-click → Properties → Security).","Ensure the .thuban/ output directory is writable.","If using a network drive, check network share permissions."]},"npm-install-fails":{title:"Cannot install Thuban via npm",symptoms:["npm ERR! during install","Permission errors","Network errors","Engine compatibility errors"],steps:["Check Node.js version: `node --version` (must be >= 16).","Try with sudo (macOS/Linux): `sudo npm install -g thuban`.","On Windows, run terminal as Administrator.","If behind a proxy: `npm config set proxy http://proxy:port`.","Clear npm cache: `npm cache clean --force`.","Try with legacy peer deps: `npm install -g thuban --legacy-peer-deps`.","As a last resort, download the tarball manually from npmjs.com."]},"command-not-found":{title:"thuban command not recognized",symptoms:["command not found","not recognized as internal or external command","No such file or directory"],steps:["Verify Thuban is installed: `npm list -g thuban`.","Find npm global bin path: `npm config get prefix`.","Add the bin directory to your PATH environment variable.","On Windows: System Properties → Advanced → Environment Variables → PATH.",'On macOS/Linux: add `export PATH="$(npm config get prefix)/bin:$PATH"` to ~/.bashrc or ~/.zshrc.',"Restart your terminal after changing PATH.","Alternative: use `npx thuban scan .` without global install."]},"wrong-node-version":{title:"Node.js version is too old",symptoms:["SyntaxError: Unexpected token","Engine compatibility error","Optional chaining not supported"],steps:["Check current version: `node --version`.","Thuban requires Node.js >= 16.0.0.","Download latest LTS from https://nodejs.org.","Or use nvm (macOS/Linux): `nvm install 18 && nvm use 18`.","Or use nvm-windows: `nvm install 18 && nvm use 18`.","After upgrading, reinstall Thuban: `npm install -g thuban`."]},"monitor-not-starting":{title:"Monitor mode fails to start or stops immediately",symptoms:["Monitor exits after starting","No scans are being recorded","Frequency not working"],steps:["Ensure the target path exists and is a valid project directory.","Check that no other monitor instance is running for the same project.","Delete .thuban/.lock if a previous monitor crashed.","Verify disk space — monitor stores results in .thuban/ directory.","On Linux, increase inotify watchers if using file-watch mode.",'Use a process manager like pm2 for persistent monitoring: `pm2 start "thuban monitor . --frequency=daily"`.',"Check logs in .thuban/monitor.log for error details."]},"git-hook-not-working":{title:"Pre-commit gate hook not triggering",symptoms:["Commits go through without gate check","Hook file missing","Permission denied on hook"],steps:["Reinstall the hook: `thuban gate --install`.","Check that .git/hooks/pre-commit exists and is executable.","On macOS/Linux: `chmod +x .git/hooks/pre-commit`.","Verify the hook file contains the thuban gate command.","Check if another tool (husky, lint-staged) is overriding the hook.","If using husky, add `thuban gate --fix` to your husky pre-commit config instead."]},"vscode-not-detecting":{title:"VS Code extension or MCP server not working",symptoms:["No inline annotations","Extension not activating","MCP connection refused"],steps:["Verify Thuban is installed globally: `thuban --version`.","For MCP: start the server manually: `thuban mcp-server`.","Check VS Code output panel for Thuban extension logs.",'Reload VS Code window: Ctrl+Shift+P → "Reload Window".',"Check MCP client configuration in your editor settings.","Ensure the project has been scanned at least once: `thuban scan .`.","Reinstall the VS Code extension if issues persist."]},"false-positives":{title:"Too many false positive detections",symptoms:["Legitimate code flagged as issues","AI detection on human-written code","Security flags on safe patterns"],steps:["Review each finding carefully — some may be valid issues you were not aware of.","Add known-safe patterns to .thubanignore.","Use inline comments to suppress specific warnings: `// thuban-ignore-next-line`.","Adjust sensitivity thresholds in .thubanrc configuration.","Report persistent false positives at https://github.com/thuban-dev/thuban/issues — this helps improve detection.","Update to the latest version — detection accuracy improves with each release."]},"missing-hallucinations":{title:"Known hallucinations not being detected",symptoms:["AI-generated fake APIs not flagged","Non-existent packages not detected","Low hallucination count on suspicious code"],steps:["Run the dedicated hallucination scan: `thuban hallucinate . --verbose`.","Ensure the file type is supported (check supported languages list).","Hallucination detection works best on JavaScript/TypeScript and Python.","Check that the file is not in .thubanignore.","Update Thuban — hallucination detection improves with each version.","Report missed hallucinations to help improve the detection engine."]},"score-too-low":{title:"Trust score seems unreasonably low",symptoms:["Score below 50 on a well-maintained project","Score dropped suddenly","Score does not match code quality"],steps:["Run `thuban scan . --verbose` to see the score breakdown by category.","Check dependency health — outdated deps heavily impact the score.","Run `thuban deps .` to identify problematic dependencies.","Check for security issues: `thuban inject .`.","Verify that test files are not being counted as production code.","Create a baseline and track improvements: `thuban baseline . --create`.","A low score is not necessarily bad — it highlights areas for improvement."]},"score-too-high":{title:"Trust score seems unreasonably high",symptoms:["Score above 95 on a project with known issues","Score does not reflect actual quality"],steps:["Run a crucible deep analysis for more thorough checking: `thuban crucible .`.","Check if most files are being ignored — run with --verbose.","Verify that the scan covered all relevant directories.","A high score on a small project with few files is normal.","Run `thuban inject .` separately to check for security issues.","Check hallucination detection: `thuban hallucinate .`."]},"report-not-generating":{title:"Report fails to generate or is empty",symptoms:["Error writing report","Empty HTML file","Report command fails"],steps:["Run a scan first: `thuban scan .` — reports use cached scan data.","Check write permissions on the output directory.","Specify an explicit output path: `thuban report . --output ./report.html`.","Try a different format: `thuban report . --format json`.","Check disk space — reports can be large for big projects.","Run with --verbose for detailed error information."]},"dashboard-empty":{title:"Dashboard shows no data or fails to load",symptoms:["Blank dashboard page","No charts or metrics","Connection refused"],steps:["Run at least one scan first: `thuban scan .`.","Check that the .thuban/ directory exists and contains scan data.","Try a different port: `thuban dashboard . --port 3001`.","Check browser console for JavaScript errors.","Clear browser cache and reload.","Verify that no firewall is blocking the local port."]},"diff-no-changes":{title:"Diff command shows no changes",symptoms:["No changed files detected","Diff returns empty results"],steps:["Verify you have uncommitted changes: `git status`.","Check the base reference: `thuban diff . --base main`.","Ensure changed files are in supported languages.","Check that changed files are not in .thubanignore.","Try a full scan instead: `thuban scan .`.","Verify git is working: `git diff --name-only`."]},"ci-integration-failing":{title:"CI/CD pipeline integration failing",symptoms:["THUBAN_TOKEN not set","Exit code issues","Scan fails in CI environment"],steps:["Set the THUBAN_TOKEN environment variable in your CI config.","Generate a CI token: `thuban activate --ci`.","Use npx for CI: `npx thuban scan . --ci --exit-code`.","Ensure Node.js >= 16 is available in the CI environment.","Check that the CI runner has read access to the source code.","Set appropriate timeout for CI: `--timeout=300`.","Use `--threshold` to set the minimum acceptable trust score.","Check CI runner logs for detailed error output."]},"mcp-not-connecting":{title:"MCP server not connecting to editor",symptoms:["Connection refused","Editor cannot find MCP server","MCP features not available"],steps:["Start the MCP server: `thuban mcp-server`.","Check that the server is running: look for the startup message with the port/socket.","Verify your editor's MCP client configuration matches the server address.","Check for port conflicts: try a different port.","Ensure Thuban is installed globally (not just locally).","Restart your editor after starting the MCP server.","Check firewall settings — the MCP server uses a local port.","Review editor-specific MCP setup documentation."]}},monitorGuide:{overview:"Thuban Monitor provides always-on, scheduled code scanning that tracks your project's health over time. It runs scans at configurable intervals and stores results locally for trend analysis.",starting:{command:"thuban monitor <path> --frequency=<hourly|daily|weekly>",examples:["thuban monitor . --frequency=hourly","thuban monitor . --frequency=daily","thuban monitor . --frequency=weekly","thuban monitor /path/to/project --frequency=daily --notify=slack"],description:"Start monitoring by specifying the project path and scan frequency. The process runs in the foreground by default."},frequencies:{hourly:"Scans every hour. Best for active development with multiple contributors. Higher disk usage.",daily:"Scans once per day (default at midnight local time). Recommended for most projects.",weekly:"Scans once per week (default Sunday midnight). Best for stable or low-activity projects."},viewingResults:{history:{command:"thuban history .",description:"View all recorded scan results with timestamps, trust scores, and issue counts."},trends:{command:"thuban trend .",description:"Visualize trust score trends over time. Shows improvement or regression patterns."},dashboard:{command:"thuban dashboard .",description:"Open an interactive HTML dashboard with charts, breakdowns, and detailed metrics."}},notifications:{slack:{flag:"--notify=slack",setup:'Configure the Slack webhook URL in .thubanrc: `{ "notifications": { "slack": "https://hooks.slack.com/services/..." } }`.',description:"Sends a summary to Slack after each scan with trust score, issue count, and trend direction."},webhook:{flag:"--notify=webhook --webhook-url=<url>",setup:'Provide the webhook URL via flag or in .thubanrc: `{ "notifications": { "webhook": "https://your-server.com/thuban-hook" } }`.',description:"Sends a JSON payload to any HTTP endpoint after each scan. Useful for custom integrations."}},storage:{location:"All monitor data is stored locally in the .thuban/ directory within the project root.",contents:"Scan results (JSON), trend data, configuration, and logs.",cleanup:"Old results are automatically pruned after 90 days. Manual cleanup: `thuban history --clear`.",gitignore:"Add .thuban/ to your .gitignore to avoid committing scan data to version control."},stopping:{interactive:"Press Ctrl+C to stop the monitor process.",command:"thuban monitor --stop",description:"Stops any running monitor instance for the current project. Safe to run even if no monitor is active."},persistentRunning:{description:"For production monitoring, use a process manager to keep the monitor running across reboots.",examples:['pm2 start "thuban monitor . --frequency=daily" --name thuban-monitor',"systemd service (Linux): create a unit file pointing to the thuban monitor command","Windows Task Scheduler: create a scheduled task running thuban monitor"]},bestPractices:["Start with daily frequency and adjust based on your team's needs.","Set up Slack or webhook notifications to stay informed without checking manually.","Use `thuban trend .` weekly to review progress.","Create a baseline before starting monitoring: `thuban baseline . --create`.","Add .thuban/ to .gitignore to keep scan data out of version control.","Use `thuban dashboard .` for team reviews and stakeholder updates.","Combine with `thuban gate --install` for pre-commit checks alongside monitoring."]},patterns:[{regex:/module not found/i,category:"error",response:"errors.MODULE_NOT_FOUND"},{regex:/cannot find module/i,category:"error",response:"errors.MODULE_NOT_FOUND"},{regex:/ENOENT/i,category:"error",response:"errors.ENOENT"},{regex:/no such file or directory/i,category:"error",response:"errors.ENOENT"},{regex:/permission denied/i,category:"error",response:"errors.EACCES"},{regex:/EACCES/i,category:"error",response:"errors.EACCES"},{regex:/EPERM/i,category:"error",response:"errors.EPERM"},{regex:/operation not permitted/i,category:"error",response:"errors.EPERM"},{regex:/shebang/i,category:"error",response:"errors.SHEBANG_ERROR"},{regex:/babel.*parse|syntax\s*error.*unexpected token/i,category:"error",response:"errors.BABEL_PARSE_ERROR"},{regex:/timed?\s*out/i,category:"error",response:"errors.TIMEOUT"},{regex:/out of memory|heap/i,category:"error",response:"errors.OUT_OF_MEMORY"},{regex:/git.*not (found|installed)/i,category:"error",response:"errors.GIT_NOT_FOUND"},{regex:/no (scannable )?files found/i,category:"error",response:"errors.NO_FILES_FOUND"},{regex:/invalid.*license|license.*invalid/i,category:"error",response:"errors.INVALID_LICENSE"},{regex:/CRLF|line ending/i,category:"error",response:"errors.CRLF_MISMATCH"},{regex:/not recognized.*command|command not found/i,category:"error",response:"errors.PATH_NOT_FOUND"},{regex:/how.*install/i,category:"faq",response:"faq.installation.install"},{regex:/what.*language/i,category:"faq",response:"faq.scanning.languages"},{regex:/what.*(trust|health).*score/i,category:"faq",response:"faq.results.trustScore"},{regex:/how.*ignore/i,category:"faq",response:"faq.configuration.ignore"},{regex:/how.*ci\/?cd|continuous integration/i,category:"faq",response:"faq.configuration.cicd"},{regex:/how.*monitor/i,category:"faq",response:"faq.monitor.setup"},{regex:/how.*pdf|generate.*report/i,category:"faq",response:"faq.reports.pdf"},{regex:/how much.*cost|pricing|free/i,category:"faq",response:"faq.pricing.tiers"},{regex:/send.*code|privacy|data.*safe/i,category:"faq",response:"faq.security.privacy"},{regex:/what.*hallucination/i,category:"faq",response:"faq.scanning.hallucination"},{regex:/what.*crucible/i,category:"faq",response:"faq.scanning.crucible"},{regex:/what.*mother.*code|what.*dna/i,category:"faq",response:"faq.results.motherCodeDna"},{regex:/what.*mcp|model context/i,category:"faq",response:"faq.configuration.mcp"},{regex:/how.*baseline/i,category:"faq",response:"faq.scanning.baseline"},{regex:/how.*diff|changed files/i,category:"faq",response:"faq.scanning.diff"},{regex:/how.*fix.*auto/i,category:"faq",response:"faq.scanning.autofix"},{regex:/how.*gate|pre-?commit/i,category:"faq",response:"faq.configuration.gate"},{regex:/how.*investor/i,category:"faq",response:"faq.reports.investor"},{regex:/how.*update|how.*upgrade/i,category:"faq",response:"faq.installation.update"},{regex:/how.*uninstall|how.*remove/i,category:"faq",response:"faq.installation.uninstall"},{regex:/offline|without internet/i,category:"faq",response:"faq.security.offline"},{regex:/too many open files|EMFILE/i,category:"error",response:"errors.EMFILE"},{regex:/no space|ENOSPC/i,category:"error",response:"errors.ENOSPC"}]};
@@ -1 +1 @@
1
- "use strict";const parser=require("@babel/parser"),path=require("path"),fs=require("fs"),{joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_TAINT_FILE_SIZE=2e4,MAX_FIXPOINT_ITERATIONS=10;function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const i=e[n];if(Array.isArray(i))for(const e of i)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else i&&"object"==typeof i&&"string"==typeof i.type&&walk(i,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkTaintIssue(e,t,n,i){const r={file:e,line:t,category:"security",type:i.type,severity:i.severity||"critical",id:i.id,name:i.name,message:i.message,code:snippet(n,t),source:"taint",taintPath:i.taintPath};return i.crossFile&&(r.crossFile=!0,r.sourceFile=i.sourceFile),r}function fileTooLargeIssue(e,t){return{file:e,line:0,category:"quality",type:"file_too_large",severity:"info",id:"TAINT003",name:"Taint Analysis Skipped",message:`File too large (${t} chars) - taint analysis skipped (cap: 20000 chars)`,code:"",source:"taint"}}const SANITIZER_FUNCS=new Set(["parseInt","parseFloat","Number","Boolean","encodeURIComponent","encodeURI","escape","sanitize","sanitizeHtml","stripTags"]),SANITIZER_MEMBER=new Set(["sanitize","escape","escapeHtml","stripTags","clean"]),SANITIZER_RECEIVER_NAMES=new Set(["dompurify","sanitizehtml","sanitize-html","html","xss","validator","bleach","markupsafe","purify","sqlstring","sanitizer","striptags","xssfilters","insane"]);function hasKnownSanitizerReceiver(e){let t=e,n=0;for(;t&&n++<20;){if("Identifier"===t.type)return SANITIZER_RECEIVER_NAMES.has(t.name.toLowerCase());if("MemberExpression"!==t.type)return!1;t=t.object}return!1}const JS_SOURCE_PATH_PATTERNS=[{re:/^req\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^request\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^process\.argv/,severity:"critical"},{re:/^process\.env/,severity:"warning"},{re:/^document\.location/,severity:"critical"},{re:/^window\.location/,severity:"critical"},{re:/^location\.(search|hash|href)/,severity:"critical"}];function memberChainToPath(e){const t=[];let n=e,i=0;for(;n&&i++<20;){if("Identifier"===n.type){t.unshift(n.name);break}if("ThisExpression"===n.type){t.unshift("this");break}if("MemberExpression"!==n.type)return null;n.computed||"Identifier"!==n.property.type?t.unshift("*"):t.unshift(n.property.name),n=n.object}return t.length?t.join("."):null}function jsSourceSeverity(e){if(!e)return null;if("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name)return"critical";if("NewExpression"===e.type&&"Identifier"===e.callee.type&&("URLSearchParams"===e.callee.name||"FormData"===e.callee.name))return"critical";const t=memberChainToPath(e);if(t)for(const e of JS_SOURCE_PATH_PATTERNS)if(e.re.test(t))return e.severity;return null}function isJSSourceExpr(e){return null!==jsSourceSeverity(e)}function describeJSSource(e){return memberChainToPath(e)||("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name?"prompt()":"NewExpression"===e.type&&"Identifier"===e.callee.type?`new ${e.callee.name}()`:"tainted source")}function isSanitizerCall(e){return!(!e||"CallExpression"!==e.type)&&(!("Identifier"!==e.callee.type||!SANITIZER_FUNCS.has(e.callee.name))||!("MemberExpression"!==e.callee.type||"Identifier"!==e.callee.property.type||!SANITIZER_MEMBER.has(e.callee.property.name))&&hasKnownSanitizerReceiver(e.callee.object))}function isTaintedExpr(e,t,n){if(n=n||0,!e||n>12)return!1;if("Identifier"===e.type)return t.has(e.name);if("CallExpression"===e.type){if(isSanitizerCall(e))return!1;if(isJSSourceExpr(e))return!0;if("MemberExpression"===e.callee.type&&isTaintedExpr(e.callee.object,t,n+1))return!0;for(const i of e.arguments)if(isTaintedExpr(i,t,n+1))return!0;return!1}return"MemberExpression"===e.type?!!isJSSourceExpr(e)||isTaintedExpr(e.object,t,n+1):"NewExpression"===e.type?isJSSourceExpr(e):"BinaryExpression"===e.type&&"+"===e.operator?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):"TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t,n+1)):"AssignmentExpression"===e.type?isTaintedExpr(e.right,t,n+1):"ConditionalExpression"===e.type?isTaintedExpr(e.consequent,t,n+1)||isTaintedExpr(e.alternate,t,n+1):"LogicalExpression"===e.type?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):!("SequenceExpression"!==e.type||!e.expressions.length)&&isTaintedExpr(e.expressions[e.expressions.length-1],t,n+1)}function isDynamicTaintedBuild(e,t){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t)):"BinaryExpression"===e.type&&"+"===e.operator&&(isTaintedExpr(e.left,t)||isTaintedExpr(e.right,t)))}function collectFunctionReturnNodes(e){const t=[];return e.body&&"BlockStatement"===e.body.type?function n(i){if(i&&"object"==typeof i&&"string"==typeof i.type&&(i===e||"FunctionDeclaration"!==i.type&&"FunctionExpression"!==i.type&&"ArrowFunctionExpression"!==i.type)){"ReturnStatement"===i.type&&i.argument&&t.push(i.argument);for(const e in i){if("loc"===e||"range"===e||"start"===e||"end"===e)continue;const t=i[e];if(Array.isArray(t))for(const e of t)e&&"object"==typeof e&&"string"==typeof e.type&&n(e);else t&&"object"==typeof t&&"string"==typeof t.type&&n(t)}}}(e.body):e.body&&t.push(e.body),t}function findTaintOrigin(e,t){let n=null;return walk(e,e=>{if(!n)if("Identifier"===e.type&&t.has(e.name))n=t.get(e.name);else{const t=jsSourceSeverity(e);t&&(n={desc:describeJSSource(e),line:lineOf(e),severity:t})}}),n}function collectTaintedVars(e,t){const n=new Set,i=new Map,r=new Set,a=new Map;if(t)for(const[e,r]of t)n.add(e),i.set(e,r);let s=!0,o=0;function l(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||!r.has(e.callee.name))}for(;s&&o<10;)s=!1,o++,walk(e,e=>{if("FunctionDeclaration"!==e.type&&"FunctionExpression"!==e.type&&"ArrowFunctionExpression"!==e.type)return;const t=e.id&&e.id.name;if(t&&!r.has(t))for(const o of collectFunctionReturnNodes(e))if(isTaintedExpr(o,n)||l(o)){r.add(t),a.set(t,findTaintOrigin(o,i)||(l(o)?a.get(o.callee.name):null)||{desc:describeJSSource(o),line:lineOf(o),severity:jsSourceSeverity(o)||"critical"}),s=!0;break}}),walk(e,e=>{let t=null,r=null;if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.id.type?(t=e.id.name,r=e.init):"AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&(t=e.left.name,r=e.right),!t||n.has(t))return;const o=l(r);if(isTaintedExpr(r,n)||o){n.add(t),s=!0;const l=findTaintOrigin(r,i)||(o?a.get(r.callee.name):null)||{desc:describeJSSource(r),line:lineOf(e),severity:jsSourceSeverity(r)||"critical"};i.set(t,l)}});return{taintedVars:n,origins:i}}const SQL_METHODS=new Set(["query","execute","raw","where"]);function parseJS(e){try{return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return null}}function analyzeJSTaint(e,t,n){const i=[],r=t.split("\n"),a=parseJS(t);if(!a)return i;const{taintedVars:s,origins:o}=collectTaintedVars(a,n),l=new Set;function c(t,n){const a=lineOf(t),s=`${a}:${n.id}`;if(l.has(s))return;l.add(s);const c=findTaintOrigin(t,o);c&&(n=Object.assign({},n,{severity:c.severity||"critical"}),c.crossFile&&(n=Object.assign({},n,{crossFile:!0,sourceFile:c.sourceFile}))),i.push(mkTaintIssue(e,a,r,n))}function p(e){const t=findTaintOrigin(e,o);if(t)return`${t.crossFile?`${t.sourceFile}: `:""}${t.desc} (line ${t.line}) -> sink (line ${lineOf(e)})`}return walk(a,e=>{if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&SQL_METHODS.has(e.callee.property.name)){const t=e.arguments[0];t&&(isDynamicTaintedBuild(t,s)||isTaintedExpr(t,s))&&c(e,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:`Tainted user input flows into .${e.callee.property.name}() without sanitization - SQL injection risk`,taintPath:p(e)})}if("AssignmentExpression"!==e.type||"MemberExpression"!==e.left.type||"Identifier"!==e.left.property.type||"innerHTML"!==e.left.property.name&&"outerHTML"!==e.left.property.name||(isTaintedExpr(e.right,s)||isDynamicTaintedBuild(e.right,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via "+e.left.property.name,message:`Tainted user input flows into ${e.left.property.name} without sanitization - XSS risk`,taintPath:p(e)}),"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"document"===e.callee.object.name&&"Identifier"===e.callee.property.type&&("write"===e.callee.property.name||"writeln"===e.callee.property.name)){const t=e.arguments[0];t&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Tainted XSS via document.${e.callee.property.name}()`,message:`Tainted user input flows into document.${e.callee.property.name}() without sanitization - XSS risk`,taintPath:p(e)})}if("JSXAttribute"===e.type&&e.name&&"dangerouslySetInnerHTML"===e.name.name&&e.value&&"JSXExpressionContainer"===e.value.type){const t=e.value.expression;if(t&&"ObjectExpression"===t.type){const n=t.properties.find(e=>e.key&&("__html"===e.key.name||"__html"===e.key.value));n&&(isTaintedExpr(n.value,s)||isDynamicTaintedBuild(n.value,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via dangerouslySetInnerHTML",message:"Tainted user input flows into dangerouslySetInnerHTML without sanitization - XSS risk",taintPath:p(e)})}}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&/^res(ponse)?$/.test(e.callee.object.name)&&"Identifier"===e.callee.property.type&&("send"===e.callee.property.name||"write"===e.callee.property.name)){const t=e.arguments[0];t&&"ObjectExpression"!==t.type&&"ArrayExpression"!==t.type&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Reflected XSS via ${e.callee.object.name}.${e.callee.property.name}()`,message:`Tainted user input is reflected via ${e.callee.object.name}.${e.callee.property.name}() without sanitization - reflected XSS risk`,taintPath:p(e)})}}),i}const PY_SOURCE_PATTERNS=[/\brequest\.args\b/,/\brequest\.form\b/,/\brequest\.json\b/,/\brequest\.data\b/,/\brequest\.GET\b/,/\brequest\.POST\b/,/\bsys\.argv\b/,/\bos\.environ\b/,/\binput\s*\(/],PY_SANITIZER_WRAP_RE=/^\s*(int|float|re\.escape|html\.escape|escape|markupsafe\.escape|bleach\.clean)\s*\(/;function describePySource(e){for(const t of PY_SOURCE_PATTERNS){const n=e.match(t);if(n)return n[0]}return"tainted source"}function isPySourceText(e){return PY_SOURCE_PATTERNS.some(t=>t.test(e))}function isDynamicPyText(e){return isDynamicPyString(e)}function collectPyTaintedVars(e){const t=new Set,n=new Map;let i=!0,r=0;const a=/^\s*([A-Za-z_]\w*)\s*=(?!=)\s*([\s\S]+?)\s*$/;for(;i&&r<10;){i=!1,r++;for(const r of e){const e=r.text.split("\n")[0].match(a)||r.text.match(a);if(!e)continue;const s=e[1],o=r.text.slice(r.text.indexOf("=")+1);if(PY_SANITIZER_WRAP_RE.test(o.trim())){t.has(s)&&(t.delete(s),n.delete(s),i=!0);continue}if(t.has(s))continue;let l=!1,c=null;if(isPySourceText(o))l=!0,c=describePySource(o);else for(const e of t)if(new RegExp("\\b"+e+"\\b").test(o)){l=!0,c=n.has(e)?n.get(e).desc:e;break}l&&(t.add(s),n.set(s,{desc:c,line:r.startLine}),i=!0)}}return{taintedVars:t,origins:n}}function textReferencesTaint(e,t){if(isPySourceText(e))return!0;for(const n of t)if(new RegExp("\\b"+n+"\\b").test(e))return!0;return!1}function analyzePythonTaint(e,t){const n=[],i=t.split("\n"),r=joinLogicalStatements(t),{taintedVars:a,origins:s}=collectPyTaintedVars(r),o=new Set;function l(t,r){const a=`${t}:${r.id}`;o.has(a)||(o.add(a),n.push(mkTaintIssue(e,t,i,r)))}function c(e,t){for(const n of a)if(new RegExp("\\b"+n+"\\b").test(e)&&s.has(n)){const e=s.get(n);return`${e.desc} (line ${e.line}) -> sink (line ${t})`}if(isPySourceText(e))return`${describePySource(e)} -> sink (line ${t})`}for(const e of r){const t=e.text,n=e.startLine,i=t.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(i){const e=t.indexOf("(",i.index),r=extractBalanced(t,e),s=splitTopLevelArgs(r)[0]||"";isDynamicPyText(s)&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:"Tainted user input flows into .execute() without sanitization - SQL injection risk",taintPath:c(s,n)})}const r=t.match(/\.(raw|extra)\s*\(|\bRawSQL\s*\(/);if(r){const e=t.indexOf("(",r.index),i=extractBalanced(t,e),s=splitTopLevelArgs(i)[0]||"";(isDynamicPyText(s)||isPySourceText(s))&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (Django)",message:"Tainted user input flows into raw SQL construct without sanitization - SQL injection risk",taintPath:c(s,n)})}const s=t.match(/\btext\s*\(/);if(s){const e=t.indexOf("(",s.index),i=extractBalanced(t,e);isDynamicPyText(i)&&textReferencesTaint(i,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (SQLAlchemy text())",message:"Tainted user input flows into SQLAlchemy text() without sanitization - SQL injection risk",taintPath:c(i,n)})}const o=t.match(/\bmark_safe\s*\(/);if(o){const e=t.indexOf("(",o.index),i=extractBalanced(t,e);textReferencesTaint(i,a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via mark_safe()",message:"Tainted user input flows into mark_safe() without sanitization - XSS risk",taintPath:c(i,n)})}const p=t.match(/\{\{\s*([A-Za-z_][\w.]*)[^}]*\|\s*safe\b/);p&&textReferencesTaint(p[1],a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via Jinja2 |safe filter",message:"Tainted user input is rendered with the |safe filter without sanitization - XSS risk",taintPath:c(p[1],n)})}return n}const JS_RESOLVE_EXTS=["",".js",".jsx",".ts",".tsx",".mjs",".cjs",path.sep+"index.js",path.sep+"index.ts",path.sep+"index.jsx",path.sep+"index.tsx"];function resolveImportPath(e,t,n){if(!t||!t.startsWith(".")&&!path.isAbsolute(t))return null;const i=path.dirname(e),r=path.isAbsolute(t)?t:path.resolve(i,t);for(const e of JS_RESOLVE_EXTS){const t=r+e;if(n(t))return t}return null}function defaultFileExists(e){try{return fs.statSync(e).isFile()}catch(e){return!1}}function extractImportBindings(e){const t=[];return walk(e,e=>{if("ImportDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[])if("ImportDefaultSpecifier"===i.type)t.push({localName:i.local.name,importedName:"default",source:n});else if("ImportNamespaceSpecifier"===i.type)t.push({localName:i.local.name,importedName:"*",source:n});else if("ImportSpecifier"===i.type){const e=i.imported&&(i.imported.name||i.imported.value);t.push({localName:i.local.name,importedName:e||i.local.name,source:n})}}if("VariableDeclarator"===e.type&&e.init){const n=unwrapRequireCall(e.init);if(n)if("Identifier"===e.id.type)t.push({localName:e.id.name,importedName:"*",source:n});else if("ObjectPattern"===e.id.type)for(const i of e.id.properties)if("ObjectProperty"===i.type||"Property"===i.type){const e=i.key&&(i.key.name||i.key.value),r=i.value&&"Identifier"===i.value.type?i.value.name:e;e&&r&&t.push({localName:r,importedName:e,source:n})}}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type){const n=unwrapRequireCall(e.right);n&&t.push({localName:e.left.name,importedName:"*",source:n})}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&"then"===e.callee.property.name&&"ImportExpression"===e.callee.object.type&&e.callee.object.source&&"string"==typeof e.callee.object.source.value){const n=e.callee.object.source.value,i=e.arguments[0];i&&("ArrowFunctionExpression"===i.type||"FunctionExpression"===i.type)&&i.params[0]&&"Identifier"===i.params[0].type&&t.push({localName:i.params[0].name,importedName:"*",source:n})}}),t}function unwrapRequireCall(e){if(!e||"CallExpression"!==e.type)return null;if("Identifier"!==e.callee.type||"require"!==e.callee.name)return null;const t=e.arguments[0];return t&&"string"==typeof t.value?t.value:null}function extractReExports(e){const t=[];return walk(e,e=>{if("ExportNamedDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[]){const e=i.local&&(i.local.name||i.local.value),r=i.exported&&(i.exported.name||i.exported.value);e&&r&&t.push({importedName:e,exportedName:r,source:n})}}if("ExportAllDeclaration"===e.type&&e.source&&"string"==typeof e.source.value&&t.push({importedName:"*",exportedName:"*",source:e.source.value}),"AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)){const n=unwrapRequireCall(e.right);n&&t.push({importedName:"*",exportedName:"*",source:n})}}),t}function extractLocalExports(e){const t=[];return walk(e,e=>{if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&!e.left.computed&&"Identifier"===e.left.property.type){const n=memberChainToPath(e.left.object);"module.exports"!==n&&"exports"!==n||t.push({exportedName:e.left.property.name,localName:null,inlineNode:e.right})}if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)&&"ObjectExpression"===e.right.type)for(const n of e.right.properties){if("ObjectProperty"!==n.type&&"Property"!==n.type)continue;const e=n.key&&(n.key.name||n.key.value);e&&(n.value&&"Identifier"===n.value.type?t.push({exportedName:e,localName:n.value.name,inlineNode:null}):t.push({exportedName:e,localName:null,inlineNode:n.value}))}if("ExportNamedDeclaration"===e.type&&e.declaration){const n=e.declaration;if("VariableDeclaration"===n.type)for(const e of n.declarations)"Identifier"===e.id.type&&t.push({exportedName:e.id.name,localName:e.id.name,inlineNode:e.init||null});else"FunctionDeclaration"!==n.type&&"ClassDeclaration"!==n.type||!n.id||t.push({exportedName:n.id.name,localName:n.id.name,inlineNode:null})}if("ExportNamedDeclaration"===e.type&&!e.source)for(const n of e.specifiers||[]){const e=n.local&&(n.local.name||n.local.value),i=n.exported&&(n.exported.name||n.exported.value);e&&i&&t.push({exportedName:i,localName:e,inlineNode:null})}if("ExportDefaultDeclaration"===e.type){const n=e.declaration;n&&("FunctionDeclaration"===n.type||"ClassDeclaration"===n.type)&&n.id?t.push({exportedName:"default",localName:n.id.name,inlineNode:null}):n&&"Identifier"===n.type?t.push({exportedName:"default",localName:n.name,inlineNode:null}):t.push({exportedName:"default",localName:null,inlineNode:n})}}),t}function buildExportTaintMap(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8").replace(/\r\n/g,"\n")),i=t.fileExists||defaultFileExists,r=t.parsedCache||null,a=new Map;for(const t of e){const e=path.extname(t).toLowerCase();if(!JS_EXTS.has(e))continue;let i;try{i=n(t)}catch(e){continue}if(r&&r.set(t,{content:i,ast:null}),i.length>2e4)continue;const s=parseJS(i);r&&(r.get(t).ast=s),s&&a.set(t,{ast:s,content:i})}const s=new Map;for(const e of a.keys())s.set(e,new Map);const o=Math.max(10,a.size+1);let l=!0,c=0;for(;l&&c<o;){l=!1,c++;for(const[e,{ast:t}]of a){const n=s.get(e),r=extractImportBindings(t),a=new Map;for(const t of r){const n=resolveImportPath(e,t.source,i);if(!n||!s.has(n))continue;const r=s.get(n);if("*"===t.importedName)for(const[,e]of r)a.has(t.localName)||a.set(t.localName,e);else r.has(t.importedName)&&a.set(t.localName,r.get(t.importedName))}const{taintedVars:o,origins:c}=collectTaintedVars(t,a),p=extractLocalExports(t);for(const t of p){if(n.has(t.exportedName))continue;let i=null;if(t.localName&&o.has(t.localName)?i=c.get(t.localName)||{desc:t.localName,line:0}:t.inlineNode&&(isTaintedExpr(t.inlineNode,o)||isDynamicTaintedBuild(t.inlineNode,o))&&(i=findTaintOrigin(t.inlineNode,c)||{desc:describeJSSource(t.inlineNode),line:lineOf(t.inlineNode)}),i){const r=i.crossFile?i.sourceFile:e;n.set(t.exportedName,{desc:i.desc,line:i.line,sourceFile:r,crossFile:!0}),l=!0}}const u=extractReExports(t);for(const t of u){const r=resolveImportPath(e,t.source,i);if(!r||!s.has(r))continue;const a=s.get(r);if("*"===t.importedName)for(const[e,t]of a)n.has(e)||(n.set(e,t),l=!0);else a.has(t.importedName)&&!n.has(t.exportedName)&&(n.set(t.exportedName,a.get(t.importedName)),l=!0)}}}return s}function buildImportSeeds(e,t,n,i){const r=new Map,a=extractImportBindings(t);for(const t of a){const a=resolveImportPath(e,t.source,i);if(!a||!n.has(a))continue;const s=n.get(a);if("*"===t.importedName)for(const[,e]of s)r.has(t.localName)||r.set(t.localName,e);else s.has(t.importedName)&&r.set(t.localName,s.get(t.importedName))}return r}function analyzeProject(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8").replace(/\r\n/g,"\n")),i=t.fileExists||defaultFileExists,r=new Map,a=buildExportTaintMap(e,{readFile:n,fileExists:i,parsedCache:r}),s=[];for(const t of e){const e=path.extname(t).toLowerCase(),o=r.get(t);let l;if(o)l=o.content;else try{l=n(t)}catch(e){continue}if(l.length>2e4)(JS_EXTS.has(e)||PY_EXTS.has(e))&&s.push(fileTooLargeIssue(t,l.length));else try{if(JS_EXTS.has(e)){const e=o?o.ast:parseJS(l);if(!e)continue;const n=buildImportSeeds(t,e,a,i);s.push(...analyzeJSTaint(t,l,n.size?n:void 0))}else PY_EXTS.has(e)&&s.push(...analyzePythonTaint(t,l))}catch(e){}}return s}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]);function analyze(e,t){if(t.length>2e4)return[fileTooLargeIssue(e,t.length)];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSTaint(e,t);if(PY_EXTS.has(n))return analyzePythonTaint(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeProject:analyzeProject,analyzeJSTaint:analyzeJSTaint,analyzePythonTaint:analyzePythonTaint,buildExportTaintMap:buildExportTaintMap,buildImportSeeds:buildImportSeeds,resolveImportPath:resolveImportPath,_internal:{walk:walk,isTaintedExpr:isTaintedExpr,isJSSourceExpr:isJSSourceExpr,isSanitizerCall:isSanitizerCall,collectTaintedVars:collectTaintedVars,memberChainToPath:memberChainToPath,isPySourceText:isPySourceText,isDynamicPyText:isDynamicPyText,collectPyTaintedVars:collectPyTaintedVars,resolveImportPath:resolveImportPath,extractImportBindings:extractImportBindings,extractReExports:extractReExports,extractLocalExports:extractLocalExports,parseJS:parseJS}};
1
+ "use strict";const parser=require("@babel/parser"),path=require("path"),fs=require("fs"),{joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_TAINT_FILE_SIZE=2e4,MAX_FIXPOINT_ITERATIONS=10;function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const i=e[n];if(Array.isArray(i))for(const e of i)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else i&&"object"==typeof i&&"string"==typeof i.type&&walk(i,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkTaintIssue(e,t,n,i){const r={file:e,line:t,category:"security",type:i.type,severity:i.severity||"critical",id:i.id,name:i.name,message:i.message,code:snippet(n,t),source:"taint",taintPath:i.taintPath};return i.crossFile&&(r.crossFile=!0,r.sourceFile=i.sourceFile),r}function fileTooLargeIssue(e,t){return{file:e,line:0,category:"quality",type:"file_too_large",severity:"info",id:"TAINT003",name:"Taint Analysis Skipped",message:`File too large (${t} chars) - taint analysis skipped (cap: 20000 chars)`,code:"",source:"taint"}}const SANITIZER_FUNCS=new Set(["parseInt","parseFloat","Number","Boolean","encodeURIComponent","encodeURI","escape","sanitize","sanitizeHtml","stripTags","escapeHtml","htmlEscape","htmlEncode","xssFilter","filterXSS","DOMPurify","purify","clean","validator"]),SANITIZER_MEMBER=new Set(["sanitize","escape","escapeHtml","stripTags","clean","escapeLiteral","escapeIdentifier","escapeId"]),SANITIZER_RECEIVER_NAMES=new Set(["dompurify","sanitizehtml","sanitize-html","html","xss","validator","bleach","markupsafe","purify","sqlstring","sanitizer","striptags","xssfilters","insane","mysql","mysql2","connection","conn","db","pool","pg","knex"]);function hasKnownSanitizerReceiver(e){let t=e,n=0;for(;t&&n++<20;){if("Identifier"===t.type)return SANITIZER_RECEIVER_NAMES.has(t.name.toLowerCase());if("MemberExpression"!==t.type)return!1;t=t.object}return!1}const JS_SOURCE_PATH_PATTERNS=[{re:/^req\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^request\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^process\.argv/,severity:"critical"},{re:/^process\.env/,severity:"warning"},{re:/^document\.location/,severity:"critical"},{re:/^window\.location/,severity:"critical"},{re:/^location\.(search|hash|href)/,severity:"critical"}];function memberChainToPath(e){const t=[];let n=e,i=0;for(;n&&i++<20;){if("Identifier"===n.type){t.unshift(n.name);break}if("ThisExpression"===n.type){t.unshift("this");break}if("MemberExpression"!==n.type)return null;n.computed||"Identifier"!==n.property.type?t.unshift("*"):t.unshift(n.property.name),n=n.object}return t.length?t.join("."):null}function jsSourceSeverity(e){if(!e)return null;if("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name)return"critical";if("NewExpression"===e.type&&"Identifier"===e.callee.type&&("URLSearchParams"===e.callee.name||"FormData"===e.callee.name))return"critical";const t=memberChainToPath(e);if(t)for(const e of JS_SOURCE_PATH_PATTERNS)if(e.re.test(t))return e.severity;return null}function isJSSourceExpr(e){return null!==jsSourceSeverity(e)}function describeJSSource(e){return memberChainToPath(e)||("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name?"prompt()":"NewExpression"===e.type&&"Identifier"===e.callee.type?`new ${e.callee.name}()`:"tainted source")}function isSanitizerCall(e){return!(!e||"CallExpression"!==e.type)&&(!("Identifier"!==e.callee.type||!SANITIZER_FUNCS.has(e.callee.name))||!("MemberExpression"!==e.callee.type||"Identifier"!==e.callee.property.type||!SANITIZER_MEMBER.has(e.callee.property.name))&&hasKnownSanitizerReceiver(e.callee.object))}function isNumericCoercionCall(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||"parseInt"!==e.callee.name&&"parseFloat"!==e.callee.name&&"Number"!==e.callee.name)}function isTaintedExpr(e,t,n){if(n=n||0,!e||n>12)return!1;if("Identifier"===e.type)return t.has(e.name);if("CallExpression"===e.type){if(isSanitizerCall(e))return!1;if(isJSSourceExpr(e))return!0;if("MemberExpression"===e.callee.type&&isTaintedExpr(e.callee.object,t,n+1))return!0;for(const i of e.arguments)if(isTaintedExpr(i,t,n+1))return!0;return!1}return"MemberExpression"===e.type?!!isJSSourceExpr(e)||isTaintedExpr(e.object,t,n+1):"NewExpression"===e.type?isJSSourceExpr(e):"BinaryExpression"===e.type&&"+"===e.operator?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):"TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t,n+1)):"AssignmentExpression"===e.type?isTaintedExpr(e.right,t,n+1):"ConditionalExpression"===e.type?isTaintedExpr(e.consequent,t,n+1)||isTaintedExpr(e.alternate,t,n+1):"LogicalExpression"===e.type?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):!("SequenceExpression"!==e.type||!e.expressions.length)&&isTaintedExpr(e.expressions[e.expressions.length-1],t,n+1)}function isDynamicTaintedBuild(e,t){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t)):"BinaryExpression"===e.type&&"+"===e.operator&&(isTaintedExpr(e.left,t)||isTaintedExpr(e.right,t)))}function collectFunctionReturnNodes(e){const t=[];return e.body&&"BlockStatement"===e.body.type?function n(i){if(i&&"object"==typeof i&&"string"==typeof i.type&&(i===e||"FunctionDeclaration"!==i.type&&"FunctionExpression"!==i.type&&"ArrowFunctionExpression"!==i.type)){"ReturnStatement"===i.type&&i.argument&&t.push(i.argument);for(const e in i){if("loc"===e||"range"===e||"start"===e||"end"===e)continue;const t=i[e];if(Array.isArray(t))for(const e of t)e&&"object"==typeof e&&"string"==typeof e.type&&n(e);else t&&"object"==typeof t&&"string"==typeof t.type&&n(t)}}}(e.body):e.body&&t.push(e.body),t}function findTaintOrigin(e,t){let n=null;return walk(e,e=>{if(!n)if("Identifier"===e.type&&t.has(e.name))n=t.get(e.name);else{const t=jsSourceSeverity(e);t&&(n={desc:describeJSSource(e),line:lineOf(e),severity:t})}}),n}function collectTaintedVars(e,t){const n=new Set,i=new Map,r=new Set,a=new Map;if(t)for(const[e,r]of t)n.add(e),i.set(e,r);let s=!0,o=0;function l(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||!r.has(e.callee.name))}for(;s&&o<10;)s=!1,o++,walk(e,e=>{if("FunctionDeclaration"!==e.type&&"FunctionExpression"!==e.type&&"ArrowFunctionExpression"!==e.type)return;const t=e.id&&e.id.name;if(t&&!r.has(t))for(const o of collectFunctionReturnNodes(e))if(isTaintedExpr(o,n)||l(o)){r.add(t),a.set(t,findTaintOrigin(o,i)||(l(o)?a.get(o.callee.name):null)||{desc:describeJSSource(o),line:lineOf(o),severity:jsSourceSeverity(o)||"critical"}),s=!0;break}}),walk(e,e=>{let t=null,r=null;if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.id.type?(t=e.id.name,r=e.init):"AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&(t=e.left.name,r=e.right),t&&r&&n.has(t)&&(isSanitizerCall(r)||isNumericCoercionCall(r)))return n.delete(t),i.delete(t),void(s=!0);if(!t||n.has(t))return;const o=l(r);if(isTaintedExpr(r,n)||o){n.add(t),s=!0;const l=findTaintOrigin(r,i)||(o?a.get(r.callee.name):null)||{desc:describeJSSource(r),line:lineOf(e),severity:jsSourceSeverity(r)||"critical"};i.set(t,l)}});return{taintedVars:n,origins:i}}const SQL_METHODS=new Set(["query","execute","raw","where"]);function parseJS(e){try{return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return null}}function analyzeJSTaint(e,t,n){const i=[],r=t.split("\n"),a=parseJS(t);if(!a)return i;const{taintedVars:s,origins:o}=collectTaintedVars(a,n),l=new Set;function c(t,n){const a=lineOf(t),s=`${a}:${n.id}`;if(l.has(s))return;l.add(s);const c=findTaintOrigin(t,o);c&&(n=Object.assign({},n,{severity:c.severity||"critical"}),c.crossFile&&(n=Object.assign({},n,{crossFile:!0,sourceFile:c.sourceFile}))),i.push(mkTaintIssue(e,a,r,n))}function p(e){const t=findTaintOrigin(e,o);if(t)return`${t.crossFile?`${t.sourceFile}: `:""}${t.desc} (line ${t.line}) -> sink (line ${lineOf(e)})`}return walk(a,e=>{if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&SQL_METHODS.has(e.callee.property.name)){const t=e.arguments[0];t&&(isDynamicTaintedBuild(t,s)||isTaintedExpr(t,s))&&c(e,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:`Tainted user input flows into .${e.callee.property.name}() without sanitization - SQL injection risk`,taintPath:p(e)})}if("AssignmentExpression"!==e.type||"MemberExpression"!==e.left.type||"Identifier"!==e.left.property.type||"innerHTML"!==e.left.property.name&&"outerHTML"!==e.left.property.name||(isTaintedExpr(e.right,s)||isDynamicTaintedBuild(e.right,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via "+e.left.property.name,message:`Tainted user input flows into ${e.left.property.name} without sanitization - XSS risk`,taintPath:p(e)}),"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"document"===e.callee.object.name&&"Identifier"===e.callee.property.type&&("write"===e.callee.property.name||"writeln"===e.callee.property.name)){const t=e.arguments[0];t&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Tainted XSS via document.${e.callee.property.name}()`,message:`Tainted user input flows into document.${e.callee.property.name}() without sanitization - XSS risk`,taintPath:p(e)})}if("JSXAttribute"===e.type&&e.name&&"dangerouslySetInnerHTML"===e.name.name&&e.value&&"JSXExpressionContainer"===e.value.type){const t=e.value.expression;if(t&&"ObjectExpression"===t.type){const n=t.properties.find(e=>e.key&&("__html"===e.key.name||"__html"===e.key.value));n&&(isTaintedExpr(n.value,s)||isDynamicTaintedBuild(n.value,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via dangerouslySetInnerHTML",message:"Tainted user input flows into dangerouslySetInnerHTML without sanitization - XSS risk",taintPath:p(e)})}}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&/^res(ponse)?$/.test(e.callee.object.name)&&"Identifier"===e.callee.property.type&&("send"===e.callee.property.name||"write"===e.callee.property.name)){const t=e.arguments[0];t&&"ObjectExpression"!==t.type&&"ArrayExpression"!==t.type&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Reflected XSS via ${e.callee.object.name}.${e.callee.property.name}()`,message:`Tainted user input is reflected via ${e.callee.object.name}.${e.callee.property.name}() without sanitization - reflected XSS risk`,taintPath:p(e)})}}),i}const PY_SOURCE_PATTERNS=[/\brequest\.args\b/,/\brequest\.form\b/,/\brequest\.json\b/,/\brequest\.data\b/,/\brequest\.GET\b/,/\brequest\.POST\b/,/\bsys\.argv\b/,/\bos\.environ\b/,/\binput\s*\(/],PY_SANITIZER_WRAP_RE=/^\s*(int|float|re\.escape|html\.escape|escape|markupsafe\.escape|bleach\.clean)\s*\(/;function describePySource(e){for(const t of PY_SOURCE_PATTERNS){const n=e.match(t);if(n)return n[0]}return"tainted source"}function isPySourceText(e){return PY_SOURCE_PATTERNS.some(t=>t.test(e))}function isDynamicPyText(e){return isDynamicPyString(e)}function collectPyTaintedVars(e){const t=new Set,n=new Map;let i=!0,r=0;const a=/^\s*([A-Za-z_]\w*)\s*=(?!=)\s*([\s\S]+?)\s*$/;for(;i&&r<10;){i=!1,r++;for(const r of e){const e=r.text.split("\n")[0].match(a)||r.text.match(a);if(!e)continue;const s=e[1],o=r.text.slice(r.text.indexOf("=")+1);if(PY_SANITIZER_WRAP_RE.test(o.trim())){t.has(s)&&(t.delete(s),n.delete(s),i=!0);continue}if(t.has(s))continue;let l=!1,c=null;if(isPySourceText(o))l=!0,c=describePySource(o);else for(const e of t)if(new RegExp("\\b"+e+"\\b").test(o)){l=!0,c=n.has(e)?n.get(e).desc:e;break}l&&(t.add(s),n.set(s,{desc:c,line:r.startLine}),i=!0)}}return{taintedVars:t,origins:n}}function textReferencesTaint(e,t){if(isPySourceText(e))return!0;for(const n of t)if(new RegExp("\\b"+n+"\\b").test(e))return!0;return!1}function analyzePythonTaint(e,t){const n=[],i=t.split("\n"),r=joinLogicalStatements(t),{taintedVars:a,origins:s}=collectPyTaintedVars(r),o=new Set;function l(t,r){const a=`${t}:${r.id}`;o.has(a)||(o.add(a),n.push(mkTaintIssue(e,t,i,r)))}function c(e,t){for(const n of a)if(new RegExp("\\b"+n+"\\b").test(e)&&s.has(n)){const e=s.get(n);return`${e.desc} (line ${e.line}) -> sink (line ${t})`}if(isPySourceText(e))return`${describePySource(e)} -> sink (line ${t})`}for(const e of r){const t=e.text,n=e.startLine,i=t.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(i){const e=t.indexOf("(",i.index),r=extractBalanced(t,e),s=splitTopLevelArgs(r)[0]||"";isDynamicPyText(s)&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:"Tainted user input flows into .execute() without sanitization - SQL injection risk",taintPath:c(s,n)})}const r=t.match(/\.(raw|extra)\s*\(|\bRawSQL\s*\(/);if(r){const e=t.indexOf("(",r.index),i=extractBalanced(t,e),s=splitTopLevelArgs(i)[0]||"";(isDynamicPyText(s)||isPySourceText(s))&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (Django)",message:"Tainted user input flows into raw SQL construct without sanitization - SQL injection risk",taintPath:c(s,n)})}const s=t.match(/\btext\s*\(/);if(s){const e=t.indexOf("(",s.index),i=extractBalanced(t,e);isDynamicPyText(i)&&textReferencesTaint(i,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (SQLAlchemy text())",message:"Tainted user input flows into SQLAlchemy text() without sanitization - SQL injection risk",taintPath:c(i,n)})}const o=t.match(/\bmark_safe\s*\(/);if(o){const e=t.indexOf("(",o.index),i=extractBalanced(t,e);textReferencesTaint(i,a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via mark_safe()",message:"Tainted user input flows into mark_safe() without sanitization - XSS risk",taintPath:c(i,n)})}const p=t.match(/\{\{\s*([A-Za-z_][\w.]*)[^}]*\|\s*safe\b/);p&&textReferencesTaint(p[1],a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via Jinja2 |safe filter",message:"Tainted user input is rendered with the |safe filter without sanitization - XSS risk",taintPath:c(p[1],n)})}return n}const JS_RESOLVE_EXTS=["",".js",".jsx",".ts",".tsx",".mjs",".cjs",path.sep+"index.js",path.sep+"index.ts",path.sep+"index.jsx",path.sep+"index.tsx"];function resolveImportPath(e,t,n){if(!t||!t.startsWith(".")&&!path.isAbsolute(t))return null;const i=path.dirname(e),r=path.isAbsolute(t)?t:path.resolve(i,t);for(const e of JS_RESOLVE_EXTS){const t=r+e;if(n(t))return t}return null}function defaultFileExists(e){try{return fs.statSync(e).isFile()}catch(e){return!1}}function extractImportBindings(e){const t=[];return walk(e,e=>{if("ImportDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[])if("ImportDefaultSpecifier"===i.type)t.push({localName:i.local.name,importedName:"default",source:n});else if("ImportNamespaceSpecifier"===i.type)t.push({localName:i.local.name,importedName:"*",source:n});else if("ImportSpecifier"===i.type){const e=i.imported&&(i.imported.name||i.imported.value);t.push({localName:i.local.name,importedName:e||i.local.name,source:n})}}if("VariableDeclarator"===e.type&&e.init){const n=unwrapRequireCall(e.init);if(n)if("Identifier"===e.id.type)t.push({localName:e.id.name,importedName:"*",source:n});else if("ObjectPattern"===e.id.type)for(const i of e.id.properties)if("ObjectProperty"===i.type||"Property"===i.type){const e=i.key&&(i.key.name||i.key.value),r=i.value&&"Identifier"===i.value.type?i.value.name:e;e&&r&&t.push({localName:r,importedName:e,source:n})}}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type){const n=unwrapRequireCall(e.right);n&&t.push({localName:e.left.name,importedName:"*",source:n})}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&"then"===e.callee.property.name&&"ImportExpression"===e.callee.object.type&&e.callee.object.source&&"string"==typeof e.callee.object.source.value){const n=e.callee.object.source.value,i=e.arguments[0];i&&("ArrowFunctionExpression"===i.type||"FunctionExpression"===i.type)&&i.params[0]&&"Identifier"===i.params[0].type&&t.push({localName:i.params[0].name,importedName:"*",source:n})}}),t}function unwrapRequireCall(e){if(!e||"CallExpression"!==e.type)return null;if("Identifier"!==e.callee.type||"require"!==e.callee.name)return null;const t=e.arguments[0];return t&&"string"==typeof t.value?t.value:null}function extractReExports(e){const t=[];return walk(e,e=>{if("ExportNamedDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[]){const e=i.local&&(i.local.name||i.local.value),r=i.exported&&(i.exported.name||i.exported.value);e&&r&&t.push({importedName:e,exportedName:r,source:n})}}if("ExportAllDeclaration"===e.type&&e.source&&"string"==typeof e.source.value&&t.push({importedName:"*",exportedName:"*",source:e.source.value}),"AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)){const n=unwrapRequireCall(e.right);n&&t.push({importedName:"*",exportedName:"*",source:n})}}),t}function extractLocalExports(e){const t=[];return walk(e,e=>{if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&!e.left.computed&&"Identifier"===e.left.property.type){const n=memberChainToPath(e.left.object);"module.exports"!==n&&"exports"!==n||t.push({exportedName:e.left.property.name,localName:null,inlineNode:e.right})}if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)&&"ObjectExpression"===e.right.type)for(const n of e.right.properties){if("ObjectProperty"!==n.type&&"Property"!==n.type)continue;const e=n.key&&(n.key.name||n.key.value);e&&(n.value&&"Identifier"===n.value.type?t.push({exportedName:e,localName:n.value.name,inlineNode:null}):t.push({exportedName:e,localName:null,inlineNode:n.value}))}if("ExportNamedDeclaration"===e.type&&e.declaration){const n=e.declaration;if("VariableDeclaration"===n.type)for(const e of n.declarations)"Identifier"===e.id.type&&t.push({exportedName:e.id.name,localName:e.id.name,inlineNode:e.init||null});else"FunctionDeclaration"!==n.type&&"ClassDeclaration"!==n.type||!n.id||t.push({exportedName:n.id.name,localName:n.id.name,inlineNode:null})}if("ExportNamedDeclaration"===e.type&&!e.source)for(const n of e.specifiers||[]){const e=n.local&&(n.local.name||n.local.value),i=n.exported&&(n.exported.name||n.exported.value);e&&i&&t.push({exportedName:i,localName:e,inlineNode:null})}if("ExportDefaultDeclaration"===e.type){const n=e.declaration;n&&("FunctionDeclaration"===n.type||"ClassDeclaration"===n.type)&&n.id?t.push({exportedName:"default",localName:n.id.name,inlineNode:null}):n&&"Identifier"===n.type?t.push({exportedName:"default",localName:n.name,inlineNode:null}):t.push({exportedName:"default",localName:null,inlineNode:n})}}),t}function buildExportTaintMap(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8").replace(/\r\n/g,"\n")),i=t.fileExists||defaultFileExists,r=t.parsedCache||null,a=new Map;for(const t of e){const e=path.extname(t).toLowerCase();if(!JS_EXTS.has(e))continue;let i;try{i=n(t)}catch(e){continue}if(r&&r.set(t,{content:i,ast:null}),i.length>2e4)continue;const s=parseJS(i);r&&(r.get(t).ast=s),s&&a.set(t,{ast:s,content:i})}const s=new Map;for(const e of a.keys())s.set(e,new Map);const o=Math.max(10,a.size+1);let l=!0,c=0;for(;l&&c<o;){l=!1,c++;for(const[e,{ast:t}]of a){const n=s.get(e),r=extractImportBindings(t),a=new Map;for(const t of r){const n=resolveImportPath(e,t.source,i);if(!n||!s.has(n))continue;const r=s.get(n);if("*"===t.importedName)for(const[,e]of r)a.has(t.localName)||a.set(t.localName,e);else r.has(t.importedName)&&a.set(t.localName,r.get(t.importedName))}const{taintedVars:o,origins:c}=collectTaintedVars(t,a),p=extractLocalExports(t);for(const t of p){if(n.has(t.exportedName))continue;let i=null;if(t.localName&&o.has(t.localName)?i=c.get(t.localName)||{desc:t.localName,line:0}:t.inlineNode&&(isTaintedExpr(t.inlineNode,o)||isDynamicTaintedBuild(t.inlineNode,o))&&(i=findTaintOrigin(t.inlineNode,c)||{desc:describeJSSource(t.inlineNode),line:lineOf(t.inlineNode)}),i){const r=i.crossFile?i.sourceFile:e;n.set(t.exportedName,{desc:i.desc,line:i.line,sourceFile:r,crossFile:!0}),l=!0}}const u=extractReExports(t);for(const t of u){const r=resolveImportPath(e,t.source,i);if(!r||!s.has(r))continue;const a=s.get(r);if("*"===t.importedName)for(const[e,t]of a)n.has(e)||(n.set(e,t),l=!0);else a.has(t.importedName)&&!n.has(t.exportedName)&&(n.set(t.exportedName,a.get(t.importedName)),l=!0)}}}return s}function buildImportSeeds(e,t,n,i){const r=new Map,a=extractImportBindings(t);for(const t of a){const a=resolveImportPath(e,t.source,i);if(!a||!n.has(a))continue;const s=n.get(a);if("*"===t.importedName)for(const[,e]of s)r.has(t.localName)||r.set(t.localName,e);else s.has(t.importedName)&&r.set(t.localName,s.get(t.importedName))}return r}function analyzeProject(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8").replace(/\r\n/g,"\n")),i=t.fileExists||defaultFileExists,r=new Map,a=buildExportTaintMap(e,{readFile:n,fileExists:i,parsedCache:r}),s=[];for(const t of e){const e=path.extname(t).toLowerCase(),o=r.get(t);let l;if(o)l=o.content;else try{l=n(t)}catch(e){continue}if(l.length>2e4)(JS_EXTS.has(e)||PY_EXTS.has(e))&&s.push(fileTooLargeIssue(t,l.length));else try{if(JS_EXTS.has(e)){const e=o?o.ast:parseJS(l);if(!e)continue;const n=buildImportSeeds(t,e,a,i);s.push(...analyzeJSTaint(t,l,n.size?n:void 0))}else PY_EXTS.has(e)&&s.push(...analyzePythonTaint(t,l))}catch(e){}}return s}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]);function analyze(e,t){if(t.length>2e4)return[fileTooLargeIssue(e,t.length)];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSTaint(e,t);if(PY_EXTS.has(n))return analyzePythonTaint(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeProject:analyzeProject,analyzeJSTaint:analyzeJSTaint,analyzePythonTaint:analyzePythonTaint,buildExportTaintMap:buildExportTaintMap,buildImportSeeds:buildImportSeeds,resolveImportPath:resolveImportPath,_internal:{walk:walk,isTaintedExpr:isTaintedExpr,isJSSourceExpr:isJSSourceExpr,isSanitizerCall:isSanitizerCall,collectTaintedVars:collectTaintedVars,memberChainToPath:memberChainToPath,isPySourceText:isPySourceText,isDynamicPyText:isDynamicPyText,collectPyTaintedVars:collectPyTaintedVars,resolveImportPath:resolveImportPath,extractImportBindings:extractImportBindings,extractReExports:extractReExports,extractLocalExports:extractLocalExports,parseJS:parseJS}};
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),DependencyGraph=require("./dependency-graph");class WidgetGenerator{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),dryRun:!1!==e.dryRun,...e},this.dependencyGraph=e.dependencyGraph||null,this.modulePatterns={sentinel:/^sentinel[\\\/]/,citadel:/^citadel[\\\/]/,forge:/^forge[\\\/]/,server:/^server[\\\/]/,"build-engine":/^build-engine[\\\/]/,autopilot:/^autopilot[\\\/]/,silverwings:/^silverwings[\\\/]/,output:/^output[\\\/]/},this.layerPatterns={presentation:/routes|api|handlers|controllers|views/i,application:/service|manager|orchestrat|core|engine/i,domain:/model|entity|domain|types/i,infrastructure:/utils|helpers|lib|data|store|memory/i},this.sideEffectPatterns=[{pattern:/fs\.(write|append|unlink|mkdir|rmdir|rename)/i,effect:"File system writes"},{pattern:/fs\.read/i,effect:"File system reads"},{pattern:/fetch\(|axios\.|http\.|https\./i,effect:"HTTP requests"},{pattern:/process\.exit/i,effect:"Process termination"},{pattern:/child_process|spawn|exec\(/i,effect:"Spawns child processes"},{pattern:/console\.(log|error|warn)/i,effect:"Console output"},{pattern:/\.emit\(|EventEmitter/i,effect:"Event emission"},{pattern:/supabase|createClient/i,effect:"Database operations"},{pattern:/twilio|sendgrid|resend/i,effect:"External API calls"}],this.purposePatterns=[{pattern:/class\s+(\w+).*extends.*Router/i,purpose:e=>`Express router for ${e[1]}`},{pattern:/class\s+(\w+).*extends.*EventEmitter/i,purpose:e=>`Event-driven ${e[1]} component`},{pattern:/class\s+(\w+)/i,purpose:e=>`${e[1]} class implementation`},{pattern:/module\.exports\s*=\s*\{([^}]+)\}/i,purpose:e=>`Exports: ${e[1].match(/\w+/g)?.slice(0,5).join(", ")}`},{pattern:/router\.(get|post|put|delete)/i,purpose:()=>"API route handlers"},{pattern:/async function\s+(\w+)/i,purpose:e=>`Provides ${e[1]} functionality`},{pattern:/function\s+(\w+)/i,purpose:e=>`Provides ${e[1]} functionality`}]}async generateAll(){console.log("[WIDGET-GENERATOR] Starting widget generation..."),this.dependencyGraph||(this.dependencyGraph=new DependencyGraph({rootPath:this.config.rootPath}),await this.dependencyGraph.build());const e=await this._discoverFiles(this.config.rootPath),t={total:e.length,generated:0,skipped:0,alreadyHasWidget:0,widgets:[]};for(const s of e){const e=fs.readFileSync(s,"utf8");if(this._hasWidget(e)){t.alreadyHasWidget++;continue}const r=await this.generateWidget(s,e);t.generated++,t.widgets.push({file:s,relativePath:path.relative(this.config.rootPath,s),widget:r}),this.config.dryRun||await this._applyWidget(s,e,r)}return console.log(`[WIDGET-GENERATOR] Generated ${t.generated} widgets (${t.alreadyHasWidget} already had widgets)`),t}async generateWidget(e,t=null){t||(t=fs.readFileSync(e,"utf8"));const s=path.relative(this.config.rootPath,e),r=path.basename(e,path.extname(e)),i={purpose:this._detectPurpose(t,r,s),module:this._detectModule(s),layer:this._detectLayer(s,t),importsFrom:this._getImportsFrom(e),exportsTo:this._getExportsTo(e),sideEffects:this._detectSideEffects(t),criticalFor:this._detectCriticalFor(e)};return{analysis:i,widgetString:this._buildWidgetString(i,r),fileName:r,relativePath:s}}async preview(e=20){const t=await this.generateAll();console.log("\n"+"=".repeat(70)),console.log("WIDGET GENERATION PREVIEW"),console.log("=".repeat(70)),console.log(`Total files: ${t.total}`),console.log(`Already have widgets: ${t.alreadyHasWidget}`),console.log(`Will generate: ${t.generated}`),console.log("");for(const s of t.widgets.slice(0,e))console.log("-".repeat(70)),console.log(`FILE: ${s.relativePath}`),console.log("-".repeat(70)),console.log(s.widget.widgetString),console.log("");return t.widgets.length>e&&console.log(`... and ${t.widgets.length-e} more files`),t}async apply(){this.config.dryRun=!1;const e=await this.generateAll();return console.log(`[WIDGET-GENERATOR] Applied ${e.generated} widgets to files`),e}_hasWidget(e){return/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\//.test(e)}_detectPurpose(e,t,s){for(const{pattern:t,purpose:s}of this.purposePatterns){const r=e.match(t);if(r)return"function"==typeof s?s(r):s}const r=t.replace(/[-_]/g," ").replace(/([A-Z])/g," $1").trim();return t.includes("route")?`Route handlers for ${r}`:t.includes("api")?`API endpoints for ${r}`:t.includes("service")?`Service layer for ${r}`:t.includes("manager")?`Manager for ${r}`:t.includes("store")?`State store for ${r}`:t.includes("util")?`Utility functions for ${r}`:t.includes("helper")?`Helper functions for ${r}`:t.includes("test")?`Tests for ${r}`:t.includes("config")?`Configuration for ${r}`:t.includes("index")?`Entry point for ${path.dirname(s).split(path.sep).pop()}`:`Provides ${r} functionality`}_detectModule(e){const t=e.replace(/\\/g,"/");for(const[e,s]of Object.entries(this.modulePatterns))if(s.test(t))return e;return t.split("/")[0]||"root"}_detectLayer(e,t){for(const[t,s]of Object.entries(this.layerPatterns))if(s.test(e))return t;return/router\.(get|post|put|delete)|app\.(get|post)/i.test(t)?"presentation":/class.*Service|class.*Manager/i.test(t)?"application":/class.*Model|schema|entity/i.test(t)?"domain":"application"}_getImportsFrom(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getDependencies(e),s=[];for(const e of t.slice(0,10))if(e.resolved){const t=path.relative(this.config.rootPath,e.resolved).split(path.sep),r=t[0],i=t[t.length-1];s.push(`${r}/${i}`)}else e.module.startsWith(".")||s.push(e.module);return s}_getExportsTo(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getDependents(e),s=[];for(const e of t.slice(0,10)){const t=path.relative(this.config.rootPath,e).split(path.sep)[0];s.push(t)}return[...new Set(s)]}_detectSideEffects(e){const t=[];for(const{pattern:s,effect:r}of this.sideEffectPatterns)s.test(e)&&t.push(r);return t.length>0?t:["None detected"]}_detectCriticalFor(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getImpactAnalysis(e);return"critical"===t.riskLevel?[`CRITICAL: ${t.totalImpact} files depend on this`]:"high"===t.riskLevel?[`High impact: ${t.totalImpact} files depend on this`]:t.affectedModules.length>0?t.affectedModules.slice(0,5):[]}_buildWidgetString(e,t){const s=["/**",` * ${this._toTitleCase(t)}`," *",` * @purpose ${e.purpose}`,` * @module ${e.module}`,` * @layer ${e.layer}`];return e.importsFrom.length>0&&s.push(` * @imports-from ${e.importsFrom.join(", ")}`),e.exportsTo.length>0&&s.push(` * @exports-to ${e.exportsTo.join(", ")}`),s.push(` * @side-effects ${e.sideEffects.join(", ")}`),e.criticalFor.length>0&&s.push(` * @critical-for ${e.criticalFor.join(", ")}`),s.push(" */"),s.join("\n")}_toTitleCase(e){return e.replace(/[-_]/g," ").replace(/([A-Z])/g," $1").trim().split(" ").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ")}async _applyWidget(e,t,s){const r=s.widgetString+"\n\n"+t;try{return fs.writeFileSync(e,r,"utf8"),!0}catch(e){return console.error("[WIDGET-GENERATOR] Failed to write widget"),!1}}async _discoverFiles(e,t=[]){const s=["node_modules",".git","dist",".vite",".orion"];try{const r=fs.readdirSync(e,{withFileTypes:!0});for(const i of r){if(s.some(e=>i.name===e||i.name.startsWith(e)))continue;const r=path.join(e,i.name);i.isDirectory()?await this._discoverFiles(r,t):i.isFile()&&i.name.endsWith(".js")&&t.push(r)}}catch(e){}return t}}module.exports=WidgetGenerator;
1
+ const fs=require("fs"),path=require("path"),crypto=require("crypto"),DependencyGraph=require("./dependency-graph");function computeDna(e){const t=e.replace(/\r\n/g,"\n").split("\n").map(e=>e.replace(/\s+$/,"")).filter(e=>e.length>0).join("\n");return crypto.createHash("sha256").update(t).digest("hex").slice(0,16)}class WidgetGenerator{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),dryRun:!1!==e.dryRun,...e},this.dependencyGraph=e.dependencyGraph||null,this.modulePatterns={sentinel:/^sentinel[\\\/]/,citadel:/^citadel[\\\/]/,forge:/^forge[\\\/]/,server:/^server[\\\/]/,"build-engine":/^build-engine[\\\/]/,autopilot:/^autopilot[\\\/]/,silverwings:/^silverwings[\\\/]/,output:/^output[\\\/]/},this.layerPatterns={presentation:/routes|api|handlers|controllers|views/i,application:/service|manager|orchestrat|core|engine/i,domain:/model|entity|domain|types/i,infrastructure:/utils|helpers|lib|data|store|memory/i},this.sideEffectPatterns=[{pattern:/fs\.(write|append|unlink|mkdir|rmdir|rename)/i,effect:"File system writes"},{pattern:/fs\.read/i,effect:"File system reads"},{pattern:/fetch\(|axios\.|http\.|https\./i,effect:"HTTP requests"},{pattern:/process\.exit/i,effect:"Process termination"},{pattern:/child_process|spawn|exec\(/i,effect:"Spawns child processes"},{pattern:/console\.(log|error|warn)/i,effect:"Console output"},{pattern:/\.emit\(|EventEmitter/i,effect:"Event emission"},{pattern:/supabase|createClient/i,effect:"Database operations"},{pattern:/twilio|sendgrid|resend/i,effect:"External API calls"}],this.purposePatterns=[{pattern:/class\s+(\w+).*extends.*Router/i,purpose:e=>`Express router for ${e[1]}`},{pattern:/class\s+(\w+).*extends.*EventEmitter/i,purpose:e=>`Event-driven ${e[1]} component`},{pattern:/class\s+(\w+)/i,purpose:e=>`${e[1]} class implementation`},{pattern:/module\.exports\s*=\s*\{([^}]+)\}/i,purpose:e=>`Exports: ${e[1].match(/\w+/g)?.slice(0,5).join(", ")}`},{pattern:/router\.(get|post|put|delete)/i,purpose:()=>"API route handlers"},{pattern:/async function\s+(\w+)/i,purpose:e=>`Provides ${e[1]} functionality`},{pattern:/function\s+(\w+)/i,purpose:e=>`Provides ${e[1]} functionality`}]}async generateAll(){console.log("[WIDGET-GENERATOR] Starting widget generation..."),this.dependencyGraph||(this.dependencyGraph=new DependencyGraph({rootPath:this.config.rootPath}),await this.dependencyGraph.build());const e=await this._discoverFiles(this.config.rootPath),t={total:e.length,generated:0,skipped:0,alreadyHasWidget:0,widgets:[]};for(const s of e){const e=fs.readFileSync(s,"utf8");if(this._hasWidget(e)){t.alreadyHasWidget++;continue}const r=await this.generateWidget(s,e);t.generated++,t.widgets.push({file:s,relativePath:path.relative(this.config.rootPath,s),widget:r}),this.config.dryRun||await this._applyWidget(s,e,r)}return console.log(`[WIDGET-GENERATOR] Generated ${t.generated} widgets (${t.alreadyHasWidget} already had widgets)`),t}async generateWidget(e,t=null){t||(t=fs.readFileSync(e,"utf8"));const s=path.relative(this.config.rootPath,e),r=path.basename(e,path.extname(e)),i={purpose:this._detectPurpose(t,r,s),module:this._detectModule(s),layer:this._detectLayer(s,t),importsFrom:this._getImportsFrom(e),exportsTo:this._getExportsTo(e),sideEffects:this._detectSideEffects(t),criticalFor:this._detectCriticalFor(e),dna:computeDna(t)};return{analysis:i,widgetString:this._buildWidgetString(i,r),fileName:r,relativePath:s}}async preview(e=20){const t=await this.generateAll();console.log("\n"+"=".repeat(70)),console.log("WIDGET GENERATION PREVIEW"),console.log("=".repeat(70)),console.log(`Total files: ${t.total}`),console.log(`Already have widgets: ${t.alreadyHasWidget}`),console.log(`Will generate: ${t.generated}`),console.log("");for(const s of t.widgets.slice(0,e))console.log("-".repeat(70)),console.log(`FILE: ${s.relativePath}`),console.log("-".repeat(70)),console.log(s.widget.widgetString),console.log("");return t.widgets.length>e&&console.log(`... and ${t.widgets.length-e} more files`),t}async apply(){this.config.dryRun=!1;const e=await this.generateAll();return console.log(`[WIDGET-GENERATOR] Applied ${e.generated} widgets to files`),e}_hasWidget(e){return/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\//.test(e)}_detectPurpose(e,t,s){for(const{pattern:t,purpose:s}of this.purposePatterns){const r=e.match(t);if(r)return"function"==typeof s?s(r):s}const r=t.replace(/[-_]/g," ").replace(/([A-Z])/g," $1").trim();return t.includes("route")?`Route handlers for ${r}`:t.includes("api")?`API endpoints for ${r}`:t.includes("service")?`Service layer for ${r}`:t.includes("manager")?`Manager for ${r}`:t.includes("store")?`State store for ${r}`:t.includes("util")?`Utility functions for ${r}`:t.includes("helper")?`Helper functions for ${r}`:t.includes("test")?`Tests for ${r}`:t.includes("config")?`Configuration for ${r}`:t.includes("index")?`Entry point for ${path.dirname(s).split(path.sep).pop()}`:`Provides ${r} functionality`}_detectModule(e){const t=e.replace(/\\/g,"/");for(const[e,s]of Object.entries(this.modulePatterns))if(s.test(t))return e;return t.split("/")[0]||"root"}_detectLayer(e,t){for(const[t,s]of Object.entries(this.layerPatterns))if(s.test(e))return t;return/router\.(get|post|put|delete)|app\.(get|post)/i.test(t)?"presentation":/class.*Service|class.*Manager/i.test(t)?"application":/class.*Model|schema|entity/i.test(t)?"domain":"application"}_getImportsFrom(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getDependencies(e),s=[];for(const e of t.slice(0,10))if(e.resolved){const t=path.relative(this.config.rootPath,e.resolved).split(path.sep),r=t[0],i=t[t.length-1];s.push(`${r}/${i}`)}else e.module.startsWith(".")||s.push(e.module);return s}_getExportsTo(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getDependents(e),s=[];for(const e of t.slice(0,10)){const t=path.relative(this.config.rootPath,e).split(path.sep)[0];s.push(t)}return[...new Set(s)]}_detectSideEffects(e){const t=[];for(const{pattern:s,effect:r}of this.sideEffectPatterns)s.test(e)&&t.push(r);return t.length>0?t:["None detected"]}_detectCriticalFor(e){if(!this.dependencyGraph)return[];const t=this.dependencyGraph.getImpactAnalysis(e);return"critical"===t.riskLevel?[`CRITICAL: ${t.totalImpact} files depend on this`]:"high"===t.riskLevel?[`High impact: ${t.totalImpact} files depend on this`]:t.affectedModules.length>0?t.affectedModules.slice(0,5):[]}_buildWidgetString(e,t){const s=["/**",` * ${this._toTitleCase(t)}`," *",` * @purpose ${e.purpose}`,` * @module ${e.module}`,` * @layer ${e.layer}`];return e.importsFrom.length>0&&s.push(` * @imports-from ${e.importsFrom.join(", ")}`),e.exportsTo.length>0&&s.push(` * @exports-to ${e.exportsTo.join(", ")}`),s.push(` * @side-effects ${e.sideEffects.join(", ")}`),e.criticalFor.length>0&&s.push(` * @critical-for ${e.criticalFor.join(", ")}`),e.dna&&s.push(` * @dna ${e.dna}`),s.push(" */"),s.join("\n")}_toTitleCase(e){return e.replace(/[-_]/g," ").replace(/([A-Z])/g," $1").trim().split(" ").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ")}async _applyWidget(e,t,s){const r=s.widgetString+"\n\n"+t;try{return fs.writeFileSync(e,r,"utf8"),!0}catch(e){return console.error("[WIDGET-GENERATOR] Failed to write widget"),!1}}async _discoverFiles(e,t=[]){const s=["node_modules",".git","dist",".vite",".orion"];try{const r=fs.readdirSync(e,{withFileTypes:!0});for(const i of r){if(s.some(e=>i.name===e||i.name.startsWith(e)))continue;const r=path.join(e,i.name);i.isDirectory()?await this._discoverFiles(r,t):i.isFile()&&i.name.endsWith(".js")&&t.push(r)}}catch(e){}return t}}module.exports=WidgetGenerator,module.exports.computeDna=computeDna;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "thuban",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "The safety layer for AI-coded software. Detect hallucinated APIs, ghost code, tech debt, and architecture drift. One command. Minimal dependencies.",
5
5
  "bin": {
6
- "thuban": "./dist/cli.js"
6
+ "thuban": "dist/cli.js"
7
7
  },
8
8
  "scripts": {
9
9
  "scan": "node cli.js scan",
@@ -36,7 +36,7 @@
36
36
  "homepage": "https://thuban.dev",
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "https://github.com/SilverwingsBenefitsGit/thuban.git"
39
+ "url": "git+https://github.com/SilverwingsBenefitsGit/thuban.git"
40
40
  },
41
41
  "bugs": {
42
42
  "url": "https://github.com/SilverwingsBenefitsGit/thuban/issues"
@@ -47,6 +47,7 @@
47
47
  "files": [
48
48
  "dist/cli.js",
49
49
  "dist/packages/scanner/*.js",
50
+ "dist/packages/scanner/python_ast_helper.py",
50
51
  "dist/packages/crucible/*.js",
51
52
  "dist/packages/crucible/seeds/**",
52
53
  "dist/packages/crucible/.golden/",