Merge pull request #2405 from PR0M3TH3AN/unstable-12848923588166411502

Run audit-agent daily task
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-12 16:14:50 -05:00
commit f482909550
12 files changed
+756 -23

No files matched your search

+10 -16
View File
@@ -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.
+9
View File
@@ -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
+9 -7
View File
@@ -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)."
}
]
}
+40
View File
@@ -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
View File
@@ -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"
1 date agent_name prompt_file status branch summary
2 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))
+58
View File
@@ -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)