dasa-sradha-kit 5.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/.agent/.shared/infinite-memory.md +19 -0
- package/.agent/.shared/max-power-core.md +27 -0
- package/.agent/ARCHITECTURE.md +104 -0
- package/.agent/agents/dasa-dharma.md +21 -0
- package/.agent/agents/dasa-dwipa.md +21 -0
- package/.agent/agents/dasa-indra.md +21 -0
- package/.agent/agents/dasa-kala.md +21 -0
- package/.agent/agents/dasa-mpu.md +21 -0
- package/.agent/agents/dasa-nala.md +21 -0
- package/.agent/agents/dasa-patih.md +21 -0
- package/.agent/agents/dasa-rsi.md +25 -0
- package/.agent/agents/dasa-sastra.md +21 -0
- package/.agent/agents/dasa-widya.md +21 -0
- package/.agent/rules/GEMINI.md +183 -0
- package/.agent/scripts/api_validator.py +70 -0
- package/.agent/scripts/arch_mapper.py +101 -0
- package/.agent/scripts/compact_memory.py +68 -0
- package/.agent/scripts/complexity_scorer.py +82 -0
- package/.agent/scripts/context_mapper.py +91 -0
- package/.agent/scripts/design_engine.py +108 -0
- package/.agent/scripts/design_memory_sync.py +87 -0
- package/.agent/scripts/lint_fixer.py +79 -0
- package/.agent/scripts/qa_gate.py +84 -0
- package/.agent/scripts/security_scan.py +82 -0
- package/.agent/scripts/semantic-scan.py +56 -0
- package/.agent/scripts/skill_search.py +91 -0
- package/.agent/scripts/status_parser.py +78 -0
- package/.agent/scripts/test_runner.py +98 -0
- package/.agent/scripts/validate_env.py +71 -0
- package/.agent/scripts/web_scraper.py +86 -0
- package/.agent/scripts/workspace-mapper.py +58 -0
- package/.agent/skills/.gitkeep +0 -0
- package/.agent/workflows/dasa-api.md +42 -0
- package/.agent/workflows/dasa-assimilate.md +44 -0
- package/.agent/workflows/dasa-commit.md +46 -0
- package/.agent/workflows/dasa-docs.md +46 -0
- package/.agent/workflows/dasa-e2e.md +41 -0
- package/.agent/workflows/dasa-feature.md +46 -0
- package/.agent/workflows/dasa-fix.md +37 -0
- package/.agent/workflows/dasa-init.md +29 -0
- package/.agent/workflows/dasa-plan.md +56 -0
- package/.agent/workflows/dasa-pr.md +47 -0
- package/.agent/workflows/dasa-refactor.md +44 -0
- package/.agent/workflows/dasa-seed.md +44 -0
- package/.agent/workflows/dasa-start-work.md +51 -0
- package/.agent/workflows/dasa-status.md +58 -0
- package/.agent/workflows/dasa-sync.md +39 -0
- package/.agent/workflows/dasa-uninstall.md +30 -0
- package/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/bin/cli.js +218 -0
- package/bin/dasa-cli.js +100 -0
- package/package.json +37 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Indra: The native QA Gate (qa_gate.py)
|
|
4
|
+
Assimilates ~800 patterns from `engineering-failures-bible`.
|
|
5
|
+
Provides native python text scanning against common language-specific pitfalls
|
|
6
|
+
before a task can be marked complete.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Embedded Failure Patterns (TOON Heuristics)
|
|
16
|
+
FAILURES = {
|
|
17
|
+
"01_Memory": [
|
|
18
|
+
(r"new\s+[a-zA-Z]+\(.*\)\s*;(?!.*\bdelete\b)", "Unmatched 'new' allocation without 'delete' (C++/Leaks)"),
|
|
19
|
+
(r"fmt\.Sprintf\(\s*\"%s\"", "String concatenation in loops instead of strings.Builder (Go/Memory)"),
|
|
20
|
+
(r"\.collect::\<Vec<\w+>\>\(\)", "Unbounded heavy .collect() instead of iterators (Rust/Memory)")
|
|
21
|
+
],
|
|
22
|
+
"02_Concurrency": [
|
|
23
|
+
(r"sync\.Mutex.*defer\s+.*Unlock", "Missing careful Lock bounds via defer (Go/Deadlock risk)"),
|
|
24
|
+
(r"std::sync::Mutex.*\.unwrap\(\)", "Poisoning panic risk on Mutex.unwrap() (Rust/Concurrency)"),
|
|
25
|
+
(r"Promise\.all\(.*\.(map|forEach)\(async", "Unbounded concurrent Promise execution. Use Promise.allSettled or batching (Node/EventLoop)")
|
|
26
|
+
],
|
|
27
|
+
"03_Security": [
|
|
28
|
+
(r"SELECT\s+.*\s+FROM\s+.*\s+WHERE\s+.*=\s*\S+\s*\+", "Raw string concatenation in SQL queries (SQL Injection)"),
|
|
29
|
+
(r"eval\(", "Use of eval() detected (RCE vulnerability)"),
|
|
30
|
+
(r"dangerouslySetInnerHTML", "Potential XSS detected in React/Next.js component")
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def scan_file(filepath: Path) -> list:
|
|
35
|
+
issues = []
|
|
36
|
+
try:
|
|
37
|
+
content = filepath.read_text(encoding="utf-8")
|
|
38
|
+
lines = content.splitlines()
|
|
39
|
+
for idx, line in enumerate(lines, 1):
|
|
40
|
+
for domain, patterns in FAILURES.items():
|
|
41
|
+
for regex, desc in patterns:
|
|
42
|
+
if re.search(regex, line):
|
|
43
|
+
issues.append(f"[FAIL] {domain} | {filepath.name}:{idx} -> {desc}")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"Skipping {filepath} ({e})")
|
|
46
|
+
return issues
|
|
47
|
+
|
|
48
|
+
def main():
|
|
49
|
+
parser = argparse.ArgumentParser(description="Dasa Indra QA Gate Scanner")
|
|
50
|
+
parser.add_argument("target", help="Directory or file to scan")
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
target_path = Path(args.target)
|
|
54
|
+
if not target_path.exists():
|
|
55
|
+
print(f"Error: Target {target_path} does not exist.")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
print(f"🕵️♂️ Dasa Indra: Initiating Engineering Failure Scan on {target_path}...")
|
|
59
|
+
|
|
60
|
+
total_issues = []
|
|
61
|
+
if target_path.is_file():
|
|
62
|
+
total_issues.extend(scan_file(target_path))
|
|
63
|
+
else:
|
|
64
|
+
for root, _, files in os.walk(target_path):
|
|
65
|
+
if ".git" in root or "node_modules" in root or "target" in root:
|
|
66
|
+
continue
|
|
67
|
+
for file in files:
|
|
68
|
+
ext = Path(file).suffix
|
|
69
|
+
if ext in [".js", ".ts", ".go", ".rs", ".java", ".php", ".py", ".cpp"]:
|
|
70
|
+
full_path = Path(root) / file
|
|
71
|
+
total_issues.extend(scan_file(full_path))
|
|
72
|
+
|
|
73
|
+
if total_issues:
|
|
74
|
+
print("\n❌ ENGINEERING FAILURES DETECTED:")
|
|
75
|
+
for issue in total_issues:
|
|
76
|
+
print(f" {issue}")
|
|
77
|
+
print("\n>> Dasa Nala is BLOCKED from completing this task. Fix the issues first.")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
else:
|
|
80
|
+
print("\n✅ QA Gate Passed. No critical engineering failures detected.")
|
|
81
|
+
sys.exit(0)
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Dharma: Secret Guardian (security_scan.py)
|
|
4
|
+
Scans git diffs or specific files for leaked API keys, tokens, and secrets.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
SECRET_PATTERNS = {
|
|
13
|
+
"AWS Access Key": r"AKIA[0-9A-Z]{16}",
|
|
14
|
+
"Stripe API Key": r"sk_(test|live)_[0-9a-zA-Z]{24}",
|
|
15
|
+
"Google API Key": r"AIza[0-9A-Za-z-_]{35}",
|
|
16
|
+
"Generic Bearer/Token": r"(?i)(bearer|token|api_key|secret)['\"\s:=]+[A-Za-z0-9\-_]{20,}",
|
|
17
|
+
"Private Key": r"-----BEGIN (RSA|OPENSSH|DSA|EC|PGP) PRIVATE KEY-----"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def get_git_diff():
|
|
21
|
+
"""Get the current staged and unstaged git changes."""
|
|
22
|
+
try:
|
|
23
|
+
# Get staged changes
|
|
24
|
+
staged = subprocess.check_output(["git", "diff", "--cached"]).decode('utf-8')
|
|
25
|
+
# Get unstaged changes
|
|
26
|
+
unstaged = subprocess.check_output(["git", "diff"]).decode('utf-8')
|
|
27
|
+
return staged + "\n" + unstaged
|
|
28
|
+
except subprocess.CalledProcessError:
|
|
29
|
+
print("🟡 [Dharma Guardian] Not a git repository or no commits yet. Skipping diff scan.")
|
|
30
|
+
return ""
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"🔴 [Dharma Guardian] Error executing git diff: {e}")
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
def scan_diff(diff_text):
|
|
36
|
+
"""Scan the diff text for secret patterns."""
|
|
37
|
+
leaks = []
|
|
38
|
+
|
|
39
|
+
# Only scan added/modified lines in diff (+ but not +++)
|
|
40
|
+
diff_lines = [line for line in diff_text.split('\n') if line.startswith('+') and not line.startswith('+++')]
|
|
41
|
+
|
|
42
|
+
for line in diff_lines:
|
|
43
|
+
for name, pattern in SECRET_PATTERNS.items():
|
|
44
|
+
if re.search(pattern, line):
|
|
45
|
+
leaks.append((name, line.strip()))
|
|
46
|
+
|
|
47
|
+
return leaks
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
print("🛡️ [Dasa Dharma] Initializing Secret Guardian Scan...")
|
|
51
|
+
|
|
52
|
+
# 1. Scan .env files (preventing accidental .env commits)
|
|
53
|
+
try:
|
|
54
|
+
git_status = subprocess.check_output(["git", "status", "-s"]).decode('utf-8')
|
|
55
|
+
status_lines = git_status.split('\n')
|
|
56
|
+
for line in status_lines:
|
|
57
|
+
if '.env' in line and not '.example' in line:
|
|
58
|
+
print(f"🔴 [Dharma Guardian] FATAL: You are attempting to commit a raw .env file!\nLine: {line.strip()}")
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
except subprocess.CalledProcessError:
|
|
61
|
+
pass # Not a git repo
|
|
62
|
+
|
|
63
|
+
# 2. Scan Git Diff for hardcoded secrets
|
|
64
|
+
diff_text = get_git_diff()
|
|
65
|
+
if not diff_text:
|
|
66
|
+
print("🟢 [Dharma Guardian] No changes to scan. Pass.")
|
|
67
|
+
sys.exit(0)
|
|
68
|
+
|
|
69
|
+
leaks = scan_diff(diff_text)
|
|
70
|
+
|
|
71
|
+
if leaks:
|
|
72
|
+
print("\n🔴 [Dharma Guardian] FATAL: Potential Secret Leaks Detected in `git diff`:")
|
|
73
|
+
for name, line in leaks:
|
|
74
|
+
print(f" - [{name}] Found in line: {line[:80]}...")
|
|
75
|
+
print("\nHALTING COMMIT. Remove hardcoded secrets and use environment variables.")
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
print("🟢 [Dharma Guardian] Security Audit Passed. No obvious secrets leaked.")
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
semantic-scan.py — Cross-platform semantic search wrapper (uses osgrep)
|
|
4
|
+
Persona: Dasa Dwipa (The Scout)
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python .agent/scripts/semantic-scan.py "JWT authentication middleware"
|
|
8
|
+
python .agent/scripts/semantic-scan.py "where is the login form handled"
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import subprocess
|
|
13
|
+
import shutil
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
def check_osgrep():
|
|
17
|
+
return shutil.which("osgrep") is not None
|
|
18
|
+
|
|
19
|
+
def run_semantic_search(query: str) -> int:
|
|
20
|
+
if not check_osgrep():
|
|
21
|
+
print("[!] osgrep is not installed. Install it with: npm install -g osgrep")
|
|
22
|
+
print(f"[!] Falling back to grep for: {query}")
|
|
23
|
+
result = subprocess.run(
|
|
24
|
+
["grep", "-r", "--include=*.ts", "--include=*.py", "--include=*.js",
|
|
25
|
+
"--include=*.php", "-n", query, "."],
|
|
26
|
+
capture_output=True, text=True
|
|
27
|
+
)
|
|
28
|
+
if result.stdout:
|
|
29
|
+
print(result.stdout[:3000])
|
|
30
|
+
return result.returncode
|
|
31
|
+
|
|
32
|
+
# osgrep available
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
["osgrep", "search", query],
|
|
36
|
+
capture_output=True, text=True, timeout=30
|
|
37
|
+
)
|
|
38
|
+
print(result.stdout)
|
|
39
|
+
if result.stderr:
|
|
40
|
+
print(result.stderr, file=sys.stderr)
|
|
41
|
+
return result.returncode
|
|
42
|
+
except subprocess.TimeoutExpired:
|
|
43
|
+
print("[x] osgrep search timed out after 30s")
|
|
44
|
+
return 1
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
print("[x] osgrep not found in PATH")
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
if len(sys.argv) < 2:
|
|
51
|
+
print("Usage: python semantic-scan.py <query>")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
query = " ".join(sys.argv[1:])
|
|
55
|
+
print(f"[+] Semantic search: {query}")
|
|
56
|
+
sys.exit(run_semantic_search(query))
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Dwipa: The Local Skill Indexer (skill_search.py)
|
|
4
|
+
A zero-dependency semantic search to find skills locally without any cloud services.
|
|
5
|
+
Scans both `.agent/skills/` and `~/.gemini/antigravity/skills/`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
def extract_yaml_frontmatter(content):
|
|
14
|
+
match = re.search(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
15
|
+
if not match:
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
yaml_text = match.group(1)
|
|
19
|
+
metadata = {}
|
|
20
|
+
for line in yaml_text.split("\n"):
|
|
21
|
+
if ":" in line:
|
|
22
|
+
key, val = line.split(":", 1)
|
|
23
|
+
metadata[key.strip()] = val.strip().strip("'\"")
|
|
24
|
+
return metadata
|
|
25
|
+
|
|
26
|
+
def parse_skills_in_directory(dir_path: Path):
|
|
27
|
+
skills = []
|
|
28
|
+
if not dir_path.exists() or not dir_path.is_dir():
|
|
29
|
+
return skills
|
|
30
|
+
|
|
31
|
+
for root, _, files in os.walk(dir_path):
|
|
32
|
+
for file in files:
|
|
33
|
+
if file == "SKILL.md":
|
|
34
|
+
skill_path = Path(root) / file
|
|
35
|
+
try:
|
|
36
|
+
content = skill_path.read_text(encoding="utf-8")
|
|
37
|
+
meta = extract_yaml_frontmatter(content)
|
|
38
|
+
if "name" in meta and "description" in meta:
|
|
39
|
+
skills.append({
|
|
40
|
+
"name": meta["name"],
|
|
41
|
+
"description": meta["description"],
|
|
42
|
+
"path": str(skill_path.parent)
|
|
43
|
+
})
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
return skills
|
|
47
|
+
|
|
48
|
+
def score_skill(skill, query_words):
|
|
49
|
+
text_corpus = (skill["name"] + " " + skill["description"]).lower()
|
|
50
|
+
score = 0
|
|
51
|
+
for word in query_words:
|
|
52
|
+
if word in text_corpus:
|
|
53
|
+
score += 1
|
|
54
|
+
return score
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
if len(sys.argv) < 2:
|
|
58
|
+
print("Usage: skill_search.py <query>")
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
query = " ".join(sys.argv[1:]).lower()
|
|
62
|
+
query_words = set(re.findall(r'\w+', query))
|
|
63
|
+
|
|
64
|
+
global_dir = Path.home() / ".gemini" / "antigravity" / "skills"
|
|
65
|
+
local_dir = Path(os.getcwd()) / ".agent" / "skills"
|
|
66
|
+
|
|
67
|
+
all_skills = parse_skills_in_directory(global_dir) + parse_skills_in_directory(local_dir)
|
|
68
|
+
|
|
69
|
+
if not all_skills:
|
|
70
|
+
print("No skills found on the local machine.")
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
for skill in all_skills:
|
|
74
|
+
skill["score"] = score_skill(skill, query_words)
|
|
75
|
+
|
|
76
|
+
ranked_skills = sorted(all_skills, key=lambda x: x["score"], reverse=True)
|
|
77
|
+
|
|
78
|
+
print(f"🔍 Dasa Dwipa: Ranked Skills for '{query}'\n")
|
|
79
|
+
found_any = False
|
|
80
|
+
for skill in ranked_skills[:3]:
|
|
81
|
+
if skill["score"] > 0:
|
|
82
|
+
found_any = True
|
|
83
|
+
print(f"✨ {skill['name']} (Score: {skill['score']})")
|
|
84
|
+
print(f" Desc: {skill['description']}")
|
|
85
|
+
print(f" Path: {skill['path']}\n")
|
|
86
|
+
|
|
87
|
+
if not found_any:
|
|
88
|
+
print("No relevant skills matched your query. Try broadening your terms.")
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Kala: The Reporter (status_parser.py)
|
|
4
|
+
Merges data from task.md and git diff --stat to output a 3-line JSON summary.
|
|
5
|
+
Prevents Kala from wasting context reading entire task checklists.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import subprocess
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
def get_task_stats():
|
|
14
|
+
"""Read task.md and count checkboxes to determine progress."""
|
|
15
|
+
task_path = ".artifacts/task.md"
|
|
16
|
+
if not os.path.exists(task_path):
|
|
17
|
+
# Fallback to older location
|
|
18
|
+
task_path = ".agent/task.toon"
|
|
19
|
+
if not os.path.exists(task_path):
|
|
20
|
+
return {"total": 0, "completed": 0, "in_progress": 0}
|
|
21
|
+
|
|
22
|
+
total = 0
|
|
23
|
+
completed = 0
|
|
24
|
+
in_progress = 0
|
|
25
|
+
|
|
26
|
+
with open(task_path, "r") as f:
|
|
27
|
+
for line in f.readlines():
|
|
28
|
+
line = line.strip()
|
|
29
|
+
if line.startswith("- [ ]"):
|
|
30
|
+
total += 1
|
|
31
|
+
elif line.startswith("- [x]") or line.startswith("- [X]"):
|
|
32
|
+
total += 1
|
|
33
|
+
completed += 1
|
|
34
|
+
elif line.startswith("- [/]"):
|
|
35
|
+
total += 1
|
|
36
|
+
in_progress += 1
|
|
37
|
+
|
|
38
|
+
return {"total": total, "completed": completed, "in_progress": in_progress}
|
|
39
|
+
|
|
40
|
+
def get_git_stats():
|
|
41
|
+
"""Get high-level git diff stats without the actual diff content."""
|
|
42
|
+
try:
|
|
43
|
+
# Get purely the stat line e.g., "3 files changed, 50 insertions(+), 10 deletions(-)"
|
|
44
|
+
stat = subprocess.check_output(["git", "diff", "--shortstat"]).decode('utf-8').strip()
|
|
45
|
+
if not stat:
|
|
46
|
+
stat = subprocess.check_output(["git", "diff", "--cached", "--shortstat"]).decode('utf-8').strip()
|
|
47
|
+
return stat if stat else "Working tree clean"
|
|
48
|
+
except Exception:
|
|
49
|
+
return "Unknown git status"
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
print("🛡️ [Dasa Kala] Initializing Project Status Reporter...")
|
|
53
|
+
|
|
54
|
+
tasks = get_task_stats()
|
|
55
|
+
git_stat = get_git_stats()
|
|
56
|
+
|
|
57
|
+
pct = 0
|
|
58
|
+
if tasks["total"] > 0:
|
|
59
|
+
pct = round((tasks["completed"] / tasks["total"]) * 100)
|
|
60
|
+
|
|
61
|
+
summary = {
|
|
62
|
+
"progress_percent": pct,
|
|
63
|
+
"tasks": f"{tasks['completed']}/{tasks['total']} ({tasks['in_progress']} active)",
|
|
64
|
+
"uncommitted_code": git_stat
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Write to a tiny file Kala can instantly parse
|
|
68
|
+
os.makedirs(".artifacts", exist_ok=True)
|
|
69
|
+
out_path = ".artifacts/status_summary.json"
|
|
70
|
+
|
|
71
|
+
with open(out_path, "w") as f:
|
|
72
|
+
json.dump(summary, f, indent=2)
|
|
73
|
+
|
|
74
|
+
print(f"🟢 [Kala Reporter] Status parsed. JSON Summary generated at {out_path}.")
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Indra: Universal Test Watcher (test_runner.py)
|
|
4
|
+
A lightweight wrapper that detects the framework, runs tests, and compresses
|
|
5
|
+
massive test console outputs into a concise TOON summary to save tokens.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import subprocess
|
|
11
|
+
import json
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
def detect_framework():
|
|
15
|
+
"""Detect the testing framework based on workspace files."""
|
|
16
|
+
if os.path.exists("package.json"):
|
|
17
|
+
with open("package.json", "r") as f:
|
|
18
|
+
content = f.read()
|
|
19
|
+
if '"jest"' in content:
|
|
20
|
+
return "npm test", "Jest"
|
|
21
|
+
if '"vitest"' in content:
|
|
22
|
+
return "npm run test", "Vitest"
|
|
23
|
+
|
|
24
|
+
if os.path.exists("pytest.ini") or os.path.exists("setup.py") or os.path.exists("requirements.txt"):
|
|
25
|
+
return "pytest", "PyTest"
|
|
26
|
+
|
|
27
|
+
if os.path.exists("go.mod"):
|
|
28
|
+
return "go test ./...", "Go Test"
|
|
29
|
+
|
|
30
|
+
return None, None
|
|
31
|
+
|
|
32
|
+
def generate_toon_report(framework, output, code):
|
|
33
|
+
"""Compress the raw test output into a clean TOON structure."""
|
|
34
|
+
lines = output.split('\n')
|
|
35
|
+
errors = [line for line in lines if 'FAIL' in line or 'Error' in line or 'ERR!' in line]
|
|
36
|
+
|
|
37
|
+
# We only take the last 50 lines to prevent token bloat
|
|
38
|
+
tail = "\n".join(lines[-50:])
|
|
39
|
+
|
|
40
|
+
status = "SUCCESS" if code == 0 else "FAILED"
|
|
41
|
+
|
|
42
|
+
report = f"""# Test Execution Report
|
|
43
|
+
Framework: {framework}
|
|
44
|
+
Status: {status}
|
|
45
|
+
Timestamp: {datetime.now().isoformat()}
|
|
46
|
+
|
|
47
|
+
## Summary Tail
|
|
48
|
+
```text
|
|
49
|
+
{tail}
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
if errors and code != 0:
|
|
53
|
+
report += "\n## Detected Failures\n```text\n" + "\n".join(errors[:20]) + "\n```\n"
|
|
54
|
+
|
|
55
|
+
return report
|
|
56
|
+
|
|
57
|
+
def main():
|
|
58
|
+
print("🛡️ [Dasa Indra] Initializing Universal Test Watcher...")
|
|
59
|
+
|
|
60
|
+
cmd, framework = detect_framework()
|
|
61
|
+
|
|
62
|
+
if not cmd:
|
|
63
|
+
print("🟡 [Indra Watcher] No recognized testing framework found in root. Skipping tests.")
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
print(f"⚡ [Indra Watcher] Detected {framework}. Running: `{cmd}`")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Run tests and capture both stdout and stderr
|
|
70
|
+
result = subprocess.run(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
|
71
|
+
output = result.stdout
|
|
72
|
+
code = result.returncode
|
|
73
|
+
except FileNotFoundError:
|
|
74
|
+
print(f"🔴 [Indra Watcher] Testing executable for '{cmd}' not found.")
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"🔴 [Indra Watcher] Unexpected error executing tests: {e}")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
# Generate the highly compressed TOON output
|
|
81
|
+
report = generate_toon_report(framework, output, code)
|
|
82
|
+
|
|
83
|
+
# We write this compressed report to a file so the AI can read it efficiently
|
|
84
|
+
# instead of bloating the chat context with 10,000 lines of Jest output.
|
|
85
|
+
os.makedirs(".artifacts", exist_ok=True)
|
|
86
|
+
report_path = ".artifacts/test_report.toon"
|
|
87
|
+
with open(report_path, "w") as f:
|
|
88
|
+
f.write(report)
|
|
89
|
+
|
|
90
|
+
if code != 0:
|
|
91
|
+
print(f"🔴 [Indra Watcher] Tests FAILED. Details written to {report_path}")
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
else:
|
|
94
|
+
print(f"🟢 [Indra Watcher] All tests passed! Summary written to {report_path}")
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Patih: Environment Gatekeeper (validate_env.py)
|
|
4
|
+
Validates the local environment against dasa.config.toon requirements before execution.
|
|
5
|
+
Ensures Python, Node, and Go (if required) are installed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import subprocess
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
def check_command(cmd):
|
|
14
|
+
"""Check if a command exists in the system PATH."""
|
|
15
|
+
try:
|
|
16
|
+
subprocess.run([cmd, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
|
17
|
+
return True
|
|
18
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def check_env_file():
|
|
22
|
+
"""Verify standard .env files exist if a .env.example is present."""
|
|
23
|
+
if os.path.exists(".env.example") and not os.path.exists(".env"):
|
|
24
|
+
print("🔴 [Patih Gatekeeper] WARNING: .env.example exists, but .env is missing. The agent might fail without environment variables.")
|
|
25
|
+
return False
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
def parse_config():
|
|
29
|
+
"""Parse dasa.config.toon for workspace paths if it exists."""
|
|
30
|
+
config_path = ".agent/dasa.config.toon"
|
|
31
|
+
if not os.path.exists(config_path):
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
with open(config_path, "r") as f:
|
|
36
|
+
return json.load(f)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"🔴 [Patih Gatekeeper] ERROR: Could not parse {config_path}: {e}")
|
|
39
|
+
return {}
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
print("🛡️ [Dasa Patih] Initializing Environment Gatekeeper...")
|
|
43
|
+
|
|
44
|
+
config = parse_config()
|
|
45
|
+
workspaces = config.get("workspaces", {"root": "./"})
|
|
46
|
+
|
|
47
|
+
# 1. Check Workspaces
|
|
48
|
+
for name, path in workspaces.items():
|
|
49
|
+
if not os.path.exists(path):
|
|
50
|
+
print(f"🔴 [Patih Gatekeeper] ERROR: Configured workspace '{name}' path '{path}' does not exist.")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
# 2. Check Dependencies
|
|
54
|
+
deps = {
|
|
55
|
+
"node": check_command("node"),
|
|
56
|
+
"npm": check_command("npm"),
|
|
57
|
+
"python": check_command("python3")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
missing = [cmd for cmd, exists in deps.items() if not exists]
|
|
61
|
+
if missing:
|
|
62
|
+
print(f"🔴 [Patih Gatekeeper] WARNING: Missing standard runtime environments: {', '.join(missing)}")
|
|
63
|
+
|
|
64
|
+
# 3. Check ENV
|
|
65
|
+
check_env_file()
|
|
66
|
+
|
|
67
|
+
print("🟢 [Patih Gatekeeper] Environment Validation Passed. Ready for execution.")
|
|
68
|
+
sys.exit(0)
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Dasa Widya: The Extractor (web_scraper.py)
|
|
4
|
+
Natively fetches URL content and strips all HTML, inline CSS, and JavaScript.
|
|
5
|
+
Outputs pure markdown text to prevent massive token waste when Widya researchers.
|
|
6
|
+
Zero extra dependencies required.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import urllib.request
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
def fetch_html(url):
|
|
14
|
+
"""Fetch raw HTML from a URL."""
|
|
15
|
+
try:
|
|
16
|
+
req = urllib.request.Request(
|
|
17
|
+
url,
|
|
18
|
+
data=None,
|
|
19
|
+
headers={
|
|
20
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
24
|
+
return response.read().decode('utf-8', errors='ignore')
|
|
25
|
+
except Exception as e:
|
|
26
|
+
print(f"🔴 [Widya Extractor] Error fetching {url}: {e}")
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def extract_text(html):
|
|
30
|
+
"""Strip all HTML tags and noise, returning clean text."""
|
|
31
|
+
if not html:
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
# 1. Remove script and style blocks completely
|
|
35
|
+
text = re.sub(r'<script.*?</script>', '', html, flags=re.IGNORECASE | re.DOTALL)
|
|
36
|
+
text = re.sub(r'<style.*?</style>', '', text, flags=re.IGNORECASE | re.DOTALL)
|
|
37
|
+
|
|
38
|
+
# 2. Replace common block elements with newlines for formatting
|
|
39
|
+
text = re.sub(r'</?(p|div|br|h[1-6]|li|tr|table|ul|ol|header|footer|nav)[^>]*>', '\n', text, flags=re.IGNORECASE)
|
|
40
|
+
|
|
41
|
+
# 3. Strip all remaining HTML tags
|
|
42
|
+
text = re.sub(r'<[^>]+>', '', text)
|
|
43
|
+
|
|
44
|
+
# 4. Clean up whitespace and empty lines
|
|
45
|
+
lines = [line.strip() for line in text.split('\n')]
|
|
46
|
+
cleaned_lines = [line for line in lines if line]
|
|
47
|
+
|
|
48
|
+
return '\n\n'.join(cleaned_lines)
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
if len(sys.argv) < 2:
|
|
52
|
+
print("Usage: python3 web_scraper.py <URL>")
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
url = sys.argv[1]
|
|
56
|
+
|
|
57
|
+
# Basic validation
|
|
58
|
+
if not url.startswith('http'):
|
|
59
|
+
url = 'https://' + url
|
|
60
|
+
|
|
61
|
+
print(f"🛡️ [Dasa Widya] Extracting clean text from: {url}")
|
|
62
|
+
|
|
63
|
+
html = fetch_html(url)
|
|
64
|
+
if not html:
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
clean_text = extract_text(html)
|
|
68
|
+
|
|
69
|
+
# Ensure artifacts directory exists
|
|
70
|
+
import os
|
|
71
|
+
os.makedirs(".artifacts", exist_ok=True)
|
|
72
|
+
out_path = ".artifacts/research_extraction.toon"
|
|
73
|
+
|
|
74
|
+
with open(out_path, "w") as f:
|
|
75
|
+
# Truncate to roughly 15,000 chars to ensure we don't blow out context
|
|
76
|
+
content = clean_text[:15000]
|
|
77
|
+
if len(clean_text) > 15000:
|
|
78
|
+
content += "\n\n... [CONTENT TRUNCATED FOR TOKEN SAFETY] ..."
|
|
79
|
+
f.write(content)
|
|
80
|
+
|
|
81
|
+
print(f"🟢 [Widya Extractor] Successfully stripped HTML noise.")
|
|
82
|
+
print(f"Clean markdown saved to {out_path} ({len(clean_text)} chars).")
|
|
83
|
+
sys.exit(0)
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|