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.
- package/AGENTS_TEMPLATE.md +116 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/auto_scaffolder/SKILL.md +23 -0
- package/auto_scaffolder/scaffolder.py +115 -0
- package/bin/install.js +71 -0
- package/clean_sweeper/SKILL.md +23 -0
- package/clean_sweeper/sweeper.py +98 -0
- package/context_mapper/SKILL.md +21 -0
- package/context_mapper/context_mapper.py +84 -0
- package/crash_decoder/SKILL.md +23 -0
- package/crash_decoder/decoder.py +66 -0
- package/db_extractor/SKILL.md +32 -0
- package/db_extractor/scripts/extractor.py +138 -0
- package/deep_analyzer/SKILL.md +21 -0
- package/deep_analyzer/analyzer.py +97 -0
- package/import_fixer/SKILL.md +24 -0
- package/import_fixer/fixer.py +111 -0
- package/install.ps1 +68 -0
- package/install.sh +58 -0
- package/package.json +31 -0
- package/plan_tracker/PLAN_TEMPLATE.md +20 -0
- package/project_guardian/SKILL.md +26 -0
- package/project_guardian/guardian.py +206 -0
- package/run_all.py +115 -0
- package/selective_reader/SKILL.md +27 -0
- package/selective_reader/reader.py +67 -0
- package/smart_replace/SKILL.md +35 -0
- package/smart_replace/replace_text.py +113 -0
- package/smart_search/SKILL.md +27 -0
- package/smart_search/code_finder.py +103 -0
- package/smart_tree/SKILL.md +33 -0
- package/smart_tree/scripts/tree_viewer.py +72 -0
|
@@ -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
|
+
}
|