abapgit-agent 1.6.0 → 1.7.0

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/bin/abapgit-agent CHANGED
@@ -538,12 +538,14 @@ async function processInspectResult(res) {
538
538
  const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
539
539
  const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
540
540
  const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
541
+ const infos = res.INFOS !== undefined ? res.INFOS : (res.infos || []);
541
542
 
542
- if (errorCount > 0 || warnings.length > 0) {
543
+ if (errorCount > 0 || warnings.length > 0 || infos.length > 0) {
543
544
  if (errorCount > 0) {
544
545
  console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
545
546
  } else {
546
- console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
547
+ const total = warnings.length + infos.length;
548
+ console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${total}):`);
547
549
  }
548
550
  console.log('\nErrors:');
549
551
  console.log('─'.repeat(60));
@@ -552,8 +554,16 @@ async function processInspectResult(res) {
552
554
  const line = err.LINE || err.line || '?';
553
555
  const column = err.COLUMN || err.column || '?';
554
556
  const text = err.TEXT || err.text || 'Unknown error';
557
+ const methodName = err.METHOD_NAME || err.method_name;
558
+ const sobjname = err.SOBJNAME || err.sobjname;
555
559
 
560
+ if (methodName) {
561
+ console.log(` Method: ${methodName}`);
562
+ }
556
563
  console.log(` Line ${line}, Column ${column}:`);
564
+ if (sobjname && sobjname.includes('====')) {
565
+ console.log(` Include: ${sobjname}`);
566
+ }
557
567
  console.log(` ${text}`);
558
568
  console.log('');
559
569
  }
@@ -565,7 +575,38 @@ async function processInspectResult(res) {
565
575
  for (const warn of warnings) {
566
576
  const line = warn.LINE || warn.line || '?';
567
577
  const text = warn.MESSAGE || warn.message || 'Unknown warning';
568
- console.log(` Line ${line}: ${text}`);
578
+ const methodName = warn.METHOD_NAME || warn.method_name;
579
+ const sobjname = warn.SOBJNAME || warn.sobjname;
580
+
581
+ if (methodName) {
582
+ console.log(` Method: ${methodName}`);
583
+ }
584
+ console.log(` Line ${line}:`);
585
+ if (sobjname && sobjname.includes('====')) {
586
+ console.log(` Include: ${sobjname}`);
587
+ }
588
+ console.log(` ${text}`);
589
+ }
590
+ }
591
+
592
+ // Show infos if any
593
+ if (infos.length > 0) {
594
+ console.log('Info:');
595
+ console.log('─'.repeat(60));
596
+ for (const info of infos) {
597
+ const line = info.LINE || info.line || '?';
598
+ const text = info.MESSAGE || info.message || 'Unknown info';
599
+ const methodName = info.METHOD_NAME || info.method_name;
600
+ const sobjname = info.SOBJNAME || info.sobjname;
601
+
602
+ if (methodName) {
603
+ console.log(` Method: ${methodName}`);
604
+ }
605
+ console.log(` Line ${line}:`);
606
+ if (sobjname && sobjname.includes('====')) {
607
+ console.log(` Include: ${sobjname}`);
608
+ }
609
+ console.log(` ${text}`);
569
610
  }
570
611
  }
571
612
  } else if (success === true || success === 'X') {
@@ -652,7 +693,7 @@ async function runUnitTests(options) {
652
693
  /**
653
694
  * Run unit test for a single file
654
695
  */
655
- async function runUnitTestForFile(sourceFile, csrfToken, config) {
696
+ async function runUnitTestForFile(sourceFile, csrfToken, config, coverage = false) {
656
697
  console.log(` Running unit test for: ${sourceFile}`);
657
698
 
658
699
  try {
@@ -687,7 +728,8 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
687
728
 
688
729
  // Send files array to unit endpoint (ABAP expects string_table of file names)
689
730
  const data = {
690
- files: [sourceFile]
731
+ files: [sourceFile],
732
+ coverage: coverage
691
733
  };
692
734
 
693
735
  const result = await request('POST', '/sap/bc/z_abapgit_agent/unit', data, { csrfToken });
@@ -700,6 +742,9 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
700
742
  const message = result.MESSAGE || result.message || '';
701
743
  const errors = result.ERRORS || result.errors || [];
702
744
 
745
+ // Handle coverage data
746
+ const coverageStats = result.COVERAGE_STATS || result.coverage_stats;
747
+
703
748
  if (testCount === 0) {
704
749
  console.log(` ➖ ${objName} - No unit tests`);
705
750
  } else if (success === 'X' || success === true) {
@@ -710,6 +755,17 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
710
755
 
711
756
  console.log(` Tests: ${testCount} | Passed: ${passedCount} | Failed: ${failedCount}`);
712
757
 
758
+ // Display coverage if available
759
+ if (coverage && coverageStats) {
760
+ const totalLines = coverageStats.TOTAL_LINES || coverageStats.total_lines || 0;
761
+ const coveredLines = coverageStats.COVERED_LINES || coverageStats.covered_lines || 0;
762
+ const coverageRate = coverageStats.COVERAGE_RATE || coverageStats.coverage_rate || 0;
763
+
764
+ console.log(` 📊 Coverage: ${coverageRate}%`);
765
+ console.log(` Total Lines: ${totalLines}`);
766
+ console.log(` Covered Lines: ${coveredLines}`);
767
+ }
768
+
713
769
  if (failedCount > 0 && errors.length > 0) {
714
770
  for (const err of errors) {
715
771
  const className = err.CLASS_NAME || err.class_name || '?';
@@ -1727,21 +1783,25 @@ Examples:
1727
1783
  const filesArgIndex = args.indexOf('--files');
1728
1784
  if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
1729
1785
  console.error('Error: --files parameter required');
1730
- console.error('Usage: abapgit-agent unit --files <file1>,<file2>,...');
1786
+ console.error('Usage: abapgit-agent unit --files <file1>,<file2>,... [--coverage]');
1731
1787
  console.error('Example: abapgit-agent unit --files zcl_my_test.clas.abap');
1788
+ console.error('Example: abapgit-agent unit --files zcl_my_test.clas.abap --coverage');
1732
1789
  process.exit(1);
1733
1790
  }
1734
1791
 
1735
1792
  const files = args[filesArgIndex + 1].split(',').map(f => f.trim());
1736
1793
 
1737
- console.log(`\n Running unit tests for ${files.length} file(s)`);
1794
+ // Check for coverage option
1795
+ const coverage = args.includes('--coverage');
1796
+
1797
+ console.log(`\n Running unit tests for ${files.length} file(s)${coverage ? ' (with coverage)' : ''}`);
1738
1798
  console.log('');
1739
1799
 
1740
1800
  const config = loadConfig();
1741
1801
  const csrfToken = await fetchCsrfToken(config);
1742
1802
 
1743
1803
  for (const sourceFile of files) {
1744
- await runUnitTestForFile(sourceFile, csrfToken, config);
1804
+ await runUnitTestForFile(sourceFile, csrfToken, config, coverage);
1745
1805
  }
1746
1806
  break;
1747
1807
  }
@@ -2007,9 +2067,9 @@ Examples:
2007
2067
  console.log(` ${description}`);
2008
2068
  }
2009
2069
 
2010
- // Display source code for classes, interfaces, and CDS views
2070
+ // Display source code for classes, interfaces, CDS views, and programs/source includes
2011
2071
  const source = obj.SOURCE || obj.source || '';
2012
- if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
2072
+ if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program')) {
2013
2073
  console.log('');
2014
2074
  // Replace escaped newlines with actual newlines and display
2015
2075
  const displaySource = source.replace(/\\n/g, '\n');
@@ -2379,6 +2439,105 @@ Examples:
2379
2439
  break;
2380
2440
  }
2381
2441
 
2442
+ case 'where': {
2443
+ const objectsArgIndex = args.indexOf('--objects');
2444
+ if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
2445
+ console.error('Error: --objects parameter required');
2446
+ console.error('Usage: abapgit-agent where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]');
2447
+ console.error('Example: abapgit-agent where --objects ZCL_MY_CLASS');
2448
+ console.error('Example: abapgit-agent where --objects ZIF_MY_INTERFACE');
2449
+ console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20');
2450
+ process.exit(1);
2451
+ }
2452
+
2453
+ const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
2454
+ const typeArg = args.indexOf('--type');
2455
+ const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
2456
+ const limitArg = args.indexOf('--limit');
2457
+ const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 100;
2458
+ const jsonOutput = args.includes('--json');
2459
+
2460
+ console.log(`\n Where-used list for ${objects.length} object(s)`);
2461
+
2462
+ const config = loadConfig();
2463
+ const csrfToken = await fetchCsrfToken(config);
2464
+
2465
+ const data = {
2466
+ objects: objects,
2467
+ limit: Math.min(Math.max(1, limit), 500)
2468
+ };
2469
+
2470
+ if (type) {
2471
+ data.type = type;
2472
+ }
2473
+
2474
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/where', data, { csrfToken });
2475
+
2476
+ // Handle uppercase keys from ABAP
2477
+ const success = result.SUCCESS || result.success;
2478
+ const whereObjects = result.OBJECTS || result.objects || [];
2479
+ const message = result.MESSAGE || result.message || '';
2480
+ const error = result.ERROR || result.error;
2481
+
2482
+ if (!success || error) {
2483
+ console.error(`\n Error: ${error || 'Failed to get where-used list'}`);
2484
+ break;
2485
+ }
2486
+
2487
+ if (jsonOutput) {
2488
+ console.log(JSON.stringify(result, null, 2));
2489
+ } else {
2490
+ console.log(`\n ${message}`);
2491
+ console.log('');
2492
+
2493
+ for (let i = 0; i < whereObjects.length; i++) {
2494
+ const obj = whereObjects[i];
2495
+ const objName = obj.NAME || obj.name || `Object ${i + 1}`;
2496
+ const objType = obj.TYPE || obj.type || '';
2497
+ const error = obj.ERROR || obj.error || '';
2498
+ const references = obj.REFERENCES || obj.references || [];
2499
+ const count = obj.COUNT || obj.count || 0;
2500
+
2501
+ // Handle object not found error
2502
+ if (error) {
2503
+ console.log(` ❌ ${objName} (${objType})`);
2504
+ console.log(` ${error}`);
2505
+ console.log('');
2506
+ continue;
2507
+ }
2508
+
2509
+ if (count === 0) {
2510
+ console.log(` ❌ ${objName} (${objType})`);
2511
+ console.log(` No references found`);
2512
+ console.log('');
2513
+ continue;
2514
+ }
2515
+
2516
+ console.log(` 🔍 ${objName} (${objType})`);
2517
+ console.log(` Found ${count} reference(s):`);
2518
+ console.log('');
2519
+
2520
+ // Display references - one line format: include → method (type) or include (type)
2521
+ for (let j = 0; j < references.length; j++) {
2522
+ const ref = references[j];
2523
+ const includeName = ref.INCLUDE_NAME || ref.include_name || '';
2524
+ const includeType = ref.INCLUDE_TYPE || ref.include_type || '';
2525
+ const methodName = ref.METHOD_NAME || ref.method_name || '';
2526
+
2527
+ let line;
2528
+ if (methodName) {
2529
+ line = ` ${j + 1}. ${includeName} → ${methodName} (${includeType})`;
2530
+ } else {
2531
+ line = ` ${j + 1}. ${includeName} (${includeType})`;
2532
+ }
2533
+ console.log(line);
2534
+ }
2535
+ console.log('');
2536
+ }
2537
+ }
2538
+ break;
2539
+ }
2540
+
2382
2541
  case 'ref': {
2383
2542
  const refSearch = require('../src/ref-search');
2384
2543
  const topicIndex = args.indexOf('--topic');
@@ -2452,32 +2611,10 @@ Examples:
2452
2611
  const pattern = args[patternIndex];
2453
2612
  const result = await refSearch.searchPattern(pattern);
2454
2613
 
2455
- // Also search guidelines if available
2456
- const guidelinesResult = await refSearch.searchGuidelines(pattern);
2457
- if (guidelinesResult && guidelinesResult.guidelinesFound && guidelinesResult.matches.length > 0) {
2458
- result.guidelines = guidelinesResult;
2459
- }
2460
-
2461
2614
  if (jsonOutput) {
2462
2615
  console.log(JSON.stringify(result, null, 2));
2463
2616
  } else {
2464
2617
  refSearch.displaySearchResults(result);
2465
- // Display guidelines results if found
2466
- if (guidelinesResult && guidelinesResult.guidelinesFound && guidelinesResult.matches.length > 0) {
2467
- console.log('\n 📋 Custom Guidelines:');
2468
- for (const match of guidelinesResult.matches.slice(0, 5)) {
2469
- console.log(` 📄 ${match.file} (line ${match.line}):`);
2470
- const lines = match.context.split('\n');
2471
- lines.forEach((line, idx) => {
2472
- const prefix = idx === 1 ? ' → ' : ' ';
2473
- const trimmed = line.slice(0, 80);
2474
- console.log(`${prefix}${trimmed}`);
2475
- });
2476
- }
2477
- if (guidelinesResult.matches.length > 5) {
2478
- console.log(` ... and ${guidelinesResult.matches.length - 5} more matches`);
2479
- }
2480
- }
2481
2618
  }
2482
2619
  break;
2483
2620
  }
@@ -2526,6 +2663,9 @@ Commands:
2526
2663
  view --objects <obj1>,<obj2>,... [--type <type>] [--json]
2527
2664
  View ABAP object definitions from the ABAP system
2528
2665
 
2666
+ where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]
2667
+ Find where-used list for ABAP objects (classes, interfaces, programs)
2668
+
2529
2669
  ref <pattern> [--json]
2530
2670
  Search ABAP reference repositories for patterns. Requires referenceFolder in .abapGitAgent.
2531
2671
 
@@ -2572,6 +2712,10 @@ Examples:
2572
2712
  abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
2573
2713
  abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
2574
2714
  abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
2715
+ abapgit-agent where --objects ZCL_SUT_AUNIT_RUNNER # Find where class is used
2716
+ abapgit-agent where --objects ZIF_MY_INTERFACE # Find interface implementations
2717
+ abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20 # Limit results
2718
+ abapgit-agent where --objects ZIF_MY_INTERFACE --json # JSON output
2575
2719
  abapgit-agent ref "CORRESPONDING" # Search all reference repos
2576
2720
  abapgit-agent ref "CX_SY_" # Search exceptions
2577
2721
  abapgit-agent ref --topic exceptions # View exception topic
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,6 +16,10 @@
16
16
  "start": "node src/server.js",
17
17
  "dev": "nodemon src/server.js",
18
18
  "test": "jest",
19
+ "test:all": "node scripts/test-all.js",
20
+ "test:jest": "jest",
21
+ "test:aunit": "node scripts/test-all.js --jest --cmd",
22
+ "test:cmd": "node scripts/test-all.js --jest --aunit",
19
23
  "pull": "node bin/abapgit-agent",
20
24
  "release": "node scripts/release.js",
21
25
  "unrelease": "node scripts/unrelease.js"
@@ -459,6 +459,52 @@ class ABAPClient {
459
459
 
460
460
  return await this.request('POST', '/list', data, { csrfToken: this.csrfToken });
461
461
  }
462
+
463
+ /**
464
+ * View ABAP object definitions
465
+ * @param {Array} objects - Array of object names to view
466
+ * @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
467
+ * @returns {object} View result with object definitions
468
+ */
469
+ async view(objects, type = null) {
470
+ await this.fetchCsrfToken();
471
+
472
+ const data = {
473
+ objects: objects
474
+ };
475
+
476
+ if (type) {
477
+ data.type = type;
478
+ }
479
+
480
+ logger.info('Viewing objects', { objects, type, service: 'abapgit-agent' });
481
+
482
+ return await this.request('POST', '/view', data, { csrfToken: this.csrfToken });
483
+ }
484
+
485
+ /**
486
+ * Find where-used list for ABAP objects
487
+ * @param {Array} objects - Array of object names to search
488
+ * @param {string} type - Object type (optional)
489
+ * @param {number} limit - Maximum results (default: 100, max: 500)
490
+ * @returns {object} Where-used result with found objects
491
+ */
492
+ async where(objects, type = null, limit = 100) {
493
+ await this.fetchCsrfToken();
494
+
495
+ const data = {
496
+ objects: objects,
497
+ limit: Math.min(Math.max(1, limit), 500)
498
+ };
499
+
500
+ if (type) {
501
+ data.type = type;
502
+ }
503
+
504
+ logger.info('Finding where-used', { objects, type, limit: data.limit, service: 'abapgit-agent' });
505
+
506
+ return await this.request('POST', '/where', data, { csrfToken: this.csrfToken });
507
+ }
462
508
  }
463
509
 
464
510
  // Singleton instance
package/src/agent.js CHANGED
@@ -264,6 +264,54 @@ class ABAPGitAgent {
264
264
  throw new Error(`List command failed: ${error.message}`);
265
265
  }
266
266
  }
267
+
268
+ /**
269
+ * View ABAP object definitions
270
+ * @param {Array} objects - Array of object names to view
271
+ * @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
272
+ * @returns {object} View result with object definitions
273
+ */
274
+ async view(objects, type = null) {
275
+ logger.info('Viewing objects', { objects, type });
276
+
277
+ try {
278
+ const result = await this.abap.view(objects, type);
279
+ return {
280
+ success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
281
+ command: result.COMMAND || result.command || 'VIEW',
282
+ objects: result.OBJECTS || result.objects || [],
283
+ error: result.ERROR || result.error || null
284
+ };
285
+ } catch (error) {
286
+ logger.error('View command failed', { error: error.message });
287
+ throw new Error(`View command failed: ${error.message}`);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Find where-used list for ABAP objects
293
+ * @param {Array} objects - Array of object names to search
294
+ * @param {string} type - Object type (optional)
295
+ * @param {number} limit - Maximum results (default: 100, max: 500)
296
+ * @returns {object} Where-used result with found objects
297
+ */
298
+ async where(objects, type = null, limit = 100) {
299
+ logger.info('Finding where-used', { objects, type, limit });
300
+
301
+ try {
302
+ const result = await this.abap.where(objects, type, limit);
303
+ return {
304
+ success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
305
+ command: result.COMMAND || result.command || 'WHERE',
306
+ objects: result.OBJECTS || result.objects || [],
307
+ message: result.MESSAGE || result.message || '',
308
+ error: result.ERROR || result.error || null
309
+ };
310
+ } catch (error) {
311
+ logger.error('Where command failed', { error: error.message });
312
+ throw new Error(`Where command failed: ${error.message}`);
313
+ }
314
+ }
267
315
  }
268
316
 
269
317
  module.exports = {
package/src/config.js CHANGED
@@ -24,7 +24,7 @@ function loadConfig() {
24
24
  if (fs.existsSync(configPath)) {
25
25
  config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
26
26
  } else {
27
- // Load from environment variables
27
+ // Load from environment variables only when no config file exists
28
28
  config = {
29
29
  host: process.env.ABAP_HOST,
30
30
  sapport: parseInt(process.env.ABAP_PORT, 10) || 443,
package/src/ref-search.js CHANGED
@@ -257,79 +257,117 @@ async function getSearchableFiles(repoPath, repoName, extensions = ['.md', '.aba
257
257
  }
258
258
 
259
259
  /**
260
- * Search for a pattern across all reference repositories
260
+ * Search for a pattern across all reference repositories and local guidelines
261
261
  * @param {string} pattern - Pattern to search for
262
262
  * @returns {Promise<Object>} Search results
263
263
  */
264
264
  async function searchPattern(pattern) {
265
265
  const refFolder = detectReferenceFolder();
266
266
  const repos = await getReferenceRepositories();
267
+ const guidelinesFolder = detectGuidelinesFolder();
267
268
 
268
- if (!refFolder) {
269
+ // If neither reference folder nor guidelines exist, return error
270
+ if (!refFolder && !guidelinesFolder) {
269
271
  return {
270
272
  error: 'Reference folder not found',
271
- hint: 'Configure referenceFolder in .abapGitAgent or clone to ~/abap-reference'
272
- };
273
- }
274
-
275
- if (repos.length === 0) {
276
- return {
277
- error: 'No ABAP repositories found in reference folder',
278
- hint: 'Clone ABAP repositories to the reference folder to enable searching'
273
+ hint: 'Configure referenceFolder in .abapGitAgent, clone to ~/abap-reference, or create abap/guidelines/ folder'
279
274
  };
280
275
  }
281
276
 
282
277
  const results = {
283
278
  pattern,
284
279
  referenceFolder: refFolder,
280
+ guidelinesFolder: guidelinesFolder,
285
281
  repositories: repos.map(r => r.name),
286
282
  files: [],
287
283
  matches: []
288
284
  };
289
285
 
290
286
  try {
291
- // Search across all repositories
292
- for (const repo of repos) {
293
- const searchableFiles = await getSearchableFiles(repo.path, repo.name);
287
+ // Search reference repositories if available
288
+ if (repos.length > 0) {
289
+ for (const repo of repos) {
290
+ const searchableFiles = await getSearchableFiles(repo.path, repo.name);
291
+
292
+ for (const fileInfo of searchableFiles) {
293
+ try {
294
+ const content = await readFile(fileInfo.path, 'utf8');
295
+
296
+ if (content.toLowerCase().includes(pattern.toLowerCase())) {
297
+ results.files.push({
298
+ repo: repo.name,
299
+ file: fileInfo.relativePath
300
+ });
301
+
302
+ // Find matching lines with context
303
+ const lines = content.split('\n');
304
+ let matchCount = 0;
305
+
306
+ for (let i = 0; i < lines.length; i++) {
307
+ if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
308
+ const start = Math.max(0, i - 1);
309
+ const end = Math.min(lines.length, i + 2);
310
+ const context = lines.slice(start, end).join('\n');
311
+
312
+ results.matches.push({
313
+ repo: repo.name,
314
+ file: fileInfo.relativePath,
315
+ line: i + 1,
316
+ context
317
+ });
318
+
319
+ matchCount++;
320
+
321
+ // Limit matches per file to avoid overwhelming output
322
+ if (matchCount >= 3) {
323
+ break;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ } catch (error) {
329
+ // Skip files we can't read
330
+ }
331
+ }
332
+ }
333
+ }
294
334
 
295
- for (const fileInfo of searchableFiles) {
296
- try {
297
- const content = await readFile(fileInfo.path, 'utf8');
335
+ // Search local guidelines folder if available
336
+ if (guidelinesFolder) {
337
+ const guidelineFiles = await getGuidelineFiles();
298
338
 
299
- if (content.toLowerCase().includes(pattern.toLowerCase())) {
300
- results.files.push({
301
- repo: repo.name,
302
- file: fileInfo.relativePath
303
- });
339
+ for (const file of guidelineFiles) {
340
+ if (file.content.toLowerCase().includes(pattern.toLowerCase())) {
341
+ results.files.push({
342
+ repo: 'guidelines',
343
+ file: file.relativePath
344
+ });
304
345
 
305
- // Find matching lines with context
306
- const lines = content.split('\n');
307
- let matchCount = 0;
346
+ // Find matching lines with context
347
+ const lines = file.content.split('\n');
348
+ let matchCount = 0;
308
349
 
309
- for (let i = 0; i < lines.length; i++) {
310
- if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
311
- const start = Math.max(0, i - 1);
312
- const end = Math.min(lines.length, i + 2);
313
- const context = lines.slice(start, end).join('\n');
350
+ for (let i = 0; i < lines.length; i++) {
351
+ if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
352
+ const start = Math.max(0, i - 1);
353
+ const end = Math.min(lines.length, i + 2);
354
+ const context = lines.slice(start, end).join('\n');
314
355
 
315
- results.matches.push({
316
- repo: repo.name,
317
- file: fileInfo.relativePath,
318
- line: i + 1,
319
- context
320
- });
356
+ results.matches.push({
357
+ repo: 'guidelines',
358
+ file: file.relativePath,
359
+ line: i + 1,
360
+ context
361
+ });
321
362
 
322
- matchCount++;
363
+ matchCount++;
323
364
 
324
- // Limit matches per file to avoid overwhelming output
325
- if (matchCount >= 3) {
326
- break;
327
- }
365
+ // Limit matches per file to avoid overwhelming output
366
+ if (matchCount >= 3) {
367
+ break;
328
368
  }
329
369
  }
330
370
  }
331
- } catch (error) {
332
- // Skip files we can't read
333
371
  }
334
372
  }
335
373
  }
@@ -469,7 +507,16 @@ function displaySearchResults(results) {
469
507
  }
470
508
 
471
509
  console.log(`\n 🔍 Searching for: '${results.pattern}'`);
472
- console.log(` 📁 Reference folder: ${results.referenceFolder}`);
510
+
511
+ // Show which sources were searched
512
+ const sources = [];
513
+ if (results.referenceFolder) {
514
+ sources.push('reference repositories');
515
+ }
516
+ if (results.guidelinesFolder) {
517
+ sources.push('local guidelines');
518
+ }
519
+ console.log(` 📁 Sources searched: ${sources.join(', ') || 'none'}`);
473
520
 
474
521
  if (results.repositories && results.repositories.length > 0) {
475
522
  console.log(` 📚 Repositories (${results.repositories.length}): ${results.repositories.join(', ')}`);
@@ -491,7 +538,8 @@ function displaySearchResults(results) {
491
538
 
492
539
  console.log(` ✅ Found in ${results.files.length} file(s):`);
493
540
  for (const [repo, files] of Object.entries(filesByRepo)) {
494
- console.log(`\n 📦 ${repo}/`);
541
+ const icon = repo === 'guidelines' ? '📋' : '📦';
542
+ console.log(`\n ${icon} ${repo}/`);
495
543
  files.forEach(file => {
496
544
  console.log(` • ${file}`);
497
545
  });