create-snowline-agents 1.0.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.
@@ -0,0 +1,113 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import re
5
+ import shutil
6
+ from datetime import datetime
7
+
8
+ # Force UTF-8 encoding for Windows terminal
9
+ if sys.stdout.encoding.lower() != 'utf-8':
10
+ sys.stdout.reconfigure(encoding='utf-8')
11
+
12
+ DEFAULT_EXCLUDES = {'.git', 'node_modules', '.history', 'vendor', 'dist', 'build', 'quarantine', '.backup_replace', '.agents'}
13
+ MAX_FILE_SIZE = 500 * 1024 # 500 KB
14
+
15
+ def find_project_root(start_path):
16
+ current = os.path.abspath(start_path)
17
+ while True:
18
+ if os.path.exists(os.path.join(current, 'package.json')) or os.path.exists(os.path.join(current, '.git')):
19
+ return current
20
+ parent = os.path.dirname(current)
21
+ if parent == current:
22
+ return os.path.abspath(start_path)
23
+ current = parent
24
+
25
+ def get_args():
26
+ parser = argparse.ArgumentParser(description="Smart Replace (Pure Python Edition)")
27
+ parser.add_argument("target_dir", help="Target directory (absolute path)")
28
+ parser.add_argument("search_string", help="String or pattern to search for")
29
+ parser.add_argument("replace_string", help="String to replace with")
30
+ parser.add_argument("--ext", help="Comma-separated extensions to include (e.g. .js,.jsx)", default="")
31
+ parser.add_argument("--regex", action="store_true", help="Treat search_string as a regular expression")
32
+ parser.add_argument("--whole-word", action="store_true", help="Match whole words only")
33
+ parser.add_argument("--apply", action="store_true", help="Actually modify the files")
34
+ return parser.parse_args()
35
+
36
+ def backup_file(filepath, backup_dir):
37
+ rel_path = os.path.relpath(filepath, os.getcwd())
38
+ backup_path = os.path.join(backup_dir, rel_path)
39
+ os.makedirs(os.path.dirname(backup_path), exist_ok=True)
40
+ shutil.copy2(filepath, backup_path)
41
+ return backup_path
42
+
43
+ def main():
44
+ args = get_args()
45
+
46
+ if not os.path.exists(args.target_dir):
47
+ print(f"[FAIL] Target directory not found: {args.target_dir}")
48
+ sys.exit(1)
49
+
50
+ exts = [e.strip() for e in args.ext.split(',')] if args.ext else []
51
+
52
+ pattern_str = args.search_string
53
+ if not args.regex:
54
+ pattern_str = re.escape(pattern_str)
55
+ if args.whole_word:
56
+ pattern_str = r'\b' + pattern_str + r'\b'
57
+
58
+ try:
59
+ regex = re.compile(pattern_str)
60
+ except re.error as e:
61
+ print(f"[FAIL] Invalid regex: {e}")
62
+ sys.exit(1)
63
+
64
+ backup_dir = None
65
+ if args.apply:
66
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
67
+ project_root = find_project_root(args.target_dir)
68
+ backup_dir = os.path.join(project_root, '.backup_replace', timestamp)
69
+
70
+ match_count = 0
71
+ file_count = 0
72
+ scanned_files = 0
73
+
74
+ for root, dirs, files in os.walk(args.target_dir):
75
+ dirs[:] = [d for d in dirs if d not in DEFAULT_EXCLUDES]
76
+ for file in files:
77
+ if exts and not any(file.endswith(e) for e in exts):
78
+ continue
79
+
80
+ filepath = os.path.join(root, file)
81
+ if os.path.getsize(filepath) > MAX_FILE_SIZE:
82
+ continue
83
+
84
+ scanned_files += 1
85
+
86
+ try:
87
+ with open(filepath, 'r', encoding='utf-8') as f:
88
+ content = f.read()
89
+ except UnicodeDecodeError:
90
+ continue
91
+
92
+ if regex.search(content):
93
+ file_count += 1
94
+ new_content, count = regex.subn(args.replace_string, content)
95
+ match_count += count
96
+
97
+ rel_path = os.path.relpath(filepath, args.target_dir)
98
+ print(f"[WARN] Found {count} matches in {rel_path}")
99
+
100
+ if args.apply:
101
+ backup_file(filepath, backup_dir)
102
+ with open(filepath, 'w', encoding='utf-8') as f:
103
+ f.write(new_content)
104
+
105
+ print(f"\n[OK] Scan selesai ({scanned_files} file dipindai). Menemukan {match_count} kecocokan di {file_count} file.")
106
+ if not args.apply:
107
+ print("\nšŸ’” PROMPT UNTUK AI (Copy-Paste ini):")
108
+ print('"Berdasarkan hasil dry-run di atas, tolong jalankan ulang perintah tersebut dengan menambahkan flag --apply untuk menerapkan perubahannya secara aman."')
109
+ else:
110
+ print(f"\n[INFO] Perubahan telah diterapkan ke {file_count} file. Backup tersimpan di: {backup_dir}")
111
+
112
+ if __name__ == "__main__":
113
+ main()
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Smart Code Reference Finder
3
+ description: Use this skill when the user asks you to find WHERE a specific function, component, class, or variable is used across a project. This skill uses a Python script to extract the code blocks surrounding the keyword, providing you with full context (e.g. what props are passed, what variables surround it) instead of just single raw lines like standard grep.
4
+ ---
5
+
6
+ ## Instructions for AI Agent
7
+
8
+ When analyzing a codebase to find references, standard `grep_search` is often insufficient because it only returns the exact line containing the keyword, omitting critical surrounding logic.
9
+
10
+ **When to use this skill:**
11
+ - The user asks to find where a component/function is used.
12
+ - You need to know *how* a specific function is invoked across multiple files (what props/arguments are passed).
13
+ - You are refactoring a core module and need to see all its dependent implementations in context.
14
+
15
+ **How to use:**
16
+ **Command to run:**
17
+ ```powershell
18
+ python .agents/skills/smart_search/code_finder.py "<absolute_target_directory>" "<keyword>" --ext "<optional_extensions>"
19
+ ```
20
+
21
+ **Parameters:**
22
+ - `<absolute_target_directory>`: The absolute path to the directory you want to scan (e.g., `/path/to/your/src`).
23
+ - `<keyword>`: The exact code string to search for (e.g., `MyComponent`).
24
+ - `--ext`: (Optional) Comma-separated file extensions to limit the search (e.g., `.jsx,.js,.php`).
25
+
26
+ **Expected Output:**
27
+ The script will output beautifully formatted Markdown text directly into standard output. It groups results by file and shows blocks of code (with 5 lines of context above and below the match). The exact line containing the match will be highlighted with a `>` arrow. Read this output carefully to understand the implementation context before deciding on your next coding action.
@@ -0,0 +1,103 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+
5
+ if sys.stdout.encoding != 'utf-8':
6
+ sys.stdout.reconfigure(encoding='utf-8')
7
+
8
+ MAX_FILE_SIZE = 500 * 1024 # 500 KB
9
+ DEFAULT_EXCLUDES = {'node_modules', '.git', 'vendor', 'build', 'dist', '.idea', '.vscode', '.history', '.backup_replace', '.agents'}
10
+
11
+ def search_files(directory, keyword, extensions):
12
+ results = []
13
+ scanned = 0
14
+ skipped = 0
15
+
16
+ for root, dirs, files in os.walk(directory):
17
+ dirs[:] = [d for d in dirs if d not in DEFAULT_EXCLUDES]
18
+
19
+ for file in files:
20
+ if extensions and not any(file.endswith(ext) for ext in extensions):
21
+ continue
22
+
23
+ filepath = os.path.join(root, file)
24
+ if os.path.getsize(filepath) > MAX_FILE_SIZE:
25
+ skipped += 1
26
+ continue
27
+
28
+ scanned += 1
29
+ try:
30
+ with open(filepath, 'r', encoding='utf-8') as f:
31
+ lines = f.readlines()
32
+ except Exception:
33
+ continue
34
+
35
+ matches = []
36
+ for i, line in enumerate(lines):
37
+ if keyword in line:
38
+ matches.append(i)
39
+
40
+ if matches:
41
+ context_lines = 5
42
+ blocks = []
43
+ current_block = None
44
+
45
+ for m in matches:
46
+ start = max(0, m - context_lines)
47
+ end = min(len(lines), m + context_lines + 1)
48
+
49
+ if current_block and start <= current_block['end']:
50
+ current_block['end'] = max(current_block['end'], end)
51
+ current_block['matches'].append(m)
52
+ else:
53
+ if current_block:
54
+ blocks.append(current_block)
55
+ current_block = {'start': start, 'end': end, 'matches': [m]}
56
+
57
+ if current_block:
58
+ blocks.append(current_block)
59
+
60
+ results.append({'file': filepath, 'blocks': blocks, 'lines': lines})
61
+
62
+ return results, scanned, skipped
63
+
64
+ def main():
65
+ parser = argparse.ArgumentParser(description="Smart Code Finder - Find code with context (Token Efficient)")
66
+ parser.add_argument("target_dir", help="Directory to scan")
67
+ parser.add_argument("keyword", help="Search keyword (e.g., 'function_name')")
68
+ parser.add_argument("--ext", help="Comma-separated extensions to filter (e.g., .js,.jsx)", default="")
69
+ args = parser.parse_args()
70
+
71
+ extensions = [ext.strip() for ext in args.ext.split(",")] if args.ext else []
72
+
73
+ results, scanned, skipped = search_files(args.target_dir, args.keyword, extensions)
74
+
75
+ if not results:
76
+ print(f"[OK] Keyword '{args.keyword}' not found in {scanned} files.")
77
+ sys.exit(0)
78
+
79
+ print(f"šŸ”Ž SEARCH RESULTS: '{args.keyword}'")
80
+ print("=" * 60)
81
+
82
+ total_matches = 0
83
+ for r in results:
84
+ rel_path = os.path.relpath(r['file'], args.target_dir)
85
+ print(f"\n[WARN] Found in: {rel_path}")
86
+ print("-" * 60)
87
+
88
+ for block in r['blocks']:
89
+ for i in range(block['start'], block['end']):
90
+ line_str = r['lines'][i].rstrip()
91
+ prefix = ">>" if i in block['matches'] else " "
92
+ print(f"{i+1:5d} | {prefix} {line_str}")
93
+ if i in block['matches']:
94
+ total_matches += 1
95
+ print("-" * 30)
96
+
97
+ print("\n" + "=" * 60)
98
+ print(f"[OK] Selesai: {total_matches} kecocokan di {len(results)} file (dari {scanned} dipindai, {skipped} dilewati karena >500KB).")
99
+ print("\nšŸ’” PROMPT UNTUK AI (Copy-Paste ini):")
100
+ print('"Tolong baca cuplikan kode di atas. Jika Anda perlu mengubah kode tersebut, gunakan tool replace_file_content pada file yang disebutkan di atas."')
101
+
102
+ if __name__ == "__main__":
103
+ main()
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Smart Directory Mapper (Tree Viewer)
3
+ description: Use this skill when you need to understand the project structure, locate files, or map out a directory tree WITHOUT consuming a huge amount of tokens. This script generates a compact, `.gitignore`-aware visual tree of a directory, making it vastly superior to the default `list_dir` tool which returns verbose JSON output.
4
+ ---
5
+
6
+ # Smart Directory Mapper (Tree Viewer)
7
+
8
+ When exploring a new project or trying to locate where certain feature files might be stored, DO NOT use the default `list_dir` tool on large folders, as it will flood your context window with raw JSON and token-heavy metadata.
9
+
10
+ Instead, ALWAYS use this `smart_tree` tool.
11
+
12
+ ## Usage
13
+ Run the script using Python. You can provide an optional `max_depth` argument to control how deep the tree goes (default is 3).
14
+
15
+ ```bash
16
+ python .agents/skills/smart_tree/scripts/tree_viewer.py <directory_path> [max_depth]
17
+ ```
18
+
19
+ ### Examples:
20
+ Map the entire project root up to 2 levels deep:
21
+ ```bash
22
+ python .agents/skills/smart_tree/scripts/tree_viewer.py . 2
23
+ ```
24
+
25
+ Map a specific component folder up to 5 levels deep:
26
+ ```bash
27
+ python .agents/skills/smart_tree/scripts/tree_viewer.py src/view/admin 5
28
+ ```
29
+
30
+ ## Advantages
31
+ 1. **Token Efficient**: Outputs a clean visual hierarchy (`ā”œā”€ā”€`, `└──`) instead of raw JSON.
32
+ 2. **Noise Reduction**: Automatically parses `.gitignore` and hard-ignores junk directories (`node_modules`, `vendor`, `.git`, `.agents`, `dist`) so you don't get overwhelmed with irrelevant files.
33
+ 3. **Controlled Depth**: The `max_depth` parameter prevents you from accidentally reading an infinitely deep directory structure.
@@ -0,0 +1,72 @@
1
+ import os
2
+ import sys
3
+ import fnmatch
4
+
5
+ def parse_gitignore(dir_path):
6
+ ignore_patterns = [
7
+ '.git', '.agents', 'node_modules', 'vendor', '__pycache__',
8
+ '.DS_Store', 'dist', 'build', '.idea', '.vscode'
9
+ ]
10
+ gitignore_path = os.path.join(dir_path, '.gitignore')
11
+ if os.path.exists(gitignore_path):
12
+ with open(gitignore_path, 'r', encoding='utf-8', errors='ignore') as f:
13
+ for line in f:
14
+ line = line.strip()
15
+ if line and not line.startswith('#'):
16
+ # Remove trailing slash for directory patterns to match effectively
17
+ if line.endswith('/'):
18
+ line = line[:-1]
19
+ ignore_patterns.append(line)
20
+ return ignore_patterns
21
+
22
+ def is_ignored(name, ignore_patterns):
23
+ for pattern in ignore_patterns:
24
+ if fnmatch.fnmatch(name, pattern) or fnmatch.fnmatch(name, pattern + '/*'):
25
+ return True
26
+ return False
27
+
28
+ def generate_tree(dir_path, prefix="", depth=0, max_depth=3, ignore_patterns=None):
29
+ if depth > max_depth:
30
+ print(f"{prefix}└── ... (max depth reached)")
31
+ return
32
+
33
+ if ignore_patterns is None:
34
+ ignore_patterns = parse_gitignore(dir_path)
35
+
36
+ try:
37
+ entries = sorted(os.listdir(dir_path))
38
+ except PermissionError:
39
+ print(f"{prefix}└── [Permission Denied]")
40
+ return
41
+
42
+ # Filter out ignored entries
43
+ entries = [e for e in entries if not is_ignored(e, ignore_patterns)]
44
+
45
+ entries_count = len(entries)
46
+ for i, entry in enumerate(entries):
47
+ is_last = (i == entries_count - 1)
48
+ entry_path = os.path.join(dir_path, entry)
49
+
50
+ connector = "└── " if is_last else "ā”œā”€ā”€ "
51
+ print(f"{prefix}{connector}{entry}")
52
+
53
+ if os.path.isdir(entry_path):
54
+ extension = " " if is_last else "│ "
55
+ generate_tree(entry_path, prefix + extension, depth + 1, max_depth, ignore_patterns)
56
+
57
+ if __name__ == "__main__":
58
+ sys.stdout.reconfigure(encoding='utf-8')
59
+ if len(sys.argv) < 2:
60
+ print("Usage: python tree_viewer.py <directory_path> [max_depth]")
61
+ sys.exit(1)
62
+
63
+ target_dir = sys.argv[1]
64
+ max_depth = int(sys.argv[2]) if len(sys.argv) > 2 else 3
65
+
66
+ if not os.path.isdir(target_dir):
67
+ print(f"Error: Directory '{target_dir}' does not exist.")
68
+ sys.exit(1)
69
+
70
+ print(f"Directory Tree for: {os.path.abspath(target_dir)} (Max Depth: {max_depth})")
71
+ print(".")
72
+ generate_tree(target_dir, max_depth=max_depth)