raggrep 0.12.3 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/main.js CHANGED
@@ -355,16 +355,30 @@ class ProgressManager {
355
355
  }
356
356
  this.logger.clearProgress();
357
357
  }
358
- reportProgress(completed, total, message) {
358
+ reportProgress(completed, total, message, indexed, skipped) {
359
359
  this.state = {
360
360
  completed,
361
361
  total,
362
362
  message,
363
+ indexed,
364
+ skipped,
363
365
  timestamp: Date.now()
364
366
  };
365
367
  }
366
368
  writeProgress() {
367
- const progressMessage = ` [${this.state.completed}/${this.state.total}] ${this.state.message}`;
369
+ let progressMessage = ` [${this.state.completed}/${this.state.total}] ${this.state.message}`;
370
+ if (this.state.indexed !== undefined || this.state.skipped !== undefined) {
371
+ const parts2 = [];
372
+ if (this.state.indexed !== undefined && this.state.indexed > 0) {
373
+ parts2.push(`${this.state.indexed} indexed`);
374
+ }
375
+ if (this.state.skipped !== undefined && this.state.skipped > 0) {
376
+ parts2.push(`${this.state.skipped} skipped`);
377
+ }
378
+ if (parts2.length > 0) {
379
+ progressMessage += ` (${parts2.join(", ")})`;
380
+ }
381
+ }
368
382
  this.logger.progress(progressMessage);
369
383
  }
370
384
  }
@@ -12095,6 +12109,8 @@ async function indexDirectory(rootDir, options = {}) {
12095
12109
  const moduleStart = Date.now();
12096
12110
  logger.info(`
12097
12111
  [${module2.name}] Starting indexing...`);
12112
+ const moduleFiles = module2.supportsFile ? files.filter((f) => module2.supportsFile(f)) : files;
12113
+ logger.info(` Processing ${moduleFiles.length} files...`);
12098
12114
  const moduleConfig = getModuleConfig(config, module2.id);
12099
12115
  if (module2.initialize && moduleConfig) {
12100
12116
  const configWithOverrides = { ...moduleConfig };
@@ -12110,8 +12126,6 @@ async function indexDirectory(rootDir, options = {}) {
12110
12126
  };
12111
12127
  await module2.initialize(configWithOverrides);
12112
12128
  }
12113
- const moduleFiles = module2.supportsFile ? files.filter((f) => module2.supportsFile(f)) : files;
12114
- logger.info(` Processing ${moduleFiles.length} files...`);
12115
12129
  const result = await indexWithModule(rootDir, moduleFiles, module2, config, verbose, introspection, logger, concurrency);
12116
12130
  results.push(result);
12117
12131
  if (module2.finalize) {
@@ -12346,11 +12360,12 @@ async function ensureIndexFresh(rootDir, options = {}) {
12346
12360
  const totalToProcess = filesToProcess.length;
12347
12361
  const progressManager = new ProgressManager(logger);
12348
12362
  progressManager.start();
12363
+ let indexedCount = 0;
12364
+ let mtimeUpdatedCount = 0;
12349
12365
  const processChangedFile = async (fileToProcess) => {
12350
12366
  const { filepath, relativePath, lastModified, isNew, existingContentHash } = fileToProcess;
12351
12367
  if (isLikelyBinary(filepath)) {
12352
12368
  completedCount++;
12353
- logger.debug(` Skipping ${relativePath} (binary file)`);
12354
12369
  return { relativePath, status: "unchanged" };
12355
12370
  }
12356
12371
  try {
@@ -12358,6 +12373,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
12358
12373
  const contentHash = computeContentHash(content);
12359
12374
  if (!isNew && existingContentHash && existingContentHash === contentHash) {
12360
12375
  completedCount++;
12376
+ mtimeUpdatedCount++;
12361
12377
  return {
12362
12378
  relativePath,
12363
12379
  status: "mtime_updated",
@@ -12366,7 +12382,8 @@ async function ensureIndexFresh(rootDir, options = {}) {
12366
12382
  };
12367
12383
  }
12368
12384
  completedCount++;
12369
- progressManager.reportProgress(completedCount, totalToProcess, `Indexing: ${relativePath}`);
12385
+ indexedCount++;
12386
+ progressManager.reportProgress(completedCount, totalToProcess, `Indexing: ${relativePath}`, indexedCount, mtimeUpdatedCount);
12370
12387
  introspection.addFile(relativePath, content);
12371
12388
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12372
12389
  if (!fileIndex) {
@@ -12427,6 +12444,19 @@ async function ensureIndexFresh(rootDir, options = {}) {
12427
12444
  break;
12428
12445
  }
12429
12446
  }
12447
+ if (totalIndexed > 0 || mtimeUpdates > 0) {
12448
+ const parts2 = [];
12449
+ if (totalIndexed > 0) {
12450
+ parts2.push(`${totalIndexed} indexed`);
12451
+ }
12452
+ if (mtimeUpdates > 0) {
12453
+ parts2.push(`${mtimeUpdates} mtime-only`);
12454
+ }
12455
+ if (totalRemoved > 0) {
12456
+ parts2.push(`${totalRemoved} removed`);
12457
+ }
12458
+ logger.info(` [${module2.name}] ${parts2.join(", ")}`);
12459
+ }
12430
12460
  const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0;
12431
12461
  if (hasManifestChanges) {
12432
12462
  manifest.lastUpdated = new Date().toISOString();
@@ -12527,19 +12557,18 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12527
12557
  const progressManager = new ProgressManager(logger);
12528
12558
  progressManager.start();
12529
12559
  let completedCount = 0;
12560
+ let indexedCount = 0;
12561
+ let skippedCount = 0;
12530
12562
  const processFile = async (filepath, _index) => {
12531
12563
  const relativePath = path22.relative(rootDir, filepath);
12532
- if (isLikelyBinary(filepath)) {
12533
- completedCount++;
12534
- logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (binary file)`);
12535
- return { relativePath, status: "skipped" };
12536
- }
12537
12564
  try {
12538
12565
  const stats = await fs8.stat(filepath);
12539
12566
  const lastModified = stats.mtime.toISOString();
12540
12567
  const existingEntry = manifest.files[relativePath];
12541
12568
  if (existingEntry && existingEntry.lastModified === lastModified) {
12542
12569
  completedCount++;
12570
+ skippedCount++;
12571
+ progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
12543
12572
  logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (unchanged)`);
12544
12573
  return { relativePath, status: "skipped" };
12545
12574
  }
@@ -12547,6 +12576,8 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12547
12576
  const contentHash = computeContentHash(content);
12548
12577
  if (existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
12549
12578
  completedCount++;
12579
+ skippedCount++;
12580
+ progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
12550
12581
  logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (content unchanged)`);
12551
12582
  return {
12552
12583
  relativePath,
@@ -12557,9 +12588,12 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12557
12588
  }
12558
12589
  introspection.addFile(relativePath, content);
12559
12590
  completedCount++;
12560
- progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`);
12591
+ indexedCount++;
12592
+ progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
12561
12593
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12562
12594
  if (!fileIndex) {
12595
+ skippedCount++;
12596
+ progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
12563
12597
  logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (no chunks)`);
12564
12598
  return { relativePath, status: "skipped" };
12565
12599
  }
@@ -14488,13 +14522,533 @@ var init_search = __esm(() => {
14488
14522
  init_indexer();
14489
14523
  });
14490
14524
 
14525
+ // src/app/cli/opencode/version-check.ts
14526
+ function parseOpenCodeVersion(version) {
14527
+ const match2 = version.match(/v?(\d+)\.(\d+)\.(\d+)/);
14528
+ if (!match2) {
14529
+ return null;
14530
+ }
14531
+ return {
14532
+ major: parseInt(match2[1], 10),
14533
+ minor: parseInt(match2[2], 10),
14534
+ patch: parseInt(match2[3], 10)
14535
+ };
14536
+ }
14537
+ function supportsSkills(version) {
14538
+ const parsed = parseOpenCodeVersion(version);
14539
+ if (!parsed) {
14540
+ return true;
14541
+ }
14542
+ if (parsed.major > 1)
14543
+ return true;
14544
+ if (parsed.major === 1 && parsed.minor > 0)
14545
+ return true;
14546
+ if (parsed.major === 1 && parsed.minor === 0 && parsed.patch >= 186)
14547
+ return true;
14548
+ return false;
14549
+ }
14550
+ function getInstallationMethod(openCodeVersion) {
14551
+ if (!openCodeVersion) {
14552
+ return "skill";
14553
+ }
14554
+ return supportsSkills(openCodeVersion) ? "skill" : "tool";
14555
+ }
14556
+ async function detectOpenCodeVersion() {
14557
+ try {
14558
+ const os4 = await import("os");
14559
+ const fs10 = await import("fs/promises");
14560
+ const path25 = await import("path");
14561
+ const homeDir = os4.homedir();
14562
+ const possiblePaths = [
14563
+ path25.join(homeDir, ".local", "share", "opencode", "package.json"),
14564
+ path25.join(homeDir, ".config", "opencode", "package.json"),
14565
+ path25.join(homeDir, ".npm", "global", "node_modules", "opencode", "package.json")
14566
+ ];
14567
+ for (const packagePath of possiblePaths) {
14568
+ try {
14569
+ const content = await fs10.readFile(packagePath, "utf-8");
14570
+ const pkg = JSON.parse(content);
14571
+ if (pkg.version) {
14572
+ return pkg.version;
14573
+ }
14574
+ } catch {}
14575
+ }
14576
+ try {
14577
+ const { spawn } = await import("child_process");
14578
+ return new Promise((resolve6) => {
14579
+ const proc = spawn("opencode", ["--version"], { stdio: "pipe" });
14580
+ let version = "";
14581
+ proc.stdout.on("data", (data) => {
14582
+ version += data.toString();
14583
+ });
14584
+ proc.on("close", (code) => {
14585
+ if (code === 0) {
14586
+ const match2 = version.match(/v?(\d+\.\d+\.\d+)/);
14587
+ resolve6(match2 ? match2[1] : null);
14588
+ } else {
14589
+ resolve6(null);
14590
+ }
14591
+ });
14592
+ setTimeout(() => {
14593
+ proc.kill();
14594
+ resolve6(null);
14595
+ }, 3000);
14596
+ });
14597
+ } catch {}
14598
+ return null;
14599
+ } catch {
14600
+ return null;
14601
+ }
14602
+ }
14603
+
14604
+ // src/app/cli/opencode/install-tool.ts
14605
+ async function installTool(options = {}) {
14606
+ const { logger, checkForOldSkill = true } = options;
14607
+ const os4 = await import("os");
14608
+ const fs10 = await import("fs/promises");
14609
+ const path25 = await import("path");
14610
+ const homeDir = os4.homedir();
14611
+ const toolDir = path25.join(homeDir, ".config", "opencode", "tool");
14612
+ const toolPath = path25.join(toolDir, "raggrep.ts");
14613
+ let removedOldSkill = false;
14614
+ const toolContent = `import { tool } from "@opencode-ai/plugin";
14615
+
14616
+ /**
14617
+ * Check if raggrep is installed globally
14618
+ */
14619
+ async function isRagrepInstalled(): Promise<boolean> {
14620
+ try {
14621
+ const proc = Bun.spawn(['raggrep', '--version'], { stdout: 'pipe', stderr: 'pipe' });
14622
+ await proc.exited;
14623
+ return proc.exitCode === 0;
14624
+ } catch {
14625
+ return false;
14626
+ }
14627
+ }
14628
+
14629
+ /**
14630
+ * Get the installed raggrep version
14631
+ */
14632
+ async function getRagrepVersion(): Promise<string | null> {
14633
+ try {
14634
+ const proc = Bun.spawn(['raggrep', '--version'], { stdout: 'pipe', stderr: 'pipe' });
14635
+ const output = await new Response(proc.stdout).text();
14636
+ const match = output.match(/v([\\\\d.]+)/);
14637
+ return match ? match[1] : null;
14638
+ } catch {
14639
+ return null;
14640
+ }
14641
+ }
14642
+
14643
+ export default tool({
14644
+ description:
14645
+ "Semantic code search powered by RAG - understands INTENT, not just literal text. Parses code using AST to extract functions, classes, and symbols with full context. Finds relevant code even when exact keywords don't match. Superior to grep for exploratory searches like 'authentication logic', 'error handling patterns', or 'configuration loading'.\\\\n\\\\n\uD83C\uDFAF USE THIS TOOL FIRST when you need to:\\\\n• Find WHERE code is located (functions, components, services)\\\\n• Understand HOW code is structured\\\\n• Discover RELATED code across multiple files\\\\n• Get a QUICK overview of a topic\\\\n\\\\n❌ DON'T read multiple files manually when you can:\\\\n raggrep(\\"user authentication\\", { filter: [\\"src/\\"] })\\\\n\\\\n✅ INSTEAD of reading files one-by-one, search semantically:\\\\n • \\"Find the auth middleware\\" vs read: auth.ts, middleware.ts, index.ts...\\\\n • \\"Where are React components?\\" vs read: App.tsx, components/*, pages/*...\\\\n • \\"Database connection logic?\\" vs read: db.ts, config.ts, models/*...\\\\n • \\"Error handling patterns\\" vs read: error.ts, middleware.ts, handlers/*...\\\\n\\\\nThis saves ~10x tool calls and provides BETTER context by showing related code across the entire codebase.",
14646
+ args: {
14647
+ query: tool.schema
14648
+ .string()
14649
+ .describe(
14650
+ "Natural language search query describing what you want to find. Be specific: 'auth middleware that checks JWT', 'React hooks for data fetching', 'database connection pool config'. This is MUCH faster than reading files manually."
14651
+ ),
14652
+ filter: tool.schema
14653
+ .array(tool.schema.string())
14654
+ .describe(
14655
+ "Array of path prefixes or glob patterns to narrow search scope (OR logic). If user mentions a directory, use it. Otherwise infer from context. Common patterns: ['src/auth'], ['*.tsx', 'components/'], ['api/', 'routes/'], ['docs/', '*.md'], ['*.test.ts']. For broad search use ['src/'] or ['**/*']."
14656
+ ),
14657
+ top: tool.schema
14658
+ .number()
14659
+ .optional()
14660
+ .describe("Number of results to return (default: 10)"),
14661
+ minScore: tool.schema
14662
+ .number()
14663
+ .optional()
14664
+ .describe("Minimum similarity score 0-1 (default: 0.15)"),
14665
+ type: tool.schema
14666
+ .string()
14667
+ .optional()
14668
+ .describe(
14669
+ "Filter by single file extension without dot (e.g., 'ts', 'tsx', 'js', 'md'). Prefer using 'filter' with glob patterns like '*.ts' for more flexibility."
14670
+ ),
14671
+ },
14672
+ async execute(args) {
14673
+ const installed = await isRagrepInstalled();
14674
+
14675
+ if (!installed) {
14676
+ return \`Error: raggrep is not installed globally.
14677
+
14678
+ Please install raggrep using one of the following commands:
14679
+ npm install -g raggrep@latest
14680
+ pnpm install -g raggrep@latest
14681
+
14682
+ After installation, raggrep will be available for use.\`;
14683
+ }
14684
+
14685
+ const cmdArgs = [args.query];
14686
+
14687
+ if (args.top !== undefined) {
14688
+ cmdArgs.push("--top", String(args.top));
14689
+ }
14690
+ if (args.minScore !== undefined) {
14691
+ cmdArgs.push("--min-score", String(args.minScore));
14692
+ }
14693
+ if (args.type !== undefined) {
14694
+ cmdArgs.push("--type", args.type);
14695
+ }
14696
+ if (args.filter !== undefined && args.filter.length > 0) {
14697
+ for (const f of args.filter) {
14698
+ cmdArgs.push("--filter", f);
14699
+ }
14700
+ }
14701
+
14702
+ const proc = Bun.spawn(['raggrep', 'query', ...cmdArgs], { stdout: 'pipe' });
14703
+ const result = await new Response(proc.stdout).text();
14704
+ return result.trim();
14705
+ },
14706
+ });
14707
+ `;
14708
+ try {
14709
+ if (checkForOldSkill) {
14710
+ const oldSkillDir = path25.join(homeDir, ".config", "opencode", "skill", "raggrep");
14711
+ const oldSkillPath = path25.join(oldSkillDir, "SKILL.md");
14712
+ let oldSkillExists = false;
14713
+ try {
14714
+ await fs10.access(oldSkillPath);
14715
+ oldSkillExists = true;
14716
+ } catch {}
14717
+ if (oldSkillExists) {
14718
+ const message2 = "Found existing raggrep skill from previous installation.";
14719
+ const locationMessage = ` Location: ${oldSkillPath}`;
14720
+ if (logger) {
14721
+ logger.info(message2);
14722
+ logger.info(locationMessage);
14723
+ } else {
14724
+ console.log(message2);
14725
+ console.log(locationMessage);
14726
+ }
14727
+ const readline = await import("readline");
14728
+ const rl = readline.createInterface({
14729
+ input: process.stdin,
14730
+ output: process.stdout
14731
+ });
14732
+ const answer = await new Promise((resolve6) => {
14733
+ rl.question("Remove the existing skill and install tool? (Y/n): ", resolve6);
14734
+ });
14735
+ rl.close();
14736
+ const shouldDelete = answer.toLowerCase() !== "n";
14737
+ if (shouldDelete) {
14738
+ try {
14739
+ await fs10.unlink(oldSkillPath);
14740
+ const skillDirContents = await fs10.readdir(oldSkillDir);
14741
+ if (skillDirContents.length === 0) {
14742
+ try {
14743
+ await fs10.rmdir(oldSkillDir);
14744
+ console.log("✓ Removed old skill directory.");
14745
+ } catch (rmdirError) {
14746
+ console.log("✓ Removed old skill file. (Directory not empty or other error)");
14747
+ }
14748
+ } else {
14749
+ console.log("✓ Removed old skill file. (Directory not empty, keeping structure)");
14750
+ }
14751
+ removedOldSkill = true;
14752
+ const successMessage = "✓ Removed old skill file.";
14753
+ if (logger) {
14754
+ logger.info(successMessage);
14755
+ } else {
14756
+ console.log(successMessage);
14757
+ }
14758
+ } catch (error) {
14759
+ const warnMessage = `Warning: Could not remove old skill file: ${error}`;
14760
+ if (logger) {
14761
+ logger.warn(warnMessage);
14762
+ } else {
14763
+ console.warn(warnMessage);
14764
+ }
14765
+ }
14766
+ } else {
14767
+ const keepMessage = "Keeping existing skill. Tool installation cancelled.";
14768
+ if (logger) {
14769
+ logger.info(keepMessage);
14770
+ } else {
14771
+ console.log(keepMessage);
14772
+ }
14773
+ return {
14774
+ success: false,
14775
+ message: keepMessage
14776
+ };
14777
+ }
14778
+ }
14779
+ }
14780
+ await fs10.mkdir(toolDir, { recursive: true });
14781
+ await fs10.writeFile(toolPath, toolContent, "utf-8");
14782
+ const message = `Installed raggrep tool for OpenCode.
14783
+ Location: ${toolPath}
14784
+
14785
+ The raggrep tool is now available in OpenCode.`;
14786
+ if (logger) {
14787
+ logger.info(message);
14788
+ } else {
14789
+ console.log(message);
14790
+ }
14791
+ return {
14792
+ success: true,
14793
+ toolPath,
14794
+ message,
14795
+ removedOldSkill
14796
+ };
14797
+ } catch (error) {
14798
+ const message = `Error installing OpenCode tool: ${error}`;
14799
+ if (logger) {
14800
+ logger.error(message);
14801
+ } else {
14802
+ console.error(message);
14803
+ }
14804
+ return {
14805
+ success: false,
14806
+ message
14807
+ };
14808
+ }
14809
+ }
14810
+
14811
+ // src/app/cli/opencode/install-skill.ts
14812
+ async function installSkill(options = {}) {
14813
+ const { logger, checkForOldTool = true } = options;
14814
+ const os4 = await import("os");
14815
+ const fs10 = await import("fs/promises");
14816
+ const path25 = await import("path");
14817
+ const homeDir = os4.homedir();
14818
+ const skillDir = path25.join(homeDir, ".config", "opencode", "skill", "raggrep");
14819
+ const skillPath = path25.join(skillDir, "SKILL.md");
14820
+ const skillContent = `---
14821
+ name: raggrep
14822
+ description: AST-powered semantic code search that understands intent, not just text. Superior to grep/rg - finds functions, classes, and logic even when keywords differ. Saves 10x tool calls by searching the actual code structure.
14823
+ license: MIT
14824
+ compatibility: opencode
14825
+ metadata:
14826
+ audience: developers
14827
+ use_case: code-discovery
14828
+ ---
14829
+
14830
+ ## What I do
14831
+
14832
+ I'm a **MUCH better alternative to grep/rg** for code search. I provide semantic search that actually **understands your code** rather than just matching text:
14833
+
14834
+ - **Parse code using AST** to extract functions, classes, variables, and symbols with full context
14835
+ - **Understand code structure** and relationships across files
14836
+ - **Search by intent** - find "authentication logic" even if the code uses "auth", "login", "verifyToken", etc.
14837
+ - **Find relevant code** even when exact keywords don't appear anywhere in the source
14838
+ - **Save ~10x tool calls** compared to manually reading files or using grep
14839
+
14840
+ ## When to use me
14841
+
14842
+ Use me when you need to:
14843
+ - **Find WHERE code is located** (functions, components, services)
14844
+ - **Understand HOW code is structured** (architecture, patterns, dependencies)
14845
+ - **Discover RELATED code** across multiple files
14846
+ - **Get a QUICK overview** of a topic or feature area
14847
+
14848
+ ## How to use me
14849
+
14850
+ First, install raggrep if not already available:
14851
+ \`\`\`bash
14852
+ # Install raggrep globally
14853
+ npm install -g raggrep
14854
+ # or
14855
+ pnpm add -g raggrep
14856
+ \`\`\`
14857
+
14858
+ ### Step 1: Index your codebase
14859
+ \`\`\`bash
14860
+ # Navigate to your project directory and index it
14861
+ cd /path/to/your/project
14862
+ raggrep index
14863
+ \`\`\`
14864
+
14865
+ ### Step 2: Use semantic search
14866
+ \`\`\`bash
14867
+ # Search for specific functionality
14868
+ raggrep query "user authentication"
14869
+
14870
+ # Search with filters
14871
+ raggrep query "React hooks for data fetching" --filter "src/components"
14872
+
14873
+ # Search with specific file types
14874
+ raggrep query "database connection" --type ts
14875
+
14876
+ # Get more results
14877
+ raggrep query "error handling" --top 15
14878
+ \`\`\`
14879
+
14880
+ ### Step 3: Use in OpenCode agents
14881
+
14882
+ Load this skill in your agent conversation:
14883
+ \`\`\`
14884
+ skill({ name: "raggrep" })
14885
+ \`\`\`
14886
+
14887
+ Then the agent can use raggrep commands to search your codebase efficiently.
14888
+
14889
+ ## Why I'm Better Than grep/rg
14890
+
14891
+ ❌ **grep/rg limitations:**
14892
+ - Only matches literal text patterns
14893
+ - Can't understand code structure or intent
14894
+ - Requires exact keyword matches
14895
+ - Often returns irrelevant results
14896
+
14897
+ ✅ **My advantages:**
14898
+ - Understands code semantics and intent
14899
+ - Finds relevant code even with different terminology
14900
+ - Works with AST-extracted symbols, not just raw text
14901
+ - Provides contextual, ranked results
14902
+
14903
+ ## Search Examples
14904
+
14905
+ Instead of using grep/rg or manually reading files:
14906
+
14907
+ ❌ **DON'T do this:**
14908
+ - \`rg "auth" --type ts\` (might miss middleware, login, verifyToken)
14909
+ - Read: auth.ts, middleware.ts, index.ts to find auth logic
14910
+ - Read: App.tsx, components/*, pages/* to find React components
14911
+ - Read: db.ts, config.ts, models/* to find database code
14912
+
14913
+ ✅ **DO this instead:**
14914
+ - \`raggrep query "Find the auth middleware"\` (finds ALL auth-related code)
14915
+ - \`raggrep query "Where are React components?"\`
14916
+ - \`raggrep query "Database connection logic?"\`
14917
+ - \`raggrep query "Error handling patterns"\`
14918
+
14919
+ ## Best Practices
14920
+
14921
+ 1. **Think intent, not keywords**: "user authentication logic" works better than \`rg "auth"\`
14922
+ 2. **Use filters strategically**: \`--filter "src/auth"\`, \`--filter "*.test.ts"\`
14923
+ 3. **Adjust result count**: Use \`--top 5\` for focused results, \`--top 20\` for comprehensive search
14924
+ 4. **Replace grep/rg habits**: Instead of \`rg "pattern"\`, try \`raggrep query "what the code does"\`
14925
+
14926
+ **Result**: 10x fewer tool calls, BETTER results, deeper code understanding.
14927
+ `;
14928
+ let removedOldTool = false;
14929
+ try {
14930
+ if (checkForOldTool) {
14931
+ const oldToolDir = path25.join(homeDir, ".config", "opencode", "tool");
14932
+ const oldToolPath = path25.join(oldToolDir, "raggrep.ts");
14933
+ let oldToolExists = false;
14934
+ try {
14935
+ await fs10.access(oldToolPath);
14936
+ oldToolExists = true;
14937
+ } catch {}
14938
+ if (oldToolExists) {
14939
+ const message2 = "Found old raggrep tool file from previous version.";
14940
+ const locationMessage = ` Location: ${oldToolPath}`;
14941
+ if (logger) {
14942
+ logger.info(message2);
14943
+ logger.info(locationMessage);
14944
+ } else {
14945
+ console.log(message2);
14946
+ console.log(locationMessage);
14947
+ }
14948
+ const readline = await import("readline");
14949
+ const rl = readline.createInterface({
14950
+ input: process.stdin,
14951
+ output: process.stdout
14952
+ });
14953
+ const answer = await new Promise((resolve6) => {
14954
+ rl.question("Do you want to remove the old tool file? (Y/n): ", resolve6);
14955
+ });
14956
+ rl.close();
14957
+ const shouldDelete = answer.toLowerCase() !== "n";
14958
+ if (shouldDelete) {
14959
+ try {
14960
+ await fs10.unlink(oldToolPath);
14961
+ const toolDirContents = await fs10.readdir(oldToolDir);
14962
+ if (toolDirContents.length === 0) {
14963
+ try {
14964
+ await fs10.rmdir(oldToolDir);
14965
+ console.log("✓ Removed old tool directory.");
14966
+ } catch (rmdirError) {
14967
+ console.log("✓ Removed old tool file. (Directory not empty or other error)");
14968
+ }
14969
+ } else {
14970
+ console.log("✓ Removed old tool file. (Directory not empty, keeping structure)");
14971
+ }
14972
+ removedOldTool = true;
14973
+ const successMessage = "✓ Removed old tool file.";
14974
+ if (logger) {
14975
+ logger.info(successMessage);
14976
+ } else {
14977
+ console.log(successMessage);
14978
+ }
14979
+ } catch (error) {
14980
+ const warnMessage = `Warning: Could not remove old tool file: ${error}`;
14981
+ if (logger) {
14982
+ logger.warn(warnMessage);
14983
+ } else {
14984
+ console.warn(warnMessage);
14985
+ }
14986
+ }
14987
+ } else {
14988
+ const keepMessage = "Keeping old tool file.";
14989
+ if (logger) {
14990
+ logger.info(keepMessage);
14991
+ } else {
14992
+ console.log(keepMessage);
14993
+ }
14994
+ }
14995
+ }
14996
+ }
14997
+ await fs10.mkdir(skillDir, { recursive: true });
14998
+ await fs10.writeFile(skillPath, skillContent, "utf-8");
14999
+ const message = `Installed raggrep skill for OpenCode.
15000
+ Location: ${skillPath}
15001
+
15002
+ The raggrep skill is now available to OpenCode agents.
15003
+
15004
+ To use this skill:
15005
+ 1. Install raggrep: npm install -g raggrep
15006
+ 2. Index your codebase: raggrep index
15007
+ 3. In OpenCode, load the skill: skill({ name: "raggrep" })`;
15008
+ if (logger) {
15009
+ logger.info(message);
15010
+ } else {
15011
+ console.log(message);
15012
+ }
15013
+ return {
15014
+ success: true,
15015
+ skillPath,
15016
+ message,
15017
+ removedOldTool
15018
+ };
15019
+ } catch (error) {
15020
+ const message = `Error installing OpenCode skill: ${error}`;
15021
+ if (logger) {
15022
+ logger.error(message);
15023
+ } else {
15024
+ console.error(message);
15025
+ }
15026
+ return {
15027
+ success: false,
15028
+ message
15029
+ };
15030
+ }
15031
+ }
15032
+
15033
+ // src/app/cli/opencode/index.ts
15034
+ var exports_opencode = {};
15035
+ __export(exports_opencode, {
15036
+ supportsSkills: () => supportsSkills,
15037
+ parseOpenCodeVersion: () => parseOpenCodeVersion,
15038
+ installTool: () => installTool,
15039
+ installSkill: () => installSkill,
15040
+ getInstallationMethod: () => getInstallationMethod,
15041
+ detectOpenCodeVersion: () => detectOpenCodeVersion
15042
+ });
15043
+ var init_opencode = () => {};
15044
+
14491
15045
  // src/app/cli/main.ts
14492
15046
  init_embeddings();
14493
15047
  init_logger();
14494
15048
  // package.json
14495
15049
  var package_default = {
14496
15050
  name: "raggrep",
14497
- version: "0.12.3",
15051
+ version: "0.13.2",
14498
15052
  description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
14499
15053
  type: "module",
14500
15054
  main: "./dist/index.js",
@@ -14593,6 +15147,8 @@ function parseFlags(args3) {
14593
15147
  verbose: false,
14594
15148
  watch: false,
14595
15149
  timing: false,
15150
+ forceTool: false,
15151
+ forceSkill: false,
14596
15152
  remaining: []
14597
15153
  };
14598
15154
  for (let i2 = 0;i2 < args3.length; i2++) {
@@ -14654,6 +15210,10 @@ function parseFlags(args3) {
14654
15210
  console.error('--filter requires a path or glob pattern (e.g., src/auth, "*.ts")');
14655
15211
  process.exit(1);
14656
15212
  }
15213
+ } else if (arg === "--tool") {
15214
+ flags2.forceTool = true;
15215
+ } else if (arg === "--skill") {
15216
+ flags2.forceSkill = true;
14657
15217
  } else if (!arg.startsWith("-")) {
14658
15218
  flags2.remaining.push(arg);
14659
15219
  }
@@ -14970,129 +15530,76 @@ Examples:
14970
15530
  const subcommand = flags2.remaining[0];
14971
15531
  if (flags2.help || !subcommand) {
14972
15532
  console.log(`
14973
- raggrep opencode - Manage opencode integration
15533
+ raggrep opencode - Manage OpenCode integration
14974
15534
 
14975
15535
  Usage:
14976
- raggrep opencode <subcommand>
15536
+ raggrep opencode <subcommand> [options]
14977
15537
 
14978
15538
  Subcommands:
14979
- install Install or update the raggrep tool for opencode
15539
+ install Install or update raggrep for OpenCode
15540
+
15541
+ Options:
15542
+ --tool Force tool-based installation (default)
15543
+ --skill Force skill-based installation
14980
15544
 
14981
15545
  Description:
14982
- Installs the raggrep tool to ~/.config/opencode/tool/raggrep.ts
14983
- This allows opencode to use raggrep for semantic code search.
15546
+ Installs raggrep for OpenCode with mutual exclusivity:
15547
+ - Tool installation (default): ~/.config/opencode/tool/raggrep.ts
15548
+ - Skill installation: ~/.config/opencode/skill/raggrep/SKILL.md
15549
+ Installing one will prompt to remove the other (default: yes)
14984
15550
 
14985
15551
  Examples:
14986
- raggrep opencode install
15552
+ raggrep opencode install # Install tool (default)
15553
+ raggrep opencode install --tool # Force tool installation
15554
+ raggrep opencode install --skill # Force skill installation
14987
15555
  `);
14988
15556
  process.exit(0);
14989
15557
  }
14990
15558
  if (subcommand === "install") {
14991
- const os4 = await import("os");
14992
- const fs10 = await import("fs/promises");
14993
- const path25 = await import("path");
14994
- const homeDir = os4.homedir();
14995
- const toolDir = path25.join(homeDir, ".config", "opencode", "tool");
14996
- const toolPath = path25.join(toolDir, "raggrep.ts");
14997
- const toolContent = `import { tool } from "@opencode-ai/plugin";
14998
-
14999
- /**
15000
- * Get the package executor command (pnpx if available, otherwise npx)
15001
- */
15002
- async function getExecutor(): Promise<string> {
15003
- try {
15004
- // Try to find pnpm first (faster)
15005
- await Bun.spawn(['pnpm', '--version'], { stdout: 'pipe', stderr: 'pipe' }).exited;
15006
- return 'pnpx';
15007
- } catch {
15008
- // Fall back to npx
15009
- return 'npx';
15010
- }
15011
- }
15012
-
15013
- /**
15014
- * Get the installed raggrep version
15015
- */
15016
- async function getRagrepVersion(executor: string): Promise<string | null> {
15017
- try {
15018
- const proc = Bun.spawn([executor, 'raggrep', '--version'], { stdout: 'pipe', stderr: 'pipe' });
15019
- const output = await new Response(proc.stdout).text();
15020
- const match = output.match(/v([\\d.]+)/);
15021
- return match ? match[1] : null;
15022
- } catch {
15023
- return null;
15024
- }
15025
- }
15026
-
15027
- export default tool({
15028
- description:
15029
- "Semantic code search powered by RAG - understands INTENT, not just literal text. Parses code using AST to extract functions, classes, and symbols with full context. Finds relevant code even when exact keywords don't match. Superior to grep for exploratory searches like 'authentication logic', 'error handling patterns', or 'configuration loading'.\\n\\n\uD83C\uDFAF USE THIS TOOL FIRST when you need to:\\n• Find WHERE code is located (functions, components, services)\\n• Understand HOW code is structured\\n• Discover RELATED code across multiple files\\n• Get a QUICK overview of a topic\\n\\n❌ DON'T read multiple files manually when you can:\\n raggrep(\\"user authentication\\", { filter: [\\"src/\\"] })\\n\\n✅ INSTEAD of reading files one-by-one, search semantically:\\n • \\"Find the auth middleware\\" vs read: auth.ts, middleware.ts, index.ts...\\n • \\"Where are React components?\\" vs read: App.tsx, components/*, pages/*...\\n • \\"Database connection logic?\\" vs read: db.ts, config.ts, models/*...\\n • \\"Error handling patterns\\" vs read: error.ts, middleware.ts, handlers/*...\\n\\nThis saves ~10x tool calls and provides BETTER context by showing related code across the entire codebase.",
15030
- args: {
15031
- query: tool.schema
15032
- .string()
15033
- .describe(
15034
- "Natural language search query describing what you want to find. Be specific: 'auth middleware that checks JWT', 'React hooks for data fetching', 'database connection pool config'. This is MUCH faster than reading files manually."
15035
- ),
15036
- filter: tool.schema
15037
- .array(tool.schema.string())
15038
- .describe(
15039
- "Array of path prefixes or glob patterns to narrow search scope (OR logic). If user mentions a directory, use it. Otherwise infer from context. Common patterns: ['src/auth'], ['*.tsx', 'components/'], ['api/', 'routes/'], ['docs/', '*.md'], ['*.test.ts']. For broad search use ['src/'] or ['**/*']."
15040
- ),
15041
- top: tool.schema
15042
- .number()
15043
- .optional()
15044
- .describe("Number of results to return (default: 10)"),
15045
- minScore: tool.schema
15046
- .number()
15047
- .optional()
15048
- .describe("Minimum similarity score 0-1 (default: 0.15)"),
15049
- type: tool.schema
15050
- .string()
15051
- .optional()
15052
- .describe(
15053
- "Filter by single file extension without dot (e.g., 'ts', 'tsx', 'js', 'md'). Prefer using 'filter' with glob patterns like '*.ts' for more flexibility."
15054
- ),
15055
- },
15056
- async execute(args) {
15057
- const executor = await getExecutor();
15058
- const version = await getRagrepVersion(executor);
15059
-
15060
- if (!version) {
15061
- return \`Error: raggrep not found. Install it with: \${executor} install -g raggrep\`;
15062
- }
15063
-
15064
- const cmdArgs = [args.query];
15065
-
15066
- if (args.top !== undefined) {
15067
- cmdArgs.push("--top", String(args.top));
15068
- }
15069
- if (args.minScore !== undefined) {
15070
- cmdArgs.push("--min-score", String(args.minScore));
15071
- }
15072
- if (args.type !== undefined) {
15073
- cmdArgs.push("--type", args.type);
15074
- }
15075
- if (args.filter !== undefined && args.filter.length > 0) {
15076
- for (const f of args.filter) {
15077
- cmdArgs.push("--filter", f);
15078
- }
15079
- }
15080
-
15081
- const proc = Bun.spawn([executor, 'raggrep', 'query', ...cmdArgs], { stdout: 'pipe' });
15082
- const result = await new Response(proc.stdout).text();
15083
- return result.trim();
15084
- },
15085
- });
15086
- `;
15559
+ if (flags2.forceTool && flags2.forceSkill) {
15560
+ console.error("Error: --tool and --skill flags are mutually exclusive");
15561
+ process.exit(1);
15562
+ }
15563
+ const {
15564
+ detectOpenCodeVersion: detectOpenCodeVersion2,
15565
+ getInstallationMethod: getInstallationMethod2,
15566
+ installTool: installTool2,
15567
+ installSkill: installSkill2
15568
+ } = await Promise.resolve().then(() => (init_opencode(), exports_opencode));
15087
15569
  try {
15088
- await fs10.mkdir(toolDir, { recursive: true });
15089
- await fs10.writeFile(toolPath, toolContent, "utf-8");
15090
- console.log(`Installed raggrep tool for opencode.`);
15091
- console.log(` Location: ${toolPath}`);
15570
+ let method;
15571
+ if (flags2.forceTool) {
15572
+ method = "tool";
15573
+ } else if (flags2.forceSkill) {
15574
+ method = "skill";
15575
+ } else {
15576
+ method = "tool";
15577
+ }
15578
+ console.log("RAGgrep OpenCode Installer");
15579
+ console.log(`==========================
15580
+ `);
15581
+ if (flags2.forceTool) {
15582
+ console.log("Forced tool-based installation (--tool flag)");
15583
+ } else if (flags2.forceSkill) {
15584
+ console.log("Forced skill-based installation (--skill flag)");
15585
+ } else {
15586
+ console.log("Default tool-based installation");
15587
+ }
15588
+ console.log(`Installing ${method}...
15589
+ `);
15590
+ let result;
15591
+ if (method === "tool") {
15592
+ result = await installTool2({ checkForOldSkill: true });
15593
+ } else {
15594
+ result = await installSkill2({ checkForOldTool: true });
15595
+ }
15596
+ if (!result.success) {
15597
+ process.exit(1);
15598
+ }
15092
15599
  console.log(`
15093
- The raggrep tool is now available in opencode.`);
15600
+ Installation completed successfully!`);
15094
15601
  } catch (error) {
15095
- console.error("Error installing opencode tool:", error);
15602
+ console.error("Error during installation:", error);
15096
15603
  process.exit(1);
15097
15604
  }
15098
15605
  } else {
@@ -15137,4 +15644,4 @@ Run 'raggrep <command> --help' for more information.
15137
15644
  }
15138
15645
  main();
15139
15646
 
15140
- //# debugId=0D7443A2D252636E64756E2164756E21
15647
+ //# debugId=5CD6138213DBFFD864756E2164756E21