| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6f9cf99 commit dc33515
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -67,13 +67,176 @@ else | |||
| 67 | 67 | echo "✖ Gradle failed with status $status. Full log at: $LOG" | |
| 68 | 68 | fi | |
| 69 | 69 | ||
| 70 | - # Hand over to your logs analyst agent — keep the main session output tiny. | ||
| 70 | + # Generate the summary artifacts deterministically — no LLM, no claude spawn. | ||
| 71 | 71 | echo | |
| 72 | - echo "Delegating to gradle-logs-analyst agent…" | ||
| 73 | - # If your CLI supports non-streaming, set it here to avoid verbose output. | ||
| 74 | - # Example (uncomment if supported): export CLAUDE_NO_STREAM=1 | ||
| 75 | - | ||
| 76 | - # Sub-agents would inherit the full parent env, including CLAUDECODE, which | ||
| 77 | - # apparently causes problems with some versions of Claude (e.g. 4.6). | ||
| 78 | - unset CLAUDECODE | ||
| 79 | - claude "Act as the gradle-logs-analyst agent to parse the build log at: $LOG. Generate the required Gradle summary artifacts as specified in the gradle-logs-analyst agent definition." | ||
| 72 | + echo "Generating gradle summary…" | ||
| 73 | + python3 - "$LOG" <<'PYEOF' || echo "⚠ gradle summary generation failed — full log at: $LOG" | ||
| 74 | + import json, os, re, sys | ||
| 75 | + | ||
| 76 | + log_path = sys.argv[1] if len(sys.argv) > 1 else "" | ||
| 77 | + OUT_DIR = os.path.join("build", "reports", "claude") | ||
| 78 | + MD = os.path.join(OUT_DIR, "gradle-summary.md") | ||
| 79 | + JS = os.path.join(OUT_DIR, "gradle-summary.json") | ||
| 80 | + | ||
| 81 | + status, total_time, cur_task, cur_test_task = "UNKNOWN", None, None, None | ||
| 82 | + failed_tasks, headlines, dep_issues, test_failures = [], [], [], [] | ||
| 83 | + warnings, seen_warnings = [], set() | ||
| 84 | + tests = {"total": 0, "failed": 0, "skipped": 0, "modules": {}} | ||
| 85 | + | ||
| 86 | + RE_STATUS = re.compile(r"^BUILD (SUCCESSFUL|FAILED)(?: in (.+?))?\s*$") | ||
| 87 | + RE_TASK = re.compile(r"^> Task (\S+)") | ||
| 88 | + RE_TEST_TASK = re.compile(r"(?i)test|gtest") | ||
| 89 | + RE_GSUM = re.compile(r"(\d+) tests? (successful|failed|skipped)") | ||
| 90 | + RE_COMP = re.compile(r"(\d+) tests? completed(?:, (\d+) failed)?(?:, (\d+) skipped)?") | ||
| 91 | + RE_GTEST_P = re.compile(r"\[ PASSED \] (\d+) tests?") | ||
| 92 | + RE_GTEST_F = re.compile(r"\[ FAILED \] (\d+) tests?") | ||
| 93 | + RE_DEP = re.compile(r"Could not (?:resolve|find|get)|timed out|status code: 40[13]|artifact .* not found", re.I) | ||
| 94 | + RE_WARN = re.compile(r"\bw: |warning:|deprecat", re.I) | ||
| 95 | + RE_FAILURE = re.compile(r"^FAILURE: Build (failed with an exception|completed with \d+ failures)") | ||
| 96 | + RE_TASK_IN_MSG = re.compile(r"task '(:[^']+)'") | ||
| 97 | + | ||
| 98 | + def mod(name): | ||
| 99 | + return tests["modules"].setdefault(name, {"total": 0, "failed": 0, "skipped": 0}) | ||
| 100 | + | ||
| 101 | + def add_counts(name, ok, failed, skipped): | ||
| 102 | + m = mod(name) | ||
| 103 | + m["total"] += ok + failed + skipped | ||
| 104 | + m["failed"] += failed | ||
| 105 | + m["skipped"] += skipped | ||
| 106 | + tests["total"] += ok + failed + skipped | ||
| 107 | + tests["failed"] += failed | ||
| 108 | + tests["skipped"] += skipped | ||
| 109 | + | ||
| 110 | + if not log_path or not os.path.isfile(log_path) or os.path.getsize(log_path) == 0: | ||
| 111 | + why = f"log not found: {log_path!r}" if not log_path or not os.path.isfile(log_path) else "log is empty" | ||
| 112 | + os.makedirs(OUT_DIR, exist_ok=True) | ||
| 113 | + open(MD, "w").write(f"# Gradle Summary\n\n- Status: UNKNOWN ({why})\n\nFull log unavailable or empty.\n") | ||
| 114 | + json.dump({"status": "UNKNOWN", "totalTime": None, "failedTasks": [], "warnings": [], | ||
| 115 | + "tests": tests, "slowTasks": [], "depIssues": [why], "actions": []}, open(JS, "w"), indent=1) | ||
| 116 | + print(f"Gradle log unusable ({why}); wrote {MD} and {JS}") | ||
| 117 | + sys.exit(0) | ||
| 118 | + | ||
| 119 | + grab_headline = False | ||
| 120 | + collecting_headline = False | ||
| 121 | + with open(log_path, encoding="utf-8", errors="replace") as f: | ||
| 122 | + for line in f: | ||
| 123 | + line = line.rstrip("\n") | ||
| 124 | + m = RE_STATUS.match(line) | ||
| 125 | + if m: | ||
| 126 | + status = m.group(1) | ||
| 127 | + total_time = m.group(2) | ||
| 128 | + continue | ||
| 129 | + m = RE_TASK.match(line) | ||
| 130 | + if m: | ||
| 131 | + cur_task = m.group(1) | ||
| 132 | + if RE_TEST_TASK.search(cur_task): | ||
| 133 | + cur_test_task = cur_task | ||
| 134 | + if line.endswith("FAILED") and m.group(1) not in failed_tasks: | ||
| 135 | + failed_tasks.append(m.group(1)) | ||
| 136 | + continue | ||
| 137 | + if RE_FAILURE.search(line): | ||
| 138 | + grab_headline = True | ||
| 139 | + continue | ||
| 140 | + if grab_headline and line.strip().startswith("* What went wrong"): | ||
| 141 | + headlines.append("") | ||
| 142 | + collecting_headline = True | ||
| 143 | + continue | ||
| 144 | + if grab_headline and collecting_headline: | ||
| 145 | + s = line.strip() | ||
| 146 | + if s.startswith("*") or not s: | ||
| 147 | + collecting_headline = False | ||
| 148 | + continue | ||
| 149 | + if headlines and len(headlines[-1]) < 200: | ||
| 150 | + headlines[-1] += (" " if headlines[-1] else "") + s | ||
| 151 | + continue | ||
| 152 | + m = RE_COMP.search(line) | ||
| 153 | + if m: | ||
| 154 | + total = int(m.group(1)) | ||
| 155 | + failed = int(m.group(2) or 0) | ||
| 156 | + skipped = int(m.group(3) or 0) | ||
| 157 | + add_counts(cur_test_task or cur_task or "(unknown)", total - failed - skipped, failed, skipped) | ||
| 158 | + continue | ||
| 159 | + m = RE_GSUM.search(line) | ||
| 160 | + if m: | ||
| 161 | + n, verb = int(m.group(1)), m.group(2) | ||
| 162 | + mm = mod(cur_test_task or cur_task or "(unknown)") | ||
| 163 | + mm["total"] += n | ||
| 164 | + tests["total"] += n | ||
| 165 | + if verb == "failed": | ||
| 166 | + mm["failed"] += n | ||
| 167 | + tests["failed"] += n | ||
| 168 | + elif verb == "skipped": | ||
| 169 | + mm["skipped"] += n | ||
| 170 | + tests["skipped"] += n | ||
| 171 | + continue | ||
| 172 | + m = RE_GTEST_P.search(line) | ||
| 173 | + if m: | ||
| 174 | + add_counts(cur_test_task or cur_task or "(gtest)", int(m.group(1)), 0, 0) | ||
| 175 | + continue | ||
| 176 | + m = RE_GTEST_F.search(line) | ||
| 177 | + if m: | ||
| 178 | + add_counts(cur_test_task or cur_task or "(gtest)", 0, int(m.group(1)), 0) | ||
| 179 | + continue | ||
| 180 | + if re.search(r" > \S+(?:\.\S+)+.* FAILED$", line) or re.search(r"\[ FAILED \] (\S+)", line): | ||
| 181 | + test_failures.append(line.strip()) | ||
| 182 | + continue | ||
| 183 | + if RE_DEP.search(line) and len(dep_issues) < 20: | ||
| 184 | + if line not in dep_issues: | ||
| 185 | + dep_issues.append(line.strip()) | ||
| 186 | + continue | ||
| 187 | + wm = RE_WARN.search(line) | ||
| 188 | + if wm and len(warnings) < 40: | ||
| 189 | + key = line.strip()[:160] | ||
| 190 | + if key not in seen_warnings: | ||
| 191 | + seen_warnings.add(key) | ||
| 192 | + warnings.append(key) | ||
| 193 | + | ||
| 194 | + headline_by_task = {} | ||
| 195 | + for h in headlines: | ||
| 196 | + tm = RE_TASK_IN_MSG.search(h) | ||
| 197 | + if tm and tm.group(1) in failed_tasks: | ||
| 198 | + headline_by_task[tm.group(1)] = h | ||
| 199 | + # Fallback for single-failure builds where the headline never names its task explicitly. | ||
| 200 | + if not headline_by_task and len(failed_tasks) == 1 and headlines: | ||
| 201 | + headline_by_task[failed_tasks[0]] = headlines[0] | ||
| 202 | + failed = [t + (" — " + headline_by_task[t] if t in headline_by_task else "") for t in failed_tasks] | ||
| 203 | + data = {"status": status, "totalTime": total_time, "failedTasks": failed, "warnings": warnings, | ||
| 204 | + "tests": tests, "slowTasks": [], "depIssues": dep_issues, "actions": []} | ||
| 205 | + | ||
| 206 | + os.makedirs(OUT_DIR, exist_ok=True) | ||
| 207 | + with open(JS, "w") as f: | ||
| 208 | + json.dump(data, f, indent=1) | ||
| 209 | + | ||
| 210 | + L = ["# Gradle Summary\n", f"- Status: {status}", f"- Total time: {total_time or 'UNKNOWN'}", | ||
| 211 | + f"- Log: {log_path}\n"] | ||
| 212 | + if failed: | ||
| 213 | + L.append("## Failing Tasks") | ||
| 214 | + L += [f"- {t}" for t in failed] | ||
| 215 | + L.append("") | ||
| 216 | + if headlines: | ||
| 217 | + L.append("## Primary Failure") | ||
| 218 | + L += [f"```text\n{h}\n```" for h in headlines[:3] if h] | ||
| 219 | + L.append("") | ||
| 220 | + if tests["total"]: | ||
| 221 | + L.append("## Tests") | ||
| 222 | + L.append(f"- Total: {tests['total']}, failed: {tests['failed']}, skipped: {tests['skipped']}") | ||
| 223 | + for name, m in tests["modules"].items(): | ||
| 224 | + L.append(f" - {name}: {m['total']} total, {m['failed']} failed, {m['skipped']} skipped") | ||
| 225 | + if test_failures: | ||
| 226 | + L.append("- Top failing tests:") | ||
| 227 | + L += [f" - {t}" for t in test_failures[:10]] | ||
| 228 | + L.append("") | ||
| 229 | + if warnings: | ||
| 230 | + L.append("## Warnings") | ||
| 231 | + L += [f"- {w}" for w in warnings[:20]] | ||
| 232 | + L.append("") | ||
| 233 | + if dep_issues: | ||
| 234 | + L.append("## Dependency / Network Issues") | ||
| 235 | + L += [f"- {d}" for d in dep_issues[:10]] | ||
| 236 | + L.append("") | ||
| 237 | + L.append("- Per-task durations are not present in plain `-i` console logs; slowTasks omitted.") | ||
| 238 | + open(MD, "w").write("\n".join(L) + "\n") | ||
| 239 | + | ||
| 240 | + print(f"{status} (time: {total_time or 'n/a'}); {len(failed)} failing task(s), {tests['failed']} test failure(s)") | ||
| 241 | + print(f"Wrote {MD} and {JS}") | ||
| 242 | + PYEOF | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments