├── .nvmrc ├── .node-version ├── .husky └── pre-commit ├── test ├── test-implementations │ └── .gitkeep ├── benchmarks │ └── fixtures │ │ └── queries.js ├── reranker-default.test.js ├── codemap_extension.test.js ├── test-mcp.js ├── chunking-strategy.test.js ├── test-database-errors.js ├── run-tests.sh ├── test-search-code.js └── symbol_boost.test.js ├── src ├── codemap │ ├── telemetry.ts │ ├── io.ts │ ├── types.ts │ ├── io.js │ └── telemetry.js ├── storage │ └── encryptedChunks.ts ├── cli │ └── commands │ │ ├── search.js │ │ └── context.js ├── types │ ├── search.js │ └── contextPack.js ├── indexer │ ├── update.js │ └── merkle.js ├── search │ ├── bm25Index.js │ ├── hybrid.js │ └── applyScope.js ├── indexer.js ├── symbols │ └── graph.js ├── mcp │ └── tools │ │ └── useContextPack.js ├── metrics │ └── ir.js └── ranking │ └── boostSymbols.js ├── assets └── pampax_banner.png ├── examples ├── chat-app-python │ ├── requirements.txt │ └── templates │ │ └── index.html ├── chat-app-java │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── application.properties │ │ │ └── static │ │ │ │ └── index.html │ │ │ └── java │ │ │ └── com │ │ │ └── pampa │ │ │ └── chat │ │ │ ├── controller │ │ │ └── HomeController.java │ │ │ ├── ChatApplication.java │ │ │ ├── config │ │ │ └── WebSocketConfig.java │ │ │ ├── model │ │ │ ├── User.java │ │ │ ├── ChatMessage.java │ │ │ └── Room.java │ │ │ └── service │ │ │ └── ChatService.java │ └── pom.xml ├── contextpacks │ └── stripe-backend.json ├── chat-app-typescript │ ├── package.json │ ├── server.js │ └── modules │ │ └── userManager.js ├── chat-app-php │ ├── websocket-server.php │ ├── composer.json │ └── public │ │ └── index.php └── chat-app-go │ ├── go.mod │ ├── static │ └── index.html │ └── README.md ├── claude-desktop-config.example.json ├── .cursor ├── mcp.json └── rules │ ├── rules-kit │ └── global │ │ ├── code-standards.mdc │ │ ├── auto-test.mdc │ │ ├── file-guard.mdc │ │ ├── best-practices.mdc │ │ ├── git-commit-guidelines.mdc │ │ └── quality-assurance.mdc │ ├── pampa-mcp-usage.mdc │ ├── pampa-project-structure.mdc │ ├── readme-sync.mdc │ └── pampa-testing-examples.mdc ├── tsconfig.json ├── .windsurf └── rules │ ├── rules-kit │ └── global │ │ ├── code-standards.md │ │ ├── auto-test.md │ │ ├── file-guard.md │ │ ├── best-practices.md │ │ ├── git-commit-guidelines.md │ │ └── quality-assurance.md │ ├── pampa-mcp-usage.md │ ├── pampa-project-structure.md │ └── pampa-testing-examples.md ├── .github └── workflows │ └── release.yml ├── test-chunking-tmp └── sample.js ├── LICENSE ├── RULE_FOR_PAMPAX_MCP.md ├── .gitignore ├── docs └── CODE_OF_CONDUCT.md ├── DEMO_MULTI_PROJECT_EN.md ├── DEMO_MULTI_PROJECT.md ├── test-token-chunking.js ├── package.json ├── RATE_LIMITING.md └── RERANKER_DEFAULT_FIX.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.19.0 2 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18.17.1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm test 2 | -------------------------------------------------------------------------------- /test/test-implementations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/codemap/telemetry.ts: -------------------------------------------------------------------------------- 1 | export { bumpSuccess, touchUsed } from './telemetry.js'; 2 | -------------------------------------------------------------------------------- /src/codemap/io.ts: -------------------------------------------------------------------------------- 1 | export { resolveCodemapPath, readCodemap, writeCodemap } from './io.js'; 2 | -------------------------------------------------------------------------------- /assets/pampax_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemon07r/pampax/HEAD/assets/pampax_banner.png -------------------------------------------------------------------------------- /examples/chat-app-python/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | uvicorn[standard]==0.24.0 3 | websockets==12.0 4 | python-multipart==0.0.6 5 | jinja2==3.1.2 6 | python-socketio==5.11.0 7 | eventlet==0.33.3 8 | uuid==1.30 9 | -------------------------------------------------------------------------------- /claude-desktop-config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "pampax": { 4 | "command": "npx", 5 | "args": ["-y", "pampax", "mcp"], 6 | "env": { 7 | "OPENAI_API_KEY": "your-openai-api-key-here" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.cursor/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "pampa-local": { 4 | "command": "/bin/zsh", 5 | "args": [ 6 | "-lc", 7 | "/Users/manu/.nvm/versions/node/v23.11.1/bin/node src/mcp-server.js" 8 | ] 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # PAMPA Chat Java Configuration 2 | server.port=8083 3 | 4 | # Logging 5 | logging.level.com.pampa.chat=INFO 6 | logging.level.org.springframework.web.socket=DEBUG 7 | 8 | # Application info 9 | spring.application.name=pampa-chat-java 10 | -------------------------------------------------------------------------------- /examples/contextpacks/stripe-backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stripe Backend", 3 | "description": "Scopes searches to the Stripe service layer", 4 | "path_glob": ["app/Services/**"], 5 | "tags": ["stripe"], 6 | "lang": ["php"], 7 | "reranker": "transformers", 8 | "hybrid": "off" 9 | } 10 | -------------------------------------------------------------------------------- /src/storage/encryptedChunks.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getActiveEncryptionKey, 3 | getEncryptionKeyError, 4 | resolveEncryptionPreference, 5 | writeChunkToDisk, 6 | readChunkFromDisk, 7 | removeChunkArtifacts, 8 | isChunkEncryptedOnDisk, 9 | resetEncryptionCacheForTests 10 | } from './encryptedChunks.js'; 11 | -------------------------------------------------------------------------------- /src/codemap/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { CodemapChunkSchema, CodemapSchema } from './types.js'; 4 | 5 | export { DEFAULT_PATH_WEIGHT, DEFAULT_SUCCESS_RATE, CodemapChunkSchema, CodemapSchema, normalizeChunkMetadata, normalizeCodemapRecord, ensureCodemapDefaults } from './types.js'; 6 | 7 | export type CodemapChunk = z.infer; 8 | export type Codemap = z.infer; 9 | -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * Home Controller 8 | * Serves the main chat page 9 | */ 10 | @Controller 11 | public class HomeController { 12 | 13 | @GetMapping("/") 14 | public String index() { 15 | return "index"; // Returns static/index.html 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/chat-app-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pampa-chat-example", 3 | "version": "1.0.0", 4 | "description": "Ejemplo de chat con funciones distribuidas", 5 | "type": "module", 6 | "main": "server.js", 7 | "scripts": { 8 | "start": "node server.js", 9 | "dev": "node --watch server.js" 10 | }, 11 | "dependencies": { 12 | "@fastify/static": "^6.12.0", 13 | "@fastify/websocket": "^8.3.1", 14 | "@xenova/transformers": "^2.17.2", 15 | "fastify": "^4.27.0", 16 | "uuid": "^9.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cli/commands/search.js: -------------------------------------------------------------------------------- 1 | import { resolveScopeWithPack } from '../../context/packs.js'; 2 | 3 | export function buildScopeFiltersFromOptions(options = {}, projectPath = '.', sessionPack = null) { 4 | return resolveScopeWithPack({ 5 | path_glob: options.path_glob, 6 | tags: options.tags, 7 | lang: options.lang, 8 | reranker: options.reranker, 9 | hybrid: options.hybrid, 10 | bm25: options.bm25, 11 | symbol_boost: options.symbol_boost 12 | }, { basePath: projectPath, sessionPack }); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "allowJs": true, 9 | "outDir": "./dist", 10 | "rootDir": "./", 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "declaration": true, 16 | "declarationMap": true, 17 | "sourceMap": true 18 | }, 19 | "include": [ 20 | "src/**/*.ts", 21 | "src/**/*.js" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "dist", 26 | "test", 27 | "examples" 28 | ] 29 | } -------------------------------------------------------------------------------- /examples/chat-app-php/websocket-server.php: -------------------------------------------------------------------------------- 1 | start(8081); 22 | } catch (Exception $e) { 23 | echo "❌ Error starting server: " . $e->getMessage() . "\n"; 24 | exit(1); 25 | } 26 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/code-standards.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: General code formatting standards 4 | globs: **/* 5 | --- 6 | # Global Code Standards 7 | 8 | - Follow consistent code formatting across the entire project. 9 | - Use meaningful variable and function names that explain their purpose. 10 | - Keep functions small and focused on a single responsibility. 11 | - Add meaningful comments for complex logic, but avoid obvious comments. 12 | - Remove debug code, commented-out code and console logs before committing. 13 | - Use proper indentation (spaces or tabs) consistently throughout the project. 14 | - Follow the language/framework's standard style guide. 15 | - Avoid deep nesting of conditionals and loops. -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/code-standards.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | projectPath: ./ 3 | cursorPath: . 4 | description: General code formatting standards 5 | globs: **/* 6 | alwaysApply: true 7 | --- 8 | # Global Code Standards 9 | 10 | - Follow consistent code formatting across the entire project. 11 | - Use meaningful variable and function names that explain their purpose. 12 | - Keep functions small and focused on a single responsibility. 13 | - Add meaningful comments for complex logic, but avoid obvious comments. 14 | - Remove debug code, commented-out code and console logs before committing. 15 | - Use proper indentation (spaces or tabs) consistently throughout the project. 16 | - Follow the language/framework's standard style guide. 17 | - Avoid deep nesting of conditionals and loops. -------------------------------------------------------------------------------- /examples/chat-app-php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pampa/chat-app-php", 3 | "description": "PAMPA Chat Example - PHP with Slim Framework and ReactPHP WebSockets", 4 | "type": "project", 5 | "require": { 6 | "php": ">=8.1", 7 | "slim/slim": "^4.12", 8 | "slim/psr7": "^1.6", 9 | "react/socket": "^1.15", 10 | "react/http": "^1.9", 11 | "ratchet/pawl": "^0.4", 12 | "ramsey/uuid": "^4.7", 13 | "monolog/monolog": "^3.5" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^10.5" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "PampaChat\\": "src/" 21 | } 22 | }, 23 | "scripts": { 24 | "start": "php -S localhost:8080 -t public", 25 | "websocket": "php websocket-server.php" 26 | }, 27 | "config": { 28 | "optimize-autoloader": true, 29 | "sort-packages": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/types/search.js: -------------------------------------------------------------------------------- 1 | export const RERANKER_OPTIONS = ['off', 'transformers', 'api']; 2 | 3 | // Get default reranker from environment variable, fallback to 'off' 4 | function getDefaultReranker() { 5 | const envValue = process.env.PAMPAX_RERANKER_DEFAULT; 6 | if (envValue && RERANKER_OPTIONS.includes(envValue.toLowerCase())) { 7 | return envValue.toLowerCase(); 8 | } 9 | return 'off'; 10 | } 11 | 12 | export const DEFAULT_RERANKER = getDefaultReranker(); 13 | 14 | export function hasScopeFilters(scope = {}) { 15 | if (!scope) { 16 | return false; 17 | } 18 | 19 | return Boolean( 20 | (Array.isArray(scope.path_glob) && scope.path_glob.length > 0) || 21 | (Array.isArray(scope.tags) && scope.tags.length > 0) || 22 | (Array.isArray(scope.lang) && scope.lang.length > 0) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | permissions: 8 | id-token: write 9 | pull-requests: write 10 | contents: write 11 | issues: write 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | - name: Install pnpm 25 | run: npm install -g pnpm 26 | 27 | - run: pnpm install 28 | - run: pnpm exec semantic-release 29 | env: 30 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/ChatApplication.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * PAMPA Chat Application - Java with Spring Boot 8 | * Main application class for the chat server 9 | */ 10 | @SpringBootApplication 11 | public class ChatApplication { 12 | 13 | public static void main(String[] args) { 14 | System.out.println("🚀 Starting PAMPA Chat Java Server..."); 15 | System.out.println("🌐 Web interface will be available at http://localhost:8083"); 16 | System.out.println("📡 WebSocket endpoint: ws://localhost:8083/ws"); 17 | System.out.println("💡 Use Ctrl+C to stop the server\n"); 18 | 19 | SpringApplication.run(ChatApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-chunking-tmp/sample.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @pampa-tags: test, large-function, sample 4 | * @pampa-intent: Test function for chunking 5 | * @pampa-description: A large test function to demonstrate token-based chunking 6 | */ 7 | function testLargeFunction() { 8 | // This is a test function with multiple statements 9 | const data = []; 10 | 11 | for (let i = 0; i < 100; i++) { 12 | data.push({ 13 | id: i, 14 | name: 'item' + i, 15 | value: Math.random() 16 | }); 17 | } 18 | 19 | function processData() { 20 | return data.map(item => { 21 | return { 22 | ...item, 23 | processed: true 24 | }; 25 | }); 26 | } 27 | 28 | return processData(); 29 | } 30 | 31 | class SampleClass { 32 | constructor() { 33 | this.value = 42; 34 | } 35 | 36 | method1() { 37 | return this.value * 2; 38 | } 39 | 40 | method2() { 41 | return this.value * 3; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/benchmarks/fixtures/queries.js: -------------------------------------------------------------------------------- 1 | export const queryFixtures = [ 2 | { 3 | name: 'Checkout Session', 4 | query: 'create stripe checkout session', 5 | relevant: ['sha-checkout-service'], 6 | vector: [0.99, 0.1, 0.05, 0.02] 7 | }, 8 | { 9 | name: 'Cart Totals', 10 | query: 'synchronize cart totals', 11 | relevant: ['sha-cart-service'], 12 | vector: [0.97, 0.14, 0.05, 0.02] 13 | }, 14 | { 15 | name: 'Password Reset', 16 | query: 'send password reset email', 17 | relevant: ['sha-auth-email'], 18 | vector: [0.14, 0.24, 0.93, 0.08] 19 | }, 20 | { 21 | name: 'Invoice PDF', 22 | query: 'render invoice pdf', 23 | relevant: ['sha-invoice-pdf'], 24 | vector: [0.18, 0.26, 0.12, 0.95] 25 | } 26 | ]; 27 | 28 | export function getQueryVector(query) { 29 | const normalized = query.toLowerCase(); 30 | const match = queryFixtures.find(entry => entry.query === normalized); 31 | if (match) { 32 | return match.vector; 33 | } 34 | 35 | return [0.5, 0.5, 0.5, 0.5]; 36 | } 37 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/auto-test.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: Guidelines for automated testing 4 | globs: **/* 5 | --- 6 | # Automated Testing 7 | 8 | Run the test suite after each change and fix issues until all tests pass. 9 | 10 | ## Testing Workflow 11 | 12 | 1. **Run Tests Frequently**: Execute tests after implementing each logical component. 13 | 2. **Fix Issues Immediately**: Address test failures as soon as they appear. 14 | 3. **Regression Testing**: Ensure existing functionality remains intact. 15 | 4. **Test Coverage**: Add new tests when implementing new features. 16 | 17 | ## Common Test Commands 18 | 19 | ```bash 20 | # Framework-specific test commands 21 | npm test # Node.js/JavaScript 22 | php artisan test # Laravel 23 | ng test # Angular 24 | mvn test # Java/Maven 25 | pytest # Python 26 | go test ./... # Go 27 | ``` 28 | 29 | ## Test-Driven Development 30 | 31 | Consider adopting TDD principles: 32 | 33 | 1. Write a failing test first 34 | 2. Implement the minimal code to pass the test 35 | 3. Refactor while keeping tests passing -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 5 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 6 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 7 | 8 | import com.pampa.chat.handler.ChatWebSocketHandler; 9 | 10 | /** 11 | * WebSocket Configuration 12 | * Configures WebSocket endpoints and handlers 13 | */ 14 | @Configuration 15 | @EnableWebSocket 16 | public class WebSocketConfig implements WebSocketConfigurer { 17 | 18 | private final ChatWebSocketHandler chatWebSocketHandler; 19 | 20 | public WebSocketConfig(ChatWebSocketHandler chatWebSocketHandler) { 21 | this.chatWebSocketHandler = chatWebSocketHandler; 22 | } 23 | 24 | @Override 25 | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 26 | registry.addHandler(chatWebSocketHandler, "/ws") 27 | .setAllowedOrigins("*"); // In production, specify allowed origins 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/auto-test.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | projectPath: ./ 3 | cursorPath: . 4 | description: Guidelines for automated testing 5 | globs: **/* 6 | alwaysApply: true 7 | --- 8 | # Automated Testing 9 | 10 | Run the test suite after each change and fix issues until all tests pass. 11 | 12 | ## Testing Workflow 13 | 14 | 1. **Run Tests Frequently**: Execute tests after implementing each logical component. 15 | 2. **Fix Issues Immediately**: Address test failures as soon as they appear. 16 | 3. **Regression Testing**: Ensure existing functionality remains intact. 17 | 4. **Test Coverage**: Add new tests when implementing new features. 18 | 19 | ## Common Test Commands 20 | 21 | ```bash 22 | # Framework-specific test commands 23 | npm test # Node.js/JavaScript 24 | php artisan test # Laravel 25 | ng test # Angular 26 | mvn test # Java/Maven 27 | pytest # Python 28 | go test ./... # Go 29 | ``` 30 | 31 | ## Test-Driven Development 32 | 33 | Consider adopting TDD principles: 34 | 35 | 1. Write a failing test first 36 | 2. Implement the minimal code to pass the test 37 | 3. Refactor while keeping tests passing -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Manuel Bruña (original pampa project) 4 | Copyright (c) 2025 Lamim (pampax fork and enhancements) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/codemap/io.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { normalizeCodemapRecord } from './types.js'; 4 | 5 | export function resolveCodemapPath(basePath = '.') { 6 | return path.resolve(basePath, 'pampa.codemap.json'); 7 | } 8 | 9 | export function readCodemap(filePath) { 10 | const resolvedPath = filePath ? path.resolve(filePath) : resolveCodemapPath('.'); 11 | 12 | if (!fs.existsSync(resolvedPath)) { 13 | return {}; 14 | } 15 | 16 | try { 17 | const raw = fs.readFileSync(resolvedPath, 'utf8'); 18 | const parsed = JSON.parse(raw); 19 | return normalizeCodemapRecord(parsed); 20 | } catch (error) { 21 | console.warn(`Failed to read codemap at ${resolvedPath}:`, error.message); 22 | return {}; 23 | } 24 | } 25 | 26 | export function writeCodemap(filePath, codemap) { 27 | const resolvedPath = filePath ? path.resolve(filePath) : resolveCodemapPath('.'); 28 | const normalized = normalizeCodemapRecord(codemap || {}); 29 | 30 | const directory = path.dirname(resolvedPath); 31 | fs.mkdirSync(directory, { recursive: true }); 32 | 33 | fs.writeFileSync(resolvedPath, JSON.stringify(normalized, null, 2)); 34 | return normalized; 35 | } 36 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/file-guard.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: File operation guidelines to prevent content loss 4 | globs: **/* 5 | --- 6 | # File Management Guard 7 | 8 | Before creating a file, check if it already exists. If it exists and there is no explicit instruction to overwrite, merge content instead of replacing. 9 | 10 | ## Best Practices for File Operations 11 | 12 | 1. **Check Existence**: Always verify if a file exists before creating it. 13 | 2. **Preserve Content**: When a file exists, preserve important elements: 14 | 15 | - Comments and documentation 16 | - Existing imports 17 | - License headers 18 | - Configuration settings 19 | 20 | 3. **Merge Strategies**: 21 | 22 | - For code files: Add new functions, classes, or methods without removing existing ones 23 | - For configuration files: Add new settings while preserving existing ones 24 | - For documentation: Append new information or integrate it with existing content 25 | 26 | 4. **Communicate Changes**: When modifying existing files, document the nature of the changes. 27 | 28 | ## When to Overwrite 29 | 30 | Only overwrite files when: 31 | 32 | 1. Explicitly instructed to do so 33 | 2. The file is known to be a generated file that should be recreated 34 | 3. The entire purpose of the file is being changed -------------------------------------------------------------------------------- /RULE_FOR_PAMPAX_MCP.md: -------------------------------------------------------------------------------- 1 | # PAMPAX MCP Usage Rule 2 | 3 | You have access to PAMPAX, a code memory system that indexes and allows semantic search in projects. 4 | 5 | ## Basic Instructions 6 | 7 | 1. **ALWAYS at the start of a session:** 8 | 9 | - Run `get_project_stats` to check if the project is indexed 10 | - If no database exists, run `index_project` 11 | - Run `update_project` to sync with recent changes 12 | 13 | 2. **BEFORE creating any function:** 14 | 15 | - Use `search_code` with semantic queries like "user authentication", "validate email", "error handling" 16 | - Review existing code with `get_code_chunk` before writing new code 17 | 18 | 3. **AFTER modifying code:** 19 | - Run `update_project` to update the knowledge base 20 | - This keeps the project memory synchronized 21 | 22 | ## Available Tools 23 | 24 | - `search_code(query, limit)` - Search code semantically 25 | - `get_code_chunk(sha)` - Get complete code of a chunk 26 | - `index_project(path)` - Index project for the first time 27 | - `update_project(path)` - Update index after changes 28 | - `get_project_stats(path)` - Get project statistics 29 | 30 | ## Strategy 31 | 32 | Use PAMPAX as your project memory. Search before creating, keep updated after changes, and leverage existing knowledge to avoid code duplication. 33 | -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/file-guard.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | projectPath: ./ 3 | cursorPath: . 4 | description: File operation guidelines to prevent content loss 5 | globs: **/* 6 | alwaysApply: true 7 | --- 8 | # File Management Guard 9 | 10 | Before creating a file, check if it already exists. If it exists and there is no explicit instruction to overwrite, merge content instead of replacing. 11 | 12 | ## Best Practices for File Operations 13 | 14 | 1. **Check Existence**: Always verify if a file exists before creating it. 15 | 2. **Preserve Content**: When a file exists, preserve important elements: 16 | 17 | - Comments and documentation 18 | - Existing imports 19 | - License headers 20 | - Configuration settings 21 | 22 | 3. **Merge Strategies**: 23 | 24 | - For code files: Add new functions, classes, or methods without removing existing ones 25 | - For configuration files: Add new settings while preserving existing ones 26 | - For documentation: Append new information or integrate it with existing content 27 | 28 | 4. **Communicate Changes**: When modifying existing files, document the nature of the changes. 29 | 30 | ## When to Overwrite 31 | 32 | Only overwrite files when: 33 | 34 | 1. Explicitly instructed to do so 35 | 2. The file is known to be a generated file that should be recreated 36 | 3. The entire purpose of the file is being changed -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/model/User.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | /** 6 | * User model representing a chat user 7 | */ 8 | public class User { 9 | private String id; 10 | private String username; 11 | private String avatar; 12 | private LocalDateTime joinedAt; 13 | private String currentRoom; 14 | 15 | public User() {} 16 | 17 | public User(String id, String username, String avatar, String currentRoom) { 18 | this.id = id; 19 | this.username = username; 20 | this.avatar = avatar; 21 | this.currentRoom = currentRoom; 22 | this.joinedAt = LocalDateTime.now(); 23 | } 24 | 25 | // Getters and Setters 26 | public String getId() { return id; } 27 | public void setId(String id) { this.id = id; } 28 | 29 | public String getUsername() { return username; } 30 | public void setUsername(String username) { this.username = username; } 31 | 32 | public String getAvatar() { return avatar; } 33 | public void setAvatar(String avatar) { this.avatar = avatar; } 34 | 35 | public LocalDateTime getJoinedAt() { return joinedAt; } 36 | public void setJoinedAt(LocalDateTime joinedAt) { this.joinedAt = joinedAt; } 37 | 38 | public String getCurrentRoom() { return currentRoom; } 39 | public void setCurrentRoom(String currentRoom) { this.currentRoom = currentRoom; } 40 | } 41 | -------------------------------------------------------------------------------- /examples/chat-app-go/go.mod: -------------------------------------------------------------------------------- 1 | module pampa-chat-go 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/gorilla/websocket v1.5.1 8 | github.com/google/uuid v1.6.0 9 | github.com/sirupsen/logrus v1.9.3 10 | ) 11 | 12 | require ( 13 | github.com/bytedance/sonic v1.9.1 // indirect 14 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 16 | github.com/gin-contrib/sse v0.1.0 // indirect 17 | github.com/go-playground/locales v0.14.1 // indirect 18 | github.com/go-playground/universal-translator v0.18.1 // indirect 19 | github.com/go-playground/validator/v10 v10.14.0 // indirect 20 | github.com/goccy/go-json v0.10.2 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 23 | github.com/leodido/go-urn v1.2.4 // indirect 24 | github.com/mattn/go-isatty v0.0.19 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | github.com/ugorji/go/codec v1.2.11 // indirect 30 | golang.org/x/arch v0.3.0 // indirect 31 | golang.org/x/crypto v0.9.0 // indirect 32 | golang.org/x/net v0.10.0 // indirect 33 | golang.org/x/sys v0.8.0 // indirect 34 | golang.org/x/text v0.9.0 // indirect 35 | google.golang.org/protobuf v1.30.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/model/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | /** 6 | * ChatMessage model representing a chat message 7 | */ 8 | public class ChatMessage { 9 | private String id; 10 | private String type; 11 | private User user; 12 | private String content; 13 | private String roomId; 14 | private LocalDateTime timestamp; 15 | 16 | public ChatMessage() {} 17 | 18 | public ChatMessage(String id, String type, User user, String content, String roomId) { 19 | this.id = id; 20 | this.type = type; 21 | this.user = user; 22 | this.content = content; 23 | this.roomId = roomId; 24 | this.timestamp = LocalDateTime.now(); 25 | } 26 | 27 | // Getters and Setters 28 | public String getId() { return id; } 29 | public void setId(String id) { this.id = id; } 30 | 31 | public String getType() { return type; } 32 | public void setType(String type) { this.type = type; } 33 | 34 | public User getUser() { return user; } 35 | public void setUser(User user) { this.user = user; } 36 | 37 | public String getContent() { return content; } 38 | public void setContent(String content) { this.content = content; } 39 | 40 | public String getRoomId() { return roomId; } 41 | public void setRoomId(String roomId) { this.roomId = roomId; } 42 | 43 | public LocalDateTime getTimestamp() { return timestamp; } 44 | public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } 45 | } 46 | -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/model/Room.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.model; 2 | 3 | /** 4 | * Room model representing a chat room 5 | */ 6 | public class Room { 7 | private String id; 8 | private String name; 9 | private String description; 10 | private boolean isPublic; 11 | private int maxUsers; 12 | private String createdBy; 13 | 14 | public Room() {} 15 | 16 | public Room(String id, String name, String description, boolean isPublic, int maxUsers, String createdBy) { 17 | this.id = id; 18 | this.name = name; 19 | this.description = description; 20 | this.isPublic = isPublic; 21 | this.maxUsers = maxUsers; 22 | this.createdBy = createdBy; 23 | } 24 | 25 | // Getters and Setters 26 | public String getId() { return id; } 27 | public void setId(String id) { this.id = id; } 28 | 29 | public String getName() { return name; } 30 | public void setName(String name) { this.name = name; } 31 | 32 | public String getDescription() { return description; } 33 | public void setDescription(String description) { this.description = description; } 34 | 35 | public boolean isPublic() { return isPublic; } 36 | public void setPublic(boolean isPublic) { this.isPublic = isPublic; } 37 | 38 | public int getMaxUsers() { return maxUsers; } 39 | public void setMaxUsers(int maxUsers) { this.maxUsers = maxUsers; } 40 | 41 | public String getCreatedBy() { return createdBy; } 42 | public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } 43 | } 44 | -------------------------------------------------------------------------------- /src/indexer/update.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { indexProject } from '../service.js'; 3 | import { normalizeToProjectPath } from './merkle.js'; 4 | 5 | function normalizeList(basePath, values = []) { 6 | const normalized = new Set(); 7 | 8 | if (!Array.isArray(values)) { 9 | return []; 10 | } 11 | 12 | for (const value of values) { 13 | const relative = normalizeToProjectPath(basePath, value); 14 | if (relative) { 15 | normalized.add(relative); 16 | } 17 | } 18 | 19 | return Array.from(normalized); 20 | } 21 | 22 | export async function updateIndex({ 23 | repoPath = '.', 24 | provider = 'auto', 25 | changedFiles = [], 26 | deletedFiles = [], 27 | onProgress = null, 28 | embeddingProvider = null, 29 | encrypt = undefined 30 | } = {}) { 31 | const root = path.resolve(repoPath); 32 | const normalizedChanged = normalizeList(root, changedFiles); 33 | const normalizedDeleted = normalizeList(root, deletedFiles); 34 | 35 | if (normalizedChanged.length === 0 && normalizedDeleted.length === 0) { 36 | return { 37 | success: true, 38 | processedChunks: 0, 39 | totalChunks: 0, 40 | provider, 41 | errors: [] 42 | }; 43 | } 44 | 45 | return indexProject({ 46 | repoPath: root, 47 | provider, 48 | onProgress, 49 | changedFiles: normalizedChanged, 50 | deletedFiles: normalizedDeleted, 51 | embeddingProviderOverride: embeddingProvider, 52 | encryptMode: encrypt 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/search/bm25Index.js: -------------------------------------------------------------------------------- 1 | import bm25Factory from 'wink-bm25-text-search'; 2 | 3 | const DEFAULT_FIELD = 'body'; 4 | 5 | function defaultPrep(text) { 6 | if (!text) { 7 | return []; 8 | } 9 | 10 | return String(text) 11 | .toLowerCase() 12 | .replace(/[^\p{L}\p{N}]+/gu, ' ') 13 | .split(/\s+/u) 14 | .filter(Boolean); 15 | } 16 | 17 | export class BM25Index { 18 | constructor() { 19 | this.engine = bm25Factory(); 20 | this.engine.defineConfig({ fldWeights: { [DEFAULT_FIELD]: 1 } }); 21 | this.engine.definePrepTasks([defaultPrep]); 22 | this.documents = new Set(); 23 | this.consolidated = false; 24 | } 25 | 26 | addDocument(id, text) { 27 | if (!id || typeof text !== 'string' || text.trim().length === 0) { 28 | return; 29 | } 30 | 31 | if (this.documents.has(id)) { 32 | return; 33 | } 34 | 35 | this.engine.addDoc({ [DEFAULT_FIELD]: text }, id); 36 | this.documents.add(id); 37 | this.consolidated = false; 38 | } 39 | 40 | addDocuments(entries = []) { 41 | for (const entry of entries) { 42 | if (!entry) continue; 43 | const { id, text } = entry; 44 | this.addDocument(id, text); 45 | } 46 | } 47 | 48 | consolidate() { 49 | if (!this.consolidated) { 50 | this.engine.consolidate(); 51 | this.consolidated = true; 52 | } 53 | } 54 | 55 | search(query, limit = 60) { 56 | if (!query || !query.trim()) { 57 | return []; 58 | } 59 | 60 | this.consolidate(); 61 | const results = this.engine.search(query, limit); 62 | return results.map(([id, score]) => ({ id, score })); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/codemap/telemetry.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_PATH_WEIGHT, DEFAULT_SUCCESS_RATE, normalizeChunkMetadata } from './types.js'; 2 | 3 | function coerceDate(value) { 4 | if (value instanceof Date && !Number.isNaN(value.valueOf())) { 5 | return value; 6 | } 7 | if (typeof value === 'string') { 8 | const parsed = new Date(value); 9 | if (!Number.isNaN(parsed.valueOf())) { 10 | return parsed; 11 | } 12 | } 13 | return null; 14 | } 15 | 16 | export function touchUsed(chunk, timestamp = new Date()) { 17 | if (!chunk || typeof chunk !== 'object') { 18 | return chunk; 19 | } 20 | 21 | const date = coerceDate(timestamp) || new Date(); 22 | const iso = date.toISOString(); 23 | 24 | chunk.last_used_at = iso; 25 | return chunk; 26 | } 27 | 28 | export function bumpSuccess(chunk, ok, alpha = 0.2) { 29 | if (!chunk || typeof chunk !== 'object') { 30 | return chunk; 31 | } 32 | 33 | const smoothing = typeof alpha === 'number' && alpha > 0 && alpha <= 1 ? alpha : 0.2; 34 | const current = typeof chunk.success_rate === 'number' && Number.isFinite(chunk.success_rate) 35 | ? Math.min(1, Math.max(0, chunk.success_rate)) 36 | : DEFAULT_SUCCESS_RATE; 37 | 38 | const target = ok ? 1 : 0; 39 | const next = current + smoothing * (target - current); 40 | const clamped = Math.min(1, Math.max(0, next)); 41 | 42 | chunk.success_rate = clamped; 43 | 44 | if (typeof chunk.path_weight !== 'number' || !Number.isFinite(chunk.path_weight)) { 45 | chunk.path_weight = DEFAULT_PATH_WEIGHT; 46 | } 47 | 48 | if (!Array.isArray(chunk.synonyms)) { 49 | const normalized = normalizeChunkMetadata(chunk); 50 | chunk.synonyms = normalized.synonyms; 51 | } 52 | 53 | return chunk; 54 | } 55 | -------------------------------------------------------------------------------- /src/types/contextPack.js: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { RERANKER_OPTIONS } from './search.js'; 3 | 4 | const stringOrStringArray = z.union([z.string(), z.array(z.string())]); 5 | const booleanLike = z.union([z.boolean(), z.string()]); 6 | 7 | export const ContextPackScopeSchema = z.object({ 8 | path_glob: stringOrStringArray.optional(), 9 | tags: stringOrStringArray.optional(), 10 | lang: stringOrStringArray.optional(), 11 | provider: z.string().optional(), 12 | reranker: z.enum(RERANKER_OPTIONS).optional(), 13 | hybrid: booleanLike.optional(), 14 | bm25: booleanLike.optional(), 15 | symbol_boost: booleanLike.optional() 16 | }).strict(); 17 | 18 | export const ContextPackSchema = z.object({ 19 | name: z.string().optional(), 20 | description: z.string().optional(), 21 | metadata: z.record(z.any()).optional(), 22 | scope: ContextPackScopeSchema.optional(), 23 | path_glob: stringOrStringArray.optional(), 24 | tags: stringOrStringArray.optional(), 25 | lang: stringOrStringArray.optional(), 26 | provider: z.string().optional(), 27 | reranker: z.enum(RERANKER_OPTIONS).optional(), 28 | hybrid: booleanLike.optional(), 29 | bm25: booleanLike.optional(), 30 | symbol_boost: booleanLike.optional() 31 | }).strict(); 32 | 33 | export function extractScopeFromPackDefinition(definition) { 34 | if (!definition || typeof definition !== 'object') { 35 | return {}; 36 | } 37 | 38 | const scopeCandidate = definition.scope && typeof definition.scope === 'object' 39 | ? { ...definition.scope } 40 | : {}; 41 | 42 | const scope = { ...scopeCandidate }; 43 | 44 | for (const key of ['path_glob', 'tags', 'lang', 'provider', 'reranker', 'hybrid', 'bm25', 'symbol_boost']) { 45 | if (Object.prototype.hasOwnProperty.call(definition, key) && typeof definition[key] !== 'undefined') { 46 | scope[key] = definition[key]; 47 | } 48 | } 49 | 50 | return scope; 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencias Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Dependencias Python 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | *.so 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # Entornos virtuales Python 31 | venv/ 32 | env/ 33 | ENV/ 34 | env.bak/ 35 | venv.bak/ 36 | .venv/ 37 | 38 | # Dependencias Java 39 | target/ 40 | *.class 41 | *.jar 42 | *.war 43 | *.ear 44 | *.nar 45 | hs_err_pid* 46 | 47 | # Dependencias PHP 48 | vendor/ 49 | composer.lock 50 | *.phar 51 | 52 | # Dependencias Go 53 | go.sum 54 | *.exe 55 | *.exe~ 56 | *.dll 57 | *.so 58 | *.dylib 59 | *.test 60 | *.out 61 | 62 | # Base de datos y chunks locales (no commitear) 63 | .pampa/ 64 | pampa.codemap.json 65 | 66 | # Backups de la base de datos (creados durante testing) 67 | .pampa.backup-*/ 68 | pampa.codemap.json.backup-* 69 | 70 | # Variables de entorno 71 | .env 72 | .env.local 73 | .env.development.local 74 | .env.test.local 75 | .env.production.local 76 | 77 | # Logs 78 | logs 79 | *.log 80 | 81 | # Archivos temporales 82 | .tmp/ 83 | .temp/ 84 | 85 | # Archivos de sistema 86 | .DS_Store 87 | Thumbs.db 88 | 89 | # IDE 90 | .vscode/ 91 | .idea/ 92 | *.swp 93 | *.swo 94 | 95 | # Build outputs 96 | dist/ 97 | build/ 98 | *.tsbuildinfo 99 | 100 | # Archivos de test 101 | coverage/ 102 | test-*.js 103 | 104 | # Test implementations (contenido privado, NO commitear) 105 | test/test-implementations/* 106 | !test/test-implementations/.gitkeep 107 | 108 | # Archivos específicos de PAMPA que SÍ se deben commitear: 109 | # pampa.codemap.json - ¡IMPORTANTE: NO ignorar este archivo! 110 | 111 | .history 112 | 113 | .npmignore 114 | PUBLISHING.md 115 | 116 | AGENTS.md 117 | WARP.md 118 | CLAUDE.md -------------------------------------------------------------------------------- /src/search/hybrid.js: -------------------------------------------------------------------------------- 1 | export function reciprocalRankFusion({ vectorResults = [], bm25Results = [], limit = 10, k = 60 }) { 2 | const scores = new Map(); 3 | 4 | const addScores = (items, source) => { 5 | items.forEach((item, index) => { 6 | if (!item || typeof item.id === 'undefined') { 7 | return; 8 | } 9 | 10 | const rankContribution = 1 / (k + index + 1); 11 | const existing = scores.get(item.id) || { 12 | id: item.id, 13 | score: 0, 14 | vectorRank: null, 15 | bm25Rank: null, 16 | vectorScore: null, 17 | bm25Score: null 18 | }; 19 | 20 | existing.score += rankContribution; 21 | if (source === 'vector') { 22 | existing.vectorRank = index; 23 | existing.vectorScore = item.score; 24 | } else if (source === 'bm25') { 25 | existing.bm25Rank = index; 26 | existing.bm25Score = item.score; 27 | } 28 | 29 | scores.set(item.id, existing); 30 | }); 31 | }; 32 | 33 | addScores(vectorResults, 'vector'); 34 | addScores(bm25Results, 'bm25'); 35 | 36 | return Array.from(scores.values()) 37 | .sort((a, b) => { 38 | if (b.score !== a.score) { 39 | return b.score - a.score; 40 | } 41 | 42 | const aVectorRank = typeof a.vectorRank === 'number' ? a.vectorRank : Number.MAX_SAFE_INTEGER; 43 | const bVectorRank = typeof b.vectorRank === 'number' ? b.vectorRank : Number.MAX_SAFE_INTEGER; 44 | if (aVectorRank !== bVectorRank) { 45 | return aVectorRank - bVectorRank; 46 | } 47 | 48 | const aBmRank = typeof a.bm25Rank === 'number' ? a.bm25Rank : Number.MAX_SAFE_INTEGER; 49 | const bBmRank = typeof b.bm25Rank === 'number' ? b.bm25Rank : Number.MAX_SAFE_INTEGER; 50 | if (aBmRank !== bBmRank) { 51 | return aBmRank - bBmRank; 52 | } 53 | 54 | return 0; 55 | }) 56 | .slice(0, limit); 57 | } 58 | -------------------------------------------------------------------------------- /.cursor/rules/pampa-mcp-usage.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # PAMPA MCP Usage Rule 7 | 8 | You have access to PAMPA, a code memory system that indexes and allows semantic search in projects. This rule is based on [RULE_FOR_PAMPA_MCP.md](mdc:RULE_FOR_PAMPA_MCP.md). 9 | 10 | ## Important: Language Usage 11 | 12 | **ALWAYS use English for all PAMPA queries and searches.** The semantic search engine is optimized for English technical terms and provides significantly better results than other languages. 13 | 14 | Examples: 15 | - ✅ Good: "user authentication", "payment processing", "database connection" 16 | - ❌ Avoid: "autenticación de usuario", "procesamiento de pagos" 17 | 18 | ## Basic Instructions 19 | 20 | 1. **ALWAYS at the start of a session:** 21 | - Run `get_project_stats` to check if the project is indexed 22 | - If no database exists, run `index_project` 23 | - Run `update_project` to sync with recent changes 24 | 25 | 2. **BEFORE creating any function:** 26 | - Use `search_code` with semantic queries like "user authentication", "validate email", "error handling" 27 | - Review existing code with `get_code_chunk` before writing new code 28 | 29 | 3. **AFTER modifying code:** 30 | - Run `update_project` to update the knowledge base 31 | - This keeps the project memory synchronized 32 | 33 | ## Available MCP Tools 34 | 35 | - `search_code(query, limit, path)` - Search code semantically in the project 36 | - `get_code_chunk(sha, path)` - Get complete source code of a specific chunk 37 | - `index_project(path, provider)` - Index project for the first time 38 | - `update_project(path, provider)` - Update index after code changes 39 | - `get_project_stats(path)` - Get project statistics and overview 40 | 41 | ## Strategy 42 | 43 | Use PAMPA as your project memory. Search before creating, keep updated after changes, and leverage existing knowledge to avoid code duplication. 44 | 45 | ## Example Workflow 46 | 47 | ``` 48 | 1. get_project_stats() → Check if project is indexed 49 | 2. search_code("authentication function") → Find existing auth code 50 | 3. get_code_chunk(sha_from_search) → Review implementation 51 | 4. [Make code changes] 52 | 5. update_project() → Sync new changes to memory 53 | -------------------------------------------------------------------------------- /.windsurf/rules/pampa-mcp-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | --- 4 | 5 | # PAMPA MCP Usage Rule 6 | 7 | You have access to PAMPA, a code memory system that indexes and allows semantic search in projects. This rule is based on [RULE_FOR_PAMPA_MCP.md](mdc:RULE_FOR_PAMPA_MCP.md). 8 | 9 | ## Important: Language Usage 10 | 11 | **ALWAYS use English for all PAMPA queries and searches.** The semantic search engine is optimized for English technical terms and provides significantly better results than other languages. 12 | 13 | Examples: 14 | 15 | - ✅ Good: "user authentication", "payment processing", "database connection" 16 | - ❌ Avoid: "autenticación de usuario", "procesamiento de pagos" 17 | 18 | ## Basic Instructions 19 | 20 | 1. **ALWAYS at the start of a session:** 21 | 22 | - Run `get_project_stats` to check if the project is indexed 23 | - If no database exists, run `index_project` 24 | - Run `update_project` to sync with recent changes 25 | 26 | 2. **BEFORE creating any function:** 27 | 28 | - Use `search_code` with semantic queries like "user authentication", "validate email", "error handling" 29 | - Review existing code with `get_code_chunk` before writing new code 30 | 31 | 3. **AFTER modifying code:** 32 | - Run `update_project` to update the knowledge base 33 | - This keeps the project memory synchronized 34 | 35 | ## Available MCP Tools 36 | 37 | - `search_code(query, limit, path)` - Search code semantically in the project 38 | - `get_code_chunk(sha, path)` - Get complete source code of a specific chunk 39 | - `index_project(path, provider)` - Index project for the first time 40 | - `update_project(path, provider)` - Update index after code changes 41 | - `get_project_stats(path)` - Get project statistics and overview 42 | 43 | ## Strategy 44 | 45 | Use PAMPA as your project memory. Search before creating, keep updated after changes, and leverage existing knowledge to avoid code duplication. 46 | 47 | ## Example Workflow 48 | 49 | ``` 50 | 1. get_project_stats() → Check if project is indexed 51 | 2. search_code("authentication function") → Find existing auth code 52 | 3. get_code_chunk(sha_from_search) → Review implementation 53 | 4. [Make code changes] 54 | 5. update_project() → Sync new changes to memory 55 | ``` 56 | -------------------------------------------------------------------------------- /.cursor/rules/pampa-project-structure.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # PAMPA Project Structure Guide 7 | 8 | This is the PAMPA (Protocol for Augmented Memory of Project Artifacts) project - an MCP-compatible semantic code search system. 9 | 10 | ## Core Architecture Files 11 | 12 | ### Main Entry Points 13 | - [cli.js](mdc:cli.js) - Command line interface, handles `npx pampa` commands 14 | - [mcp-server.js](mdc:mcp-server.js) - MCP server that exposes tools to AI agents 15 | - [package.json](mdc:package.json) - Project configuration and dependencies 16 | 17 | ### Core Engine 18 | - [service.js](mdc:service.js) - Core business logic: indexing, searching, chunking 19 | - [indexer.js](mdc:indexer.js) - Tree-sitter based code parsing and chunking 20 | - [providers.js](mdc:providers.js) - Embedding providers (OpenAI, Transformers, Ollama, Cohere) 21 | 22 | ### Generated Files 23 | - [pampa.codemap.json](mdc:pampa.codemap.json) - Lightweight index committed to git 24 | - `.pampa/pampa.db` - SQLite database with vectors and metadata (not committed) 25 | - `.pampa/chunks/*.gz` - Compressed code chunks (not committed) 26 | 27 | ## Technology Stack 28 | 29 | - **Node.js** - Runtime environment 30 | - **SQLite3** - Local vector database 31 | - **Tree-sitter** - Code parsing and AST analysis 32 | - **OpenAI/Transformers.js** - Embedding generation 33 | - **MCP Protocol** - AI agent communication 34 | - **gzip** - Code chunk compression 35 | 36 | ## Key Concepts 37 | 38 | 1. **Chunking**: Code is split into semantic units (functions, classes, methods) 39 | 2. **Embedding**: Each chunk gets vectorized for semantic search 40 | 3. **Indexing**: Vectors + metadata stored in local SQLite 41 | 4. **Codemap**: Git-friendly JSON index for context portability 42 | 5. **MCP Serving**: Tools exposed to AI agents via MCP protocol 43 | 44 | ## Development Workflow 45 | 46 | 1. **CLI Development**: Modify [cli.js](mdc:cli.js) for new commands 47 | 2. **MCP Tools**: Add tools in [mcp-server.js](mdc:mcp-server.js) 48 | 3. **Core Logic**: Business logic goes in [service.js](mdc:service.js) 49 | 4. **New Providers**: Add embedding providers in [providers.js](mdc:providers.js) 50 | 51 | ## File Patterns 52 | 53 | - `*.js` - Main application code 54 | - `*.md` - Documentation and rules 55 | - `.pampa/` - Generated database and chunks (gitignored) 56 | - `examples/` - Test projects for validation 57 | - `test/` - Test suite 58 | -------------------------------------------------------------------------------- /.windsurf/rules/pampa-project-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | --- 4 | 5 | # PAMPA Project Structure Guide 6 | 7 | This is the PAMPA (Protocol for Augmented Memory of Project Artifacts) project - an MCP-compatible semantic code search system. 8 | 9 | ## Core Architecture Files 10 | 11 | ### Main Entry Points 12 | 13 | - [cli.js](mdc:cli.js) - Command line interface, handles `npx pampa` commands 14 | - [mcp-server.js](mdc:mcp-server.js) - MCP server that exposes tools to AI agents 15 | - [package.json](mdc:package.json) - Project configuration and dependencies 16 | 17 | ### Core Engine 18 | 19 | - [service.js](mdc:service.js) - Core business logic: indexing, searching, chunking 20 | - [indexer.js](mdc:indexer.js) - Tree-sitter based code parsing and chunking 21 | - [providers.js](mdc:providers.js) - Embedding providers (OpenAI, Transformers, Ollama, Cohere) 22 | 23 | ### Generated Files 24 | 25 | - [pampa.codemap.json](mdc:pampa.codemap.json) - Lightweight index committed to git 26 | - `.pampa/pampa.db` - SQLite database with vectors and metadata (not committed) 27 | - `.pampa/chunks/*.gz` - Compressed code chunks (not committed) 28 | 29 | ## Technology Stack 30 | 31 | - **Node.js** - Runtime environment 32 | - **SQLite3** - Local vector database 33 | - **Tree-sitter** - Code parsing and AST analysis 34 | - **OpenAI/Transformers.js** - Embedding generation 35 | - **MCP Protocol** - AI agent communication 36 | - **gzip** - Code chunk compression 37 | 38 | ## Key Concepts 39 | 40 | 1. **Chunking**: Code is split into semantic units (functions, classes, methods) 41 | 2. **Embedding**: Each chunk gets vectorized for semantic search 42 | 3. **Indexing**: Vectors + metadata stored in local SQLite 43 | 4. **Codemap**: Git-friendly JSON index for context portability 44 | 5. **MCP Serving**: Tools exposed to AI agents via MCP protocol 45 | 46 | ## Development Workflow 47 | 48 | 1. **CLI Development**: Modify [cli.js](mdc:cli.js) for new commands 49 | 2. **MCP Tools**: Add tools in [mcp-server.js](mdc:mcp-server.js) 50 | 3. **Core Logic**: Business logic goes in [service.js](mdc:service.js) 51 | 4. **New Providers**: Add embedding providers in [providers.js](mdc:providers.js) 52 | 53 | ## File Patterns 54 | 55 | - `*.js` - Main application code 56 | - `*.md` - Documentation and rules 57 | - `.pampa/` - Generated database and chunks (gitignored) 58 | - `examples/` - Test projects for validation 59 | - `test/` - Test suite 60 | -------------------------------------------------------------------------------- /test/reranker-default.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, before, after } from 'node:test'; 2 | import assert from 'node:assert/strict'; 3 | import { fileURLToPath } from 'url'; 4 | import { dirname, join } from 'path'; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = dirname(__filename); 8 | 9 | describe('PAMPAX_RERANKER_DEFAULT environment variable', () => { 10 | let originalEnv; 11 | 12 | before(() => { 13 | originalEnv = process.env.PAMPAX_RERANKER_DEFAULT; 14 | }); 15 | 16 | after(() => { 17 | if (originalEnv !== undefined) { 18 | process.env.PAMPAX_RERANKER_DEFAULT = originalEnv; 19 | } else { 20 | delete process.env.PAMPAX_RERANKER_DEFAULT; 21 | } 22 | }); 23 | 24 | it('should default to "off" when PAMPAX_RERANKER_DEFAULT is not set', async () => { 25 | delete process.env.PAMPAX_RERANKER_DEFAULT; 26 | 27 | // Use query parameter to force fresh import 28 | const { DEFAULT_RERANKER } = await import(`../src/types/search.js?v=${Date.now()}`); 29 | assert.equal(DEFAULT_RERANKER, 'off'); 30 | }); 31 | 32 | it('should use "api" when PAMPAX_RERANKER_DEFAULT is set to "api"', async () => { 33 | process.env.PAMPAX_RERANKER_DEFAULT = 'api'; 34 | 35 | const { DEFAULT_RERANKER } = await import(`../src/types/search.js?v=${Date.now()}`); 36 | assert.equal(DEFAULT_RERANKER, 'api'); 37 | }); 38 | 39 | it('should use "transformers" when PAMPAX_RERANKER_DEFAULT is set to "transformers"', async () => { 40 | process.env.PAMPAX_RERANKER_DEFAULT = 'transformers'; 41 | 42 | const { DEFAULT_RERANKER } = await import(`../src/types/search.js?v=${Date.now()}`); 43 | assert.equal(DEFAULT_RERANKER, 'transformers'); 44 | }); 45 | 46 | it('should fallback to "off" for invalid PAMPAX_RERANKER_DEFAULT values', async () => { 47 | process.env.PAMPAX_RERANKER_DEFAULT = 'invalid-mode'; 48 | 49 | const { DEFAULT_RERANKER } = await import(`../src/types/search.js?v=${Date.now()}`); 50 | assert.equal(DEFAULT_RERANKER, 'off'); 51 | }); 52 | 53 | it('should be case-insensitive', async () => { 54 | process.env.PAMPAX_RERANKER_DEFAULT = 'API'; 55 | 56 | const { DEFAULT_RERANKER } = await import(`../src/types/search.js?v=${Date.now()}`); 57 | assert.equal(DEFAULT_RERANKER, 'api'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment: 18 | 19 | - Demonstrating empathy and kindness toward other people 20 | - Being respectful of differing opinions, viewpoints, and experiences 21 | - Giving and gracefully accepting constructive feedback 22 | - Accepting responsibility and apologizing to those affected by our mistakes 23 | - Focusing on what is best for the community 24 | 25 | Examples of unacceptable behavior: 26 | 27 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 28 | - Trolling, insulting/derogatory comments, and personal or political attacks 29 | - Public or private harassment 30 | - Publishing others' private information without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a professional setting 32 | 33 | ## Enforcement Responsibilities 34 | 35 | Project maintainers are responsible for clarifying and enforcing our standards of 36 | acceptable behavior and will take appropriate and fair corrective action in 37 | response to any behavior that they deem inappropriate, threatening, offensive, 38 | or harmful. 39 | 40 | ## Scope 41 | 42 | This Code of Conduct applies within all community spaces, and also applies when 43 | an individual is representing the community in public spaces. 44 | 45 | ## Enforcement 46 | 47 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 48 | reported to the project team. All complaints will be reviewed and investigated 49 | promptly and fairly. 50 | 51 | All project team members are obligated to respect the privacy and security of the 52 | reporter of any incident. 53 | 54 | ## Attribution 55 | 56 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 57 | version 2.0, available at 58 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 59 | -------------------------------------------------------------------------------- /src/indexer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PAMPA Indexer - Presentation Layer 3 | * 4 | * This module provides the user-facing interface for PAMPA core services. 5 | * It handles logging, console output, and user feedback while delegating 6 | * business logic to the service layer. 7 | */ 8 | 9 | import * as service from './service.js'; 10 | 11 | // Re-export service functions with presentation layer 12 | export async function indexProject({ repoPath = '.', provider = 'auto', encrypt = undefined }) { 13 | console.log('Starting project indexing...'); 14 | console.log(`Provider: ${provider}`); 15 | if (typeof encrypt === 'string') { 16 | console.log(`Encryption: ${encrypt}`); 17 | } 18 | 19 | const result = await service.indexProject({ 20 | repoPath, 21 | provider, 22 | encryptMode: encrypt, 23 | onProgress: ({ type, file, symbol }) => { 24 | // Silent progress - could add verbose mode here 25 | } 26 | }); 27 | 28 | if (result.success) { 29 | console.log('Indexing completed successfully'); 30 | 31 | // Log errors if any 32 | if (result.errors.length > 0) { 33 | console.log(`${result.errors.length} errors occurred during indexing:`); 34 | result.errors.forEach(error => { 35 | console.error(`ERROR ${error.type}: ${error.error}`); 36 | }); 37 | } 38 | } else { 39 | console.error('Indexing failed'); 40 | throw new Error(result.message || 'Unknown indexing error'); 41 | } 42 | 43 | return result; 44 | } 45 | 46 | export async function searchCode(query, limit = 10, provider = 'auto') { 47 | const result = await service.searchCode(query, limit, provider); 48 | 49 | if (!result.success) { 50 | if (result.error === 'no_chunks_found') { 51 | console.log(`WARNING: ${result.message}`); 52 | console.log(`TIP: ${result.suggestion}`); 53 | } else if (result.error === 'no_relevant_matches') { 54 | console.log(`NO MATCHES: ${result.message}`); 55 | console.log(`TIP: ${result.suggestion}`); 56 | } else { 57 | console.error('Search error:', result.message); 58 | } 59 | return []; 60 | } 61 | 62 | // Convert to legacy format for backward compatibility 63 | return result.results; 64 | } 65 | 66 | export async function getChunk(sha) { 67 | const result = await service.getChunk(sha); 68 | 69 | if (!result.success) { 70 | throw new Error(result.message); 71 | } 72 | 73 | return result.content; 74 | } -------------------------------------------------------------------------------- /examples/chat-app-typescript/server.js: -------------------------------------------------------------------------------- 1 | import fastifyStatic from '@fastify/static'; 2 | import fastifyWebsocket from '@fastify/websocket'; 3 | import fastify from 'fastify'; 4 | import { dirname, join } from 'path'; 5 | import { fileURLToPath } from 'url'; 6 | import { ChatManager } from './modules/chatManager.js'; 7 | import { MessageHandler } from './modules/messageHandler.js'; 8 | import { RoomManager } from './modules/roomManager.js'; 9 | import { UserManager } from './modules/userManager.js'; 10 | 11 | const __filename = fileURLToPath(import.meta.url); 12 | const __dirname = dirname(__filename); 13 | 14 | // Configuración del servidor 15 | const server = fastify({ 16 | logger: true, 17 | connectionTimeout: 60000, 18 | keepAliveTimeout: 30000 19 | }); 20 | 21 | // Registro de plugins 22 | await server.register(fastifyWebsocket); 23 | await server.register(fastifyStatic, { 24 | root: join(__dirname, 'public'), 25 | prefix: '/' 26 | }); 27 | 28 | // Inicialización de managers 29 | const userManager = new UserManager(); 30 | const roomManager = new RoomManager(); 31 | const messageHandler = new MessageHandler(userManager, roomManager); 32 | const chatManager = new ChatManager(userManager, roomManager, messageHandler); 33 | 34 | // Ruta principal 35 | server.get('/', async (request, reply) => { 36 | return reply.sendFile('index.html'); 37 | }); 38 | 39 | // WebSocket para el chat 40 | server.register(async function (fastify) { 41 | fastify.get('/chat', { websocket: true }, (connection, req) => { 42 | chatManager.handleConnection(connection, req); 43 | }); 44 | }); 45 | 46 | // API REST para obtener información 47 | server.get('/api/rooms', async (request, reply) => { 48 | return roomManager.getAllRooms(); 49 | }); 50 | 51 | server.get('/api/users', async (request, reply) => { 52 | return userManager.getAllUsers(); 53 | }); 54 | 55 | server.get('/api/messages/:roomId', async (request, reply) => { 56 | const { roomId } = request.params; 57 | return messageHandler.getMessageHistory(roomId); 58 | }); 59 | 60 | // Manejo de errores 61 | server.setErrorHandler((error, request, reply) => { 62 | server.log.error(error); 63 | reply.status(500).send({ error: 'Error interno del servidor' }); 64 | }); 65 | 66 | // Inicio del servidor 67 | const start = async () => { 68 | try { 69 | const port = process.env.PORT || 3000; 70 | const host = process.env.HOST || 'localhost'; 71 | 72 | await server.listen({ port, host }); 73 | console.log(`🚀 Servidor de chat iniciado en http://${host}:${port}`); 74 | console.log(`📱 WebSocket disponible en ws://${host}:${port}/chat`); 75 | } catch (err) { 76 | server.log.error(err); 77 | process.exit(1); 78 | } 79 | }; 80 | 81 | start(); -------------------------------------------------------------------------------- /examples/chat-app-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-parent 11 | 3.2.1 12 | 13 | 14 | 15 | com.pampa 16 | chat-app-java 17 | 1.0.0 18 | jar 19 | 20 | PAMPA Chat Java 21 | PAMPA Chat Example - Java with Spring Boot and WebSockets 22 | 23 | 24 | 17 25 | 17 26 | 17 27 | UTF-8 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-websocket 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-thymeleaf 45 | 46 | 47 | 48 | 49 | com.fasterxml.jackson.core 50 | jackson-databind 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-devtools 57 | runtime 58 | true 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-maven-plugin 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/best-practices.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: Global best practices for any development project 4 | globs: **/* 5 | --- 6 | # Global Best Practices 7 | 8 | This document defines best practices applicable to any development project, regardless of the technology stack used. 9 | 10 | ## General Principles 11 | 12 | - **Code Quality**: Maintain high quality standards throughout all code. 13 | - **Documentation**: Update documentation when functionality changes. 14 | - **Testing**: Implement tests for all new or modified code. 15 | - **Single Responsibility**: Each component should have a single responsibility. 16 | - **DRY (Don't Repeat Yourself)**: Avoid duplication of code and logic. 17 | 18 | ## Code Development and Modification 19 | 20 | When creating or modifying any component of the system, follow these guidelines: 21 | 22 | ### Development Process 23 | 24 | 1. **Context Understanding**: Fully understand the problem before writing code. 25 | 2. **Prior Design**: Design the solution before implementing it. 26 | 3. **Incremental Development**: Implement in small, verifiable increments. 27 | 4. **Code Review**: Review code before considering it complete. 28 | 5. **Refactoring**: Improve existing code without changing its behavior. 29 | 30 | ### Testing 31 | 32 | 1. **Test First**: Consider writing tests before implementing code (TDD when possible). 33 | 2. **Coverage**: Ensure new features have adequate tests. 34 | 3. **Automation**: Run tests automatically before committing changes. 35 | 4. **Verification**: Ensure all tests pass before finalizing. 36 | 37 | ### Quality and Maintenance 38 | 39 | 1. **Error Handling**: Implement appropriate error and exception handling. 40 | 2. **Logging**: Add appropriate logs to facilitate debugging and monitoring. 41 | 3. **Security**: Consider security implications in every change. 42 | 4. **Performance**: Evaluate the performance impact of changes made. 43 | 44 | ## Source Code Management 45 | 46 | 1. **Version Control**: Properly use the version control system. 47 | 2. **Small Commits**: Make small, focused commits with specific purposes. 48 | 3. **Descriptive Messages**: Write clear and descriptive commit messages. 49 | 4. **Working Branches**: Use branches for features, fixes, or tasks. 50 | 5. **Pull Requests**: Request code review through pull requests. 51 | 52 | ## Problem Solving 53 | 54 | 1. **Root Cause Analysis**: Identify the fundamental cause of problems. 55 | 2. **Durable Solutions**: Implement solutions that address the root problem. 56 | 3. **Solution Documentation**: Document complex problems and their solutions. 57 | 4. **Continuous Learning**: Learn from mistakes and continuously improve. 58 | 59 | ## Teamwork 60 | 61 | 1. **Clear Communication**: Communicate changes and design decisions to the team. 62 | 2. **Collaboration**: Collaborate with other team members on complex problems. 63 | 3. **Knowledge Sharing**: Document and share knowledge with the team. 64 | 4. **Code Reviews**: Participate in code reviews constructively. -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/best-practices.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | projectPath: ./ 3 | cursorPath: . 4 | description: Global best practices for any development project 5 | globs: **/* 6 | alwaysApply: true 7 | --- 8 | # Global Best Practices 9 | 10 | This document defines best practices applicable to any development project, regardless of the technology stack used. 11 | 12 | ## General Principles 13 | 14 | - **Code Quality**: Maintain high quality standards throughout all code. 15 | - **Documentation**: Update documentation when functionality changes. 16 | - **Testing**: Implement tests for all new or modified code. 17 | - **Single Responsibility**: Each component should have a single responsibility. 18 | - **DRY (Don't Repeat Yourself)**: Avoid duplication of code and logic. 19 | 20 | ## Code Development and Modification 21 | 22 | When creating or modifying any component of the system, follow these guidelines: 23 | 24 | ### Development Process 25 | 26 | 1. **Context Understanding**: Fully understand the problem before writing code. 27 | 2. **Prior Design**: Design the solution before implementing it. 28 | 3. **Incremental Development**: Implement in small, verifiable increments. 29 | 4. **Code Review**: Review code before considering it complete. 30 | 5. **Refactoring**: Improve existing code without changing its behavior. 31 | 32 | ### Testing 33 | 34 | 1. **Test First**: Consider writing tests before implementing code (TDD when possible). 35 | 2. **Coverage**: Ensure new features have adequate tests. 36 | 3. **Automation**: Run tests automatically before committing changes. 37 | 4. **Verification**: Ensure all tests pass before finalizing. 38 | 39 | ### Quality and Maintenance 40 | 41 | 1. **Error Handling**: Implement appropriate error and exception handling. 42 | 2. **Logging**: Add appropriate logs to facilitate debugging and monitoring. 43 | 3. **Security**: Consider security implications in every change. 44 | 4. **Performance**: Evaluate the performance impact of changes made. 45 | 46 | ## Source Code Management 47 | 48 | 1. **Version Control**: Properly use the version control system. 49 | 2. **Small Commits**: Make small, focused commits with specific purposes. 50 | 3. **Descriptive Messages**: Write clear and descriptive commit messages. 51 | 4. **Working Branches**: Use branches for features, fixes, or tasks. 52 | 5. **Pull Requests**: Request code review through pull requests. 53 | 54 | ## Problem Solving 55 | 56 | 1. **Root Cause Analysis**: Identify the fundamental cause of problems. 57 | 2. **Durable Solutions**: Implement solutions that address the root problem. 58 | 3. **Solution Documentation**: Document complex problems and their solutions. 59 | 4. **Continuous Learning**: Learn from mistakes and continuously improve. 60 | 61 | ## Teamwork 62 | 63 | 1. **Clear Communication**: Communicate changes and design decisions to the team. 64 | 2. **Collaboration**: Collaborate with other team members on complex problems. 65 | 3. **Knowledge Sharing**: Document and share knowledge with the team. 66 | 4. **Code Reviews**: Participate in code reviews constructively. -------------------------------------------------------------------------------- /DEMO_MULTI_PROJECT_EN.md: -------------------------------------------------------------------------------- 1 | # 🚀 PAMPA Multi-Project Demo 2 | 3 | PAMPA now supports working with multiple projects using explicit aliases for greater clarity. 4 | 5 | ## 📋 New Options Added 6 | 7 | All main commands now support: 8 | 9 | - `--project ` - Clear alias to specify the project directory 10 | - `--directory ` - Alternative alias for the project directory 11 | 12 | ## 🎯 Updated Commands 13 | 14 | ### 1. **Index Project** 15 | 16 | ```bash 17 | # Traditional way 18 | pampa index /path/to/project 19 | 20 | # New clearer options 21 | pampa index --project /path/to/project 22 | pampa index --directory /path/to/project 23 | ``` 24 | 25 | ### 2. **Search Code** 26 | 27 | ```bash 28 | # Traditional way 29 | pampa search "create policy" /path/to/project 30 | 31 | # New clearer options 32 | pampa search "create policy" --project /path/to/project 33 | pampa search "create policy" --directory /path/to/project 34 | ``` 35 | 36 | ### 3. **Update Index** 37 | 38 | ```bash 39 | # Traditional way 40 | pampa update /path/to/project 41 | 42 | # New clearer options 43 | pampa update --project /path/to/project 44 | pampa update --directory /path/to/project 45 | ``` 46 | 47 | ### 4. **Watch Changes** 48 | 49 | ```bash 50 | # Traditional way 51 | pampa watch /path/to/project 52 | 53 | # New clearer options 54 | pampa watch --project /path/to/project 55 | pampa watch --directory /path/to/project 56 | ``` 57 | 58 | ## 🏗️ Practical Examples 59 | 60 | ### Working with Laravel Project 61 | 62 | ```bash 63 | # Index the Laravel project 64 | pampa index --project /path/to/laravel-project --provider transformers 65 | 66 | # Search for payment-related functions 67 | pampa search "payment processing" --project /path/to/laravel-project --lang php 68 | 69 | # Search in specific services 70 | pampa search "create policy" --project /path/to/laravel-project --path_glob "app/Services/**" 71 | 72 | # Update after changes 73 | pampa update --project /path/to/laravel-project 74 | ``` 75 | 76 | ### Working with React Project 77 | 78 | ```bash 79 | # Index React project 80 | pampa index --directory /path/to/react-app --provider openai 81 | 82 | # Search components 83 | pampa search "user authentication" --directory /path/to/react-app --lang tsx 84 | 85 | # Watch changes during development 86 | pampa watch --directory /path/to/react-app --debounce 1000 87 | ``` 88 | 89 | ## 🔄 Compatibility 90 | 91 | ✅ **Fully Compatible**: Traditional ways continue to work 92 | ✅ **Priority**: `--project` > `--directory` > positional argument > current directory 93 | ✅ **MCP Server**: Already supported the `path` parameter in all tools 94 | 95 | ## 🎉 Result 96 | 97 | Now it's much clearer and more explicit to work with projects in different locations: 98 | 99 | ```bash 100 | # ❌ Before: Not so clear 101 | pampa search "function" /some/long/path/to/project 102 | 103 | # ✅ Now: Much clearer 104 | pampa search "function" --project /some/long/path/to/project 105 | ``` 106 | 107 | This improvement makes PAMPA more intuitive for developers working with multiple projects simultaneously. 108 | -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/git-commit-guidelines.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Conventions for Git commit messages 3 | globs: **/* 4 | alwaysApply: true 5 | --- 6 | # Git Commit Guidelines 7 | 8 | Adopt **Conventional Commits** with emojis for quick context: 9 | 10 | | Type | Emoji | Example | 11 | | -------- | ----- | --------------------------------------- | 12 | | feat | ✨ | `feat(api): ✨ add user authentication` | 13 | | fix | 🐛 | `fix(payment): 🐛 handle timeout error` | 14 | | docs | 📝 | `docs(readme): 📝 clarify setup` | 15 | | refactor | ♻️ | `refactor(core): ♻️ extract helper` | 16 | | test | ✅ | `test(utils): ✅ edge cases for parser` | 17 | | chore | 🔧 | `chore(ci): 🔧 bump node version` | 18 | 19 | **Versioning** 20 | 21 | - **MAJOR**: breaking changes (v2.0.0) 22 | - **MINOR**: new features, backward‑compatible (v1.1.0) 23 | - **PATCH**: bug fixes, backward‑compatible (v1.0.1) 24 | 25 | ## Author Identity Rules 26 | 27 | To clearly differentiate the origin of each commit, **ALWAYS** use `--author` according to context: 28 | 29 | ### Human Developer Commits 30 | Commits made directly by human developers: 31 | ```bash 32 | # Use developer's normal identity (without --author) 33 | git commit -m "feat(auth): ✨ implement OAuth login" 34 | ``` 35 | 36 | ### AI Assistant Commits 37 | Commits made by AI assistants (Cursor, Claude, ChatGPT, etc.): 38 | ```bash 39 | # MANDATORY use --author with AI identification 40 | git commit --author="Cursor AI " -m "feat(api): ✨ add user validation" 41 | git commit --author="Cursor AI " -m "fix(auth): 🐛 handle token expiry" 42 | ``` 43 | 44 | ### Automated System Commits 45 | Automatic commits (semantic-release, bots, CI/CD): 46 | ```bash 47 | # These systems already configure their own author automatically 48 | # No manual action required 49 | ``` 50 | 51 | ### Important Notes 52 | - **Avatar Display**: Avatar is determined by email. Emails not associated with GitHub accounts will show default avatar 53 | - **Consistency**: Maintain consistency in AI email format: `.ai@assistant.local` 54 | - **Transparency**: This practice improves transparency and traceability of collaborative development 55 | 56 | ## Release Guidelines 57 | 58 | ### When NOT to Create Releases 59 | - **Default Behavior**: Do NOT create releases unless explicitly requested 60 | - **Check Existing Automation**: Before attempting any release, verify if automation already exists: 61 | - GitHub Actions workflows (`.github/workflows/`) 62 | - GitLab CI/CD pipelines (`.gitlab-ci.yml`) 63 | - `semantic-release` in `package.json` dependencies 64 | - Other automated release tools 65 | 66 | ### When to Create Releases 67 | Only create releases when: 68 | 1. **Explicitly requested** by the user 69 | 2. **No existing automation** is found 70 | 3. **Manual release process** is confirmed to be needed 71 | 72 | ### Release Verification Commands 73 | ```bash 74 | # Check for existing automation 75 | ls .github/workflows/ 76 | cat package.json | grep semantic-release 77 | cat .gitlab-ci.yml 2>/dev/null || echo "No GitLab CI found" 78 | ``` 79 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/git-commit-guidelines.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: Conventions for Git commit messages 4 | globs: **/* 5 | --- 6 | # Git Commit Guidelines 7 | 8 | Adopt **Conventional Commits** with emojis for quick context: 9 | 10 | | Type | Emoji | Example | 11 | | -------- | ----- | --------------------------------------- | 12 | | feat | ✨ | `feat(api): ✨ add user authentication` | 13 | | fix | 🐛 | `fix(payment): 🐛 handle timeout error` | 14 | | docs | 📝 | `docs(readme): 📝 clarify setup` | 15 | | refactor | ♻️ | `refactor(core): ♻️ extract helper` | 16 | | test | ✅ | `test(utils): ✅ edge cases for parser` | 17 | | chore | 🔧 | `chore(ci): 🔧 bump node version` | 18 | 19 | **Versioning** 20 | 21 | - **MAJOR**: breaking changes (v2.0.0) 22 | - **MINOR**: new features, backward‑compatible (v1.1.0) 23 | - **PATCH**: bug fixes, backward‑compatible (v1.0.1) 24 | 25 | ## Author Identity Rules 26 | 27 | To clearly differentiate the origin of each commit, **ALWAYS** use `--author` according to context: 28 | 29 | ### Human Developer Commits 30 | Commits made directly by human developers: 31 | ```bash 32 | # Use developer's normal identity (without --author) 33 | git commit -m "feat(auth): ✨ implement OAuth login" 34 | ``` 35 | 36 | ### AI Assistant Commits 37 | Commits made by AI assistants (Cursor, Claude, ChatGPT, etc.): 38 | ```bash 39 | # MANDATORY use --author with AI identification 40 | git commit --author="Cursor AI " -m "feat(api): ✨ add user validation" 41 | git commit --author="Cursor AI " -m "fix(auth): 🐛 handle token expiry" 42 | ``` 43 | 44 | ### Automated System Commits 45 | Automatic commits (semantic-release, bots, CI/CD): 46 | ```bash 47 | # These systems already configure their own author automatically 48 | # No manual action required 49 | ``` 50 | 51 | ### Important Notes 52 | - **Avatar Display**: Avatar is determined by email. Emails not associated with GitHub accounts will show default avatar 53 | - **Consistency**: Maintain consistency in AI email format: `.ai@assistant.local` 54 | - **Transparency**: This practice improves transparency and traceability of collaborative development 55 | 56 | ## Release Guidelines 57 | 58 | ### When NOT to Create Releases 59 | - **Default Behavior**: Do NOT create releases unless explicitly requested 60 | - **Check Existing Automation**: Before attempting any release, verify if automation already exists: 61 | - GitHub Actions workflows (`.github/workflows/`) 62 | - GitLab CI/CD pipelines (`.gitlab-ci.yml`) 63 | - `semantic-release` in `package.json` dependencies 64 | - Other automated release tools 65 | 66 | ### When to Create Releases 67 | Only create releases when: 68 | 1. **Explicitly requested** by the user 69 | 2. **No existing automation** is found 70 | 3. **Manual release process** is confirmed to be needed 71 | 72 | ### Release Verification Commands 73 | ```bash 74 | # Check for existing automation 75 | ls .github/workflows/ 76 | cat package.json | grep semantic-release 77 | cat .gitlab-ci.yml 2>/dev/null || echo "No GitLab CI found" 78 | ``` 79 | -------------------------------------------------------------------------------- /src/indexer/merkle.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import xxhashFactory from 'xxhash-wasm'; 4 | 5 | const MERKLE_DIR = '.pampa'; 6 | const MERKLE_FILENAME = 'merkle.json'; 7 | 8 | let hasherPromise = null; 9 | 10 | async function getHasher() { 11 | if (!hasherPromise) { 12 | hasherPromise = xxhashFactory().then(factory => factory.h64); 13 | } 14 | return hasherPromise; 15 | } 16 | 17 | function ensureObject(value) { 18 | if (!value || typeof value !== 'object') { 19 | return {}; 20 | } 21 | return value; 22 | } 23 | 24 | export async function computeFastHash(input) { 25 | const hasher = await getHasher(); 26 | let normalized; 27 | if (typeof input === 'string') { 28 | normalized = input; 29 | } else if (Buffer.isBuffer(input)) { 30 | normalized = input.toString('utf8'); 31 | } else { 32 | normalized = String(input ?? ''); 33 | } 34 | 35 | const result = hasher(normalized); 36 | return typeof result === 'bigint' ? result.toString() : String(result); 37 | } 38 | 39 | export function loadMerkle(basePath = '.') { 40 | const absolute = path.resolve(basePath); 41 | const merklePath = path.join(absolute, MERKLE_DIR, MERKLE_FILENAME); 42 | 43 | if (!fs.existsSync(merklePath)) { 44 | return {}; 45 | } 46 | 47 | try { 48 | const data = fs.readFileSync(merklePath, 'utf8'); 49 | return ensureObject(JSON.parse(data)); 50 | } catch (error) { 51 | return {}; 52 | } 53 | } 54 | 55 | export function saveMerkle(basePath = '.', merkle = {}) { 56 | const absolute = path.resolve(basePath); 57 | const dirPath = path.join(absolute, MERKLE_DIR); 58 | const merklePath = path.join(dirPath, MERKLE_FILENAME); 59 | 60 | fs.mkdirSync(dirPath, { recursive: true }); 61 | fs.writeFileSync(merklePath, JSON.stringify(merkle, null, 2)); 62 | } 63 | 64 | export function toPosixPath(relativePath) { 65 | if (typeof relativePath !== 'string') { 66 | return null; 67 | } 68 | return relativePath.split(path.sep).join('/'); 69 | } 70 | 71 | export function normalizeToProjectPath(basePath = '.', filePath) { 72 | if (typeof filePath !== 'string' || filePath.length === 0) { 73 | return null; 74 | } 75 | 76 | const absoluteBase = path.resolve(basePath); 77 | const absoluteFile = path.isAbsolute(filePath) ? filePath : path.join(absoluteBase, filePath); 78 | const relative = path.relative(absoluteBase, absoluteFile); 79 | 80 | if (!relative || relative.startsWith('..')) { 81 | return null; 82 | } 83 | 84 | return toPosixPath(relative); 85 | } 86 | 87 | export function removeMerkleEntry(merkle, relativePath) { 88 | if (!merkle || typeof merkle !== 'object') { 89 | return false; 90 | } 91 | 92 | if (Object.prototype.hasOwnProperty.call(merkle, relativePath)) { 93 | delete merkle[relativePath]; 94 | return true; 95 | } 96 | 97 | return false; 98 | } 99 | 100 | export function cloneMerkle(merkle) { 101 | return JSON.parse(JSON.stringify(ensureObject(merkle))); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /DEMO_MULTI_PROJECT.md: -------------------------------------------------------------------------------- 1 | # 🚀 PAMPA Multi-Project Demo 2 | 3 | PAMPA ahora soporta trabajar con múltiples proyectos usando aliases explícitos para mayor claridad. 4 | 5 | ## 📋 Nuevas Opciones Agregadas 6 | 7 | Todos los comandos principales ahora soportan: 8 | 9 | - `--project ` - Alias claro para especificar el directorio del proyecto 10 | - `--directory ` - Alias alternativo para el directorio del proyecto 11 | 12 | ## 🎯 Comandos Actualizados 13 | 14 | ### 1. **Indexar Proyecto** 15 | 16 | ```bash 17 | # Forma tradicional 18 | pampa index /path/to/project 19 | 20 | # Nuevas opciones más claras 21 | pampa index --project /path/to/project 22 | pampa index --directory /path/to/project 23 | ``` 24 | 25 | ### 2. **Buscar Código** 26 | 27 | ```bash 28 | # Forma tradicional 29 | pampa search "create policy" /path/to/project 30 | 31 | # Nuevas opciones más claras 32 | pampa search "create policy" --project /path/to/project 33 | pampa search "create policy" --directory /path/to/project 34 | ``` 35 | 36 | ### 3. **Actualizar Índice** 37 | 38 | ```bash 39 | # Forma tradicional 40 | pampa update /path/to/project 41 | 42 | # Nuevas opciones más claras 43 | pampa update --project /path/to/project 44 | pampa update --directory /path/to/project 45 | ``` 46 | 47 | ### 4. **Observar Cambios** 48 | 49 | ```bash 50 | # Forma tradicional 51 | pampa watch /path/to/project 52 | 53 | # Nuevas opciones más claras 54 | pampa watch --project /path/to/project 55 | pampa watch --directory /path/to/project 56 | ``` 57 | 58 | ## 🏗️ Ejemplos Prácticos 59 | 60 | ### Trabajar con Proyecto Laravel 61 | 62 | ```bash 63 | # Indexar el proyecto Laravel 64 | pampa index --project /path/to/laravel-project --provider transformers 65 | 66 | # Buscar funciones relacionadas con pagos 67 | pampa search "payment processing" --project /path/to/laravel-project --lang php 68 | 69 | # Buscar en servicios específicos 70 | pampa search "create policy" --project /path/to/laravel-project --path_glob "app/Services/**" 71 | 72 | # Actualizar después de cambios 73 | pampa update --project /path/to/laravel-project 74 | ``` 75 | 76 | ### Trabajar con Proyecto React 77 | 78 | ```bash 79 | # Indexar proyecto React 80 | pampa index --directory /path/to/react-app --provider openai 81 | 82 | # Buscar componentes 83 | pampa search "user authentication" --directory /path/to/react-app --lang tsx 84 | 85 | # Observar cambios en desarrollo 86 | pampa watch --directory /path/to/react-app --debounce 1000 87 | ``` 88 | 89 | ## 🔄 Compatibilidad 90 | 91 | ✅ **Totalmente Compatible**: Las formas tradicionales siguen funcionando 92 | ✅ **Prioridad**: `--project` > `--directory` > argumento posicional > directorio actual 93 | ✅ **MCP Server**: Ya soportaba el parámetro `path` en todos los tools 94 | 95 | ## 🎉 Resultado 96 | 97 | Ahora es mucho más claro y explícito trabajar con proyectos en diferentes ubicaciones: 98 | 99 | ```bash 100 | # ❌ Antes: No tan claro 101 | pampa search "function" /some/long/path/to/project 102 | 103 | # ✅ Ahora: Mucho más claro 104 | pampa search "function" --project /some/long/path/to/project 105 | ``` 106 | 107 | Esta mejora hace que PAMPA sea más intuitivo para desarrolladores que trabajan con múltiples proyectos simultáneamente. 108 | -------------------------------------------------------------------------------- /examples/chat-app-go/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PAMPA Chat - Go 7 | 8 | 11 | 12 | 13 |
14 | 15 | 31 | 32 | 33 | 99 | 100 | 101 |
102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/chat-app-python/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PAMPA Chat - Python 7 | 8 | 11 | 12 | 13 |
14 | 15 | 31 | 32 | 33 | 99 | 100 | 101 |
102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PAMPA Chat - Java 7 | 8 | 11 | 12 | 13 |
14 | 15 | 31 | 32 | 33 | 99 | 100 | 101 |
102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/codemap_extension.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict'; 2 | import fs from 'fs'; 3 | import os from 'os'; 4 | import path from 'path'; 5 | import test from 'node:test'; 6 | import { readCodemap, writeCodemap } from '../src/codemap/io.js'; 7 | import { bumpSuccess, touchUsed } from '../src/codemap/telemetry.js'; 8 | import { DEFAULT_PATH_WEIGHT, DEFAULT_SUCCESS_RATE, normalizeChunkMetadata } from '../src/codemap/types.js'; 9 | 10 | test('legacy codemap loads with telemetry defaults', () => { 11 | const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pampa-codemap-')); 12 | const codemapPath = path.join(tmpDir, 'pampa.codemap.json'); 13 | 14 | const legacyCodemap = { 15 | 'chunk-1': { 16 | file: 'src/foo.js', 17 | symbol: 'foo', 18 | sha: 'abc123', 19 | lang: 'javascript', 20 | chunkType: 'function', 21 | provider: 'transformers', 22 | dimensions: 768, 23 | hasPampaTags: false, 24 | hasIntent: false, 25 | hasDocumentation: false, 26 | variableCount: 2 27 | } 28 | }; 29 | 30 | fs.writeFileSync(codemapPath, JSON.stringify(legacyCodemap, null, 2)); 31 | 32 | const codemap = readCodemap(codemapPath); 33 | const chunk = codemap['chunk-1']; 34 | 35 | assert.equal(chunk.file, 'src/foo.js'); 36 | assert.equal(chunk.symbol, 'foo'); 37 | assert.equal(chunk.lang, 'javascript'); 38 | assert.equal(chunk.path_weight, DEFAULT_PATH_WEIGHT); 39 | assert.equal(chunk.success_rate, DEFAULT_SUCCESS_RATE); 40 | assert.deepEqual(chunk.synonyms, []); 41 | assert.equal(chunk.last_used_at, undefined); 42 | assert.equal(chunk.encrypted, false); 43 | 44 | const normalized = writeCodemap(codemapPath, codemap); 45 | const persisted = JSON.parse(fs.readFileSync(codemapPath, 'utf8')); 46 | 47 | assert.deepEqual(normalized, readCodemap(codemapPath)); 48 | assert.deepEqual(persisted['chunk-1'].synonyms, []); 49 | assert.equal(persisted['chunk-1'].path_weight, DEFAULT_PATH_WEIGHT); 50 | assert.equal(persisted['chunk-1'].success_rate, DEFAULT_SUCCESS_RATE); 51 | }); 52 | 53 | test('success telemetry tracks usage and confidence', () => { 54 | const chunk = normalizeChunkMetadata({ 55 | file: 'src/example.ts', 56 | sha: 'def456', 57 | lang: 'typescript' 58 | }); 59 | 60 | assert.equal(chunk.success_rate, DEFAULT_SUCCESS_RATE); 61 | assert.equal(chunk.path_weight, DEFAULT_PATH_WEIGHT); 62 | assert.equal(chunk.encrypted, false); 63 | 64 | bumpSuccess(chunk, true, 0.5); 65 | const afterFirst = chunk.success_rate; 66 | assert.ok(afterFirst > 0 && afterFirst < 1); 67 | 68 | bumpSuccess(chunk, true, 0.5); 69 | assert.ok(chunk.success_rate > afterFirst); 70 | 71 | for (let i = 0; i < 5; i += 1) { 72 | bumpSuccess(chunk, true); 73 | } 74 | assert.ok(chunk.success_rate > 0.6, 'EMA should trend upward with repeated success'); 75 | 76 | const beforeFailures = chunk.success_rate; 77 | for (let i = 0; i < 6; i += 1) { 78 | bumpSuccess(chunk, false); 79 | } 80 | assert.ok(chunk.success_rate < beforeFailures, 'EMA should decrease after failures'); 81 | assert.ok(chunk.success_rate >= 0 && chunk.success_rate <= 1); 82 | 83 | touchUsed(chunk, '2024-01-01T00:00:00Z'); 84 | assert.equal(chunk.last_used_at, '2024-01-01T00:00:00.000Z'); 85 | 86 | touchUsed(chunk, new Date('2024-02-01T12:34:56Z')); 87 | assert.equal(chunk.last_used_at, '2024-02-01T12:34:56.000Z'); 88 | }); 89 | -------------------------------------------------------------------------------- /.windsurf/rules/rules-kit/global/quality-assurance.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | description: Quality control practices for all projects 4 | globs: **/* 5 | --- 6 | # Project Quality Assurance 7 | 8 | This document unifies quality control practices applicable to any project, regardless of the technology stack used. 9 | 10 | ## Quality Principles 11 | 12 | 1. **Continuous Verification**: Constantly verify code quality throughout development. 13 | 2. **Error Prevention**: Preventing errors is better than correcting them later. 14 | 3. **Continuous Improvement**: Continually seek ways to improve processes and code. 15 | 4. **Documentation**: Maintain accurate documentation of processes and decisions. 16 | 17 | ## Automated Testing 18 | 19 | - **Regular Execution**: Run automated test suites after each significant change. 20 | - **Problem Resolution**: Don't consider a change complete until all tests pass. 21 | - **Coverage**: Add new tests for new or modified code. 22 | - **Regression**: Ensure changes don't break existing functionality. 23 | 24 | ```bash 25 | # Run tests and verify all pass 26 | npm run test 27 | 28 | # Framework-specific test commands 29 | php artisan test # Laravel 30 | ng test # Angular 31 | pytest # Python 32 | go test ./... # Go 33 | ``` 34 | 35 | ## Process Logging 36 | 37 | For complex or long-running tasks, maintain a process log: 38 | 39 | 1. **Checklist Creation**: Create a log file in `docs/logs-process/`. 40 | 2. **Naming Convention**: Use the format `YYYY-MM-DD_task-description.md`. 41 | 3. **Content Structure**: 42 | - Objectives and scope of the task 43 | - List of subtasks with checkboxes 44 | - Problems encountered and solutions applied 45 | - Decisions made and their justification 46 | 47 | Example: 48 | 49 | ```markdown 50 | # Authentication Implementation - 2024-08-15 51 | 52 | ## Objectives 53 | 54 | - Implement email/password login 55 | - Add OAuth authentication 56 | - Implement password recovery 57 | 58 | ## Tasks 59 | 60 | - [x] Create user models and migrations 61 | - [x] Implement authentication controller 62 | - [ ] Configure OAuth providers 63 | - [ ] Implement password recovery emails 64 | 65 | ## Problems and Solutions 66 | 67 | - Problem: Conflict with session storage 68 | Solution: Use database-based storage 69 | ``` 70 | 71 | ## File Management 72 | 73 | When creating or modifying files in the project: 74 | 75 | 1. **Existence Verification**: Check if the file already exists before creating it. 76 | 2. **Content Preservation**: 77 | - If it exists and there's no explicit instruction to overwrite, merge changes. 78 | - Preserve important aspects of the existing file (comments, structure). 79 | 3. **Duplication Prevention**: Avoid creating duplicate files or files with similar functionality. 80 | 4. **Appropriate Location**: Ensure files are created in the correct location according to the project structure. 81 | 82 | ## Code Review 83 | 84 | 1. **Self-Review**: Review your own code before considering it complete. 85 | 2. **Verification Checklist**: 86 | - Does the code follow project conventions? 87 | - Is it adequately tested? 88 | - Is documentation updated? 89 | - Have edge cases been considered? 90 | - Is performance acceptable? 91 | 92 | ## Task Completion 93 | 94 | Consider a task complete only when: 95 | 96 | 1. All automated tests pass. 97 | 2. It is properly documented. 98 | 3. The process log is updated (if applicable). 99 | 4. A code review has been performed. 100 | 5. Changes are ready to be deployed to production. -------------------------------------------------------------------------------- /.cursor/rules/rules-kit/global/quality-assurance.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | projectPath: ./ 3 | cursorPath: . 4 | description: Quality control practices for all projects 5 | globs: **/* 6 | alwaysApply: true 7 | --- 8 | # Project Quality Assurance 9 | 10 | This document unifies quality control practices applicable to any project, regardless of the technology stack used. 11 | 12 | ## Quality Principles 13 | 14 | 1. **Continuous Verification**: Constantly verify code quality throughout development. 15 | 2. **Error Prevention**: Preventing errors is better than correcting them later. 16 | 3. **Continuous Improvement**: Continually seek ways to improve processes and code. 17 | 4. **Documentation**: Maintain accurate documentation of processes and decisions. 18 | 19 | ## Automated Testing 20 | 21 | - **Regular Execution**: Run automated test suites after each significant change. 22 | - **Problem Resolution**: Don't consider a change complete until all tests pass. 23 | - **Coverage**: Add new tests for new or modified code. 24 | - **Regression**: Ensure changes don't break existing functionality. 25 | 26 | ```bash 27 | # Run tests and verify all pass 28 | npm run test 29 | 30 | # Framework-specific test commands 31 | php artisan test # Laravel 32 | ng test # Angular 33 | pytest # Python 34 | go test ./... # Go 35 | ``` 36 | 37 | ## Process Logging 38 | 39 | For complex or long-running tasks, maintain a process log: 40 | 41 | 1. **Checklist Creation**: Create a log file in `docs/logs-process/`. 42 | 2. **Naming Convention**: Use the format `YYYY-MM-DD_task-description.md`. 43 | 3. **Content Structure**: 44 | - Objectives and scope of the task 45 | - List of subtasks with checkboxes 46 | - Problems encountered and solutions applied 47 | - Decisions made and their justification 48 | 49 | Example: 50 | 51 | ```markdown 52 | # Authentication Implementation - 2024-08-15 53 | 54 | ## Objectives 55 | 56 | - Implement email/password login 57 | - Add OAuth authentication 58 | - Implement password recovery 59 | 60 | ## Tasks 61 | 62 | - [x] Create user models and migrations 63 | - [x] Implement authentication controller 64 | - [ ] Configure OAuth providers 65 | - [ ] Implement password recovery emails 66 | 67 | ## Problems and Solutions 68 | 69 | - Problem: Conflict with session storage 70 | Solution: Use database-based storage 71 | ``` 72 | 73 | ## File Management 74 | 75 | When creating or modifying files in the project: 76 | 77 | 1. **Existence Verification**: Check if the file already exists before creating it. 78 | 2. **Content Preservation**: 79 | - If it exists and there's no explicit instruction to overwrite, merge changes. 80 | - Preserve important aspects of the existing file (comments, structure). 81 | 3. **Duplication Prevention**: Avoid creating duplicate files or files with similar functionality. 82 | 4. **Appropriate Location**: Ensure files are created in the correct location according to the project structure. 83 | 84 | ## Code Review 85 | 86 | 1. **Self-Review**: Review your own code before considering it complete. 87 | 2. **Verification Checklist**: 88 | - Does the code follow project conventions? 89 | - Is it adequately tested? 90 | - Is documentation updated? 91 | - Have edge cases been considered? 92 | - Is performance acceptable? 93 | 94 | ## Task Completion 95 | 96 | Consider a task complete only when: 97 | 98 | 1. All automated tests pass. 99 | 2. It is properly documented. 100 | 3. The process log is updated (if applicable). 101 | 4. A code review has been performed. 102 | 5. Changes are ready to be deployed to production. -------------------------------------------------------------------------------- /.cursor/rules/readme-sync.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | globs: README.md,README_es.md 3 | description: Regla para mantener los archivos README en inglés y español sincronizados 4 | --- 5 | 6 | # README Synchronization Rule 7 | 8 | ## Purpose 9 | 10 | This rule ensures that both [README.md](mdc:README.md) and [README_es.md](mdc:README_es.md) files are kept synchronized with the same content, only in their respective languages. 11 | 12 | ## Requirements 13 | 14 | ### Content Parity 15 | 16 | - Both README files must have identical structure and sections 17 | - All features, version numbers, and technical information must be the same 18 | - Only the language should differ between the files 19 | 20 | ### Version Consistency 21 | 22 | - Version numbers in headers must match exactly 23 | - Feature lists and "What's New" sections must have equivalent content 24 | - All technical specifications must be identical 25 | 26 | ### Structure Matching 27 | 28 | - Section headers must correspond (e.g., "🌟 What's New" ↔ "🌟 Novedades") 29 | - Table of contents must have equivalent entries 30 | - Code examples and commands should be identical 31 | - Links and references must point to the same resources 32 | 33 | ### Language Guidelines 34 | 35 | - README.md: English content 36 | - README_es.md: Spanish content 37 | - Technical terms may remain in English when appropriate (e.g., "MCP", "Node.js") 38 | - Commands and code blocks should remain identical 39 | 40 | ## When Making Changes 41 | 42 | ### To English README (README.md): 43 | 44 | 1. Make your changes to the English version first 45 | 2. Immediately update the Spanish version (README_es.md) with equivalent content 46 | 3. Ensure version numbers and technical details match exactly 47 | 48 | ### To Spanish README (README_es.md): 49 | 50 | 1. If changes are made to Spanish version, ensure English version is updated 51 | 2. Maintain consistency in technical information 52 | 3. Keep the same structure and organization 53 | 54 | ### Version Updates: 55 | 56 | - Always update version numbers in both files simultaneously 57 | - Feature announcements must be translated and included in both versions 58 | - Performance metrics and technical specifications must match 59 | 60 | ## Quality Checklist 61 | 62 | Before committing changes to either README: 63 | 64 | - [ ] Version numbers match in both files 65 | - [ ] "What's New" sections have equivalent content 66 | - [ ] Table of contents corresponds between languages 67 | - [ ] All technical specifications are identical 68 | - [ ] Code examples and commands are the same 69 | - [ ] Links and references work in both versions 70 | - [ ] Structure and organization match 71 | - [ ] Both files are complete and well-formatted 72 | 73 | ## Examples of Synchronized Content 74 | 75 | ### Version Headers 76 | 77 | ```markdown 78 | # English 79 | 80 | **Version 1.11.x** · **Semantic Search** · **MCP Compatible** · **Node.js** 81 | 82 | # Spanish 83 | 84 | **Versión 1.11.x** · **Búsqueda Semántica** · **Compatible con MCP** · **Node.js** 85 | ``` 86 | 87 | ### Feature Announcements 88 | 89 | ```markdown 90 | # English 91 | 92 | 🐍 **Python Integration** - Full support for Python code indexing and semantic search 93 | 94 | # Spanish 95 | 96 | 🐍 **Integración de Python** - Soporte completo para indexado de código Python y búsqueda semántica 97 | ``` 98 | 99 | ### Technical Commands 100 | 101 | ```bash 102 | # Both versions should have identical commands 103 | npx pampa index --provider transformers 104 | ``` 105 | 106 | This rule helps maintain professional documentation standards and ensures users get consistent information regardless of their language preference. 107 | -------------------------------------------------------------------------------- /src/symbols/graph.js: -------------------------------------------------------------------------------- 1 | const MAX_NEIGHBORS = 16; 2 | 3 | function toLower(value) { 4 | return typeof value === 'string' ? value.trim().toLowerCase() : ''; 5 | } 6 | 7 | function buildSymbolIndex(codemap) { 8 | const index = new Map(); 9 | for (const [chunkId, entry] of Object.entries(codemap)) { 10 | if (!entry || typeof entry !== 'object') { 11 | continue; 12 | } 13 | const symbol = typeof entry.symbol === 'string' ? entry.symbol.trim() : ''; 14 | if (!symbol) { 15 | continue; 16 | } 17 | const key = symbol.toLowerCase(); 18 | if (!index.has(key)) { 19 | index.set(key, []); 20 | } 21 | index.get(key).push({ 22 | chunkId, 23 | sha: entry.sha, 24 | file: entry.file, 25 | symbol 26 | }); 27 | } 28 | return index; 29 | } 30 | 31 | function selectCandidate(candidates, entry) { 32 | if (!Array.isArray(candidates) || candidates.length === 0) { 33 | return null; 34 | } 35 | 36 | if (!entry || !entry.file) { 37 | return candidates[0]; 38 | } 39 | 40 | const sameFile = candidates.find(candidate => candidate.file === entry.file); 41 | return sameFile || candidates[0]; 42 | } 43 | 44 | export function attachSymbolGraphToCodemap(codemap) { 45 | if (!codemap || typeof codemap !== 'object') { 46 | return codemap; 47 | } 48 | 49 | const symbolIndex = buildSymbolIndex(codemap); 50 | const adjacency = new Map(); 51 | 52 | for (const [chunkId, entry] of Object.entries(codemap)) { 53 | if (!entry || typeof entry !== 'object' || typeof entry.sha !== 'string') { 54 | continue; 55 | } 56 | 57 | const outgoing = new Set(); 58 | const rawCalls = Array.isArray(entry.symbol_calls) ? entry.symbol_calls : []; 59 | 60 | for (const rawName of rawCalls) { 61 | const candidateName = toLower(rawName); 62 | if (!candidateName) { 63 | continue; 64 | } 65 | 66 | const candidates = symbolIndex.get(candidateName); 67 | if (!candidates || candidates.length === 0) { 68 | continue; 69 | } 70 | 71 | const target = selectCandidate(candidates, entry); 72 | if (target && typeof target.sha === 'string' && target.sha.length > 0 && target.sha !== entry.sha) { 73 | outgoing.add(target.sha); 74 | } 75 | } 76 | 77 | adjacency.set(entry.sha, outgoing); 78 | entry.symbol_call_targets = Array.from(outgoing).slice(0, MAX_NEIGHBORS); 79 | } 80 | 81 | const incoming = new Map(); 82 | for (const [fromSha, targets] of adjacency.entries()) { 83 | for (const targetSha of targets) { 84 | if (!incoming.has(targetSha)) { 85 | incoming.set(targetSha, new Set()); 86 | } 87 | incoming.get(targetSha).add(fromSha); 88 | } 89 | } 90 | 91 | for (const entry of Object.values(codemap)) { 92 | if (!entry || typeof entry.sha !== 'string') { 93 | continue; 94 | } 95 | 96 | const outgoing = adjacency.get(entry.sha) || new Set(); 97 | const inbound = incoming.get(entry.sha) || new Set(); 98 | const neighbors = new Set([...outgoing, ...inbound]); 99 | 100 | entry.symbol_call_targets = Array.from(outgoing).slice(0, MAX_NEIGHBORS); 101 | entry.symbol_callers = Array.from(inbound).slice(0, MAX_NEIGHBORS); 102 | entry.symbol_neighbors = Array.from(neighbors).slice(0, MAX_NEIGHBORS * 2); 103 | } 104 | 105 | return codemap; 106 | } 107 | -------------------------------------------------------------------------------- /src/mcp/tools/useContextPack.js: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { loadContextPack } from '../../context/packs.js'; 3 | 4 | export const useContextPackInputSchema = z.object({ 5 | name: z.string().min(1, 'Context pack name is required'), 6 | path: z.string().optional() 7 | }); 8 | 9 | export const useContextPackResultSchema = z.object({ 10 | success: z.boolean(), 11 | message: z.string(), 12 | pack: z.object({ 13 | key: z.string(), 14 | name: z.string(), 15 | description: z.string().nullable(), 16 | scope: z.record(z.any()) 17 | }).optional() 18 | }); 19 | 20 | export function createUseContextPackHandler(options) { 21 | const { getWorkingPath, setSessionPack, clearSessionPack, errorLogger } = options; 22 | 23 | return async ({ name, path: explicitPath }) => { 24 | const basePath = explicitPath && explicitPath.trim().length > 0 25 | ? explicitPath.trim() 26 | : (typeof getWorkingPath === 'function' ? getWorkingPath() : '.'); 27 | 28 | if (name === 'default' || name === 'none' || name === 'clear') { 29 | if (typeof clearSessionPack === 'function') { 30 | clearSessionPack(); 31 | } 32 | if (errorLogger && typeof errorLogger.debugLog === 'function') { 33 | errorLogger.debugLog('Cleared MCP session context pack', { basePath, name }); 34 | } 35 | return { 36 | success: true, 37 | message: 'Cleared active context pack for this session' 38 | }; 39 | } 40 | 41 | try { 42 | const pack = loadContextPack(name, basePath); 43 | const sessionPack = { 44 | ...pack, 45 | basePath 46 | }; 47 | 48 | if (typeof setSessionPack === 'function') { 49 | setSessionPack(sessionPack); 50 | } 51 | 52 | if (errorLogger && typeof errorLogger.debugLog === 'function') { 53 | errorLogger.debugLog('Activated MCP session context pack', { 54 | pack: pack.key, 55 | basePath 56 | }); 57 | } 58 | 59 | return { 60 | success: true, 61 | message: `Context pack "${pack.key}" activated for session`, 62 | pack: { 63 | key: pack.key, 64 | name: pack.name, 65 | description: pack.description || null, 66 | scope: pack.scope 67 | } 68 | }; 69 | } catch (error) { 70 | if (errorLogger && typeof errorLogger.log === 'function') { 71 | errorLogger.log(error, { 72 | operation: 'use_context_pack', 73 | name, 74 | basePath 75 | }); 76 | } 77 | throw error; 78 | } 79 | }; 80 | } 81 | 82 | export function registerUseContextPackTool(server, options) { 83 | const handler = createUseContextPackHandler(options); 84 | 85 | server.tool( 86 | 'use_context_pack', 87 | { 88 | name: z.string().min(1).describe('Context pack name (e.g., "test-pack", "stripe-backend") or "clear" to reset'), 89 | path: z.string().optional().describe('PROJECT ROOT directory path (defaults to ".")') 90 | }, 91 | async (params) => { 92 | const result = await handler(params); 93 | return { 94 | content: [ 95 | { 96 | type: 'text', 97 | text: result.message 98 | } 99 | ] 100 | }; 101 | } 102 | ); 103 | 104 | return handler; 105 | } 106 | -------------------------------------------------------------------------------- /test/test-mcp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from 'child_process'; 4 | 5 | /** 6 | * Simple script to test the MCP server 7 | */ 8 | 9 | async function testMcpServer() { 10 | console.log('🔄 Starting MCP server test...\n'); 11 | 12 | const server = spawn('node', ['src/mcp-server.js'], { 13 | stdio: ['pipe', 'pipe', 'pipe'] 14 | }); 15 | 16 | let output = ''; 17 | let errorOutput = ''; 18 | 19 | server.stdout.on('data', (data) => { 20 | output += data.toString(); 21 | }); 22 | 23 | server.stderr.on('data', (data) => { 24 | errorOutput += data.toString(); 25 | }); 26 | 27 | // Enviar mensaje de inicialización 28 | const initMessage = { 29 | jsonrpc: "2.0", 30 | id: 1, 31 | method: "initialize", 32 | params: { 33 | protocolVersion: "2024-11-05", 34 | capabilities: { 35 | tools: {} 36 | }, 37 | clientInfo: { 38 | name: "test-client", 39 | version: "1.0.0" 40 | } 41 | } 42 | }; 43 | 44 | console.log('📤 Sending initialization message...'); 45 | server.stdin.write(JSON.stringify(initMessage) + '\n'); 46 | 47 | // Esperar respuesta de inicialización 48 | await new Promise(resolve => setTimeout(resolve, 2000)); 49 | 50 | // Enviar comando de test con get_project_stats 51 | const testMessage = { 52 | jsonrpc: "2.0", 53 | id: 2, 54 | method: "tools/call", 55 | params: { 56 | name: "get_project_stats", 57 | arguments: { 58 | path: "." 59 | } 60 | } 61 | }; 62 | 63 | console.log('📤 Sending get_project_stats test...'); 64 | server.stdin.write(JSON.stringify(testMessage) + '\n'); 65 | 66 | // Esperar respuesta 67 | await new Promise(resolve => setTimeout(resolve, 3000)); 68 | 69 | server.kill(); 70 | 71 | console.log('\n📊 Test results:\n'); 72 | console.log('📤 STDOUT:'); 73 | console.log(output); 74 | 75 | console.log('\n🔧 STDERR:'); 76 | console.log(errorOutput); 77 | 78 | // Analizar si hay errores JSON 79 | const jsonErrors = errorOutput.match(/SyntaxError.*JSON/g); 80 | if (jsonErrors) { 81 | console.log('\n❌ JSON errors detected:'); 82 | jsonErrors.forEach(error => console.log(` - ${error}`)); 83 | console.log('\n💡 The server is sending non-JSON text to the stream.'); 84 | } else { 85 | console.log('\n✅ No JSON errors detected in the stream.'); 86 | } 87 | 88 | // Verificar si hay output JSON válido 89 | const lines = output.split('\n').filter(line => line.trim()); 90 | let validJsonCount = 0; 91 | let invalidJsonCount = 0; 92 | 93 | lines.forEach(line => { 94 | try { 95 | JSON.parse(line); 96 | validJsonCount++; 97 | } catch (e) { 98 | invalidJsonCount++; 99 | if (line.includes('✅') || line.includes('❌') || line.includes('🔄')) { 100 | console.log(`⚠️ Non-JSON line detected: ${line.substring(0, 100)}...`); 101 | } 102 | } 103 | }); 104 | 105 | console.log(`\n📈 Stream statistics:`); 106 | console.log(` ✅ Valid JSON lines: ${validJsonCount}`); 107 | console.log(` ❌ Non-JSON lines: ${invalidJsonCount}`); 108 | 109 | if (invalidJsonCount === 0) { 110 | console.log('\n🎉 MCP Server working correctly!'); 111 | } else { 112 | console.log('\n⚠️ Problems detected in the stream JSON.'); 113 | } 114 | } 115 | 116 | // Ejecutar test si se llama directamente 117 | if (import.meta.url === `file://${process.argv[1]}`) { 118 | testMcpServer().catch(error => { 119 | console.error('❌ MCP test error:', error); 120 | process.exit(1); 121 | }); 122 | } -------------------------------------------------------------------------------- /test-token-chunking.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Test token-based chunking implementation 5 | */ 6 | 7 | import { indexProject } from './src/service.js'; 8 | import fs from 'fs'; 9 | import path from 'path'; 10 | 11 | async function testTokenChunking() { 12 | console.log('=== Testing Token-Based Chunking Implementation ===\n'); 13 | 14 | // Create a test directory with a sample file 15 | const testDir = path.join(process.cwd(), 'test-chunking-tmp'); 16 | if (!fs.existsSync(testDir)) { 17 | fs.mkdirSync(testDir, { recursive: true }); 18 | } 19 | 20 | // Create a sample JavaScript file with a large function 21 | const testFile = path.join(testDir, 'sample.js'); 22 | const sampleCode = ` 23 | /** 24 | * @pampa-tags: test, large-function, sample 25 | * @pampa-intent: Test function for chunking 26 | * @pampa-description: A large test function to demonstrate token-based chunking 27 | */ 28 | function testLargeFunction() { 29 | // This is a test function with multiple statements 30 | const data = []; 31 | 32 | for (let i = 0; i < 100; i++) { 33 | data.push({ 34 | id: i, 35 | name: 'item' + i, 36 | value: Math.random() 37 | }); 38 | } 39 | 40 | function processData() { 41 | return data.map(item => { 42 | return { 43 | ...item, 44 | processed: true 45 | }; 46 | }); 47 | } 48 | 49 | return processData(); 50 | } 51 | 52 | class SampleClass { 53 | constructor() { 54 | this.value = 42; 55 | } 56 | 57 | method1() { 58 | return this.value * 2; 59 | } 60 | 61 | method2() { 62 | return this.value * 3; 63 | } 64 | } 65 | `; 66 | 67 | fs.writeFileSync(testFile, sampleCode); 68 | 69 | console.log('Test file created:', testFile); 70 | console.log('\n--- Testing with default provider (Transformers.js) ---'); 71 | 72 | try { 73 | // Test with transformers (small context window) 74 | await indexProject({ 75 | repoPath: testDir, 76 | provider: 'transformers' 77 | }); 78 | 79 | console.log('\n✓ Transformers test completed successfully!'); 80 | 81 | // If OpenAI key is available, test with OpenAI 82 | if (process.env.OPENAI_API_KEY) { 83 | console.log('\n--- Testing with OpenAI provider ---'); 84 | await indexProject({ 85 | repoPath: testDir, 86 | provider: 'openai' 87 | }); 88 | console.log('\n✓ OpenAI test completed successfully!'); 89 | } else { 90 | console.log('\nℹ OpenAI_API_KEY not set, skipping OpenAI test'); 91 | } 92 | 93 | // Test with custom environment variables 94 | console.log('\n--- Testing with custom PAMPAX_MAX_TOKENS ---'); 95 | process.env.PAMPAX_MAX_TOKENS = '512'; 96 | process.env.PAMPAX_DIMENSIONS = '256'; 97 | 98 | await indexProject({ 99 | repoPath: testDir, 100 | provider: 'transformers' 101 | }); 102 | 103 | console.log('\n✓ Custom configuration test completed successfully!'); 104 | 105 | // Clean up 106 | delete process.env.PAMPAX_MAX_TOKENS; 107 | delete process.env.PAMPAX_DIMENSIONS; 108 | 109 | console.log('\n=== All Tests Passed! ==='); 110 | console.log('\nImplementation verified:'); 111 | console.log(' ✓ Token counting infrastructure'); 112 | console.log(' ✓ Model profiles'); 113 | console.log(' ✓ Environment variable overrides'); 114 | console.log(' ✓ Provider integration'); 115 | console.log('\nYou can now use:'); 116 | console.log(' export PAMPAX_MAX_TOKENS=2000'); 117 | console.log(' export PAMPAX_DIMENSIONS=768'); 118 | console.log(' pampax index --provider openai'); 119 | 120 | } catch (error) { 121 | console.error('\n❌ Test failed:', error.message); 122 | console.error(error.stack); 123 | process.exit(1); 124 | } 125 | } 126 | 127 | testTokenChunking(); 128 | -------------------------------------------------------------------------------- /src/metrics/ir.js: -------------------------------------------------------------------------------- 1 | export function normalizeRelevance(relevance) { 2 | if (!relevance) { 3 | return new Map(); 4 | } 5 | 6 | if (relevance instanceof Map) { 7 | return relevance; 8 | } 9 | 10 | if (relevance instanceof Set) { 11 | const map = new Map(); 12 | for (const value of relevance) { 13 | map.set(value, 1); 14 | } 15 | return map; 16 | } 17 | 18 | if (Array.isArray(relevance)) { 19 | const map = new Map(); 20 | for (const value of relevance) { 21 | if (value && typeof value === 'object' && 'sha' in value) { 22 | const sha = value.sha; 23 | const gain = typeof value.gain === 'number' ? value.gain : 1; 24 | map.set(sha, gain); 25 | } else if (typeof value === 'string') { 26 | map.set(value, 1); 27 | } 28 | } 29 | return map; 30 | } 31 | 32 | if (typeof relevance === 'object') { 33 | const map = new Map(); 34 | for (const [key, value] of Object.entries(relevance)) { 35 | if (typeof value === 'number') { 36 | map.set(key, value); 37 | } else if (value) { 38 | map.set(key, 1); 39 | } 40 | } 41 | return map; 42 | } 43 | 44 | return new Map(); 45 | } 46 | 47 | export function precisionAt(results, relevance, k = 1) { 48 | if (!Array.isArray(results) || results.length === 0 || k <= 0) { 49 | return 0; 50 | } 51 | 52 | const relevanceMap = normalizeRelevance(relevance); 53 | if (relevanceMap.size === 0) { 54 | return 0; 55 | } 56 | 57 | const topK = results.slice(0, k); 58 | const relevantHits = topK.filter(result => relevanceMap.has(result.sha)).length; 59 | return relevantHits / k; 60 | } 61 | 62 | export function reciprocalRankAt(results, relevance, k = 10) { 63 | if (!Array.isArray(results) || results.length === 0 || k <= 0) { 64 | return 0; 65 | } 66 | 67 | const relevanceMap = normalizeRelevance(relevance); 68 | if (relevanceMap.size === 0) { 69 | return 0; 70 | } 71 | 72 | const topK = results.slice(0, k); 73 | for (let index = 0; index < topK.length; index++) { 74 | const candidate = topK[index]; 75 | if (relevanceMap.has(candidate.sha)) { 76 | return 1 / (index + 1); 77 | } 78 | } 79 | 80 | return 0; 81 | } 82 | 83 | function computeDcg(results, relevanceMap, k) { 84 | let dcg = 0; 85 | 86 | for (let index = 0; index < Math.min(k, results.length); index++) { 87 | const candidate = results[index]; 88 | const gain = relevanceMap.get(candidate.sha) || 0; 89 | if (gain <= 0) { 90 | continue; 91 | } 92 | 93 | const denominator = Math.log2(index + 2); 94 | dcg += (Math.pow(2, gain) - 1) / denominator; 95 | } 96 | 97 | return dcg; 98 | } 99 | 100 | export function ndcgAt(results, relevance, k = 10) { 101 | if (!Array.isArray(results) || results.length === 0 || k <= 0) { 102 | return 0; 103 | } 104 | 105 | const relevanceMap = normalizeRelevance(relevance); 106 | if (relevanceMap.size === 0) { 107 | return 0; 108 | } 109 | 110 | const dcg = computeDcg(results, relevanceMap, k); 111 | if (dcg === 0) { 112 | return 0; 113 | } 114 | 115 | const idealGains = Array.from(relevanceMap.values()) 116 | .filter(value => value > 0) 117 | .sort((a, b) => b - a); 118 | 119 | const idealResults = idealGains.map((gain, index) => ({ sha: `ideal-${index}`, gain })); 120 | const idealMap = new Map(idealResults.map(item => [item.sha, item.gain])); 121 | const idealDcg = computeDcg( 122 | idealResults, 123 | idealMap, 124 | Math.min(k, idealResults.length) 125 | ); 126 | 127 | if (idealDcg === 0) { 128 | return 0; 129 | } 130 | 131 | return dcg / idealDcg; 132 | } 133 | 134 | export function averageMetric(values) { 135 | if (!Array.isArray(values) || values.length === 0) { 136 | return 0; 137 | } 138 | 139 | const sum = values.reduce((acc, value) => acc + value, 0); 140 | return sum / values.length; 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pampax", 3 | "version": "1.16.13", 4 | "description": "PAMPAX – Protocol for Augmented Memory of Project Artifacts Extended (MCP compatible)", 5 | "author": "Lamim", 6 | "type": "module", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/lemon07r/pampax.git" 11 | }, 12 | "homepage": "https://github.com/lemon07r/pampax#readme", 13 | "bugs": { 14 | "url": "https://github.com/lemon07r/pampax/issues" 15 | }, 16 | "bin": { 17 | "pampax": "src/cli.js", 18 | "pampax-mcp": "src/mcp-server.js" 19 | }, 20 | "files": [ 21 | "src/", 22 | "README.md", 23 | "README_es.md", 24 | "README_FOR_AGENTS.md", 25 | "RULE_FOR_PAMPAX_MCP.md" 26 | ], 27 | "scripts": { 28 | "index": "node src/cli.js index", 29 | "index:local": "node src/cli.js index --provider transformers", 30 | "index:openai": "node src/cli.js index --provider openai", 31 | "index:ollama": "node src/cli.js index --provider ollama", 32 | "mcp": "node src/cli.js mcp", 33 | "search": "node src/cli.js search", 34 | "info": "node src/cli.js info", 35 | "build": "tsc", 36 | "dev": "tsc --watch", 37 | "prepare": "husky install", 38 | "prepublishOnly": "npm test", 39 | "test": "bash test/run-tests.sh", 40 | "test:unit": "node test/test-search-code.js", 41 | "test:mcp": "node test/test-mcp.js", 42 | "test:diagnostics": "node test/pampa-diagnostics.js", 43 | "bench": "node --test test/benchmarks/bench.test.js" 44 | }, 45 | "dependencies": { 46 | "@modelcontextprotocol/sdk": "^1.20.1", 47 | "@tree-sitter-grammars/tree-sitter-kotlin": "^1.1.0", 48 | "@tree-sitter-grammars/tree-sitter-markdown": "^0.3.2", 49 | "@xenova/transformers": "^2.17.2", 50 | "chokidar": "^3.6.0", 51 | "commander": "^14.0.1", 52 | "fast-glob": "^3.3.2", 53 | "micromatch": "^4.0.8", 54 | "openai": "^4.103.0", 55 | "sqlite3": "^5.1.6", 56 | "tiktoken": "^1.0.22", 57 | "tree-sitter": "^0.25.0", 58 | "tree-sitter-bash": "^0.25.0", 59 | "tree-sitter-c": "^0.24.1", 60 | "tree-sitter-c-sharp": "^0.23.1", 61 | "tree-sitter-cpp": "^0.23.4", 62 | "tree-sitter-css": "^0.25.0", 63 | "tree-sitter-elixir": "^0.3.4", 64 | "tree-sitter-go": "^0.25.0", 65 | "tree-sitter-haskell": "^0.23.1", 66 | "tree-sitter-html": "^0.23.2", 67 | "tree-sitter-java": "^0.23.5", 68 | "tree-sitter-javascript": "^0.25.0", 69 | "tree-sitter-json": "^0.24.8", 70 | "tree-sitter-lua": "^2.1.3", 71 | "tree-sitter-ocaml": "^0.24.2", 72 | "tree-sitter-php": "^0.24.2", 73 | "tree-sitter-python": "^0.25.0", 74 | "tree-sitter-ruby": "^0.23.1", 75 | "tree-sitter-rust": "^0.24.0", 76 | "tree-sitter-scala": "^0.24.0", 77 | "tree-sitter-swift": "^0.7.0", 78 | "tree-sitter-typescript": "^0.23.2", 79 | "wink-bm25-text-search": "^3.1.2", 80 | "xxhash-wasm": "^1.0.2", 81 | "zod": "^3.25.6" 82 | }, 83 | "optionalDependencies": { 84 | "cohere-ai": "^7.9.5", 85 | "ollama": "^0.6.0" 86 | }, 87 | "devDependencies": { 88 | "@semantic-release/changelog": "^6.0.3", 89 | "@semantic-release/git": "^10.0.1", 90 | "@semantic-release/github": "^11.0.2", 91 | "@semantic-release/npm": "^12.0.1", 92 | "@types/node": "^20.19.22", 93 | "husky": "^9.1.7", 94 | "semantic-release": "^24.2.3", 95 | "typescript": "^5.9.3" 96 | }, 97 | "keywords": [ 98 | "mcp", 99 | "model-context-protocol", 100 | "ai", 101 | "code-search", 102 | "embeddings", 103 | "vector-search" 104 | ], 105 | "engines": { 106 | "node": ">=16.0.0" 107 | }, 108 | "volta": { 109 | "node": "18.17.1" 110 | }, 111 | "packageManager": "pnpm@8.15.0", 112 | "release": { 113 | "branches": [ 114 | "master" 115 | ], 116 | "plugins": [ 117 | "@semantic-release/commit-analyzer", 118 | "@semantic-release/release-notes-generator", 119 | "@semantic-release/changelog", 120 | "@semantic-release/npm", 121 | "@semantic-release/github", 122 | [ 123 | "@semantic-release/git", 124 | { 125 | "assets": [ 126 | "CHANGELOG.md", 127 | "package.json" 128 | ], 129 | "message": "chore(release): ${nextRelease.version} [skip ci]" 130 | } 131 | ] 132 | ] 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/search/applyScope.js: -------------------------------------------------------------------------------- 1 | import micromatch from 'micromatch'; 2 | import { DEFAULT_RERANKER, RERANKER_OPTIONS } from '../types/search.js'; 3 | 4 | function toArray(value) { 5 | if (!value) { 6 | return []; 7 | } 8 | 9 | return Array.isArray(value) ? value : [value]; 10 | } 11 | 12 | function normalizeList(values) { 13 | return toArray(values) 14 | .map(value => (typeof value === 'string' ? value.trim() : '')) 15 | .filter(Boolean); 16 | } 17 | 18 | function normalizeToggle(value, defaultValue) { 19 | if (typeof value === 'boolean') { 20 | return value; 21 | } 22 | 23 | if (typeof value === 'string') { 24 | const normalized = value.trim().toLowerCase(); 25 | if (['on', 'true', '1', 'yes', 'enable', 'enabled'].includes(normalized)) { 26 | return true; 27 | } 28 | 29 | if (['off', 'false', '0', 'no', 'disable', 'disabled'].includes(normalized)) { 30 | return false; 31 | } 32 | } 33 | 34 | return defaultValue; 35 | } 36 | 37 | export function normalizeScopeFilters(scope = {}) { 38 | const normalized = {}; 39 | 40 | const normalizedPath = normalizeList(scope.path_glob || scope.pathGlob || scope.path); 41 | if (normalizedPath.length > 0) { 42 | normalized.path_glob = normalizedPath; 43 | } 44 | 45 | const normalizedTags = normalizeList(scope.tags || scope.tag); 46 | if (normalizedTags.length > 0) { 47 | normalized.tags = normalizedTags.map(tag => tag.toLowerCase()); 48 | } 49 | 50 | const normalizedLang = normalizeList(scope.lang || scope.language || scope.languages); 51 | if (normalizedLang.length > 0) { 52 | normalized.lang = normalizedLang.map(lang => lang.toLowerCase()); 53 | } 54 | 55 | const candidateProvider = typeof scope.provider === 'string' ? scope.provider.trim() : ''; 56 | if (candidateProvider) { 57 | normalized.provider = candidateProvider; 58 | } 59 | 60 | const candidateReranker = typeof scope.reranker === 'string' ? scope.reranker.trim() : ''; 61 | if (candidateReranker && RERANKER_OPTIONS.includes(candidateReranker)) { 62 | normalized.reranker = candidateReranker; 63 | } else { 64 | normalized.reranker = DEFAULT_RERANKER; 65 | } 66 | 67 | normalized.hybrid = normalizeToggle(scope.hybrid, true); 68 | normalized.bm25 = normalizeToggle(scope.bm25, true); 69 | normalized.symbol_boost = normalizeToggle(scope.symbol_boost, true); 70 | 71 | return normalized; 72 | } 73 | 74 | export function applyScope(chunks, scope = {}) { 75 | if (!Array.isArray(chunks) || chunks.length === 0) { 76 | return chunks; 77 | } 78 | 79 | const normalized = { 80 | path_glob: Array.isArray(scope.path_glob) ? scope.path_glob : normalizeList(scope.path_glob), 81 | tags: Array.isArray(scope.tags) ? scope.tags : normalizeList(scope.tags).map(tag => tag.toLowerCase()), 82 | lang: Array.isArray(scope.lang) ? scope.lang : normalizeList(scope.lang).map(lang => lang.toLowerCase()) 83 | }; 84 | 85 | let filtered = chunks; 86 | 87 | if (normalized.path_glob.length > 0) { 88 | filtered = filtered.filter(chunk => { 89 | const filePath = chunk.file_path || chunk.path || ''; 90 | if (!filePath) { 91 | return false; 92 | } 93 | 94 | return micromatch.isMatch(filePath, normalized.path_glob, { dot: true }); 95 | }); 96 | } 97 | 98 | if (normalized.tags.length > 0) { 99 | const tagSet = new Set(normalized.tags); 100 | filtered = filtered.filter(chunk => { 101 | if (!chunk.pampa_tags) { 102 | return false; 103 | } 104 | 105 | try { 106 | const tags = JSON.parse(chunk.pampa_tags || '[]'); 107 | if (!Array.isArray(tags)) { 108 | return false; 109 | } 110 | 111 | return tags.some(tag => typeof tag === 'string' && tagSet.has(tag.toLowerCase())); 112 | } catch (error) { 113 | return false; 114 | } 115 | }); 116 | } 117 | 118 | if (normalized.lang.length > 0) { 119 | const langSet = new Set(normalized.lang); 120 | filtered = filtered.filter(chunk => { 121 | if (!chunk.lang) { 122 | return false; 123 | } 124 | 125 | return langSet.has(String(chunk.lang).toLowerCase()); 126 | }); 127 | } 128 | 129 | return filtered; 130 | } 131 | -------------------------------------------------------------------------------- /RATE_LIMITING.md: -------------------------------------------------------------------------------- 1 | # Rate Limiting in Pampax 2 | 3 | ## Overview 4 | 5 | Pampax now includes intelligent rate limiting to prevent hitting API provider limits during indexing. This is especially important for cloud embedding providers like OpenAI and Cohere that have strict rate limits. 6 | 7 | ## Environment Variable 8 | 9 | ### `PAMPAX_RATE_LIMIT` 10 | 11 | Set the maximum number of embedding API requests per minute. 12 | 13 | ```bash 14 | export PAMPAX_RATE_LIMIT=50 # For OpenAI free tier 15 | export PAMPAX_RATE_LIMIT=500 # For OpenAI paid tier 16 | export PAMPAX_RATE_LIMIT=100 # For Cohere trial 17 | ``` 18 | 19 | ## Default Rate Limits 20 | 21 | If `PAMPAX_RATE_LIMIT` is not set, Pampax uses these defaults: 22 | 23 | | Provider | Default RPM | Notes | 24 | |----------|-------------|-------| 25 | | OpenAI | 50 | Free tier limit | 26 | | Cohere | 100 | Trial tier limit | 27 | | Ollama | No limit | Local model | 28 | | Transformers.js | No limit | Local model | 29 | 30 | ## How It Works 31 | 32 | 1. **Request Queue**: Embedding requests are queued and processed sequentially 33 | 2. **Throttling**: Requests are delayed to stay within the rate limit 34 | 3. **Automatic Retry**: 429 errors trigger exponential backoff retry (up to 4 attempts) 35 | 4. **Smart Detection**: Local models bypass rate limiting for maximum speed 36 | 37 | ## Features 38 | 39 | ### Exponential Backoff 40 | 41 | When a 429 (rate limit) error occurs: 42 | - 1st retry: Wait 1 second 43 | - 2nd retry: Wait 2 seconds 44 | - 3rd retry: Wait 5 seconds 45 | - 4th retry: Wait 10 seconds 46 | 47 | ### Logs 48 | 49 | During indexing, you'll see: 50 | 51 | ``` 52 | 📊 Chunking Configuration: 53 | Provider: OpenAI 54 | Model: text-embedding-3-large 55 | ... 56 | 🔒 Rate limiting: 50 requests/minute # API providers 57 | 58 | OR 59 | 60 | ⚡ Rate limiting: disabled (local model) # Local models 61 | ``` 62 | 63 | If rate limiting triggers: 64 | ``` 65 | ⚠️ Rate limit hit (429). Retrying in 1000ms... (attempt 1/4) 66 | ``` 67 | 68 | ## Examples 69 | 70 | ### OpenAI with Custom Rate Limit 71 | 72 | ```bash 73 | # Paid tier with higher limits 74 | export PAMPAX_RATE_LIMIT=500 75 | node src/cli.js index /path/to/project --provider auto 76 | ``` 77 | 78 | ### Cohere with Trial Limits 79 | 80 | ```bash 81 | export PAMPAX_RATE_LIMIT=100 82 | export COHERE_API_KEY=your-key 83 | node src/cli.js index /path/to/project --provider auto 84 | ``` 85 | 86 | ### Local Model (No Limits) 87 | 88 | ```bash 89 | # No rate limiting needed 90 | node src/cli.js index /path/to/project --provider transformers 91 | ``` 92 | 93 | ## Calculating Indexing Time 94 | 95 | With rate limiting, you can estimate indexing time: 96 | 97 | ``` 98 | chunks = number of code chunks 99 | rpm = requests per minute (from PAMPAX_RATE_LIMIT) 100 | time_minutes = chunks / rpm 101 | 102 | Example: 103 | 49 chunks @ 50 RPM = ~1 minute 104 | 500 chunks @ 50 RPM = ~10 minutes 105 | 500 chunks @ 500 RPM = ~1 minute 106 | ``` 107 | 108 | ## Troubleshooting 109 | 110 | ### Still Getting 429 Errors? 111 | 112 | 1. **Lower the rate limit**: 113 | ```bash 114 | export PAMPAX_RATE_LIMIT=30 # More conservative 115 | ``` 116 | 117 | 2. **Check your provider tier**: Free tiers have lower limits 118 | 119 | 3. **Verify API key**: Wrong keys may have different limits 120 | 121 | ### Indexing Too Slow? 122 | 123 | 1. **Upgrade provider tier**: Get higher RPM limits 124 | 2. **Use local models**: No rate limits at all 125 | ```bash 126 | npm install @xenova/transformers 127 | node src/cli.js index . --provider transformers 128 | ``` 129 | 130 | ## Technical Details 131 | 132 | ### Implementation 133 | 134 | - **File**: `src/utils/rate-limiter.js` 135 | - **Pattern**: Request queue with sliding window 136 | - **Memory**: Tracks last 60 seconds of requests 137 | - **Cleanup**: Automatic cleanup of old request timestamps 138 | 139 | ### Provider Integration 140 | 141 | All embedding providers support rate limiting: 142 | - `OpenAIProvider` 143 | - `CohereProvider` 144 | - `OllamaProvider` (bypassed for local) 145 | - `TransformersProvider` (bypassed for local) 146 | 147 | ### Error Handling 148 | 149 | The rate limiter handles these errors: 150 | - `status === 429` 151 | - Error message contains "429" 152 | - Error message contains "rate limit" 153 | - Error message contains "too many requests" 154 | 155 | ## See Also 156 | 157 | - [Chunking Strategy](OPTIMIZATION_SUMMARY.md) - How Pampax reduces chunk count 158 | - [Provider Configuration](README.md#providers) - Setting up embedding providers 159 | - [Environment Variables](README.md#environment-variables) - All configuration options 160 | -------------------------------------------------------------------------------- /src/cli/commands/context.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { getActiveContextPack, listContextPacks, loadContextPack, setActiveContextPack } from '../../context/packs.js'; 3 | 4 | function resolveProjectPath(projectPath = '.') { 5 | return path.resolve(projectPath || '.'); 6 | } 7 | 8 | function formatPackLine(pack, activeKey) { 9 | const parts = []; 10 | const isActive = pack.key === activeKey; 11 | const marker = isActive ? '•' : '-'; 12 | parts.push(`${marker} ${pack.key}`); 13 | 14 | if (pack.name && pack.name !== pack.key) { 15 | parts.push(`(${pack.name})`); 16 | } 17 | 18 | if (pack.description) { 19 | parts.push(`– ${pack.description}`); 20 | } 21 | 22 | if (pack.invalid) { 23 | parts.push('(invalid)'); 24 | } 25 | 26 | return parts.join(' '); 27 | } 28 | 29 | function printPackDetails(pack) { 30 | const output = { 31 | key: pack.key, 32 | name: pack.name, 33 | description: pack.description || null, 34 | scope: pack.scope 35 | }; 36 | console.log(JSON.stringify(output, null, 2)); 37 | } 38 | 39 | export function registerContextCommands(program) { 40 | const contextCommand = program 41 | .command('context') 42 | .description('Manage context packs for scoped search defaults'); 43 | 44 | contextCommand 45 | .command('list [path]') 46 | .description('List available context packs for a project') 47 | .action((projectPath = '.') => { 48 | const resolvedPath = resolveProjectPath(projectPath); 49 | const packs = listContextPacks(resolvedPath); 50 | const active = getActiveContextPack(resolvedPath); 51 | const activeKey = active ? active.key : null; 52 | 53 | if (!packs || packs.length === 0) { 54 | console.log('No context packs found. Create files in .pampa/contextpacks/*.json'); 55 | return; 56 | } 57 | 58 | console.log(`Context packs in ${resolvedPath}:`); 59 | packs 60 | .sort((a, b) => a.key.localeCompare(b.key)) 61 | .forEach(pack => { 62 | console.log(` ${formatPackLine(pack, activeKey)}`); 63 | }); 64 | 65 | if (active) { 66 | console.log(`\nActive: ${active.key}${active.name && active.name !== active.key ? ` (${active.name})` : ''}`); 67 | } 68 | }); 69 | 70 | contextCommand 71 | .command('show [path]') 72 | .description('Show the definition of a context pack') 73 | .action((name, projectPath = '.') => { 74 | const resolvedPath = resolveProjectPath(projectPath); 75 | try { 76 | const pack = loadContextPack(name, resolvedPath); 77 | printPackDetails(pack); 78 | } catch (error) { 79 | console.error(`Failed to load context pack "${name}": ${error.message}`); 80 | process.exitCode = 1; 81 | } 82 | }); 83 | 84 | contextCommand 85 | .command('use [path]') 86 | .description('Activate a context pack as the default scope for CLI searches') 87 | .action((name, projectPath = '.') => { 88 | const resolvedPath = resolveProjectPath(projectPath); 89 | try { 90 | const pack = setActiveContextPack(name, resolvedPath); 91 | console.log(`Activated context pack: ${pack.key}`); 92 | if (pack.name && pack.name !== pack.key) { 93 | console.log(`Display name: ${pack.name}`); 94 | } 95 | if (pack.description) { 96 | console.log(`Description: ${pack.description}`); 97 | } 98 | if (pack.scope && Object.keys(pack.scope).length > 0) { 99 | console.log('Default scope:'); 100 | for (const [key, value] of Object.entries(pack.scope)) { 101 | console.log(` ${key}: ${Array.isArray(value) ? value.join(', ') : value}`); 102 | } 103 | } 104 | } catch (error) { 105 | console.error(`Failed to activate context pack "${name}": ${error.message}`); 106 | process.exitCode = 1; 107 | } 108 | }); 109 | 110 | contextCommand.addHelpText('after', `\nExamples:\n $ pampa context list\n $ pampa context show stripe-backend\n $ pampa context use stripe-backend\n $ pampa context use clear\n\nContext packs live in .pampa/contextpacks/*.json. Flags passed to \`pampa search\` always override pack defaults.\n`); 111 | } 112 | -------------------------------------------------------------------------------- /RERANKER_DEFAULT_FIX.md: -------------------------------------------------------------------------------- 1 | # Fix: Reranker Not Being Called Even When Configured 2 | 3 | ## Problem 4 | The user had configured the Novita reranker API with `PAMPAX_RERANK_API_URL` and `PAMPAX_RERANK_API_KEY`, but the reranker was never being called. Their Novita dashboard showed 0 usage despite using pampax all day. 5 | 6 | ## Root Cause 7 | The reranker parameter in PAMPAX defaults to `'off'` in the MCP server and CLI. Even when the reranker API is properly configured, the reranker code is only invoked when explicitly passing `reranker: 'api'` (or `reranker: 'transformers'`) in search requests. 8 | 9 | ### Code Flow 10 | 1. **MCP Server** (`src/mcp-server.js` line 224): 11 | ```javascript 12 | reranker: z.enum(['off', 'transformers', 'api']).optional().default('off') 13 | ``` 14 | 15 | 2. **Service** (`src/service.js` line 2228-2243): 16 | ```javascript 17 | if (vectorResults.length > 1 && 18 | (normalizedScope.reranker === 'transformers' || normalizedScope.reranker === 'api')) { 19 | // Only then is the reranker called 20 | } 21 | ``` 22 | 23 | 3. **Types** (`src/types/search.js`): 24 | ```javascript 25 | export const DEFAULT_RERANKER = 'off'; 26 | ``` 27 | 28 | ## Solution 29 | Added a new environment variable `PAMPAX_RERANKER_DEFAULT` that allows users to set the default reranker mode globally. 30 | 31 | ### Changes Made 32 | 33 | #### 1. Modified `src/types/search.js` 34 | ```javascript 35 | export const RERANKER_OPTIONS = ['off', 'transformers', 'api']; 36 | 37 | // Get default reranker from environment variable, fallback to 'off' 38 | function getDefaultReranker() { 39 | const envValue = process.env.PAMPAX_RERANKER_DEFAULT; 40 | if (envValue && RERANKER_OPTIONS.includes(envValue.toLowerCase())) { 41 | return envValue.toLowerCase(); 42 | } 43 | return 'off'; 44 | } 45 | 46 | export const DEFAULT_RERANKER = getDefaultReranker(); 47 | ``` 48 | 49 | #### 2. Updated Documentation 50 | - **README.md**: Added `PAMPAX_RERANKER_DEFAULT` to reranker configuration section 51 | - **README_FOR_AGENTS.md**: Added to reranking configuration options 52 | - Updated MCP configuration example to include the new variable 53 | 54 | #### 3. Added Tests 55 | Created `test/reranker-default.test.js` with comprehensive test coverage: 56 | - ✅ Defaults to "off" when not set 57 | - ✅ Uses "api" when set to "api" 58 | - ✅ Uses "transformers" when set to "transformers" 59 | - ✅ Falls back to "off" for invalid values 60 | - ✅ Case-insensitive handling 61 | 62 | #### 4. Updated CHANGELOG.md 63 | Added entry in [Unreleased] section documenting the new feature. 64 | 65 | ## Usage 66 | 67 | ### For MCP Users (Recommended) 68 | Add to your Claude Desktop or Cursor configuration: 69 | 70 | ```json 71 | { 72 | "mcpServers": { 73 | "pampax": { 74 | "command": "npx", 75 | "args": ["-y", "pampax", "mcp"], 76 | "env": { 77 | "OPENAI_API_KEY": "your-api-key", 78 | "OPENAI_BASE_URL": "https://api.novita.ai/openai", 79 | "PAMPAX_OPENAI_EMBEDDING_MODEL": "qwen/qwen3-embedding-8b", 80 | "PAMPAX_RERANK_API_URL": "https://api.novita.ai/openai/v1/rerank", 81 | "PAMPAX_RERANK_API_KEY": "your-api-key", 82 | "PAMPAX_RERANK_MODEL": "qwen/qwen3-reranker-8b", 83 | "PAMPAX_RERANKER_DEFAULT": "api" 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | ### For CLI Users 91 | ```bash 92 | export PAMPAX_RERANKER_DEFAULT=api 93 | pampax search "your query" # Will now use API reranker by default 94 | ``` 95 | 96 | ### For Environment Files 97 | ```bash 98 | # .env 99 | PAMPAX_RERANKER_DEFAULT=api 100 | ``` 101 | 102 | ## Benefits 103 | 1. **No manual intervention**: Users with configured reranker APIs no longer need to pass `reranker: 'api'` in every search 104 | 2. **API usage tracking**: The reranker API will now be called automatically, showing usage in dashboards 105 | 3. **Flexible override**: Users can still override the default on a per-search basis 106 | 4. **Backward compatible**: Defaults to 'off' if not set, maintaining existing behavior 107 | 108 | ## Testing 109 | All tests pass successfully: 110 | ``` 111 | ✅ PASS MCP Server Basic Test 112 | ✅ PASS Search Code Validation Test 113 | ✅ PASS Database Error Handling Test 114 | ✅ PASS Scoped Search Filters Test 115 | ✅ PASS Hybrid Search Fusion Test 116 | ✅ PASS Cross-Encoder Reranker Test 117 | ✅ PASS PAMPAX_RERANKER_DEFAULT environment variable (5 tests) 118 | ``` 119 | 120 | ## Next Steps for User 121 | To fix your issue, update your MCP configuration to include: 122 | ```json 123 | "PAMPAX_RERANKER_DEFAULT": "api" 124 | ``` 125 | 126 | After restarting your MCP client, all searches will automatically use the reranker, and you should see usage in your Novita dashboard. 127 | -------------------------------------------------------------------------------- /examples/chat-app-typescript/modules/userManager.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | /** 4 | * Gestiona los usuarios conectados al chat 5 | */ 6 | export class UserManager { 7 | constructor() { 8 | this.users = new Map(); // userId -> userData 9 | this.connections = new Map(); // connection -> userId 10 | } 11 | 12 | /** 13 | * Registra un nuevo usuario 14 | */ 15 | registerUser(connection, userData) { 16 | const userId = uuidv4(); 17 | const user = { 18 | id: userId, 19 | username: userData.username || `Usuario_${userId.slice(0, 8)}`, 20 | avatar: userData.avatar || this.generateAvatar(), 21 | joinedAt: new Date(), 22 | isOnline: true, 23 | currentRoom: userData.room || 'general' 24 | }; 25 | 26 | this.users.set(userId, user); 27 | this.connections.set(connection, userId); 28 | 29 | console.log(`👤 Usuario registrado: ${user.username} (${userId})`); 30 | return user; 31 | } 32 | 33 | /** 34 | * Desconecta un usuario 35 | */ 36 | disconnectUser(connection) { 37 | const userId = this.connections.get(connection); 38 | if (userId) { 39 | const user = this.users.get(userId); 40 | if (user) { 41 | user.isOnline = false; 42 | user.disconnectedAt = new Date(); 43 | console.log(`👋 Usuario desconectado: ${user.username}`); 44 | } 45 | this.connections.delete(connection); 46 | return userId; 47 | } 48 | return null; 49 | } 50 | 51 | /** 52 | * Obtiene un usuario por su conexión 53 | */ 54 | getUserByConnection(connection) { 55 | const userId = this.connections.get(connection); 56 | return userId ? this.users.get(userId) : null; 57 | } 58 | 59 | /** 60 | * Obtiene un usuario por su ID 61 | */ 62 | getUserById(userId) { 63 | return this.users.get(userId); 64 | } 65 | 66 | /** 67 | * Obtiene todos los usuarios 68 | */ 69 | getAllUsers() { 70 | return Array.from(this.users.values()); 71 | } 72 | 73 | /** 74 | * Obtiene usuarios online 75 | */ 76 | getOnlineUsers() { 77 | return Array.from(this.users.values()).filter(user => user.isOnline); 78 | } 79 | 80 | /** 81 | * Obtiene usuarios en una sala específica 82 | */ 83 | getUsersInRoom(roomId) { 84 | return Array.from(this.users.values()).filter( 85 | user => user.currentRoom === roomId && user.isOnline 86 | ); 87 | } 88 | 89 | /** 90 | * Cambia un usuario de sala 91 | */ 92 | changeUserRoom(userId, newRoomId) { 93 | const user = this.users.get(userId); 94 | if (user) { 95 | const oldRoom = user.currentRoom; 96 | user.currentRoom = newRoomId; 97 | console.log(`🚪 ${user.username} se movió de ${oldRoom} a ${newRoomId}`); 98 | return { oldRoom, newRoom: newRoomId }; 99 | } 100 | return null; 101 | } 102 | 103 | /** 104 | * Actualiza el estado de un usuario 105 | */ 106 | updateUserStatus(userId, status) { 107 | const user = this.users.get(userId); 108 | if (user) { 109 | user.status = status; 110 | user.lastActivity = new Date(); 111 | return true; 112 | } 113 | return false; 114 | } 115 | 116 | /** 117 | * Genera un avatar aleatorio 118 | */ 119 | generateAvatar() { 120 | const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8']; 121 | const emojis = ['😀', '😎', '🤖', '🦄', '🐱', '🐶', '🦊', '🐼', '🦁', '🐸']; 122 | 123 | return { 124 | color: colors[Math.floor(Math.random() * colors.length)], 125 | emoji: emojis[Math.floor(Math.random() * emojis.length)] 126 | }; 127 | } 128 | 129 | /** 130 | * Obtiene estadísticas de usuarios 131 | */ 132 | getStats() { 133 | const total = this.users.size; 134 | const online = this.getOnlineUsers().length; 135 | const offline = total - online; 136 | 137 | return { 138 | total, 139 | online, 140 | offline, 141 | rooms: this.getRoomDistribution() 142 | }; 143 | } 144 | 145 | /** 146 | * Obtiene la distribución de usuarios por sala 147 | */ 148 | getRoomDistribution() { 149 | const distribution = {}; 150 | this.getOnlineUsers().forEach(user => { 151 | distribution[user.currentRoom] = (distribution[user.currentRoom] || 0) + 1; 152 | }); 153 | return distribution; 154 | } 155 | } -------------------------------------------------------------------------------- /examples/chat-app-java/src/main/java/com/pampa/chat/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.pampa.chat.service; 2 | 3 | import com.pampa.chat.model.ChatMessage; 4 | import com.pampa.chat.model.Room; 5 | import com.pampa.chat.model.User; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.socket.WebSocketSession; 8 | 9 | import java.util.*; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * Chat Service 14 | * Manages chat rooms, users, and messages 15 | */ 16 | @Service 17 | public class ChatService { 18 | 19 | private final Map sessions = new ConcurrentHashMap<>(); 20 | private final Map users = new ConcurrentHashMap<>(); 21 | private final Map rooms = new ConcurrentHashMap<>(); 22 | private final Map> roomUsers = new ConcurrentHashMap<>(); 23 | private final Map> messageHistory = new ConcurrentHashMap<>(); 24 | 25 | public ChatService() { 26 | initializeDefaultRooms(); 27 | } 28 | 29 | private void initializeDefaultRooms() { 30 | List defaultRooms = Arrays.asList( 31 | new Room("general", "General", "Sala principal para conversaciones generales", true, 50, "system"), 32 | new Room("java", "Java", "Todo sobre Java y Spring", true, 30, "system"), 33 | new Room("tecnologia", "Tecnología", "Discusiones sobre tecnología y programación", true, 25, "system") 34 | ); 35 | 36 | for (Room room : defaultRooms) { 37 | rooms.put(room.getId(), room); 38 | roomUsers.put(room.getId(), ConcurrentHashMap.newKeySet()); 39 | messageHistory.put(room.getId(), Collections.synchronizedList(new ArrayList<>())); 40 | } 41 | } 42 | 43 | public void addSession(String sessionId, WebSocketSession session) { 44 | sessions.put(sessionId, session); 45 | } 46 | 47 | public void removeSession(String sessionId) { 48 | sessions.remove(sessionId); 49 | 50 | // Remove user and clean up 51 | User user = users.remove(sessionId); 52 | if (user != null) { 53 | String roomId = user.getCurrentRoom(); 54 | if (roomId != null) { 55 | removeUserFromRoom(sessionId, roomId); 56 | } 57 | } 58 | } 59 | 60 | public boolean registerUser(String sessionId, String username) { 61 | // Check if username already exists 62 | for (User user : users.values()) { 63 | if (user.getUsername().equalsIgnoreCase(username)) { 64 | return false; 65 | } 66 | } 67 | 68 | User user = new User(sessionId, username, generateAvatarColor(), "general"); 69 | users.put(sessionId, user); 70 | addUserToRoom(sessionId, "general"); 71 | return true; 72 | } 73 | 74 | public User getUser(String sessionId) { 75 | return users.get(sessionId); 76 | } 77 | 78 | public WebSocketSession getSession(String sessionId) { 79 | return sessions.get(sessionId); 80 | } 81 | 82 | public Set getRoomUsers(String roomId) { 83 | return roomUsers.getOrDefault(roomId, Collections.emptySet()); 84 | } 85 | 86 | public List getMessageHistory(String roomId) { 87 | return messageHistory.getOrDefault(roomId, Collections.emptyList()); 88 | } 89 | 90 | public void addUserToRoom(String userId, String roomId) { 91 | roomUsers.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(userId); 92 | User user = users.get(userId); 93 | if (user != null) { 94 | user.setCurrentRoom(roomId); 95 | } 96 | } 97 | 98 | public void removeUserFromRoom(String userId, String roomId) { 99 | Set users = roomUsers.get(roomId); 100 | if (users != null) { 101 | users.remove(userId); 102 | } 103 | } 104 | 105 | public void addMessage(String roomId, ChatMessage message) { 106 | List history = messageHistory.get(roomId); 107 | if (history != null) { 108 | history.add(message); 109 | 110 | // Keep only last 100 messages 111 | if (history.size() > 100) { 112 | history.subList(0, history.size() - 100).clear(); 113 | } 114 | } 115 | } 116 | 117 | public Room getRoom(String roomId) { 118 | return rooms.get(roomId); 119 | } 120 | 121 | public Collection getAllRooms() { 122 | return rooms.values(); 123 | } 124 | 125 | private String generateAvatarColor() { 126 | String[] colors = {"#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FECA57", 127 | "#FF9FF3", "#54A0FF", "#5F27CD", "#00D2D3", "#FF9F43"}; 128 | return colors[new Random().nextInt(colors.length)]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /.cursor/rules/pampa-testing-examples.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # PAMPA Testing & Examples Guide 7 | 8 | This guide explains how to test PAMPA functionality using the provided examples and testing tools. 9 | 10 | ## Available Examples 11 | 12 | ### Chat App Example 13 | - **Location**: [examples/chat-app/](mdc:examples/chat-app) 14 | - **Purpose**: Real-world JavaScript project for testing indexing and search 15 | - **Features**: Multiple modules, utility functions, complex codebase structure 16 | - **Use Case**: Test semantic search on actual code patterns 17 | 18 | ### Test Implementations (Private) 19 | - **Location**: `test/test-implementations/` 20 | - **Purpose**: Private testing area for implementation experiments 21 | - **Features**: Gitignored content, safe for sensitive tests 22 | - **Use Case**: Test PAMPA with real codebases without exposing them publicly 23 | 24 | ## Testing Workflow 25 | 26 | ### 1. Basic Functionality Test 27 | ```bash 28 | # Index the chat-app example 29 | npx pampa index examples/chat-app/ 30 | 31 | # Test search functionality 32 | npx pampa search "validation function" -p examples/chat-app/ 33 | 34 | # Check project stats 35 | npx pampa info examples/chat-app/ 36 | ``` 37 | 38 | ### 2. Private Implementation Testing 39 | ```bash 40 | # Copy your real codebase to test-implementations for testing 41 | cp -r /path/to/your/project test/test-implementations/my-project/ 42 | 43 | # Index and test with real code 44 | npx pampa index test/test-implementations/my-project/ 45 | npx pampa search "your specific query" -p test/test-implementations/my-project/ 46 | 47 | # Content is gitignored, safe for testing 48 | ``` 49 | 50 | ### 3. MCP Integration Test 51 | ```bash 52 | # Start MCP server 53 | npx pampa mcp 54 | 55 | # Test MCP tools via CLI or agent: 56 | # - get_project_stats(path="examples/chat-app") 57 | # - search_code("user validation", path="examples/chat-app") 58 | # - get_code_chunk(sha_from_search_results) 59 | ``` 60 | 61 | ### 4. Update Workflow Test 62 | ```bash 63 | # Make changes to example files 64 | # Then test update functionality 65 | npx pampa update examples/chat-app/ 66 | 67 | # Verify changes are reflected in search 68 | npx pampa search "new function name" -p examples/chat-app/ 69 | ``` 70 | 71 | ## Test Validation Points 72 | 73 | ### Indexing Tests 74 | - ✅ Database created at `examples/chat-app/.pampa/pampa.db` 75 | - ✅ Chunks directory populated with `.gz` files 76 | - ✅ Codemap generated at `examples/chat-app/pampa.codemap.json` 77 | - ✅ Functions properly extracted and chunked 78 | 79 | ### Search Tests 80 | - ✅ Semantic search returns relevant results 81 | - ✅ Similarity scores are reasonable (>0.3 for good matches) 82 | - ✅ Results include proper metadata (file, line, symbol name) 83 | - ✅ SHA references are valid and retrievable 84 | 85 | ### MCP Tests 86 | - ✅ All MCP tools respond without errors 87 | - ✅ Path parameters work correctly 88 | - ✅ Results are properly formatted JSON 89 | - ✅ get_code_chunk returns complete source code 90 | 91 | ## Test Scenarios 92 | 93 | ### New Function Detection 94 | 1. Add a new function to [examples/chat-app/modules/utils.js](mdc:examples/chat-app/modules/utils.js) 95 | 2. Run `update_project` 96 | 3. Search for the new function 97 | 4. Verify it's found and retrievable 98 | 99 | ### Code Modification Detection 100 | 1. Modify existing function in chat-app 101 | 2. Run `update_project` 102 | 3. Verify updated content is indexed 103 | 4. Check that old version is replaced 104 | 105 | ### Multi-language Support 106 | 1. Add files in different supported languages to examples 107 | 2. Test indexing and search across languages 108 | 3. Verify language-specific parsing works 109 | 110 | ### Real Project Testing (Private) 111 | 1. Copy real project to `test/test-implementations/` 112 | 2. Test PAMPA performance with actual codebase 113 | 3. Validate semantic search on domain-specific code 114 | 4. Content remains private (gitignored) 115 | 116 | ## Example Test Commands 117 | 118 | ```bash 119 | # Full test cycle 120 | npx pampa index examples/chat-app/ 121 | npx pampa search "sanitize text" -p examples/chat-app/ 122 | npx pampa info examples/chat-app/ 123 | 124 | # Update test 125 | echo "export function testFunction() { return 'test'; }" >> examples/chat-app/modules/utils.js 126 | npx pampa update examples/chat-app/ 127 | npx pampa search "testFunction" -p examples/chat-app/ 128 | 129 | # Private implementation test 130 | npx pampa index test/test-implementations/your-project/ 131 | npx pampa search "domain specific query" -p test/test-implementations/your-project/ 132 | 133 | # MCP test 134 | npx pampa mcp & 135 | # Then test via MCP client 136 | ``` 137 | 138 | ## Debugging Tips 139 | 140 | - Use `--debug` flag for detailed logging 141 | - Check `.pampa/pampa_debug.log` for troubleshooting 142 | - Verify file permissions on .pampa directory 143 | - Ensure embedding provider is properly configured 144 | - Use `test/test-implementations/` for testing with real codebases safely 145 | -------------------------------------------------------------------------------- /examples/chat-app-php/public/index.php: -------------------------------------------------------------------------------- 1 | addErrorMiddleware(true, true, true); 21 | 22 | // Routes 23 | $app->get('/', function ($request, $response, $args) { 24 | $html = ' 25 | 26 | 27 | 28 | 29 | PAMPA Chat - PHP 30 | 31 | 32 | 33 | 34 |
35 | 36 | 53 | 54 | 55 | 114 | 115 | 116 |
117 |
118 | 119 | 120 | 121 | '; 122 | 123 | $response->getBody()->write($html); 124 | return $response->withHeader('Content-Type', 'text/html'); 125 | }); 126 | 127 | $app->run(); 128 | -------------------------------------------------------------------------------- /.windsurf/rules/pampa-testing-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | trigger: always_on 3 | --- 4 | 5 | # PAMPA Testing & Examples Guide 6 | 7 | This guide explains how to test PAMPA functionality using the provided examples and testing tools. 8 | 9 | ## Available Examples 10 | 11 | ### Chat App Example 12 | 13 | - **Location**: [examples/chat-app/](mdc:examples/chat-app) 14 | - **Purpose**: Real-world JavaScript project for testing indexing and search 15 | - **Features**: Multiple modules, utility functions, complex codebase structure 16 | - **Use Case**: Test semantic search on actual code patterns 17 | 18 | ### Test Implementations (Private) 19 | 20 | - **Location**: `test/test-implementations/` 21 | - **Purpose**: Private testing area for implementation experiments 22 | - **Features**: Gitignored content, safe for sensitive tests 23 | - **Use Case**: Test PAMPA with real codebases without exposing them publicly 24 | 25 | ## Testing Workflow 26 | 27 | ### 1. Basic Functionality Test 28 | 29 | ```bash 30 | # Index the chat-app example 31 | npx pampa index examples/chat-app/ 32 | 33 | # Test search functionality 34 | npx pampa search "validation function" -p examples/chat-app/ 35 | 36 | # Check project stats 37 | npx pampa info examples/chat-app/ 38 | ``` 39 | 40 | ### 2. Private Implementation Testing 41 | 42 | ```bash 43 | # Copy your real codebase to test-implementations for testing 44 | cp -r /path/to/your/project test/test-implementations/my-project/ 45 | 46 | # Index and test with real code 47 | npx pampa index test/test-implementations/my-project/ 48 | npx pampa search "your specific query" -p test/test-implementations/my-project/ 49 | 50 | # Content is gitignored, safe for testing 51 | ``` 52 | 53 | ### 3. MCP Integration Test 54 | 55 | ```bash 56 | # Start MCP server 57 | npx pampa mcp 58 | 59 | # Test MCP tools via CLI or agent: 60 | # - get_project_stats(path="examples/chat-app") 61 | # - search_code("user validation", path="examples/chat-app") 62 | # - get_code_chunk(sha_from_search_results) 63 | ``` 64 | 65 | ### 4. Update Workflow Test 66 | 67 | ```bash 68 | # Make changes to example files 69 | # Then test update functionality 70 | npx pampa update examples/chat-app/ 71 | 72 | # Verify changes are reflected in search 73 | npx pampa search "new function name" -p examples/chat-app/ 74 | ``` 75 | 76 | ## Test Validation Points 77 | 78 | ### Indexing Tests 79 | 80 | - ✅ Database created at `examples/chat-app/.pampa/pampa.db` 81 | - ✅ Chunks directory populated with `.gz` files 82 | - ✅ Codemap generated at `examples/chat-app/pampa.codemap.json` 83 | - ✅ Functions properly extracted and chunked 84 | 85 | ### Search Tests 86 | 87 | - ✅ Semantic search returns relevant results 88 | - ✅ Similarity scores are reasonable (>0.3 for good matches) 89 | - ✅ Results include proper metadata (file, line, symbol name) 90 | - ✅ SHA references are valid and retrievable 91 | 92 | ### MCP Tests 93 | 94 | - ✅ All MCP tools respond without errors 95 | - ✅ Path parameters work correctly 96 | - ✅ Results are properly formatted JSON 97 | - ✅ get_code_chunk returns complete source code 98 | 99 | ## Test Scenarios 100 | 101 | ### New Function Detection 102 | 103 | 1. Add a new function to [examples/chat-app/modules/utils.js](mdc:examples/chat-app/modules/utils.js) 104 | 2. Run `update_project` 105 | 3. Search for the new function 106 | 4. Verify it's found and retrievable 107 | 108 | ### Code Modification Detection 109 | 110 | 1. Modify existing function in chat-app 111 | 2. Run `update_project` 112 | 3. Verify updated content is indexed 113 | 4. Check that old version is replaced 114 | 115 | ### Multi-language Support 116 | 117 | 1. Add files in different supported languages to examples 118 | 2. Test indexing and search across languages 119 | 3. Verify language-specific parsing works 120 | 121 | ### Real Project Testing (Private) 122 | 123 | 1. Copy real project to `test/test-implementations/` 124 | 2. Test PAMPA performance with actual codebase 125 | 3. Validate semantic search on domain-specific code 126 | 4. Content remains private (gitignored) 127 | 128 | ## Example Test Commands 129 | 130 | ```bash 131 | # Full test cycle 132 | npx pampa index examples/chat-app/ 133 | npx pampa search "sanitize text" -p examples/chat-app/ 134 | npx pampa info examples/chat-app/ 135 | 136 | # Update test 137 | echo "export function testFunction() { return 'test'; }" >> examples/chat-app/modules/utils.js 138 | npx pampa update examples/chat-app/ 139 | npx pampa search "testFunction" -p examples/chat-app/ 140 | 141 | # Private implementation test 142 | npx pampa index test/test-implementations/your-project/ 143 | npx pampa search "domain specific query" -p test/test-implementations/your-project/ 144 | 145 | # MCP test 146 | npx pampa mcp & 147 | # Then test via MCP client 148 | ``` 149 | 150 | ## Debugging Tips 151 | 152 | - Use `--debug` flag for detailed logging 153 | - Check `.pampa/pampa_debug.log` for troubleshooting 154 | - Verify file permissions on .pampa directory 155 | - Ensure embedding provider is properly configured 156 | - Use `test/test-implementations/` for testing with real codebases safely 157 | -------------------------------------------------------------------------------- /test/chunking-strategy.test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Test for the improved token-aware chunking strategy 5 | */ 6 | 7 | import { test } from 'node:test'; 8 | import assert from 'node:assert'; 9 | import fs from 'fs'; 10 | import path from 'path'; 11 | import { indexProject, searchCode } from '../src/service.js'; 12 | 13 | const TEST_DIR = '.pampa-test-chunking'; 14 | const TEST_CODEMAP = 'pampa.codemap.test.json'; 15 | 16 | test('Chunking Strategy Tests', async (t) => { 17 | // Clean up before tests 18 | if (fs.existsSync(TEST_DIR)) { 19 | fs.rmSync(TEST_DIR, { recursive: true, force: true }); 20 | } 21 | if (fs.existsSync(TEST_CODEMAP)) { 22 | fs.unlinkSync(TEST_CODEMAP); 23 | } 24 | 25 | await t.test('should analyze and report chunking statistics', async () => { 26 | console.log(' Testing chunking statistics...'); 27 | 28 | const result = await indexProject({ 29 | repoPath: '.', 30 | provider: 'transformers', 31 | workingPath: TEST_DIR 32 | }); 33 | 34 | // Check that result has chunking stats 35 | assert.ok(result.chunkingStats, 'Result should contain chunkingStats'); 36 | assert.ok(typeof result.chunkingStats.totalNodes === 'number', 'totalNodes should be a number'); 37 | assert.ok(typeof result.chunkingStats.normalChunks === 'number', 'normalChunks should be a number'); 38 | assert.ok(typeof result.chunkingStats.subdivided === 'number', 'subdivided should be a number'); 39 | assert.ok(typeof result.chunkingStats.mergedSmall === 'number', 'mergedSmall should be a number'); 40 | assert.ok(typeof result.chunkingStats.statementFallback === 'number', 'statementFallback should be a number'); 41 | assert.ok(typeof result.chunkingStats.skippedSmall === 'number', 'skippedSmall should be a number'); 42 | 43 | console.log(` Total nodes analyzed: ${result.chunkingStats.totalNodes}`); 44 | console.log(` Normal chunks: ${result.chunkingStats.normalChunks}`); 45 | console.log(` Subdivided: ${result.chunkingStats.subdivided}`); 46 | console.log(` Merged small: ${result.chunkingStats.mergedSmall}`); 47 | console.log(` Final chunks: ${result.processedChunks}`); 48 | }); 49 | 50 | await t.test('should skip very small chunks', async () => { 51 | console.log(' Testing small chunk skipping...'); 52 | 53 | const result = await indexProject({ 54 | repoPath: '.', 55 | provider: 'transformers', 56 | workingPath: TEST_DIR 57 | }); 58 | 59 | // Most projects should have some small chunks that get skipped or merged 60 | const smallChunksHandled = result.chunkingStats.skippedSmall + result.chunkingStats.mergedSmall; 61 | console.log(` Small chunks handled (skipped + merged): ${smallChunksHandled}`); 62 | 63 | // The processed chunks should be less than total nodes analyzed 64 | assert.ok( 65 | result.processedChunks <= result.chunkingStats.totalNodes, 66 | 'Processed chunks should be <= total nodes' 67 | ); 68 | }); 69 | 70 | await t.test('should create merged chunks for small methods', async () => { 71 | console.log(' Testing small chunk merging...'); 72 | 73 | const result = await indexProject({ 74 | repoPath: '.', 75 | provider: 'transformers', 76 | workingPath: TEST_DIR 77 | }); 78 | 79 | console.log(` Merged chunks created: ${result.chunkingStats.mergedSmall}`); 80 | 81 | // Stats should be consistent 82 | const totalProcessed = result.chunkingStats.normalChunks + 83 | result.chunkingStats.mergedSmall + 84 | result.chunkingStats.statementFallback; 85 | 86 | console.log(` Total processed (normal + merged + fallback): ${totalProcessed}`); 87 | assert.ok(totalProcessed > 0, 'Should have processed some chunks'); 88 | }); 89 | 90 | await t.test('should successfully search after chunking', async () => { 91 | console.log(' Testing search functionality after chunking...'); 92 | 93 | // First index 94 | await indexProject({ 95 | repoPath: '.', 96 | provider: 'transformers', 97 | workingPath: TEST_DIR 98 | }); 99 | 100 | // Then search for something we know exists 101 | const searchResult = await searchCode('indexProject', 10, 'transformers', TEST_DIR); 102 | 103 | assert.ok(searchResult.success, 'Search should succeed'); 104 | assert.ok(searchResult.results.length > 0, 'Should find results'); 105 | console.log(` Found ${searchResult.results.length} results for "indexProject"`); 106 | }); 107 | 108 | // Clean up after tests 109 | if (fs.existsSync(TEST_DIR)) { 110 | fs.rmSync(TEST_DIR, { recursive: true, force: true }); 111 | } 112 | if (fs.existsSync(TEST_CODEMAP)) { 113 | fs.unlinkSync(TEST_CODEMAP); 114 | } 115 | }); 116 | 117 | console.log('✅ Chunking strategy tests completed'); 118 | -------------------------------------------------------------------------------- /test/test-database-errors.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Test para verificar el manejo correcto del error "database not found" 5 | * Este test verifica que cuando no existe la base de datos SQLite, 6 | * las funciones devuelvan errores claros en lugar de fallar con SQLITE_CANTOPEN 7 | */ 8 | 9 | import fs from 'fs'; 10 | 11 | console.log('🧪 Testing database error handling...\n'); 12 | 13 | let testsPassedCount = 0; 14 | let testsFailedCount = 0; 15 | 16 | function testPassed(name) { 17 | console.log(`✅ PASS: ${name}`); 18 | testsPassedCount++; 19 | } 20 | 21 | function testFailed(name, details) { 22 | console.log(`❌ FAIL: ${name}`); 23 | console.log(` Details: ${details}`); 24 | testsFailedCount++; 25 | } 26 | 27 | function testSkipped(name, reason) { 28 | console.log(`⏭️ SKIP: ${name}`); 29 | console.log(` Reason: ${reason}`); 30 | } 31 | 32 | // Try to import service functions, handle native dependency errors gracefully 33 | let getOverview, searchCode; 34 | try { 35 | const serviceModule = await import('../src/service.js'); 36 | getOverview = serviceModule.getOverview; 37 | searchCode = serviceModule.searchCode; 38 | } catch (error) { 39 | if (error.message.includes('bindings') || error.message.includes('sqlite3')) { 40 | console.log('⚠️ Native dependencies (sqlite3) not available in this environment'); 41 | console.log(' This is expected in some CI/CD environments'); 42 | console.log(' Skipping database error handling tests...\n'); 43 | 44 | testSkipped('getOverview handles missing database correctly', 'sqlite3 bindings not available'); 45 | testSkipped('searchCode handles missing database correctly', 'sqlite3 bindings not available'); 46 | testSkipped('searchCode with empty query handles missing database correctly', 'sqlite3 bindings not available'); 47 | 48 | console.log('\n📊 Test Summary:'); 49 | console.log(`✅ Tests passed: ${testsPassedCount}`); 50 | console.log(`❌ Tests failed: ${testsFailedCount}`); 51 | console.log(`⏭️ Tests skipped: 3 (due to environment limitations)`); 52 | console.log('\n🎉 All available tests completed successfully!'); 53 | process.exit(0); 54 | } else { 55 | console.error('❌ Unexpected error importing service module:', error.message); 56 | process.exit(1); 57 | } 58 | } 59 | 60 | // Create temporary directory for tests 61 | const testDir = '/tmp/pampa-test-no-db'; 62 | if (fs.existsSync(testDir)) { 63 | fs.rmSync(testDir, { recursive: true, force: true }); 64 | } 65 | fs.mkdirSync(testDir, { recursive: true }); 66 | 67 | // Test 1: getOverview sin base de datos 68 | console.log('📊 Test 1: getOverview should handle missing database gracefully'); 69 | try { 70 | const result = await getOverview(10, testDir); 71 | 72 | if (!result.success && result.error === 'database_not_found') { 73 | testPassed('getOverview handles missing database correctly'); 74 | console.log(` Message: ${result.message}`); 75 | console.log(` Suggestion: ${result.suggestion}`); 76 | } else { 77 | testFailed('getOverview error handling', `Expected database_not_found error, got: ${JSON.stringify(result)}`); 78 | } 79 | } catch (error) { 80 | testFailed('getOverview exception handling', `Unexpected exception: ${error.message}`); 81 | } 82 | 83 | console.log(''); 84 | 85 | // Test 2: searchCode sin base de datos 86 | console.log('🔍 Test 2: searchCode should handle missing database gracefully'); 87 | try { 88 | const result = await searchCode('test query', 10, 'auto', testDir); 89 | 90 | if (!result.success && result.error === 'database_not_found') { 91 | testPassed('searchCode handles missing database correctly'); 92 | console.log(` Message: ${result.message}`); 93 | console.log(` Suggestion: ${result.suggestion}`); 94 | } else { 95 | testFailed('searchCode error handling', `Expected database_not_found error, got: ${JSON.stringify(result)}`); 96 | } 97 | } catch (error) { 98 | testFailed('searchCode exception handling', `Unexpected exception: ${error.message}`); 99 | } 100 | 101 | console.log(''); 102 | 103 | // Test 3: searchCode con query vacía sin base de datos (debería usar getOverview) 104 | console.log('🔍 Test 3: searchCode with empty query should handle missing database gracefully'); 105 | try { 106 | const result = await searchCode('', 10, 'auto', testDir); 107 | 108 | if (!result.success && result.error === 'database_not_found') { 109 | testPassed('searchCode with empty query handles missing database correctly'); 110 | console.log(` Message: ${result.message}`); 111 | } else { 112 | testFailed('searchCode empty query error handling', `Expected database_not_found error, got: ${JSON.stringify(result)}`); 113 | } 114 | } catch (error) { 115 | testFailed('searchCode empty query exception handling', `Unexpected exception: ${error.message}`); 116 | } 117 | 118 | // Cleanup 119 | console.log('\n🧹 Cleaning up test directory...'); 120 | if (fs.existsSync(testDir)) { 121 | fs.rmSync(testDir, { recursive: true, force: true }); 122 | } 123 | 124 | // Summary 125 | console.log('\n📊 Test Summary:'); 126 | console.log(`✅ Tests passed: ${testsPassedCount}`); 127 | console.log(`❌ Tests failed: ${testsFailedCount}`); 128 | 129 | if (testsFailedCount > 0) { 130 | console.log('\n💥 Some tests failed!'); 131 | process.exit(1); 132 | } else { 133 | console.log('\n🎉 All database error handling tests passed!'); 134 | process.exit(0); 135 | } -------------------------------------------------------------------------------- /test/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "🧪 Running PAMPA test suite..." 4 | echo "" 5 | 6 | # Colors for output 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[1;33m' 10 | NC='\033[0m' # No Color 11 | 12 | # Test results 13 | TESTS_PASSED=0 14 | TESTS_FAILED=0 15 | 16 | # Determine the correct test directory 17 | if [ -f "test/pampa-diagnostics.js" ]; then 18 | TEST_DIR="test" 19 | elif [ -f "pampa-diagnostics.js" ]; then 20 | TEST_DIR="." 21 | else 22 | echo -e "${RED}❌ Error: Cannot find test files${NC}" 23 | exit 1 24 | fi 25 | 26 | echo "🔍 Test directory: $TEST_DIR" 27 | 28 | # Function to run a test 29 | run_test() { 30 | local test_file=$1 31 | local test_name=$2 32 | 33 | echo -e "${YELLOW}Running${NC} $test_name..." 34 | 35 | if node "$TEST_DIR/$test_file" > /dev/null 2>&1; then 36 | echo -e "${GREEN}✅ PASS${NC} $test_name" 37 | ((TESTS_PASSED++)) 38 | else 39 | echo -e "${RED}❌ FAIL${NC} $test_name" 40 | echo " Error details:" 41 | node "$TEST_DIR/$test_file" 2>&1 | head -5 | sed 's/^/ /' 42 | ((TESTS_FAILED++)) 43 | fi 44 | echo "" 45 | } 46 | 47 | # Run diagnostics first 48 | echo -e "${YELLOW}🔍 Running diagnostics...${NC}" 49 | node "$TEST_DIR/pampa-diagnostics.js" 50 | echo "" 51 | 52 | # Run MCP server test 53 | run_test "test-mcp.js" "MCP Server Basic Test" 54 | 55 | # Run search code test 56 | run_test "test-search-code.js" "Search Code Validation Test" 57 | 58 | # Run database error handling test 59 | run_test "test-database-errors.js" "Database Error Handling Test" 60 | 61 | echo -e "${YELLOW}Running Scoped Search Filters Test...${NC}" 62 | if TEST_OUTPUT=$(node --test "$TEST_DIR/search_scoped.test.js" 2>&1); then 63 | echo -e "${GREEN}✅ PASS${NC} Scoped Search Filters Test" 64 | ((TESTS_PASSED++)) 65 | else 66 | echo -e "${RED}❌ FAIL${NC} Scoped Search Filters Test" 67 | echo " Error details:" 68 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 69 | ((TESTS_FAILED++)) 70 | fi 71 | echo "" 72 | 73 | echo -e "${YELLOW}Running Hybrid Search Fusion Test...${NC}" 74 | if TEST_OUTPUT=$(node --test "$TEST_DIR/search_hybrid.test.js" 2>&1); then 75 | echo -e "${GREEN}✅ PASS${NC} Hybrid Search Fusion Test" 76 | ((TESTS_PASSED++)) 77 | else 78 | echo -e "${RED}❌ FAIL${NC} Hybrid Search Fusion Test" 79 | echo " Error details:" 80 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 81 | ((TESTS_FAILED++)) 82 | fi 83 | echo "" 84 | 85 | echo -e "${YELLOW}Running Cross-Encoder Reranker Test...${NC}" 86 | if TEST_OUTPUT=$(node --test "$TEST_DIR/reranker.test.js" 2>&1); then 87 | echo -e "${GREEN}✅ PASS${NC} Cross-Encoder Reranker Test" 88 | ((TESTS_PASSED++)) 89 | else 90 | echo -e "${RED}❌ FAIL${NC} Cross-Encoder Reranker Test" 91 | echo " Error details:" 92 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 93 | ((TESTS_FAILED++)) 94 | fi 95 | echo "" 96 | 97 | echo -e "${YELLOW}Running Symbol Boost Ranking Test...${NC}" 98 | if TEST_OUTPUT=$(node --test "$TEST_DIR/symbol_boost.test.js" 2>&1); then 99 | echo -e "${GREEN}✅ PASS${NC} Symbol Boost Ranking Test" 100 | ((TESTS_PASSED++)) 101 | else 102 | echo -e "${RED}❌ FAIL${NC} Symbol Boost Ranking Test" 103 | echo " Error details:" 104 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 105 | ((TESTS_FAILED++)) 106 | fi 107 | echo "" 108 | 109 | echo -e "${YELLOW}Running Watcher & Merkle Incremental Test...${NC}" 110 | if TEST_OUTPUT=$(node --test "$TEST_DIR/watch_merkle.test.js" 2>&1); then 111 | echo -e "${GREEN}✅ PASS${NC} Watcher & Merkle Incremental Test" 112 | ((TESTS_PASSED++)) 113 | else 114 | echo -e "${RED}❌ FAIL${NC} Watcher & Merkle Incremental Test" 115 | echo " Error details:" 116 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 117 | ((TESTS_FAILED++)) 118 | fi 119 | echo "" 120 | 121 | echo -e "${YELLOW}Running Context Packs Test...${NC}" 122 | if TEST_OUTPUT=$(node --test "$TEST_DIR/context_packs.test.js" 2>&1); then 123 | echo -e "${GREEN}✅ PASS${NC} Context Packs Test" 124 | ((TESTS_PASSED++)) 125 | else 126 | echo -e "${RED}❌ FAIL${NC} Context Packs Test" 127 | echo " Error details:" 128 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 129 | ((TESTS_FAILED++)) 130 | fi 131 | echo "" 132 | 133 | echo -e "${YELLOW}Running Codemap Extension Test...${NC}" 134 | if TEST_OUTPUT=$(node --test "$TEST_DIR/codemap_extension.test.js" 2>&1); then 135 | echo -e "${GREEN}✅ PASS${NC} Codemap Extension Test" 136 | ((TESTS_PASSED++)) 137 | else 138 | echo -e "${RED}❌ FAIL${NC} Codemap Extension Test" 139 | echo " Error details:" 140 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 141 | ((TESTS_FAILED++)) 142 | fi 143 | echo "" 144 | 145 | echo -e "${YELLOW}Running Chunk Encryption Test...${NC}" 146 | if TEST_OUTPUT=$(node --test "$TEST_DIR/encryption.test.js" 2>&1); then 147 | echo -e "${GREEN}✅ PASS${NC} Chunk Encryption Test" 148 | ((TESTS_PASSED++)) 149 | else 150 | echo -e "${RED}❌ FAIL${NC} Chunk Encryption Test" 151 | echo " Error details:" 152 | echo "$TEST_OUTPUT" | head -5 | sed 's/^/ /' 153 | ((TESTS_FAILED++)) 154 | fi 155 | echo "" 156 | 157 | # Summary 158 | echo "=========================================" 159 | echo -e "${GREEN}Tests passed: $TESTS_PASSED${NC}" 160 | echo -e "${RED}Tests failed: $TESTS_FAILED${NC}" 161 | 162 | if [ $TESTS_FAILED -eq 0 ]; then 163 | echo -e "${GREEN}🎉 All tests passed!${NC}" 164 | exit 0 165 | else 166 | echo -e "${RED}💥 Some tests failed!${NC}" 167 | exit 1 168 | fi -------------------------------------------------------------------------------- /test/test-search-code.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from 'child_process'; 4 | 5 | /** 6 | * Script para probar específicamente las herramientas MCP con diferentes casos 7 | */ 8 | 9 | const searchTestCases = [ 10 | { 11 | name: "Búsqueda válida", 12 | tool: "search_code", 13 | args: { query: "chat function" }, 14 | shouldPass: true 15 | }, 16 | { 17 | name: "Query vacío", 18 | tool: "search_code", 19 | args: { query: "" }, 20 | shouldPass: false 21 | }, 22 | { 23 | name: "Query con solo espacios", 24 | tool: "search_code", 25 | args: { query: " " }, 26 | shouldPass: false 27 | }, 28 | { 29 | name: "Query undefined", 30 | tool: "search_code", 31 | args: {}, 32 | shouldPass: false 33 | }, 34 | { 35 | name: "SHA válido (simulado)", 36 | tool: "get_code_chunk", 37 | args: { sha: "a1b2c3d4e5f6789" }, 38 | shouldPass: true // Aunque fallará porque no existe, debe pasar validación 39 | }, 40 | { 41 | name: "SHA vacío", 42 | tool: "get_code_chunk", 43 | args: { sha: "" }, 44 | shouldPass: false 45 | }, 46 | { 47 | name: "SHA undefined", 48 | tool: "get_code_chunk", 49 | args: {}, 50 | shouldPass: false 51 | }, 52 | { 53 | name: "SHA con espacios", 54 | tool: "get_code_chunk", 55 | args: { sha: " " }, 56 | shouldPass: false 57 | } 58 | ]; 59 | 60 | async function testMcpTools() { 61 | console.log('🧪 Testing herramientas MCP con diferentes casos...\n'); 62 | 63 | for (let i = 0; i < searchTestCases.length; i++) { 64 | const testCase = searchTestCases[i]; 65 | console.log(`📋 Test ${i + 1}: ${testCase.name} (${testCase.tool})`); 66 | 67 | const server = spawn('node', ['mcp-server.js'], { 68 | stdio: ['pipe', 'pipe', 'pipe'] 69 | }); 70 | 71 | let output = ''; 72 | let errorOutput = ''; 73 | 74 | server.stdout.on('data', (data) => { 75 | output += data.toString(); 76 | }); 77 | 78 | server.stderr.on('data', (data) => { 79 | errorOutput += data.toString(); 80 | }); 81 | 82 | // Inicialización 83 | const initMessage = { 84 | jsonrpc: "2.0", 85 | id: 1, 86 | method: "initialize", 87 | params: { 88 | protocolVersion: "2024-11-05", 89 | capabilities: { tools: {} }, 90 | clientInfo: { name: "test-client", version: "1.0.0" } 91 | } 92 | }; 93 | 94 | server.stdin.write(JSON.stringify(initMessage) + '\n'); 95 | await new Promise(resolve => setTimeout(resolve, 1000)); 96 | 97 | // Test de la herramienta 98 | const toolMessage = { 99 | jsonrpc: "2.0", 100 | id: 2, 101 | method: "tools/call", 102 | params: { 103 | name: testCase.tool, 104 | arguments: testCase.args 105 | } 106 | }; 107 | 108 | server.stdin.write(JSON.stringify(toolMessage) + '\n'); 109 | await new Promise(resolve => setTimeout(resolve, 2000)); 110 | 111 | server.kill(); 112 | 113 | // Analizar resultado 114 | const hasJsonError = errorOutput.includes('SyntaxError') && errorOutput.includes('JSON'); 115 | const hasInvalidArguments = output.includes('Invalid arguments'); 116 | const hasValidResponse = output.includes('"result":'); 117 | const hasEmojiError = errorOutput.includes('Unexpected token') && 118 | (errorOutput.includes('✅') || errorOutput.includes('❌') || 119 | errorOutput.includes('🔍') || errorOutput.includes('📊')); 120 | 121 | if (testCase.shouldPass) { 122 | if (hasValidResponse && !hasJsonError && !hasInvalidArguments && !hasEmojiError) { 123 | console.log(` ✅ PASÓ - Respuesta válida sin emojis`); 124 | } else { 125 | console.log(` ❌ FALLÓ - Expected válido pero got:`); 126 | if (hasJsonError) console.log(` - Error JSON detectado`); 127 | if (hasInvalidArguments) console.log(` - Argumentos inválidos`); 128 | if (hasEmojiError) console.log(` - Error de emoji en JSON`); 129 | if (!hasValidResponse) console.log(` - No hay respuesta válida`); 130 | } 131 | } else { 132 | if (hasInvalidArguments || (hasValidResponse && output.includes('ERROR:'))) { 133 | if (!hasEmojiError && !hasJsonError) { 134 | console.log(` ✅ PASÓ - Error manejado correctamente sin emojis`); 135 | } else { 136 | console.log(` ⚠️ PASÓ CON ADVERTENCIAS - Error manejado pero:`); 137 | if (hasEmojiError) console.log(` - Emojis detectados en stream`); 138 | if (hasJsonError) console.log(` - Errores JSON detectados`); 139 | } 140 | } else { 141 | console.log(` ❌ FALLÓ - Expected error pero got respuesta válida`); 142 | } 143 | } 144 | 145 | console.log(` Args: ${JSON.stringify(testCase.args)}`); 146 | 147 | // Verificar que no hay emojis en la respuesta 148 | if (output.includes('✅') || output.includes('❌') || output.includes('🔍') || 149 | output.includes('📊') || output.includes('💡') || output.includes('🔧')) { 150 | console.log(` ⚠️ ADVERTENCIA: Emojis detectados en respuesta JSON`); 151 | } 152 | 153 | console.log(''); 154 | } 155 | 156 | console.log('🎯 Test completado!'); 157 | } 158 | 159 | // Run test if called directly 160 | if (import.meta.url === `file://${process.argv[1]}`) { 161 | testMcpTools().catch(error => { 162 | console.error('❌ Test error:', error); 163 | process.exit(1); 164 | }); 165 | } -------------------------------------------------------------------------------- /examples/chat-app-go/README.md: -------------------------------------------------------------------------------- 1 | # PAMPA Chat - Ejemplo Go 2 | 3 | Un ejemplo completo de aplicación de chat en tiempo real construida con Go, Gin Framework y Gorilla WebSockets. 4 | 5 | ## 🏗️ Arquitectura del Proyecto 6 | 7 | Este proyecto demuestra cómo crear una aplicación de chat usando Go y tecnologías modernas: 8 | 9 | ### Backend (Go) 10 | 11 | ``` 12 | chat-app-go/ 13 | ├── main.go # Aplicación principal con Gin y WebSockets 14 | ├── go.mod # Módulo Go y dependencias 15 | ├── go.sum # Checksums de dependencias 16 | ├── static/ 17 | │ ├── index.html # Interfaz de usuario 18 | │ ├── styles.css # Estilos CSS 19 | │ └── chat.js # Lógica del cliente 20 | └── README.md # Esta documentación 21 | ``` 22 | 23 | ## 🚀 Características 24 | 25 | ### Funcionalidades de Chat 26 | 27 | - ✅ **Chat en tiempo real** con Gorilla WebSockets 28 | - ✅ **Múltiples salas** de chat (General, Go, Tecnología) 29 | - ✅ **Concurrencia** con goroutines y channels 30 | - ✅ **Historial de mensajes** por sala 31 | - ✅ **Avatares coloridos** generados automáticamente 32 | - ✅ **Notificaciones** de eventos en tiempo real 33 | 34 | ### Funcionalidades de Usuario 35 | 36 | - ✅ **Registro simple** con nombre de usuario 37 | - ✅ **Estados de conexión** visuales 38 | - ✅ **Manejo robusto** de conexiones WebSocket 39 | - ✅ **Logging estructurado** con Logrus 40 | 41 | ### Funcionalidades de Sala 42 | 43 | - ✅ **Salas predefinidas** (General, Go, Tecnología) 44 | - ✅ **Límites de usuarios** por sala 45 | - ✅ **Broadcast eficiente** a usuarios en sala 46 | - ✅ **Navegación** entre salas 47 | 48 | ## 📋 Comandos Disponibles 49 | 50 | El chat incluye comandos básicos: 51 | 52 | - Envío de mensajes en tiempo real 53 | - Notificaciones de usuarios conectándose/desconectándose 54 | - Historial de mensajes al unirse a una sala 55 | 56 | ## 🛠️ Instalación y Uso 57 | 58 | ### Prerrequisitos 59 | 60 | - Go 1.21+ 61 | - Conexión a internet para descargar dependencias 62 | 63 | ### Instalación 64 | 65 | 1. **Navega al directorio del ejemplo:** 66 | 67 | ```bash 68 | cd examples/chat-app-go 69 | ``` 70 | 71 | 2. **Descarga las dependencias:** 72 | 73 | ```bash 74 | go mod tidy 75 | ``` 76 | 77 | 3. **Inicia el servidor:** 78 | 79 | ```bash 80 | go run main.go 81 | ``` 82 | 83 | 4. **Abre tu navegador:** 84 | ``` 85 | http://localhost:8082 86 | ``` 87 | 88 | ### Scripts Disponibles 89 | 90 | ```bash 91 | go run main.go # Iniciar servidor de desarrollo 92 | go build # Compilar binario 93 | ./chat-app-go # Ejecutar binario compilado 94 | go mod tidy # Actualizar dependencias 95 | ``` 96 | 97 | ## 🔧 Configuración 98 | 99 | ### Puerto 100 | 101 | - **Servidor**: `http://localhost:8082` (Gin + WebSockets) 102 | 103 | ### Personalización 104 | 105 | #### Agregar Nuevas Salas 106 | 107 | Edita `main.go` en el método `initializeDefaultRooms()`: 108 | 109 | ```go 110 | Room{ 111 | ID: "mi-sala", 112 | Name: "Mi Sala", 113 | Description: "Descripción de mi sala", 114 | IsPublic: true, 115 | MaxUsers: 25, 116 | CreatedBy: "system", 117 | } 118 | ``` 119 | 120 | #### Modificar Comandos 121 | 122 | Agrega nuevos comandos en `main.go` en el método `handleCommand()`: 123 | 124 | ```go 125 | case "/micomando": 126 | response := SystemMessageResponse{ 127 | Type: MessageTypeSystemMsg, 128 | Content: "Respuesta del comando", 129 | Timestamp: time.Now(), 130 | } 131 | return s.sendToUser(connID, response) 132 | ``` 133 | 134 | ## 🏛️ Arquitectura Técnica 135 | 136 | ### Patrón de Diseño 137 | 138 | - **Concurrencia**: Goroutines para cada conexión WebSocket 139 | - **Channels**: Comunicación segura entre goroutines 140 | - **Mutex**: Protección de datos compartidos 141 | - **Struct-based**: Organización clara con tipos Go 142 | 143 | ### Flujo de Datos 144 | 145 | 1. **Cliente** se conecta via WebSocket 146 | 2. **Goroutine** maneja lectura/escritura por conexión 147 | 3. **ChatServer** coordina mensajes via channels 148 | 4. **Broadcast** eficiente a usuarios en sala 149 | 150 | ### Tecnologías Utilizadas 151 | 152 | - **Gin**: Framework web rápido y minimalista 153 | - **Gorilla WebSocket**: Implementación robusta de WebSockets 154 | - **UUID**: Generación de identificadores únicos 155 | - **Logrus**: Logging estructurado y configurable 156 | - **Go Modules**: Gestión moderna de dependencias 157 | 158 | ## 🧪 Testing 159 | 160 | Para probar el chat: 161 | 162 | 1. **Inicia el servidor** con `go run main.go` 163 | 2. **Abre múltiples pestañas** del navegador 164 | 3. **Regístrate con diferentes usuarios** 165 | 4. **Envía mensajes** y observa la sincronización 166 | 5. **Prueba desconexiones** y reconexiones 167 | 168 | ## 🔄 Extensiones Posibles 169 | 170 | Este ejemplo puede extenderse fácilmente: 171 | 172 | - **Base de datos**: PostgreSQL con GORM 173 | - **Autenticación**: JWT tokens con middleware 174 | - **Redis**: Cache y pub/sub para escalabilidad 175 | - **Tests**: Testing con `testing` package 176 | - **Docker**: Containerización multi-stage 177 | - **gRPC**: Comunicación entre servicios 178 | 179 | ## 📚 Dependencias Principales 180 | 181 | ### Producción 182 | 183 | - **gin-gonic/gin**: Framework web HTTP 184 | - **gorilla/websocket**: WebSocket implementation 185 | - **google/uuid**: UUID generation 186 | - **sirupsen/logrus**: Structured logging 187 | 188 | ### Características Go 189 | 190 | - **Goroutines**: Concurrencia ligera 191 | - **Channels**: Comunicación segura 192 | - **Interfaces**: Abstracciones limpias 193 | - **Structs**: Tipos de datos organizados 194 | 195 | ## 🤝 Contribuciones 196 | 197 | Este es un proyecto de ejemplo educativo. Siéntete libre de: 198 | 199 | - Reportar bugs o problemas 200 | - Sugerir mejoras 201 | - Crear forks y extensiones 202 | - Usar como base para tus proyectos 203 | 204 | ## 📄 Licencia 205 | 206 | Este proyecto es de código abierto y está disponible bajo la licencia MIT. 207 | 208 | --- 209 | 210 | **¡Disfruta chateando con Go! 🐹💬** 211 | -------------------------------------------------------------------------------- /src/ranking/boostSymbols.js: -------------------------------------------------------------------------------- 1 | const SIGNATURE_MATCH_BOOST = 0.3; 2 | const NEIGHBOR_MATCH_BOOST = 0.15; 3 | 4 | function escapeRegex(value) { 5 | return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 6 | } 7 | 8 | function buildQueryTokenRegex(token) { 9 | if (!token || token.length < 3) { 10 | return null; 11 | } 12 | 13 | const escaped = escapeRegex(token.toLowerCase()); 14 | return new RegExp(`\\b${escaped}[a-z0-9_]*\\b`, 'i'); 15 | } 16 | 17 | function splitSymbolWords(symbol) { 18 | if (!symbol) { 19 | return []; 20 | } 21 | 22 | const cleaned = symbol.replace(/[^A-Za-z0-9_]/g, ' '); 23 | return cleaned 24 | .replace(/([a-z])([A-Z])/g, '$1 $2') 25 | .split(/[\s_]+/) 26 | .map(word => word.trim().toLowerCase()) 27 | .filter(word => word.length > 0); 28 | } 29 | 30 | function computeSignatureMatchStrength(query, entry) { 31 | if (!entry) { 32 | return 0; 33 | } 34 | 35 | const queryLower = query.toLowerCase(); 36 | const rawSymbol = typeof entry.symbol === 'string' ? entry.symbol : ''; 37 | const symbol = rawSymbol.toLowerCase(); 38 | const signature = typeof entry.symbol_signature === 'string' ? entry.symbol_signature.toLowerCase() : ''; 39 | 40 | let weight = 0; 41 | const matchedTokens = new Set(); 42 | 43 | if (symbol && queryLower.includes(symbol)) { 44 | weight += 4; 45 | } 46 | 47 | if (signature) { 48 | const normalizedSignature = signature.replace(/\s+/g, ' '); 49 | if (queryLower.includes(normalizedSignature)) { 50 | weight = Math.max(weight, 3.5); 51 | } 52 | } 53 | 54 | const symbolTokens = splitSymbolWords(rawSymbol).map(token => token.toLowerCase()); 55 | let symbolTokenMatches = 0; 56 | for (const token of symbolTokens) { 57 | if (token.length < 3 || matchedTokens.has(token)) { 58 | continue; 59 | } 60 | 61 | const regex = buildQueryTokenRegex(token); 62 | if (regex && regex.test(query)) { 63 | matchedTokens.add(token); 64 | symbolTokenMatches += 1; 65 | } 66 | } 67 | 68 | if (symbolTokenMatches > 0) { 69 | weight += 1 + 0.5 * (symbolTokenMatches - 1); 70 | } 71 | 72 | let parameterMatches = 0; 73 | if (Array.isArray(entry.symbol_parameters)) { 74 | for (const param of entry.symbol_parameters) { 75 | if (typeof param !== 'string') { 76 | continue; 77 | } 78 | 79 | const parts = param 80 | .toLowerCase() 81 | .split(/[^a-z0-9]+/) 82 | .map(part => part.trim()) 83 | .filter(part => part.length >= 3); 84 | 85 | for (const part of parts) { 86 | if (matchedTokens.has(part)) { 87 | continue; 88 | } 89 | 90 | const regex = buildQueryTokenRegex(part); 91 | if (regex && regex.test(query)) { 92 | matchedTokens.add(part); 93 | parameterMatches += 1; 94 | break; 95 | } 96 | } 97 | } 98 | } 99 | 100 | if (parameterMatches > 0) { 101 | weight += 0.35 * parameterMatches; 102 | } 103 | 104 | if (weight <= 0) { 105 | return 0; 106 | } 107 | 108 | return Math.max(0, Math.min(weight / 4, 1)); 109 | } 110 | 111 | function buildShaIndex(codemap) { 112 | const index = new Map(); 113 | if (!codemap || typeof codemap !== 'object') { 114 | return index; 115 | } 116 | 117 | for (const [chunkId, entry] of Object.entries(codemap)) { 118 | if (!entry || typeof entry.sha !== 'string') { 119 | continue; 120 | } 121 | index.set(entry.sha, { chunkId, entry }); 122 | } 123 | 124 | return index; 125 | } 126 | 127 | export function applySymbolBoost(results, { query, codemap }) { 128 | if (!Array.isArray(results) || results.length === 0) { 129 | return; 130 | } 131 | 132 | if (!codemap || typeof codemap !== 'object') { 133 | return; 134 | } 135 | 136 | const shaIndex = buildShaIndex(codemap); 137 | 138 | for (const result of results) { 139 | const metadata = codemap[result.id]; 140 | if (!metadata) { 141 | continue; 142 | } 143 | 144 | let boost = 0; 145 | const sources = []; 146 | 147 | const matchStrength = computeSignatureMatchStrength(query, metadata); 148 | if (matchStrength > 0) { 149 | boost += SIGNATURE_MATCH_BOOST * matchStrength; 150 | sources.push('signature'); 151 | result.symbolMatchStrength = matchStrength; 152 | } 153 | 154 | const neighborShas = Array.isArray(metadata.symbol_neighbors) 155 | ? metadata.symbol_neighbors 156 | : []; 157 | 158 | let bestNeighborStrength = 0; 159 | if (neighborShas.length > 0) { 160 | for (const neighborSha of neighborShas) { 161 | const neighbor = shaIndex.get(neighborSha); 162 | if (!neighbor) { 163 | continue; 164 | } 165 | 166 | const neighborStrength = computeSignatureMatchStrength(query, neighbor.entry); 167 | if (neighborStrength > bestNeighborStrength) { 168 | bestNeighborStrength = neighborStrength; 169 | } 170 | } 171 | } 172 | 173 | if (bestNeighborStrength > 0) { 174 | boost += NEIGHBOR_MATCH_BOOST * bestNeighborStrength; 175 | sources.push('neighbor'); 176 | result.symbolNeighborStrength = bestNeighborStrength; 177 | } 178 | 179 | if (boost > 0) { 180 | const cappedBoost = Math.min(boost, 0.45); 181 | const baseScore = typeof result.score === 'number' ? result.score : 0; 182 | result.score = baseScore + cappedBoost; 183 | result.symbolBoost = cappedBoost; 184 | result.symbolBoostSources = sources; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /test/symbol_boost.test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { test, mock } from 'node:test'; 3 | import assert from 'node:assert/strict'; 4 | import os from 'node:os'; 5 | import path from 'node:path'; 6 | import fs from 'node:fs/promises'; 7 | import { __setTestProviderFactory, __resetTestProviderFactory } from '../src/providers.js'; 8 | 9 | let sqliteAvailable = true; 10 | let sqlite3Module; 11 | let indexProject; 12 | let searchCode; 13 | let clearBasePath; 14 | 15 | try { 16 | sqlite3Module = await import('sqlite3'); 17 | const serviceModule = await import('../src/service.js'); 18 | indexProject = serviceModule.indexProject; 19 | searchCode = serviceModule.searchCode; 20 | clearBasePath = serviceModule.clearBasePath; 21 | } catch (error) { 22 | if (error.message.includes('sqlite3') || error.message.includes('bindings')) { 23 | sqliteAvailable = false; 24 | } else { 25 | throw error; 26 | } 27 | } 28 | 29 | function embeddingForText(text) { 30 | const lower = String(text || '').toLowerCase(); 31 | if (lower.includes('where is token validated')) { 32 | return [0.8, 0.2]; 33 | } 34 | if (lower.includes('sanitizetoken')) { 35 | return [0.6, 0.4]; 36 | } 37 | if (lower.includes('validatetoken')) { 38 | return [0.4, 0.6]; 39 | } 40 | if (lower.includes('checksignature')) { 41 | return [0.35, 0.65]; 42 | } 43 | if (lower.includes('issuetoken')) { 44 | return [0.45, 0.55]; 45 | } 46 | return [0.5, 0.5]; 47 | } 48 | 49 | if (!sqliteAvailable) { 50 | test('symbol boost ranking (skipped)', { skip: 'sqlite3 bindings not available in this environment' }, () => {}); 51 | } else { 52 | const sqlite3 = sqlite3Module.default || sqlite3Module; 53 | 54 | test('symbol-aware boost promotes direct matches and graph neighbors', async () => { 55 | const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pampa-symbol-')); 56 | const srcDir = path.join(tmpDir, 'src', 'auth'); 57 | await fs.mkdir(srcDir, { recursive: true }); 58 | 59 | const tsFile = [ 60 | 'export function sanitizeToken(token: string) {', 61 | ' return token.trim();', 62 | '}', 63 | '', 64 | 'export function checkSignature(token: string) {', 65 | " return token.startsWith('valid:');", 66 | '}', 67 | '', 68 | 'export function validateToken(token: string) {', 69 | ' const sanitized = sanitizeToken(token);', 70 | ' return sanitized.length > 0 && checkSignature(sanitized);', 71 | '}', 72 | '', 73 | 'export function issueToken(payload: Record) {', 74 | " return JSON.stringify({ token: `valid:${payload.id}` });", 75 | '}' 76 | ].join('\n'); 77 | 78 | const phpFile = [ 79 | 'header('X-Token');", 87 | ' return validateToken($token);', 88 | ' }', 89 | '}', 90 | '' 91 | ].join('\n'); 92 | 93 | await fs.writeFile(path.join(srcDir, 'token.ts'), tsFile, 'utf8'); 94 | await fs.writeFile(path.join(srcDir, 'TokenMiddleware.php'), phpFile, 'utf8'); 95 | 96 | const providerStub = { 97 | init: async () => {}, 98 | generateEmbedding: mock.fn(async text => embeddingForText(text)), 99 | getDimensions: () => 2, 100 | getName: () => 'TestProvider' 101 | }; 102 | 103 | __setTestProviderFactory(() => providerStub); 104 | clearBasePath(); 105 | 106 | try { 107 | await indexProject({ repoPath: tmpDir, provider: 'auto' }); 108 | 109 | const codemapPath = path.join(tmpDir, 'pampa.codemap.json'); 110 | const codemapRaw = JSON.parse(await fs.readFile(codemapPath, 'utf8')); 111 | const codemapValues = Object.values(codemapRaw); 112 | 113 | const validateEntry = codemapValues.find(entry => entry && entry.symbol === 'validateToken'); 114 | assert.ok(validateEntry, 'validateToken entry should exist'); 115 | assert.ok(typeof validateEntry.symbol_signature === 'string' && validateEntry.symbol_signature.includes('validateToken')); 116 | assert.ok(Array.isArray(validateEntry.symbol_call_targets) && validateEntry.symbol_call_targets.length >= 1); 117 | 118 | const checkEntry = codemapValues.find(entry => entry && entry.symbol === 'checkSignature'); 119 | assert.ok(checkEntry, 'checkSignature entry should exist'); 120 | assert.ok(Array.isArray(checkEntry.symbol_callers) && checkEntry.symbol_callers.includes(validateEntry.sha)); 121 | 122 | const query = 'where is token validated'; 123 | const boosted = await searchCode(query, 3, 'auto', tmpDir, { reranker: 'off' }); 124 | const disabled = await searchCode(query, 3, 'auto', tmpDir, { reranker: 'off', symbol_boost: false }); 125 | 126 | assert.equal(boosted.success, true); 127 | assert.equal(boosted.results[0].meta.symbol, 'validateToken'); 128 | assert.ok(boosted.symbolBoost && boosted.symbolBoost.enabled); 129 | assert.ok(boosted.symbolBoost.boosted, 'symbol boost should have triggered'); 130 | assert.ok(boosted.results[0].meta.symbolBoost > 0); 131 | assert.ok(Array.isArray(boosted.results[0].meta.symbolBoostSources) && boosted.results[0].meta.symbolBoostSources.includes('signature')); 132 | 133 | assert.equal(disabled.success, true); 134 | assert.equal(disabled.symbolBoost.enabled, false); 135 | assert.equal(disabled.results[0].meta.symbol, 'sanitizeToken'); 136 | } finally { 137 | __resetTestProviderFactory(); 138 | clearBasePath(); 139 | await fs.rm(tmpDir, { recursive: true, force: true }); 140 | } 141 | }); 142 | } 143 | --------------------------------------------------------------------------------