Fix deploy: remove .worktrees and add to .gitignore (#44)

* Fix deploy: remove .worktrees and add to .gitignore Remove the .worktrees/bender-pr32 directory from git tracking that was accidentally committed. Add .worktrees/ to .gitignore to prevent similar issues in the future. Also remove 'submodules: recursive' from the deploy-site.yml workflow since the project does not use git submodules. This was causing the 'fatal: No url found for submodule path' error during checkout. Fixes the Deploy Hugo site workflow failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Write operator docs and rollout checklist - Update README.md with complete project overview, architecture diagram, and quick start - Add What SquadScope is/does section with clear value proposition - Document 5-stage pipeline: Crawl → Analyze → Generate → Deploy → Reskill - Create docs/operator-guide.md with: - Prerequisites and initial setup (Copilot token, GitHub Pages, Hugo) - Step-by-step configuration guide - Manual workflow triggers and stage-by-stage execution - Complete troubleshooting section for common failures: - Copilot auth/quota issues - GitHub API rate limits - Hugo build failures - Quality gate blocks - Pages deployment issues - Reskill cycle explanation - Monitoring and health check procedures - Create docs/rollout-checklist.md with: - Pre-launch verification items - First-time testing checklist - Pre-production setup - First automated run validation - Post-launch monitoring - Sign-off section for operators Closes #23 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Copilot review comments on PR #44 - Remove submodule initialization instructions from rollout-checklist.md (project does not use git submodules) - Remove submodule-related instructions from operator-guide.md: - Remove --recurse-submodules from clone command - Remove git submodule initialization instructions - Remove submodule references from troubleshooting section - Fix ISO week number consistency in operator-guide.md examples: - Update all W20 references to W21 (correct ISO week for 2026-05-18) - Update affected examples: crawl, analyze, gate, and generate stages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed May 18, 2026 at 16:31 UTC 7d402a3e79b43d0c24df15e6de41841c984a44d7
17 files changed +1143 -69
.github/workflows/deploy-site.yml
-1
@@ -25,7 +25,6 @@ jobs:
25 - name: Check out repository
26 uses: actions/checkout@v4
27 with:
28 - submodules: recursive
28 fetch-depth: 0
29
30 - name: Configure GitHub Pages
.gitignore
+3
@@ -12,3 +12,6 @@
12 public/
13 resources/_gen/
14 .hugo_build.lock
15 +
16 +# Git worktrees
17 +.worktrees/
.worktrees/bender-pr32 deleted
-1
@@ -1 +0,0 @@
1 -Subproject commit 34e5fa7efb3da4e431b3bb4760077665dad0f205
README.md
+111 -41
@@ -1,68 +1,138 @@
1 # SquadScope
2
3 -SquadScope is a Hugo-powered GitHub Pages site for weekly, monthly, and yearly tech trend summaries sourced from GitHub.
3 +SquadScope is an automated AI-powered GitHub Pages site that publishes weekly, monthly, and yearly summaries of tech trends sourced from GitHub. The system crawls trending and newly-created repositories, applies AI analysis to identify signal vs. noise, and publishes curated insights with zero manual intervention.
4 +
5 +## What is SquadScope?
6 +
7 +SquadScope solves the information overload problem in open-source development. Each week, millions of repositories are created or gain stars. SquadScope:
8 +
9 +1. **Crawls** GitHub API for new and trending repositories in a given week
10 +2. **Analyzes** the data with Copilot to identify what's genuinely important vs. hype
11 +3. **Generates** human-readable weekly summaries with signal, noise, and gaps analysis
12 +4. **Publishes** to GitHub Pages with RSS feeds for consumption
13 +5. **Reskills** every 5 runs to improve its own analysis quality
14 +
15 +Result: Curated tech trend insights delivered automatically every Monday.
16 +
17 +## Architecture
18 +
19 +SquadScope uses a **5-stage pipeline** executed entirely in GitHub Actions:
20 +
21 +```
22 +Crawl → Analyze → Generate → Deploy → Reskill
23 + ↓ ↓ ↓ ↓ ↓
24 +JSON Markdown Hugo Pages Improvements
25 +```
26 +
27 +**Stage 1: Crawl** (`scripts/crawl.py`)
28 +- Queries GitHub API for repos created/trending in the current week
29 +- Applies heuristic filtering (language, topic, description quality)
30 +- Outputs: `data/raw/YYYY-WNN.json`, `data/snapshots/YYYY-WNN-stars.json`
31 +
32 +**Stage 2: Analyze** (Copilot CLI or fallback)
33 +- Reads raw JSON; applies AI analysis to classify repos as signal/noise/gaps
34 +- Outputs: `data/analyzed/YYYY-WNN-summary.md` with quality score and summary sections
35 +- Quality gate: Blocks publish if quality_score < 60 or missing required sections
36 +
37 +**Stage 3: Generate** (`scripts/generate_content.py`)
38 +- Converts analyzed Markdown into Hugo content structure
39 +- Outputs: `content/weekly/YYYY/WNN.md` ready for Hugo build
40 +
41 +**Stage 4: Deploy** (Hugo + GitHub Pages)
42 +- Builds static site from Hugo and publishes to GitHub Pages
43 +- Outputs: Live website + RSS feeds at `https://your-site/feed/`
44 +
45 +**Stage 5: Reskill** (every 5th run)
46 +- Copilot reads squad history and decisions
47 +- Writes improvement recommendations to `.squad/reskill/YYYY-WNN.md`
48 +- Optional: Can trigger PR with proposed prompt refinements
49
50 ## Theme and stack
51
7 -- Static site generator: Hugo (extended)
8 -- Theme: [PaperMod](https://github.com/adityatelange/hugo-PaperMod)
9 -- Deployment: GitHub Pages via GitHub Actions
52 +- **Static site generator:** Hugo (extended, v0.146.0+)
53 +- **Theme:** [PaperMod](https://github.com/adityatelange/hugo-PaperMod)
54 +- **Search:** Pagefind (static, client-side)
55 +- **Notifications:** RSS feeds + GitHub Releases
56 +- **Automation:** GitHub Actions
57 +- **Deployment:** GitHub Pages
58 +- **Analysis engine:** Copilot CLI (with GitHub Models API fallback)
59
11 -## Local development
60 +## Quick start
61
13 -1. Install the Hugo extended binary (version 0.146.0 or newer).
14 -2. Clone the repository with submodules, or initialize them after cloning:
15 - - `git clone --recurse-submodules https://github.com/jmservera/SquadScope.git`
16 - - `git submodule update --init --recursive`
17 -3. Start the local development server:
18 - - `hugo server`
19 -4. Create a production build:
20 - - `hugo --minify`
62 +### For end users
63
22 -## Content structure
64 +1. **Visit the site:** [SquadScope](https://your-repo/): Browse weekly, monthly, yearly summaries
65 +2. **Subscribe to RSS:** Add `https://your-site/feed/` to your reader
66 +3. **Check GitHub Releases:** New summaries also posted as releases
67
24 -- `content/weekly/` — weekly summaries
25 -- `content/monthly/` — monthly rollups
26 -- `content/yearly/` — yearly summaries
27 -- `data/raw/` — crawler output
28 -- `data/analyzed/` — analysis output
29 -- `data/snapshots/` — star count snapshots
68 +### For operators (see `docs/operator-guide.md` for full setup)
69
31 -## Crawler notes
70 +1. Fork this repository
71 +2. Configure `COPILOT_GH_TOKEN` secret in your repo (fine-grained PAT with Copilot Requests permission)
72 +3. Enable GitHub Pages (Actions source)
73 +4. Test manual run: `gh workflow run crawl-and-publish.yml`
74 +5. Monitor the first automated run
75 +
76 +⚠️ **First-time operators:** Start with `docs/rollout-checklist.md` to ensure all prerequisites are in place.
77 +
78 +## Local development
79
33 -- `signals.top_topics` de-duplicates repositories by `full_name` across the new and trending buckets before counting topics, so a repo found by both searches only contributes once.
34 -- `data/snapshots/YYYY-WNN-stars.json` intentionally stores the broader pre-filter search candidate universe to preserve week-over-week `stars_gained` comparisons even when a repo is later filtered out of the published payload.
35 -- Live runs use open-ended `created:>` / `pushed:>` GitHub search filters; `--as-of` runs switch to bounded date ranges so historical backfills stay deterministic.
80 +1. **Install Hugo:** `brew install hugo` (macOS) or download from [hugo releases](https://github.com/gohugoio/hugo/releases) (v0.146.0 or newer)
81 +2. **Clone with submodules:**
82 + ```bash
83 + git clone --recurse-submodules https://github.com/jmservera/SquadScope.git
84 + git submodule update --init --recursive
85 + ```
86 +3. **Start dev server:** `hugo server` (default: http://localhost:1313)
87 +4. **Create production build:** `hugo --minify` (output: `public/`)
88 +
89 +## Content structure
90 +
91 +- `content/weekly/YYYY/WNN.md` — immutable weekly summaries (published once, never modified)
92 +- `content/monthly/YYYY/MM.md` — monthly rollups (append-only)
93 +- `content/yearly/YYYY.md` — yearly summaries (append-only)
94 +- `data/raw/YYYY-WNN.json` — crawler output (array of repo objects)
95 +- `data/analyzed/YYYY-WNN-summary.md` — AI analysis with quality score
96 +- `data/snapshots/YYYY-WNN-stars.json` — star count snapshots for trending analysis
97
98 ## Automated weekly pipeline
99
39 -`.github/workflows/crawl-and-publish.yml` runs the full weekly automation chain:
100 +`.github/workflows/crawl-and-publish.yml` runs the full weekly automation every Monday at 08:00 UTC:
101
41 -1. `crawl` writes `data/raw/YYYY-WNN.json` and `data/snapshots/YYYY-WNN-stars.json`.
42 -2. `analyze` turns the raw payload into `data/analyzed/YYYY-WNN-summary.md`.
43 -3. `generate` converts the analysis into `content/weekly/YYYY/WNN.md`.
44 -4. `deploy` builds `public/` with Hugo and publishes the site to GitHub Pages.
102 +1. **Crawl:** GitHub API → `data/raw/YYYY-WNN.json`
103 +2. **Analyze:** Copilot → `data/analyzed/YYYY-WNN-summary.md`
104 +3. **Quality gate:** Validates quality_score ≥ 60; blocks publish if failed
105 +4. **Generate:** Markdown → `content/weekly/YYYY/WNN.md`
106 +5. **Deploy:** Hugo build → GitHub Pages
107 +6. **Reskill:** Every 5th run, review and improve squad state
108
109 ### Schedule and manual runs
110
48 -- Scheduled cron: `0 8 * * 1` (`Monday 08:00 UTC`)
49 -- Manual workflow trigger: GitHub Actions UI or `gh workflow run crawl-and-publish.yml`
111 +- **Automated schedule:** Monday 08:00 UTC (`0 8 * * 1`)
112 +- **Manual trigger:** `gh workflow run crawl-and-publish.yml` or GitHub Actions UI
113
114 ### Required secrets
115
53 -- `COPILOT_GH_TOKEN` for the primary Copilot CLI analysis path
54 -- `GITHUB_TOKEN` for crawling, fallback analysis, commits, and Pages deployment
116 +- `COPILOT_GH_TOKEN` — Fine-grained PAT with **Account → Copilot Requests** permission (primary analysis)
117 +- `GITHUB_TOKEN` — Built-in; used for crawling, fallback analysis, commits, Pages deployment
118
56 -### Local/manual stage commands
119 +## Crawler notes
120
58 -- Crawl: `python3 scripts/crawl.py --as-of YYYY-MM-DD`
59 -- Analyze fallback: `python3 scripts/analyze_fallback.py --raw-json data/raw/YYYY-WNN.json --output data/analyzed/YYYY-WNN-summary.md --current-datetime YYYY-MM-DDTHH:MM:SSZ`
60 -- Gate: `python3 scripts/analysis_gate.py --analysis-file data/analyzed/YYYY-WNN-summary.md --raw-json data/raw/YYYY-WNN.json --current-datetime YYYY-MM-DDTHH:MM:SSZ`
61 -- Generate: `python3 scripts/generate_content.py data/analyzed/YYYY-WNN-summary.md`
62 -- Build: `hugo --minify`
121 +- `signals.top_topics` de-duplicates repositories by `full_name` across new and trending buckets
122 +- `data/snapshots/YYYY-WNN-stars.json` preserves the broader pre-filter candidate set for consistent week-over-week `stars_gained` comparisons
123 +- Live runs use open-ended `created:>` / `pushed:>` GitHub search filters
124 +- `--as-of` mode switches to bounded date ranges for deterministic historical backfills
125
126 ## Deployment
127
66 -Direct pushes to `main` still trigger `.github/workflows/deploy-site.yml` for standard site deploys. The weekly automation deploys Pages from `crawl-and-publish.yml` and `deploy-site.yml` skips bot-authored pushes from that workflow to avoid duplicate Pages runs.
128 +- **Standard deploys:** Direct pushes to `main` trigger `.github/workflows/deploy-site.yml`
129 +- **Weekly automation:** `crawl-and-publish.yml` handles full pipeline and deploys Pages
130 +- **Deduplication:** Deploy workflow skips bot-authored pushes to avoid duplicate Pages runs
131 +
132 +## Documentation
133
68 -See `docs/pipeline-validation.md` for the stage checklist, artifact handoffs, success criteria, and known limitations.
134 +- **`docs/operator-guide.md`** — Complete setup, configuration, and troubleshooting guide for new operators
135 +- **`docs/rollout-checklist.md`** — Step-by-step verification checklist for first-time deployments
136 +- **`docs/pipeline-validation.md`** — Stage checklist, artifact handoffs, success criteria, and known limitations
137 +- **`docs/analysis-spec.md`** — Detailed specification for analysis output format and quality gate criteria
138 +- **`.squad/decisions.md`** — Architectural decisions and decision history
assets/css/extended/squadscope.css
+182
@@ -182,8 +182,190 @@
182 --pagefind-ui-font: inherit;
183 }
184
185 +a:focus-visible,
186 +button:focus-visible,
187 +input:focus-visible,
188 +select:focus-visible,
189 +textarea:focus-visible,
190 +summary:focus-visible,
191 +[tabindex]:focus-visible {
192 + outline: 3px solid var(--primary);
193 + outline-offset: 3px;
194 + border-radius: calc(var(--radius) / 2);
195 +}
196 +
197 +.metric-value a,
198 +.archive-entry-title a,
199 +.home-report-card a,
200 +.taxonomy-card a {
201 + overflow-wrap: anywhere;
202 +}
203 +
204 +.post-content,
205 +.md-content {
206 + overflow-wrap: anywhere;
207 +}
208 +
209 +.search-shortcuts {
210 + display: flex;
211 + flex-wrap: wrap;
212 + gap: 0.5rem;
213 + margin-top: 1rem;
214 + color: var(--secondary);
215 + font-size: 0.95rem;
216 +}
217 +
218 +.search-shortcuts-title {
219 + align-self: center;
220 + color: var(--primary);
221 + font-weight: 700;
222 +}
223 +
224 +.search-shortcut {
225 + display: inline-flex;
226 + align-items: center;
227 + gap: 0.4rem;
228 + padding: 0.35rem 0.7rem;
229 + border: 1px solid var(--border);
230 + border-radius: 999px;
231 + background: var(--entry);
232 +}
233 +
234 +.search-shortcut kbd {
235 + display: inline-flex;
236 + align-items: center;
237 + justify-content: center;
238 + min-width: 1.8rem;
239 + min-height: 1.8rem;
240 + padding: 0 0.35rem;
241 + border: 1px solid var(--border);
242 + border-radius: 0.5rem;
243 + background: var(--theme);
244 + color: var(--primary);
245 + font: inherit;
246 + font-weight: 700;
247 +}
248 +
249 +.search-panel {
250 + display: grid;
251 + gap: 1rem;
252 +}
253 +
254 +.search-panel .pagefind-ui__search-input {
255 + font-size: 1rem;
256 +}
257 +
258 +.search-panel .pagefind-ui__results,
259 +.search-panel .pagefind-ui__result-link,
260 +.search-panel .pagefind-ui__result-excerpt {
261 + overflow-wrap: anywhere;
262 +}
263 +
264 +.table-scroll {
265 + margin: 1rem 0;
266 + overflow-x: auto;
267 + border: 1px solid var(--border);
268 + border-radius: var(--radius);
269 + background: var(--entry);
270 + -webkit-overflow-scrolling: touch;
271 +}
272 +
273 +.table-scroll:focus-visible {
274 + outline: 3px solid var(--primary);
275 + outline-offset: 2px;
276 +}
277 +
278 +.table-scroll table {
279 + width: 100%;
280 + min-width: 36rem;
281 + margin: 0;
282 +}
283 +
284 +.table-scroll th,
285 +.table-scroll td {
286 + white-space: nowrap;
287 +}
288 +
289 +.visually-hidden {
290 + position: absolute;
291 + width: 1px;
292 + height: 1px;
293 + padding: 0;
294 + margin: -1px;
295 + overflow: hidden;
296 + clip: rect(0, 0, 0, 0);
297 + white-space: nowrap;
298 + border: 0;
299 +}
300 +
301 +@media (max-width: 768px) {
302 + .header-nav {
303 + align-items: flex-start;
304 + }
305 +
306 + .logo,
307 + .menu {
308 + width: 100%;
309 + }
310 +
311 + .logo {
312 + justify-content: space-between;
313 + gap: 0.75rem;
314 + }
315 +
316 + .menu {
317 + flex-wrap: wrap;
318 + overflow-x: visible;
319 + white-space: normal;
320 + gap: 0.5rem;
321 + }
322 +
323 + .menu a {
324 + padding: 0.35rem 0.75rem;
325 + border: 1px solid var(--border);
326 + border-radius: 999px;
327 + background: var(--entry);
328 + line-height: 1.4;
329 + }
330 +
331 + .post-title {
332 + font-size: clamp(2rem, 8vw, 2.75rem);
333 + }
334 +
335 + .post-meta {
336 + display: flex;
337 + flex-wrap: wrap;
338 + gap: 0.35rem 0.75rem;
339 + }
340 +
341 + .home-actions,
342 + .report-shortcuts {
343 + flex-direction: column;
344 + align-items: stretch;
345 + }
346 +
347 + .home-action-primary,
348 + .home-action-secondary,
349 + .report-shortcuts a {
350 + width: 100%;
351 + }
352 +
353 + .table-scroll table {
354 + min-width: 30rem;
355 + }
356 +
357 + .search-panel .pagefind-ui__search-input {
358 + font-size: 16px;
359 + }
360 +}
361 +
362 @media (max-width: 640px) {
363 .metric-card-wide {
364 grid-column: span 1;
365 }
366 +
367 + .search-shortcuts {
368 + flex-direction: column;
369 + align-items: stretch;
370 + }
371 }
docs/analysis-spec.md
+9
@@ -229,6 +229,15 @@ The body must follow this exact top-level section order:
229
230 Every repository mentioned in the body must be rendered as a clickable markdown link in this exact format: `[owner/repo](https://github.com/owner/repo)`.
231
232 +### Image accessibility guidance
233 +
234 +If an analysis ever includes an image, chart, or screenshot:
235 +
236 +- provide concise, descriptive alt text that explains the information a reader would otherwise miss,
237 +- do not use placeholder alt text like `image`, `screenshot`, or the file name,
238 +- keep decorative images rare; only use empty alt text when the image adds no editorial meaning,
239 +- explain any important numbers or trends in the surrounding prose so the page still works without the image.
240 +
241 ### Section guidance
242
243 #### 1. Notable New Repositories
docs/operator-guide.md new
+443
@@ -0,0 +1,443 @@
1 +# SquadScope Operator Guide
2 +
3 +This guide covers everything needed to set up, operate, monitor, and troubleshoot SquadScope in production.
4 +
5 +## Prerequisites
6 +
7 +Before starting, ensure you have:
8 +
9 +1. **A GitHub repository** — Fork or create a copy of SquadScope
10 +2. **GitHub Pages enabled** — In repo settings, set source to "GitHub Actions"
11 +3. **GitHub Copilot subscription** — Required for AI-powered analysis (primary path)
12 + - Copilot Pro ($20/month) or Copilot Team subscription
13 + - Account with Copilot API access
14 +4. **Fine-grained Personal Access Token (PAT)**
15 + - Scope: `github_pat_...` (not `ghp_...`)
16 + - Permission: Account → Copilot Requests (at least)
17 + - No expiration recommended (or set far future)
18 +5. **Basic CLI tools** — `gh` CLI installed (`brew install gh` on macOS, `apt install gh` on Linux)
19 +
20 +## Initial Setup
21 +
22 +### Step 1: Clone the repository
23 +
24 +```bash
25 +git clone https://github.com/YOUR_USERNAME/SquadScope.git
26 +cd SquadScope
27 +```
28 +
29 +### Step 2: Verify Hugo version locally
30 +
31 +Ensure your Hugo binary is v0.146.0 or newer:
32 +
33 +```bash
34 +hugo version
35 +```
36 +
37 +If needed, download the extended version:
38 +- macOS: `brew install hugo`
39 +- Linux: Download from [hugo releases](https://github.com/gohugoio/hugo/releases)
40 +- Windows: Download from releases or use Chocolatey
41 +
42 +### Step 3: Configure GitHub Copilot secret
43 +
44 +Create a fine-grained PAT on GitHub:
45 +
46 +1. Navigate to **Settings → Developer settings → Personal access tokens → Fine-grained tokens**
47 +2. Click **Generate new token**
48 +3. Configure:
49 + - **Token name:** `SquadScope-Copilot-Token`
50 + - **Expiration:** No expiration (or set to 1 year)
51 + - **Repository access:** Select your SquadScope repository
52 + - **Account permissions:** Scroll to **Copilot Requests** and select **Read and write**
53 + - Click **Generate token**
54 +4. Copy the token (you won't see it again)
55 +
56 +Add the token as a repository secret:
57 +
58 +```bash
59 +# Using GitHub CLI
60 +gh secret set COPILOT_GH_TOKEN --body YOUR_PAT_HERE -R YOUR_USERNAME/SquadScope
61 +
62 +# Or manually in repo settings:
63 +# Settings → Secrets and variables → Actions → New repository secret
64 +# Name: COPILOT_GH_TOKEN
65 +# Value: github_pat_...
66 +```
67 +
68 +Verify the secret exists:
69 +
70 +```bash
71 +gh secret list -R YOUR_USERNAME/SquadScope
72 +```
73 +
74 +### Step 4: Enable GitHub Pages
75 +
76 +1. Navigate to repo **Settings → Pages**
77 +2. Set **Source** to "GitHub Actions"
78 +3. (Optional) Configure custom domain if desired
79 +4. Save
80 +
81 +### Step 5: Test local build
82 +
83 +Ensure the Hugo build works locally:
84 +
85 +```bash
86 +hugo server
87 +```
88 +
89 +Visit `http://localhost:1313` and verify the site loads. Press `Ctrl+C` to stop.
90 +
91 +For production build:
92 +
93 +```bash
94 +hugo --minify
95 +```
96 +
97 +This generates the `public/` directory with optimized static assets.
98 +
99 +## Running the Pipeline
100 +
101 +### Option A: Automatic scheduling (default)
102 +
103 +The pipeline runs automatically every **Monday at 08:00 UTC** via `.github/workflows/crawl-and-publish.yml`.
104 +
105 +You don't need to do anything. Go to your repo's **Actions** tab to monitor runs.
106 +
107 +### Option B: Manual trigger
108 +
109 +Run the workflow manually:
110 +
111 +```bash
112 +gh workflow run crawl-and-publish.yml -R YOUR_USERNAME/SquadScope
113 +```
114 +
115 +Or through the GitHub UI:
116 +1. Go to **Actions → Crawl and Publish**
117 +2. Click **Run workflow**
118 +3. Confirm
119 +
120 +The workflow takes ~2-3 minutes depending on GitHub API response times.
121 +
122 +### Option C: Run individual stages locally
123 +
124 +For debugging or testing, run stages separately:
125 +
126 +#### Crawl
127 +
128 +```bash
129 +python3 scripts/crawl.py --as-of 2026-05-18
130 +```
131 +
132 +Output: `data/raw/2026-W21.json`, `data/snapshots/2026-W21-stars.json`
133 +
134 +#### Analyze (Fallback — if Copilot unavailable)
135 +
136 +```bash
137 +python3 scripts/analyze_fallback.py \
138 + --raw-json data/raw/2026-W21.json \
139 + --output data/analyzed/2026-W21-summary.md \
140 + --current-datetime 2026-05-18T16:00:00Z
141 +```
142 +
143 +Output: `data/analyzed/2026-W21-summary.md`
144 +
145 +#### Quality gate
146 +
147 +```bash
148 +python3 scripts/analysis_gate.py \
149 + --analysis-file data/analyzed/2026-W21-summary.md \
150 + --raw-json data/raw/2026-W21.json \
151 + --current-datetime 2026-05-18T16:00:00Z
152 +```
153 +
154 +If the gate fails, it will exit with a non-zero code and log the reason (e.g., quality_score < 60).
155 +
156 +#### Generate
157 +
158 +```bash
159 +python3 scripts/generate_content.py data/analyzed/2026-W21-summary.md
160 +```
161 +
162 +Output: `content/weekly/2026/W21.md`
163 +
164 +#### Build and deploy
165 +
166 +```bash
167 +hugo --minify
168 +```
169 +
170 +Output: `public/` directory ready for GitHub Pages.
171 +
172 +## Monitoring the Cron Schedule
173 +
174 +### View recent runs
175 +
176 +```bash
177 +gh run list -R YOUR_USERNAME/SquadScope --workflow crawl-and-publish.yml --limit 10
178 +```
179 +
180 +### Check a specific run
181 +
182 +```bash
183 +gh run view RUN_ID -R YOUR_USERNAME/SquadScope
184 +```
185 +
186 +### Stream live logs
187 +
188 +```bash
189 +gh run view RUN_ID -R YOUR_USERNAME/SquadScope --log
190 +```
191 +
192 +### Monitor in the UI
193 +
194 +Navigate to **Actions → Crawl and Publish** in your repo. Green checkmarks = success, red X = failure.
195 +
196 +## Troubleshooting Common Failures
197 +
198 +### ❌ Copilot auth failure
199 +
200 +**Error message:** `COPILOT_GITHUB_TOKEN not configured` or `401 Unauthorized`
201 +
202 +**Cause:** The `COPILOT_GH_TOKEN` secret is missing, expired, or has insufficient permissions.
203 +
204 +**Fix:**
205 +1. Verify the secret exists: `gh secret list -R YOUR_USERNAME/SquadScope`
206 +2. Regenerate the PAT if expired (Settings → Developer settings → Personal access tokens)
207 +3. Confirm it has **Account → Copilot Requests** permission
208 +4. Update the secret: `gh secret set COPILOT_GH_TOKEN --body YOUR_NEW_PAT -R YOUR_USERNAME/SquadScope`
209 +5. Re-run the workflow
210 +
211 +**Fallback:** The workflow automatically falls back to GitHub Models API if Copilot fails. Check the workflow logs to see which path was used.
212 +
213 +### ❌ GitHub API rate limits exceeded
214 +
215 +**Error message:** `API rate limit exceeded (60/60)` or `secondary rate limit`
216 +
217 +**Cause:** Too many API calls in a short time window. GitHub's search endpoint is particularly strict.
218 +
219 +**Fix:**
220 +1. The crawler has built-in backoff logic. Wait 15 minutes and retry.
221 +2. For immediate relief, configure a `GITHUB_TOKEN` (built-in) in the workflow to increase rate limits from 60/hour to 5,000/hour.
222 +3. If using a fine-grained PAT, ensure it has minimal required permissions (reduces quota consumption).
223 +
224 +**Avoid:** Running crawl jobs back-to-back in rapid succession.
225 +
226 +### ❌ Copilot quota exhausted
227 +
228 +**Error message:** `Quota exhausted` or `429 Too Many Requests`
229 +
230 +**Cause:** You've used up your Copilot API requests for the billing cycle (typically thousands per month for Copilot Pro).
231 +
232 +**Fix:**
233 +1. Check your Copilot usage: https://github.com/settings/copilot
234 +2. If you hit the limit, wait for the next billing cycle or upgrade your plan
235 +3. The workflow will automatically fall back to GitHub Models API (lower quality, but functional)
236 +
237 +**Mitigation:** Copilot Pro includes generous quota. For automated pipelines, consider Copilot Team (more quota, better for organizations).
238 +
239 +### ❌ Hugo build fails
240 +
241 +**Error message:** `error: /content/weekly/... failed to parse date` or `theme not found`
242 +
243 +**Cause:**
244 +- Hugo version mismatch (too old)
245 +- Corrupted frontmatter in generated content
246 +
247 +**Fix:**
248 +1. Check Hugo version: `hugo version` (must be v0.146.0+)
249 +2. Check generated markdown in `content/weekly/` for valid YAML frontmatter
250 +3. Run locally: `hugo server` to see detailed error messages
251 +
252 +### ❌ GitHub Pages doesn't update
253 +
254 +**Error message:** Site shows old content or doesn't deploy
255 +
256 +**Cause:**
257 +- Pages source not set to "GitHub Actions"
258 +- Deployment workflow didn't complete successfully
259 +- Cache not cleared
260 +
261 +**Fix:**
262 +1. Verify Pages source: Repo **Settings → Pages → Source = "GitHub Actions"**
263 +2. Check the **Actions** tab for failed deploy jobs
264 +3. Clear browser cache (`Ctrl+Shift+Delete`) and refresh
265 +4. Force redeploy: Push a dummy commit to `main` or manually trigger `deploy-site.yml` workflow
266 +
267 +### ❌ Quality gate blocks publish
268 +
269 +**Error message:** `quality_score < 60` or `Missing required sections (Signal, Noise, Gaps)`
270 +
271 +**Cause:** The AI analysis didn't meet quality thresholds. This is a **feature, not a bug** — the gate prevents low-quality content from publishing.
272 +
273 +**Fix:**
274 +1. Check the analysis file: `cat data/analyzed/YYYY-WNN-summary.md`
275 +2. Review the quality_score in the YAML frontmatter
276 +3. If analysis is genuinely poor, this may indicate a problem with the raw data (crawler issue) or Copilot API issues
277 +4. **Do NOT bypass the gate.** Instead:
278 + - Investigate why analysis quality was low
279 + - Retry the workflow (Copilot may have had transient issues)
280 + - If consistent, open an issue with the analyzed output for review
281 +
282 +### ❌ Reskill workflow doesn't trigger
283 +
284 +**Error message:** Reskill job skipped in the logs
285 +
286 +**Cause:** Reskill only runs every 5th pipeline execution. If you've only run 1-4 times, it won't trigger yet.
287 +
288 +**Fix:**
289 +1. Run the workflow 4 more times to trigger a reskill (or wait for 4 more weeks)
290 +2. Check `.squad/run-counter.txt` to see how many runs have executed
291 +3. To manually test reskill logic, run: `gh workflow run squad-heartbeat.yml -R YOUR_USERNAME/SquadScope` (separate workflow)
292 +
293 +## The Reskill Cycle
294 +
295 +Every 5th pipeline run, SquadScope enters a "reskill" phase where it reviews its own behavior and proposes improvements.
296 +
297 +### What happens
298 +
299 +1. **Copilot reads:**
300 + - `.squad/agents/*/history.md` — Learnings from all agents
301 + - `.squad/decisions.md` — Architectural decisions made
302 + - `data/analyzed/` — Recent analysis outputs (quality trend)
303 + - `.squad/run-counter.txt` — Run history
304 +
305 +2. **Analysis:**
306 + - What patterns are working well?
307 + - What should change in prompts or logic?
308 + - Are there quality drift signals?
309 +
310 +3. **Output:**
311 + - Recommendations written to `.squad/reskill/YYYY-WNN.md`
312 + - Optional: PR proposed with prompt refinements (not auto-merged)
313 +
314 +### What to expect
315 +
316 +- **First reskill (run 5):** Observations and baseline recommendations
317 +- **Subsequent reskills:** Trend analysis ("quality is improving" vs. "signal/noise classification drifting")
318 +- **Prompts may be refined:** If reskill suggests prompt changes, review the PR and decide whether to merge
319 +
320 +### No manual action required
321 +
322 +Reskill runs automatically. You can review the outputs in `.squad/reskill/` directory, but the system works fine without human intervention.
323 +
324 +## Updating Configuration
325 +
326 +### Change the crawl schedule
327 +
328 +Edit `.github/workflows/crawl-and-publish.yml`:
329 +
330 +```yaml
331 +on:
332 + schedule:
333 + - cron: '0 8 * * 1' # Change to your desired time (UTC)
334 +```
335 +
336 +Times are in UTC. Use crontab.guru to generate your schedule.
337 +
338 +### Change the reskill interval
339 +
340 +Edit `.github/workflows/crawl-and-publish.yml` or `.squad/run-counter.txt`:
341 +
342 +```yaml
343 +# In workflow, modify the modulo check:
344 +if [ $((COUNTER % 5)) -eq 0 ]; then # Change 5 to desired interval
345 +```
346 +
347 +### Update the analyzer prompts
348 +
349 +Prompts live in `prompts/` directory. Edit and commit changes. They take effect on the next workflow run.
350 +
351 +### Customize quality gate thresholds
352 +
353 +Edit `scripts/analysis_gate.py`:
354 +
355 +```python
356 +MIN_QUALITY_SCORE = 60 # Change threshold here
357 +MIN_WORD_COUNT = 200
358 +```
359 +
360 +## Monitoring Site Health
361 +
362 +### Check RSS feed generation
363 +
364 +```bash
365 +curl https://YOUR_SITE/feed/
366 +```
367 +
368 +Should return valid XML with recent article entries.
369 +
370 +### Verify Hugo build output
371 +
372 +```bash
373 +# After running locally:
374 +ls public/
375 +hugo --minify && wc -l public/*.html
376 +```
377 +
378 +Should show HTML files for each content page.
379 +
380 +### Monitor GitHub API usage
381 +
382 +Check your GitHub API rate limits:
383 +
384 +```bash
385 +gh api rate_limit
386 +```
387 +
388 +Output shows `limit`, `remaining`, and `reset` time. If you're consistently hitting limits, consider:
389 +- Running less frequently
390 +- Optimizing the crawler queries
391 +- Using a GitHub App instead of PAT (higher limits)
392 +
393 +## Disaster Recovery
394 +
395 +### Revert a bad analysis
396 +
397 +If analysis was published but is clearly incorrect:
398 +
399 +1. Manually edit `data/analyzed/YYYY-WNN-summary.md` to correct it
400 +2. Commit and push
401 +3. Re-run `generate` and `deploy` stages (or full pipeline)
402 +4. The week's content will be regenerated with corrected data
403 +
404 +### Reset the run counter
405 +
406 +If you want reskill to trigger immediately:
407 +
408 +```bash
409 +echo "5" > .squad/run-counter.txt
410 +git add .squad/run-counter.txt
411 +git commit -m "Reset run counter to trigger reskill"
412 +git push
413 +```
414 +
415 +Next run will trigger reskill logic.
416 +
417 +### Restore from a previous week
418 +
419 +Content is immutable by design. If you need to restore:
420 +
421 +1. Check git history: `git log --oneline -- data/analyzed/YYYY-WNN-summary.md`
422 +2. Revert if necessary: `git revert COMMIT_HASH`
423 +3. Regenerate and deploy
424 +
425 +## Getting Help
426 +
427 +- **Logs:** Check **Actions** tab in GitHub UI for detailed workflow logs
428 +- **Common issues:** See troubleshooting section above
429 +- **Architecture questions:** See `.squad/decisions.md` for design rationale
430 +- **PRD:** See `docs/PRD.md` for feature requirements and future phases
431 +- **Issues:** Open an issue in the repository with logs and error messages
432 +
433 +## What's Next?
434 +
435 +Once SquadScope is running smoothly:
436 +
437 +1. **Monitor quality:** Review weekly analyses for patterns and trends
438 +2. **Iterate prompts:** Based on reskill recommendations, refine analysis quality
439 +3. **Extend sources:** Add additional data sources (HackerNews, Reddit, etc.) via MCP tools
440 +4. **Add notifications:** Configure GitHub Releases or webhook integrations
441 +5. **Topic channels:** Explore multi-topic feature (see `docs/PRD-topic-channels.md`)
442 +
443 +SquadScope is designed to improve itself. Trust the system, monitor the trends, and enjoy curated tech news delivered every week.
docs/rollout-checklist.md new
+159
@@ -0,0 +1,159 @@
1 +# SquadScope Rollout Checklist
2 +
3 +Use this checklist to verify your SquadScope instance is ready for production. Complete each item before considering the system operational.
4 +
5 +## Pre-Launch
6 +
7 +- [ ] **Repository created**
8 + - [ ] GitHub repository exists and is accessible
9 + - [ ] Hugo version verified: `hugo version` (must be v0.146.0+)
10 +
11 +- [ ] **Copilot auth configured**
12 + - [ ] Fine-grained PAT created with **Account → Copilot Requests** permission
13 + - [ ] Token is `github_pat_...` (not `ghp_...`)
14 + - [ ] Secret `COPILOT_GH_TOKEN` set in repo: `gh secret set COPILOT_GH_TOKEN --body TOKEN -R OWNER/REPO`
15 + - [ ] Secret verified: `gh secret list -R OWNER/REPO` shows `COPILOT_GH_TOKEN`
16 + - [ ] (Optional) Copilot subscription verified at https://github.com/settings/copilot
17 +
18 +- [ ] **GitHub Pages enabled**
19 + - [ ] Repo **Settings → Pages → Source** set to "GitHub Actions"
20 + - [ ] No errors in Pages deployment section
21 +
22 +## First-Time Testing
23 +
24 +- [ ] **Local build succeeds**
25 + - [ ] Run `hugo server` locally and verify site loads at http://localhost:1313
26 + - [ ] No Hugo build errors in console
27 + - [ ] Run `hugo --minify` and verify `public/` directory created with HTML files
28 +
29 +- [ ] **First manual crawl successful**
30 + - [ ] Trigger: `gh workflow run crawl-and-publish.yml -R OWNER/REPO`
31 + - [ ] Monitor: `gh run list -R OWNER/REPO --workflow crawl-and-publish.yml`
32 + - [ ] Verify: Green checkmark in Actions tab
33 + - [ ] Artifacts created: `data/raw/YYYY-WNN.json` and `data/snapshots/YYYY-WNN-stars.json`
34 +
35 +- [ ] **First manual analysis successful**
36 + - [ ] Previous crawl completed
37 + - [ ] Verify: `data/analyzed/YYYY-WNN-summary.md` exists and contains valid Markdown + YAML
38 + - [ ] Check: YAML frontmatter includes `quality_score`, `title`, `date`, `categories`
39 + - [ ] Check: File contains required sections (`## Signal`, `## Noise`, `## Gaps`)
40 +
41 +- [ ] **Quality gate passed**
42 + - [ ] Analysis quality_score ≥ 60 (check frontmatter)
43 + - [ ] Gate did NOT block publish (no quality-gate-failure issue opened)
44 + - [ ] Content sections present and not empty
45 +
46 +- [ ] **Site deploys correctly**
47 + - [ ] Wait for full workflow to complete (crawl + analyze + generate + deploy)
48 + - [ ] Monitor: **Actions → Crawl and Publish → Latest run → Deploy Pages** shows success
49 + - [ ] Verify: GitHub Pages shows "Deployment successful"
50 +
51 +- [ ] **RSS feed accessible**
52 + - [ ] Visit: `https://PAGES_URL/feed/` (replace with your Pages domain)
53 + - [ ] Verify: Valid XML returned (not 404 or error)
54 + - [ ] Verify: Latest week's summary appears in feed items
55 +
56 +- [ ] **Content visible on site**
57 + - [ ] Visit: `https://PAGES_URL/` (your GitHub Pages site)
58 + - [ ] Verify: Site loads without errors
59 + - [ ] Verify: Latest week appears in post list
60 + - [ ] Click into latest week's article and verify content renders correctly
61 +
62 +## Pre-Production
63 +
64 +- [ ] **Cron schedule enabled**
65 + - [ ] Verify workflow file: `.github/workflows/crawl-and-publish.yml` has schedule trigger:
66 + ```yaml
67 + schedule:
68 + - cron: '0 8 * * 1' # Monday 08:00 UTC
69 + ```
70 + - [ ] Confirm schedule is correct for your timezone
71 +
72 +- [ ] **Run counter initialized**
73 + - [ ] File exists: `.squad/run-counter.txt`
74 + - [ ] Contains integer counter (e.g., `1`)
75 + - [ ] Will trigger reskill automatically every 5 runs
76 +
77 +## First Automated Run
78 +
79 +- [ ] **Automated run completed (wait until next Monday 08:00 UTC or manual trigger)**
80 + - [ ] Check Actions tab at scheduled time
81 + - [ ] Verify workflow completed with green checkmark
82 + - [ ] Monitor logs for any warnings or errors
83 +
84 +- [ ] **Data artifacts generated**
85 + - [ ] `data/raw/YYYY-WNN.json` created (current week)
86 + - [ ] `data/analyzed/YYYY-WNN-summary.md` created
87 + - [ ] `content/weekly/YYYY/WNN.md` created
88 +
89 +- [ ] **Pages deployment succeeded**
90 + - [ ] Pages Shows "Deployment successful" in deployment history
91 + - [ ] Site updated with new week's content
92 +
93 +- [ ] **RSS feed updated**
94 + - [ ] `https://PAGES_URL/feed/` now shows latest week's entry
95 + - [ ] Feed readers can subscribe and receive new entries
96 +
97 +## Post-Launch Monitoring
98 +
99 +- [ ] **Weekly runs continue automatically**
100 + - [ ] Check Actions every Monday morning to confirm workflow ran
101 + - [ ] (Optional) Set a calendar reminder to check on Mondays
102 +
103 +- [ ] **Quality remains consistent**
104 + - [ ] Review a few published analyses for quality
105 + - [ ] If quality degrades, check workflow logs and Copilot API status
106 +
107 +- [ ] **Site remains accessible**
108 + - [ ] Periodically visit your SquadScope site
109 + - [ ] Verify RSS feed is updated each week
110 +
111 +## Optional Verification
112 +
113 +- [ ] **Manual stage execution (debug only)**
114 + - [ ] Crawl: `python3 scripts/crawl.py --as-of 2026-05-18`
115 + - [ ] Analyze (fallback): `python3 scripts/analyze_fallback.py --raw-json data/raw/YYYY-WNN.json --output /tmp/test-analysis.md --current-datetime 2026-05-18T16:00:00Z`
116 + - [ ] Gate: `python3 scripts/analysis_gate.py --analysis-file data/analyzed/YYYY-WNN-summary.md --raw-json data/raw/YYYY-WNN.json --current-datetime 2026-05-18T16:00:00Z`
117 + - [ ] Generate: `python3 scripts/generate_content.py data/analyzed/YYYY-WNN-summary.md`
118 +
119 +- [ ] **Fallback analysis path works (Copilot unavailable scenario)**
120 + - [ ] Comment out or rename `COPILOT_GH_TOKEN` secret temporarily
121 + - [ ] Manually trigger workflow
122 + - [ ] Verify workflow still completes but uses GitHub Models API
123 + - [ ] Restore secret after testing
124 +
125 +- [ ] **Reskill cycle verified (after 5 runs)**
126 + - [ ] Count automated runs (every Monday = 1 run)
127 + - [ ] After 5 weeks, check for `.squad/reskill/YYYY-WNN.md` file
128 + - [ ] Verify reskill outputs analysis and recommendations
129 +
130 +## Sign-Off
131 +
132 +- **Operator name:** _______________
133 +- **Date:** _______________
134 +- **SquadScope instance:** https://_______________
135 +- **All items checked:** Yes ☐ / No ☐
136 +
137 +**Notes:**
138 +```
139 +[Space for operator notes or issues encountered]
140 +
141 +
142 +
143 +
144 +```
145 +
146 +---
147 +
148 +## Troubleshooting During Rollout
149 +
150 +If any checklist item fails, see `docs/operator-guide.md` for troubleshooting guidance:
151 +
152 +- **Auth failures:** Section "Copilot auth failure"
153 +- **Rate limits:** Section "GitHub API rate limits exceeded"
154 +- **Build failures:** Section "Hugo build fails"
155 +- **Quality gate blocks:** Section "Quality gate blocks publish"
156 +- **Pages doesn't update:** Section "GitHub Pages doesn't update"
157 +- **Reskill doesn't trigger:** Section "Reskill workflow doesn't trigger"
158 +
159 +Good luck with your SquadScope rollout! 🚀
layouts/_default/single.html
+5 -5
@@ -20,11 +20,11 @@
20 </header>
21
22 <nav class="report-shortcuts" aria-label="Report navigation">
23 - <a href="{{ `/search/` | relURL }}">Search</a>
24 - <a href="{{ `/tags/` | relURL }}">Tags</a>
25 - <a href="{{ `/categories/` | relURL }}">Categories</a>
26 - <a href="{{ `/archive/` | relURL }}">Archive</a>
27 - <a href="{{ `/index.xml` | relURL }}">RSS</a>
23 + <a href="{{ `search/` | absLangURL }}">Search</a>
24 + <a href="{{ `tags/` | absLangURL }}">Tags</a>
25 + <a href="{{ `categories/` | absLangURL }}">Categories</a>
26 + <a href="{{ `archive/` | absLangURL }}">Archive</a>
27 + <a href="{{ `index.xml` | absLangURL }}">RSS</a>
28 </nav>
29
30 {{- if .Content }}
layouts/_markup/render-table.html new
+43
@@ -0,0 +1,43 @@
1 +{{- $regionLabel := "Scrollable table" -}}
2 +{{- with index .Attributes "aria-label" -}}
3 + {{- $regionLabel = . -}}
4 +{{- end -}}
5 +<div class="table-scroll" role="region" aria-label="{{ $regionLabel }}" tabindex="0">
6 + <table
7 + {{- range $k, $v := .Attributes }}
8 + {{- if $v }}
9 + {{- printf " %s=%q" $k $v | safeHTMLAttr }}
10 + {{- end }}
11 + {{- end }}>
12 + <thead>
13 + {{- range .THead }}
14 + <tr>
15 + {{- range . }}
16 + <th
17 + {{- with .Alignment }}
18 + {{- printf " style=%q" (printf "text-align: %s" .) | safeHTMLAttr }}
19 + {{- end -}}
20 + >
21 + {{- .Text -}}
22 + </th>
23 + {{- end }}
24 + </tr>
25 + {{- end }}
26 + </thead>
27 + <tbody>
28 + {{- range .TBody }}
29 + <tr>
30 + {{- range . }}
31 + <td
32 + {{- with .Alignment }}
33 + {{- printf " style=%q" (printf "text-align: %s" .) | safeHTMLAttr }}
34 + {{- end -}}
35 + >
36 + {{- .Text -}}
37 + </td>
38 + {{- end }}
39 + </tr>
40 + {{- end }}
41 + </tbody>
42 + </table>
43 +</div>
layouts/index.html
+8 -8
@@ -14,31 +14,31 @@
14 </section>
15
16 <section class="home-nav-grid" aria-label="Primary views">
17 - <a class="home-nav-card" href="{{ "/archive/" | relURL }}">
17 + <a class="home-nav-card" href="{{ `archive/` | absLangURL }}">
18 <span class="home-nav-title">Archive</span>
19 <span class="home-nav-copy">Browse every weekly summary in reverse chronological order.</span>
20 </a>
21 - <a class="home-nav-card" href="{{ "/monthly/" | relURL }}">
21 + <a class="home-nav-card" href="{{ `monthly/` | absLangURL }}">
22 <span class="home-nav-title">Monthly</span>
23 <span class="home-nav-copy">Step back and review the strongest themes across each month.</span>
24 </a>
25 - <a class="home-nav-card" href="{{ "/yearly/" | relURL }}">
25 + <a class="home-nav-card" href="{{ `yearly/` | absLangURL }}">
26 <span class="home-nav-title">Yearly</span>
27 <span class="home-nav-copy">Follow broader platform shifts and long-running trend lines.</span>
28 </a>
29 - <a class="home-nav-card" href="{{ "/tags/" | relURL }}">
29 + <a class="home-nav-card" href="{{ `tags/` | absLangURL }}">
30 <span class="home-nav-title">Tags</span>
31 <span class="home-nav-copy">Jump into recurring topics like AI, security, and developer tooling.</span>
32 </a>
33 - <a class="home-nav-card" href="{{ "/categories/" | relURL }}">
33 + <a class="home-nav-card" href="{{ `categories/` | absLangURL }}">
34 <span class="home-nav-title">Categories</span>
35 <span class="home-nav-copy">Move between weekly, monthly, and yearly reporting cadences.</span>
36 </a>
37 - <a class="home-nav-card" href="{{ "/search/" | relURL }}">
37 + <a class="home-nav-card" href="{{ `search/` | absLangURL }}">
38 <span class="home-nav-title">Search</span>
39 <span class="home-nav-copy">Use Pagefind to search by keyword, filter, date, or repository name.</span>
40 </a>
41 - <a class="home-nav-card" href="{{ "/index.xml" | relURL }}">
41 + <a class="home-nav-card" href="{{ `index.xml` | absLangURL }}">
42 <span class="home-nav-title">RSS</span>
43 <span class="home-nav-copy">Subscribe to the newest reports in your feed reader.</span>
44 </a>
@@ -53,7 +53,7 @@
53 {{ partial "report-metrics.html" $latest }}
54 <div class="home-actions">
55 <a class="home-action-primary" href="{{ $latest.RelPermalink }}">Read summary</a>
56 - <a class="home-action-secondary" href="{{ "/archive/" | relURL }}">Open archive</a>
56 + <a class="home-action-secondary" href="{{ `archive/` | absLangURL }}">Open archive</a>
57 </div>
58 </section>
59 {{- else -}}
layouts/partials/extend_head.html
+2 -2
@@ -1,4 +1,4 @@
1 {{- if .Params.pagefind -}}
2 -<link rel="stylesheet" href="{{ `/pagefind/pagefind-ui.css` | relURL }}">
3 -<script src="{{ `/pagefind/pagefind-ui.js` | relURL }}" defer></script>
2 +<link rel="stylesheet" href="{{ `pagefind/pagefind-ui.css` | absURL }}">
3 +<script src="{{ `pagefind/pagefind-ui.js` | absURL }}" defer></script>
4 {{- end -}}
layouts/partials/header.html new
+116
@@ -0,0 +1,116 @@
1 +<header class="header">
2 + <nav class="header-nav" aria-label="Primary">
3 + <div class="logo">
4 + {{- $label_text := (site.Params.label.text | default site.Title) }}
5 + {{- if site.Title }}
6 + <a href="{{ "" | absLangURL }}" accesskey="h" title="{{ $label_text }} (Alt + H)" aria-label="{{ $label_text }}">
7 + {{- if site.Params.label.icon }}
8 + {{- $img := resources.Get site.Params.label.icon }}
9 + {{- if $img }}
10 + {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
11 + {{- if hugo.IsExtended -}}
12 + {{- $processableFormats = $processableFormats | append "webp" -}}
13 + {{- end -}}
14 + {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
15 + {{- if and (in $processableFormats $img.MediaType.SubType) (eq $prod true)}}
16 + {{- if site.Params.label.iconHeight }}
17 + {{- $img = $img.Resize (printf "x%d" site.Params.label.iconHeight) }}
18 + {{ else }}
19 + {{- $img = $img.Resize "x30" }}
20 + {{- end }}
21 + {{- end }}
22 + <img src="{{ $img.Permalink }}" alt="" aria-label="logo"
23 + height="{{- site.Params.label.iconHeight | default "30" -}}">
24 + {{- else }}
25 + <img src="{{- site.Params.label.icon | absURL -}}" alt="" aria-label="logo"
26 + height="{{- site.Params.label.iconHeight | default "30" -}}">
27 + {{- end -}}
28 + {{- else if hasPrefix site.Params.label.iconSVG "<svg" }}
29 + {{ site.Params.label.iconSVG | safeHTML }}
30 + {{- end -}}
31 + {{- $label_text -}}
32 + </a>
33 + {{- end }}
34 +
35 + {{- $lsenabled := (or (not site.Params.disableThemeToggle) (and (not site.Params.disableLangToggle) (hugo.IsMultilingual))) }}
36 + {{- if $lsenabled }}
37 + <div class="logo-switches">
38 + {{- if (not site.Params.disableThemeToggle) }}
39 + <button id="theme-toggle" class="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
40 + <svg class="moon" xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24"
41 + fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
42 + stroke-linejoin="round">
43 + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
44 + </svg>
45 + <svg class="sun" xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24"
46 + fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
47 + stroke-linejoin="round">
48 + <circle cx="12" cy="12" r="5"></circle>
49 + <line x1="12" y1="1" x2="12" y2="3"></line>
50 + <line x1="12" y1="21" x2="12" y2="23"></line>
51 + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
52 + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
53 + <line x1="1" y1="12" x2="3" y2="12"></line>
54 + <line x1="21" y1="12" x2="23" y2="12"></line>
55 + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
56 + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
57 + </svg>
58 + </button>
59 + {{- end }}
60 +
61 + {{- if (not site.Params.disableLangToggle) }}
62 + {{- $lang := .Lang}}
63 + {{- with site.Home.Translations }}
64 + {{- $separator := or $label_text (not site.Params.disableThemeToggle)}}
65 + {{- if $separator }}<span class="nav-sep">|</span>{{ end }}
66 + <ul class="lang-menu">
67 + {{- range . -}}
68 + {{- if ne $lang .Lang }}
69 + <li>
70 + <a href="{{- .Permalink -}}" title="{{ .Language.Params.languageAltTitle | default (.Language.LanguageName | emojify) | default (.Lang | title) }}"
71 + aria-label="{{ .Language.LanguageName | default (.Lang | title) }}">
72 + {{- if (and site.Params.displayFullLangName (.Language.LanguageName)) }}
73 + {{- .Language.LanguageName | emojify -}}
74 + {{- else }}
75 + {{- .Lang | title -}}
76 + {{- end -}}
77 + </a>
78 + </li>
79 + {{- end -}}
80 + {{- end}}
81 + </ul>
82 + {{- end }}
83 + {{- end }}
84 + </div>
85 + {{- end }}
86 + </div>
87 + {{- $currentPage := . }}
88 + <ul id="menu" class="menu" aria-label="Primary menu">
89 + {{- range site.Menus.main }}
90 + {{- $menu_item_url := (cond (strings.HasSuffix .URL "/") .URL (printf "%s/" .URL) ) | absLangURL }}
91 + {{- $page_url:= $currentPage.Permalink | absLangURL }}
92 + {{- $is_search := eq (site.GetPage .KeyName).Layout `search` }}
93 + {{- $is_current := eq $menu_item_url $page_url }}
94 + <li>
95 + <a href="{{ .URL | absLangURL }}" title="{{ .Title | default .Name }} {{- cond $is_search (" (Alt + /)" | safeHTMLAttr) ("" | safeHTMLAttr ) }}"
96 + {{- cond $is_search (" accesskey=/" | safeHTMLAttr) ("" | safeHTMLAttr ) }}
97 + {{- if $is_current }} aria-current="page"{{- end }}>
98 + <span {{- if $is_current }} class="active" {{- end }}>
99 + {{- .Pre }}
100 + {{- .Name -}}
101 + {{ .Post -}}
102 + </span>
103 + {{- if (findRE "://" .URL) }}&nbsp;
104 + <svg fill="none" shape-rendering="geometricPrecision" stroke="currentColor" stroke-linecap="round"
105 + stroke-linejoin="round" stroke-width="2.5" viewBox="0 0 24 24" height="12" width="12">
106 + <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path>
107 + <path d="M15 3h6v6"></path>
108 + <path d="M10 14L21 3"></path>
109 + </svg>
110 + {{- end }}
111 + </a>
112 + </li>
113 + {{- end }}
114 + </ul>
115 + </nav>
116 +</header>
layouts/partials/report-metrics.html
+1 -1
@@ -26,7 +26,7 @@
26 {{- if $hasTopRepo -}}
27 <div class="metric-card metric-card-wide">
28 <span class="metric-label">Top repo</span>
29 - <strong class="metric-value"><a href="https://github.com/{{ index $page.Params "top_repo" }}">{{ index $page.Params "top_repo" }}</a></strong>
29 + <strong class="metric-value"><a href="https://github.com/{{ index $page.Params "top_repo" }}" aria-label="Open GitHub repository {{ index $page.Params "top_repo" }}">{{ index $page.Params "top_repo" }}</a></strong>
30 </div>
31 {{- end -}}
32 </div>
layouts/search/list.html
+44 -3
@@ -18,17 +18,56 @@
18 <span class="section-topline">Static search</span>
19 <h2 id="search-interface-title">Find trends by keyword or filter</h2>
20 <p>Search every published report, then narrow the results by category, tag, year, or featured repository.</p>
21 + <div class="search-shortcuts" id="search-shortcuts" aria-label="Search keyboard shortcuts">
22 + <span class="search-shortcuts-title">Keyboard</span>
23 + <span class="search-shortcut"><kbd>/</kbd><span>Focus search</span></span>
24 + <span class="search-shortcut"><kbd>Tab</kbd><span>Move through filters and results</span></span>
25 + <span class="search-shortcut"><kbd>Enter</kbd><span>Open a result</span></span>
26 + </div>
27 + <p id="search-help" class="visually-hidden">Search every SquadScope report by keyword or filter. Use slash to focus the search field, Tab to move through filters and results, and Enter to open a result.</p>
28 </div>
22 - <div id="search"></div>
29 + <div id="search" class="search-panel" role="search" aria-labelledby="search-interface-title" aria-describedby="search-help search-shortcuts"></div>
30 </section>
31
32 <script>
33 window.addEventListener("DOMContentLoaded", function () {
27 - if (!window.PagefindUI) return;
34 + const searchElement = document.getElementById("search");
35 + if (!searchElement || !window.PagefindUI) return;
36 +
37 + const applyA11y = function () {
38 + const input = searchElement.querySelector(".pagefind-ui__search-input");
39 + if (input) {
40 + input.setAttribute("aria-label", "Search SquadScope reports");
41 + input.setAttribute("aria-describedby", "search-help search-shortcuts");
42 + }
43 +
44 + const results = searchElement.querySelector(".pagefind-ui__results");
45 + if (results) {
46 + results.setAttribute("aria-live", "polite");
47 + results.setAttribute("aria-label", "Search results");
48 + }
49 +
50 + const clearButton = searchElement.querySelector(".pagefind-ui__search-clear");
51 + if (clearButton && !clearButton.getAttribute("aria-label")) {
52 + clearButton.setAttribute("aria-label", "Clear search query");
53 + }
54 +
55 + searchElement.querySelectorAll("button").forEach(function (button) {
56 + if (!button.getAttribute("aria-label")) {
57 + const label = button.textContent.trim();
58 + if (label) {
59 + button.setAttribute("aria-label", label);
60 + }
61 + }
62 + });
63 + };
64 +
65 + const observer = new MutationObserver(applyA11y);
66 + observer.observe(searchElement, { childList: true, subtree: true });
67
68 new PagefindUI({
69 element: "#search",
31 - bundlePath: "{{ `/pagefind/` | relURL }}",
70 + bundlePath: "{{ `pagefind/` | absURL }}",
71 showSubResults: true,
72 showImages: false,
73 excerptLength: 24,
@@ -39,6 +78,8 @@
78 placeholder: "Search reports, tags, categories, or repos"
79 }
80 });
81 +
82 + requestAnimationFrame(applyA11y);
83 });
84 </script>
85 {{- end -}}
layouts/weekly/single.html
+7 -7
@@ -20,13 +20,13 @@
20 {{ partial "report-metrics.html" . }}
21
22 <nav class="report-shortcuts" aria-label="Report navigation">
23 - <a href="{{ "/archive/" | relURL }}">Archive</a>
24 - <a href="{{ "/monthly/" | relURL }}">Monthly</a>
25 - <a href="{{ "/yearly/" | relURL }}">Yearly</a>
26 - <a href="{{ "/tags/" | relURL }}">Tags</a>
27 - <a href="{{ "/categories/" | relURL }}">Categories</a>
28 - <a href="{{ "/search/" | relURL }}">Search</a>
29 - <a href="{{ "/index.xml" | relURL }}">RSS</a>
23 + <a href="{{ `archive/` | absLangURL }}">Archive</a>
24 + <a href="{{ `monthly/` | absLangURL }}">Monthly</a>
25 + <a href="{{ `yearly/` | absLangURL }}">Yearly</a>
26 + <a href="{{ `tags/` | absLangURL }}">Tags</a>
27 + <a href="{{ `categories/` | absLangURL }}">Categories</a>
28 + <a href="{{ `search/` | absLangURL }}">Search</a>
29 + <a href="{{ `index.xml` | absLangURL }}">RSS</a>
30 </nav>
31
32 <div class="post-content md-content">
reply_thread1.graphql new
+10
@@ -0,0 +1,10 @@
1 +mutation {
2 + addPullRequestReviewThreadReply(input: {
3 + threadId: "PRRT_kwDOSgq4hM6C3UXy"
4 + body: "✅ Fixed: Removed the submodule initialization instruction from the rollout checklist. The project does not use git submodules."
5 + }) {
6 + comment {
7 + id
8 + }
9 + }
10 +}