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,84 @@
1
+ import os
2
+ import sys
3
+
4
+ if sys.stdout.encoding != 'utf-8':
5
+ sys.stdout.reconfigure(encoding='utf-8')
6
+
7
+ IGNORE_DIRS = {'node_modules', '.git', 'vendor', 'dist', 'build', '.history', 'quarantine', '.backup_replace'}
8
+ KNOWLEDGE_DIR = '.agents/knowledge'
9
+
10
+ def generate_tree(dir_path, prefix=""):
11
+ tree_str = ""
12
+ try:
13
+ entries = sorted(os.listdir(dir_path))
14
+ except Exception:
15
+ return ""
16
+
17
+ entries = [e for e in entries if e not in IGNORE_DIRS]
18
+ for i, entry in enumerate(entries):
19
+ path = os.path.join(dir_path, entry)
20
+ is_last = (i == len(entries) - 1)
21
+ connector = "└── " if is_last else "├── "
22
+
23
+ if os.path.isdir(path):
24
+ tree_str += f"{prefix}{connector}📁 {entry}/\n"
25
+ extension = " " if is_last else "│ "
26
+ tree_str += generate_tree(path, prefix + extension)
27
+ else:
28
+ if not entry.startswith('.'):
29
+ tree_str += f"{prefix}{connector}📄 {entry}\n"
30
+ return tree_str
31
+
32
+ def main():
33
+ apply_mode = "--apply" in sys.argv
34
+
35
+ target_dir = os.getcwd()
36
+ knowledge_path = os.path.join(target_dir, KNOWLEDGE_DIR)
37
+
38
+ struct_path = os.path.join(knowledge_path, 'PROJECT_STRUCTURE.md')
39
+ tree = generate_tree(target_dir)
40
+
41
+ struct_content = "# 🗺️ Project Structure Map\n\n"
42
+ struct_content += "This map was automatically generated by `context_mapper.py`. AI agents must read this file upon entering the project to grasp the architecture without scanning the whole folder.\n\n"
43
+ struct_content += "```text\n"
44
+ struct_content += f"📁 {os.path.basename(target_dir)}/\n"
45
+ struct_content += tree
46
+ struct_content += "```\n"
47
+
48
+ patterns_path = os.path.join(knowledge_path, 'COMMON_PATTERNS.md')
49
+ patterns_content = ""
50
+ if not os.path.exists(patterns_path):
51
+ patterns_content = "# 🧩 Common Patterns & Conventions\n\n"
52
+ patterns_content += "This file contains the fundamental rules of the project. The AI MUST read this file before writing code.\n\n"
53
+ patterns_content += "## 1. Architecture\n- Document architecture conventions here (e.g. all APIs are in `src/services`).\n\n"
54
+ patterns_content += "## 2. Code Style\n- Document styling rules here (e.g. no Tailwind, use Vanilla CSS).\n\n"
55
+ patterns_content += "## 3. Security\n- Never store credentials in code. Always use `.env`.\n"
56
+
57
+ if not apply_mode:
58
+ print("[DRY-RUN MODE] Context Mapper Preview")
59
+ print("=" * 50)
60
+ print(f"Target File: {struct_path}")
61
+ print("--- Content Preview ---")
62
+ print(struct_content[:500] + "\n... (truncated)")
63
+ if patterns_content:
64
+ print(f"\nTarget File: {patterns_path}")
65
+ print("--- Content Preview ---")
66
+ print(patterns_content[:200] + "\n... (truncated)")
67
+ print("=" * 50)
68
+ print("\n💡 PROMPT UNTUK AI (Copy-Paste ini):")
69
+ print('"Pratinjau berhasil. Silakan jalankan ulang perintah dengan tambahan flag --apply untuk menyimpan perubahan ini ke dalam disk."')
70
+ else:
71
+ os.makedirs(knowledge_path, exist_ok=True)
72
+ with open(struct_path, 'w', encoding='utf-8') as f:
73
+ f.write(struct_content)
74
+
75
+ if patterns_content:
76
+ with open(patterns_path, 'w', encoding='utf-8') as f:
77
+ f.write(patterns_content)
78
+
79
+ print(f"[OK] Knowledge Catalog berhasil dibuat/diperbarui di folder `{KNOWLEDGE_DIR}/`.")
80
+ print("\n💡 PROMPT UNTUK AI (Copy-Paste ini):")
81
+ print('"Mulai sekarang, setiap kali Anda menangani proyek ini, tolong panggil view_file pada .agents/knowledge/PROJECT_STRUCTURE.md dan COMMON_PATTERNS.md terlebih dahulu sebelum melakukan pencarian atau menulis kode."')
82
+
83
+ if __name__ == "__main__":
84
+ main()
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Error Trace Analyzer (Crash Decoder)
3
+ description: Use this skill to parse huge raw error logs (stack traces, npm test failures, backend crashes) and instantly find which specific lines in the source code caused the problem, ignoring node_modules noise.
4
+ ---
5
+
6
+ ## Instructions for AI Agent
7
+
8
+ **When to use this skill:**
9
+ - When the user pastes a massive terminal error.
10
+ - When `npm run test` or `npm run dev` fails with a huge traceback.
11
+ - **NEVER read raw tracebacks manually.** Always save the user's terminal output to a temporary `.txt` file in the workspace, then run this tool.
12
+
13
+ **Command to run:**
14
+ ```powershell
15
+ # 1. Save the error to a temporary file (e.g. error.log)
16
+ # 2. Run the decoder:
17
+ python .agents/skills/crash_decoder/decoder.py "error.log"
18
+ ```
19
+
20
+ **Expected Behavior & Next Steps:**
21
+ 1. The tool will output the exact `Error` message and the top 5 relevant file paths and line numbers (e.g. `at Object.<anonymous> (src/index.js:42:15)`).
22
+ 2. After seeing the exact line number, use `view_file` to read that specific line range.
23
+ 3. Fix the bug.
@@ -0,0 +1,66 @@
1
+ import os
2
+ import sys
3
+ import re
4
+
5
+ if sys.stdout.encoding != 'utf-8':
6
+ sys.stdout.reconfigure(encoding='utf-8')
7
+
8
+ def decode_crash(file_path):
9
+ print("🚨 CRASH DECODER 🚨")
10
+ print("=" * 60)
11
+
12
+ if not os.path.exists(file_path):
13
+ print(f"[FAIL] File not found: {file_path}")
14
+ return
15
+
16
+ try:
17
+ with open(file_path, 'r', encoding='utf-8') as f:
18
+ content = f.read()
19
+
20
+ # Common error identifiers
21
+ error_lines = []
22
+ stack_lines = []
23
+
24
+ for line in content.split('\n'):
25
+ line = line.strip()
26
+ # Capture actual error messages
27
+ if re.match(r'^(Error|TypeError|ReferenceError|SyntaxError|UnhandledPromiseRejectionWarning|Exception):', line, re.IGNORECASE):
28
+ error_lines.append(line)
29
+ # Capture stack trace lines
30
+ elif line.startswith('at '):
31
+ # Filter out node_modules and internal node scripts
32
+ if 'node_modules' not in line and 'node:internal' not in line:
33
+ stack_lines.append(line)
34
+ # Python tracebacks
35
+ elif line.startswith('File "') and 'site-packages' not in line and 'Python\\' not in line:
36
+ stack_lines.append(line)
37
+
38
+ if not error_lines and not stack_lines:
39
+ print("[WARN] No standard crash signature found in this log.")
40
+ print("[INFO] Make sure you copy-pasted the entire stack trace.")
41
+ else:
42
+ print("[FAIL] CRASH DETECTED:\n")
43
+ for e in error_lines[:3]: # limit to top 3 errors
44
+ print(f" ❌ {e}")
45
+
46
+ if stack_lines:
47
+ print("\n[INFO] RELEVANT SOURCE CODE TRACE (Noise Filtered):")
48
+ for s in stack_lines[:5]: # limit to top 5 traces
49
+ print(f" 👉 {s}")
50
+
51
+ print("\n" + "=" * 60)
52
+ print("💡 PROMPT UNTUK AI (Copy-Paste ini):")
53
+ print('"Berdasarkan hasil Crash Decoder di atas, tolong gunakan tool view_file untuk memeriksa baris spesifik yang menyebabkan error tersebut, dan berikan solusinya."')
54
+
55
+ except Exception as e:
56
+ print(f"[FAIL] Could not read log file: {e}")
57
+
58
+ def main():
59
+ if len(sys.argv) < 2:
60
+ print("Usage: python decoder.py <path_to_error_log.txt>")
61
+ sys.exit(1)
62
+
63
+ decode_crash(sys.argv[1])
64
+
65
+ if __name__ == "__main__":
66
+ main()
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Database Schema Extractor (db_extractor)
3
+ description: Use this skill when you need to understand the database schema of a project (tables, columns, types, relations) without running raw SQL queries manually in the terminal. It parses `.env` to connect to MySQL/MariaDB or falls back to static code analysis for NoSQL (Firebase, MongoDB) and unknown DBs.
4
+ ---
5
+
6
+ # Database Schema Extractor
7
+
8
+ When a task requires you to interact with the database, DO NOT guess table names or run raw `SHOW TABLES` / `DESCRIBE` queries manually via terminal, as this risks formatting issues and consumes too many tokens.
9
+
10
+ ALWAYS use this `db_extractor` tool first to get a clean Markdown representation of the database schema.
11
+
12
+ ## Usage
13
+ Run the script using Python, passing the path to the project root (where the `.env` file and source code reside).
14
+
15
+ ```bash
16
+ python .agents/skills/db_extractor/scripts/extractor.py <project_root_dir>
17
+ ```
18
+
19
+ ### Examples:
20
+ Extract schema for the current project:
21
+ ```bash
22
+ python .agents/skills/db_extractor/scripts/extractor.py .
23
+ ```
24
+
25
+ Extract schema for a specific backend folder:
26
+ ```bash
27
+ python .agents/skills/db_extractor/scripts/extractor.py src/backend
28
+ ```
29
+
30
+ ## How It Works
31
+ 1. **Network Extraction**: It reads the `.env` file (looking for `DB_CONNECTION`). If it's a supported SQL database (like MySQL) and the Python driver is available, it connects and generates a Markdown table of the schema.
32
+ 2. **Static Analysis Fallback**: If the database is NoSQL (e.g., Firebase, MongoDB), unknown, or the connection fails, the tool automatically switches to **Static Code Analysis Mode**. It scans the project's source code for Prisma files or Model directories (`lib/models`, `app/Models`, etc.) to reconstruct the schema purely from code. This guarantees you will always get structural context!
@@ -0,0 +1,138 @@
1
+ import os
2
+ import sys
3
+ import re
4
+
5
+ def parse_env(env_path):
6
+ config = {}
7
+ if os.path.exists(env_path):
8
+ with open(env_path, 'r', encoding='utf-8') as f:
9
+ for line in f:
10
+ line = line.strip()
11
+ if line and not line.startswith('#') and '=' in line:
12
+ key, val = line.split('=', 1)
13
+ config[key.strip()] = val.strip().strip("'\"")
14
+ return config
15
+
16
+ def static_analysis_fallback(project_root):
17
+ print("### 🔄 Universal Fallback: Static Code Analysis Mode")
18
+ print("Could not connect via network driver or DB is unknown. Scanning source code for models...\n")
19
+
20
+ found_models = False
21
+
22
+ # 1. Check Prisma
23
+ prisma_path = os.path.join(project_root, 'prisma', 'schema.prisma')
24
+ if os.path.exists(prisma_path):
25
+ print("#### Prisma Schema Found")
26
+ found_models = True
27
+ with open(prisma_path, 'r', encoding='utf-8') as f:
28
+ content = f.read()
29
+ # simple extract model blocks
30
+ models = re.findall(r'model\s+(\w+)\s+\{([^}]+)\}', content)
31
+ for m_name, m_body in models:
32
+ print(f"- **{m_name}**")
33
+ lines = [l.strip() for l in m_body.strip().split('\n') if l.strip() and not l.strip().startswith('//')]
34
+ for l in lines:
35
+ print(f" - `{l}`")
36
+ print("")
37
+
38
+ # 2. If not prisma, check if it's a typical MVC / models structure
39
+ if not found_models:
40
+ search_dirs = ['models', 'src/models', 'lib/models', 'app/Models']
41
+ for sdir in search_dirs:
42
+ full_path = os.path.join(project_root, sdir)
43
+ if os.path.exists(full_path) and os.path.isdir(full_path):
44
+ print(f"#### Application Models Found in `{sdir}`")
45
+ found_models = True
46
+ for root, _, files in os.walk(full_path):
47
+ for file in files:
48
+ if file.endswith(('.php', '.js', '.ts', '.dart', '.py')):
49
+ print(f"- `{file}`")
50
+ print("\n*(Tip: Use Selective Reader on these model files to see their exact attributes)*")
51
+ break
52
+
53
+ if not found_models:
54
+ print("No standard database schema or model folder found via static analysis. Project might not have a database layer yet, or it uses a custom structure.")
55
+
56
+
57
+ def extract_mysql(config):
58
+ try:
59
+ import pymysql
60
+ except ImportError:
61
+ print("Error: `pymysql` module is not installed. Run `pip install pymysql` or fallback to static analysis.")
62
+ return False
63
+
64
+ try:
65
+ host = config.get('DB_HOST', '127.0.0.1')
66
+ user = config.get('DB_USERNAME', config.get('DB_USER', 'root'))
67
+ password = config.get('DB_PASSWORD', '')
68
+ database = config.get('DB_DATABASE', config.get('DB_NAME', ''))
69
+
70
+ if not database:
71
+ print("No DB_DATABASE or DB_NAME defined in .env")
72
+ return False
73
+
74
+ connection = pymysql.connect(host=host, user=user, password=password, database=database, cursorclass=pymysql.cursors.DictCursor)
75
+
76
+ with connection.cursor() as cursor:
77
+ cursor.execute("SHOW TABLES")
78
+ tables = [list(r.values())[0] for r in cursor.fetchall()]
79
+
80
+ print(f"### MySQL Database: `{database}`\n")
81
+ for table in tables:
82
+ print(f"#### Table: `{table}`")
83
+ print("| Column | Type | Null | Key | Default | Extra |")
84
+ print("|---|---|---|---|---|---|")
85
+ cursor.execute(f"DESCRIBE `{table}`")
86
+ columns = cursor.fetchall()
87
+ for col in columns:
88
+ print(f"| {col['Field']} | {col['Type']} | {col['Null']} | {col['Key']} | {col['Default']} | {col['Extra']} |")
89
+ print("")
90
+ connection.close()
91
+ return True
92
+ except Exception as e:
93
+ print(f"MySQL Connection Failed: {e}")
94
+ return False
95
+
96
+ def main():
97
+ sys.stdout.reconfigure(encoding='utf-8')
98
+ if len(sys.argv) < 2:
99
+ print("Usage: python extractor.py <project_root_dir>")
100
+ sys.exit(1)
101
+
102
+ project_root = sys.argv[1]
103
+ env_path = os.path.join(project_root, '.env')
104
+
105
+ config = parse_env(env_path)
106
+ db_connection = config.get('DB_CONNECTION', '').lower()
107
+
108
+ # Infer MySQL if missing DB_CONNECTION but has typical MySQL env vars (Node.js style)
109
+ if not db_connection and ('DB_HOST' in config or 'DB_USER' in config):
110
+ db_connection = 'mysql'
111
+
112
+ print(f"Database Extractor (Target: {project_root})\n")
113
+
114
+ success = False
115
+
116
+ if db_connection == 'mysql' or db_connection == 'mariadb':
117
+ print(f"Detected Database Engine: {db_connection.upper()} via .env (or inferred)")
118
+ success = extract_mysql(config)
119
+ elif db_connection == 'sqlite':
120
+ print("Detected Database Engine: SQLITE. (Network extraction not fully implemented here yet, falling back to static analysis).")
121
+ # Could add sqlite3 extraction logic here
122
+ elif db_connection == 'pgsql' or db_connection == 'postgres':
123
+ print("Detected Database Engine: POSTGRESQL. (Network extraction not fully implemented here yet, falling back to static analysis).")
124
+ # Could add psycopg2 extraction logic here
125
+ elif db_connection == 'firebase' or db_connection == 'firestore':
126
+ print("Detected Database Engine: FIREBASE (NoSQL). Firebase is schemaless.")
127
+ # Firebase has no fixed schema, force fallback to model analysis
128
+ else:
129
+ if db_connection:
130
+ print(f"Unknown or Unsupported Database Engine: '{db_connection}'")
131
+ else:
132
+ print("No DB_CONNECTION found in .env, or .env is missing.")
133
+
134
+ if not success:
135
+ static_analysis_fallback(project_root)
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: Deep Analyzer (Project Profiler)
3
+ description: Use this skill to automatically extract project tech stack, dependencies, test commands, and file statistics without manually reading package.json or config files, saving tokens and time.
4
+ ---
5
+
6
+ ## Instructions for AI Agent
7
+
8
+ **When to use this skill:**
9
+ - When the user asks you to "analyze the project" or "find potential bugs" and you need to know what test scripts are available.
10
+ - When you first enter a new project and need to know the Tech Stack (e.g. React vs Vue, Vite vs Next.js).
11
+ - When you want to see what dependencies are installed without wasting 1000+ tokens reading the entire `package.json`.
12
+
13
+ **Command to run:**
14
+ ```powershell
15
+ python .agents/skills/deep_analyzer/analyzer.py
16
+ ```
17
+
18
+ **Expected Behavior & Next Steps:**
19
+ 1. The script will output the Tech Stack, available NPM/Yarn commands (like `npm run test`), core dependencies, and directory statistics.
20
+ 2. If a test script like `npm run test` or `npm run lint` is found, you can use `run_command` to execute it to find potential bugs!
21
+ 3. NEVER read `package.json` manually using `view_file` unless you specifically need to modify a version number. Always use `analyzer.py` to get the overview.
@@ -0,0 +1,97 @@
1
+ import os
2
+ import sys
3
+ import json
4
+
5
+ if sys.stdout.encoding != 'utf-8':
6
+ sys.stdout.reconfigure(encoding='utf-8')
7
+
8
+ def analyze_project(target_dir):
9
+ print("\n🔬 DEEP ANALYZER: Project Profiler 🔬")
10
+ print("=" * 60)
11
+
12
+ # 1. Tech Stack Detection
13
+ stack = []
14
+ if os.path.exists(os.path.join(target_dir, 'package.json')):
15
+ stack.append("Node.js")
16
+ if os.path.exists(os.path.join(target_dir, 'requirements.txt')) or os.path.exists(os.path.join(target_dir, 'Pipfile')):
17
+ stack.append("Python")
18
+ if os.path.exists(os.path.join(target_dir, 'vite.config.js')) or os.path.exists(os.path.join(target_dir, 'vite.config.ts')):
19
+ stack.append("Vite (React/Vue)")
20
+ if os.path.exists(os.path.join(target_dir, 'next.config.js')):
21
+ stack.append("Next.js")
22
+ if os.path.exists(os.path.join(target_dir, 'composer.json')):
23
+ stack.append("PHP/Laravel")
24
+
25
+ stack_str = ", ".join(stack) if stack else "Unknown (Vanilla/Other)"
26
+ print(f"[{'OK' if stack else 'WARN'}] Tech Stack Detected: {stack_str}")
27
+
28
+ # 2. Package.json Parsing (Commands & Deps)
29
+ pkg_path = os.path.join(target_dir, 'package.json')
30
+ if os.path.exists(pkg_path):
31
+ try:
32
+ with open(pkg_path, 'r', encoding='utf-8') as f:
33
+ pkg = json.load(f)
34
+
35
+ scripts = pkg.get('scripts', {})
36
+ if scripts:
37
+ print("\n[INFO] Available npm/yarn Commands:")
38
+ for name, cmd in scripts.items():
39
+ print(f" - npm run {name:<12} : {cmd}")
40
+ else:
41
+ print("\n[WARN] No scripts found in package.json")
42
+
43
+ deps = pkg.get('dependencies', {})
44
+ dev_deps = pkg.get('devDependencies', {})
45
+ print(f"\n[INFO] Core Dependencies: {len(deps)} runtime, {len(dev_deps)} dev")
46
+ # Print top 5 dependencies just for context
47
+ top_deps = list(deps.keys())[:5]
48
+ if top_deps:
49
+ print(f" - Key libraries: {', '.join(top_deps)}...")
50
+
51
+ except Exception as e:
52
+ print(f"[FAIL] Could not parse package.json: {e}")
53
+
54
+ # 3. Quick Directory Stats
55
+ print("\n[INFO] Directory Statistics:")
56
+ ignore_dirs = {'.git', 'node_modules', 'vendor', 'dist', 'build', '.history'}
57
+ file_counts = {'.js': 0, '.jsx': 0, '.ts': 0, '.tsx': 0, '.py': 0, '.php': 0, '.html': 0, '.css': 0}
58
+ total_files = 0
59
+ total_size = 0
60
+
61
+ for root, dirs, files in os.walk(target_dir):
62
+ dirs[:] = [d for d in dirs if d not in ignore_dirs]
63
+ for file in files:
64
+ total_files += 1
65
+ filepath = os.path.join(root, file)
66
+ try:
67
+ total_size += os.path.getsize(filepath)
68
+ except: pass
69
+
70
+ ext = os.path.splitext(file)[1].lower()
71
+ if ext in file_counts:
72
+ file_counts[ext] += 1
73
+
74
+ mb_size = total_size / (1024 * 1024)
75
+ print(f" - Total Scanned Files: {total_files} ({mb_size:.2f} MB)")
76
+
77
+ active_exts = {k: v for k, v in file_counts.items() if v > 0}
78
+ ext_str = ", ".join([f"{k} ({v})" for k, v in active_exts.items()])
79
+ if ext_str:
80
+ print(f" - Source Files: {ext_str}")
81
+
82
+ def main():
83
+ target = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
84
+
85
+ if not os.path.isdir(target):
86
+ print(f"[FAIL] Target is not a directory: {target}")
87
+ sys.exit(1)
88
+
89
+ analyze_project(target)
90
+
91
+ print("\n" + "=" * 60)
92
+ print("[OK] Analisis proyek selesai dengan cepat tanpa membebani token.")
93
+ print("\n💡 PROMPT UNTUK AI (Copy-Paste ini):")
94
+ print('"Berdasarkan hasil Deep Analyzer di atas, gunakan perintah npm/test yang tersedia jika Anda perlu memvalidasi bug, atau gunakan alat lain untuk menyelidiki file spesifik."')
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: Smart Import Fixer
3
+ description: Use this skill to automatically find and fix broken relative imports (e.g. ones reported by Project Guardian).
4
+ ---
5
+
6
+ ## Instructions for AI Agent
7
+
8
+ **When to use this skill:**
9
+ - When Project Guardian reports `Relative import '../services/api' does not exist physically!`.
10
+ - **NEVER use grep to manually hunt for the correct path.** Let this tool compute the exact relative path (`../../`) automatically.
11
+
12
+ **Command to run:**
13
+ ```powershell
14
+ # 1. Dry Run (Preview the correct path)
15
+ python .agents/skills/import_fixer/fixer.py "src/backend/routes/user.js" "../services/api"
16
+
17
+ # 2. Apply Changes (Actually fixes the file)
18
+ python .agents/skills/import_fixer/fixer.py "src/backend/routes/user.js" "../services/api" --apply
19
+ ```
20
+
21
+ **Expected Behavior:**
22
+ 1. The tool will search the entire project for the basename (`api.js` or `api.jsx`).
23
+ 2. It will compute the exact relative path from the `source_file` to the `target_file`.
24
+ 3. If `--apply` is used, it safely backs up the file and rewrites the import string.
@@ -0,0 +1,111 @@
1
+ import os
2
+ import sys
3
+ import re
4
+ import shutil
5
+ from datetime import datetime
6
+
7
+ if sys.stdout.encoding != 'utf-8':
8
+ sys.stdout.reconfigure(encoding='utf-8')
9
+
10
+ IGNORE_DIRS = {'.git', 'node_modules', 'vendor', 'dist', 'build', '.history'}
11
+ EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
12
+
13
+ def find_file(filename, root_dir):
14
+ matches = []
15
+ # If filename has no extension, we will search for it with common extensions
16
+ has_ext = any(filename.endswith(ext) for ext in EXTENSIONS)
17
+ search_names = [filename] if has_ext else [filename + ext for ext in EXTENSIONS]
18
+
19
+ for r, dirs, files in os.walk(root_dir):
20
+ dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
21
+ for f in files:
22
+ if f in search_names:
23
+ matches.append(os.path.join(r, f))
24
+ return matches
25
+
26
+ def compute_relative_path(source_file, target_file):
27
+ source_dir = os.path.dirname(os.path.abspath(source_file))
28
+ target_abs = os.path.abspath(target_file)
29
+ # Get relative path from source_dir to target_file
30
+ rel_path = os.path.relpath(target_abs, source_dir)
31
+ # Convert Windows backslashes to forward slashes for imports
32
+ rel_path = rel_path.replace('\\', '/')
33
+ # Ensure it starts with ./ or ../
34
+ if not rel_path.startswith('.'):
35
+ rel_path = './' + rel_path
36
+ # Remove extension for JS/TS imports
37
+ for ext in EXTENSIONS:
38
+ if rel_path.endswith(ext):
39
+ rel_path = rel_path[:-len(ext)]
40
+ break
41
+ return rel_path
42
+
43
+ def fix_import(source_file, broken_import, apply_mode):
44
+ print("🔗 SMART IMPORT FIXER 🔗")
45
+ print("=" * 60)
46
+
47
+ if not os.path.exists(source_file):
48
+ print(f"[FAIL] Source file not found: {source_file}")
49
+ return
50
+
51
+ basename = os.path.basename(broken_import)
52
+ print(f"[INFO] Searching for actual location of '{basename}'...")
53
+
54
+ root_dir = os.getcwd()
55
+ matches = find_file(basename, root_dir)
56
+
57
+ if not matches:
58
+ print(f"[FAIL] Could not find any file named {basename} in the project.")
59
+ return
60
+
61
+ target_file = matches[0]
62
+ if len(matches) > 1:
63
+ print(f"[WARN] Multiple files found. Using the first one: {target_file}")
64
+
65
+ new_import = compute_relative_path(source_file, target_file)
66
+ print(f"\n[INFO] Broken Import : {broken_import}")
67
+ print(f"[INFO] Correct Import: {new_import}")
68
+
69
+ try:
70
+ with open(source_file, 'r', encoding='utf-8') as f:
71
+ content = f.read()
72
+
73
+ if broken_import not in content:
74
+ print(f"[WARN] The string '{broken_import}' was not found in {source_file}.")
75
+ return
76
+
77
+ new_content = content.replace(broken_import, new_import)
78
+
79
+ if not apply_mode:
80
+ print("\n" + "=" * 60)
81
+ print("[OK] Dry-run complete. Found the correct path.")
82
+ print("\n💡 PROMPT UNTUK AI (Copy-Paste ini):")
83
+ print(f'"Berdasarkan hasil pencarian Smart Import Fixer di atas, jalankan ulang perintah dengan tambahan flag --apply untuk memperbaiki rute secara otomatis."')
84
+ else:
85
+ backup_dir = os.path.join(root_dir, '.backup_replace', datetime.now().strftime("%Y%m%d_%H%M%S"))
86
+ os.makedirs(backup_dir, exist_ok=True)
87
+ backup_path = os.path.join(backup_dir, os.path.basename(source_file) + ".bak")
88
+ shutil.copy2(source_file, backup_path)
89
+
90
+ with open(source_file, 'w', encoding='utf-8') as f:
91
+ f.write(new_content)
92
+
93
+ print("\n" + "=" * 60)
94
+ print(f"[OK] Import fixed! Backup saved to {backup_dir}")
95
+
96
+ except Exception as e:
97
+ print(f"[FAIL] Error processing file: {e}")
98
+
99
+ def main():
100
+ if len(sys.argv) < 3:
101
+ print("Usage: python fixer.py <source_file> <broken_import_string> [--apply]")
102
+ sys.exit(1)
103
+
104
+ source_file = sys.argv[1]
105
+ broken_import = sys.argv[2]
106
+ apply_mode = "--apply" in sys.argv
107
+
108
+ fix_import(source_file, broken_import, apply_mode)
109
+
110
+ if __name__ == "__main__":
111
+ main()
package/install.ps1 ADDED
@@ -0,0 +1,68 @@
1
+ Write-Host "Installing Snowline Agent Ecosystem (Project-Level)..." -ForegroundColor Cyan
2
+
3
+ $ProjectRoot = (Get-Location).Path
4
+ $AgentsDir = Join-Path $ProjectRoot ".agents"
5
+ $SkillsDir = Join-Path $AgentsDir "skills"
6
+ $KnowledgeDir = Join-Path $AgentsDir "knowledge"
7
+ $RepoUrl = "https://github.com/UsmanAzizz/snowline-agent-tools.git"
8
+ $IsNewInstall = $false
9
+
10
+ # Ensure .agents and .agents/knowledge exist
11
+ if (-not (Test-Path $AgentsDir)) {
12
+ New-Item -ItemType Directory -Force -Path $AgentsDir | Out-Null
13
+ }
14
+ if (-not (Test-Path $KnowledgeDir)) {
15
+ New-Item -ItemType Directory -Force -Path $KnowledgeDir | Out-Null
16
+ }
17
+
18
+ # Scaffold skills
19
+ if (Test-Path $SkillsDir) {
20
+ Write-Host "Found existing skills directory at $SkillsDir" -ForegroundColor Yellow
21
+ if (Test-Path (Join-Path $SkillsDir ".git")) {
22
+ Write-Host "Pulling latest updates..." -ForegroundColor Cyan
23
+ Set-Location $SkillsDir
24
+ try {
25
+ git pull origin main
26
+ } catch {
27
+ Write-Host "Failed to update repository." -ForegroundColor Red
28
+ }
29
+ Set-Location $ProjectRoot
30
+ } else {
31
+ Write-Host "Existing skills directory is not a git repository. Skipping git pull." -ForegroundColor Yellow
32
+ }
33
+ } else {
34
+ $IsNewInstall = $true
35
+ Write-Host "Downloading Snowline Agent skills..." -ForegroundColor Cyan
36
+ try {
37
+ git clone $RepoUrl $SkillsDir
38
+ } catch {
39
+ Write-Host "Failed to clone repository. Make sure git is installed." -ForegroundColor Red
40
+ exit 1
41
+ }
42
+ }
43
+
44
+ # Copy AGENTS_TEMPLATE.md to AGENTS.md
45
+ $TemplatePath = Join-Path $SkillsDir "AGENTS_TEMPLATE.md"
46
+ $LocalAgentsPath = Join-Path $AgentsDir "AGENTS.md"
47
+
48
+ if (Test-Path $TemplatePath) {
49
+ if (-not (Test-Path $LocalAgentsPath)) {
50
+ Write-Host "Creating Project AGENTS.md..." -ForegroundColor Cyan
51
+ Copy-Item -Path $TemplatePath -Destination $LocalAgentsPath -Force
52
+ Write-Host "Project AGENTS.md created successfully." -ForegroundColor Green
53
+ } else {
54
+ Write-Host "Project AGENTS.md already exists. Skipping overwrite." -ForegroundColor Yellow
55
+ }
56
+ }
57
+
58
+ # Scaffold PLAN.md
59
+ $PlanPath = Join-Path $ProjectRoot "PLAN.md"
60
+ if (-not (Test-Path $PlanPath)) {
61
+ Write-Host "Creating PLAN.md..." -ForegroundColor Cyan
62
+ Set-Content -Path $PlanPath -Value "# Project Plan / Task Tracker`n`n- [ ] Initial task" -Encoding UTF8
63
+ }
64
+
65
+ if ($IsNewInstall) {
66
+ Write-Host "`nInstallation Complete!" -ForegroundColor Green
67
+ Write-Host "This project is now powered by the Snowline Agent Ecosystem." -ForegroundColor Cyan
68
+ }