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,182 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ tools/generate_tc_from_story.py — Large Action Model (LAM) Script Generator
5
+ Inspired by LaVague: Converts natural language user stories into Clean POM test scripts.
6
+ Supports configurable packages, dynamic Page Class discovery, and cross-platform execution.
7
+ """
8
+
9
+ import sys
10
+ import os
11
+ import re
12
+ import glob
13
+ import argparse
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
+ DEFAULT_PAGE_MAP = {
24
+ "login": "LoginPage",
25
+ "auth": "LoginPage",
26
+ "user": "UserAdminPage",
27
+ "common": "CommonCategoryPage",
28
+ "category": "CategoryPage",
29
+ "planning": "PlanningPage",
30
+ "report": "ReportPage",
31
+ "search": "SearchPage",
32
+ }
33
+
34
+
35
+ def detect_project_package(repo_root="."):
36
+ """Detects the standard package prefix from existing project structure or guide."""
37
+ # 1. Check PROJECT_AI_GUIDE.md for explicit package hints
38
+ guide_path = os.path.join(repo_root, "PROJECT_AI_GUIDE.md")
39
+ if os.path.isfile(guide_path):
40
+ try:
41
+ with open(guide_path, "r", encoding="utf-8", errors="ignore") as f:
42
+ content = f.read()
43
+ m = re.search(r'import\s+([\w\.]+)\.pages\.', content)
44
+ if m:
45
+ return f"{m.group(1)}.pages"
46
+ except Exception:
47
+ pass
48
+
49
+ # 2. Check existing Keywords/ directory
50
+ keywords_dir = os.path.join(repo_root, "Keywords")
51
+ if os.path.isdir(keywords_dir):
52
+ for root, _, files in os.walk(keywords_dir):
53
+ for file in files:
54
+ if file.endswith(".groovy"):
55
+ rel = os.path.relpath(root, keywords_dir)
56
+ parts = [p for p in rel.replace("\\", "/").split("/") if p and p != "."]
57
+ if parts:
58
+ return ".".join(parts)
59
+
60
+ return "pages"
61
+
62
+
63
+ def discover_page_classes(repo_root="."):
64
+ """Discovers available Page classes dynamically from workspace files."""
65
+ pages = dict(DEFAULT_PAGE_MAP)
66
+ search_dirs = [
67
+ os.path.join(repo_root, "Keywords"),
68
+ os.path.join(repo_root, "pages"),
69
+ os.path.join(repo_root, "src", "pages"),
70
+ os.path.join(repo_root, "tests", "pages")
71
+ ]
72
+ for d in search_dirs:
73
+ if os.path.isdir(d):
74
+ for f in glob.glob(os.path.join(d, "**", "*Page*.*"), recursive=True):
75
+ base = os.path.basename(f)
76
+ name, _ = os.path.splitext(base)
77
+ clean_key = name.lower().replace("page", "").replace("_", "")
78
+ if clean_key:
79
+ pages[clean_key] = name
80
+ return pages
81
+
82
+
83
+ def generate_script(story, module_name="CategoryPage", tc_name="TC_AutoGenerated", package_name=None, repo_root="."):
84
+ detected_package = package_name or detect_project_package(repo_root)
85
+ discovered_pages = discover_page_classes(repo_root)
86
+
87
+ # Normalize module name
88
+ mod_clean = module_name.lower().replace("page", "").replace("_", "")
89
+ target_page = discovered_pages.get(mod_clean, f"{module_name[:1].upper()}{module_name[1:]}")
90
+ if not target_page.endswith("Page"):
91
+ target_page += "Page"
92
+
93
+ # Detect action intent from story keywords
94
+ story_lower = story.lower()
95
+ is_delete = any(k in story_lower for k in ["xóa", "xoa", "delete", "remove"])
96
+ is_edit = any(k in story_lower for k in ["sį»­a", "sua", "chỉnh sį»­a", "chinh sua", "edit", "update"])
97
+ is_search = any(k in story_lower for k in ["tìm", "tim", "tìm kiếm", "tim kiem", "search", "filter"])
98
+ is_add = not (is_delete or is_edit or is_search) or any(k in story_lower for k in ["thĆŖm", "them", "tįŗ”o mį»›i", "tao moi", "add", "create"])
99
+
100
+ lines = []
101
+ lines.append("// ===========================================================================")
102
+ lines.append(f"// Test Case: {tc_name}")
103
+ lines.append(f"// Requirement: {story}")
104
+ lines.append("// Architecture: Clean POM & Flat Architecture (Generated by LaVague LAM Engine)")
105
+ lines.append("// ===========================================================================")
106
+ lines.append("")
107
+ lines.append(f"import {detected_package}.LoginPage")
108
+ lines.append(f"import {detected_package}.{target_page}")
109
+ lines.append("")
110
+ lines.append("// 1. Ensure system authentication session")
111
+ lines.append("LoginPage.ensureLoggedIn()")
112
+ lines.append("")
113
+ lines.append("// 2. Initialize dynamic idempotent test data")
114
+ lines.append('String uniqueCode = "AUTO_" + System.currentTimeMillis()')
115
+ lines.append('String testName = "Test Entity " + uniqueCode')
116
+ lines.append("")
117
+
118
+ if is_add:
119
+ lines.append("// 3. Execute entity creation workflow")
120
+ lines.append(f"{target_page}.openCreateModal()")
121
+ lines.append(f"{target_page}.fillCreateForm(uniqueCode, testName)")
122
+ lines.append(f"{target_page}.confirmCreate()")
123
+ lines.append(f"{target_page}.verifyCreateSuccess(uniqueCode)")
124
+
125
+ elif is_edit:
126
+ lines.append("// 3. Execute entity update workflow")
127
+ lines.append('String updatedName = testName + " [Updated]"')
128
+ lines.append(f"{target_page}.searchKeyword(uniqueCode)")
129
+ lines.append(f"{target_page}.openEditFirstRow()")
130
+ lines.append(f"{target_page}.updateName(updatedName)")
131
+ lines.append(f"{target_page}.confirmEdit()")
132
+ lines.append(f"{target_page}.verifyEditSuccess(updatedName)")
133
+
134
+ elif is_delete:
135
+ lines.append("// 3. Execute entity deletion workflow")
136
+ lines.append(f"{target_page}.searchKeyword(uniqueCode)")
137
+ lines.append(f"{target_page}.clickDeleteFirstRow()")
138
+ lines.append(f"{target_page}.confirmDelete()")
139
+ lines.append(f"{target_page}.verifyDeleted(uniqueCode)")
140
+
141
+ elif is_search:
142
+ lines.append("// 3. Execute entity search/filter workflow")
143
+ lines.append(f"{target_page}.searchKeyword(uniqueCode)")
144
+ lines.append(f"{target_page}.verifySearchResults(uniqueCode)")
145
+
146
+ lines.append("")
147
+ lines.append("// 4. Automated idempotent teardown cleanup")
148
+ lines.append(f"// {target_page}.teardown(uniqueCode)")
149
+ lines.append("")
150
+
151
+ return "\n".join(lines)
152
+
153
+
154
+ def main():
155
+ parser = argparse.ArgumentParser(description="LaVague-style LAM Script Generator for Clean POM Automation")
156
+ parser.add_argument("--story", "-s", required=True, help="User story or Acceptance Criteria (natural language)")
157
+ parser.add_argument("--module", "-m", default="CategoryPage", help="Target Page Class or Module name")
158
+ parser.add_argument("--name", "-n", default="TC_AutoGenerated", help="Test Case name")
159
+ parser.add_argument("--package", "-p", default=None, help="Base package name for imports (e.g. 'com.company.pages')")
160
+ parser.add_argument("--out", "-o", default=None, help="Output file path (optional)")
161
+ args = parser.parse_args()
162
+
163
+ script_content = generate_script(
164
+ story=args.story,
165
+ module_name=args.module,
166
+ tc_name=args.name,
167
+ package_name=args.package
168
+ )
169
+
170
+ if args.out:
171
+ out_dir = os.path.dirname(args.out)
172
+ if out_dir:
173
+ os.makedirs(out_dir, exist_ok=True)
174
+ with open(args.out, "w", encoding="utf-8") as f:
175
+ f.write(script_content)
176
+ print(f"Generated Clean POM script successfully saved to: {args.out}")
177
+ else:
178
+ print(script_content)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1,135 @@
1
+ # PowerShell Script to ensure TestReports and summaryTemp.txt for CI/CD Dashboard
2
+ param(
3
+ [string]$ProjectName = "Enterprise-Automation"
4
+ )
5
+
6
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
7
+ Write-Host "=== [Centralize CI/CD] Generating TestReports & summaryTemp.txt for $ProjectName ===" -ForegroundColor Cyan
8
+
9
+ $testReportsDir = Join-Path $PSScriptRoot "..\TestReports"
10
+ $reportsDir = Join-Path $PSScriptRoot "..\Reports"
11
+
12
+ if (-not (Test-Path $testReportsDir)) {
13
+ New-Item -ItemType Directory -Path $testReportsDir -Force | Out-Null
14
+ }
15
+
16
+ $passed = 0
17
+ $failures = 0
18
+ $errors = 0
19
+ $incomplete = 0
20
+ $skipped = 0
21
+ $foundExistingSummary = $false
22
+
23
+ # 0. Check if Cleanup_Browser already exported summaryTemp.txt with business test case counts
24
+ $existingSummaryFile = Join-Path $reportsDir "summaryTemp.txt"
25
+ if (-not (Test-Path $existingSummaryFile)) {
26
+ $existingSummaryFile = Join-Path $testReportsDir "summaryTemp.txt"
27
+ }
28
+ if (Test-Path $existingSummaryFile) {
29
+ try {
30
+ $rawSummary = [System.IO.File]::ReadAllText($existingSummaryFile, [System.Text.Encoding]::UTF8)
31
+ if ($rawSummary -match "(\d+)\s+Passed\s+-\s+.*?(\d+)\s+Failures\s+-\s+.*?(\d+)\s+Errors") {
32
+ $passed = [int]$matches[1]
33
+ $failures = [int]$matches[2]
34
+ $errors = [int]$matches[3]
35
+ if ($passed -gt 0) {
36
+ $foundExistingSummary = $true
37
+ Write-Host "[+] Found valid summaryTemp.txt from Cleanup_Browser: $rawSummary" -ForegroundColor Green
38
+ }
39
+ }
40
+ } catch {}
41
+ }
42
+
43
+ # 1. Find the latest report directory (check Reports, then TestReports)
44
+ $latestReport = Get-ChildItem -Path @($reportsDir, $testReportsDir) -Recurse -Filter "*junit*.xml" -ErrorAction SilentlyContinue |
45
+ Sort-Object LastWriteTime -Descending | Select-Object -First 1
46
+
47
+ if (-not $foundExistingSummary -and $latestReport) {
48
+ Write-Host "[+] Found latest JUnit report: $($latestReport.FullName)" -ForegroundColor Green
49
+ try {
50
+ [xml]$xml = Get-Content $latestReport.FullName
51
+ if ($xml.testsuites) {
52
+ $passed = [int]($xml.testsuites.passed)
53
+ $failures = [int]($xml.testsuites.failures)
54
+ $errors = [int]($xml.testsuites.errors)
55
+ $incomplete = [int]($xml.testsuites.incompleted)
56
+ $skipped = [int]($xml.testsuites.skipped)
57
+ } elseif ($xml.testsuite) {
58
+ $tests = [int]($xml.testsuite.tests)
59
+ $failures = [int]($xml.testsuite.failures)
60
+ $errors = [int]($xml.testsuite.errors)
61
+ $skipped = [int]($xml.testsuite.skipped)
62
+ $passed = [Math]::Max(0, $tests - $failures - $errors - $skipped)
63
+ }
64
+ $foundExistingSummary = $true
65
+ } catch {
66
+ Write-Warning "[!] Failed to parse XML: $_"
67
+ }
68
+ }
69
+
70
+ if (-not $foundExistingSummary) {
71
+ # Fallback to console0.log
72
+ $latestLog = Get-ChildItem -Path @($reportsDir, $testReportsDir) -Recurse -Filter "console0.log" -ErrorAction SilentlyContinue |
73
+ Sort-Object LastWriteTime -Descending | Select-Object -First 1
74
+
75
+ if ($latestLog) {
76
+ Write-Host "[+] Fallback: Parsing console log: $($latestLog.FullName)" -ForegroundColor Yellow
77
+ $logContent = Get-Content $latestLog.FullName -Raw
78
+ if ($logContent -match "Passed:\s*(\d+)\s*\|\s*Failed:\s*(\d+)\s*\|\s*Errors:\s*(\d+)") {
79
+ $passed = [int]$matches[1]
80
+ $failures = [int]$matches[2]
81
+ $errors = [int]$matches[3]
82
+ } elseif ($logContent -match "Tổng số:\s*(\d+)\s*\|\s*ĐẔt:\s*(\d+)\s*\|\s*Thįŗ„t bįŗ”i:\s*(\d+)\s*\|\s*Lį»—i:\s*(\d+)") {
83
+ $passed = [int]$matches[2]
84
+ $failures = [int]$matches[3]
85
+ $errors = [int]$matches[4]
86
+ } else {
87
+ $passed = ([regex]::Matches($logContent, "\[TEST_CASE\]\[PASSED\]")).Count
88
+ $failures = ([regex]::Matches($logContent, "\[TEST_CASE\]\[FAILED\]")).Count
89
+ $errors = ([regex]::Matches($logContent, "\[TEST_CASE\]\[ERROR\]")).Count
90
+ }
91
+ }
92
+ }
93
+
94
+ Write-Host "[+] Metrics: Passed=$passed, Failures=$failures, Errors=$errors, Incomplete=$incomplete, Skipped=$skipped" -ForegroundColor Cyan
95
+
96
+ # 2. Write summaryTemp.txt: \u2705 {P} Passed - \u274C {F} Failures - \u274C {E} Errors - \u2757 {I} Incomplete - \u26AA {S} Skipped
97
+ $summaryContent = "\u2705 $passed Passed - \u274C $failures Failures - \u274C $errors Errors - \u2757 $incomplete Incomplete - \u26AA $skipped Skipped"
98
+
99
+ $summaryFile = Join-Path $testReportsDir "summaryTemp.txt"
100
+ [System.IO.File]::WriteAllText($summaryFile, $summaryContent, [System.Text.Encoding]::ASCII)
101
+ Write-Host "[+] Created: $summaryFile" -ForegroundColor Green
102
+ Write-Host " $summaryContent"
103
+
104
+ # Also write to Reports/summaryTemp.txt for fallback
105
+ if (Test-Path $reportsDir) {
106
+ [System.IO.File]::WriteAllText((Join-Path $reportsDir "summaryTemp.txt"), $summaryContent, [System.Text.Encoding]::ASCII)
107
+ }
108
+
109
+ # 3. Synchronize report files into TestReports/<Project>_<timestamp>/ if Katalon saved to Reports/
110
+ if ($latestReport) {
111
+ $reportParentDir = $latestReport.Directory.FullName
112
+ $nowStr = Get-Date -Format "yyyyMMddHHmmss"
113
+ if ($ProjectName -match "_\d{10,14}$") {
114
+ $targetProjDir = Join-Path $testReportsDir $ProjectName
115
+ } else {
116
+ $targetProjDir = Join-Path $testReportsDir "${ProjectName}_$nowStr"
117
+ }
118
+ $targetSubDir = Join-Path $targetProjDir (Split-Path $reportParentDir -Leaf)
119
+
120
+ if (-not (Test-Path $targetSubDir)) {
121
+ New-Item -ItemType Directory -Path $targetSubDir -Force | Out-Null
122
+ }
123
+
124
+ Copy-Item -Path "$reportParentDir\*" -Destination $targetSubDir -Recurse -Force
125
+ Write-Host "[+] Synced artifacts to: $targetSubDir" -ForegroundColor Green
126
+
127
+ # Ensure results.html exists in targetProjDir
128
+ $htmlFiles = Get-ChildItem -Path $targetSubDir -Filter "*.html"
129
+ if ($htmlFiles.Count -gt 0) {
130
+ Copy-Item -Path $htmlFiles[0].FullName -Destination (Join-Path $targetProjDir "results.html") -Force
131
+ Write-Host "[+] Created results.html in: $targetProjDir" -ForegroundColor Green
132
+ }
133
+ }
134
+
135
+ Write-Host "=== [Centralize CI/CD] Report Generation Complete ===" -ForegroundColor Cyan
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Git Pre-Commit Architecture & Discipline Guard
4
+ --------------------------------------------------
5
+ Automated gatekeeper executed before every 'git commit'.
6
+ Ensures:
7
+ 1. 100% UTF-8 No BOM across repository files.
8
+ 2. Flat Architecture & Clean POM: Zero raw XPaths or findTestObject in Scripts/TC-xxx.
9
+ 3. Path Portability: Zero absolute machine paths (Users/, phungcao, /path/to/project).
10
+ """
11
+
12
+ import sys
13
+ import os
14
+ import subprocess
15
+
16
+ FORBIDDEN_PATHS = ["/Users/", "C:\\Workspace\\", "D:\\Workspace\\", "file:///c:", "file:///d:"]
17
+
18
+ def run_cmd(cmd):
19
+ return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
20
+
21
+ def check_bom(files):
22
+ bom_files = []
23
+ for f in files:
24
+ if not os.path.exists(f):
25
+ continue
26
+ try:
27
+ with open(f, "rb") as fp:
28
+ if fp.read(3) == b"\xef\xbb\xbf":
29
+ bom_files.append(f)
30
+ except Exception:
31
+ pass
32
+ return bom_files
33
+
34
+ def check_forbidden_paths(files):
35
+ violations = []
36
+ for f in files:
37
+ if not os.path.exists(f) or os.path.isdir(f):
38
+ continue
39
+ # Skip this guard script itself and binary files
40
+ if f.endswith((".py", ".db", ".png", ".jpg", ".zip", ".tar", ".gz")):
41
+ continue
42
+ try:
43
+ with open(f, "r", encoding="utf-8", errors="ignore") as fp:
44
+ for line_no, line in enumerate(fp, 1):
45
+ # Allow mention in historical report or gitignore
46
+ if "FINAL_REPORT.md" in f or ".gitignore" in f:
47
+ continue
48
+ for forbidden in FORBIDDEN_PATHS:
49
+ if forbidden in line:
50
+ violations.append((f, line_no, forbidden))
51
+ except Exception:
52
+ pass
53
+ return violations
54
+
55
+ def check_scripts_clean_pom(files):
56
+ violations = []
57
+ for f in files:
58
+ if not os.path.exists(f):
59
+ continue
60
+ # Only check test scripts in Scripts/
61
+ if "Scripts/" in f and f.endswith(".groovy"):
62
+ try:
63
+ with open(f, "r", encoding="utf-8", errors="ignore") as fp:
64
+ for line_no, line in enumerate(fp, 1):
65
+ stripped = line.strip()
66
+ if stripped.startswith("//"):
67
+ continue
68
+ if "findTestObject" in line:
69
+ violations.append((f, line_no, "Raw findTestObject is forbidden in Scripts/"))
70
+ if "//" in line and ("//table" in line or "//input" in line or "//button" in line):
71
+ violations.append((f, line_no, "Raw XPath is forbidden in Scripts/"))
72
+ except Exception:
73
+ pass
74
+ return violations
75
+
76
+ def main():
77
+ # If called with --install
78
+ if len(sys.argv) > 1 and sys.argv[1] == "--install":
79
+ git_dir = run_cmd("git rev-parse --git-dir").stdout.strip()
80
+ if not git_dir or not os.path.isdir(git_dir):
81
+ print("āŒ Error: Not a git repository.")
82
+ sys.exit(1)
83
+
84
+ # 1. Pre-commit hook
85
+ pre_commit_path = os.path.join(git_dir, "hooks", "pre-commit")
86
+ pre_commit_content = (
87
+ "#!/bin/sh\n"
88
+ "# Enterprise Pre-Commit Guard\n"
89
+ "python3 \"$0/../tools/git_pre_commit_guard.py\" 2>/dev/null || python tools/git_pre_commit_guard.py \"$@\"\n"
90
+ )
91
+ with open(pre_commit_path, "w", encoding="utf-8") as fp:
92
+ fp.write(pre_commit_content)
93
+ os.chmod(pre_commit_path, 0o755)
94
+
95
+ # 2. Post-merge hook (Auto-sync memory cache on git pull)
96
+ post_merge_path = os.path.join(git_dir, "hooks", "post-merge")
97
+ post_merge_content = (
98
+ "#!/bin/sh\n"
99
+ "# Enterprise Post-Merge Hook: Automatically initialize & sync Central Agent Memory\n"
100
+ "if [ -f \"tools/cache_resolver.py\" ]; then\n"
101
+ " echo \"šŸ”„ [Git Post-Merge] Syncing Central Agent Memory...\"\n"
102
+ " python3 \"$0/../tools/cache_resolver.py\" init 2>/dev/null || python tools/cache_resolver.py init 2>/dev/null || true\n"
103
+ "fi\n"
104
+ )
105
+ with open(post_merge_path, "w", encoding="utf-8") as fp:
106
+ fp.write(post_merge_content)
107
+ os.chmod(post_merge_path, 0o755)
108
+
109
+ # 3. Pre-push hook (Auto-sync memory cache on git push)
110
+ pre_push_path = os.path.join(git_dir, "hooks", "pre-push")
111
+ pre_push_content = (
112
+ "#!/bin/sh\n"
113
+ "# Enterprise Pre-Push Hook: Synchronize Central Agent Memory before pushing\n"
114
+ "if [ -f \"tools/cache_resolver.py\" ]; then\n"
115
+ " echo \"šŸ”„ [Git Pre-Push] Synchronizing Central Agent Memory to local cache...\"\n"
116
+ " python3 \"$0/../tools/cache_resolver.py\" sync 2>/dev/null || python tools/cache_resolver.py sync 2>/dev/null || true\n"
117
+ "fi\n"
118
+ )
119
+ with open(pre_push_path, "w", encoding="utf-8") as fp:
120
+ fp.write(pre_push_content)
121
+ os.chmod(pre_push_path, 0o755)
122
+
123
+ print(f"āœ… Git hooks installed successfully:")
124
+ print(f" - Pre-commit: {pre_commit_path}")
125
+ print(f" - Post-merge: {post_merge_path} (Auto-runs cache_resolver.py init on git pull)")
126
+ print(f" - Pre-push: {pre_push_path} (Auto-runs cache_resolver.py sync on git push)")
127
+ sys.exit(0)
128
+
129
+ # Get staged files
130
+ res = run_cmd("git diff --cached --name-only")
131
+ staged_files = [f.strip() for f in res.stdout.splitlines() if f.strip()]
132
+
133
+ if not staged_files:
134
+ sys.exit(0)
135
+
136
+ errors_found = False
137
+
138
+ # 1. BOM Check
139
+ target_exts = (".groovy", ".tc", ".ts", ".glbl", ".md", ".py", ".json", ".ps1")
140
+ check_files = [f for f in staged_files if f.endswith(target_exts)]
141
+ bom_files = check_bom(check_files)
142
+ if bom_files:
143
+ print("āŒ [PRE-COMMIT BLOCKED] UTF-8 BOM detected in staged files:")
144
+ for bf in bom_files:
145
+ print(f" - {bf}")
146
+ errors_found = True
147
+
148
+ # 2. Path Portability Check
149
+ path_violations = check_forbidden_paths(staged_files)
150
+ if path_violations:
151
+ print("āŒ [PRE-COMMIT BLOCKED] Absolute machine path detected in staged files:")
152
+ for f, line_no, term in path_violations:
153
+ print(f" - {f}:{line_no} (Forbidden: '{term}')")
154
+ errors_found = True
155
+
156
+ # 3. Clean POM Guard
157
+ pom_violations = check_scripts_clean_pom(staged_files)
158
+ if pom_violations:
159
+ print("āŒ [PRE-COMMIT BLOCKED] Clean POM violation in Scripts/:")
160
+ for f, line_no, reason in pom_violations:
161
+ print(f" - {f}:{line_no} ({reason})")
162
+ errors_found = True
163
+
164
+ # 4. AI Documentation & Anti-Drift Guard
165
+ doc_affected = any(f.endswith(".md") or ".agents" in f or "tools/" in f or "Keywords/" in f or "Test Suites/" in f for f in staged_files)
166
+ if doc_affected:
167
+ linter_script = os.path.join(os.path.dirname(__file__), "lint_ai_docs.py")
168
+ if os.path.exists(linter_script):
169
+ res = subprocess.run([sys.executable, linter_script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
170
+ if res.returncode != 0:
171
+ print("āŒ [PRE-COMMIT BLOCKED] AI Documentation Linter detected drift:")
172
+ print(res.stdout.strip())
173
+ errors_found = True
174
+
175
+ # 5. Keywords/ change -> Auto-regenerate Repo Map
176
+ keywords_affected = any(f.startswith("Keywords/") and f.endswith(".groovy") for f in staged_files)
177
+ if keywords_affected:
178
+ repo_map_script = os.path.join(os.path.dirname(__file__), "repo_map.py")
179
+ if os.path.exists(repo_map_script):
180
+ subprocess.run([sys.executable, repo_map_script], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
181
+ # Stage updated repo_map files
182
+ subprocess.run(["git", "add", "tools/repo_map.txt", "tools/repo_map.json"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
183
+
184
+ # 6. Scripts/ change -> Static Defect Linter (Meta Infer-Style)
185
+ scripts_affected = any(f.startswith("Scripts/") and f.endswith(".groovy") for f in staged_files)
186
+ if scripts_affected:
187
+ infer_script = os.path.join(os.path.dirname(__file__), "infer_linter.py")
188
+ if os.path.exists(infer_script):
189
+ res = subprocess.run([sys.executable, infer_script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
190
+ if res.returncode != 0:
191
+ print("āŒ [PRE-COMMIT BLOCKED] Static Defect Sentinel detected critical issues in staged scripts:")
192
+ print(res.stdout.strip())
193
+ errors_found = True
194
+
195
+ if errors_found:
196
+ print("\n🚫 Commit aborted. Please fix violations before committing.")
197
+ sys.exit(1)
198
+ else:
199
+ print("āœ… [PRE-COMMIT GUARD] All quality gates passed (No BOM, Clean POM, Clean Paths, Docs Aligned, Static Lint Pass).")
200
+ sys.exit(0)
201
+
202
+ if __name__ == "__main__":
203
+ main()