synapse-hub 0.0.0-stage → 2.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.
Files changed (122) hide show
  1. package/.agents/agents/failure-analyst.md +47 -0
  2. package/.agents/agents/live-explorer.md +41 -0
  3. package/.agents/agents/orchestrator.md +43 -0
  4. package/.agents/agents/pom-crafter.md +81 -0
  5. package/.agents/agents/quality-gatekeeper.md +47 -0
  6. package/.agents/agents/test-runner.md +43 -0
  7. package/.agents/rules/context-engineering.md +88 -0
  8. package/.agents/rules/git-push-protocol.md +101 -0
  9. package/.agents/rules/graphify.md +14 -0
  10. package/.agents/rules/lessons-learned.md +23 -0
  11. package/.agents/rules/mandatory-playwright-live.md +29 -0
  12. package/.agents/rules/skill-precedence.md +42 -0
  13. package/.agents/rules/systematic-debugging.md +99 -0
  14. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
  15. package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
  16. package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
  17. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
  18. package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
  19. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
  20. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
  21. package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
  22. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
  23. package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
  24. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
  25. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
  26. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
  27. package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
  28. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
  29. package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
  30. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
  31. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
  32. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
  33. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
  34. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
  35. package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
  36. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
  37. package/.agents/skills/github-actions-templates/SKILL.md +334 -0
  38. package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
  39. package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
  40. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
  41. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
  42. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
  43. package/.agents/skills/lam-script-generator/SKILL.md +83 -0
  44. package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
  45. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
  46. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
  47. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
  48. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
  49. package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
  50. package/.agents/skills/self-healing-locator/SKILL.md +73 -0
  51. package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
  52. package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
  53. package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
  54. package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
  55. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
  56. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
  57. package/.agents/templates/completion_report.md +57 -0
  58. package/.agents/workflows/graphify.md +10 -0
  59. package/.agents/workflows/test-impact.md +15 -0
  60. package/.agents/workflows/verify.md +34 -0
  61. package/AGENTS.md +115 -0
  62. package/README.md +394 -2
  63. package/bin/agent-qa.js +90 -0
  64. package/package.json +59 -4
  65. package/skills_manifest.json +262 -0
  66. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
  67. package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
  68. package/tools/TOOLS_RUNBOOK.md +298 -0
  69. package/tools/agent_canary.py +501 -0
  70. package/tools/analyze_project.py +242 -0
  71. package/tools/api_drift_sentinel.py +208 -0
  72. package/tools/auto_heal.py +189 -0
  73. package/tools/browser_helpers.js +593 -0
  74. package/tools/bug_reporter.py +224 -0
  75. package/tools/cache_resolver.py +846 -0
  76. package/tools/check_env.py +288 -0
  77. package/tools/ci_healing_hook.py +128 -0
  78. package/tools/cli.py +402 -0
  79. package/tools/config_decoupler.py +253 -0
  80. package/tools/db_sandbox.py +341 -0
  81. package/tools/distill_log.py +152 -0
  82. package/tools/e2e_flow_prober.ps1 +65 -0
  83. package/tools/flaky_detector.py +222 -0
  84. package/tools/fuzz_payloads.py +197 -0
  85. package/tools/generate_allure_report.ps1 +30 -0
  86. package/tools/generate_api_matrix.py +193 -0
  87. package/tools/generate_tc_from_story.py +182 -0
  88. package/tools/generate_test_reports.ps1 +135 -0
  89. package/tools/git_pre_commit_guard.py +203 -0
  90. package/tools/heal_pr_automator.py +195 -0
  91. package/tools/heuristic_locator_matcher.py +202 -0
  92. package/tools/infer_linter.py +238 -0
  93. package/tools/lint_ai_docs.py +254 -0
  94. package/tools/lookup_tc.py +165 -0
  95. package/tools/lookup_xpath.py +167 -0
  96. package/tools/matrix_sharder.py +146 -0
  97. package/tools/mcp_excel_server.ps1 +251 -0
  98. package/tools/mcp_server.py +490 -0
  99. package/tools/micro_mutator.py +309 -0
  100. package/tools/network_mock_shield.py +170 -0
  101. package/tools/omni_emitter.py +500 -0
  102. package/tools/omni_parser.py +435 -0
  103. package/tools/omni_transpiler.py +168 -0
  104. package/tools/pinpoint_failure.py +88 -0
  105. package/tools/project_state.py +169 -0
  106. package/tools/repo_map.json +12 -0
  107. package/tools/repo_map.py +277 -0
  108. package/tools/repo_map.txt +7 -0
  109. package/tools/safety_firewall.py +245 -0
  110. package/tools/setup_project.py +265 -0
  111. package/tools/smart_visual_masker.py +134 -0
  112. package/tools/test_impact.py +321 -0
  113. package/tools/tms_sync_bridge.py +342 -0
  114. package/tools/trace_data_flow.py +240 -0
  115. package/tools/traffic_session_converter.py +256 -0
  116. package/tools/universal_ir.py +480 -0
  117. package/tools/validate_groovy_scripts.py +173 -0
  118. package/tools/verify_all.py +496 -0
  119. package/tools/verify_all.sh +4 -0
  120. package/tools/visual_diff_canvas.py +142 -0
  121. package/tools/visual_som_helper.py +418 -0
  122. package/tools/wcag_audit.py +214 -0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fast Zero-Token Failure Pinpointer for Katalon & Jenkins Reports.
4
+ Scans the latest Reports/**/JUnit_Report.xml, extracts failed Test Cases,
5
+ exact failed lines in Groovy/Script, and the root-cause exception.
6
+ Outputs < 100 tokens.
7
+
8
+ Usage:
9
+ python3 tools/pinpoint_failure.py
10
+ python3 tools/pinpoint_failure.py path/to/JUnit_Report.xml
11
+ """
12
+ import os
13
+ import sys
14
+ import glob
15
+ import xml.etree.ElementTree as ET
16
+ import re
17
+
18
+ def find_latest_report():
19
+ reports = glob.glob("Reports/**/JUnit_Report.xml", recursive=True)
20
+ if not reports:
21
+ return None
22
+ reports.sort(key=lambda x: os.path.getmtime(x), reverse=True)
23
+ return reports[0]
24
+
25
+ def parse_failures(xml_path):
26
+ if not os.path.exists(xml_path):
27
+ print(f"Error: {xml_path} does not exist.")
28
+ return []
29
+
30
+ tree = ET.parse(xml_path)
31
+ root = tree.getroot()
32
+ failures = []
33
+
34
+ suites = [root] if root.tag == "testsuite" else root.findall("testsuite")
35
+
36
+ for suite in suites:
37
+ for tc in suite.findall("testcase"):
38
+ tc_name = tc.get("name", "Unknown")
39
+ tc_time = tc.get("time", "0")
40
+ fail = tc.find("failure")
41
+ err = tc.find("error")
42
+ target = fail if fail is not None else err
43
+
44
+ if target is not None:
45
+ msg = target.get("message", "")
46
+ text = target.text or ""
47
+ combined = (msg + "\n" + text).replace("&#xa;", "\n")
48
+
49
+ # Extract Root Cause Reason
50
+ reason_match = re.search(r"Reason:\s*(.+?)(?=\n\s*at|\Z)", combined, re.DOTALL)
51
+ if reason_match:
52
+ reason = reason_match.group(1).strip()
53
+ else:
54
+ lines = [l.strip() for l in combined.split("\n") if l.strip()]
55
+ reason = lines[0] if lines else "Unknown Error"
56
+
57
+ # Extract Failed Line in Groovy Page / Script
58
+ Enterprise_lines = re.findall(r"(?:Enterprise\.pages\.[\w]+|TC-[\w\s\u00C0-\u1EF9]+|Script\d+)\.groovy:\d+|(?:TC-[\w\s\u00C0-\u1EF9]+:\d+)", combined)
59
+
60
+ failures.append({
61
+ "tc": tc_name.split("/")[-1],
62
+ "time": f"{float(tc_time):.1f}s",
63
+ "reason": reason.split("\n")[0][:180],
64
+ "failed_at": Enterprise_lines[:2] if Enterprise_lines else ["See JUnit report"]
65
+ })
66
+
67
+ return failures
68
+
69
+ def main():
70
+ xml_path = sys.argv[1] if len(sys.argv) > 1 else find_latest_report()
71
+ if not xml_path:
72
+ print("No JUnit_Report.xml found in Reports/.")
73
+ sys.exit(0)
74
+
75
+ print(f"=== Report: {xml_path} ===")
76
+ failures = parse_failures(xml_path)
77
+ if not failures:
78
+ print("✅ 100% PASSED: Zero failures found in report.")
79
+ return
80
+
81
+ print(f"❌ Found {len(failures)} Failed Test Case(s):")
82
+ for i, f in enumerate(failures, 1):
83
+ print(f"[{i}] {f['tc']} ({f['time']})")
84
+ print(f" Cause: {f['reason']}")
85
+ print(f" Line: {', '.join(f['failed_at'])}")
86
+
87
+ if __name__ == "__main__":
88
+ main()
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Project State Machine & Token-Capped Memory Anchor
4
+ --------------------------------------------------------------
5
+ Maintains persistent execution state across long-running AI sessions and Antigravity context compaction.
6
+ Enforces a strict Rolling Window (< 50 lines, ~200 tokens) to guarantee zero token bloat.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import json
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+
15
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
16
+ if hasattr(sys.stdout, 'reconfigure'):
17
+ try:
18
+ sys.stdout.reconfigure(encoding='utf-8')
19
+ sys.stderr.reconfigure(encoding='utf-8')
20
+ except Exception:
21
+ pass
22
+
23
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24
+ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
25
+ TEMPLATES_DIR = os.path.join(HUB_ROOT, "templates")
26
+
27
+
28
+ def resolve_state_file(target_dir):
29
+ return os.path.join(target_dir, "PROJECT_STATE.md")
30
+
31
+
32
+ def init_state(target_dir, goal="Initial Autonomous Setup"):
33
+ """Initializes PROJECT_STATE.md if missing, strictly capped to < 50 lines."""
34
+ state_file = resolve_state_file(target_dir)
35
+ if os.path.exists(state_file):
36
+ return state_file
37
+
38
+ project_name = os.path.basename(os.path.abspath(target_dir))
39
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
+
41
+ content = f"""# Project Execution State (`PROJECT_STATE.md`)
42
+
43
+ - **Project:** `{project_name}`
44
+ - **Last Sync:** `{timestamp}`
45
+ - **Overall Status:** `READY`
46
+
47
+ ## 1. Active Goal
48
+ - {goal}
49
+
50
+ ## 2. Task Checklist (Rolling Window - Max 8 active items)
51
+ - [x] Initialized project state anchor
52
+ - [ ] Ready for feature or test automation
53
+
54
+ ## 3. Active Watchpoints & Healing (Max 3 items)
55
+ - Target Suite / Script: `[None]`
56
+ - Failing Locator / Line: `[None]`
57
+
58
+ ## 4. Quality Verification
59
+ - 7 Quality Gates: `READY`
60
+ - Clean POM Integrity: `VERIFIED`
61
+ """
62
+ with open(state_file, "w", encoding="utf-8") as f:
63
+ f.write(content)
64
+ return state_file
65
+
66
+
67
+ def update_state(target_dir, goal=None, completed_task=None, pending_task=None, watchpoint=None, gate_status=None):
68
+ """Updates state in-place while pruning old entries to guarantee < 50 lines (~200 tokens)."""
69
+ state_file = resolve_state_file(target_dir)
70
+ if not os.path.exists(state_file):
71
+ init_state(target_dir, goal or "Active Task")
72
+
73
+ with open(state_file, "r", encoding="utf-8") as f:
74
+ lines = [line.rstrip() for line in f.readlines()]
75
+
76
+ project_name = os.path.basename(os.path.abspath(target_dir))
77
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
78
+
79
+ # Parse existing tasks
80
+ existing_tasks = []
81
+ current_goal = goal or "Continue autonomous QA workflow"
82
+ current_watchpoints = []
83
+ current_gate = gate_status or "READY"
84
+
85
+ in_tasks = False
86
+ in_watch = False
87
+
88
+ for line in lines:
89
+ if line.startswith("## 1. Active Goal"):
90
+ in_tasks = False
91
+ in_watch = False
92
+ elif line.startswith("## 2. Task Checklist"):
93
+ in_tasks = True
94
+ in_watch = False
95
+ continue
96
+ elif line.startswith("## 3. Active Watchpoints"):
97
+ in_tasks = False
98
+ in_watch = True
99
+ continue
100
+ elif line.startswith("## 4. Quality Verification"):
101
+ in_tasks = False
102
+ in_watch = False
103
+ continue
104
+
105
+ if in_tasks and line.strip().startswith("- ["):
106
+ existing_tasks.append(line.strip())
107
+ elif in_watch and line.strip().startswith("- "):
108
+ current_watchpoints.append(line.strip())
109
+ elif line.startswith("- **Last Sync:**") and not goal:
110
+ pass
111
+
112
+ if completed_task:
113
+ existing_tasks.append(f"- [x] {completed_task}")
114
+ if pending_task:
115
+ existing_tasks.append(f"- [ ] {pending_task}")
116
+ if watchpoint:
117
+ current_watchpoints = [f"- Focus: {watchpoint}"]
118
+
119
+ # Rolling window cap: Keep maximum 8 task items (purge oldest completed tasks)
120
+ if len(existing_tasks) > 8:
121
+ # Keep pending tasks and the most recent completed ones
122
+ pending = [t for t in existing_tasks if t.startswith("- [ ]")]
123
+ completed = [t for t in existing_tasks if t.startswith("- [x]")]
124
+ # Keep up to 4 completed and remaining pending
125
+ completed = completed[-4:]
126
+ existing_tasks = completed + pending
127
+ existing_tasks = existing_tasks[-8:]
128
+
129
+ # Reconstruct compact markdown (< 40 lines)
130
+ tasks_block = "\n".join(existing_tasks) if existing_tasks else "- [ ] Standby for instructions"
131
+ watch_block = "\n".join(current_watchpoints[:3]) if current_watchpoints else "- Target Suite / Script: `[None]`\n- Failing Locator / Line: `[None]`"
132
+
133
+ content = f"""# Project Execution State (`PROJECT_STATE.md`)
134
+
135
+ - **Project:** `{project_name}`
136
+ - **Last Sync:** `{timestamp}`
137
+ - **Overall Status:** `IN_PROGRESS`
138
+
139
+ ## 1. Active Goal
140
+ - {current_goal}
141
+
142
+ ## 2. Task Checklist (Rolling Window - Max 8 active items)
143
+ {tasks_block}
144
+
145
+ ## 3. Active Watchpoints & Healing (Max 3 items)
146
+ {watch_block}
147
+
148
+ ## 4. Quality Verification
149
+ - 7 Quality Gates: `{current_gate}`
150
+ - Clean POM Integrity: `VERIFIED`
151
+ """
152
+ with open(state_file, "w", encoding="utf-8") as f:
153
+ f.write(content)
154
+ return state_file
155
+
156
+
157
+ def read_state(target_dir):
158
+ """Reads state file if exists, returning condensed text."""
159
+ state_file = resolve_state_file(target_dir)
160
+ if not os.path.exists(state_file):
161
+ return None
162
+ with open(state_file, "r", encoding="utf-8") as f:
163
+ return f.read()
164
+
165
+
166
+ if __name__ == "__main__":
167
+ target = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
168
+ sf = init_state(target)
169
+ print(f"Project state anchor initialized at: {sf}")
@@ -0,0 +1,12 @@
1
+ {
2
+ "generated_at": "2026-10-29 19:59:47",
3
+ "target_repository": "/Users/phungcao/Documents/Agent Skill",
4
+ "classes_count": 0,
5
+ "methods_count": 0,
6
+ "source_lines": 0,
7
+ "raw_bytes": 0,
8
+ "compressed_bytes": 442,
9
+ "compression_ratio_pct": -44100.0,
10
+ "estimated_tokens": 110,
11
+ "classes": []
12
+ }
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Repository Map Compressor (Inspired by Aider-AI/aider)
4
+ -----------------------------------------------------------
5
+ Extracts an ultra-compact, token-budgeted AST map of all Page Classes and Helpers.
6
+ Compresses hundreds of kilobytes of Groovy source code into a clean ~150-line summary.
7
+ Enables AI Agents to discover all available methods and signatures with < 600 tokens.
8
+ Supports dynamic target repository resolution (CLI flag, CWD auto-detect, ENV var).
9
+ Saves a persistent in-place copy (repo_map.txt) inside the target project directory.
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import re
15
+ import json
16
+ import argparse
17
+ from datetime import datetime
18
+
19
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
20
+ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
21
+ OUTPUT_TXT = os.path.join(SCRIPT_DIR, "repo_map.txt")
22
+ OUTPUT_JSON = os.path.join(SCRIPT_DIR, "repo_map.json")
23
+
24
+ # Regex patterns for Groovy classes and methods
25
+ CLASS_PATTERN = re.compile(r'^\s*(?:public\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?', re.MULTILINE)
26
+ METHOD_PATTERN = re.compile(
27
+ r'^\s*(?:@\w+\s+)*(?:public\s+|protected\s+|private\s+)?(?:static\s+)?(?:def\s+|void\s+|[\w<>\[\],]+\s+)?(\w+)\s*\(([^)]*)\)\s*(?:throws\s+[\w,\s]+)?\s*\{',
28
+ re.MULTILINE
29
+ )
30
+ DOC_PATTERN = re.compile(r'/\*\*(.*?)\*/', re.DOTALL)
31
+
32
+
33
+ def resolve_target_repo(cli_target=None, cli_repo=None):
34
+ """
35
+ Resolves the target repository path for repo mapping:
36
+ 1. Explicit CLI flag: --repo / --path or positional target.
37
+ 2. Environment variable: TARGET_PROJECT_DIR.
38
+ 3. Current Working Directory (if different from Hub and has Keywords/ or Scripts/).
39
+ 4. Default fallback: QA Hub root (parent of tools/).
40
+ """
41
+ chosen = cli_repo or cli_target
42
+ if chosen:
43
+ resolved = os.path.abspath(chosen)
44
+ if not os.path.exists(resolved):
45
+ raise FileNotFoundError(f"Target repository path not found: {resolved}")
46
+ return resolved
47
+
48
+ env_dir = os.environ.get("TARGET_PROJECT_DIR")
49
+ if env_dir and os.path.exists(env_dir):
50
+ return os.path.abspath(env_dir)
51
+
52
+ cwd = os.path.abspath(os.getcwd())
53
+ if cwd.lower() != HUB_ROOT.lower():
54
+ if os.path.exists(os.path.join(cwd, "Keywords")) or os.path.exists(os.path.join(cwd, "Scripts")):
55
+ return cwd
56
+
57
+ return HUB_ROOT
58
+
59
+
60
+ def extract_class_info(file_path, repo_root=None):
61
+ """Parses a Groovy file and extracts class name, parent, and exported methods."""
62
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
63
+ content = f.read()
64
+
65
+ base_root = repo_root or HUB_ROOT
66
+ try:
67
+ rel_path = os.path.relpath(file_path, base_root)
68
+ except ValueError:
69
+ rel_path = os.path.abspath(file_path)
70
+
71
+ class_match = CLASS_PATTERN.search(content)
72
+ if not class_match:
73
+ return None
74
+
75
+ class_name = class_match.group(1)
76
+ extends_name = class_match.group(2) or ""
77
+
78
+ methods = []
79
+ control_keywords = {"if", "for", "while", "switch", "catch", "synchronized"}
80
+
81
+ lines = content.splitlines()
82
+ for idx, line in enumerate(lines):
83
+ line_clean = line.strip()
84
+ if not line_clean or line_clean.startswith("//") or line_clean.startswith("/*") or line_clean.startswith("*"):
85
+ continue
86
+
87
+ # If '=' appears before '(', it's a variable assignment / constant declaration, not a method
88
+ if "=" in line_clean:
89
+ eq_pos = line_clean.find("=")
90
+ paren_pos = line_clean.find("(")
91
+ if paren_pos != -1 and eq_pos < paren_pos:
92
+ continue
93
+
94
+ # Must have parentheses and end with '{' or next line starts with '{'
95
+ if "(" in line_clean and ")" in line_clean:
96
+ has_brace = line_clean.endswith("{") or (idx + 1 < len(lines) and lines[idx + 1].strip().startswith("{"))
97
+ if has_brace:
98
+ # Match method definition: modifiers + return type + name + (params)
99
+ m = re.search(r'(?:public\s+|protected\s+|private\s+|static\s+|def\s+|void\s+|[\w<>\[\],]+\s+)+([a-zA-Z_]\w*)\s*\(([^)]*)\)', line_clean)
100
+ if m:
101
+ name = m.group(1)
102
+ params = m.group(2).strip()
103
+ if name not in control_keywords and name != class_name:
104
+ # Extract preceding single-line comment if any
105
+ comment = ""
106
+ if idx > 0 and lines[idx - 1].strip().startswith("//"):
107
+ comment = lines[idx - 1].strip().lstrip("/ ").strip()
108
+ methods.append({
109
+ "name": name,
110
+ "params": re.sub(r'\s+', ' ', params),
111
+ "comment": comment
112
+ })
113
+
114
+ # Deduplicate methods preserving order
115
+ seen = set()
116
+ unique_methods = []
117
+ for meth in methods:
118
+ sig = f"{meth['name']}({meth['params']})"
119
+ if sig not in seen:
120
+ seen.add(sig)
121
+ unique_methods.append(meth)
122
+
123
+ return {
124
+ "rel_path": rel_path,
125
+ "class_name": class_name,
126
+ "extends": extends_name,
127
+ "raw_size_bytes": len(content.encode("utf-8")),
128
+ "line_count": len(lines),
129
+ "methods": unique_methods
130
+ }
131
+
132
+
133
+ def build_repo_map(target_repo=None, output_txt=None, output_json=None):
134
+ """Scans Keywords in target_repo and builds the condensed repo map."""
135
+ repo = target_repo or resolve_target_repo()
136
+ txt_dest = output_txt or OUTPUT_TXT
137
+ json_dest = output_json or OUTPUT_JSON
138
+
139
+ classes = []
140
+ total_raw_bytes = 0
141
+ total_raw_lines = 0
142
+
143
+ keywords_dir = os.path.join(repo, "Keywords")
144
+
145
+ if os.path.isdir(keywords_dir):
146
+ for root, dirs, files in os.walk(keywords_dir):
147
+ for fname in sorted(files):
148
+ if fname.endswith(".groovy"):
149
+ full_path = os.path.join(root, fname)
150
+ info = extract_class_info(full_path, repo_root=repo)
151
+ if info:
152
+ classes.append(info)
153
+ total_raw_bytes += info["raw_size_bytes"]
154
+ total_raw_lines += info["line_count"]
155
+
156
+ # Sort classes: BasePage first, then alphabetical
157
+ classes.sort(key=lambda c: (0 if c["class_name"] == "BasePage" else 1, c["class_name"]))
158
+
159
+ # Render text map
160
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
161
+ out_lines = [
162
+ f"# ==============================================================================",
163
+ f"# ENTERPRISE AUTOMATION REPOSITORY MAP (Aider-Style AST Compression)",
164
+ f"# Generated : {timestamp}",
165
+ f"# Target : {repo}",
166
+ f"# Scope : {len(classes)} Classes ({total_raw_lines} source lines, {total_raw_bytes / 1024:.1f} KB)",
167
+ f"# Purpose : Token-budgeted index (< 600 tokens) for instant method lookup",
168
+ f"# ==============================================================================\n"
169
+ ]
170
+
171
+ total_methods = 0
172
+ for cls in classes:
173
+ ext_str = f" extends {cls['extends']}" if cls['extends'] else ""
174
+ out_lines.append(f"{cls['rel_path']} ({cls['line_count']} lines)")
175
+ out_lines.append(f" class {cls['class_name']}{ext_str}:")
176
+ for m in cls["methods"]:
177
+ total_methods += 1
178
+ cmt = f" # {m['comment']}" if m['comment'] else ""
179
+ out_lines.append(f" def {m['name']}({m['params']}){cmt}")
180
+ out_lines.append("")
181
+
182
+ rendered_text = "\n".join(out_lines)
183
+ compressed_bytes = len(rendered_text.encode("utf-8"))
184
+ ratio = (1.0 - (compressed_bytes / max(1, total_raw_bytes))) * 100
185
+ est_tokens = len(rendered_text) // 4
186
+
187
+ # 1. Save to primary destination (tools/repo_map.txt in Hub)
188
+ os.makedirs(os.path.dirname(txt_dest), exist_ok=True)
189
+ with open(txt_dest, "w", encoding="utf-8") as f:
190
+ f.write(rendered_text)
191
+
192
+ os.makedirs(os.path.dirname(json_dest), exist_ok=True)
193
+ with open(json_dest, "w", encoding="utf-8") as f:
194
+ json.dump({
195
+ "generated_at": timestamp,
196
+ "target_repository": repo,
197
+ "classes_count": len(classes),
198
+ "methods_count": total_methods,
199
+ "source_lines": total_raw_lines,
200
+ "raw_bytes": total_raw_bytes,
201
+ "compressed_bytes": compressed_bytes,
202
+ "compression_ratio_pct": round(ratio, 2),
203
+ "estimated_tokens": est_tokens,
204
+ "classes": classes
205
+ }, f, indent=2, ensure_ascii=False)
206
+
207
+ # 2. Save a persistent copy directly into target repository root (for AI self-contained inspection)
208
+ if os.path.abspath(repo).lower() != os.path.abspath(HUB_ROOT).lower():
209
+ try:
210
+ target_txt = os.path.join(repo, "repo_map.txt")
211
+ with open(target_txt, "w", encoding="utf-8") as f:
212
+ f.write(rendered_text)
213
+ target_json = os.path.join(repo, "repo_map.json")
214
+ with open(target_json, "w", encoding="utf-8") as f:
215
+ json.dump({
216
+ "generated_at": timestamp,
217
+ "target_repository": repo,
218
+ "classes_count": len(classes),
219
+ "methods_count": total_methods,
220
+ "source_lines": total_raw_lines,
221
+ "raw_bytes": total_raw_bytes,
222
+ "compressed_bytes": compressed_bytes,
223
+ "compression_ratio_pct": round(ratio, 2),
224
+ "estimated_tokens": est_tokens,
225
+ "classes": classes
226
+ }, f, indent=2, ensure_ascii=False)
227
+ except Exception:
228
+ pass
229
+
230
+ return {
231
+ "target_repo": repo,
232
+ "classes_count": len(classes),
233
+ "methods_count": total_methods,
234
+ "source_lines": total_raw_lines,
235
+ "raw_bytes": total_raw_bytes,
236
+ "compressed_bytes": compressed_bytes,
237
+ "compression_ratio_pct": ratio,
238
+ "estimated_tokens": est_tokens,
239
+ "output_file": txt_dest
240
+ }
241
+
242
+
243
+ def main():
244
+ parser = argparse.ArgumentParser(description="Enterprise Repository Map Compressor")
245
+ parser.add_argument("target", nargs="?", default=None, help="Target repository containing Keywords/")
246
+ parser.add_argument("--repo", "--path", dest="repo_path", default=None, help="Target repository path")
247
+ parser.add_argument("--print", action="store_true", help="Print repository map to console")
248
+ args = parser.parse_args()
249
+
250
+ try:
251
+ target_repo = resolve_target_repo(cli_target=args.target, cli_repo=args.repo_path)
252
+ except FileNotFoundError as e:
253
+ print(f"❌ Error: {e}")
254
+ return 1
255
+
256
+ stats = build_repo_map(target_repo=target_repo)
257
+ print(f"\n======================================================================")
258
+ print(f" Enterprise Repository Map Compressor (Aider-Style AST Map) ")
259
+ print(f"======================================================================")
260
+ print(f" Target Repository : {stats['target_repo']}")
261
+ print(f" Indexed Classes : {stats['classes_count']} Page Classes & Helpers")
262
+ print(f" Extracted Methods : {stats['methods_count']} API signatures")
263
+ print(f" Source Lines Processed : {stats['source_lines']:,} lines ({stats['raw_bytes'] / 1024:.1f} KB)")
264
+ print(f" Compressed Map Size : {stats['compressed_bytes'] / 1024:.1f} KB (~{stats['estimated_tokens']} tokens)")
265
+ print(f" Compression Ratio : {stats['compression_ratio_pct']:.1f}% reduction")
266
+ print(f" Artifact Generated : {stats['output_file']}")
267
+ print(f"======================================================================\n")
268
+
269
+ if args.print:
270
+ with open(stats['output_file'], "r", encoding="utf-8") as f:
271
+ print(f.read())
272
+
273
+ return 0
274
+
275
+
276
+ if __name__ == "__main__":
277
+ sys.exit(main())
@@ -0,0 +1,7 @@
1
+ # ==============================================================================
2
+ # ENTERPRISE AUTOMATION REPOSITORY MAP (Aider-Style AST Compression)
3
+ # Generated : 2026-10-29 19:59:47
4
+ # Target : /Users/phungcao/Documents/Agent Skill
5
+ # Scope : 0 Classes (0 source lines, 0.0 KB)
6
+ # Purpose : Token-budgeted index (< 600 tokens) for instant method lookup
7
+ # ==============================================================================