abapgit-agent 1.12.0 → 1.13.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/README.md +30 -133
- package/abap/.github/copilot-instructions.slim.md +114 -0
- package/abap/CLAUDE.md +90 -25
- package/abap/CLAUDE.slim.md +42 -0
- package/abap/guidelines/abapgit.md +9 -47
- package/abap/guidelines/cds-testing.md +4 -1
- package/abap/guidelines/cds.md +38 -10
- package/abap/guidelines/testing.md +46 -2
- package/bin/abapgit-agent +1 -0
- package/package.json +3 -1
- package/src/commands/guide.js +276 -0
- package/src/commands/help.js +15 -0
- package/src/commands/init.js +132 -65
- package/src/commands/pull.js +69 -20
- package/src/utils/abap-reference.js +254 -118
|
@@ -17,9 +17,9 @@ const stat = promisify(fs.stat);
|
|
|
17
17
|
const TOPIC_MAP = {
|
|
18
18
|
'internal-tables': '01_Internal_Tables.md',
|
|
19
19
|
'structures': '02_Structures.md',
|
|
20
|
-
'sql': '03_ABAP_SQL.md',
|
|
20
|
+
'sql-cheatsheet': '03_ABAP_SQL.md',
|
|
21
21
|
'oop': '04_ABAP_Object_Orientation.md',
|
|
22
|
-
'
|
|
22
|
+
'oop-cheatsheet': '04_ABAP_Object_Orientation.md',
|
|
23
23
|
'constructors': '05_Constructor_Expressions.md',
|
|
24
24
|
'constructor': '05_Constructor_Expressions.md',
|
|
25
25
|
'dynamic': '06_Dynamic_Programming.md',
|
|
@@ -33,24 +33,23 @@ const TOPIC_MAP = {
|
|
|
33
33
|
'flow': '13_Program_Flow_Logic.md',
|
|
34
34
|
'unit-tests': '14_ABAP_Unit_Tests.md',
|
|
35
35
|
'unit': '14_ABAP_Unit_Tests.md',
|
|
36
|
-
'
|
|
37
|
-
'cds': '15_CDS_View_Entities.md',
|
|
36
|
+
'cds-cheatsheet': '15_CDS_View_Entities.md',
|
|
38
37
|
'datatypes': '16_Data_Types_and_Objects.md',
|
|
39
38
|
'luw': '17_SAP_LUW.md',
|
|
40
39
|
'dynpro': '18_Dynpro.md',
|
|
41
40
|
'cloud': '19_ABAP_for_Cloud_Development.md',
|
|
42
41
|
'selection-screens': '20_Selection_Screens_Lists.md',
|
|
43
42
|
'json-xml': '21_XML_JSON.md',
|
|
44
|
-
'json': '21_XML_JSON.md',
|
|
45
|
-
'xml': '21_XML_JSON.md',
|
|
43
|
+
'json-cheatsheet': '21_XML_JSON.md',
|
|
44
|
+
'xml-cheatsheet': '21_XML_JSON.md',
|
|
46
45
|
'released-classes': '22_Released_ABAP_Classes.md',
|
|
47
46
|
'datetime': '23_Date_and_Time.md',
|
|
48
47
|
'functions': '24_Builtin_Functions.md',
|
|
49
48
|
'auth': '25_Authorization_Checks.md',
|
|
50
49
|
'authorization': '25_Authorization_Checks.md',
|
|
51
50
|
'dictionary': '26_ABAP_Dictionary.md',
|
|
52
|
-
'exceptions': '27_Exceptions.md',
|
|
53
|
-
'exception': '27_Exceptions.md',
|
|
51
|
+
'exceptions-cheatsheet': '27_Exceptions.md',
|
|
52
|
+
'exception-cheatsheet': '27_Exceptions.md',
|
|
54
53
|
'regex': '28_Regular_Expressions.md',
|
|
55
54
|
'numeric': '29_Numeric_Operations.md',
|
|
56
55
|
'ai': '30_Generative_AI.md',
|
|
@@ -380,9 +379,10 @@ async function searchPattern(pattern) {
|
|
|
380
379
|
const refFolder = detectReferenceFolder();
|
|
381
380
|
const repos = await getReferenceRepositories();
|
|
382
381
|
const guidelinesFolder = detectGuidelinesFolder();
|
|
382
|
+
const builtInPath = getBuiltInGuidelinesPath();
|
|
383
383
|
|
|
384
|
-
// If
|
|
385
|
-
if (!refFolder && !guidelinesFolder) {
|
|
384
|
+
// If no sources at all, return error
|
|
385
|
+
if (!refFolder && !guidelinesFolder && !builtInPath) {
|
|
386
386
|
return {
|
|
387
387
|
error: 'Reference folder not found',
|
|
388
388
|
hint: 'Configure referenceFolder in .abapGitAgent, clone to ~/abap-reference, or create abap/guidelines/ folder'
|
|
@@ -398,6 +398,34 @@ async function searchPattern(pattern) {
|
|
|
398
398
|
matches: []
|
|
399
399
|
};
|
|
400
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Helper: search a list of guideline file objects and push results
|
|
403
|
+
*/
|
|
404
|
+
function searchGuidelineFiles(guidelineFiles, repoLabel) {
|
|
405
|
+
for (const file of guidelineFiles) {
|
|
406
|
+
if (file.content.toLowerCase().includes(pattern.toLowerCase())) {
|
|
407
|
+
results.files.push({ repo: repoLabel, file: file.relativePath });
|
|
408
|
+
|
|
409
|
+
const lines = file.content.split('\n');
|
|
410
|
+
let matchCount = 0;
|
|
411
|
+
for (let i = 0; i < lines.length; i++) {
|
|
412
|
+
if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
|
|
413
|
+
const start = Math.max(0, i - 1);
|
|
414
|
+
const end = Math.min(lines.length, i + 2);
|
|
415
|
+
results.matches.push({
|
|
416
|
+
repo: repoLabel,
|
|
417
|
+
file: file.relativePath,
|
|
418
|
+
line: i + 1,
|
|
419
|
+
context: lines.slice(start, end).join('\n')
|
|
420
|
+
});
|
|
421
|
+
matchCount++;
|
|
422
|
+
if (matchCount >= 3) break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
401
429
|
try {
|
|
402
430
|
// Search reference repositories if available
|
|
403
431
|
if (repos.length > 0) {
|
|
@@ -447,44 +475,22 @@ async function searchPattern(pattern) {
|
|
|
447
475
|
}
|
|
448
476
|
}
|
|
449
477
|
|
|
450
|
-
//
|
|
478
|
+
// Tier 1: search local guidelines folder
|
|
451
479
|
if (guidelinesFolder) {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (file.content.toLowerCase().includes(pattern.toLowerCase())) {
|
|
456
|
-
results.files.push({
|
|
457
|
-
repo: 'guidelines',
|
|
458
|
-
file: file.relativePath
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Find matching lines with context
|
|
462
|
-
const lines = file.content.split('\n');
|
|
463
|
-
let matchCount = 0;
|
|
464
|
-
|
|
465
|
-
for (let i = 0; i < lines.length; i++) {
|
|
466
|
-
if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
|
|
467
|
-
const start = Math.max(0, i - 1);
|
|
468
|
-
const end = Math.min(lines.length, i + 2);
|
|
469
|
-
const context = lines.slice(start, end).join('\n');
|
|
470
|
-
|
|
471
|
-
results.matches.push({
|
|
472
|
-
repo: 'guidelines',
|
|
473
|
-
file: file.relativePath,
|
|
474
|
-
line: i + 1,
|
|
475
|
-
context
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
matchCount++;
|
|
480
|
+
const localFiles = await getGuidelineFilesFromPath(guidelinesFolder, 'guidelines');
|
|
481
|
+
searchGuidelineFiles(localFiles, 'guidelines');
|
|
482
|
+
}
|
|
479
483
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
// Tier 2: search built-in guidelines for files not shadowed by local ones
|
|
485
|
+
if (builtInPath) {
|
|
486
|
+
const localFileNames = guidelinesFolder
|
|
487
|
+
? new Set(fs.readdirSync(guidelinesFolder).filter(f => f.endsWith('.md')))
|
|
488
|
+
: new Set();
|
|
489
|
+
const builtInFiles = await getGuidelineFilesFromPath(builtInPath, 'guidelines');
|
|
490
|
+
const filesToSearch = builtInFiles.filter(f => !localFileNames.has(f.name));
|
|
491
|
+
// Override relativePath to signal built-in source
|
|
492
|
+
filesToSearch.forEach(f => { f.relativePath = path.join('guidelines', f.name); });
|
|
493
|
+
searchGuidelineFiles(filesToSearch, '[built-in]');
|
|
488
494
|
}
|
|
489
495
|
|
|
490
496
|
return results;
|
|
@@ -523,8 +529,8 @@ async function getTopic(topic) {
|
|
|
523
529
|
return {
|
|
524
530
|
topic,
|
|
525
531
|
file: guidelineFile,
|
|
526
|
-
content: content.slice(0,
|
|
527
|
-
truncated: content.length >
|
|
532
|
+
content: content.slice(0, 15000),
|
|
533
|
+
truncated: content.length > 15000,
|
|
528
534
|
totalLength: content.length,
|
|
529
535
|
source: 'guidelines'
|
|
530
536
|
};
|
|
@@ -535,46 +541,113 @@ async function getTopic(topic) {
|
|
|
535
541
|
}
|
|
536
542
|
}
|
|
537
543
|
|
|
538
|
-
// Fall back to
|
|
544
|
+
// Fall back to cheat sheets TOPIC_MAP
|
|
539
545
|
const cheatSheetsDir = getCheatSheetsDir();
|
|
540
546
|
|
|
541
|
-
if (!cheatSheetsDir) {
|
|
542
|
-
return {
|
|
543
|
-
error: 'Reference folder not found',
|
|
544
|
-
hint: 'Configure referenceFolder in .abapGitAgent or clone to ~/abap-reference'
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
547
|
const fileName = TOPIC_MAP[topicLower];
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
548
|
+
if (fileName && cheatSheetsDir) {
|
|
549
|
+
const filePath = path.join(cheatSheetsDir, fileName);
|
|
550
|
+
if (fs.existsSync(filePath)) {
|
|
551
|
+
try {
|
|
552
|
+
const content = await readFile(filePath, 'utf8');
|
|
553
|
+
return {
|
|
554
|
+
topic,
|
|
555
|
+
file: fileName,
|
|
556
|
+
content: content.slice(0, 5000),
|
|
557
|
+
truncated: content.length > 5000,
|
|
558
|
+
totalLength: content.length,
|
|
559
|
+
source: 'cheat-sheets'
|
|
560
|
+
};
|
|
561
|
+
} catch (error) {
|
|
562
|
+
// fall through
|
|
563
|
+
}
|
|
564
|
+
}
|
|
554
565
|
}
|
|
555
566
|
|
|
556
|
-
|
|
567
|
+
// TOPIC_MAP matched but cheat sheets not available — give a helpful setup hint
|
|
568
|
+
if (fileName && !cheatSheetsDir) {
|
|
569
|
+
// Distinguish between "not configured" and "configured but folder missing"
|
|
570
|
+
let configuredRefFolder = null;
|
|
571
|
+
try {
|
|
572
|
+
const configPath = path.join(process.cwd(), '.abapGitAgent');
|
|
573
|
+
if (fs.existsSync(configPath)) {
|
|
574
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
575
|
+
if (config.referenceFolder) configuredRefFolder = config.referenceFolder;
|
|
576
|
+
}
|
|
577
|
+
} catch (e) { /* ignore */ }
|
|
578
|
+
|
|
579
|
+
if (configuredRefFolder) {
|
|
580
|
+
return {
|
|
581
|
+
error: `Topic '${topic}' requires SAP ABAP cheat sheets (folder not found: ${configuredRefFolder})`,
|
|
582
|
+
hint: [
|
|
583
|
+
'The referenceFolder is configured but does not exist on disk.',
|
|
584
|
+
`Clone the cheat sheets into it: abapgit-agent ref --clone SAP-samples/abap-cheat-sheets`,
|
|
585
|
+
'',
|
|
586
|
+
'Bundled topics (no setup needed): run abapgit-agent ref --list-topics'
|
|
587
|
+
].join('\n')
|
|
588
|
+
};
|
|
589
|
+
}
|
|
557
590
|
|
|
558
|
-
if (!fs.existsSync(filePath)) {
|
|
559
591
|
return {
|
|
560
|
-
error: `
|
|
592
|
+
error: `Topic '${topic}' requires SAP ABAP cheat sheets (not configured)`,
|
|
593
|
+
hint: [
|
|
594
|
+
'To access SAP cheat sheet topics:',
|
|
595
|
+
' 1. Add to .abapGitAgent: { "referenceFolder": "/path/to/abap-reference" }',
|
|
596
|
+
' 2. Clone: abapgit-agent ref --clone SAP-samples/abap-cheat-sheets',
|
|
597
|
+
'',
|
|
598
|
+
'Bundled topics (no setup needed): run abapgit-agent ref --list-topics'
|
|
599
|
+
].join('\n')
|
|
561
600
|
};
|
|
562
601
|
}
|
|
563
602
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
603
|
+
// Fall back to bundled guidelines by filename stem
|
|
604
|
+
// e.g. 'debug-session' → 'debug-session.md', 'debug' → 'debug-session.md' (if unambiguous)
|
|
605
|
+
const builtInPath = getBuiltInGuidelinesPath();
|
|
606
|
+
if (builtInPath) {
|
|
607
|
+
try {
|
|
608
|
+
const builtInFiles = fs.readdirSync(builtInPath).filter(f => f.endsWith('.md'));
|
|
609
|
+
|
|
610
|
+
// Exact stem match first
|
|
611
|
+
const exactMatch = builtInFiles.find(f => f.replace(/\.md$/, '') === topicLower);
|
|
612
|
+
if (exactMatch) {
|
|
613
|
+
const content = await readFile(path.join(builtInPath, exactMatch), 'utf8');
|
|
614
|
+
return {
|
|
615
|
+
topic,
|
|
616
|
+
file: exactMatch,
|
|
617
|
+
content: content.slice(0, 15000),
|
|
618
|
+
truncated: content.length > 15000,
|
|
619
|
+
totalLength: content.length,
|
|
620
|
+
source: 'built-in guidelines'
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Partial stem match (unambiguous only)
|
|
625
|
+
const partialMatches = builtInFiles.filter(f => f.replace(/\.md$/, '').includes(topicLower));
|
|
626
|
+
if (partialMatches.length === 1) {
|
|
627
|
+
const content = await readFile(path.join(builtInPath, partialMatches[0]), 'utf8');
|
|
628
|
+
return {
|
|
629
|
+
topic,
|
|
630
|
+
file: partialMatches[0],
|
|
631
|
+
content: content.slice(0, 15000),
|
|
632
|
+
truncated: content.length > 15000,
|
|
633
|
+
totalLength: content.length,
|
|
634
|
+
source: 'built-in guidelines'
|
|
635
|
+
};
|
|
636
|
+
} else if (partialMatches.length > 1) {
|
|
637
|
+
return {
|
|
638
|
+
error: `Ambiguous topic: '${topic}' matches multiple guideline files`,
|
|
639
|
+
hint: `Be more specific. Matches: ${partialMatches.map(f => f.replace(/\.md$/, '')).join(', ')}`
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
} catch (error) {
|
|
643
|
+
// fall through to final error
|
|
644
|
+
}
|
|
577
645
|
}
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
error: `Unknown topic: ${topic}`,
|
|
649
|
+
hint: 'For project guidelines, use the filename stem (e.g. --topic debug-session, --topic workflow-detailed)\nRun: abapgit-agent ref --list-topics'
|
|
650
|
+
};
|
|
578
651
|
}
|
|
579
652
|
|
|
580
653
|
/**
|
|
@@ -584,27 +657,50 @@ async function getTopic(topic) {
|
|
|
584
657
|
async function listTopics() {
|
|
585
658
|
const cheatSheetsDir = getCheatSheetsDir();
|
|
586
659
|
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
error: 'Reference folder not found',
|
|
590
|
-
hint: 'Configure referenceFolder in .abapGitAgent or clone to ~/abap-reference'
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Build topic list from files that exist
|
|
660
|
+
// Build topic list from cheat sheet files that exist
|
|
595
661
|
const topics = [];
|
|
596
|
-
const seenFiles = new Set();
|
|
597
662
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
seenFiles.
|
|
663
|
+
if (cheatSheetsDir) {
|
|
664
|
+
const seenFiles = new Set();
|
|
665
|
+
for (const [topic, file] of Object.entries(TOPIC_MAP)) {
|
|
666
|
+
if (!seenFiles.has(file) && fs.existsSync(path.join(cheatSheetsDir, file))) {
|
|
667
|
+
topics.push({ topic, file });
|
|
668
|
+
seenFiles.add(file);
|
|
669
|
+
}
|
|
602
670
|
}
|
|
603
671
|
}
|
|
604
672
|
|
|
673
|
+
// Build guideline topic list from bundled guidelines (filename stem → filename)
|
|
674
|
+
const builtInPath = getBuiltInGuidelinesPath();
|
|
675
|
+
const guidelineTopics = [];
|
|
676
|
+
if (builtInPath) {
|
|
677
|
+
try {
|
|
678
|
+
const entries = fs.readdirSync(builtInPath).filter(f => f.endsWith('.md')).sort();
|
|
679
|
+
for (const file of entries) {
|
|
680
|
+
const stem = file.replace(/\.md$/, '');
|
|
681
|
+
guidelineTopics.push({ topic: stem, file });
|
|
682
|
+
}
|
|
683
|
+
} catch (e) { /* ignore */ }
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Also include local guidelines/ topics (if present)
|
|
687
|
+
const localGuidelinesDir = detectGuidelinesFolder();
|
|
688
|
+
const localGuidelineTopics = [];
|
|
689
|
+
if (localGuidelinesDir) {
|
|
690
|
+
try {
|
|
691
|
+
const entries = fs.readdirSync(localGuidelinesDir).filter(f => f.endsWith('.md')).sort();
|
|
692
|
+
for (const file of entries) {
|
|
693
|
+
const stem = file.replace(/\.md$/, '');
|
|
694
|
+
localGuidelineTopics.push({ topic: stem, file });
|
|
695
|
+
}
|
|
696
|
+
} catch (e) { /* ignore */ }
|
|
697
|
+
}
|
|
698
|
+
|
|
605
699
|
return {
|
|
606
|
-
referenceFolder: path.dirname(cheatSheetsDir),
|
|
607
|
-
topics: topics.sort((a, b) => a.file.localeCompare(b.file))
|
|
700
|
+
referenceFolder: cheatSheetsDir ? path.dirname(cheatSheetsDir) : null,
|
|
701
|
+
topics: topics.sort((a, b) => a.file.localeCompare(b.file)),
|
|
702
|
+
guidelineTopics,
|
|
703
|
+
localGuidelineTopics
|
|
608
704
|
};
|
|
609
705
|
}
|
|
610
706
|
|
|
@@ -631,6 +727,11 @@ function displaySearchResults(results) {
|
|
|
631
727
|
if (results.guidelinesFolder) {
|
|
632
728
|
sources.push('local guidelines');
|
|
633
729
|
}
|
|
730
|
+
// Check if built-in guidelines were searched (present in matches)
|
|
731
|
+
const hasBuiltIn = results.files.some(f => f.repo === '[built-in]');
|
|
732
|
+
if (hasBuiltIn) {
|
|
733
|
+
sources.push('built-in guidelines');
|
|
734
|
+
}
|
|
634
735
|
console.log(` 📁 Sources searched: ${sources.join(', ') || 'none'}`);
|
|
635
736
|
|
|
636
737
|
if (results.repositories && results.repositories.length > 0) {
|
|
@@ -653,8 +754,9 @@ function displaySearchResults(results) {
|
|
|
653
754
|
|
|
654
755
|
console.log(` ✅ Found in ${results.files.length} file(s):`);
|
|
655
756
|
for (const [repo, files] of Object.entries(filesByRepo)) {
|
|
656
|
-
const icon = repo === 'guidelines' ? '📋' : '📦';
|
|
657
|
-
|
|
757
|
+
const icon = repo === 'guidelines' ? '📋' : repo === '[built-in]' ? '📦' : '📦';
|
|
758
|
+
const label = repo === '[built-in]' ? 'built-in guidelines' : repo;
|
|
759
|
+
console.log(`\n ${icon} ${label}/`);
|
|
658
760
|
files.forEach(file => {
|
|
659
761
|
console.log(` • ${file}`);
|
|
660
762
|
});
|
|
@@ -705,12 +807,13 @@ function displayTopic(result) {
|
|
|
705
807
|
return;
|
|
706
808
|
}
|
|
707
809
|
|
|
708
|
-
|
|
810
|
+
const sourceLabel = result.source === 'built-in guidelines' ? ' [built-in]' : result.source === 'guidelines' ? ' [local]' : '';
|
|
811
|
+
console.log(`\n 📖 ${result.file}${sourceLabel}`);
|
|
709
812
|
console.log(' ' + '─'.repeat(60));
|
|
710
813
|
console.log('');
|
|
711
814
|
|
|
712
|
-
// Display
|
|
713
|
-
const lines = result.content.split('\n').slice(0,
|
|
815
|
+
// Display up to 300 lines (covers full guideline files without truncation)
|
|
816
|
+
const lines = result.content.split('\n').slice(0, 300);
|
|
714
817
|
lines.forEach(line => {
|
|
715
818
|
const trimmed = line.slice(0, 100);
|
|
716
819
|
console.log(` ${trimmed}`);
|
|
@@ -736,15 +839,40 @@ function displayTopics(result) {
|
|
|
736
839
|
}
|
|
737
840
|
|
|
738
841
|
console.log(`\n 📚 Available ABAP Reference Topics`);
|
|
739
|
-
|
|
842
|
+
if (result.referenceFolder) {
|
|
843
|
+
console.log(` 📁 Reference folder: ${result.referenceFolder}`);
|
|
844
|
+
}
|
|
740
845
|
console.log('');
|
|
741
|
-
console.log(' Topic File');
|
|
742
|
-
console.log(' ' + '─'.repeat(60));
|
|
743
846
|
|
|
744
|
-
result.topics.
|
|
745
|
-
|
|
746
|
-
console.log(
|
|
747
|
-
|
|
847
|
+
if (result.topics.length > 0) {
|
|
848
|
+
console.log(' SAP Cheat Sheets');
|
|
849
|
+
console.log(' Topic File');
|
|
850
|
+
console.log(' ' + '─'.repeat(60));
|
|
851
|
+
result.topics.forEach(({ topic, file }) => {
|
|
852
|
+
console.log(` ${topic.padEnd(20)} ${file}`);
|
|
853
|
+
});
|
|
854
|
+
console.log('');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (result.localGuidelineTopics && result.localGuidelineTopics.length > 0) {
|
|
858
|
+
console.log(' Local Guidelines (guidelines/)');
|
|
859
|
+
console.log(' Topic File');
|
|
860
|
+
console.log(' ' + '─'.repeat(60));
|
|
861
|
+
result.localGuidelineTopics.forEach(({ topic, file }) => {
|
|
862
|
+
console.log(` ${topic.padEnd(20)} ${file}`);
|
|
863
|
+
});
|
|
864
|
+
console.log('');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (result.guidelineTopics && result.guidelineTopics.length > 0) {
|
|
868
|
+
console.log(' Bundled Guidelines (use: abapgit-agent ref --topic <topic>)');
|
|
869
|
+
console.log(' Topic File');
|
|
870
|
+
console.log(' ' + '─'.repeat(60));
|
|
871
|
+
result.guidelineTopics.forEach(({ topic, file }) => {
|
|
872
|
+
console.log(` ${topic.padEnd(20)} ${file}`);
|
|
873
|
+
});
|
|
874
|
+
console.log('');
|
|
875
|
+
}
|
|
748
876
|
}
|
|
749
877
|
|
|
750
878
|
/**
|
|
@@ -933,39 +1061,46 @@ function initGuidelines() {
|
|
|
933
1061
|
}
|
|
934
1062
|
|
|
935
1063
|
/**
|
|
936
|
-
* Get all guideline files from
|
|
937
|
-
* @
|
|
1064
|
+
* Get all guideline files from a specific path
|
|
1065
|
+
* @param {string} guidelinesPath - Path to guidelines folder
|
|
1066
|
+
* @param {string} [label] - Optional label suffix for relativePath (default: 'guidelines')
|
|
1067
|
+
* @returns {Promise<Array<{name: string, path: string, content: string, relativePath: string}>>}
|
|
938
1068
|
*/
|
|
939
|
-
async function
|
|
940
|
-
const
|
|
941
|
-
if (!guidelinesFolder) {
|
|
942
|
-
return [];
|
|
943
|
-
}
|
|
944
|
-
|
|
1069
|
+
async function getGuidelineFilesFromPath(guidelinesPath, label) {
|
|
1070
|
+
const folderLabel = label || 'guidelines';
|
|
945
1071
|
const files = [];
|
|
946
|
-
|
|
947
1072
|
try {
|
|
948
|
-
const entries = await readdir(
|
|
949
|
-
|
|
1073
|
+
const entries = await readdir(guidelinesPath);
|
|
950
1074
|
for (const entry of entries) {
|
|
951
1075
|
if (entry.endsWith('.md')) {
|
|
952
|
-
const fullPath = path.join(
|
|
1076
|
+
const fullPath = path.join(guidelinesPath, entry);
|
|
953
1077
|
const content = await readFile(fullPath, 'utf8');
|
|
954
1078
|
files.push({
|
|
955
1079
|
name: entry,
|
|
956
1080
|
path: fullPath,
|
|
957
1081
|
content,
|
|
958
|
-
relativePath: path.join(
|
|
1082
|
+
relativePath: path.join(folderLabel, entry)
|
|
959
1083
|
});
|
|
960
1084
|
}
|
|
961
1085
|
}
|
|
962
1086
|
} catch (error) {
|
|
963
1087
|
// Return empty array on error
|
|
964
1088
|
}
|
|
965
|
-
|
|
966
1089
|
return files.sort((a, b) => a.name.localeCompare(b.name));
|
|
967
1090
|
}
|
|
968
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* Get all guideline files from the project
|
|
1094
|
+
* @returns {Promise<Array<{name: string, path: string, content: string}>>}
|
|
1095
|
+
*/
|
|
1096
|
+
async function getGuidelineFiles() {
|
|
1097
|
+
const guidelinesFolder = detectGuidelinesFolder();
|
|
1098
|
+
if (!guidelinesFolder) {
|
|
1099
|
+
return [];
|
|
1100
|
+
}
|
|
1101
|
+
return getGuidelineFilesFromPath(guidelinesFolder);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
969
1104
|
/**
|
|
970
1105
|
* Export guidelines to reference folder
|
|
971
1106
|
* Copies guideline files to the reference folder for searching
|
|
@@ -1134,6 +1269,7 @@ module.exports = {
|
|
|
1134
1269
|
ensureReferenceFolder,
|
|
1135
1270
|
detectGuidelinesFolder,
|
|
1136
1271
|
getBuiltInGuidelinesPath,
|
|
1272
|
+
getGuidelineFilesFromPath,
|
|
1137
1273
|
initGuidelines,
|
|
1138
1274
|
getReferenceRepositories,
|
|
1139
1275
|
getGuidelineFiles,
|