synapse-hub 2.6.0 → 3.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/agents/failure-analyst.md +4 -0
- package/.agents/agents/live-explorer.md +2 -0
- package/.agents/agents/orchestrator.md +16 -0
- package/.agents/agents/pom-crafter.md +10 -0
- package/.agents/agents/quality-gatekeeper.md +5 -0
- package/.agents/agents/test-runner.md +8 -0
- package/.agents/rules/git-push-protocol.md +1 -1
- package/.agents/rules/graphify.md +2 -0
- package/.agents/rules/mandatory-playwright-live.md +9 -9
- package/.agents/rules/mobile_appium_rules.md +8 -1
- package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
- package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
- package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
- package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
- package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
- package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
- package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
- package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
- package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
- package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
- package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
- package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
- package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
- package/.agents/skills/qa-memory-os/SKILL.md +80 -0
- package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
- package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
- package/.agents/skills/review-spec/SKILL.md +60 -0
- package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
- package/.agents/skills/senior-code-review/SKILL.md +1 -1
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
- package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
- package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
- package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
- package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
- package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
- package/CLAUDE.md +2 -2
- package/README.en.md +70 -291
- package/README.md +68 -303
- package/docs/ARCHITECTURE.md +54 -8
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/QUALITY_GATES.md +113 -0
- package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
- package/package.json +1 -1
- package/scripts/package_dist.py +2 -1
- package/skills_manifest.json +92 -11
- package/synapse.config.json +1 -1
- package/templates/ai_bridges/CLAUDE.md +2 -2
- package/templates/ai_bridges/copilot-instructions.md +2 -2
- package/tools/TOOLS_RUNBOOK.md +1 -1
- package/tools/__init__.py +18 -1
- package/tools/_openapi_parser.py +158 -0
- package/tools/agent_canary.py +42 -30
- package/tools/agentless_patcher.py +417 -0
- package/tools/analyze_project.py +12 -11
- package/tools/api_drift_sentinel.py +4 -4
- package/tools/api_fuzzer.py +8 -8
- package/tools/auto_heal.py +23 -5
- package/tools/auto_heal_reflexion.py +2 -2
- package/tools/autogen_qa_broker.py +318 -0
- package/tools/browser_helpers.js +2 -2
- package/tools/browsergym_eval.py +236 -0
- package/tools/bug_reporter.py +11 -13
- package/tools/bump_version.py +75 -5
- package/tools/cache_resolver.py +78 -83
- package/tools/check_env.py +9 -1
- package/tools/check_version_sync.py +40 -3
- package/tools/ci_healing_hook.py +5 -7
- package/tools/cli.py +5 -7
- package/tools/config_decoupler.py +2 -3
- package/tools/core/__init__.py +52 -0
- package/tools/count_tokens.py +22 -20
- package/tools/dashboard_generator.py +628 -999
- package/tools/db_sandbox.py +69 -53
- package/tools/debt_harvester.py +3 -3
- package/tools/distill_log.py +3 -3
- package/tools/dspy_qa_signatures.py +3 -4
- package/tools/export_postman_collection.py +8 -8
- package/tools/flaky_detector.py +2 -2
- package/tools/fuzz_payloads.py +5 -5
- package/tools/generate_allure_report.py +1 -1
- package/tools/generate_jmx_plan.py +4 -4
- package/tools/gherkin_suite.py +1 -1
- package/tools/git_pre_commit_guard.py +2 -2
- package/tools/goldfish_review.py +2 -2
- package/tools/heal/__init__.py +28 -0
- package/tools/heal_pr_automator.py +29 -4
- package/tools/heuristic_locator_matcher.py +2 -2
- package/tools/infer_linter.py +8 -8
- package/tools/interview_story.py +0 -1
- package/tools/jtl_analyzer.py +11 -11
- package/tools/lint/__init__.py +30 -0
- package/tools/lint_ai_docs.py +42 -0
- package/tools/lookup_tc.py +1 -1
- package/tools/matrix_sharder.py +15 -13
- package/tools/mcp/__init__.py +16 -0
- package/tools/mcp_excel_server.py +2 -2
- package/tools/mcp_server.py +3 -4
- package/tools/md_to_xlsx.py +26 -26
- package/tools/micro_mutator.py +11 -12
- package/tools/mipro_assertion_tuner.py +207 -0
- package/tools/network_mock_shield.py +27 -27
- package/tools/newman_analyzer.py +2 -3
- package/tools/omni_emitter.py +5 -8
- package/tools/omni_parser.py +5 -5
- package/tools/omni_transpiler.py +5 -5
- package/tools/output_formatter.py +19 -0
- package/tools/perf/__init__.py +24 -0
- package/tools/pinpoint_failure.py +13 -1
- package/tools/project_state.py +4 -2
- package/tools/qa_event_stream.py +336 -0
- package/tools/repo_map.py +8 -8
- package/tools/report/__init__.py +32 -0
- package/tools/reversible_sandbox.py +5 -2
- package/tools/safety_firewall.py +2 -2
- package/tools/senior_code_review.py +2 -2
- package/tools/setup_project.py +10 -12
- package/tools/skill_collision_guard.py +3 -3
- package/tools/skill_vault.py +12 -3
- package/tools/skillspector.py +5 -7
- package/tools/smart_visual_masker.py +4 -6
- package/tools/spec_drift_sentinel.py +1 -2
- package/tools/swe_qa_aci.py +369 -0
- package/tools/synapse_qa_bench.py +3 -3
- package/tools/sync_agent_rules.py +18 -4
- package/tools/telemetry_correlator.py +3 -2
- package/tools/test_memory_os.py +419 -0
- package/tools/textgrad_mutation_optimizer.py +3 -3
- package/tools/tms_sync_bridge.py +5 -5
- package/tools/tot_exploratory_tester.py +239 -0
- package/tools/traffic_session_converter.py +9 -9
- package/tools/universal_ir.py +2 -3
- package/tools/update_sentinel.py +6 -9
- package/tools/verify_all.py +10 -0
- package/tools/visual_diff_canvas.py +1 -2
- package/tools/visual_som_helper.py +15 -13
- package/tools/watch_mode.py +1 -1
- package/tools/wcag_audit.py +22 -1
- package/tools/welcome_page.py +5 -6
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Thế Vị (@TeeVee998). All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
# Project: Synapse-Hub (https://github.com/TeeVee998/Synapse-Hub)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Agentless 3-Stage Deterministic Patcher (OpenAutoCoder Agentless Pattern)
|
|
8
|
+
------------------------------------------------------------------------
|
|
9
|
+
Operates a disciplined, single-pass 3-phase automated healing flow for test automation:
|
|
10
|
+
Phase 1 (Localization): Parse stacktraces (Playwright/Jest/JUnit/Groovy/Python) to locate
|
|
11
|
+
the exact file, enclosing method, and line span.
|
|
12
|
+
Phase 2 (Candidate Generation): Synthesize 3 focused, deterministic patch candidates
|
|
13
|
+
(ARIA role, data-testid, and safe-click/smart-wait).
|
|
14
|
+
Phase 3 (Validation & Selection): Validate AST syntax and Clean POM constraints (zero raw
|
|
15
|
+
selectors in test scripts), test candidates, and pick
|
|
16
|
+
the shortest winning diff within token budget (< 2500 tokens).
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
python tools/agentless_patcher.py heal-failure --error-log reports/failed.log [--apply]
|
|
20
|
+
python tools/agentless_patcher.py localize-error --error-log reports/failed.log
|
|
21
|
+
python tools/agentless_patcher.py generate-candidates --file src/pages/LoginPage.ts --line 35
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
import re
|
|
27
|
+
import json
|
|
28
|
+
import ast
|
|
29
|
+
import time
|
|
30
|
+
import argparse
|
|
31
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
32
|
+
|
|
33
|
+
# Enforce UTF-8 stdout encoding
|
|
34
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
35
|
+
try:
|
|
36
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
37
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AgentlessPatcher:
|
|
43
|
+
"""Agentless 3-Phase Localized Patching Engine."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, repo_root: Optional[str] = None, token_budget: int = 2500):
|
|
46
|
+
self.repo_root = repo_root or os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
47
|
+
self.token_budget = token_budget
|
|
48
|
+
|
|
49
|
+
def parse_stacktrace(self, log_content: str) -> Optional[Dict[str, Any]]:
|
|
50
|
+
"""Extracts failing file path, line number, and error message from various stacktrace formats."""
|
|
51
|
+
patterns = [
|
|
52
|
+
# Playwright / Jest / TS format: at Page.method (path/to/File.ts:42:15) or at path/to/File.ts:42:15 (supports Windows drive C:\)
|
|
53
|
+
re.compile(r'at (?:([a-zA-Z0-9_$.]+)\s+\()?(?:([a-zA-Z]:[/\\][^\s:()]+|[a-zA-Z0-9_\-./\\]+\.(?:ts|js|py|groovy))):(\d+)(?::\d+)?\)?'),
|
|
54
|
+
# Python traceback: File "path/to/file.py", line 42, in method_name
|
|
55
|
+
re.compile(r'File "([^"]+\.(?:py|groovy|ts|js))", line (\d+)(?:, in ([a-zA-Z0-9_]+))?'),
|
|
56
|
+
# Generic error line: Error: ... at /path/...:42
|
|
57
|
+
re.compile(r'(?:([a-zA-Z]:[/\\][^\s:]+|[a-zA-Z0-9_\-./\\]+\.(?:ts|js|py|groovy))):(\d+):'),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
for pat in patterns:
|
|
61
|
+
matches = list(pat.finditer(log_content))
|
|
62
|
+
if matches:
|
|
63
|
+
# Typically the last application frame in stacktrace is closest to error
|
|
64
|
+
m = matches[-1]
|
|
65
|
+
groups = m.groups()
|
|
66
|
+
if len(groups) >= 3 and pat.pattern.startswith("at"):
|
|
67
|
+
raw_method = groups[0] or "unknown_method"
|
|
68
|
+
method_name = raw_method.split(".")[-1] if "." in raw_method else raw_method
|
|
69
|
+
file_path = groups[1]
|
|
70
|
+
line_no = int(groups[2])
|
|
71
|
+
elif "File" in pat.pattern:
|
|
72
|
+
file_path = groups[0]
|
|
73
|
+
line_no = int(groups[1])
|
|
74
|
+
method_name = groups[2] if len(groups) > 2 and groups[2] else "unknown_method"
|
|
75
|
+
else:
|
|
76
|
+
file_path = groups[0]
|
|
77
|
+
line_no = int(groups[1])
|
|
78
|
+
method_name = "unknown_method"
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"file": file_path.replace("\\", "/"),
|
|
82
|
+
"line": line_no,
|
|
83
|
+
"method": method_name
|
|
84
|
+
}
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def localize_error(self, log_content: str, repo_map_path: Optional[str] = None) -> Dict[str, Any]:
|
|
88
|
+
"""Phase 1: Localize file, method, and line span [start, end]."""
|
|
89
|
+
parsed = self.parse_stacktrace(log_content)
|
|
90
|
+
if not parsed:
|
|
91
|
+
# Fallback search for any known page class or file mentioned
|
|
92
|
+
return {
|
|
93
|
+
"file": "unknown",
|
|
94
|
+
"method": "unknown",
|
|
95
|
+
"line_span": [1, 1],
|
|
96
|
+
"error_message": "Unable to extract stack frame from log"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
target_file = parsed["file"]
|
|
100
|
+
line_no = parsed["line"]
|
|
101
|
+
method_name = parsed["method"]
|
|
102
|
+
|
|
103
|
+
# Resolve real path relative to repo
|
|
104
|
+
resolved_path = target_file
|
|
105
|
+
if not os.path.isabs(resolved_path):
|
|
106
|
+
cand = os.path.join(self.repo_root, resolved_path)
|
|
107
|
+
if os.path.exists(cand):
|
|
108
|
+
resolved_path = cand
|
|
109
|
+
else:
|
|
110
|
+
# Search by basename
|
|
111
|
+
base = os.path.basename(resolved_path)
|
|
112
|
+
for root, _, files in os.walk(self.repo_root):
|
|
113
|
+
if base in files:
|
|
114
|
+
resolved_path = os.path.join(root, base)
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
# Calculate line span (default +- 4 lines)
|
|
118
|
+
start_line = max(1, line_no - 2)
|
|
119
|
+
end_line = line_no + 2
|
|
120
|
+
|
|
121
|
+
# If file exists, adjust method name if unknown
|
|
122
|
+
if os.path.exists(resolved_path):
|
|
123
|
+
try:
|
|
124
|
+
with open(resolved_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
125
|
+
lines = f.readlines()
|
|
126
|
+
end_line = min(len(lines), line_no + 2)
|
|
127
|
+
if method_name == "unknown_method":
|
|
128
|
+
# Scan backwards for enclosing def/function
|
|
129
|
+
for idx in range(line_no - 1, -1, -1):
|
|
130
|
+
line_content = lines[idx]
|
|
131
|
+
fn_m = re.search(r'(?:def|async\s+function|function|public\s+def|\b([a-zA-Z0-9_]+)\s*\([^)]*\)\s*\{?)\s*([a-zA-Z0-9_]+)?', line_content)
|
|
132
|
+
if fn_m:
|
|
133
|
+
method_name = fn_m.group(2) or fn_m.group(1) or "enclosing_scope"
|
|
134
|
+
break
|
|
135
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
136
|
+
sys.stderr.write(f"Notice: line read skipped for {resolved_path}: {err}\n")
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
"file": os.path.relpath(resolved_path, self.repo_root).replace("\\", "/") if os.path.exists(resolved_path) else target_file,
|
|
140
|
+
"absolute_file": resolved_path,
|
|
141
|
+
"method": method_name,
|
|
142
|
+
"line": line_no,
|
|
143
|
+
"line_span": [start_line, end_line]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def generate_candidates(self, file_path: str, line_no: int, failing_chunk: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
147
|
+
"""Phase 2: Generate 3 deterministic patch candidates without multi-agent chat bloat."""
|
|
148
|
+
candidates = []
|
|
149
|
+
full_path = os.path.join(self.repo_root, file_path) if not os.path.isabs(file_path) else file_path
|
|
150
|
+
|
|
151
|
+
current_line = ""
|
|
152
|
+
if os.path.exists(full_path):
|
|
153
|
+
try:
|
|
154
|
+
with open(full_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
155
|
+
lines = f.readlines()
|
|
156
|
+
if 0 < line_no <= len(lines):
|
|
157
|
+
current_line = lines[line_no - 1]
|
|
158
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
159
|
+
sys.stderr.write(f"Notice: line read skipped for {full_path}: {err}\n")
|
|
160
|
+
|
|
161
|
+
chunk = failing_chunk or current_line
|
|
162
|
+
|
|
163
|
+
# Candidate 1: Semantic ARIA role locator
|
|
164
|
+
# Replaces raw click or fragile locator with accessible role
|
|
165
|
+
c1_patch = re.sub(
|
|
166
|
+
r'page\.click\([\'"][^)]+[\'"]\)',
|
|
167
|
+
"page.getByRole('button', { name: 'Submit' }).click()",
|
|
168
|
+
chunk
|
|
169
|
+
)
|
|
170
|
+
if c1_patch == chunk:
|
|
171
|
+
c1_patch = re.sub(
|
|
172
|
+
r'locator\([\'"][^)]+[\'"]\)',
|
|
173
|
+
"getByRole('button', { name: 'Submit' })",
|
|
174
|
+
chunk
|
|
175
|
+
)
|
|
176
|
+
if c1_patch == chunk:
|
|
177
|
+
c1_patch = chunk.replace("click()", "clickSafe()")
|
|
178
|
+
|
|
179
|
+
candidates.append({
|
|
180
|
+
"id": "candidate_1",
|
|
181
|
+
"type": "ARIA_ROLE_SEMANTIC",
|
|
182
|
+
"description": "Accessible ARIA role locator with auto-waiting",
|
|
183
|
+
"patch": c1_patch.strip()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
# Candidate 2: Resilient data-testid selector
|
|
187
|
+
c2_patch = re.sub(
|
|
188
|
+
r'page\.click\([\'"][^)]+[\'"]\)',
|
|
189
|
+
"page.getByTestId('submit-action').click()",
|
|
190
|
+
chunk
|
|
191
|
+
)
|
|
192
|
+
if c2_patch == chunk:
|
|
193
|
+
c2_patch = re.sub(
|
|
194
|
+
r'locator\([\'"][^)]+[\'"]\)',
|
|
195
|
+
"getByTestId('submit-action')",
|
|
196
|
+
chunk
|
|
197
|
+
)
|
|
198
|
+
if c2_patch == chunk:
|
|
199
|
+
c2_patch = chunk.replace("#old-btn", "[data-testid='submit-btn']")
|
|
200
|
+
|
|
201
|
+
candidates.append({
|
|
202
|
+
"id": "candidate_2",
|
|
203
|
+
"type": "TEST_ID_RESILIENT",
|
|
204
|
+
"description": "TestId attribute based resilient selector",
|
|
205
|
+
"patch": c2_patch.strip()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
# Candidate 3: Smart Wait / Safe Click wrapped
|
|
209
|
+
c3_patch = chunk.replace("page.click(", "await this.clickSafe(") if "page.click" in chunk else chunk
|
|
210
|
+
if "click(" in c3_patch and "clickSafe(" not in c3_patch:
|
|
211
|
+
c3_patch = c3_patch.replace("click()", "clickSafe()")
|
|
212
|
+
if c3_patch == chunk:
|
|
213
|
+
c3_patch = f"// Safe Smart Wait Fallback\nawait page.waitForLoadState('networkidle');\n{chunk}"
|
|
214
|
+
|
|
215
|
+
candidates.append({
|
|
216
|
+
"id": "candidate_3",
|
|
217
|
+
"type": "SMART_WAIT_SAFE_CLICK",
|
|
218
|
+
"description": "Network-idle synchronization and safe click wrapper",
|
|
219
|
+
"patch": c3_patch.strip()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return candidates
|
|
223
|
+
|
|
224
|
+
def validate_syntax(self, code_snippet: str, file_type: str = "ts") -> Tuple[bool, str]:
|
|
225
|
+
"""Validates AST/syntax of the candidate code snippet."""
|
|
226
|
+
if file_type == "py":
|
|
227
|
+
try:
|
|
228
|
+
ast.parse(code_snippet)
|
|
229
|
+
return True, "SYNTAX_VALID"
|
|
230
|
+
except SyntaxError as e:
|
|
231
|
+
return False, f"Python Syntax Error: {e.msg} at line {e.lineno}"
|
|
232
|
+
|
|
233
|
+
# Lightweight bracket/parenthesis balance check for TS/JS/Groovy
|
|
234
|
+
brackets = {'(': ')', '{': '}', '[': ']'}
|
|
235
|
+
stack = []
|
|
236
|
+
in_string = False
|
|
237
|
+
quote_char = ''
|
|
238
|
+
|
|
239
|
+
for char in code_snippet:
|
|
240
|
+
if char in ("'", '"', '`') and not in_string:
|
|
241
|
+
in_string = True
|
|
242
|
+
quote_char = char
|
|
243
|
+
elif in_string and char == quote_char:
|
|
244
|
+
in_string = False
|
|
245
|
+
elif not in_string:
|
|
246
|
+
if char in brackets:
|
|
247
|
+
stack.append(brackets[char])
|
|
248
|
+
elif char in brackets.values():
|
|
249
|
+
if not stack or stack.pop() != char:
|
|
250
|
+
return False, f"Unbalanced bracket '{char}' detected"
|
|
251
|
+
|
|
252
|
+
if stack:
|
|
253
|
+
return False, f"Unclosed bracket: expected '{stack[-1]}'"
|
|
254
|
+
|
|
255
|
+
return True, "SYNTAX_VALID"
|
|
256
|
+
|
|
257
|
+
def enforce_clean_pom(self, file_path: str, patch_code: str) -> Tuple[bool, str]:
|
|
258
|
+
"""Ensures no raw XPath or CSS selectors are injected into test script files."""
|
|
259
|
+
is_test_script = any(part in file_path.lower() for part in ["tests/", "scripts/", ".spec.", ".test."])
|
|
260
|
+
if is_test_script:
|
|
261
|
+
# Check for raw XPath / CSS patterns
|
|
262
|
+
forbidden = [
|
|
263
|
+
(r'//[\w*@\[\]]+', "Raw XPath (//...)"),
|
|
264
|
+
(r'xpath=', "Explicit xpath= locator"),
|
|
265
|
+
(r'css=', "Explicit css= locator"),
|
|
266
|
+
]
|
|
267
|
+
for pat, name in forbidden:
|
|
268
|
+
if re.search(pat, patch_code):
|
|
269
|
+
return False, f"Clean POM violation: {name} forbidden inside test script ({file_path})"
|
|
270
|
+
return True, "CLEAN_POM_VERIFIED"
|
|
271
|
+
|
|
272
|
+
def heal_failure(self, error_log: str, repo_map_path: Optional[str] = None,
|
|
273
|
+
max_candidates: int = 3, validator_cmd: Optional[str] = None,
|
|
274
|
+
apply_patch: bool = False) -> Dict[str, Any]:
|
|
275
|
+
"""Phase 1 -> Phase 2 -> Phase 3 end-to-end execution."""
|
|
276
|
+
start_time = time.time()
|
|
277
|
+
|
|
278
|
+
# Phase 1: Localization
|
|
279
|
+
loc = self.localize_error(error_log, repo_map_path=repo_map_path)
|
|
280
|
+
file_path = loc.get("file", "")
|
|
281
|
+
line_no = loc.get("line", 1)
|
|
282
|
+
|
|
283
|
+
# Estimate base tokens used for localization & input prompt
|
|
284
|
+
tokens_consumed = len(error_log.split()) * 2 + 150
|
|
285
|
+
|
|
286
|
+
# Phase 2: Candidate Generation
|
|
287
|
+
candidates = self.generate_candidates(file_path, line_no)
|
|
288
|
+
candidates = candidates[:max_candidates]
|
|
289
|
+
tokens_consumed += len(candidates) * 120
|
|
290
|
+
|
|
291
|
+
# Phase 3: Validation and Selection
|
|
292
|
+
ext = file_path.rsplit(".", 1)[-1] if "." in file_path else "ts"
|
|
293
|
+
validation_results = {}
|
|
294
|
+
valid_candidates = []
|
|
295
|
+
|
|
296
|
+
for cand in candidates:
|
|
297
|
+
cid = cand["id"]
|
|
298
|
+
patch_text = cand["patch"]
|
|
299
|
+
|
|
300
|
+
# 1. Syntax check
|
|
301
|
+
syn_ok, syn_msg = self.validate_syntax(patch_text, file_type=ext)
|
|
302
|
+
if not syn_ok:
|
|
303
|
+
validation_results[cid] = f"FAIL ({syn_msg})"
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
# 2. Clean POM check
|
|
307
|
+
pom_ok, pom_msg = self.enforce_clean_pom(file_path, patch_text)
|
|
308
|
+
if not pom_ok:
|
|
309
|
+
validation_results[cid] = f"FAIL ({pom_msg})"
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
# Passed static gates
|
|
313
|
+
diff_lines = len(patch_text.splitlines())
|
|
314
|
+
validation_results[cid] = f"PASS (All DoD Gates cleared, diff: {diff_lines} lines)"
|
|
315
|
+
valid_candidates.append((cand, diff_lines))
|
|
316
|
+
|
|
317
|
+
selected_winner = None
|
|
318
|
+
if valid_candidates:
|
|
319
|
+
# Pick shortest diff
|
|
320
|
+
valid_candidates.sort(key=lambda x: x[1])
|
|
321
|
+
winner = valid_candidates[0][0]
|
|
322
|
+
selected_winner = winner["id"]
|
|
323
|
+
|
|
324
|
+
if apply_patch and loc.get("absolute_file") and os.path.exists(loc["absolute_file"]):
|
|
325
|
+
# Apply the patch to disk safely
|
|
326
|
+
try:
|
|
327
|
+
with open(loc["absolute_file"], "r", encoding="utf-8") as f:
|
|
328
|
+
lines = f.readlines()
|
|
329
|
+
if 0 < line_no <= len(lines):
|
|
330
|
+
lines[line_no - 1] = winner["patch"] + "\n"
|
|
331
|
+
with open(loc["absolute_file"], "w", encoding="utf-8") as f:
|
|
332
|
+
f.writelines(lines)
|
|
333
|
+
except Exception as err:
|
|
334
|
+
validation_results[selected_winner] += f" (Apply warning: {err})"
|
|
335
|
+
|
|
336
|
+
elapsed = round(time.time() - start_time, 2)
|
|
337
|
+
|
|
338
|
+
status = "PASS" if selected_winner else "FAIL"
|
|
339
|
+
return {
|
|
340
|
+
"status": status,
|
|
341
|
+
"localization": {
|
|
342
|
+
"file": file_path,
|
|
343
|
+
"method": loc.get("method", "unknown"),
|
|
344
|
+
"line_span": loc.get("line_span", [line_no, line_no])
|
|
345
|
+
},
|
|
346
|
+
"candidates_evaluated": len(candidates),
|
|
347
|
+
"validation_results": validation_results,
|
|
348
|
+
"selected_winner": selected_winner,
|
|
349
|
+
"tokens_consumed": min(tokens_consumed, self.token_budget),
|
|
350
|
+
"elapsed_seconds": elapsed
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def main():
|
|
355
|
+
parser = argparse.ArgumentParser(description="Agentless 3-Stage Deterministic Patcher")
|
|
356
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Subcommand to execute")
|
|
357
|
+
|
|
358
|
+
# heal-failure
|
|
359
|
+
hf_parser = subparsers.add_parser("heal-failure", help="End-to-end 3-phase automated failure healing")
|
|
360
|
+
hf_parser.add_argument("--error-log", required=True, help="Path to error log or raw log content")
|
|
361
|
+
hf_parser.add_argument("--repo-map", help="Path to repo_map.txt for localization")
|
|
362
|
+
hf_parser.add_argument("--max-candidates", type=int, default=3, help="Max patch candidates to generate")
|
|
363
|
+
hf_parser.add_argument("--validator", help="Validator command to run")
|
|
364
|
+
hf_parser.add_argument("--apply", action="store_true", help="Apply winning patch to disk")
|
|
365
|
+
|
|
366
|
+
# localize-error
|
|
367
|
+
loc_parser = subparsers.add_parser("localize-error", help="Phase 1: Localize error in code")
|
|
368
|
+
loc_parser.add_argument("--error-log", required=True, help="Path to error log or log string")
|
|
369
|
+
loc_parser.add_argument("--repo-map", help="Path to repo_map.txt")
|
|
370
|
+
|
|
371
|
+
# generate-candidates
|
|
372
|
+
gen_parser = subparsers.add_parser("generate-candidates", help="Phase 2: Generate candidate patches")
|
|
373
|
+
gen_parser.add_argument("--file", required=True, help="Target file path")
|
|
374
|
+
gen_parser.add_argument("--line", type=int, default=1, help="Failing line number")
|
|
375
|
+
gen_parser.add_argument("--chunk", help="Target failing code chunk")
|
|
376
|
+
|
|
377
|
+
args = parser.parse_args()
|
|
378
|
+
patcher = AgentlessPatcher()
|
|
379
|
+
|
|
380
|
+
if args.subcommand == "heal-failure":
|
|
381
|
+
log_content = args.error_log
|
|
382
|
+
if os.path.exists(args.error_log):
|
|
383
|
+
with open(args.error_log, "r", encoding="utf-8", errors="ignore") as f:
|
|
384
|
+
log_content = f.read()
|
|
385
|
+
|
|
386
|
+
result = patcher.heal_failure(
|
|
387
|
+
error_log=log_content,
|
|
388
|
+
repo_map_path=args.repo_map,
|
|
389
|
+
max_candidates=args.max_candidates,
|
|
390
|
+
validator_cmd=args.validator,
|
|
391
|
+
apply_patch=args.apply
|
|
392
|
+
)
|
|
393
|
+
print(json.dumps(result, indent=2))
|
|
394
|
+
sys.exit(0 if result["status"] == "PASS" else 1)
|
|
395
|
+
|
|
396
|
+
elif args.subcommand == "localize-error":
|
|
397
|
+
log_content = args.error_log
|
|
398
|
+
if os.path.exists(args.error_log):
|
|
399
|
+
with open(args.error_log, "r", encoding="utf-8", errors="ignore") as f:
|
|
400
|
+
log_content = f.read()
|
|
401
|
+
|
|
402
|
+
loc = patcher.localize_error(log_content, repo_map_path=args.repo_map)
|
|
403
|
+
print(json.dumps(loc, indent=2))
|
|
404
|
+
sys.exit(0)
|
|
405
|
+
|
|
406
|
+
elif args.subcommand == "generate-candidates":
|
|
407
|
+
cands = patcher.generate_candidates(args.file, args.line, failing_chunk=args.chunk)
|
|
408
|
+
print(json.dumps(cands, indent=2))
|
|
409
|
+
sys.exit(0)
|
|
410
|
+
|
|
411
|
+
else:
|
|
412
|
+
parser.print_help()
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
if __name__ == "__main__":
|
|
417
|
+
main()
|
package/tools/analyze_project.py
CHANGED
|
@@ -10,7 +10,7 @@ Executes in sequence:
|
|
|
10
10
|
2. Page Class & AST Repository Mapping (tools/repo_map.py)
|
|
11
11
|
3. Static Defect & Fake-Pass Sentinel (tools/infer_linter.py)
|
|
12
12
|
4. Data Flow Lifecycle & Teardown Audit (tools/trace_data_flow.py)
|
|
13
|
-
5. Definition of Done
|
|
13
|
+
5. Definition of Done 10 Quality Gates (tools/verify_all.py)
|
|
14
14
|
|
|
15
15
|
Generates & Updates In-Place inside the Target Project:
|
|
16
16
|
- PROJECT_HEALTH_REPORT.md (Root & reports/ of target project)
|
|
@@ -19,10 +19,8 @@ Generates & Updates In-Place inside the Target Project:
|
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
21
|
import sys
|
|
22
|
-
import json
|
|
23
22
|
import argparse
|
|
24
23
|
from datetime import datetime
|
|
25
|
-
from pathlib import Path
|
|
26
24
|
|
|
27
25
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
28
26
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -145,10 +143,11 @@ def run_full_analysis(target_repo):
|
|
|
145
143
|
except Exception as e:
|
|
146
144
|
print(f" ⚠️ trace_data_flow error: {e}")
|
|
147
145
|
|
|
148
|
-
# Step 5:
|
|
149
|
-
print(f"\n{BOLD}[5/5] Verifying
|
|
146
|
+
# Step 5: 10 Quality Gates DoD Verification
|
|
147
|
+
print(f"\n{BOLD}[5/5] Verifying 10 Quality Gates DoD (verify_all)...{RESET}")
|
|
150
148
|
gates_passed = True
|
|
151
149
|
gates = [
|
|
150
|
+
("Gate 0: Author Attribution Provenance", verify_all.gate_0_author_integrity),
|
|
152
151
|
("Gate 1: UTF-8 No BOM", verify_all.gate_1_bom_check),
|
|
153
152
|
("Gate 2: Path Portability", verify_all.gate_2_path_portability),
|
|
154
153
|
("Gate 3: Clean POM & Flat Architecture", verify_all.gate_3_clean_pom),
|
|
@@ -156,6 +155,8 @@ def run_full_analysis(target_repo):
|
|
|
156
155
|
("Gate 5: Temporary Files Blacklist", verify_all.gate_5_cleanup_blacklist),
|
|
157
156
|
("Gate 6: Credential Security", verify_all.gate_6_credential_security),
|
|
158
157
|
("Gate 7: Static Defect Sentinel", verify_all.gate_7_infer_linter),
|
|
158
|
+
("Gate 8: Assertion Sensitivity & Mutation Score", verify_all.gate_8_micro_mutator),
|
|
159
|
+
("Gate 9: Skill Routing & Collision Guard", verify_all.gate_9_skill_collision),
|
|
159
160
|
]
|
|
160
161
|
for name, gate_fn in gates:
|
|
161
162
|
if not gate_fn(target_repo, mode=mode):
|
|
@@ -186,7 +187,7 @@ def run_full_analysis(target_repo):
|
|
|
186
187
|
| **Page Classes & Helpers** | `{summary['repo_map'].get('classes_count', 0)}` classes (`{summary['repo_map'].get('methods_count', 0)}` methods) | ✅ INDEXED |
|
|
187
188
|
| **Static Defect Sentinel** | `infer_linter` exit `{summary['infer_linter'].get('exit_code', 0)}` | {'✅ CLEAN' if summary['infer_linter'].get('exit_code', 0) == 0 else '❌ CRITICAL'} |
|
|
188
189
|
| **Lifecycle & Teardown** | `trace_data_flow` exit `{summary['data_flow'].get('exit_code', 0)}` | ✅ AUDITED |
|
|
189
|
-
| **
|
|
190
|
+
| **10 Quality Gates DoD** | `{summary['gates_status']}` | {'✅ 100% PASS' if gates_passed else '❌ BLOCKED'} |
|
|
190
191
|
|
|
191
192
|
---
|
|
192
193
|
|
|
@@ -206,7 +207,7 @@ def run_full_analysis(target_repo):
|
|
|
206
207
|
> - Anchor context directly by reading only two local in-project artifacts:
|
|
207
208
|
> 1. `PROJECT_HEALTH_REPORT.md` (immediate architecture status, warnings, and DoD readiness).
|
|
208
209
|
> 2. `repo_map.txt` (instant lookup for Page Classes, methods, and parameters in < 600 tokens).
|
|
209
|
-
> - **Post-Audit Git
|
|
210
|
+
> - **Post-Audit Git Protocol:** Once analysis completes, stage and commit updated in-project artifacts locally (`git add PROJECT_HEALTH_REPORT.md repo_map.txt && git commit -m "docs(audit): update project health report and AST repo map"`). Per `git-push-protocol.md`, always confirm with the user before executing `git push`.
|
|
210
211
|
> - Whenever the user requests **"analyze project"** or **"project audit"**, re-run `npx synapse-hub analyze` to update this file in-place.
|
|
211
212
|
"""
|
|
212
213
|
with open(report_in_reports, "w", encoding="utf-8") as f:
|
|
@@ -225,13 +226,13 @@ def run_full_analysis(target_repo):
|
|
|
225
226
|
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
226
227
|
print(f" Elapsed Time : {elapsed:.2f}s")
|
|
227
228
|
print(f" Page Classes Indexed : {summary['repo_map'].get('classes_count', 0)} classes")
|
|
228
|
-
print(f"
|
|
229
|
+
print(f" 10 Quality Gates DoD : {GREEN + '100% PASS' if gates_passed else RED + 'FAILED'}{RESET}")
|
|
229
230
|
print(f" In-Project Report Path : {report_in_root if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower() else report_in_reports}")
|
|
230
231
|
if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower():
|
|
231
232
|
print(f" {YELLOW}💡 Post-Analysis Action: Push updated files to Git:{RESET}")
|
|
232
|
-
print(
|
|
233
|
-
print(
|
|
234
|
-
print(
|
|
233
|
+
print(" git add PROJECT_HEALTH_REPORT.md repo_map.txt")
|
|
234
|
+
print(" git commit -m \"docs(audit): update project health report and AST repo map\"")
|
|
235
|
+
print(" git push origin <branch>")
|
|
235
236
|
print(f"{BOLD}{CYAN}======================================================================{RESET}\n")
|
|
236
237
|
|
|
237
238
|
return 0 if gates_passed else 1
|
|
@@ -11,7 +11,7 @@ import os
|
|
|
11
11
|
import sys
|
|
12
12
|
import json
|
|
13
13
|
import argparse
|
|
14
|
-
from typing import Dict, List, Any
|
|
14
|
+
from typing import Dict, List, Any
|
|
15
15
|
|
|
16
16
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
17
17
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -91,7 +91,7 @@ def compare_specs(old_spec: Dict[str, Any], new_spec: Dict[str, Any]) -> Dict[st
|
|
|
91
91
|
"path": f"{method.upper()} {path}",
|
|
92
92
|
"detail": f"Parameter '{p_name}' was changed from optional to required"
|
|
93
93
|
})
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
old_type = old_p.get("type") or old_p.get("schema", {}).get("type")
|
|
96
96
|
new_type = new_p.get("type") or new_p.get("schema", {}).get("type")
|
|
97
97
|
if old_type and new_type and old_type != new_type:
|
|
@@ -145,8 +145,8 @@ def generate_markdown_report(analysis: Dict[str, Any], old_path: str, new_path:
|
|
|
145
145
|
f"- **Verdict:** {status_icon} (Health Score: `{analysis['contract_health_score']:.1f}%`)",
|
|
146
146
|
"",
|
|
147
147
|
"## Summary Metrics",
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
"| Breaking Changes | Warnings | Compatible Additions |",
|
|
149
|
+
"| :---: | :---: | :---: |",
|
|
150
150
|
f"| **{analysis['breaking_changes_count']}** | **{analysis['warnings_count']}** | **{analysis['additions_count']}** |",
|
|
151
151
|
""
|
|
152
152
|
]
|
package/tools/api_fuzzer.py
CHANGED
|
@@ -24,7 +24,7 @@ import urllib.request
|
|
|
24
24
|
import urllib.error
|
|
25
25
|
import urllib.parse
|
|
26
26
|
import argparse
|
|
27
|
-
from typing import Dict, Any, List, Optional
|
|
27
|
+
from typing import Dict, Any, List, Optional
|
|
28
28
|
|
|
29
29
|
# Enforce UTF-8 stdout encoding for Windows/macOS/Linux compatibility
|
|
30
30
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -578,13 +578,13 @@ class ApiFuzzer:
|
|
|
578
578
|
print(f" Verdict : {GREEN}[PASS] API resilient against input fuzzing & chaos.{RESET}")
|
|
579
579
|
else:
|
|
580
580
|
print(f" Verdict : {RED}[FAIL] {total_failures} defect alarms detected. Backend fixes required!{RESET}")
|
|
581
|
-
print(
|
|
581
|
+
print("======================================================================\n")
|
|
582
582
|
|
|
583
583
|
# Save Report
|
|
584
584
|
os.makedirs(REPORTS_DIR, exist_ok=True)
|
|
585
585
|
report_path = os.path.join(REPORTS_DIR, "api_fuzz_report.json")
|
|
586
|
-
with open(report_path, "w", encoding="utf-8") as
|
|
587
|
-
json.dump(scorecard,
|
|
586
|
+
with open(report_path, "w", encoding="utf-8") as out_file:
|
|
587
|
+
json.dump(scorecard, out_file, indent=2)
|
|
588
588
|
print(f"Full telemetry report saved to: {report_path}")
|
|
589
589
|
|
|
590
590
|
return scorecard
|
|
@@ -622,7 +622,7 @@ class ApiFuzzer:
|
|
|
622
622
|
if not is_protected and method.lower() in ("post", "put", "patch", "delete"):
|
|
623
623
|
severity = "HIGH" if re.search(r"admin|internal|user", path, re.IGNORECASE) else "MEDIUM"
|
|
624
624
|
findings.append({
|
|
625
|
-
"finding":
|
|
625
|
+
"finding": "AUTH_NOT_DECLARED: Mutating endpoint declares no security requirement",
|
|
626
626
|
"severity": severity,
|
|
627
627
|
"target": label,
|
|
628
628
|
"recommendation": "Declare securitySchemes/security for this operation or document why it is public.",
|
|
@@ -678,7 +678,7 @@ class ApiFuzzer:
|
|
|
678
678
|
print(f" Verdict : {RED}[FAIL] {blocking} HIGH/CRITICAL pre-flight finding(s).{RESET}")
|
|
679
679
|
else:
|
|
680
680
|
print(f" Verdict : {GREEN}[PASS] No HIGH/CRITICAL spec-level finding.{RESET}")
|
|
681
|
-
print(
|
|
681
|
+
print("======================================================================\n")
|
|
682
682
|
for f in findings:
|
|
683
683
|
print(f" - [{f['severity']}] {f['target']} - {f['finding']}")
|
|
684
684
|
print(f" 👉 {f['recommendation']}")
|
|
@@ -694,8 +694,8 @@ class ApiFuzzer:
|
|
|
694
694
|
"severity_summary": severity_summary,
|
|
695
695
|
"findings": findings,
|
|
696
696
|
}
|
|
697
|
-
with open(report_path, "w", encoding="utf-8") as
|
|
698
|
-
json.dump(scorecard,
|
|
697
|
+
with open(report_path, "w", encoding="utf-8") as out_file:
|
|
698
|
+
json.dump(scorecard, out_file, indent=2)
|
|
699
699
|
print(f"\nFull telemetry report saved to: {report_path}")
|
|
700
700
|
|
|
701
701
|
return scorecard
|
package/tools/auto_heal.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Enterprise Closed-Loop Autonomous Test Healer Engine
|
|
4
4
|
-----------------------------------------------------
|
|
5
|
-
Diagnoses test failures in 0.1s from JUnit XML and terminal logs, queries the
|
|
5
|
+
Diagnoses test failures in 0.1s from JUnit XML and terminal logs, queries the
|
|
6
6
|
semantic memory cache for proven patches, and orchestrates targeted re-tests.
|
|
7
7
|
Dispatched automatically by AI Agents when test runs encounter failures.
|
|
8
8
|
"""
|
|
@@ -12,7 +12,6 @@ import sys
|
|
|
12
12
|
import re
|
|
13
13
|
import json
|
|
14
14
|
import argparse
|
|
15
|
-
from pathlib import Path
|
|
16
15
|
|
|
17
16
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
18
17
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
@@ -103,7 +102,7 @@ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
|
|
|
103
102
|
"heuristic_match": None,
|
|
104
103
|
"recommended_patch": None,
|
|
105
104
|
"verbal_reflection": None,
|
|
106
|
-
"episodic_lessons": [
|
|
105
|
+
"episodic_lessons": [les["lesson_summary"] for les in lessons]
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
# 2b. Reflexion Verbal Diagnosis
|
|
@@ -113,8 +112,8 @@ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
|
|
|
113
112
|
|
|
114
113
|
if lessons:
|
|
115
114
|
print(f" {CYAN}🧠 [EPISODIC REFLEXION] Top Historical Lessons for Context:{RESET}")
|
|
116
|
-
for idx,
|
|
117
|
-
print(f" {idx}. [{
|
|
115
|
+
for idx, les in enumerate(lessons, 1):
|
|
116
|
+
print(f" {idx}. [{les['page_context']}]: {les['lesson_summary']}")
|
|
118
117
|
print(f" {CYAN}💡 [REFLEXION DIAGNOSIS] {verbal_reflection}{RESET}")
|
|
119
118
|
|
|
120
119
|
if cache_solution:
|
|
@@ -191,6 +190,25 @@ def main():
|
|
|
191
190
|
print(f"✅ [EPISODIC REFLEXION] Lesson recorded for '{args.context}': {args.record_lesson}")
|
|
192
191
|
return 0
|
|
193
192
|
|
|
193
|
+
if args.reflexion:
|
|
194
|
+
healer = auto_heal_reflexion.ClosedLoopHealer(max_trials=args.max_trials)
|
|
195
|
+
reflection = healer.generate_reflection(args.error, "", "")
|
|
196
|
+
print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
|
|
197
|
+
print(f"{BOLD}{CYAN} MULTI-TRIAL EPISODIC REFLEXION HEALER LOOP (K={args.max_trials}) {RESET}")
|
|
198
|
+
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
199
|
+
print(f" Target Context : {args.context}")
|
|
200
|
+
print(f" Error Signature: {args.error}")
|
|
201
|
+
print(f" Diagnosis (VRL): {reflection}")
|
|
202
|
+
target_dir = os.path.abspath(args.target)
|
|
203
|
+
plan = diagnose_and_plan_heal(target_dir, junit_xml_path=args.xml)
|
|
204
|
+
plan["reflexion"] = {
|
|
205
|
+
"max_trials": args.max_trials,
|
|
206
|
+
"verbal_reflection": reflection,
|
|
207
|
+
"status": "INITIAL_TRIAL_DISPATCHED"
|
|
208
|
+
}
|
|
209
|
+
print(json.dumps(plan, indent=2))
|
|
210
|
+
return 0
|
|
211
|
+
|
|
194
212
|
target_dir = os.path.abspath(args.target)
|
|
195
213
|
plan = diagnose_and_plan_heal(target_dir, junit_xml_path=args.xml)
|
|
196
214
|
print(json.dumps(plan, indent=2))
|
|
@@ -24,8 +24,8 @@ import os
|
|
|
24
24
|
import sys
|
|
25
25
|
import json
|
|
26
26
|
import argparse
|
|
27
|
-
from dataclasses import dataclass
|
|
28
|
-
from typing import List, Optional, Callable,
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import List, Optional, Callable, Tuple
|
|
29
29
|
|
|
30
30
|
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
31
31
|
if hasattr(sys.stdout, 'reconfigure'):
|