From 3ca14c8f534ba69d2e8c842e0b47e48664f457eb Mon Sep 17 00:00:00 2001 From: itsjustmithun Date: Sat, 27 Dec 2025 14:19:07 +0100 Subject: [PATCH 1/3] rename: changing salt-docs to wikigen - easier find --- .github/workflows/ci.yml | 8 +- .github/workflows/release.yml | 4 +- .github/workflows/salt-docs.yml | 38 ++--- .gitignore | 2 +- CHANGELOG.md | 24 +-- CONTRIBUTING.md | 6 +- README.md | 104 ++++++------- RELEASE.md | 4 +- SECURITY.md | 6 +- docs/CI.md | 60 ++++---- docs/SALT-DOCS.md | 190 ++++++++++++------------ pyproject.toml | 12 +- salt_docs/__init__.py | 2 +- salt_docs/cli.py | 34 ++--- salt_docs/config.py | 20 +-- salt_docs/defaults.py | 4 +- salt_docs/flows/__init__.py | 2 +- salt_docs/flows/flow.py | 2 +- salt_docs/formatter/help_formatter.py | 2 +- salt_docs/formatter/init_formatter.py | 2 +- salt_docs/formatter/output_formatter.py | 6 +- salt_docs/mcp/__init__.py | 2 +- salt_docs/mcp/server.py | 4 +- salt_docs/metadata/__init__.py | 2 +- salt_docs/metadata/logo.py | 4 +- salt_docs/metadata/project.py | 14 +- salt_docs/metadata/version.py | 2 +- salt_docs/nodes/__init__.py | 2 +- salt_docs/nodes/nodes.py | 14 +- salt_docs/utils/call_llm.py | 2 +- salt_docs/utils/version_check.py | 4 +- setup.py | 4 +- tests/__init__.py | 2 +- tests/cli/test_ci_integration.py | 32 ++-- tests/cli/test_cli.py | 20 +-- tests/cli/test_version_check.py | 74 ++++----- tests/llm/test_call_llm_routing.py | 8 +- tests/llm/test_config_integration.py | 4 +- tests/llm/test_llm_providers.py | 2 +- tests/mcp/test_mcp_tools.py | 16 +- tests/mcp/test_output_resources.py | 4 +- tests/mcp/test_search_index.py | 2 +- tests/mcp/test_semantic_search.py | 6 +- tests/mode/test_documentation_mode.py | 8 +- 44 files changed, 382 insertions(+), 382 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c361b8..5ed9c0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,16 +29,16 @@ jobs: - name: Lint with flake8 run: | - flake8 salt_docs --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 salt_docs --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 wikigen --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 wikigen --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Format check with black run: | - black --check salt_docs + black --check wikigen - name: Test with pytest run: | - pytest --cov=salt_docs --cov-report=xml + pytest --cov=wikigen --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e3c9f5..00d689c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,7 @@ jobs: repo: context.repo.repo, deployment_id: deployment.data.id, state: 'success', - environment_url: 'https://pypi.org/project/salt-docs/', + environment_url: 'https://pypi.org/project/wikigen/', description: 'Package published to PyPI' }); @@ -79,7 +79,7 @@ jobs: ## Installation \`\`\`bash - pip install salt-docs + pip install wikigen \`\`\` See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for detailed changes." diff --git a/.github/workflows/salt-docs.yml b/.github/workflows/salt-docs.yml index 2fed74b..1c44b48 100644 --- a/.github/workflows/salt-docs.yml +++ b/.github/workflows/salt-docs.yml @@ -1,4 +1,4 @@ -name: Salt Docs - Auto Documentation +name: WikiGen - Auto Documentation on: push: @@ -24,23 +24,23 @@ jobs: with: python-version: '3.12' - - name: Install Salt Docs + - name: Install WikiGen run: | - pip install salt-docs + pip install wikigen - - name: Configure Salt Docs + - name: Configure WikiGen run: | # Create config directory - mkdir -p ~/.config/saltdocs + mkdir -p ~/.config/wikigen # Create minimal config (API key will be from secrets) - cat > ~/.config/saltdocs/config.json << EOF + cat > ~/.config/wikigen/config.json << EOF { - "llm_provider": "${{ vars.SALT_LLM_PROVIDER || 'gemini' }}", - "llm_model": "${{ vars.SALT_LLM_MODEL || 'gemini-2.0-flash-exp' }}", + "llm_provider": "${{ vars.WIKIGEN_LLM_PROVIDER || 'gemini' }}", + "llm_model": "${{ vars.WIKIGEN_LLM_MODEL || 'gemini-2.0-flash-exp' }}", "output_dir": "output", - "language": "${{ vars.SALT_LANGUAGE || 'english' }}", - "max_abstractions": ${{ vars.SALT_MAX_ABSTRACTIONS || 10 }}, + "language": "${{ vars.WIKIGEN_LANGUAGE || 'english' }}", + "max_abstractions": ${{ vars.WIKIGEN_MAX_ABSTRACTIONS || 10 }}, "max_file_size": 100000, "use_cache": true, "include_patterns": [], @@ -52,7 +52,7 @@ jobs: run: | # Store API key in a temporary config (keyring not available in CI) # The key will be read from GitHub Secrets based on the provider - PROVIDER="${{ vars.SALT_LLM_PROVIDER || 'gemini' }}" + PROVIDER="${{ vars.WIKIGEN_LLM_PROVIDER || 'gemini' }}" case "$PROVIDER" in gemini) @@ -86,10 +86,10 @@ jobs: id: generate run: | # Set output path (default: docs/) - OUTPUT_PATH="${{ vars.SALT_OUTPUT_PATH || 'docs' }}" + OUTPUT_PATH="${{ vars.WIKIGEN_OUTPUT_PATH || 'docs' }}" - # Run salt-docs in CI mode with custom output path - salt-docs run . \ + # Run wikigen in CI mode with custom output path + wikigen run . \ --ci \ --output-path "$OUTPUT_PATH" \ --check-changes @@ -106,7 +106,7 @@ jobs: - name: Check for changes id: check_changes run: | - OUTPUT_PATH="${{ vars.SALT_OUTPUT_PATH || 'docs' }}" + OUTPUT_PATH="${{ vars.WIKIGEN_OUTPUT_PATH || 'docs' }}" if [ -n "$(git status --porcelain $OUTPUT_PATH)" ]; then echo "changes=true" >> $GITHUB_OUTPUT @@ -122,20 +122,20 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'docs: updated documentation for new changes' - branch: salt-docs/update-${{ github.run_number }} + branch: wikigen/update-${{ github.run_number }} delete-branch: true title: '📚 Update Documentation' body: | ## Automated Documentation Update - This PR contains automatically generated documentation updates from Salt Docs. + This PR contains automatically generated documentation updates from WikiGen. **Triggered by:** ${{ github.event.head_commit.message }} **Commit:** ${{ github.sha }} **Branch:** ${{ github.ref_name }} ### Changes - - Updated documentation in `${{ vars.SALT_OUTPUT_PATH || 'docs' }}/` + - Updated documentation in `${{ vars.WIKIGEN_OUTPUT_PATH || 'docs' }}/` ### Review Checklist - [ ] Documentation is accurate and up-to-date @@ -143,7 +143,7 @@ jobs: - [ ] Formatting is correct --- - *Generated by [Salt Docs](https://github.com/usesalt/salt-docs)* + *Generated by [WikiGen](https://github.com/usesalt/wikigen)* labels: | documentation automated diff --git a/.gitignore b/.gitignore index a9f9a04..a2ecb82 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ __pycache__/ # Python package build artifacts *.egg-info/ -salt_docs.egg-info/ +wikigen.egg-info/ # Environment files .env diff --git a/CHANGELOG.md b/CHANGELOG.md index 3334dc8..7ed7aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ All notable changes to this project will be documented in this file. - `--output-path`: Allows specifying a custom output directory (overriding config) - `--check-changes`: Exits with status code 1 if documentation changes are detected (for conditional PR creation) - `--update`: Merges new documentation with existing files instead of overwriting -- **GitHub Actions Workflow** - Added `salt-docs.yml` template for quick setup +- **GitHub Actions Workflow** - Added `wikigen.yml` template for quick setup - **Documentation** - Comprehensive CI/CD integration guide and examples ### Changed @@ -55,11 +55,11 @@ All notable changes to this project will be documented in this file. - **Comprehensive Mode** - Full detailed documentation with extensive explanations and examples - **New CLI Argument**: `--mode` - Override documentation mode for a single run - Options: `minimal` or `comprehensive` - - Example: `salt-docs --repo --mode comprehensive` + - Example: `wikigen --repo --mode comprehensive` - **Configuration Field**: `documentation_mode` - Set default documentation mode in config - Default: `minimal` - - Can be set via `salt-docs config set documentation-mode ` - - Interactive selection during `salt-docs init` setup wizard + - Can be set via `wikigen config set documentation-mode ` + - Interactive selection during `wikigen init` setup wizard - **Comprehensive Test Suite** - Full test coverage for documentation mode configuration ### Changed @@ -112,9 +112,9 @@ All notable changes to this project will be documented in this file. ### Changed - **Cross-platform config directory** - Migrated config file location to OS-appropriate directories - - macOS/Linux: `~/.config/saltdocs/config.json` (or `$XDG_CONFIG_HOME/saltdocs/config.json`) - - Windows: `%APPDATA%\saltdocs\config.json` - - Previous location: `~/Documents/Salt Docs/.salt/config.json` (no longer used) + - macOS/Linux: `~/.config/wikigen/config.json` (or `$XDG_CONFIG_HOME/wikigen/config.json`) + - Windows: `%APPDATA%\wikigen\config.json` + - Previous location: `~/Documents/WikiGen/.salt/config.json` (no longer used) ### Improved - Automatic migration from legacy config location on first load @@ -261,7 +261,7 @@ All notable changes to this project will be documented in this file. ## [0.1.0] ### Added -- Initial release of Salt Docs CLI +- Initial release of WikiGen CLI - AI-powered codebase analysis and documentation generation - Support for GitHub repositories and local directories - Configurable API key management with secure keyring storage @@ -271,9 +271,9 @@ All notable changes to this project will be documented in this file. - LLM response caching for improved performance ### Features -- `salt-docs init` - Initial setup wizard -- `salt-docs config` - Configuration management -- `salt-docs --repo ` - Analyze GitHub repository -- `salt-docs --dir ` - Analyze local directory +- `wikigen init` - Initial setup wizard +- `wikigen config` - Configuration management +- `wikigen --repo ` - Analyze GitHub repository +- `wikigen --dir ` - Analyze local directory - Support for Python 3.10+ environments - Integration with Google Gemini AI diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ecca2c4..9ecee56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ # Contributing -Thank you for your interest in contributing to Salt Docs. +Thank you for your interest in contributing to WikiGen. ## Development Setup 1. Clone the repository: ```bash -git clone https://github.com/usesalt/salt-docs.git -cd salt-docs +git clone https://github.com/usesalt/wikigen.git +cd wikigen ``` 2. Install development dependencies: diff --git a/README.md b/README.md index 11ff8e6..e0fbe72 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -![Salt Docs](assets/saltdocs.jpg) +![WikiGen](assets/wikigen.jpg) -## SALT DOCS +## WIKIGEN -[![PyPI](https://img.shields.io/badge/pypi-v0.2.4-blue)](https://pypi.org/project/salt-docs/) [![Python](https://img.shields.io/badge/python-3.12+-blue)](https://www.python.org/) [![Downloads](https://img.shields.io/badge/downloads-3k+-brightgreen)](https://pypi.org/project/salt-docs/) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![GitHub](https://img.shields.io/badge/github-usesalt%2Fsalt--docs-red)](https://github.com/usesalt/salt-docs) +[![PyPI](https://img.shields.io/badge/pypi-v0.2.4-blue)](https://pypi.org/project/wikigen/) [![Python](https://img.shields.io/badge/python-3.12+-blue)](https://www.python.org/) [![Downloads](https://img.shields.io/badge/downloads-3k+-brightgreen)](https://pypi.org/project/wikigen/) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![GitHub](https://img.shields.io/badge/github-usesalt%2Fwikigen-red)](https://github.com/usesalt/wikigen) -**Salt Docs** is a compact, human-readable documentation generator for codebases that minimizes tokens and makes structure easy for models to follow. +**WikiGen** is a compact, human-readable documentation generator for codebases that minimizes tokens and makes structure easy for models to follow. It's intended for **LLM input** as a drop-in, lossless representation of your existing codebase. @@ -15,13 +15,13 @@ It's intended for **LLM input** as a drop-in, lossless representation of your ex ### Option 1: Install from PyPI ```bash -pip install salt-docs +pip install wikigen ``` ### Option 2: Install from source ```bash -git clone https://github.com/usesalt/salt-docs.git -cd salt-docs +git clone https://github.com/usesalt/wikigen.git +cd wikigen pip install -e . ``` @@ -31,32 +31,32 @@ pip install -e . Run the setup wizard to configure your API keys and preferences: ```bash -salt-docs init +wikigen init ``` ### 2. Generate Documentation #### Analyze GitHub repository ```bash -salt-docs run https://github.com/username/repo +wikigen run https://github.com/username/repo ``` #### Analyze local directory ```bash -salt-docs run /path/to/your/codebase +wikigen run /path/to/your/codebase ``` #### With custom options ```bash -salt-docs run https://github.com/username/repo --output /custom/path --language spanish --max-abstractions 10 +wikigen run https://github.com/username/repo --output /custom/path --language spanish --max-abstractions 10 ``` ## Configuration -Salt Docs stores configuration in a per-user config file and uses your system's keyring for secure API key storage. +WikiGen stores configuration in a per-user config file and uses your system's keyring for secure API key storage. -- macOS/Linux: `~/.config/saltdocs/config.json` (or `$XDG_CONFIG_HOME/saltdocs/config.json`) -- Windows: `%APPDATA%\saltdocs\config.json` +- macOS/Linux: `~/.config/wikigen/config.json` (or `$XDG_CONFIG_HOME/wikigen/config.json`) +- Windows: `%APPDATA%\wikigen\config.json` ### Configuration Options - `llm_provider`: LLM provider to use (gemini, openai, anthropic, openrouter, ollama) - default: gemini @@ -74,59 +74,59 @@ Salt Docs stores configuration in a per-user config file and uses your system's #### View Current Configuration ```bash -salt-docs config show +wikigen config show ``` #### Update API Keys ```bash # Update API key for any provider (interactive) -salt-docs config update-api-key gemini -salt-docs config update-api-key openai -salt-docs config update-api-key anthropic -salt-docs config update-api-key openrouter +wikigen config update-api-key gemini +wikigen config update-api-key openai +wikigen config update-api-key anthropic +wikigen config update-api-key openrouter # Legacy command (still works, redirects to update-api-key) -salt-docs config update-gemini-key +wikigen config update-gemini-key # Update GitHub token (interactive) -salt-docs config update-github-token +wikigen config update-github-token # Update GitHub token directly -salt-docs config update-github-token "your-token-here" +wikigen config update-github-token "your-token-here" ``` #### Update Other Settings ```bash # Change LLM provider -salt-docs config set llm-provider openai +wikigen config set llm-provider openai # Change LLM model -salt-docs config set llm-model gpt-4o-mini +wikigen config set llm-model gpt-4o-mini # Change default language -salt-docs config set language spanish +wikigen config set language spanish # Change max abstractions -salt-docs config set max_abstractions 15 +wikigen config set max_abstractions 15 # Disable caching -salt-docs config set use_cache false +wikigen config set use_cache false # Update output directory -salt-docs config set output_dir /custom/path +wikigen config set output_dir /custom/path ``` --- ## CI/CD Integration -Salt Docs can automatically generate and update documentation in your CI/CD pipeline. Perfect for keeping docs in sync with code changes! +WikiGen can automatically generate and update documentation in your CI/CD pipeline. Perfect for keeping docs in sync with code changes! ### Quick Setup for GitHub Actions -1. **Add workflow file** to `.github/workflows/salt-docs.yml`: +1. **Add workflow file** to `.github/workflows/wikigen.yml`: ```yaml -name: Salt Docs +name: WikiGen on: push: @@ -143,14 +143,14 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.12' - - run: pip install salt-docs - - run: salt-docs run . --ci --output-path docs/ + - run: pip install wikigen + - run: wikigen run . --ci --output-path docs/ env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - uses: peter-evans/create-pull-request@v6 with: commit-message: 'docs: updated documentation for new changes' - branch: salt-docs/update-${{ github.run_number }} + branch: wikigen/update-${{ github.run_number }} title: 'Update Documentation' ``` @@ -179,7 +179,7 @@ See the complete [CI/CD Integration Guide](docs/ci-cd-integration.md) for: --- ## MCP Server Setup -Salt Docs includes an MCP (Model Context Protocol) server that exposes your generated documentation to AI assistants in IDEs like Cursor, Continue.dev, and Claude Desktop. +WikiGen includes an MCP (Model Context Protocol) server that exposes your generated documentation to AI assistants in IDEs like Cursor, Continue.dev, and Claude Desktop. ### MCP Tools Available @@ -197,13 +197,13 @@ The MCP server provides these tools: - **macOS/Linux**: `~/.cursor/mcp.json` - **Windows**: `%APPDATA%\Cursor\mcp.json` -2. Add the salt-docs server configuration: +2. Add the wikigen server configuration: ```json { "mcpServers": { - "salt-docs": { - "command": "salt-docs", + "wikigen": { + "command": "wikigen", "args": ["mcp"] } } @@ -214,7 +214,7 @@ The MCP server provides these tools: 4. The AI assistant in Cursor can now access your documentation using tools like: - "What documentation do we have?" - - "Get me the documentation for 'SALT project" + - "Get me the documentation for 'WIKIGEN project" - "Read the README documentation" #### Claude Desktop @@ -224,13 +224,13 @@ The MCP server provides these tools: - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` - **Linux**: `~/.config/Claude/claude_desktop_config.json` -2. Add the salt-docs server configuration: +2. Add the wikigen server configuration: ```json { "mcpServers": { - "salt-docs": { - "command": "salt-docs", + "wikigen": { + "command": "wikigen", "args": ["mcp"] } } @@ -241,23 +241,23 @@ The MCP server provides these tools: #### Troubleshooting -- **Command not found**: Make sure `salt-docs` is in your PATH. You can verify by running `salt-docs --version` in your terminal. -- **Server not starting**: Ensure you've run `salt-docs init` and have generated at least one documentation project. -- **No docs found**: The MCP server discovers docs from your configured `output_dir`. Run `salt-docs config show` to check your output directory. +- **Command not found**: Make sure `wikigen` is in your PATH. You can verify by running `wikigen --version` in your terminal. +- **Server not starting**: Ensure you've run `wikigen init` and have generated at least one documentation project. +- **No docs found**: The MCP server discovers docs from your configured `output_dir`. Run `wikigen config show` to check your output directory. ### Testing the MCP Server You can test the MCP server directly: ```bash -salt-docs mcp +wikigen mcp ``` This will start the server in stdio mode (for MCP clients). To test locally, you can use the test scripts in the `tests/` directory. ## LLM Provider Support -Salt Docs supports multiple LLM providers, allowing you to choose the best option for your needs: +WikiGen supports multiple LLM providers, allowing you to choose the best option for your needs: ### Supported Providers @@ -291,13 +291,13 @@ You can switch between providers at any time: ```bash # Switch to OpenAI -salt-docs config set llm-provider openai -salt-docs config set llm-model gpt-4o-mini -salt-docs config update-api-key openai +wikigen config set llm-provider openai +wikigen config set llm-model gpt-4o-mini +wikigen config update-api-key openai # Switch to Ollama (local) -salt-docs config set llm-provider ollama -salt-docs config set llm-model llama3.2 +wikigen config set llm-provider ollama +wikigen config set llm-model llama3.2 # No API key needed for Ollama! ``` diff --git a/RELEASE.md b/RELEASE.md index b6e7e0d..03f9a5b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,6 @@ # Release Guide -This document explains how to release new versions of Salt Docs CLI. +This document explains how to release new versions of WikiGen CLI. ## Prerequisites @@ -56,7 +56,7 @@ Once you create a tag or release, the GitHub Actions workflow will: - Go to **Actions** tab in GitHub to see the workflow progress - Check **PyPI** to see your package appear -- Test installation: `pip install salt-docs==0.1.0` +- Test installation: `pip install wikigen==0.1.0` ## Troubleshooting diff --git a/SECURITY.md b/SECURITY.md index c3d7518..9153df6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -43,12 +43,12 @@ We follow a coordinated disclosure process: ## Security Considerations -Salt Docs handles sensitive information including: +WikiGen handles sensitive information including: - API keys for LLM providers (stored in system keyring) - GitHub personal access tokens - Generated documentation from private repositories -When using Salt Docs: +When using WikiGen: - Never commit API keys or tokens to version control - Use environment variables or the secure keyring storage provided by the tool - Review the documentation generated from private repositories before sharing publicly @@ -60,5 +60,5 @@ When using Salt Docs: - Rotate API keys regularly - Use GitHub tokens with minimal required permissions - Review generated documentation for sensitive information before publishing -- Run Salt Docs in secure environments when processing sensitive codebases +- Run WikiGen in secure environments when processing sensitive codebases diff --git a/docs/CI.md b/docs/CI.md index 5668cb5..9c3ac82 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -1,15 +1,15 @@ # CI/CD Integration Guide -This guide explains how to integrate Salt Docs into your CI/CD pipeline to automatically generate and update documentation whenever code changes are pushed to your repository. +This guide explains how to integrate WikiGen into your CI/CD pipeline to automatically generate and update documentation whenever code changes are pushed to your repository. ## Quick Start ### 1. Add Workflow File -Create `.github/workflows/salt-docs.yml` in your repository: +Create `.github/workflows/wikigen.yml` in your repository: ```yaml -name: Salt Docs - Auto Documentation +name: WikiGen - Auto Documentation on: push: @@ -29,12 +29,12 @@ jobs: with: python-version: '3.12' - - name: Install Salt Docs - run: pip install salt-docs + - name: Install WikiGen + run: pip install wikigen - name: Generate Documentation run: | - salt-docs run . --ci --output-path docs/ + wikigen run . --ci --output-path docs/ env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} @@ -42,7 +42,7 @@ jobs: uses: peter-evans/create-pull-request@v6 with: commit-message: 'docs: updated documentation for new changes' - branch: salt-docs/update-${{ github.run_number }} + branch: wikigen/update-${{ github.run_number }} title: 'Update Documentation' ``` @@ -64,18 +64,18 @@ For additional customization, add repository variables: 1. Go to **Settings** → **Secrets and variables** → **Actions** → **Variables** 2. Add any of these optional variables: - - `SALT_LLM_PROVIDER` - LLM provider (default: `gemini`) - - `SALT_LLM_MODEL` - Model name (default: `gemini-2.0-flash-exp`) - - `SALT_OUTPUT_PATH` - Output directory (default: `docs`) - - `SALT_LANGUAGE` - Documentation language (default: `english`) - - `SALT_MAX_ABSTRACTIONS` - Max abstractions (default: `10`) + - `WIKIGEN_LLM_PROVIDER` - LLM provider (default: `gemini`) + - `WIKIGEN_LLM_MODEL` - Model name (default: `gemini-2.0-flash-exp`) + - `WIKIGEN_OUTPUT_PATH` - Output directory (default: `docs`) + - `WIKIGEN_LANGUAGE` - Documentation language (default: `english`) + - `WIKIGEN_MAX_ABSTRACTIONS` - Max abstractions (default: `10`) ## How It Works ```mermaid graph LR A[Push to main] --> B[Workflow Triggers] - B --> C[Install Salt Docs] + B --> C[Install WikiGen] C --> D[Generate Docs] D --> E{Changes?} E -->|Yes| F[Create PR] @@ -84,15 +84,15 @@ graph LR ``` 1. **Trigger**: Workflow runs on every push to main/master -2. **Setup**: Installs Python and Salt Docs -3. **Generate**: Runs `salt-docs run . --ci --output-path docs/` +2. **Setup**: Installs Python and WikiGen +3. **Generate**: Runs `wikigen run . --ci --output-path docs/` 4. **Detect Changes**: Checks if documentation was updated 5. **Create PR**: If changes detected, creates a PR with updated docs 6. **Review**: Team reviews and merges the PR ## CLI Flags for CI -Salt Docs provides special flags for CI/CD workflows: +WikiGen provides special flags for CI/CD workflows: | Flag | Description | |------|-------------| @@ -105,13 +105,13 @@ Salt Docs provides special flags for CI/CD workflows: ```bash # Basic CI usage -salt-docs run . --ci --output-path docs/ +wikigen run . --ci --output-path docs/ # Update existing docs -salt-docs run . --ci --update --output-path docs/ +wikigen run . --ci --update --output-path docs/ # Check for changes (for conditional PR creation) -salt-docs run . --ci --check-changes --output-path docs/ +wikigen run . --ci --check-changes --output-path docs/ if [ $? -eq 1 ]; then echo "Documentation changed" fi @@ -139,7 +139,7 @@ Generate docs in different locations: ```yaml - name: Generate Documentation run: | - salt-docs run . --ci --output-path documentation/ + wikigen run . --ci --output-path documentation/ ``` ### Conditional Execution @@ -164,24 +164,24 @@ on: ```yaml - name: Generate Documentation run: | - salt-docs run . --ci --output-path docs/ + wikigen run . --ci --output-path docs/ env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ``` -Set `SALT_LLM_PROVIDER=openai` and `SALT_LLM_MODEL=gpt-4o-mini` in repository variables. +Set `WIKIGEN_LLM_PROVIDER=openai` and `WIKIGEN_LLM_MODEL=gpt-4o-mini` in repository variables. #### Anthropic Claude ```yaml - name: Generate Documentation run: | - salt-docs run . --ci --output-path docs/ + wikigen run . --ci --output-path docs/ env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ``` -Set `SALT_LLM_PROVIDER=anthropic` and `SALT_LLM_MODEL=claude-3-5-sonnet-20241022` in repository variables. +Set `WIKIGEN_LLM_PROVIDER=anthropic` and `WIKIGEN_LLM_MODEL=claude-3-5-sonnet-20241022` in repository variables. ## Troubleshooting @@ -230,7 +230,7 @@ permissions: Require PR reviews before merging documentation updates: ```yaml -# .github/workflows/salt-docs.yml +# .github/workflows/wikigen.yml - name: Create Pull Request uses: peter-evans/create-pull-request@v6 with: @@ -279,7 +279,7 @@ on: ## Future Integrations -Salt Docs is designed to support multiple documentation platforms. Future versions will support: +WikiGen is designed to support multiple documentation platforms. Future versions will support: - **Confluence**: Automatic wiki page updates - **Notion**: Database and page synchronization @@ -293,7 +293,7 @@ To prepare for these integrations, use the `--output-path` flag to organize your ### Complete Workflow with All Features -See [`.github/workflows/salt-docs.yml`](file:///.github/workflows/salt-docs.yml) in this repository for a complete example with: +See [`.github/workflows/wikigen.yml`](file:///.github/workflows/wikigen.yml) in this repository for a complete example with: - Multi-provider support - Configurable options - Error handling @@ -317,8 +317,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - - run: pip install salt-docs - - run: salt-docs run . --ci --output-path docs/ + - run: pip install wikigen + - run: wikigen run . --ci --output-path docs/ env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - uses: peter-evans/create-pull-request@v6 @@ -330,5 +330,5 @@ jobs: ## Support For issues or questions: -- GitHub Issues: [usesalt/salt-docs](https://github.com/usesalt/salt-docs/issues) +- GitHub Issues: [usesalt/wikigen](https://github.com/usesalt/wikigen/issues) - Documentation: [README.md](../README.md) diff --git a/docs/SALT-DOCS.md b/docs/SALT-DOCS.md index 51d63c1..bbc6975 100644 --- a/docs/SALT-DOCS.md +++ b/docs/SALT-DOCS.md @@ -1,6 +1,6 @@ -# salt-docs +# wikigen -`salt-docs` is a **local-first** CLI tool designed to _auto-generate wikis and documentation_ from codebases, addressing the challenge of context loss across repositories. Its core functionality is driven by the **Documentation Generation Pipeline**, which _crawls and parses_ source code (from local directories or GitHub), employs **Large Language Models (LLMs) via a BYOK (Bring Your Own Key) model** to _identify and extract key abstractions_, and then _formats this information into structured markdown files_. +`wikigen` is a **local-first** CLI tool designed to _auto-generate wikis and documentation_ from codebases, addressing the challenge of context loss across repositories. Its core functionality is driven by the **Documentation Generation Pipeline**, which _crawls and parses_ source code (from local directories or GitHub), employs **Large Language Models (LLMs) via a BYOK (Bring Your Own Key) model** to _identify and extract key abstractions_, and then _formats this information into structured markdown files_. A pivotal architectural component is the **MCP Server & AI Integration**, which establishes a _local Model Context Protocol server_. This server exposes the generated documentation as a set of _queryable tools_ (`list_docs`, `get_docs`, `search_docs`, `index_directories`) to AI coding assistants like Cursor, Continue, and Claude Desktop, enabling them to retrieve _project-specific, relevant context_ directly from the codebase's documentation. @@ -36,7 +36,7 @@ flowchart TD ## Component 1: Local Configuration Management -This component is the bedrock for personalizing and securing the `salt-docs` experience for every engineer. In complex development environments, engineers juggle numerous projects, often requiring unique settings, API keys, and preferences. The **Local Configuration Management** component addresses the critical need for a robust, secure, and user-friendly system to manage all user-specific operational parameters, ensuring both ease of use and stringent privacy. +This component is the bedrock for personalizing and securing the `wikigen` experience for every engineer. In complex development environments, engineers juggle numerous projects, often requiring unique settings, API keys, and preferences. The **Local Configuration Management** component addresses the critical need for a robust, secure, and user-friendly system to manage all user-specific operational parameters, ensuring both ease of use and stringent privacy. ### Core Responsibilities @@ -44,9 +44,9 @@ The primary goal of this abstraction is to handle all aspects of user configurat * **Secure Credential Storage**: Safely stores sensitive information like Gemini API keys and GitHub tokens using OS-native keyring services, preventing plain-text storage and unauthorized access. * **Persistent User Preferences**: Manages user-defined settings such as default output directories, preferred languages, caching preferences, and file pattern filters. -* **OS-Appropriate Directory Handling**: Ensures configuration files are stored in standard, OS-specific locations (e.g., `~/.config/saltdocs` on macOS/Linux and `%APPDATA%\saltdocs` on Windows) for consistency and discoverability. -* **Interactive Initial Setup**: Provides a guided wizard (`salt-docs init`) for first-time users to set up essential configurations and credentials effortlessly. -* **Configuration Management CLI**: Offers a comprehensive command-line interface (`salt-docs config`) for viewing, updating, and resetting specific configuration parameters. +* **OS-Appropriate Directory Handling**: Ensures configuration files are stored in standard, OS-specific locations (e.g., `~/.config/wikigen` on macOS/Linux and `%APPDATA%\wikigen` on Windows) for consistency and discoverability. +* **Interactive Initial Setup**: Provides a guided wizard (`wikigen init`) for first-time users to set up essential configurations and credentials effortlessly. +* **Configuration Management CLI**: Offers a comprehensive command-line interface (`wikigen config`) for viewing, updating, and resetting specific configuration parameters. * **Automatic Configuration Migration**: Gracefully handles transitions from older configuration formats or locations, ensuring backward compatibility and a seamless upgrade experience. * **Upholding Privacy**: A foundational principle is that all user data and credentials remain local to the machine, never being transmitted to external services beyond direct interaction with the respective API providers (e.g., Google Gemini). @@ -59,14 +59,14 @@ Sensitive data, particularly API keys and tokens, are never stored directly in t #### OS-Appropriate Configuration Paths Adhering to operating system conventions is crucial for maintainability and user expectations. -* On **macOS and Linux**, configurations are stored under `~/.config/saltdocs/config.json` or `$XDG_CONFIG_HOME/saltdocs/config.json`. -* On **Windows**, it utilizes `%APPDATA%\saltdocs\config.json`. +* On **macOS and Linux**, configurations are stored under `~/.config/wikigen/config.json` or `$XDG_CONFIG_HOME/wikigen/config.json`. +* On **Windows**, it utilizes `%APPDATA%\wikigen\config.json`. -This ensures that `salt-docs` integrates cleanly with the user's environment. The `CHANGELOG.md` reflects this significant improvement in version `0.1.8`. +This ensures that `wikigen` integrates cleanly with the user's environment. The `CHANGELOG.md` reflects this significant improvement in version `0.1.8`. #### Interactive Setup Wizard -The `salt-docs init` command guides users through a simple, interactive process to set up their environment. This includes prompting for necessary API keys (with input masking) and setting default preferences, dramatically reducing the barrier to entry for new users. +The `wikigen init` command guides users through a simple, interactive process to set up their environment. This includes prompting for necessary API keys (with input masking) and setting default preferences, dramatically reducing the barrier to entry for new users. #### Configuration Schema @@ -74,7 +74,7 @@ The `config.json` file stores non-sensitive preferences. A typical configuration ```json { - "output_dir": "/Users/user/Documents/Salt Docs", + "output_dir": "/Users/user/Documents/WikiGen", "language": "english", "max_abstractions": 5, "max_file_size": 102400, @@ -85,7 +85,7 @@ The `config.json` file stores non-sensitive preferences. A typical configuration } ``` -These settings are fully configurable via the `salt-docs config set` commands. +These settings are fully configurable via the `wikigen config set` commands. ### Internal Implementation @@ -96,14 +96,14 @@ The **Local Configuration Management** component operates through a central conf ```mermaid sequenceDiagram participant User - participant CLI as salt-docs CLI + participant CLI as wikigen CLI participant ConfigMgr as Configuration Manager participant FileSys as Filesystem (config.json) participant Keyring as System Keyring - User->>CLI: `salt-docs init` or `salt-docs config` + User->>CLI: `wikigen init` or `wikigen config` CLI->>ConfigMgr: Initialize/Load config - ConfigMgr->>FileSys: Check/Create config file (e.g., ~/.config/saltdocs/config.json) + ConfigMgr->>FileSys: Check/Create config file (e.g., ~/.config/wikigen/config.json) FileSys-->>ConfigMgr: Returns config data alt If sensitive data requested (e.g., API key) ConfigMgr->>Keyring: Retrieve credential (e.g., Gemini API Key) @@ -140,10 +140,10 @@ def get_config_path() -> str: """Determines the OS-appropriate configuration directory.""" if sys.platform == "win32": base_dir = os.environ.get("APPDATA") - config_dir = os.path.join(base_dir, "saltdocs") + config_dir = os.path.join(base_dir, "wikigen") # ... similar logic for macOS using ~/Library/Application Support ... else: # Example for Linux/other standard path - config_dir = os.path.join(os.path.expanduser("~"), ".config", "saltdocs") + config_dir = os.path.join(os.path.expanduser("~"), ".config", "wikigen") os.makedirs(config_dir, exist_ok=True) return os.path.join(config_dir, "config.json") @@ -167,7 +167,7 @@ def load_config(config_path: str) -> dict: with open(config_path, 'r') as f: config_data.update(json.load(f)) # Merge existing config - config_data.setdefault("output_dir", os.path.join(os.path.expanduser("~"), "Documents", "Salt Docs")) + config_data.setdefault("output_dir", os.path.join(os.path.expanduser("~"), "Documents", "WikiGen")) config_data.setdefault("language", "english") # ... other defaults ... return config_data @@ -197,7 +197,7 @@ Interaction with the system keyring is typically abstracted through a dedicated ```python # Part of a larger module, depends on the 'keyring' library -SERVICE_NAME = "salt-docs" +SERVICE_NAME = "wikigen" def get_secret(key_name: str) -> str | None: """Retrieves a secret from the system keyring.""" @@ -211,14 +211,14 @@ def set_secret(key_name: str, value: str): ``` *Explanation*: These functions provide a simple interface for interacting with the system keyring. `SERVICE_NAME` helps categorize credentials within the keyring, and the actual keyring calls are abstracted away in this simplified representation. -#### Initial Setup Wizard (`salt-docs init`) +#### Initial Setup Wizard (`wikigen init`) The `init` command orchestrates the initial configuration by calling the above functions interactively. It prompts the user for necessary information, uses `set_secret` for API keys, and `save_config` for general preferences. ```python # Simplified snippet from init.py, depends on 'rich.prompt' and config functions def run_init_wizard(): - print("Welcome to Salt Docs CLI setup wizard!") + print("Welcome to WikiGen CLI setup wizard!") # Prompt for Gemini API Key gemini_key = Prompt.ask("Enter your Gemini API key", password=True) @@ -234,14 +234,14 @@ def run_init_wizard(): ### Concrete Usage Examples -The `salt-docs` CLI provides direct access to manage configurations. +The `wikigen` CLI provides direct access to manage configurations. #### 1. Initial Setup with Wizard First-time users will run the interactive setup wizard: ```bash -salt-docs init +wikigen init ``` This command will guide you through setting up your Gemini API key, GitHub token (optional), and other core preferences, saving them securely. @@ -250,12 +250,12 @@ This command will guide you through setting up your Gemini API key, GitHub token To inspect your current non-sensitive settings: ```bash -salt-docs config show +wikigen config show ``` Output (example): ```json { - "output_dir": "/Users/user/Documents/Salt Docs", + "output_dir": "/Users/user/Documents/WikiGen", "language": "english", "max_abstractions": 5, "max_file_size": 102400, @@ -271,20 +271,20 @@ Output (example): To update your Gemini API key: ```bash -salt-docs config update-gemini-key +wikigen config update-gemini-key # You will be prompted to enter the new key securely. ``` Alternatively, for non-interactive updates (e.g., in scripts, though generally less secure for secrets): ```bash -salt-docs config update-gemini-key "your-new-gemini-api-key-here" +wikigen config update-gemini-key "your-new-gemini-api-key-here" ``` Similarly for the GitHub token: ```bash -salt-docs config update-github-token +wikigen config update-github-token ``` #### 4. Modifying General Settings @@ -292,24 +292,24 @@ salt-docs config update-github-token To change the default language for generated documentation to Spanish: ```bash -salt-docs config set language spanish +wikigen config set language spanish ``` To disable LLM response caching: ```bash -salt-docs config set use_cache false +wikigen config set use_cache false ``` To update the default output directory: ```bash -salt-docs config set output_dir /path/to/my/custom/docs +wikigen config set output_dir /path/to/my/custom/docs ``` ### Key Takeaways -The **Local Configuration Management** component is essential for providing a personalized, secure, and user-friendly experience within `salt-docs`. It effectively: +The **Local Configuration Management** component is essential for providing a personalized, secure, and user-friendly experience within `wikigen`. It effectively: * **Secures sensitive credentials** through system keyring integration. * **Manages user preferences** in OS-appropriate file system locations. @@ -323,7 +323,7 @@ This component forms the foundational layer upon which other features like the [ ## Component 2: Documentation Generation Pipeline -Building upon the foundational settings established by the [Local Configuration Management](01_local_configuration_management_.md) component, the **Documentation Generation Pipeline** is the core engine of `salt-docs`. It transforms raw source code into intelligent, structured, and human-readable documentation. +Building upon the foundational settings established by the [Local Configuration Management](01_local_configuration_management_.md) component, the **Documentation Generation Pipeline** is the core engine of `wikigen`. It transforms raw source code into intelligent, structured, and human-readable documentation. ### Core Responsibilities @@ -368,7 +368,7 @@ The following sequence diagram illustrates the high-level flow when a user initi ```mermaid sequenceDiagram participant User - participant CLI as salt-docs CLI + participant CLI as wikigen CLI participant PipelineMgr as Pipeline Manager participant ConfigMgr as Config Manager participant CodeCrawler as Code Crawler @@ -376,7 +376,7 @@ sequenceDiagram participant DocFormatter as Doc Formatter participant FileSys as Filesystem - User->>CLI: `salt-docs --repo ` or `--dir ` + User->>CLI: `wikigen --repo ` or `--dir ` CLI->>PipelineMgr: Initiate generation(source, opts) PipelineMgr->>ConfigMgr: Load configuration & credentials ConfigMgr-->>PipelineMgr: Returns config (output_dir, api_key, filters) @@ -398,16 +398,16 @@ sequenceDiagram #### Code Walkthrough -The core logic of the pipeline is typically initiated by the `salt-docs` CLI command, which then delegates to a central pipeline manager. +The core logic of the pipeline is typically initiated by the `wikigen` CLI command, which then delegates to a central pipeline manager. ##### 1. Entry Point and Configuration Loading The CLI command parses user arguments and then calls the pipeline's main function, which immediately loads the necessary configuration from the [Local Configuration Management](01_local_configuration_management_.md) component. ```python -# Simplified excerpt from salt_docs/cli.py -from salt_docs.config import load_config, get_secret, get_config_path -from salt_docs.pipeline import run_documentation_pipeline +# Simplified excerpt from wikigen/cli.py +from wikigen.config import load_config, get_secret, get_config_path +from wikigen.pipeline import run_documentation_pipeline def main(): # ... argument parsing for repo/dir, output, language, etc. @@ -432,14 +432,14 @@ def main(): if __name__ == "__main__": main() ``` -*Explanation*: The `main` function in the CLI module acts as the entry point. It first loads the global configuration and any sensitive API keys using functions from `salt_docs.config` (Component 1). It then prepares these parameters, potentially overriding defaults with command-line arguments, and passes them to the `run_documentation_pipeline` function which orchestrates the generation process. +*Explanation*: The `main` function in the CLI module acts as the entry point. It first loads the global configuration and any sensitive API keys using functions from `wikigen.config` (Component 1). It then prepares these parameters, potentially overriding defaults with command-line arguments, and passes them to the `run_documentation_pipeline` function which orchestrates the generation process. ##### 2. Codebase Crawling The pipeline's `CodeCrawler` component is responsible for recursively scanning directories or cloning repositories, filtering files based on configured patterns. ```python -# Simplified excerpt from salt_docs/pipeline.py (or a crawler module) +# Simplified excerpt from wikigen/pipeline.py (or a crawler module) import os def crawl_codebase(source_path: str, include_patterns: list, exclude_patterns: list, max_size: int) -> dict: @@ -464,8 +464,8 @@ def crawl_codebase(source_path: str, include_patterns: list, exclude_patterns: l This stage involves making calls to the LLM. Caching is crucial here to prevent redundant API calls. ```python -# Simplified excerpt from salt_docs/llm_service.py -from salt_docs.cache import get_cache, set_cache # From Component 1 context for caching +# Simplified excerpt from wikigen/llm_service.py +from wikigen.cache import get_cache, set_cache # From Component 1 context for caching def identify_abstractions_with_llm(code_content: str, api_key: str, use_cache: bool) -> dict: """Uses LLM to identify abstractions, with caching.""" @@ -494,7 +494,7 @@ def identify_abstractions_with_llm(code_content: str, api_key: str, use_cache: b The structured output from the LLM is then transformed into formatted Markdown. ```python -# Simplified excerpt from salt_docs/doc_formatter.py +# Simplified excerpt from wikigen/doc_formatter.py def format_to_markdown(llm_output: dict, language: str) -> str: """Converts structured LLM output into Markdown.""" markdown_content = f"# {llm_output['component_name']}\n\n" @@ -513,23 +513,23 @@ def format_to_markdown(llm_output: dict, language: str) -> str: ### Concrete Usage Examples -Engineers interact with the Documentation Generation Pipeline primarily through the `salt-docs` CLI. +Engineers interact with the Documentation Generation Pipeline primarily through the `wikigen` CLI. #### 1. Generating Docs for a GitHub Repository -To generate documentation for a public GitHub repository, `salt-docs` will clone it temporarily, process it, and then delete the temporary clone. Your GitHub token (if configured via `salt-docs init` or `salt-docs config update-github-token`) will be used for private repositories. +To generate documentation for a public GitHub repository, `wikigen` will clone it temporarily, process it, and then delete the temporary clone. Your GitHub token (if configured via `wikigen init` or `wikigen config update-github-token`) will be used for private repositories. ```bash -salt-docs --repo https://github.com/my-org/my-project +wikigen --repo https://github.com/my-org/my-project ``` -This command processes the specified GitHub repository and saves the generated Markdown files to your configured `output_dir` (e.g., `~/Documents/Salt Docs/my-project`). +This command processes the specified GitHub repository and saves the generated Markdown files to your configured `output_dir` (e.g., `~/Documents/WikiGen/my-project`). #### 2. Generating Docs for a Local Directory For codebases residing on your local machine: ```bash -salt-docs --dir /path/to/my/local/codebase +wikigen --dir /path/to/my/local/codebase ``` This command scans the local directory and generates documentation, again saving it to the default or specified output directory. @@ -538,7 +538,7 @@ This command scans the local directory and generates documentation, again saving You can override default settings (established by [Local Configuration Management](01_local_configuration_management_.md)) directly via CLI flags for specific runs: ```bash -salt-docs --dir /path/to/project \ +wikigen --dir /path/to/project \ --output /custom/docs/output \ --language spanish \ --include "*.py" "*.txt" \ @@ -550,7 +550,7 @@ This example shows how to direct the output to a custom path, generate documenta ### Key Takeaways -The **Documentation Generation Pipeline** is the dynamic heart of `salt-docs`, effectively addressing the challenge of maintaining current and comprehensive codebase documentation. +The **Documentation Generation Pipeline** is the dynamic heart of `wikigen`, effectively addressing the challenge of maintaining current and comprehensive codebase documentation. * It orchestrates the **end-to-end process** from source code ingestion to formatted Markdown output. * It leverages **LLMs for intelligent analysis**, identifying core abstractions and their context. @@ -565,7 +565,7 @@ This component transforms your codebase into an accessible and queryable documen ## Component 3: Fast Documentation Search -Building upon the structured documentation produced by the [Documentation Generation Pipeline](02_documentation_generation_pipeline_.md), the **Fast Documentation Search** component introduces a critical capability: the ability to rapidly locate relevant information across all generated Markdown files. In large codebases with extensive documentation, finding specific details quickly is paramount for both human engineers and AI assistants. This component ensures that the rich context generated by `salt-docs` is not just available, but also highly accessible. +Building upon the structured documentation produced by the [Documentation Generation Pipeline](02_documentation_generation_pipeline_.md), the **Fast Documentation Search** component introduces a critical capability: the ability to rapidly locate relevant information across all generated Markdown files. In large codebases with extensive documentation, finding specific details quickly is paramount for both human engineers and AI assistants. This component ensures that the rich context generated by `wikigen` is not just available, but also highly accessible. ### Core Responsibilities @@ -595,11 +595,11 @@ This makes it an ideal choice for searching through natural language documentati #### Automatic Indexing -When `salt-docs` is run or a search is initiated for the first time in a given directory, the system automatically indexes all relevant Markdown files. This process involves: +When `wikigen` is run or a search is initiated for the first time in a given directory, the system automatically indexes all relevant Markdown files. This process involves: 1. Scanning the configured `output_dir` (from [Local Configuration Management](01_local_configuration_management_.md)). 2. Reading the content of each Markdown file. 3. Inserting the file's path, name, and content into the SQLite FTS5 index. -This indexing process creates a `.saltdocs-search.db` SQLite database within the `output_dir` to store the FTS5 table, ensuring locality and speed. +This indexing process creates a `.wikigen-search.db` SQLite database within the `output_dir` to store the FTS5 table, ensuring locality and speed. #### Search Scope and `search_docs` Tool @@ -625,7 +625,7 @@ sequenceDiagram UserAI->>MCPServer: Request `search_docs("query")` MCPServer->>SearchIndex: `perform_search("query")` - SearchIndex->>FileSys: Check/Load search DB (`.saltdocs-search.db`) + SearchIndex->>FileSys: Check/Load search DB (`.wikigen-search.db`) alt If index is outdated or missing SearchIndex->>FileSys: Scan `output_dir` for Markdown files FileSys-->>SearchIndex: Return file paths and content @@ -646,13 +646,13 @@ The core logic for indexing and searching resides within a `search_index.py` mod The system first ensures that a search database exists and that the documentation directory is indexed. If not, it creates the SQLite database and an FTS5 table, then populates it. ```python -# Simplified excerpt from salt_docs/search_index.py +# Simplified excerpt from wikigen/search_index.py import sqlite3 import os def ensure_index_ready(docs_dir: str): """Ensures the search database and FTS5 table are initialized and up-to-date.""" - db_path = os.path.join(docs_dir, ".saltdocs-search.db") + db_path = os.path.join(docs_dir, ".wikigen-search.db") conn = sqlite3.connect(db_path) cursor = conn.cursor() @@ -690,10 +690,10 @@ def ensure_index_ready(docs_dir: str): Once the index is ready, performing a search is a straightforward FTS5 query. ```python -# Simplified excerpt from salt_docs/search_index.py +# Simplified excerpt from wikigen/search_index.py def search_documentation(docs_dir: str, query: str) -> list[dict]: """Performs a full-text search on the indexed documentation.""" - db_path = os.path.join(docs_dir, ".saltdocs-search.db") + db_path = os.path.join(docs_dir, ".wikigen-search.db") conn = sqlite3.connect(db_path) cursor = conn.cursor() @@ -740,15 +740,15 @@ The AI would then translate this into an MCP call: } } ``` -The underlying `search_documentation` function in `salt-docs` would execute, returning relevant Markdown files and snippets. +The underlying `search_documentation` function in `wikigen` would execute, returning relevant Markdown files and snippets. #### 2. Manually Indexing Directories -While indexing often happens automatically, you can trigger a manual re-indexing of a directory if needed, for instance, after a large batch of new documentation is added outside of a `salt-docs` generation run. The `index_directories` MCP tool can be used for this. +While indexing often happens automatically, you can trigger a manual re-indexing of a directory if needed, for instance, after a large batch of new documentation is added outside of a `wikigen` generation run. The `index_directories` MCP tool can be used for this. ```bash # This is how the MCP Server might call it internally or if exposed as a CLI command -salt-docs mcp --tool index_directories --args '{"directory": "/Users/user/Documents/Salt Docs/my-project"}' +wikigen mcp --tool index_directories --args '{"directory": "/Users/user/Documents/WikiGen/my-project"}' ``` This command would ensure the specified directory's documentation is fully indexed and ready for fast searching. @@ -769,19 +769,19 @@ This component ensures that the effort put into generating detailed documentatio ## Component 4: MCP Server & AI Integration -Building directly on the structured and searchable documentation provided by the [Fast Documentation Search](03_fast_documentation_search_.md) component, the **MCP Server & AI Integration** is the crucial bridge that connects your meticulously generated `salt-docs` context to the power of modern AI coding assistants. In an era where AI is becoming an indispensable part of the development workflow, enabling these assistants to understand and reference project-specific nuances is paramount. +Building directly on the structured and searchable documentation provided by the [Fast Documentation Search](03_fast_documentation_search_.md) component, the **MCP Server & AI Integration** is the crucial bridge that connects your meticulously generated `wikigen` context to the power of modern AI coding assistants. In an era where AI is becoming an indispensable part of the development workflow, enabling these assistants to understand and reference project-specific nuances is paramount. ### Core Responsibilities -The primary goal of the MCP Server is to make the `salt-docs` knowledge base directly queryable by AI coding assistants running in the engineer's local environment. It addresses the common problem where AI, despite its capabilities, often lacks specific, up-to-date project context, leading to generic or inaccurate suggestions. +The primary goal of the MCP Server is to make the `wikigen` knowledge base directly queryable by AI coding assistants running in the engineer's local environment. It addresses the common problem where AI, despite its capabilities, often lacks specific, up-to-date project context, leading to generic or inaccurate suggestions. Its core responsibilities include: * **Local AI Integration**: Providing a local server endpoint that adheres to the Model Context Protocol (MCP), enabling seamless communication with AI assistants like Cursor, Continue, and Claude Desktop. -* **Tool Exposure**: Exposing `salt-docs`'s documentation management capabilities as a defined set of AI tools, including `list_docs`, `get_docs`, `search_docs`, and `index_directories`. +* **Tool Exposure**: Exposing `wikigen`'s documentation management capabilities as a defined set of AI tools, including `list_docs`, `get_docs`, `search_docs`, and `index_directories`. * **Contextual Querying**: Allowing AI assistants to query the generated documentation and retrieve specific project context, abstractions, and code explanations. * **Enhanced AI Accuracy**: Significantly improving the relevance and accuracy of AI-generated responses by providing direct access to the project's internal documentation. -* **Privacy Assurance**: Ensuring all interactions remain local to the engineer's machine, with no documentation or codebase context being transmitted externally via the `salt-docs` server. +* **Privacy Assurance**: Ensuring all interactions remain local to the engineer's machine, with no documentation or codebase context being transmitted externally via the `wikigen` server. ### Key Concepts @@ -789,18 +789,18 @@ The integration relies on the Model Context Protocol and a defined set of tools #### Model Context Protocol (MCP) -The **Model Context Protocol (MCP)** is a standardized way for local AI coding assistants to interact with the user's development environment. It defines a communication layer that allows these assistants to discover and invoke "tools" or "capabilities" exposed by local services. By implementing an MCP server, `salt-docs` transforms its documentation features into actionable functions that AI assistants can call to retrieve information. This is highlighted as a major feature addition in `CHANGELOG.md` version `0.1.7`. +The **Model Context Protocol (MCP)** is a standardized way for local AI coding assistants to interact with the user's development environment. It defines a communication layer that allows these assistants to discover and invoke "tools" or "capabilities" exposed by local services. By implementing an MCP server, `wikigen` transforms its documentation features into actionable functions that AI assistants can call to retrieve information. This is highlighted as a major feature addition in `CHANGELOG.md` version `0.1.7`. #### Local-First Architecture -A fundamental principle of `salt-docs` is its **local-first architecture**. The MCP server runs directly on the engineer's machine, alongside their IDE and AI assistant. This ensures that: +A fundamental principle of `wikigen` is its **local-first architecture**. The MCP server runs directly on the engineer's machine, alongside their IDE and AI assistant. This ensures that: * **Data Privacy**: All project documentation and code context remain entirely local, never leaving the machine. * **Speed**: Interactions between the AI assistant and the documentation server are fast, without network latency. * **Security**: No sensitive project data is transmitted to external servers, enhancing the overall security posture. #### Defined Set of Tools -The `salt-docs` MCP server exposes four primary tools that AI assistants can leverage: +The `wikigen` MCP server exposes four primary tools that AI assistants can leverage: * `list_docs`: Allows the AI to enumerate all generated documentation files within the configured output directory. This helps the AI understand the scope of available documentation. * `get_docs`: Retrieves the full content of a specific documentation file, identified by its resource name or absolute file path. This is crucial for the AI to "read" relevant documents. @@ -809,11 +809,11 @@ The `salt-docs` MCP server exposes four primary tools that AI assistants can lev #### AI Assistant Integration -Modern AI assistants like Cursor, Continue, and Claude Desktop provide mechanisms to configure local MCP servers. By adding `salt-docs` as an MCP server, these assistants automatically discover and integrate the `list_docs`, `get_docs`, `search_docs`, and `index_directories` tools into their capabilities. This allows engineers to naturally ask questions like "How does the `Local Configuration Management` component work?" or "Search the docs for API key handling," and the AI can use the `salt-docs` tools to provide highly accurate, project-specific answers. +Modern AI assistants like Cursor, Continue, and Claude Desktop provide mechanisms to configure local MCP servers. By adding `wikigen` as an MCP server, these assistants automatically discover and integrate the `list_docs`, `get_docs`, `search_docs`, and `index_directories` tools into their capabilities. This allows engineers to naturally ask questions like "How does the `Local Configuration Management` component work?" or "Search the docs for API key handling," and the AI can use the `wikigen` tools to provide highly accurate, project-specific answers. ### Internal Implementation -The MCP Server operates as a long-running process that listens for tool call requests from AI clients via standard input/output (stdio). It then dispatches these requests to the appropriate `salt-docs` internal functions. +The MCP Server operates as a long-running process that listens for tool call requests from AI clients via standard input/output (stdio). It then dispatches these requests to the appropriate `wikigen` internal functions. #### Workflow Overview @@ -838,15 +838,15 @@ sequenceDiagram #### Code Walkthrough -The `salt-docs mcp` command initiates the MCP server. This server enters a loop, processing incoming JSON messages (tool calls) from the AI client and responding with results. +The `wikigen mcp` command initiates the MCP server. This server enters a loop, processing incoming JSON messages (tool calls) from the AI client and responding with results. ##### 1. MCP Server Entry Point -The `mcp` subcommand in `salt-docs` launches the server in stdio mode. +The `mcp` subcommand in `wikigen` launches the server in stdio mode. ```python -# Simplified excerpt from salt_docs/cli.py -from salt_docs.mcp_server import run_mcp_server +# Simplified excerpt from wikigen/cli.py +from wikigen.mcp_server import run_mcp_server def main(): # ... argument parsing ... @@ -854,18 +854,18 @@ def main(): run_mcp_server() # ... other commands ... ``` -*Explanation*: When `salt-docs mcp` is executed, the `run_mcp_server` function is called. This function encapsulates the logic for listening to stdio, parsing MCP requests, and dispatching them. +*Explanation*: When `wikigen mcp` is executed, the `run_mcp_server` function is called. This function encapsulates the logic for listening to stdio, parsing MCP requests, and dispatching them. ##### 2. MCP Server Loop and Tool Dispatch -The `run_mcp_server` function typically contains a loop that reads messages, identifies the requested tool, and invokes the corresponding `salt-docs` internal function. +The `run_mcp_server` function typically contains a loop that reads messages, identifies the requested tool, and invokes the corresponding `wikigen` internal function. ```python -# Simplified excerpt from salt_docs/mcp_server.py +# Simplified excerpt from wikigen/mcp_server.py import json -from salt_docs.output_resources import list_documentation_files, get_document_content -from salt_docs.search_index import search_documentation, ensure_index_ready -from salt_docs.config import get_config_path, load_config +from wikigen.output_resources import list_documentation_files, get_document_content +from wikigen.search_index import search_documentation, ensure_index_ready +from wikigen.config import get_config_path, load_config def run_mcp_server(): config_path = get_config_path() @@ -901,7 +901,7 @@ def run_mcp_server(): The `output_resources.py` module (renamed from `doc_discovery.py` in `CHANGELOG.md` version `0.1.7`) provides functions to interact with the generated documentation files. ```python -# Simplified excerpt from salt_docs/output_resources.py +# Simplified excerpt from wikigen/output_resources.py import os def list_documentation_files(docs_dir: str) -> list[str]: @@ -931,31 +931,31 @@ def get_document_content(docs_dir: str, resource_name: str = None, file_path: st ### Concrete Usage Examples -Engineers integrate with the MCP Server by configuring their AI assistant to use `salt-docs`. +Engineers integrate with the MCP Server by configuring their AI assistant to use `wikigen`. #### 1. Setting up MCP with AI Assistants (e.g., Cursor) -As detailed in the `README.md`, setting up `salt-docs` as an MCP server is straightforward. +As detailed in the `README.md`, setting up `wikigen` as an MCP server is straightforward. -First, ensure `salt-docs` is installed and `salt-docs init` has been run, and you've generated some documentation (e.g., `salt-docs --dir /path/to/my/project`). +First, ensure `wikigen` is installed and `wikigen init` has been run, and you've generated some documentation (e.g., `wikigen --dir /path/to/my/project`). Then, configure your AI assistant. For Cursor, this involves modifying `~/.cursor/mcp.json` (or `%APPDATA%\Cursor\mcp.json` on Windows): ```json { "mcpServers": { - "salt-docs": { - "command": "salt-docs", + "wikigen": { + "command": "wikigen", "args": ["mcp"] } } } ``` -After restarting Cursor, the AI will automatically detect and load the `salt-docs` tools. Similar configurations apply to Claude Desktop (see `README.md` for paths). +After restarting Cursor, the AI will automatically detect and load the `wikigen` tools. Similar configurations apply to Claude Desktop (see `README.md` for paths). #### 2. Interacting with AI for Documentation -Once configured, engineers can use natural language queries within their AI assistant, and the AI will invoke the appropriate `salt-docs` MCP tool. +Once configured, engineers can use natural language queries within their AI assistant, and the AI will invoke the appropriate `wikigen` MCP tool. **Example 1: Listing Available Documentation** @@ -968,7 +968,7 @@ Once configured, engineers can use natural language queries within their AI assi "tool_args": {} } ``` -* **`salt-docs` Response**: A list of file paths (e.g., `["/path/to/docs/01_local_config.md", "/path/to/docs/my_component.md"]`). +* **`wikigen` Response**: A list of file paths (e.g., `["/path/to/docs/01_local_config.md", "/path/to/docs/my_component.md"]`). **Example 2: Searching for Specific Context** @@ -983,7 +983,7 @@ Once configured, engineers can use natural language queries within their AI assi } } ``` -* **`salt-docs` Response**: Relevant snippets from `01_local_configuration_management_.md` (powered by [Fast Documentation Search](03_fast_documentation_search_.md)). +* **`wikigen` Response**: Relevant snippets from `01_local_configuration_management_.md` (powered by [Fast Documentation Search](03_fast_documentation_search_.md)). **Example 3: Retrieving Full Document Content** @@ -998,19 +998,19 @@ Once configured, engineers can use natural language queries within their AI assi } } ``` -* **`salt-docs` Response**: The full Markdown content of `02_documentation_generation_pipeline_.md`. +* **`wikigen` Response**: The full Markdown content of `02_documentation_generation_pipeline_.md`. ### Key Takeaways The **MCP Server & AI Integration** component is transformative for developers, enabling a new level of productivity and context awareness. -* It acts as the **bridge between `salt-docs` and AI coding assistants**, leveraging the Model Context Protocol. +* It acts as the **bridge between `wikigen` and AI coding assistants**, leveraging the Model Context Protocol. * The server operates **locally**, ensuring absolute **privacy and fast interactions**. * It exposes a powerful set of **`list_docs`, `get_docs`, `search_docs`, and `index_directories` tools** for AI consumption. * By providing AI assistants with direct access to project-specific context, it dramatically **enhances the relevance and accuracy of AI-generated responses**. -This component solidifies `salt-docs`'s position as an essential tool for engineers seeking comprehensive, intelligent, and privacy-preserving documentation within their development workflow. +This component solidifies `wikigen`'s position as an essential tool for engineers seeking comprehensive, intelligent, and privacy-preserving documentation within their development workflow. --- -Wiki created by [SALT](https://usesalt.co) +Wiki created by [WIKIGEN](https://usesalt.co) diff --git a/pyproject.toml b/pyproject.toml index e14c902..0154146 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "salt-docs" +name = "wikigen" version = "0.3.0" description = "Wiki's for nerds, by nerds" readme = "README.md" @@ -26,16 +26,16 @@ classifiers = [ dependencies = ['pocketflow>=0.0.1', 'pyyaml>=6.0', 'requests>=2.28.0', 'gitpython>=3.1.0', 'google-genai>=1.9.0', 'pathspec>=0.11.0', 'keyring>=24.0.0', 'mcp>=1.19.0', 'faiss-cpu>=1.7.4', 'sentence-transformers>=2.2.0', 'numpy>=1.24.0'] [project.scripts] -salt-docs = "salt_docs.cli:main" +wikigen = "wikigen.cli:main" [project.urls] -Homepage = "https://github.com/usesalt/salt-docs" -Repository = "https://github.com/usesalt/salt-docs" -Issues = "https://github.com/usesalt/salt-docs/issues" +Homepage = "https://github.com/usesalt/wikigen" +Repository = "https://github.com/usesalt/wikigen" +Issues = "https://github.com/usesalt/wikigen/issues" [tool.setuptools.packages.find] where = ["."] -include = ["salt_docs*"] +include = ["wikigen*"] [tool.pytest.ini_options] filterwarnings = [ diff --git a/salt_docs/__init__.py b/salt_docs/__init__.py index bca9b89..502e0ad 100644 --- a/salt_docs/__init__.py +++ b/salt_docs/__init__.py @@ -1,5 +1,5 @@ """ -Salt Docs - Wiki for your codebase +WikiGen - Wiki for your codebase """ from .metadata import AUTHOR_NAME diff --git a/salt_docs/cli.py b/salt_docs/cli.py index 15a9706..a9a6444 100644 --- a/salt_docs/cli.py +++ b/salt_docs/cli.py @@ -1,5 +1,5 @@ """ -CLI entry point for Salt Docs. +CLI entry point for WikiGen. """ import sys @@ -250,7 +250,7 @@ def main(): # Check if config exists, if not, prompt user to run init if not check_config_exists(): - print("✘ Salt Docs is not configured yet.") + print("✘ WikiGen is not configured yet.") print( f"Please run '{CLI_ENTRY_POINT} init' to set up your configuration first." ) @@ -284,7 +284,7 @@ def main(): # Check if config exists, if not, prompt user to run init if not check_config_exists(): - print("✘ Salt Docs is not configured yet.") + print("✘ WikiGen is not configured yet.") print( f"Please run '{CLI_ENTRY_POINT} init' to set up your configuration first." ) @@ -339,7 +339,7 @@ def _add_common_arguments(parser, config): "-v", "--version", action="version", - version=f"salt-docs {get_version()}", + version=f"wikigen {get_version()}", ) parser.add_argument( @@ -453,9 +453,9 @@ def _check_for_updates_quietly(): def handle_config_command(): - """Handle salt-docs config commands.""" + """Handle wikigen config commands.""" if len(sys.argv) < 3: - print("Usage: salt-docs config ") + print("Usage: wikigen config ") print("Commands:") print(" show - Show current configuration") print(" set - Set a configuration value") @@ -466,9 +466,9 @@ def handle_config_command(): " update-github-token [token] - Update GitHub token (interactive if no token provided)" ) print("\nExamples:") - print(" salt-docs config set llm-provider openai") - print(" salt-docs config set llm-model gpt-4o-mini") - print(" salt-docs config update-api-key gemini") + print(" wikigen config set llm-provider openai") + print(" wikigen config set llm-model gpt-4o-mini") + print(" wikigen config update-api-key gemini") return command = sys.argv[2] @@ -477,16 +477,16 @@ def handle_config_command(): show_config() elif command == "set": if len(sys.argv) < 5: - print("Usage: salt-docs config set ") - print("Example: salt-docs config set language spanish") - print("Example: salt-docs config set llm-provider openai") + print("Usage: wikigen config set ") + print("Example: wikigen config set language spanish") + print("Example: wikigen config set llm-provider openai") return key = sys.argv[3] value = sys.argv[4] set_config_value(key, value) elif command == "update-api-key": if len(sys.argv) < 4: - print("Usage: salt-docs config update-api-key ") + print("Usage: wikigen config update-api-key ") print("Providers: gemini, openai, anthropic, openrouter") return provider = sys.argv[3] @@ -504,7 +504,7 @@ def handle_config_command(): update_github_token() else: print(f"Unknown command: {command}") - print("Run 'salt-docs config' to see available commands") + print("Run 'wikigen config' to see available commands") def show_config(): @@ -514,7 +514,7 @@ def show_config(): return config = load_config() - print(" Current Salt Docs Configuration:") + print(" Current WikiGen Configuration:") print(f" LLM Provider: {config.get('llm_provider', 'Not set')}") print(f" LLM Model: {config.get('llm_model', 'Not set')}") print(f" Output Directory: {config.get('output_dir', 'Not set')}") @@ -606,11 +606,11 @@ def _update_secret( if KEYRING_AVAILABLE: try: if secret_value: - keyring.set_password("salt-docs", secret_key, secret_value) + keyring.set_password("wikigen", secret_key, secret_value) print(f"✓ {display_name} updated securely in keyring") return elif allow_empty: - keyring.delete_password("salt-docs", secret_key) + keyring.delete_password("wikigen", secret_key) print(f"✓ {display_name} removed from keyring") return else: diff --git a/salt_docs/config.py b/salt_docs/config.py index 7fca355..fb1b0f9 100644 --- a/salt_docs/config.py +++ b/salt_docs/config.py @@ -1,5 +1,5 @@ """ -Configuration management for Salt Docs. +Configuration management for WikiGen. Handles loading, saving, and merging configuration with CLI arguments. """ @@ -42,19 +42,19 @@ def _get_platform_config_base() -> Path: def _get_new_config_dir() -> Path: - """Return the new config directory for saltdocs under the platform base.""" - return _get_platform_config_base() / "saltdocs" + """Return the new config directory for wikigen under the platform base.""" + return _get_platform_config_base() / "wikigen" def _get_legacy_config_dir() -> Path: """Return the previous Documents-based config directory (for migration).""" - return Path.home() / "Documents" / "Salt Docs" / ".salt" + return Path.home() / "Documents" / "WikiGen" / ".salt" # Configuration paths CONFIG_DIR = _get_new_config_dir() CONFIG_FILE = CONFIG_DIR / "config.json" -DEFAULT_OUTPUT_DIR = Path.home() / "Documents" / "Salt Docs" +DEFAULT_OUTPUT_DIR = Path.home() / "Documents" / "WikiGen" def _migrate_legacy_config_if_needed() -> None: @@ -233,9 +233,9 @@ def init_config() -> None: if keyring_available: try: if api_key: - keyring.set_password("salt-docs", provider_info["keyring_key"], api_key) + keyring.set_password("wikigen", provider_info["keyring_key"], api_key) if github_token: - keyring.set_password("salt-docs", "github_token", github_token) + keyring.set_password("wikigen", "github_token", github_token) except (OSError, RuntimeError, AttributeError): keyring_available = False @@ -347,11 +347,11 @@ def load_config() -> Dict[str, Any]: for provider_id, provider_info in LLM_PROVIDERS.items(): keyring_key = provider_info.get("keyring_key") if keyring_key: - api_key = keyring.get_password("salt-docs", keyring_key) + api_key = keyring.get_password("wikigen", keyring_key) if api_key: config[keyring_key] = api_key - github_token = keyring.get_password("salt-docs", "github_token") + github_token = keyring.get_password("wikigen", "github_token") if github_token: config["github_token"] = github_token except (OSError, RuntimeError, AttributeError) as e: @@ -458,7 +458,7 @@ def get_api_key() -> Optional[str]: api_key = None if keyring_key and KEYRING_AVAILABLE: try: - api_key = keyring.get_password("salt-docs", keyring_key) + api_key = keyring.get_password("wikigen", keyring_key) except (OSError, RuntimeError, AttributeError): pass diff --git a/salt_docs/defaults.py b/salt_docs/defaults.py index c165e57..9470bc9 100644 --- a/salt_docs/defaults.py +++ b/salt_docs/defaults.py @@ -1,5 +1,5 @@ """ -Default configuration values for Salt Docs. +Default configuration values for WikiGen. """ # Default file patterns for inclusion @@ -58,7 +58,7 @@ # Default configuration values DEFAULT_CONFIG = { - "output_dir": "~/Documents/Salt Docs", + "output_dir": "~/Documents/WikiGen", "language": "english", "max_abstractions": 10, "max_file_size": 100000, diff --git a/salt_docs/flows/__init__.py b/salt_docs/flows/__init__.py index 9301347..44361c2 100644 --- a/salt_docs/flows/__init__.py +++ b/salt_docs/flows/__init__.py @@ -1 +1 @@ -"""Flows module for Salt Docs.""" +"""Flows module for WikiGen.""" diff --git a/salt_docs/flows/flow.py b/salt_docs/flows/flow.py index 7428cd8..4692912 100644 --- a/salt_docs/flows/flow.py +++ b/salt_docs/flows/flow.py @@ -1,7 +1,7 @@ from pocketflow import Flow # Import all node classes from nodes.py -from salt_docs.nodes.nodes import ( +from wikigen.nodes.nodes import ( FetchRepo, IdentifyAbstractions, AnalyzeRelationships, diff --git a/salt_docs/formatter/help_formatter.py b/salt_docs/formatter/help_formatter.py index 70eeeaf..c878405 100644 --- a/salt_docs/formatter/help_formatter.py +++ b/salt_docs/formatter/help_formatter.py @@ -1,5 +1,5 @@ """ -Enhanced help formatting for Salt Docs CLI. +Enhanced help formatting for WikiGen CLI. Provides colored, structured help output with icons and tree structure. """ diff --git a/salt_docs/formatter/init_formatter.py b/salt_docs/formatter/init_formatter.py index 1237a95..88bac01 100644 --- a/salt_docs/formatter/init_formatter.py +++ b/salt_docs/formatter/init_formatter.py @@ -1,5 +1,5 @@ """ -Init mode formatter for Salt Docs CLI. +Init mode formatter for WikiGen CLI. Provides structured visual output for the configuration setup process. """ diff --git a/salt_docs/formatter/output_formatter.py b/salt_docs/formatter/output_formatter.py index 07eb709..6e0f33a 100644 --- a/salt_docs/formatter/output_formatter.py +++ b/salt_docs/formatter/output_formatter.py @@ -1,5 +1,5 @@ """ -Output formatting utilities for Salt Docs CLI. +Output formatting utilities for WikiGen CLI. Provides tree-structured output with icons, colors, and timing. """ @@ -101,7 +101,7 @@ def print_header(version=None): version = __version__ - print(f"{Colors.WHITE}SALT DOCS {Colors.LIGHT_GRAY}v{version}{Colors.RESET}") + print(f"{Colors.WHITE}WikiGen {Colors.LIGHT_GRAY}v{version}{Colors.RESET}") def print_info(label, value): @@ -286,5 +286,5 @@ def print_update_notification(current_version: str, latest_version: str): f"{Colors.WHITE} → {Colors.WHITE}v{latest_version}{Colors.RESET}" ) print( - f"{Colors.MEDIUM_GRAY} To upgrade, run: {Colors.WHITE}pip install --upgrade salt-docs{Colors.RESET}" + f"{Colors.MEDIUM_GRAY} To upgrade, run: {Colors.WHITE}pip install --upgrade wikigen{Colors.RESET}" ) diff --git a/salt_docs/mcp/__init__.py b/salt_docs/mcp/__init__.py index 58f5f70..b71091d 100644 --- a/salt_docs/mcp/__init__.py +++ b/salt_docs/mcp/__init__.py @@ -1,4 +1,4 @@ -"""MCP (Model Context Protocol) server for salt-docs.""" +"""MCP (Model Context Protocol) server for wikigen.""" # Lazy import to avoid requiring mcp package at module load time diff --git a/salt_docs/mcp/server.py b/salt_docs/mcp/server.py index 6b7036f..59f8dfd 100644 --- a/salt_docs/mcp/server.py +++ b/salt_docs/mcp/server.py @@ -1,4 +1,4 @@ -"""MCP server implementation for salt-docs.""" +"""MCP server implementation for wikigen.""" from pathlib import Path from typing import Dict, List, Optional @@ -12,7 +12,7 @@ # Initialize the MCP server # Instructions help editors/clients understand what this server provides app = FastMCP( - "salt-docs", + "wikigen", instructions=( "Expose local wiki markdown files as MCP tools. " "Available tools: search_docs (semantic search across indexed directories), " diff --git a/salt_docs/metadata/__init__.py b/salt_docs/metadata/__init__.py index f7cc1f5..2a66eb7 100644 --- a/salt_docs/metadata/__init__.py +++ b/salt_docs/metadata/__init__.py @@ -1,5 +1,5 @@ """ -Metadata package for Salt Docs. +Metadata package for WikiGen. Centralized source of truth for project information. """ diff --git a/salt_docs/metadata/logo.py b/salt_docs/metadata/logo.py index 336b177..017bc19 100644 --- a/salt_docs/metadata/logo.py +++ b/salt_docs/metadata/logo.py @@ -1,11 +1,11 @@ -"""ASCII logo for Salt Docs CLI.""" +"""ASCII logo for WikiGen CLI.""" from .project import DESCRIPTION from .version import get_version def print_logo(): - """Print the Salt Docs ASCII logo with simple gray colors.""" + """Print the WikiGen ASCII logo with simple gray colors.""" # Simple colors that work well on both light and dark backgrounds LOGO_COLOR = "\033[38;5;240m" # Medium gray - visible everywhere ATTRIB_COLOR = "\033[38;5;245m" # Light gray diff --git a/salt_docs/metadata/project.py b/salt_docs/metadata/project.py index bdafb27..5c08200 100644 --- a/salt_docs/metadata/project.py +++ b/salt_docs/metadata/project.py @@ -1,20 +1,20 @@ """ -Project metadata for Salt Docs. +Project metadata for WikiGen. Single source of truth for project information. """ import datetime # Project info -PROJECT_NAME = "salt-docs" +PROJECT_NAME = "wikigen" AUTHOR_NAME = "Mithun Ramesh" -ORGANIZATION = "USESALT.CO" +ORGANIZATION = "USEWIKIGEN.CO" DESCRIPTION = "WIKI'S FOR NERDS, BY NERDS" # Repository info -REPOSITORY_URL = "https://github.com/usesalt/salt-docs" +REPOSITORY_URL = "https://github.com/usesalt/wikigen" HOMEPAGE_URL = "https://usesalt.co" -ISSUES_URL = "https://github.com/usesalt/salt-docs/issues" +ISSUES_URL = "https://github.com/usesalt/wikigen/issues" # Dynamic values CURRENT_YEAR = datetime.datetime.now().year @@ -24,5 +24,5 @@ MIN_PYTHON_VERSION = "3.12" # Package info -PACKAGE_NAME = "salt-docs" -CLI_ENTRY_POINT = "salt-docs" +PACKAGE_NAME = "wikigen" +CLI_ENTRY_POINT = "wikigen" diff --git a/salt_docs/metadata/version.py b/salt_docs/metadata/version.py index 9ed56a4..53459aa 100644 --- a/salt_docs/metadata/version.py +++ b/salt_docs/metadata/version.py @@ -1,5 +1,5 @@ """ -Version management for Salt Docs. +Version management for WikiGen. Centralized version definition for consistency. """ diff --git a/salt_docs/nodes/__init__.py b/salt_docs/nodes/__init__.py index 8e33a0e..cdc174b 100644 --- a/salt_docs/nodes/__init__.py +++ b/salt_docs/nodes/__init__.py @@ -1 +1 @@ -"""Nodes module for Salt Docs.""" +"""Nodes module for WikiGen.""" diff --git a/salt_docs/nodes/nodes.py b/salt_docs/nodes/nodes.py index 53a8872..5d956f7 100644 --- a/salt_docs/nodes/nodes.py +++ b/salt_docs/nodes/nodes.py @@ -2,10 +2,10 @@ import time import yaml from pocketflow import Node, BatchNode -from salt_docs.utils.crawl_github_files import crawl_github_files -from salt_docs.utils.call_llm import call_llm -from salt_docs.utils.crawl_local_files import crawl_local_files -from salt_docs.formatter.output_formatter import ( +from wikigen.utils.crawl_github_files import crawl_github_files +from wikigen.utils.call_llm import call_llm +from wikigen.utils.crawl_local_files import crawl_local_files +from wikigen.formatter.output_formatter import ( Icons, print_phase_start, print_operation, @@ -908,7 +908,7 @@ def _generate_combined_content( self, project_name, index_content, components_content ): """Generate the combined documentation file content.""" - from salt_docs.utils.adjust_headings import ( + from wikigen.utils.adjust_headings import ( adjust_heading_levels, strip_attribution_footer, ) @@ -934,7 +934,7 @@ def _generate_combined_content( # Add separator at the bottom combined += ( - "\n\n---\n\nWiki created by [SALT](https://github.com/usesalt/salt-docs)\n" + "\n\n---\n\nWiki created by [WIKIGEN](https://github.com/usesalt/wikigen)\n" ) return combined @@ -1028,7 +1028,7 @@ def exec(self, prep_res): ) # Add attribution to index content (using English fixed string) - index_content += "\n\n---\n\nGenerated by [SALT](https://usesalt.co)" + index_content += "\n\n---\n\nGenerated by [WIKIGEN](https://usesalt.co)" # Generate combined content combined_content = self._generate_combined_content( diff --git a/salt_docs/utils/call_llm.py b/salt_docs/utils/call_llm.py index bbe88b7..4602cfa 100644 --- a/salt_docs/utils/call_llm.py +++ b/salt_docs/utils/call_llm.py @@ -24,7 +24,7 @@ def get_cache_file_path() -> Path: - """Get the cache file path in the Salt Docs directory.""" + """Get the cache file path in the WikiGen directory.""" try: from ..config import DEFAULT_OUTPUT_DIR diff --git a/salt_docs/utils/version_check.py b/salt_docs/utils/version_check.py index 4c59af1..1923d24 100644 --- a/salt_docs/utils/version_check.py +++ b/salt_docs/utils/version_check.py @@ -1,5 +1,5 @@ """ -Version checking utilities for Salt Docs CLI. +Version checking utilities for WikiGen CLI. Queries PyPI API to check for available updates. """ @@ -8,7 +8,7 @@ def fetch_latest_version( - package_name: str = "salt-docs", timeout: float = 5.0 + package_name: str = "wikigen", timeout: float = 5.0 ) -> Optional[str]: """ Fetch the latest version from PyPI API. diff --git a/setup.py b/setup.py index b543418..deb72ad 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def get_version(): # Static metadata -PROJECT_NAME = "salt-docs" +PROJECT_NAME = "wikigen" AUTHOR_NAME = "Mithun Ramesh" DESCRIPTION = "Wiki's for nerds, by nerds" MIN_PYTHON_VERSION = "3.12" @@ -42,7 +42,7 @@ def get_version(): python_requires=f">={MIN_PYTHON_VERSION}", entry_points={ "console_scripts": [ - "salt-docs=salt_docs.cli:main", + "wikigen=wikigen.cli:main", ], }, include_package_data=True, diff --git a/tests/__init__.py b/tests/__init__.py index 82f0234..39bb620 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -# Tests for Salt Docs CLI +# Tests for WikiGen CLI diff --git a/tests/cli/test_ci_integration.py b/tests/cli/test_ci_integration.py index 4d95086..335d2f6 100644 --- a/tests/cli/test_ci_integration.py +++ b/tests/cli/test_ci_integration.py @@ -1,5 +1,5 @@ """ -Tests for CI/CD integration features in Salt Docs CLI. +Tests for CI/CD integration features in WikiGen CLI. """ import pytest @@ -11,7 +11,7 @@ # Add project root to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) -from salt_docs.cli import main, _run_documentation_generation +from wikigen.cli import main, _run_documentation_generation class TestCIIntegration: @@ -20,13 +20,13 @@ class TestCIIntegration: def test_ci_flag_parsing(self): """Test that --ci flag is correctly parsed and passed to generation.""" with ( - patch("salt_docs.cli.check_config_exists", return_value=True), - patch("salt_docs.cli.load_config", return_value={"output_dir": "docs"}), - patch("salt_docs.cli._run_documentation_generation") as mock_run, + patch("wikigen.cli.check_config_exists", return_value=True), + patch("wikigen.cli.load_config", return_value={"output_dir": "docs"}), + patch("wikigen.cli._run_documentation_generation") as mock_run, ): # Test with explicit --ci flag - with patch("sys.argv", ["salt-docs", "run", ".", "--ci"]): + with patch("sys.argv", ["wikigen", "run", ".", "--ci"]): main() args = mock_run.call_args[0][2] assert args.ci is True @@ -55,8 +55,8 @@ def test_ci_env_var_detection(self): # Test with CI=true environment variable with ( patch.dict(os.environ, {"CI": "true"}), - patch("salt_docs.cli.create_wiki_flow") as mock_flow_factory, - patch("salt_docs.cli.print_info") as mock_print_info, + patch("wikigen.cli.create_wiki_flow") as mock_flow_factory, + patch("wikigen.cli.print_info") as mock_print_info, ): mock_flow = MagicMock() @@ -92,8 +92,8 @@ def test_output_path_flag(self): } with ( - patch("salt_docs.cli.create_wiki_flow") as mock_flow_factory, - patch("salt_docs.cli.print_info"), + patch("wikigen.cli.create_wiki_flow") as mock_flow_factory, + patch("wikigen.cli.print_info"), ): mock_flow = MagicMock() @@ -126,9 +126,9 @@ def test_check_changes_exit_code(self): } with ( - patch("salt_docs.cli.create_wiki_flow") as mock_flow_factory, - patch("salt_docs.cli.print_info"), - patch("salt_docs.cli.print_final_success"), + patch("wikigen.cli.create_wiki_flow") as mock_flow_factory, + patch("wikigen.cli.print_info"), + patch("wikigen.cli.print_final_success"), ): mock_flow = MagicMock() @@ -166,9 +166,9 @@ def test_check_changes_no_exit_code(self): } with ( - patch("salt_docs.cli.create_wiki_flow") as mock_flow_factory, - patch("salt_docs.cli.print_info"), - patch("salt_docs.cli.print_final_success"), + patch("wikigen.cli.create_wiki_flow") as mock_flow_factory, + patch("wikigen.cli.print_info"), + patch("wikigen.cli.print_final_success"), ): mock_flow = MagicMock() diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index e509043..f00f8b8 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -1,5 +1,5 @@ """ -Basic tests for Salt Docs CLI. +Basic tests for WikiGen CLI. """ import pytest @@ -12,8 +12,8 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) -from salt_docs.cli import main -from salt_docs.config import load_config, save_config +from wikigen.cli import main +from wikigen.config import load_config, save_config class TestCLI: @@ -21,8 +21,8 @@ class TestCLI: def test_init_command(self): """Test that init command works without errors.""" - with patch("salt_docs.cli.init_config") as mock_init: - with patch("sys.argv", ["salt-docs", "init"]): + with patch("wikigen.cli.init_config") as mock_init: + with patch("sys.argv", ["wikigen", "init"]): main() mock_init.assert_called_once() @@ -37,10 +37,10 @@ def test_config_show_command(self): "max_abstractions": 10, } - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): save_config(test_config) - with patch("sys.argv", ["salt-docs", "config", "show"]): + with patch("sys.argv", ["wikigen", "config", "show"]): with patch("builtins.print") as mock_print: main() # Should print the config @@ -48,8 +48,8 @@ def test_config_show_command(self): def test_main_without_config(self): """Test that main exits when config doesn't exist.""" - with patch("salt_docs.cli.check_config_exists", return_value=False): - with patch("sys.argv", ["salt-docs", "--help"]): + with patch("wikigen.cli.check_config_exists", return_value=False): + with patch("sys.argv", ["wikigen", "--help"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 @@ -68,7 +68,7 @@ def test_save_and_load_config(self): "max_abstractions": 10, } - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): save_config(test_config) loaded_config = load_config() # Check that our specific values are preserved diff --git a/tests/cli/test_version_check.py b/tests/cli/test_version_check.py index 9f7ea01..1af33e9 100644 --- a/tests/cli/test_version_check.py +++ b/tests/cli/test_version_check.py @@ -12,12 +12,12 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) -from salt_docs.utils.version_check import ( +from wikigen.utils.version_check import ( fetch_latest_version, compare_versions, check_for_update, ) -from salt_docs.config import ( +from wikigen.config import ( should_check_for_updates, update_last_check_timestamp, load_config, @@ -60,7 +60,7 @@ def test_compare_versions_fallback_to_string(self): class TestFetchLatestVersion: """Test fetching latest version from PyPI.""" - @patch("salt_docs.utils.version_check.requests.get") + @patch("wikigen.utils.version_check.requests.get") def test_fetch_latest_version_success(self, mock_get): """Test successful version fetch from PyPI.""" mock_response = MagicMock() @@ -71,10 +71,10 @@ def test_fetch_latest_version_success(self, mock_get): version = fetch_latest_version() assert version == "0.1.6" mock_get.assert_called_once_with( - "https://pypi.org/pypi/salt-docs/json", timeout=5.0 + "https://pypi.org/pypi/wikigen/json", timeout=5.0 ) - @patch("salt_docs.utils.version_check.requests.get") + @patch("wikigen.utils.version_check.requests.get") def test_fetch_latest_version_network_error(self, mock_get): """Test that network errors return None.""" import requests @@ -84,7 +84,7 @@ def test_fetch_latest_version_network_error(self, mock_get): version = fetch_latest_version() assert version is None - @patch("salt_docs.utils.version_check.requests.get") + @patch("wikigen.utils.version_check.requests.get") def test_fetch_latest_version_invalid_response(self, mock_get): """Test that invalid JSON responses return None.""" mock_response = MagicMock() @@ -95,7 +95,7 @@ def test_fetch_latest_version_invalid_response(self, mock_get): version = fetch_latest_version() assert version is None - @patch("salt_docs.utils.version_check.requests.get") + @patch("wikigen.utils.version_check.requests.get") def test_fetch_latest_version_custom_package(self, mock_get): """Test fetching version with custom package name.""" mock_response = MagicMock() @@ -113,7 +113,7 @@ def test_fetch_latest_version_custom_package(self, mock_get): class TestCheckForUpdate: """Test the check_for_update function.""" - @patch("salt_docs.utils.version_check.fetch_latest_version") + @patch("wikigen.utils.version_check.fetch_latest_version") def test_check_for_update_available(self, mock_fetch): """Test when an update is available.""" mock_fetch.return_value = "0.1.6" @@ -121,7 +121,7 @@ def test_check_for_update_available(self, mock_fetch): result = check_for_update("0.1.5") assert result == "0.1.6" - @patch("salt_docs.utils.version_check.fetch_latest_version") + @patch("wikigen.utils.version_check.fetch_latest_version") def test_check_for_update_not_available(self, mock_fetch): """Test when no update is available (same version).""" mock_fetch.return_value = "0.1.5" @@ -129,7 +129,7 @@ def test_check_for_update_not_available(self, mock_fetch): result = check_for_update("0.1.5") assert result is None - @patch("salt_docs.utils.version_check.fetch_latest_version") + @patch("wikigen.utils.version_check.fetch_latest_version") def test_check_for_update_current_newer(self, mock_fetch): """Test when current version is newer (shouldn't happen but test anyway).""" mock_fetch.return_value = "0.1.5" @@ -137,7 +137,7 @@ def test_check_for_update_current_newer(self, mock_fetch): result = check_for_update("0.1.6") assert result is None - @patch("salt_docs.utils.version_check.fetch_latest_version") + @patch("wikigen.utils.version_check.fetch_latest_version") def test_check_for_update_network_error(self, mock_fetch): """Test when network error occurs.""" mock_fetch.return_value = None @@ -154,7 +154,7 @@ def test_should_check_for_updates_never_checked(self): with tempfile.TemporaryDirectory() as temp_dir: config_path = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): # Create config without last_update_check config = {"output_dir": "/tmp"} save_config(config) @@ -166,7 +166,7 @@ def test_should_check_for_updates_just_checked(self): with tempfile.TemporaryDirectory() as temp_dir: config_path = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): # Set last check to now config = {"output_dir": "/tmp", "last_update_check": time.time()} save_config(config) @@ -178,7 +178,7 @@ def test_should_check_for_updates_24h_passed(self): with tempfile.TemporaryDirectory() as temp_dir: config_path = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): # Set last check to 25 hours ago config = { "output_dir": "/tmp", @@ -193,7 +193,7 @@ def test_update_last_check_timestamp(self): with tempfile.TemporaryDirectory() as temp_dir: config_path = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_path): + with patch("wikigen.config.CONFIG_FILE", config_path): # Initially no timestamp config = {"output_dir": "/tmp"} save_config(config) @@ -213,20 +213,20 @@ def test_update_last_check_timestamp(self): class TestCLIIntegration: """Test CLI integration of version checking.""" - @patch("salt_docs.cli.should_check_for_updates") - @patch("salt_docs.cli.check_for_update") - @patch("salt_docs.cli.update_last_check_timestamp") - @patch("salt_docs.cli.print_update_notification") + @patch("wikigen.cli.should_check_for_updates") + @patch("wikigen.cli.check_for_update") + @patch("wikigen.cli.update_last_check_timestamp") + @patch("wikigen.cli.print_update_notification") def test_check_updates_called_on_success( self, mock_notify, mock_update_ts, mock_check, mock_should ): """Test that update check is called after successful execution.""" - from salt_docs.cli import _check_for_updates_quietly + from wikigen.cli import _check_for_updates_quietly mock_should.return_value = True mock_check.return_value = "0.1.6" - with patch("salt_docs.cli.get_version", return_value="0.1.5"): + with patch("wikigen.cli.get_version", return_value="0.1.5"): _check_for_updates_quietly() mock_should.assert_called_once() @@ -234,15 +234,15 @@ def test_check_updates_called_on_success( mock_update_ts.assert_called_once() mock_notify.assert_called_once_with("0.1.5", "0.1.6") - @patch("salt_docs.cli.should_check_for_updates") - @patch("salt_docs.cli.check_for_update") - @patch("salt_docs.cli.update_last_check_timestamp") - @patch("salt_docs.cli.print_update_notification") + @patch("wikigen.cli.should_check_for_updates") + @patch("wikigen.cli.check_for_update") + @patch("wikigen.cli.update_last_check_timestamp") + @patch("wikigen.cli.print_update_notification") def test_check_updates_skipped_if_too_recent( self, mock_notify, mock_update_ts, mock_check, mock_should ): """Test that update check is skipped if checked recently.""" - from salt_docs.cli import _check_for_updates_quietly + from wikigen.cli import _check_for_updates_quietly mock_should.return_value = False @@ -253,35 +253,35 @@ def test_check_updates_skipped_if_too_recent( mock_update_ts.assert_not_called() mock_notify.assert_not_called() - @patch("salt_docs.cli.should_check_for_updates") - @patch("salt_docs.cli.check_for_update") - @patch("salt_docs.cli.update_last_check_timestamp") - @patch("salt_docs.cli.print_update_notification") + @patch("wikigen.cli.should_check_for_updates") + @patch("wikigen.cli.check_for_update") + @patch("wikigen.cli.update_last_check_timestamp") + @patch("wikigen.cli.print_update_notification") def test_check_updates_no_notification_if_no_update( self, mock_notify, mock_update_ts, mock_check, mock_should ): """Test that no notification is shown if no update available.""" - from salt_docs.cli import _check_for_updates_quietly + from wikigen.cli import _check_for_updates_quietly mock_should.return_value = True mock_check.return_value = None # No update available - with patch("salt_docs.cli.get_version", return_value="0.1.5"): + with patch("wikigen.cli.get_version", return_value="0.1.5"): _check_for_updates_quietly() mock_check.assert_called_once() mock_update_ts.assert_called_once() mock_notify.assert_not_called() - @patch("salt_docs.cli.should_check_for_updates") - @patch("salt_docs.cli.check_for_update") - @patch("salt_docs.cli.update_last_check_timestamp") - @patch("salt_docs.cli.print_update_notification") + @patch("wikigen.cli.should_check_for_updates") + @patch("wikigen.cli.check_for_update") + @patch("wikigen.cli.update_last_check_timestamp") + @patch("wikigen.cli.print_update_notification") def test_check_updates_handles_exceptions_gracefully( self, mock_notify, mock_update_ts, mock_check, mock_should ): """Test that exceptions are handled gracefully.""" - from salt_docs.cli import _check_for_updates_quietly + from wikigen.cli import _check_for_updates_quietly mock_should.return_value = True mock_check.side_effect = Exception("Unexpected error") diff --git a/tests/llm/test_call_llm_routing.py b/tests/llm/test_call_llm_routing.py index 5dca82c..c523569 100644 --- a/tests/llm/test_call_llm_routing.py +++ b/tests/llm/test_call_llm_routing.py @@ -14,7 +14,7 @@ def test_provider_routing_logic(): print("Testing Call LLM Routing Logic") print("=" * 60) - from salt_docs.utils.call_llm import ( + from wikigen.utils.call_llm import ( _call_gemini, _call_openai, _call_anthropic, @@ -63,7 +63,7 @@ def test_ollama_routing(): print("Testing Ollama Routing") print("=" * 60) - from salt_docs.utils.call_llm import _call_ollama + from wikigen.utils.call_llm import _call_ollama import inspect # Check that Ollama function accepts api_key as optional @@ -92,7 +92,7 @@ def test_openai_o1_support(): print("Testing OpenAI o1 Support") print("=" * 60) - from salt_docs.utils.call_llm import _call_openai + from wikigen.utils.call_llm import _call_openai import inspect # Check the function has logic for o1 models @@ -112,7 +112,7 @@ def test_anthropic_extended_thinking(): print("Testing Anthropic Extended Thinking") print("=" * 60) - from salt_docs.utils.call_llm import _call_anthropic + from wikigen.utils.call_llm import _call_anthropic import inspect # Check the function has logic for extended thinking diff --git a/tests/llm/test_config_integration.py b/tests/llm/test_config_integration.py index 9814eaf..7f564bf 100644 --- a/tests/llm/test_config_integration.py +++ b/tests/llm/test_config_integration.py @@ -9,7 +9,7 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent)) -from salt_docs.config import ( +from wikigen.config import ( get_llm_provider, get_llm_model, get_api_key, @@ -17,7 +17,7 @@ save_config, DEFAULT_CONFIG, ) -from salt_docs.utils.llm_providers import get_provider_info, requires_api_key +from wikigen.utils.llm_providers import get_provider_info, requires_api_key def test_config_defaults(): diff --git a/tests/llm/test_llm_providers.py b/tests/llm/test_llm_providers.py index a628067..6019187 100644 --- a/tests/llm/test_llm_providers.py +++ b/tests/llm/test_llm_providers.py @@ -7,7 +7,7 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent)) -from salt_docs.utils.llm_providers import ( +from wikigen.utils.llm_providers import ( get_provider_list, get_provider_info, get_display_name, diff --git a/tests/mcp/test_mcp_tools.py b/tests/mcp/test_mcp_tools.py index 7fc1720..1308263 100644 --- a/tests/mcp/test_mcp_tools.py +++ b/tests/mcp/test_mcp_tools.py @@ -7,8 +7,8 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent.parent)) -from salt_docs.mcp.output_resources import discover_all_projects -from salt_docs.config import get_output_dir +from wikigen.mcp.output_resources import discover_all_projects +from wikigen.config import get_output_dir def test_server_initialization(): @@ -18,7 +18,7 @@ def test_server_initialization(): print("=" * 60) try: - from salt_docs.mcp.server import app + from wikigen.mcp.server import app print(f"✓ Server name: {app.name}") print("✓ Server initialized successfully") @@ -39,7 +39,7 @@ def test_get_docs(): print("Testing get_docs tool") print("=" * 60) - from salt_docs.mcp.server import get_docs + from wikigen.mcp.server import get_docs # First, get list of available docs projects = discover_all_projects() @@ -103,8 +103,8 @@ def test_search_docs(): print("Testing search_docs tool") print("=" * 60) - from salt_docs.mcp.server import search_docs - from salt_docs.mcp.search_index import FileIndexer + from wikigen.mcp.server import search_docs + from wikigen.mcp.search_index import FileIndexer # Ensure we have an index output_dir = get_output_dir() @@ -186,8 +186,8 @@ def test_index_directories(): print("Testing index_directories tool") print("=" * 60) - from salt_docs.mcp.server import index_directories - from salt_docs.mcp.search_index import FileIndexer + from wikigen.mcp.server import index_directories + from wikigen.mcp.search_index import FileIndexer output_dir = get_output_dir() diff --git a/tests/mcp/test_output_resources.py b/tests/mcp/test_output_resources.py index ccfacaf..51f5f3f 100644 --- a/tests/mcp/test_output_resources.py +++ b/tests/mcp/test_output_resources.py @@ -8,8 +8,8 @@ sys.path.insert(0, str(Path(__file__).parent)) # Import directly using the file path to avoid __init__ importing server -from salt_docs.mcp.output_resources import discover_all_projects -from salt_docs.config import get_output_dir +from wikigen.mcp.output_resources import discover_all_projects +from wikigen.config import get_output_dir print("=" * 60) print("Testing Output Directory Resource Mapping") diff --git a/tests/mcp/test_search_index.py b/tests/mcp/test_search_index.py index b3fa030..c73b2e3 100644 --- a/tests/mcp/test_search_index.py +++ b/tests/mcp/test_search_index.py @@ -8,7 +8,7 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent.parent)) -from salt_docs.mcp.search_index import FileIndexer +from wikigen.mcp.search_index import FileIndexer def test_file_indexer_basic(): diff --git a/tests/mcp/test_semantic_search.py b/tests/mcp/test_semantic_search.py index 22d9a42..c2f66be 100755 --- a/tests/mcp/test_semantic_search.py +++ b/tests/mcp/test_semantic_search.py @@ -9,9 +9,9 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) -from salt_docs.mcp.search_index import FileIndexer -from salt_docs.mcp.chunking import chunk_markdown -from salt_docs.mcp.embeddings import get_embedding, get_embeddings_batch +from wikigen.mcp.search_index import FileIndexer +from wikigen.mcp.chunking import chunk_markdown +from wikigen.mcp.embeddings import get_embedding, get_embeddings_batch def create_test_documents(tmp_path: Path): diff --git a/tests/mode/test_documentation_mode.py b/tests/mode/test_documentation_mode.py index ae4fb56..6dc9897 100644 --- a/tests/mode/test_documentation_mode.py +++ b/tests/mode/test_documentation_mode.py @@ -11,8 +11,8 @@ # Add project to path sys.path.insert(0, str(Path(__file__).parent.parent)) -from salt_docs.config import load_config, save_config, CONFIG_FILE -from salt_docs.defaults import DEFAULT_CONFIG +from wikigen.config import load_config, save_config, CONFIG_FILE +from wikigen.defaults import DEFAULT_CONFIG class TestDocumentationMode: @@ -28,7 +28,7 @@ def test_config_saves_documentation_mode(self): """Test that documentation_mode can be saved and loaded.""" with tempfile.TemporaryDirectory() as temp_dir: config_file = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_file): + with patch("wikigen.config.CONFIG_FILE", config_file): # Test minimal mode test_config = { "output_dir": "/tmp/test", @@ -54,7 +54,7 @@ def test_config_defaults_to_minimal_when_not_set(self): """Test that config defaults to minimal when documentation_mode is not set.""" with tempfile.TemporaryDirectory() as temp_dir: config_file = Path(temp_dir) / "config.json" - with patch("salt_docs.config.CONFIG_FILE", config_file): + with patch("wikigen.config.CONFIG_FILE", config_file): # Save config without documentation_mode test_config = { "output_dir": "/tmp/test", From e9813b886a30a19d5781c85cc657690773e0064e Mon Sep 17 00:00:00 2001 From: itsjustmithun Date: Sat, 27 Dec 2025 14:25:26 +0100 Subject: [PATCH 2/3] rename: renaming the files --- assets/{saltdocs.jpg => wikigen.jpg} | Bin salt_docs/metadata/logo.py | 28 ------------------ {salt_docs => wikigen}/__init__.py | 0 {salt_docs => wikigen}/cli.py | 0 {salt_docs => wikigen}/config.py | 0 {salt_docs => wikigen}/defaults.py | 0 {salt_docs => wikigen}/flows/__init__.py | 0 {salt_docs => wikigen}/flows/flow.py | 0 .../formatter/help_formatter.py | 0 .../formatter/init_formatter.py | 0 .../formatter/output_formatter.py | 0 {salt_docs => wikigen}/mcp/__init__.py | 0 {salt_docs => wikigen}/mcp/chunking.py | 0 {salt_docs => wikigen}/mcp/embeddings.py | 0 .../mcp/output_resources.py | 0 {salt_docs => wikigen}/mcp/search_index.py | 0 {salt_docs => wikigen}/mcp/server.py | 0 {salt_docs => wikigen}/mcp/vector_index.py | 0 {salt_docs => wikigen}/metadata/__init__.py | 0 wikigen/metadata/logo.py | 28 ++++++++++++++++++ {salt_docs => wikigen}/metadata/project.py | 0 {salt_docs => wikigen}/metadata/version.py | 0 {salt_docs => wikigen}/nodes/__init__.py | 0 {salt_docs => wikigen}/nodes/nodes.py | 0 {salt_docs => wikigen}/utils/__init__.py | 0 .../utils/adjust_headings.py | 0 {salt_docs => wikigen}/utils/call_llm.py | 0 .../utils/crawl_github_files.py | 0 .../utils/crawl_local_files.py | 0 {salt_docs => wikigen}/utils/llm_providers.py | 0 {salt_docs => wikigen}/utils/version_check.py | 0 31 files changed, 28 insertions(+), 28 deletions(-) rename assets/{saltdocs.jpg => wikigen.jpg} (100%) delete mode 100644 salt_docs/metadata/logo.py rename {salt_docs => wikigen}/__init__.py (100%) rename {salt_docs => wikigen}/cli.py (100%) rename {salt_docs => wikigen}/config.py (100%) rename {salt_docs => wikigen}/defaults.py (100%) rename {salt_docs => wikigen}/flows/__init__.py (100%) rename {salt_docs => wikigen}/flows/flow.py (100%) rename {salt_docs => wikigen}/formatter/help_formatter.py (100%) rename {salt_docs => wikigen}/formatter/init_formatter.py (100%) rename {salt_docs => wikigen}/formatter/output_formatter.py (100%) rename {salt_docs => wikigen}/mcp/__init__.py (100%) rename {salt_docs => wikigen}/mcp/chunking.py (100%) rename {salt_docs => wikigen}/mcp/embeddings.py (100%) rename {salt_docs => wikigen}/mcp/output_resources.py (100%) rename {salt_docs => wikigen}/mcp/search_index.py (100%) rename {salt_docs => wikigen}/mcp/server.py (100%) rename {salt_docs => wikigen}/mcp/vector_index.py (100%) rename {salt_docs => wikigen}/metadata/__init__.py (100%) create mode 100644 wikigen/metadata/logo.py rename {salt_docs => wikigen}/metadata/project.py (100%) rename {salt_docs => wikigen}/metadata/version.py (100%) rename {salt_docs => wikigen}/nodes/__init__.py (100%) rename {salt_docs => wikigen}/nodes/nodes.py (100%) rename {salt_docs => wikigen}/utils/__init__.py (100%) rename {salt_docs => wikigen}/utils/adjust_headings.py (100%) rename {salt_docs => wikigen}/utils/call_llm.py (100%) rename {salt_docs => wikigen}/utils/crawl_github_files.py (100%) rename {salt_docs => wikigen}/utils/crawl_local_files.py (100%) rename {salt_docs => wikigen}/utils/llm_providers.py (100%) rename {salt_docs => wikigen}/utils/version_check.py (100%) diff --git a/assets/saltdocs.jpg b/assets/wikigen.jpg similarity index 100% rename from assets/saltdocs.jpg rename to assets/wikigen.jpg diff --git a/salt_docs/metadata/logo.py b/salt_docs/metadata/logo.py deleted file mode 100644 index 017bc19..0000000 --- a/salt_docs/metadata/logo.py +++ /dev/null @@ -1,28 +0,0 @@ -"""ASCII logo for WikiGen CLI.""" - -from .project import DESCRIPTION -from .version import get_version - - -def print_logo(): - """Print the WikiGen ASCII logo with simple gray colors.""" - # Simple colors that work well on both light and dark backgrounds - LOGO_COLOR = "\033[38;5;240m" # Medium gray - visible everywhere - ATTRIB_COLOR = "\033[38;5;245m" # Light gray - RESET = "\033[0m" - - version = get_version() - logo = f""" -{ATTRIB_COLOR}INTRODUCING -{RESET}{LOGO_COLOR} -██╗ ██╗ ███████╗ ███████╗ ██╗ ████████╗ - ██║ ██║ ██╔════╝ ██╔══██║ ██║ ╚══██╔══╝ - ██║ ██║ ███████╗ ███████║ ██║ ██║ - ██║ ██║ ╚════██║ ██╔══██║ ██║ ██║ -██║ ██║ ███████║ ██║ ██║ ███████╗ ██║ -╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ -{RESET} -{ATTRIB_COLOR}{DESCRIPTION} ♥ {RESET} -{ATTRIB_COLOR}v{version}{RESET} -""" - print(logo) diff --git a/salt_docs/__init__.py b/wikigen/__init__.py similarity index 100% rename from salt_docs/__init__.py rename to wikigen/__init__.py diff --git a/salt_docs/cli.py b/wikigen/cli.py similarity index 100% rename from salt_docs/cli.py rename to wikigen/cli.py diff --git a/salt_docs/config.py b/wikigen/config.py similarity index 100% rename from salt_docs/config.py rename to wikigen/config.py diff --git a/salt_docs/defaults.py b/wikigen/defaults.py similarity index 100% rename from salt_docs/defaults.py rename to wikigen/defaults.py diff --git a/salt_docs/flows/__init__.py b/wikigen/flows/__init__.py similarity index 100% rename from salt_docs/flows/__init__.py rename to wikigen/flows/__init__.py diff --git a/salt_docs/flows/flow.py b/wikigen/flows/flow.py similarity index 100% rename from salt_docs/flows/flow.py rename to wikigen/flows/flow.py diff --git a/salt_docs/formatter/help_formatter.py b/wikigen/formatter/help_formatter.py similarity index 100% rename from salt_docs/formatter/help_formatter.py rename to wikigen/formatter/help_formatter.py diff --git a/salt_docs/formatter/init_formatter.py b/wikigen/formatter/init_formatter.py similarity index 100% rename from salt_docs/formatter/init_formatter.py rename to wikigen/formatter/init_formatter.py diff --git a/salt_docs/formatter/output_formatter.py b/wikigen/formatter/output_formatter.py similarity index 100% rename from salt_docs/formatter/output_formatter.py rename to wikigen/formatter/output_formatter.py diff --git a/salt_docs/mcp/__init__.py b/wikigen/mcp/__init__.py similarity index 100% rename from salt_docs/mcp/__init__.py rename to wikigen/mcp/__init__.py diff --git a/salt_docs/mcp/chunking.py b/wikigen/mcp/chunking.py similarity index 100% rename from salt_docs/mcp/chunking.py rename to wikigen/mcp/chunking.py diff --git a/salt_docs/mcp/embeddings.py b/wikigen/mcp/embeddings.py similarity index 100% rename from salt_docs/mcp/embeddings.py rename to wikigen/mcp/embeddings.py diff --git a/salt_docs/mcp/output_resources.py b/wikigen/mcp/output_resources.py similarity index 100% rename from salt_docs/mcp/output_resources.py rename to wikigen/mcp/output_resources.py diff --git a/salt_docs/mcp/search_index.py b/wikigen/mcp/search_index.py similarity index 100% rename from salt_docs/mcp/search_index.py rename to wikigen/mcp/search_index.py diff --git a/salt_docs/mcp/server.py b/wikigen/mcp/server.py similarity index 100% rename from salt_docs/mcp/server.py rename to wikigen/mcp/server.py diff --git a/salt_docs/mcp/vector_index.py b/wikigen/mcp/vector_index.py similarity index 100% rename from salt_docs/mcp/vector_index.py rename to wikigen/mcp/vector_index.py diff --git a/salt_docs/metadata/__init__.py b/wikigen/metadata/__init__.py similarity index 100% rename from salt_docs/metadata/__init__.py rename to wikigen/metadata/__init__.py diff --git a/wikigen/metadata/logo.py b/wikigen/metadata/logo.py new file mode 100644 index 0000000..ba50fba --- /dev/null +++ b/wikigen/metadata/logo.py @@ -0,0 +1,28 @@ +"""ASCII logo for WikiGen CLI.""" + +from .project import DESCRIPTION +from .version import get_version + + +def print_logo(): + """Print the WikiGen ASCII logo with simple gray colors.""" + # Simple colors that work well on both light and dark backgrounds + LOGO_COLOR = "\033[38;5;240m" # Medium gray - visible everywhere + ATTRIB_COLOR = "\033[38;5;245m" # Light gray + RESET = "\033[0m" + + version = get_version() + logo = f""" +{ATTRIB_COLOR}INTRODUCING +{RESET}{LOGO_COLOR} +██╗ ██╗ ██╗ ██╗ ██╗ ██╗ ██╗ ██╗ ██████╗ ███████╗ ███╗ ██╗ + ██║ ██║ ██║ ██║ ██║ ██║ ██╔╝ ██║ ██╔════╝ ██╔════╝ ████╗ ██║ + ██║ ██║ ██║ █╗ ██║ ██║ █████╔╝ ██║ ██║ ███╗ ██████╗ ██╔██╗ ██║ + ██║ ██║ ██║███╗██║ ██║ ██╔═██╗ ██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ +██║ ██║ ╚███╔███╔╝ ██║ ██║ ██╗ ██║ ╚██████╔╝ ███████╗ ██║ ╚████║ +╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═══╝ +{RESET} +{ATTRIB_COLOR}{DESCRIPTION} ♥ {RESET} +{ATTRIB_COLOR}v{version}{RESET} +""" + print(logo) diff --git a/salt_docs/metadata/project.py b/wikigen/metadata/project.py similarity index 100% rename from salt_docs/metadata/project.py rename to wikigen/metadata/project.py diff --git a/salt_docs/metadata/version.py b/wikigen/metadata/version.py similarity index 100% rename from salt_docs/metadata/version.py rename to wikigen/metadata/version.py diff --git a/salt_docs/nodes/__init__.py b/wikigen/nodes/__init__.py similarity index 100% rename from salt_docs/nodes/__init__.py rename to wikigen/nodes/__init__.py diff --git a/salt_docs/nodes/nodes.py b/wikigen/nodes/nodes.py similarity index 100% rename from salt_docs/nodes/nodes.py rename to wikigen/nodes/nodes.py diff --git a/salt_docs/utils/__init__.py b/wikigen/utils/__init__.py similarity index 100% rename from salt_docs/utils/__init__.py rename to wikigen/utils/__init__.py diff --git a/salt_docs/utils/adjust_headings.py b/wikigen/utils/adjust_headings.py similarity index 100% rename from salt_docs/utils/adjust_headings.py rename to wikigen/utils/adjust_headings.py diff --git a/salt_docs/utils/call_llm.py b/wikigen/utils/call_llm.py similarity index 100% rename from salt_docs/utils/call_llm.py rename to wikigen/utils/call_llm.py diff --git a/salt_docs/utils/crawl_github_files.py b/wikigen/utils/crawl_github_files.py similarity index 100% rename from salt_docs/utils/crawl_github_files.py rename to wikigen/utils/crawl_github_files.py diff --git a/salt_docs/utils/crawl_local_files.py b/wikigen/utils/crawl_local_files.py similarity index 100% rename from salt_docs/utils/crawl_local_files.py rename to wikigen/utils/crawl_local_files.py diff --git a/salt_docs/utils/llm_providers.py b/wikigen/utils/llm_providers.py similarity index 100% rename from salt_docs/utils/llm_providers.py rename to wikigen/utils/llm_providers.py diff --git a/salt_docs/utils/version_check.py b/wikigen/utils/version_check.py similarity index 100% rename from salt_docs/utils/version_check.py rename to wikigen/utils/version_check.py From 3db08f68068b98178cd93d67db1a61d8fafd8274 Mon Sep 17 00:00:00 2001 From: itsjustmithun Date: Sat, 27 Dec 2025 22:18:45 +0100 Subject: [PATCH 3/3] rename: adding assets --- README.md | 6 ++---- assets/wikigen.jpg | Bin 63469 -> 0 bytes assets/wikigen.png | Bin 0 -> 25072 bytes wikigen/metadata/version.py | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 assets/wikigen.jpg create mode 100644 assets/wikigen.png diff --git a/README.md b/README.md index e0fbe72..5f0dfba 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ -![WikiGen](assets/wikigen.jpg) - +![WikiGen](assets/wikigen.png) ## WIKIGEN [![PyPI](https://img.shields.io/badge/pypi-v0.2.4-blue)](https://pypi.org/project/wikigen/) [![Python](https://img.shields.io/badge/python-3.12+-blue)](https://www.python.org/) [![Downloads](https://img.shields.io/badge/downloads-3k+-brightgreen)](https://pypi.org/project/wikigen/) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![GitHub](https://img.shields.io/badge/github-usesalt%2Fwikigen-red)](https://github.com/usesalt/wikigen) - -**WikiGen** is a compact, human-readable documentation generator for codebases that minimizes tokens and makes structure easy for models to follow. +**WikiGen** (Previously named "salt-docs") is a compact, human-readable documentation generator for codebases that minimizes tokens and makes structure easy for models to follow. It's intended for **LLM input** as a drop-in, lossless representation of your existing codebase. diff --git a/assets/wikigen.jpg b/assets/wikigen.jpg deleted file mode 100644 index 767035474fc0582be2d69e81503601665fcff2a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63469 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

*@EGjjiFq!XOrFEc) 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 diff --git a/assets/wikigen.png b/assets/wikigen.png new file mode 100644 index 0000000000000000000000000000000000000000..c71b30a336ceed451c809b80676604c07739ef60 GIT binary patch literal 25072 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 Bt04dY literal 0 HcmV?d00001 diff --git a/wikigen/metadata/version.py b/wikigen/metadata/version.py index 53459aa..91cfc55 100644 --- a/wikigen/metadata/version.py +++ b/wikigen/metadata/version.py @@ -4,7 +4,7 @@ """ # Current version - update this when releasing -__version__ = "0.3.0" +__version__ = "1.0.0" def get_version(): @@ -12,6 +12,6 @@ def get_version(): Get the current version. Returns: - str: The current version string (e.g., "0.3.0") + str: The current version string (e.g., "1.0.0") """ return __version__