mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2405 from PR0M3TH3AN/unstable-12848923588166411502
Run audit-agent daily task
This commit is contained in:
12 files changed
+756
-23
No files matched your search
+10
-16
@@ -1,25 +1,19 @@
|
||||
# CONTEXT - bitvid-perf-agent
|
||||
# CONTEXT - bitvid-audit-agent
|
||||
|
||||
## Goal
|
||||
Daily, measurable improvement of app responsiveness by identifying and remediating background CPU/network work that degrades UX — while also ensuring user-facing docs (/content) reflect the actual runtime behavior of upload/contribution flows.
|
||||
Run the daily/weekly static audit scripts (file size, innerHTML, lint) to detect regressions and generate a report.
|
||||
|
||||
## Scope
|
||||
- Repo: `PR0M3TH3AN/bitvid` (unstable branch).
|
||||
- Focus Areas:
|
||||
- P0: Login/auth, relay initialization, decryption (hashtags, subscriptions, blocks, watch history).
|
||||
- P1: Initial UI responsiveness (profile cache, relay load).
|
||||
- P2: User-initiated background features (WebTorrent).
|
||||
- Docs: `/content` (user-facing docs), ensuring upload/contribution docs match code.
|
||||
- Focus: Static analysis reports.
|
||||
|
||||
## Constraints
|
||||
- Never invent files, APIs, libraries, or behaviors.
|
||||
- Prefer minimal incremental changes.
|
||||
- Keep build/tests green.
|
||||
- Preserve repo style and architecture.
|
||||
- Security-critical changes require human signoff.
|
||||
- Read-only job (do not modify source files).
|
||||
- Reproducible reports.
|
||||
- Confidentiality (redact secrets).
|
||||
|
||||
## Definition of Done
|
||||
- Performance issues identified and fixed with small, safe PRs.
|
||||
- Docs in `/content` matched to code.
|
||||
- `daily-perf-report` generated.
|
||||
- Artifacts (`TODO.md`, `DECISIONS.md`, `TEST_LOG.md`) updated.
|
||||
- Audit scripts run successfully.
|
||||
- Artifacts (`raw-*.log`, `*-report.json`) generated.
|
||||
- `summary.md` created.
|
||||
- `AGENT_TASK_LOG.csv` updated.
|
||||
@@ -1,5 +1,14 @@
|
||||
# Decisions Log
|
||||
|
||||
## 2026-02-12: Audit Run
|
||||
|
||||
### Context
|
||||
Running the scheduled `audit-agent` task.
|
||||
|
||||
### Decision: Initial Run
|
||||
- **Action:** Created initial audit report artifacts.
|
||||
- **Rationale:** No previous reports found, so no delta comparison performed.
|
||||
|
||||
## 2026-02-17: Dependency Audit & Upgrade
|
||||
|
||||
### Context
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# TODO: Dependency Management
|
||||
# TODO: Audit Run 2026-02-12
|
||||
|
||||
## Priority: High (Blockers)
|
||||
## Priority: High (Audit)
|
||||
- [x] Run `scripts/check-file-size.mjs --report`.
|
||||
- [x] Run `scripts/check-innerhtml.mjs --report`.
|
||||
- [x] Run `npm run lint`.
|
||||
- [x] Parse logs into JSON.
|
||||
- [x] Generate summary report.
|
||||
|
||||
## Priority: Medium (Backlog)
|
||||
- [ ] Fix E2E test environment: `npx playwright install` must be run in the CI/Agent environment before running `npm run test:e2e` for upgrades. This blocked the `esbuild` upgrade.
|
||||
|
||||
## Priority: Medium (Security/Compliance)
|
||||
- [ ] Review `nostr-tools` upgrade (v2.19.4 -> v2.23.0). See `issues/upgrade-nostr-tools.md`.
|
||||
- [ ] Review `tailwindcss` (v4). See `issues/upgrade-tailwindcss.md`.
|
||||
|
||||
## Priority: Low (Maintenance)
|
||||
- [ ] Batch upgrade dev dependencies (`stylelint`, `postcss-import`).
|
||||
|
||||
## Priority: High (Performance)
|
||||
- [x] Bound concurrency in `authService.js` for profile hydration (P0).
|
||||
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"grandfathered_files": 43,
|
||||
"grandfathered_excess_lines": 49463,
|
||||
"new_oversized_files": 0,
|
||||
"new_excess_lines": 0,
|
||||
"grandfathered_list": [
|
||||
{
|
||||
"path": "js/adminListStore.js",
|
||||
"lines": 1033,
|
||||
"excess": 33
|
||||
},
|
||||
{
|
||||
"path": "js/app/authSessionCoordinator.js",
|
||||
"lines": 1743,
|
||||
"excess": 743
|
||||
},
|
||||
{
|
||||
"path": "js/app/feedCoordinator.js",
|
||||
"lines": 1767,
|
||||
"excess": 767
|
||||
},
|
||||
{
|
||||
"path": "js/app/playbackCoordinator.js",
|
||||
"lines": 1217,
|
||||
"excess": 217
|
||||
},
|
||||
{
|
||||
"path": "js/app.js",
|
||||
"lines": 5245,
|
||||
"excess": 4245
|
||||
},
|
||||
{
|
||||
"path": "js/channelProfile.js",
|
||||
"lines": 5530,
|
||||
"excess": 4530
|
||||
},
|
||||
{
|
||||
"path": "js/feedEngine/stages.js",
|
||||
"lines": 1350,
|
||||
"excess": 350
|
||||
},
|
||||
{
|
||||
"path": "js/historyView.js",
|
||||
"lines": 2547,
|
||||
"excess": 1547
|
||||
},
|
||||
{
|
||||
"path": "js/index.js",
|
||||
"lines": 1407,
|
||||
"excess": 407
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/client.js",
|
||||
"lines": 3990,
|
||||
"excess": 2990
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/managers/SignerManager.js",
|
||||
"lines": 1106,
|
||||
"excess": 106
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/nip46Client.js",
|
||||
"lines": 2102,
|
||||
"excess": 1102
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/nip46Connector.js",
|
||||
"lines": 1094,
|
||||
"excess": 94
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/nip71.js",
|
||||
"lines": 1531,
|
||||
"excess": 531
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/publishHelpers.js",
|
||||
"lines": 1415,
|
||||
"excess": 415
|
||||
},
|
||||
{
|
||||
"path": "js/nostr/watchHistory.js",
|
||||
"lines": 2211,
|
||||
"excess": 1211
|
||||
},
|
||||
{
|
||||
"path": "js/nostrEventSchemas.js",
|
||||
"lines": 2908,
|
||||
"excess": 1908
|
||||
},
|
||||
{
|
||||
"path": "js/payments/nwcClient.js",
|
||||
"lines": 1642,
|
||||
"excess": 642
|
||||
},
|
||||
{
|
||||
"path": "js/services/authService.js",
|
||||
"lines": 1321,
|
||||
"excess": 321
|
||||
},
|
||||
{
|
||||
"path": "js/services/commentThreadService.js",
|
||||
"lines": 1122,
|
||||
"excess": 122
|
||||
},
|
||||
{
|
||||
"path": "js/services/hashtagPreferencesService.js",
|
||||
"lines": 1435,
|
||||
"excess": 435
|
||||
},
|
||||
{
|
||||
"path": "js/services/moderationService.js",
|
||||
"lines": 2513,
|
||||
"excess": 1513
|
||||
},
|
||||
{
|
||||
"path": "js/services/nostrService.js",
|
||||
"lines": 2227,
|
||||
"excess": 1227
|
||||
},
|
||||
{
|
||||
"path": "js/services/playbackService.js",
|
||||
"lines": 1289,
|
||||
"excess": 289
|
||||
},
|
||||
{
|
||||
"path": "js/services/r2Service.js",
|
||||
"lines": 1194,
|
||||
"excess": 194
|
||||
},
|
||||
{
|
||||
"path": "js/state/cache.js",
|
||||
"lines": 1829,
|
||||
"excess": 829
|
||||
},
|
||||
{
|
||||
"path": "js/subscriptions.js",
|
||||
"lines": 2374,
|
||||
"excess": 1374
|
||||
},
|
||||
{
|
||||
"path": "js/ui/applicationBootstrap.js",
|
||||
"lines": 1169,
|
||||
"excess": 169
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/EditModal.js",
|
||||
"lines": 1130,
|
||||
"excess": 130
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/RevertModal.js",
|
||||
"lines": 1663,
|
||||
"excess": 663
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/SimilarContentCard.js",
|
||||
"lines": 1303,
|
||||
"excess": 303
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/UploadModal.js",
|
||||
"lines": 1320,
|
||||
"excess": 320
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/VideoCard.js",
|
||||
"lines": 3130,
|
||||
"excess": 2130
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/VideoModal.js",
|
||||
"lines": 6285,
|
||||
"excess": 5285
|
||||
},
|
||||
{
|
||||
"path": "js/ui/loginModalController.js",
|
||||
"lines": 2552,
|
||||
"excess": 1552
|
||||
},
|
||||
{
|
||||
"path": "js/ui/moreMenuController.js",
|
||||
"lines": 1601,
|
||||
"excess": 601
|
||||
},
|
||||
{
|
||||
"path": "js/ui/overlay/popoverEngine.js",
|
||||
"lines": 1070,
|
||||
"excess": 70
|
||||
},
|
||||
{
|
||||
"path": "js/ui/profileModalController.js",
|
||||
"lines": 8757,
|
||||
"excess": 7757
|
||||
},
|
||||
{
|
||||
"path": "js/ui/views/VideoListView.js",
|
||||
"lines": 1392,
|
||||
"excess": 392
|
||||
},
|
||||
{
|
||||
"path": "js/ui/zapController.js",
|
||||
"lines": 1206,
|
||||
"excess": 206
|
||||
},
|
||||
{
|
||||
"path": "js/userBlocks.js",
|
||||
"lines": 2305,
|
||||
"excess": 1305
|
||||
},
|
||||
{
|
||||
"path": "js/watchHistoryService.js",
|
||||
"lines": 1400,
|
||||
"excess": 400
|
||||
},
|
||||
{
|
||||
"path": "js/webtorrent.js",
|
||||
"lines": 1038,
|
||||
"excess": 38
|
||||
}
|
||||
],
|
||||
"new_list": []
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"total_assignments": 87,
|
||||
"files_count": 34,
|
||||
"top_offenders": [
|
||||
{
|
||||
"path": "js/channelProfile.js",
|
||||
"count": 10,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/RevertModal.js",
|
||||
"count": 10,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/docsView.js",
|
||||
"count": 8,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/subscriptions.js",
|
||||
"count": 7,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/subscriptionHistoryController.js",
|
||||
"count": 7,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/historyView.js",
|
||||
"count": 4,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/DeleteModal.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/ShareNostrModal.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/DMSettingsModalController.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/loginModalController.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/views/VideoListView.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/viewManager.js",
|
||||
"count": 3,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/utils/qrcode.js",
|
||||
"count": 2,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/app/feedCoordinator.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/app.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/exploreView.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/forYouView.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/index.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/kidsView.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/EditModal.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/EmbedVideoModal.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/EventDetailsModal.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/UploadModal.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/VideoModal.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/components/nip71FormManager.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/AppShell.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/Composer.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/ConversationList.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/DMRelaySettings.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/MessageThread.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/dm/NotificationCenter.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/moreMenuController.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/profileModalController.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"path": "js/ui/videoListViewController.js",
|
||||
"count": 1,
|
||||
"is_new": false
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"total_failures": 0,
|
||||
"files_with_errors": [],
|
||||
"skipped_checks": [
|
||||
{
|
||||
"check": "lint:assets",
|
||||
"reason": "Missing dist/asset-manifest.json.",
|
||||
"details": "asset reference check (build required)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
Title: Audit Report — 2026-02-12 (unstable)
|
||||
|
||||
**Summary**
|
||||
|
||||
* Date: 2026-02-12
|
||||
* Branch: unstable
|
||||
|
||||
**Metrics**
|
||||
|
||||
* Grandfathered oversized files: 43 files (total excess lines: 49463)
|
||||
* New oversized files: 0 files (total excess lines: 0)
|
||||
* Total innerHTML assignments: 87 across 34 files
|
||||
|
||||
* Top offenders:
|
||||
|
||||
1. js/channelProfile.js — 10
|
||||
2. js/ui/components/RevertModal.js — 10
|
||||
3. js/docsView.js — 8
|
||||
4. js/subscriptions.js — 7
|
||||
5. js/ui/subscriptionHistoryController.js — 7
|
||||
6. js/historyView.js — 4
|
||||
7. js/ui/components/DeleteModal.js — 3
|
||||
8. js/ui/components/ShareNostrModal.js — 3
|
||||
9. js/ui/dm/DMSettingsModalController.js — 3
|
||||
10. js/ui/loginModalController.js — 3
|
||||
|
||||
* Lint failures: 0
|
||||
* Skipped checks: 1
|
||||
* lint:assets: Missing dist/asset-manifest.json.
|
||||
|
||||
**High-priority items**
|
||||
|
||||
* None. Keep it up!
|
||||
|
||||
**Artifacts**
|
||||
|
||||
* file-size-report.json
|
||||
* innerhtml-report.json
|
||||
* lint-report.json
|
||||
* raw logs
|
||||
@@ -1 +1,2 @@
|
||||
date,agent_name,prompt_file,status,branch,summary
|
||||
2026-02-12,audit-agent,bitvid-audit-agent.md,completed,unstable,"Ran audit scripts, created initial reports"
|
||||
|
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
def parse_file_size_log(filepath):
|
||||
"""
|
||||
Parses check-file-size.mjs raw output.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
metrics = {
|
||||
"grandfathered_files": 0,
|
||||
"grandfathered_excess_lines": 0,
|
||||
"new_oversized_files": 0,
|
||||
"new_excess_lines": 0,
|
||||
"grandfathered_list": [],
|
||||
"new_list": [],
|
||||
}
|
||||
|
||||
# Match "grandfathered" lines
|
||||
# Example: " ⚠ grandfathered: js/adminListStore.js (1033 lines)"
|
||||
grandfathered_matches = re.findall(r"⚠ grandfathered: (.+?) \((\d+) lines\)", content)
|
||||
for path, lines in grandfathered_matches:
|
||||
metrics["grandfathered_files"] += 1
|
||||
excess = int(lines) - 1000 # Default threshold
|
||||
metrics["grandfathered_excess_lines"] += excess
|
||||
metrics["grandfathered_list"].append({"path": path, "lines": int(lines), "excess": excess})
|
||||
|
||||
# Match "violations" (new files)
|
||||
# Example: " ✗ NEW: js/newfile.js (1200 lines, threshold 1000)"
|
||||
new_matches = re.findall(r"✗ NEW: (.+?) \((\d+) lines, threshold (\d+)\)", content)
|
||||
for path, lines, threshold in new_matches:
|
||||
metrics["new_oversized_files"] += 1
|
||||
excess = int(lines) - int(threshold)
|
||||
metrics["new_excess_lines"] += excess
|
||||
metrics["new_list"].append({"path": path, "lines": int(lines), "excess": excess})
|
||||
|
||||
# Match "GREW" violations (grandfathered growing)
|
||||
# Example: " ✗ GREW: js/file.js (1200 lines, was 1100, limit 1150)"
|
||||
grew_matches = re.findall(r"✗ GREW: (.+?) \((\d+) lines, was (\d+), limit (\d+)\)", content)
|
||||
for path, lines, was, limit in grew_matches:
|
||||
metrics["new_oversized_files"] += 1 # Count as a violation
|
||||
excess = int(lines) - int(limit) # Or relative to original? Let's use diff
|
||||
metrics["new_excess_lines"] += excess
|
||||
metrics["new_list"].append({"path": path, "lines": int(lines), "excess": excess, "type": "GREW"})
|
||||
|
||||
return metrics
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python parse_file_size.py <logfile>")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = sys.argv[1]
|
||||
metrics = parse_file_size_log(filepath)
|
||||
print(json.dumps(metrics, indent=2))
|
||||
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
def parse_innerhtml_log(filepath):
|
||||
"""
|
||||
Parses check-innerhtml.mjs raw output.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
metrics = {
|
||||
"total_assignments": 0,
|
||||
"files_count": 0,
|
||||
"top_offenders": [],
|
||||
"violations": []
|
||||
}
|
||||
|
||||
# Match total summary
|
||||
# Example: "innerHTML usage: 87 assignments across 34 files"
|
||||
summary_match = re.search(r"innerHTML usage: (\d+) assignments across (\d+) files", content)
|
||||
if summary_match:
|
||||
metrics["total_assignments"] = int(summary_match.group(1))
|
||||
metrics["files_count"] = int(summary_match.group(2))
|
||||
|
||||
# Match individual file usage
|
||||
# Example: " js/channelProfile.js: 10" or " js/file.js: 5 ← NEW"
|
||||
usage_matches = re.findall(r"^\s+([^\s:]+): (\d+)( ← NEW)?", content, re.MULTILINE)
|
||||
for path, count, is_new in usage_matches:
|
||||
metrics["top_offenders"].append({"path": path, "count": int(count), "is_new": bool(is_new)})
|
||||
|
||||
# Match violations (enforce mode output)
|
||||
# Example: " ✗ js/file.js: 5 total (baseline 3, +2 new) at line(s) 10, 20"
|
||||
violation_matches = re.findall(r"✗ ([^:]+): (\d+) total \(baseline (\d+), \+(\d+) new\) at line\(s\) (.+)", content)
|
||||
for path, total, baseline, new, lines in violation_matches:
|
||||
metrics["violations"].append({
|
||||
"path": path,
|
||||
"total": int(total),
|
||||
"baseline": int(baseline),
|
||||
"new": int(new),
|
||||
"lines": lines
|
||||
})
|
||||
|
||||
return metrics
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python parse_innerhtml.py <logfile>")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = sys.argv[1]
|
||||
metrics = parse_innerhtml_log(filepath)
|
||||
print(json.dumps(metrics, indent=2))
|
||||
@@ -0,0 +1,58 @@
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
def parse_lint_log(filepath):
|
||||
"""
|
||||
Parses npm run lint raw output.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
metrics = {
|
||||
"total_failures": 0,
|
||||
"files_with_errors": [],
|
||||
"skipped_checks": []
|
||||
}
|
||||
|
||||
# Looking for typical npm error outputs or specific linter errors.
|
||||
# Since we are running `npm run lint` which chains multiple commands, failure results in `npm ERR!`.
|
||||
|
||||
# Check if the process exited with error (usually indicated by npm ERR!)
|
||||
if "npm ERR!" in content:
|
||||
metrics["total_failures"] = 1 # At least one failure
|
||||
|
||||
# Check for specific skipped checks
|
||||
# Example: "[lint:assets] Missing dist/asset-manifest.json. Skipping asset reference check (build required)."
|
||||
skipped_matches = re.findall(r"\[(.+?)\] (.+?) Skipping (.+)", content)
|
||||
for check_name, reason, details in skipped_matches:
|
||||
metrics["skipped_checks"].append({
|
||||
"check": check_name,
|
||||
"reason": reason.strip(),
|
||||
"details": details.strip()
|
||||
})
|
||||
|
||||
# Check for stylelint errors
|
||||
# Example: "css/source.css\n 10:5 × Unexpected unknown property "foo" property-no-unknown"
|
||||
# This is hard to parse generally without JSON output, but we can look for "×" or "error" lines.
|
||||
|
||||
# Count "problems" from eslint/stylelint if standard format
|
||||
# "3 problems (3 errors, 0 warnings)"
|
||||
problem_matches = re.findall(r"(\d+) problems? \((\d+) errors?, (\d+) warnings?\)", content)
|
||||
for problems, errors, warnings in problem_matches:
|
||||
metrics["total_failures"] += int(errors)
|
||||
|
||||
return metrics
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python parse_lint.py <logfile>")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = sys.argv[1]
|
||||
metrics = parse_lint_log(filepath)
|
||||
print(json.dumps(metrics, indent=2))
|
||||
@@ -0,0 +1,99 @@
|
||||
import json
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
def load_json(filepath):
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
def generate_summary(file_size_path, innerhtml_path, lint_path, date_str):
|
||||
file_size = load_json(file_size_path)
|
||||
innerhtml = load_json(innerhtml_path)
|
||||
lint = load_json(lint_path)
|
||||
|
||||
lines = []
|
||||
lines.append(f"Title: Audit Report — {date_str} (unstable)")
|
||||
lines.append("")
|
||||
lines.append("**Summary**")
|
||||
lines.append("")
|
||||
lines.append(f"* Date: {date_str}")
|
||||
lines.append("* Branch: unstable")
|
||||
lines.append("")
|
||||
lines.append("**Metrics**")
|
||||
lines.append("")
|
||||
|
||||
# File Size
|
||||
gf_files = file_size.get("grandfathered_files", 0)
|
||||
gf_excess = file_size.get("grandfathered_excess_lines", 0)
|
||||
new_files = file_size.get("new_oversized_files", 0)
|
||||
new_excess = file_size.get("new_excess_lines", 0)
|
||||
|
||||
lines.append(f"* Grandfathered oversized files: {gf_files} files (total excess lines: {gf_excess})")
|
||||
lines.append(f"* New oversized files: {new_files} files (total excess lines: {new_excess})")
|
||||
|
||||
# InnerHTML
|
||||
total_assign = innerhtml.get("total_assignments", 0)
|
||||
files_count = innerhtml.get("files_count", 0)
|
||||
lines.append(f"* Total innerHTML assignments: {total_assign} across {files_count} files")
|
||||
lines.append("")
|
||||
lines.append(" * Top offenders:")
|
||||
lines.append("")
|
||||
|
||||
top_offenders = innerhtml.get("top_offenders", [])
|
||||
# Sort by count desc
|
||||
top_offenders.sort(key=lambda x: x["count"], reverse=True)
|
||||
|
||||
for i, offender in enumerate(top_offenders[:10]):
|
||||
status = " (NEW)" if offender.get("is_new") else ""
|
||||
lines.append(f" {i+1}. {offender['path']} — {offender['count']}{status}")
|
||||
|
||||
# Lint
|
||||
lint_failures = lint.get("total_failures", 0)
|
||||
lint_skipped = lint.get("skipped_checks", [])
|
||||
|
||||
lines.append("")
|
||||
lines.append(f"* Lint failures: {lint_failures}")
|
||||
if lint_skipped:
|
||||
lines.append(f"* Skipped checks: {len(lint_skipped)}")
|
||||
for skip in lint_skipped:
|
||||
lines.append(f" * {skip['check']}: {skip['reason']}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("**High-priority items**")
|
||||
lines.append("")
|
||||
|
||||
if new_files > 0:
|
||||
for f in file_size.get("new_list", []):
|
||||
lines.append(f"* Remove or trim oversized file `{f['path']}` (excess lines: {f['excess']})")
|
||||
|
||||
violations = innerhtml.get("violations", [])
|
||||
if violations:
|
||||
for v in violations:
|
||||
lines.append(f"* Review `{v['path']}` for new innerHTML usage (+{v['new']})")
|
||||
|
||||
if lint_failures > 0:
|
||||
lines.append(f"* Fix lint errors (see logs)")
|
||||
|
||||
if not (new_files > 0 or violations or lint_failures > 0):
|
||||
lines.append("* None. Keep it up!")
|
||||
|
||||
lines.append("")
|
||||
lines.append("**Artifacts**")
|
||||
lines.append("")
|
||||
lines.append("* file-size-report.json")
|
||||
lines.append("* innerhtml-report.json")
|
||||
lines.append("* lint-report.json")
|
||||
lines.append("* raw logs")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 5:
|
||||
print("Usage: python generate_summary.py <file-size-json> <innerhtml-json> <lint-json> <date>")
|
||||
sys.exit(1)
|
||||
|
||||
summary = generate_summary(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print(summary)
|
||||
Reference in new issue
Block a user