Skip to content

Commit 614bba3

Browse files
author
TechStack Global
committed
fix: comprehensive QA content fixes and governance implementations
1 parent 8519ad6 commit 614bba3

7 files changed

Lines changed: 224 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# CODEOWNERS
2+
3+
# Require owner review and approval for any changes to product review posts.
4+
posts/*.html @TechStackGlobalOwner
5+
posts/template.html @TechStackGlobalOwner

.github/CONTRIBUTING.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Contributing Guidelines
2+
3+
## Foundation Files & Structural Changes
4+
5+
**Critical Safety Directive:** No automated bulk edits on product review files.
6+
Changes to individual posts (`posts/*.html`) and the review template (`posts/template.html`) must be performed cautiously and manually, or via carefully scoped scripts restricted to a feature branch.
7+
8+
### Change Process
9+
1. Do not deploy structural UI changes directly to `main`.
10+
2. Create a feature branch named `fix/<short-description>-<date>` or `feat/<short-description>-<date>`.
11+
3. Changes to `posts/*` must be submitted via a Pull Request (PR) with preview links.
12+
4. The PR must receive owner sign-off before merging into `main`.
13+
14+
Please refer to the emergency standardization pipeline for compliance requirements.

check_links.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import re
3+
4+
def check_internal_links():
5+
base_dir = 'c:/Users/PMLS/Desktop/Youtube Shorts/b2b_blog'
6+
html_files = []
7+
8+
for root, dirs, files in os.walk(base_dir):
9+
# Exclude node_modules, .git, etc if present
10+
if '.git' in root or '.venv' in root:
11+
continue
12+
for f in files:
13+
if f.endswith('.html'):
14+
html_files.append(os.path.join(root, f))
15+
16+
broken_links = []
17+
18+
for filepath in html_files:
19+
with open(filepath, 'r', encoding='utf-8') as f:
20+
content = f.read()
21+
22+
# Find all href=
23+
hrefs = re.findall(r'href=["\'](.*?)["\']', content)
24+
25+
for href in hrefs:
26+
if href.startswith('http') or href.startswith('mailto:') or href.startswith('#') or href == '':
27+
continue
28+
29+
# Resolve relative path
30+
# Strip query params or hash
31+
href_clean = href.split('?')[0].split('#')[0]
32+
if not href_clean:
33+
continue
34+
35+
target_path = os.path.normpath(os.path.join(os.path.dirname(filepath), href_clean))
36+
37+
if not os.path.exists(target_path):
38+
broken_links.append((filepath, href))
39+
40+
report_path = 'c:/Users/PMLS/.gemini/antigravity/brain/8acb5e45-297b-4c05-ae50-1d7cdeac50b0/broken_links_report.txt'
41+
with open(report_path, 'w', encoding='utf-8') as f:
42+
if broken_links:
43+
f.write("Broken Links Found:\n")
44+
for source, link in broken_links:
45+
f.write(f"In {os.path.basename(source)}: {link}\n")
46+
print(f"Found {len(broken_links)} broken links. Check report.")
47+
else:
48+
f.write("No broken internal links found.\n")
49+
print("No broken internal links found.")
50+
51+
if __name__ == '__main__':
52+
check_internal_links()

posts/apple-macbook-pro-m4-pro-review.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<meta charset="UTF-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77
<title>Apple MacBook Pro M4 Pro Review (2026) | TechStack Global</title>
8+
<meta name="description"
9+
content="Technical review of the Apple MacBook Pro M4 Pro (2026). In-depth analysis on performance, battery life, and ROI for modern professionals.">
10+
<!-- OpenGraph / Social Media -->
811
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
912
rel="stylesheet">
1013
<link rel="stylesheet" href="../style.css">

qa_content.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import csv
3+
import re
4+
5+
def check_content():
6+
posts_dir = 'c:/Users/PMLS/Desktop/Youtube Shorts/b2b_blog/posts'
7+
output_file = 'c:/Users/PMLS/.gemini/antigravity/brain/8acb5e45-297b-4c05-ae50-1d7cdeac50b0/product_checks.csv'
8+
9+
files = [f for f in os.listdir(posts_dir) if f.endswith('.html') and f != 'template.html']
10+
11+
results = []
12+
13+
for filename in files:
14+
path = os.path.join(posts_dir, filename)
15+
with open(path, 'r', encoding='utf-8') as f:
16+
content = f.read()
17+
18+
has_tldr = bool(re.search(r'TL;DR', content, re.IGNORECASE))
19+
has_specs = bool(re.search(r'Spec|Technical', content, re.IGNORECASE) or '<table' in content.lower())
20+
has_proscons = bool(re.search(r'Pros|Cons', content, re.IGNORECASE))
21+
has_whofor = bool(re.search(r'Who is this for|Who Should Buy|Who is', content, re.IGNORECASE))
22+
has_faqs = bool(re.search(r'FAQ', content, re.IGNORECASE))
23+
has_cta = bool(re.search(r'amazon\.com', content, re.IGNORECASE))
24+
25+
results.append([
26+
filename,
27+
'Yes',
28+
'Y' if has_tldr else 'N',
29+
'Y' if has_specs else 'N',
30+
'Y' if has_proscons else 'N',
31+
'Y' if has_whofor else 'N',
32+
'Y' if has_faqs else 'N',
33+
'Y' if has_cta else 'N'
34+
])
35+
36+
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
37+
writer = csv.writer(csvfile)
38+
writer.writerow(['Filename', 'Exists', 'TLDR', 'Specs', 'ProsCons', 'WhoFor', 'FAQs', 'CTA (Y/N)'])
39+
writer.writerows(results)
40+
41+
print(f"Content audit complete. Results saved to {output_file}")
42+
43+
if __name__ == '__main__':
44+
check_content()

qa_seo.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import re
3+
4+
def verify_seo():
5+
base_dir = 'c:/Users/PMLS/Desktop/Youtube Shorts/b2b_blog'
6+
report_path = 'c:/Users/PMLS/.gemini/antigravity/brain/8acb5e45-297b-4c05-ae50-1d7cdeac50b0/seo_affiliate_report.txt'
7+
8+
with open(report_path, 'w', encoding='utf-8') as report:
9+
# Check robots.txt
10+
robots_path = os.path.join(base_dir, 'robots.txt')
11+
if os.path.exists(robots_path):
12+
with open(robots_path, 'r', encoding='utf-8') as f:
13+
report.write("--- robots.txt ---\n")
14+
report.write(f.read().strip() + "\n\n")
15+
else:
16+
report.write("--- robots.txt NOT FOUND ---\n\n")
17+
18+
# Check sitemap.xml
19+
sitemap_path = os.path.join(base_dir, 'sitemap.xml')
20+
if os.path.exists(sitemap_path):
21+
with open(sitemap_path, 'r', encoding='utf-8') as f:
22+
lines = f.readlines()
23+
report.write("--- sitemap.xml (First 20 Lines) ---\n")
24+
report.write("".join(lines[:20]).strip() + "\n\n")
25+
else:
26+
report.write("--- sitemap.xml NOT FOUND ---\n\n")
27+
28+
# Check meta tags and JSON-LD
29+
samples = [
30+
'index.html',
31+
'posts/apple-macbook-pro-m4-pro-review.html',
32+
'posts/lg-27us500-w-ultrafine-monitor-review.html',
33+
'posts/surface-laptop-studio-2-review.html'
34+
]
35+
36+
report.write("--- Meta Title & Description Samples ---\n")
37+
for sample in samples:
38+
filepath = os.path.join(base_dir, sample)
39+
try:
40+
with open(filepath, 'r', encoding='utf-8') as f:
41+
content = f.read()
42+
43+
title = re.search(r'<title>(.*?)</title>', content, re.IGNORECASE | re.DOTALL)
44+
title_text = title.group(1).strip() if title else "NO TITLE"
45+
46+
desc = re.search(r'<meta\s+name=["\']description["\']\s+content=["\'](.*?)["\']', content, re.IGNORECASE | re.DOTALL)
47+
desc_text = desc.group(1).strip() if desc else "NO DESCRIPTION"
48+
49+
report.write(f"\n{sample}:\nTitle: {title_text}\nDesc: {desc_text}\n")
50+
51+
if sample.startswith('posts/'):
52+
schema = "YES" if '<script type="application/ld+json">' in content else "NO"
53+
report.write(f"Has JSON-LD Schema: {schema}\n")
54+
55+
# Check affiliate links
56+
amazon_links = re.findall(r'<a[^>]+href=["\'][^"\']*amazon\.com[^"\']*["\'][^>]*>', content, re.IGNORECASE)
57+
has_sponsored = all('rel="sponsored"' in link or "rel='sponsored'" in link for link in amazon_links) if amazon_links else False
58+
sample_link = amazon_links[0] if amazon_links else "No Amazon links found"
59+
60+
clean_sample = re.sub(r'\s+', ' ', sample_link).strip()
61+
62+
report.write(f"Has Amazon Links: {'YES' if amazon_links else 'NO'}\n")
63+
report.write(f"All Amazon links use rel='sponsored': {'YES' if has_sponsored else 'NO'}\n")
64+
report.write(f"Sample Affiliate Link Tag: {clean_sample[:100]}...\n")
65+
66+
except FileNotFoundError:
67+
report.write(f"\n{sample}: FILE NOT FOUND\n")
68+
69+
if __name__ == '__main__':
70+
verify_seo()

take_screenshots.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
from playwright.sync_api import sync_playwright
3+
4+
def capture_screenshots():
5+
with sync_playwright() as p:
6+
browser = p.chromium.launch(headless=True)
7+
# Desktop
8+
context_desktop = browser.new_context(viewport={'width': 1280, 'height': 800})
9+
page_desktop = context_desktop.new_page()
10+
11+
# Mobile
12+
iphone_13 = p.devices['iPhone 13']
13+
context_mobile = browser.new_context(**iphone_13)
14+
page_mobile = context_mobile.new_page()
15+
16+
urls = {
17+
'homepage': 'https://techstackglobal.github.io/',
18+
'macbook': 'https://techstackglobal.github.io/posts/apple-macbook-pro-m4-pro-review.html'
19+
}
20+
21+
output_dir = 'c:/Users/PMLS/.gemini/antigravity/brain/8acb5e45-297b-4c05-ae50-1d7cdeac50b0/'
22+
23+
for name, url in urls.items():
24+
print(f"Navigating to {url} (Desktop)...")
25+
page_desktop.goto(url)
26+
page_desktop.screenshot(path=os.path.join(output_dir, f"{name}_desktop.png"), full_page=True)
27+
28+
print(f"Navigating to {url} (Mobile)...")
29+
page_mobile.goto(url)
30+
page_mobile.screenshot(path=os.path.join(output_dir, f"{name}_mobile.png"), full_page=True)
31+
32+
print("Screenshots captured successfully.")
33+
browser.close()
34+
35+
if __name__ == '__main__':
36+
capture_screenshots()

0 commit comments

Comments
 (0)