update styles, bundle CSS and minimize
- Remove bottom border from issues-nav - Position label dropdown on the far right - Fix: pass sorted labels to all issue list pages so dropdown appears style: simplify issue list rows - Show hash and comment count (when > 0) in light gray on the left - Show labels as comma-separated plain text on the right - Remove pill/button styling from labels in list view - Move date to the far right column style: tighten up issue list UI - Remove gap from issues-list - Move labels to issue-item__stats (right column, under timestamp) - Reduce padding and margins for tighter layout - Align data blocks with flex column layout style: redesign individual issue page header - Title on left, status on right in flex row - Title wraps if too long - Mobile: flex column with status under title - Open status: red (--text-red) - Closed status: green (--text-green) - Add CSS variables for theme compatibility style: adjust spacing on issue detail page - Mobile: normal line-height between title and status - Add normal line-height between meta and labels style: remove gap, use proper margins instead - Remove gap from issue-detail__title-row - Use margin-right on title instead - Mobile: use margin-top on status for line-height spacing style: add dots between labels on issue detail page - Add • between labels using ::after pseudo-element - Use :not(:last-child) to avoid trailing dot deps: add tdewolff/minify for CSS processing feat: add CSSFile field to Config struct feat: add CSS bundling function feat: integrate CSS bundling into build flow
35 files changed,  +2927, -44
M Makefile
+7, -5
 1@@ -10,7 +10,7 @@ clean:
 2 .PHONY: clean
 3 
 4 build:
 5-	go build -o pgit ./main.go
 6+	go build -o pgit .
 7 .PHONY: build
 8 
 9 img:
10@@ -31,15 +31,17 @@ test:
11 
12 test-site:
13 	mkdir -p testdata.site
14-	go run main.go \
15+	rm -rf testdata.site/*
16+	go run . \
17 		--repo ./testdata \
18 		--out testdata.site \
19 		--clone-url "https://test.com/test/test2" \
20 		--home-url "https://test.com/test" \
21 		--label testdata \
22-		--desc "pgit testing site" \
23-		--theme "dracula" \
24-		--revs "main,branch-c"
25+		--desc "pgit testing site - [link](https://yourmom.com)" \
26+		--theme "kanagawa-dragon" \
27+		--revs "main,branch-c" \
28+		--issues
29 .PHONY: test-site
30 
31 static: build clean
M devbox.json
+2, -1
 1@@ -8,7 +8,8 @@
 2     "ripgrep@latest",
 3     "nodejs@latest",
 4     "yarn@latest",
 5-    "git@latest"
 6+    "git@latest",
 7+    "git-bug@latest"
 8   ],
 9   "shell": {
10     "init_hook": [
M devbox.lock
+48, -0
 1@@ -197,6 +197,54 @@
 2         }
 3       }
 4     },
 5+    "git-bug@latest": {
 6+      "last_modified": "2026-03-21T07:29:51Z",
 7+      "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#git-bug",
 8+      "source": "devbox-search",
 9+      "version": "0.10.1",
10+      "systems": {
11+        "aarch64-darwin": {
12+          "outputs": [
13+            {
14+              "name": "out",
15+              "path": "/nix/store/cs52jih5d6zjp73z5aw73jmrgz8kk8nh-git-bug-0.10.1",
16+              "default": true
17+            }
18+          ],
19+          "store_path": "/nix/store/cs52jih5d6zjp73z5aw73jmrgz8kk8nh-git-bug-0.10.1"
20+        },
21+        "aarch64-linux": {
22+          "outputs": [
23+            {
24+              "name": "out",
25+              "path": "/nix/store/h06gd6mgm5jhb2ny43ql7xbxrrd76cxw-git-bug-0.10.1",
26+              "default": true
27+            }
28+          ],
29+          "store_path": "/nix/store/h06gd6mgm5jhb2ny43ql7xbxrrd76cxw-git-bug-0.10.1"
30+        },
31+        "x86_64-darwin": {
32+          "outputs": [
33+            {
34+              "name": "out",
35+              "path": "/nix/store/7kr8ypm571jkqacl9bk5cvfasj031h1l-git-bug-0.10.1",
36+              "default": true
37+            }
38+          ],
39+          "store_path": "/nix/store/7kr8ypm571jkqacl9bk5cvfasj031h1l-git-bug-0.10.1"
40+        },
41+        "x86_64-linux": {
42+          "outputs": [
43+            {
44+              "name": "out",
45+              "path": "/nix/store/gqs0rch1z1gpl2qr52rjgfaxjzqrxr7n-git-bug-0.10.1",
46+              "default": true
47+            }
48+          ],
49+          "store_path": "/nix/store/gqs0rch1z1gpl2qr52rjgfaxjzqrxr7n-git-bug-0.10.1"
50+        }
51+      }
52+    },
53     "git@latest": {
54       "last_modified": "2026-03-21T07:29:51Z",
55       "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#git",
A docs/plans/2025-01-07-git-bug-integration.md
+939, -0
  1@@ -0,0 +1,939 @@
  2+# git-bug Integration Implementation Plan
  3+
  4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
  5+
  6+**Goal:** Integrate git-bug issue tracking into pgit static site generator, creating issue list pages and individual issue detail pages.
  7+
  8+**Architecture:** Use git-bug's Go library (cache and entities/bug packages) to read issues from the repository at build time, filter and categorize them, then generate static HTML pages using Go templates.
  9+
 10+**Tech Stack:** Go, git-bug library, Go templates (existing pgit template system)
 11+
 12+---
 13+
 14+## Task 1: Add git-bug Dependency
 15+
 16+**Files:**
 17+- Modify: `go.mod`
 18+- Run: `go mod tidy`
 19+
 20+**Step 1: Add git-bug dependency**
 21+
 22+Run: `go get github.com/git-bug/[email protected]`
 23+
 24+**Step 2: Verify go.mod updated**
 25+
 26+Check that `github.com/git-bug/git-bug v0.10.1` appears in go.mod
 27+
 28+**Step 3: Commit**
 29+
 30+```bash
 31+jj commit -m "deps: add git-bug library for issue tracking"
 32+```
 33+
 34+---
 35+
 36+## Task 2: Create Issue Data Structures
 37+
 38+**Files:**
 39+- Create: `issues.go` (new file)
 40+
 41+**Step 1: Create issue data types**
 42+
 43+```go
 44+package main
 45+
 46+import (
 47+	"html/template"
 48+	"time"
 49+)
 50+
 51+// IssueData represents a git-bug issue for templates
 52+type IssueData struct {
 53+	ID           string
 54+	FullID       string
 55+	Title        string
 56+	Status       string // "open" or "closed"
 57+	Author       string
 58+	CreatedAt    time.Time
 59+	HumanDate    string
 60+	Labels       []string
 61+	CommentCount int // excludes original description
 62+	Description  template.HTML
 63+	Comments     []CommentData
 64+	URL          template.URL
 65+}
 66+
 67+// CommentData represents a comment on an issue
 68+type CommentData struct {
 69+	Author    string
 70+	CreatedAt time.Time
 71+	HumanDate string
 72+	Body      template.HTML
 73+}
 74+
 75+// IssuesListPageData for list pages (open, closed, by label)
 76+type IssuesListPageData struct {
 77+	*PageData
 78+	Filter      string // "open", "closed", or "label"
 79+	OpenCount   int
 80+	ClosedCount int
 81+	Issues      []*IssueData
 82+	Label       string   // set when Filter == "label"
 83+	AllLabels   []string // all unique labels from open issues
 84+}
 85+
 86+// IssueDetailPageData for individual issue pages
 87+type IssueDetailPageData struct {
 88+	*PageData
 89+	Issue *IssueData
 90+}
 91+
 92+// Active returns the active navigation item
 93+func (i *IssuesListPageData) Active() string { return "issues" }
 94+func (i *IssueDetailPageData) Active() string { return "issues" }
 95+```
 96+
 97+**Step 2: Commit**
 98+
 99+```bash
100+jj commit -m "feat: add issue data structures"
101+```
102+
103+---
104+
105+## Task 3: Create Issue Loading Function
106+
107+**Files:**
108+- Modify: `issues.go` (add import and functions)
109+
110+**Step 1: Add imports and loading function**
111+
112+```go
113+import (
114+	"fmt"
115+	"net/url"
116+	"path/filepath"
117+	"strings"
118+
119+	"github.com/git-bug/git-bug/cache"
120+	"github.com/git-bug/git-bug/entities/bug"
121+	"github.com/git-bug/git-bug/repository"
122+)
123+
124+// loadIssues reads all issues from git-bug in the repository
125+func (c *Config) loadIssues() ([]*IssueData, error) {
126+	// Open the repository with git-bug's repo opener
127+	repo, err := repository.OpenGoGitRepo(c.RepoPath, nil, nil)
128+	if err != nil {
129+		return nil, fmt.Errorf("failed to open repository: %w", err)
130+	}
131+	
132+	// Create cache for efficient querying
133+	repoCache, err := cache.NewRepoCacheNoEvents(repo)
134+	if err != nil {
135+		return nil, fmt.Errorf("failed to create repo cache: %w", err)
136+	}
137+	defer repoCache.Close()
138+	
139+	// Read all bugs
140+	var issues []*IssueData
141+	bugCache := repoCache.Bugs()
142+	
143+	for streamedBug := range bugCache.ReadAll() {
144+		if streamedBug.Err != nil {
145+			c.Logger.Warn("failed to read bug", "error", streamedBug.Err)
146+			continue
147+		}
148+		
149+		b := streamedBug.Entity
150+		snap := b.Compile()
151+		
152+		// Count comments (excluding the original description)
153+		commentCount := 0
154+		for _, op := range snap.Operations {
155+			if _, ok := op.(*bug.AddCommentOp); ok {
156+				commentCount++
157+			}
158+		}
159+		
160+		// Build labels slice
161+		labels := make([]string, len(snap.Labels))
162+		copy(labels, snap.Labels)
163+		
164+		// Build comments
165+		var comments []CommentData
166+		for _, op := range snap.Operations {
167+			if addCommentOp, ok := op.(*bug.AddCommentOp); ok {
168+				comments = append(comments, CommentData{
169+					Author:    addCommentOp.Author.Name,
170+					CreatedAt: addCommentOp.Timestamp,
171+					HumanDate: humanizeTime(addCommentOp.Timestamp),
172+					Body:      renderMarkdown(addCommentOp.Message),
173+				})
174+			}
175+		}
176+		
177+		fullID := b.Id().String()
178+		issue := &IssueData{
179+			ID:           getShortID(fullID),
180+			FullID:       fullID,
181+			Title:        snap.Title,
182+			Status:       string(snap.Status),
183+			Author:       snap.Author.Name,
184+			CreatedAt:    snap.CreateTime,
185+			HumanDate:    humanizeTime(snap.CreateTime),
186+			Labels:       labels,
187+			CommentCount: commentCount,
188+			Description:  renderMarkdown(snap.Description),
189+			Comments:     comments,
190+			URL:          c.getIssueURL(fullID),
191+		}
192+		issues = append(issues, issue)
193+	}
194+	
195+	return issues, nil
196+}
197+
198+// getIssueURL generates the URL for an issue detail page
199+func (c *Config) getIssueURL(issueID string) template.URL {
200+	url := fmt.Sprintf("%sissues/%s.html", c.RootRelative, issueID)
201+	return template.URL(url)
202+}
203+
204+// getIssuesListURL generates URL for issues list pages
205+func (c *Config) getIssuesListURL(filter string, label string) template.URL {
206+	var path string
207+	switch filter {
208+	case "open", "closed":
209+		path = filepath.Join("issues", filter, "index.html")
210+	case "label":
211+		// URL-encode the label
212+		encodedLabel := url.PathEscape(label)
213+		path = filepath.Join("issues", "label", encodedLabel, "index.html")
214+	default:
215+		path = filepath.Join("issues", "open", "index.html")
216+	}
217+	return c.compileURL("/", path)
218+}
219+```
220+
221+**Step 2: Commit**
222+
223+```bash
224+jj commit -m "feat: add issue loading from git-bug"
225+```
226+
227+---
228+
229+## Task 4: Create Issue List Template
230+
231+**Files:**
232+- Create: `html/issues_list.page.tmpl`
233+
234+**Step 1: Create the list template**
235+
236+```html
237+{{template "base" .}}
238+
239+{{define "title"}}{{if eq .Filter "label"}}Issues labeled "{{.Label}}"{{else if eq .Filter "closed"}}Closed Issues{{else}}Open Issues{{end}} - {{.Repo.RepoName}}{{end}}
240+
241+{{define "content"}}
242+<div class="issues-page">
243+  <div class="issues-header">
244+    <h1>
245+      {{if eq .Filter "label"}}
246+        Issues labeled "{{.Label}}"
247+      {{else if eq .Filter "closed"}}
248+        Closed Issues
249+      {{else}}
250+        Open Issues
251+      {{end}}
252+    </h1>
253+    
254+    <div class="issues-stats">
255+      {{if eq .Filter "open"}}
256+        <span class="stat active">{{.OpenCount}} Open</span>
257+        <a href="{{getIssuesListURL "closed" ""}}" class="stat">{{.ClosedCount}} Closed</a>
258+      {{else if eq .Filter "closed"}}
259+        <a href="{{getIssuesListURL "open" ""}}" class="stat">{{.OpenCount}} Open</a>
260+        <span class="stat active">{{.ClosedCount}} Closed</span>
261+      {{else}}
262+        <a href="{{getIssuesListURL "open" ""}}" class="stat">{{.OpenCount}} Open</a>
263+        <a href="{{getIssuesListURL "closed" ""}}" class="stat">{{.ClosedCount}} Closed</a>
264+        <span class="label-indicator">Label: {{.Label}}</span>
265+      {{end}}
266+    </div>
267+  </div>
268+
269+  {{if eq .Filter "open"}}
270+  <div class="issues-labels">
271+    <span>Filter by label:</span>
272+    {{range .AllLabels}}
273+      <a href="{{$.Repo.getIssuesListURL "label" .}}" class="label-link">{{.}}</a>
274+    {{end}}
275+  </div>
276+  {{end}}
277+
278+  <div class="issues-list">
279+    {{range .Issues}}
280+    <div class="issue-item">
281+      <div class="issue-item__main">
282+        <a href="{{.URL}}" class="issue-item__title">{{.Title}}</a>
283+        <div class="issue-item__meta">
284+          <span class="issue-id">#{{.ID}}</span>
285+          <span class="issue-status issue-status--{{.Status}}">{{.Status}}</span>
286+          {{if .Labels}}
287+          <span class="issue-labels">
288+            {{range .Labels}}<span class="issue-label">{{.}}</span>{{end}}
289+          </span>
290+          {{end}}
291+        </div>
292+      </div>
293+      <div class="issue-item__stats">
294+        <span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
295+        {{if gt .CommentCount 0}}
296+        <span class="issue-comments">{{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}</span>
297+        {{end}}
298+      </div>
299+    </div>
300+    {{else}}
301+    <p class="no-issues">No issues found.</p>
302+    {{end}}
303+  </div>
304+</div>
305+{{end}}
306+```
307+
308+**Step 2: Commit**
309+
310+```bash
311+jj commit -m "feat: add issues list template"
312+```
313+
314+---
315+
316+## Task 5: Create Issue Detail Template
317+
318+**Files:**
319+- Create: `html/issue_detail.page.tmpl`
320+
321+**Step 1: Create the detail template**
322+
323+```html
324+{{template "base" .}}
325+
326+{{define "title"}}{{.Issue.Title}} - Issue #{{.Issue.ID}} - {{.Repo.RepoName}}{{end}}
327+
328+{{define "content"}}
329+<div class="issue-detail">
330+  <div class="issue-detail__header">
331+    <h1>
332+      <span class="issue-status issue-status--{{.Issue.Status}}">{{.Issue.Status}}</span>
333+      {{.Issue.Title}}
334+    </h1>
335+    <div class="issue-detail__meta">
336+      <span class="issue-id">#{{.Issue.ID}}</span>
337+      <span class="issue-author">opened by {{.Issue.Author}}</span>
338+      <span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
339+    </div>
340+    {{if .Issue.Labels}}
341+    <div class="issue-detail__labels">
342+      <span>Labels:</span>
343+      {{range .Issue.Labels}}<span class="issue-label">{{.}}</span>{{end}}
344+    </div>
345+    {{end}}
346+  </div>
347+
348+  <div class="issue-description">
349+    <div class="issue-comment">
350+      <div class="issue-comment__header">
351+        <span class="issue-comment__author">{{.Issue.Author}}</span>
352+        <span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
353+      </div>
354+      <div class="issue-comment__body">{{.Issue.Description}}</div>
355+    </div>
356+  </div>
357+
358+  {{if .Issue.Comments}}
359+  <div class="issue-comments">
360+    <h3>{{len .Issue.Comments}} Comment{{if ne (len .Issue.Comments) 1}}s{{end}}</h3>
361+    {{range .Issue.Comments}}
362+    <div class="issue-comment">
363+      <div class="issue-comment__header">
364+        <span class="issue-comment__author">{{.Author}}</span>
365+        <span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
366+      </div>
367+      <div class="issue-comment__body">{{.Body}}</div>
368+    </div>
369+    {{end}}
370+  </div>
371+  {{end}}
372+</div>
373+{{end}}
374+```
375+
376+**Step 2: Commit**
377+
378+```bash
379+jj commit -m "feat: add issue detail template"
380+```
381+
382+---
383+
384+## Task 6: Create Issue Page Generation Functions
385+
386+**Files:**
387+- Modify: `issues.go` (add generation functions)
388+
389+**Step 1: Add generation functions**
390+
391+```go
392+// writeIssueListPage generates an issues list page
393+func (c *Config) writeIssueListPage(data *PageData, filter string, label string, issues []*IssueData, openCount, closedCount int, allLabels []string) {
394+	c.Logger.Info("writing issues list", "filter", filter, "label", label, "count", len(issues))
395+	
396+	pageData := &IssuesListPageData{
397+		PageData:    data,
398+		Filter:      filter,
399+		OpenCount:   openCount,
400+		ClosedCount: closedCount,
401+		Issues:      issues,
402+		Label:       label,
403+		AllLabels:   allLabels,
404+	}
405+	
406+	var subdir string
407+	switch filter {
408+	case "open":
409+		subdir = "issues/open"
410+	case "closed":
411+		subdir = "issues/closed"
412+	case "label":
413+		encodedLabel := url.PathEscape(label)
414+		subdir = filepath.Join("issues/label", encodedLabel)
415+	}
416+	
417+	c.writeHtml(&WriteData{
418+		Filename: "index.html",
419+		Subdir:   subdir,
420+		Template: "html/issues_list.page.tmpl",
421+		Data:     pageData,
422+	})
423+}
424+
425+// writeIssueDetailPage generates an individual issue page
426+func (c *Config) writeIssueDetailPage(data *PageData, issue *IssueData) {
427+	c.Logger.Info("writing issue detail", "id", issue.ID, "title", issue.Title)
428+	
429+	pageData := &IssueDetailPageData{
430+		PageData: data,
431+		Issue:    issue,
432+	}
433+	
434+	c.writeHtml(&WriteData{
435+		Filename: fmt.Sprintf("%s.html", issue.FullID),
436+		Subdir:   "issues",
437+		Template: "html/issue_detail.page.tmpl",
438+		Data:     pageData,
439+	})
440+}
441+
442+// writeIssues generates all issue-related pages
443+func (c *Config) writeIssues(pageData *PageData) error {
444+	// Load all issues from git-bug
445+	issues, err := c.loadIssues()
446+	if err != nil {
447+		return fmt.Errorf("failed to load issues: %w", err)
448+	}
449+	
450+	// If no issues, skip generation
451+	if len(issues) == 0 {
452+		c.Logger.Info("no git-bug issues found, skipping issue generation")
453+		return nil
454+	}
455+	
456+	c.Logger.Info("loaded issues", "count", len(issues))
457+	
458+	// Categorize issues
459+	var openIssues, closedIssues []*IssueData
460+	labelIssues := make(map[string][]*IssueData)
461+	allLabels := make(map[string]bool)
462+	
463+	for _, issue := range issues {
464+		if issue.Status == "open" {
465+			openIssues = append(openIssues, issue)
466+			// Collect labels from open issues
467+			for _, label := range issue.Labels {
468+				allLabels[label] = true
469+				labelIssues[label] = append(labelIssues[label], issue)
470+			}
471+		} else {
472+			closedIssues = append(closedIssues, issue)
473+		}
474+	}
475+	
476+	openCount := len(openIssues)
477+	closedCount := len(closedIssues)
478+	
479+	// Build sorted label list
480+	var sortedLabels []string
481+	for label := range allLabels {
482+		sortedLabels = append(sortedLabels, label)
483+	}
484+	sort.Strings(sortedLabels)
485+	
486+	// Generate individual issue pages for all issues
487+	for _, issue := range issues {
488+		c.writeIssueDetailPage(pageData, issue)
489+	}
490+	
491+	// Generate list pages
492+	c.writeIssueListPage(pageData, "open", "", openIssues, openCount, closedCount, sortedLabels)
493+	c.writeIssueListPage(pageData, "closed", "", closedIssues, openCount, closedCount, sortedLabels)
494+	
495+	// Generate label filter pages (only for open issues)
496+	for label, issues := range labelIssues {
497+		c.writeIssueListPage(pageData, "label", label, issues, openCount, closedCount, sortedLabels)
498+	}
499+	
500+	return nil
501+}
502+```
503+
504+**Step 2: Add import for sort**
505+
506+Add `sort` to imports in `issues.go`
507+
508+**Step 3: Commit**
509+
510+```bash
511+jj commit -m "feat: add issue page generation functions"
512+```
513+
514+---
515+
516+## Task 7: Update Header Template for Issues Navigation
517+
518+**Files:**
519+- Modify: `html/header.partial.tmpl`
520+
521+**Step 1: Add Issues link to navigation**
522+
523+Add after the commits link (around line 16):
524+
525+```html
526+{{if .SiteURLs.IssuesURL}}{{if eq .Active "issues"}}<span class="active">issues</span>{{else}}<a href="{{.SiteURLs.IssuesURL}}">issues</a>{{end}}{{end}}
527+```
528+
529+**Step 2: Commit**
530+
531+```bash
532+jj commit -m "feat: add issues navigation link to header"
533+```
534+
535+---
536+
537+## Task 8: Add IssuesURL to SiteURLs
538+
539+**Files:**
540+- Modify: `main.go` (SiteURLs struct and getURLs method)
541+
542+**Step 1: Add IssuesURL to SiteURLs struct**
543+
544+Around line 167, add:
545+
546+```go
547+type SiteURLs struct {
548+	HomeURL    template.URL
549+	CloneURL   template.URL
550+	SummaryURL template.URL
551+	RefsURL    template.URL
552+	IssuesURL  template.URL // new
553+}
554+```
555+
556+**Step 2: Add IssuesURL generation to getURLs method**
557+
558+Around line 609, add:
559+
560+```go
561+func (c *Config) getURLs() *SiteURLs {
562+	return &SiteURLs{
563+		HomeURL:    c.HomeURL,
564+		CloneURL:   c.CloneURL,
565+		RefsURL:    c.getRefsURL(),
566+		SummaryURL: c.getSummaryURL(),
567+		IssuesURL:  c.getIssuesURL(),
568+	}
569+}
570+
571+// getIssuesURL returns the URL for the issues page
572+func (c *Config) getIssuesURL() template.URL {
573+	url := c.RootRelative + "issues/open/index.html"
574+	return template.URL(url)
575+}
576+```
577+
578+**Step 3: Commit**
579+
580+```bash
581+jj commit -m "feat: add IssuesURL to site URLs"
582+```
583+
584+---
585+
586+## Task 9: Add --issues CLI Flag and Integration
587+
588+**Files:**
589+- Modify: `main.go` (Config struct, flags, and writeRepo)
590+
591+**Step 1: Add Issues field to Config struct**
592+
593+Around line 34, add:
594+
595+```go
596+type Config struct {
597+	// ... existing fields ...
598+	Issues bool // enable git-bug issue generation
599+	// ... rest of fields ...
600+}
601+```
602+
603+**Step 2: Add --issues flag**
604+
605+Around line 1117, add:
606+
607+```go
608+var issuesFlag = flag.Bool("issues", false, "enable git-bug issue generation")
609+```
610+
611+**Step 3: Add Issues to config initialization**
612+
613+Around line 1146, add:
614+
615+```go
616+config := &Config{
617+	// ... existing fields ...
618+	Issues: *issuesFlag,
619+	// ... rest ...
620+}
621+```
622+
623+**Step 4: Integrate issue generation into writeRepo**
624+
625+Around line 740 (before `c.writeRefs`), add:
626+
627+```go
628+// Generate issue pages if enabled
629+if c.Issues {
630+	err := c.writeIssues(data)
631+	if err != nil {
632+		c.Logger.Warn("failed to write issues", "error", err)
633+	}
634+}
635+```
636+
637+**Step 5: Commit**
638+
639+```bash
640+jj commit -m "feat: add --issues flag and integrate issue generation"
641+```
642+
643+---
644+
645+## Task 10: Add CSS Styling for Issues
646+
647+**Files:**
648+- Modify: `html/header.partial.tmpl` (add style block) or create separate CSS
649+
650+**Step 1: Add styles to header template**
651+
652+Add inside the `<style>` block or in a style tag in header.partial.tmpl:
653+
654+```html
655+<style>
656+/* Issues styles */
657+.issues-page {
658+  max-width: 1200px;
659+  margin: 0 auto;
660+  padding: 20px;
661+}
662+
663+.issues-header {
664+  margin-bottom: 20px;
665+  border-bottom: 1px solid var(--border);
666+  padding-bottom: 15px;
667+}
668+
669+.issues-header h1 {
670+  margin-bottom: 10px;
671+}
672+
673+.issues-stats {
674+  display: flex;
675+  gap: 15px;
676+  align-items: center;
677+}
678+
679+.issues-stats .stat {
680+  padding: 5px 10px;
681+  border-radius: 4px;
682+  text-decoration: none;
683+}
684+
685+.issues-stats .stat.active {
686+  background: var(--link-color);
687+  color: var(--bg-color);
688+}
689+
690+.issues-labels {
691+  margin: 15px 0;
692+  padding: 10px;
693+  background: rgba(255,255,255,0.05);
694+  border-radius: 4px;
695+}
696+
697+.issues-labels .label-link {
698+  margin-left: 10px;
699+  padding: 2px 8px;
700+  background: var(--border);
701+  border-radius: 3px;
702+  font-size: 0.9em;
703+}
704+
705+.issue-item {
706+  display: flex;
707+  justify-content: space-between;
708+  align-items: flex-start;
709+  padding: 15px;
710+  border-bottom: 1px solid var(--border);
711+}
712+
713+.issue-item:hover {
714+  background: rgba(255,255,255,0.02);
715+}
716+
717+.issue-item__title {
718+  font-size: 1.1em;
719+  font-weight: bold;
720+  margin-bottom: 5px;
721+  display: block;
722+}
723+
724+.issue-item__meta {
725+  display: flex;
726+  gap: 10px;
727+  align-items: center;
728+  font-size: 0.85em;
729+  color: var(--text-color);
730+  opacity: 0.7;
731+}
732+
733+.issue-status {
734+  padding: 2px 8px;
735+  border-radius: 3px;
736+  font-size: 0.85em;
737+  font-weight: bold;
738+}
739+
740+.issue-status--open {
741+  background: #28a745;
742+  color: white;
743+}
744+
745+.issue-status--closed {
746+  background: #6c757d;
747+  color: white;
748+}
749+
750+.issue-labels {
751+  display: flex;
752+  gap: 5px;
753+}
754+
755+.issue-label {
756+  padding: 2px 6px;
757+  background: var(--border);
758+  border-radius: 3px;
759+  font-size: 0.8em;
760+}
761+
762+.issue-item__stats {
763+  text-align: right;
764+  font-size: 0.85em;
765+  opacity: 0.7;
766+}
767+
768+.issue-comments {
769+  margin-left: 10px;
770+}
771+
772+/* Issue detail */
773+.issue-detail {
774+  max-width: 1000px;
775+  margin: 0 auto;
776+  padding: 20px;
777+}
778+
779+.issue-detail__header {
780+  margin-bottom: 20px;
781+  padding-bottom: 15px;
782+  border-bottom: 1px solid var(--border);
783+}
784+
785+.issue-detail__header h1 {
786+  margin-bottom: 10px;
787+}
788+
789+.issue-detail__meta {
790+  display: flex;
791+  gap: 10px;
792+  align-items: center;
793+  margin-bottom: 10px;
794+}
795+
796+.issue-detail__labels {
797+  margin-top: 10px;
798+}
799+
800+.issue-description {
801+  margin-bottom: 30px;
802+}
803+
804+.issue-comment {
805+  padding: 15px;
806+  border: 1px solid var(--border);
807+  border-radius: 6px;
808+  margin-bottom: 15px;
809+}
810+
811+.issue-comment__header {
812+  display: flex;
813+  justify-content: space-between;
814+  margin-bottom: 10px;
815+  padding-bottom: 10px;
816+  border-bottom: 1px solid var(--border);
817+}
818+
819+.issue-comment__author {
820+  font-weight: bold;
821+}
822+
823+.issue-comment__date {
824+  opacity: 0.7;
825+  font-size: 0.9em;
826+}
827+
828+.issue-comment__body {
829+  line-height: 1.6;
830+}
831+
832+.issue-comments h3 {
833+  margin-bottom: 15px;
834+}
835+</style>
836+```
837+
838+**Step 2: Commit**
839+
840+```bash
841+jj commit -m "feat: add CSS styling for issues"
842+```
843+
844+---
845+
846+## Task 11: Build and Test
847+
848+**Files:**
849+- Run: Build and test commands
850+
851+**Step 1: Build the project**
852+
853+```bash
854+go build -o pgit
855+```
856+
857+**Step 2: Test without --issues (should work as before)**
858+
859+```bash
860+./pgit --revs HEAD --out ./test-output
861+```
862+
863+**Step 3: Test with --issues on repo without git-bug (should skip gracefully)**
864+
865+```bash
866+./pgit --revs HEAD --out ./test-output --issues
867+```
868+
869+Expected: Should see "no git-bug issues found, skipping issue generation" in logs
870+
871+**Step 4: Test with --issues on repo with git-bug (if available)**
872+
873+If you have a repo with git-bug:
874+```bash
875+./pgit --repo /path/to/repo-with-issues --revs HEAD --out ./test-output --issues
876+```
877+
878+Expected: Should see issues pages generated
879+
880+**Step 5: Commit**
881+
882+```bash
883+jj commit -m "build: verify issues feature compiles and runs"
884+```
885+
886+---
887+
888+## Task 12: Add Template Function for IssuesListURL
889+
890+**Files:**
891+- Modify: `main.go` (writeHtml method to add template functions)
892+
893+**Step 1: Add template function**
894+
895+In `writeHtml` method, modify the template parsing to include a function map:
896+
897+```go
898+func (c *Config) writeHtml(writeData *WriteData) {
899+	// Create function map
900+	funcMap := template.FuncMap{
901+		"getIssuesListURL": func(filter string, label string) template.URL {
902+			return c.getIssuesListURL(filter, label)
903+		},
904+	}
905+	
906+	ts, err := template.New("").Funcs(funcMap).ParseFS(
907+		embedFS,
908+		writeData.Template,
909+		"html/header.partial.tmpl",
910+		"html/footer.partial.tmpl",
911+		"html/base.layout.tmpl",
912+	)
913+	bail(err)
914+	
915+	// ... rest of method ...
916+}
917+```
918+
919+**Step 2: Commit**
920+
921+```bash
922+jj commit -m "feat: add getIssuesListURL template function"
923+```
924+
925+---
926+
927+## Summary
928+
929+After completing all tasks, the pgit static site generator will:
930+
931+1. Support `--issues` flag to enable git-bug integration
932+2. Generate `/issues/open/index.html` with all open issues
933+3. Generate `/issues/closed/index.html` with all closed issues
934+4. Generate `/issues/label/{label}/index.html` for each label (URL-encoded)
935+5. Generate `/issues/{short-hash}.html` for each issue
936+6. Show issue counts and navigation links on list pages
937+7. Display individual issues with title, status, labels, description, and comments
938+8. Add "Issues" link to site navigation
939+
940+All pages will match the existing pgit styling and use the same template system.
A docs/plans/2026-04-08-css-build-pipeline-design.md
+129, -0
  1@@ -0,0 +1,129 @@
  2+# CSS Build Pipeline Improvement - Design Document
  3+
  4+**Date:** 2026-04-08
  5+
  6+## Goal
  7+
  8+Improve the CSS build pipeline by concatenating all CSS files in the proper precedence order, minimizing the result, appending a content-based hash to the filename, and inserting the correct link in the header.
  9+
 10+## Architecture
 11+
 12+Build-time CSS processing integrated into the existing Go build flow. CSS files are concatenated in precedence order, minified using a Go library, hashed for cache-busting, and the resulting filename is passed to templates.
 13+
 14+**Tech Stack:**
 15+- Go 1.21+
 16+- `github.com/tdewolff/minify/v2` - CSS minification library
 17+- SHA256 for content hashing
 18+
 19+---
 20+
 21+## Current State
 22+
 23+**Current CSS Setup:**
 24+- `static/pgit.css` - Main stylesheet (1380 lines, ~23KB)
 25+- `vars.css` - Dynamically generated from Chroma theme colors
 26+- `syntax.css` - Dynamically generated for syntax highlighting
 27+- CSS links are hardcoded in `html/base.layout.tmpl`
 28+
 29+**Current Build Flow:**
 30+1. `copyStatic()` copies static files as-is
 31+2. `vars.css` and `syntax.css` are generated in main.go
 32+3. No minification or concatenation happens
 33+
 34+---
 35+
 36+## Design Decisions
 37+
 38+### CSS Precedence Order
 39+
 40+1. **`pgit.css`** - Base styles (default variables, layout, components)
 41+2. **`vars.css`** - Theme overrides (CSS variables from Chroma theme)  
 42+3. **`syntax.css`** - Syntax highlighting (`.chroma*` selectors)
 43+
 44+This order ensures base styles load first, theme variables override defaults, and syntax highlighting has its specific selectors applied last.
 45+
 46+### CSS Minification Library
 47+
 48+**Selected:** `github.com/tdewolff/minify/v2`
 49+
 50+**Rationale:**
 51+- Pure Go implementation (no external dependencies)
 52+- Very fast (~50-70MB/s for CSS processing)
 53+- Safe minifications (removes comments/whitespace, optimizes colors/numbers, but won't restructure rules)
 54+- Well maintained (4.1k stars, active development)
 55+- Simple API for integration
 56+- Can also handle HTML minification if needed in future
 57+
 58+**Typical compression:** 10-15% reduction for CSS files.
 59+
 60+### Build Flow
 61+
 62+**New steps in `main()`:**
 63+
 64+1. **Collect CSS content in order:**
 65+   - Read `static/pgit.css` from embedded FS
 66+   - Generate `vars.css` content (from existing `style()` function)
 67+   - Generate `syntax.css` content (from existing `formatter.WriteCSS()`)
 68+
 69+2. **Concatenate:** `pgit.css` + `vars.css` + `syntax.css`
 70+
 71+3. **Minify** using `tdewolff/minify/v2/css`
 72+
 73+4. **Generate hash** (SHA256, first 8 chars of hex encoding)
 74+
 75+5. **Write file:** `styles.{hash}.css`
 76+
 77+6. **Update Config** with the CSS filename for templates
 78+
 79+7. **Update template** `html/base.layout.tmpl` to use single CSS link
 80+
 81+### Output Structure
 82+
 83+```
 84+public/
 85+  styles.a3f7b2c1.css   # Minified, hashed bundle (replaces pgit.css, vars.css, syntax.css)
 86+  index.html            # References styles.a3f7b2c1.css
 87+  ...
 88+```
 89+
 90+---
 91+
 92+## Files to Modify
 93+
 94+1. **`main.go`** - Add CSS bundling logic
 95+   - Add `CSSFile` field to `Config` struct
 96+   - Create `bundleCSS()` function
 97+   - Call it in `main()` after theme setup, before `writeRepo()`
 98+
 99+2. **`html/base.layout.tmpl`** - Update CSS link
100+   - Replace two `<link>` tags with single `{{.Repo.CSSFile}}` reference
101+
102+3. **`go.mod`** - Add tdewolff/minify dependency
103+
104+---
105+
106+## Out of Scope
107+
108+- HTML minification (pages are already lightweight)
109+- Source maps for CSS (not needed)
110+- External CSS processing tools (keeping it pure Go)
111+- Additional CSS files (none planned)
112+
113+---
114+
115+## Success Criteria
116+
117+- [ ] All three CSS files concatenated in correct order
118+- [ ] Result is minified (smaller than sum of individual files)
119+- [ ] Filename includes content-based hash (e.g., `styles.a3f7b2c1.css`)
120+- [ ] HTML templates reference the hashed CSS file correctly
121+- [ ] Site renders identically to before (visual parity)
122+- [ ] Build completes without errors
123+
124+## Benefits
125+
126+- ✅ **Single HTTP request** for all CSS (vs. 3 separate files)
127+- ✅ **Cache-busting** via content hash in filename
128+- ✅ **Smaller file size** (~10-15% reduction from minification)
129+- ✅ **No external tools** - pure Go implementation
130+- ✅ **Deterministic** - same content = same hash
A docs/plans/2026-04-08-css-build-pipeline-implementation.md
+402, -0
  1@@ -0,0 +1,402 @@
  2+# CSS Build Pipeline Implementation Plan
  3+
  4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
  5+
  6+**Goal:** Concatenate all CSS files in proper precedence order, minimize, add content hash to filename, and update templates to reference the bundled CSS.
  7+
  8+**Architecture:** Build-time CSS processing integrated into existing Go build flow using tdewolff/minify library.
  9+
 10+**Tech Stack:** Go 1.21+, github.com/tdewolff/minify/v2, SHA256 hashing
 11+
 12+---
 13+
 14+## Task 1: Add CSS minification dependency
 15+
 16+**Files:**
 17+- Modify: `go.mod`
 18+
 19+**Step 1: Add tdewolff/minify dependency**
 20+
 21+Run: `go get github.com/tdewolff/minify/v2/css`
 22+
 23+Expected: Module added to go.mod and go.sum updated
 24+
 25+**Step 2: Commit**
 26+
 27+```bash
 28+jj commit -m "deps: add tdewolff/minify for CSS processing"
 29+```
 30+
 31+---
 32+
 33+## Task 2: Add CSSFile field to Config struct
 34+
 35+**Files:**
 36+- Modify: `main.go:39-81` (Config struct)
 37+
 38+**Step 1: Add CSSFile field**
 39+
 40+Add to Config struct after line 80:
 41+
 42+```go
 43+	// CSS bundle filename (with content hash)
 44+	CSSFile string
 45+```
 46+
 47+**Step 2: Verify struct compiles**
 48+
 49+Run: `go build -o /dev/null .`
 50+
 51+Expected: Build succeeds
 52+
 53+**Step 3: Commit**
 54+
 55+```bash
 56+jj commit -m "feat: add CSSFile field to Config struct"
 57+```
 58+
 59+---
 60+
 61+## Task 3: Create CSS bundling function
 62+
 63+**Files:**
 64+- Modify: `main.go` (add new function before main())
 65+
 66+**Step 1: Add imports for minification and hashing**
 67+
 68+Add to imports section:
 69+
 70+```go
 71+	"crypto/sha256"
 72+	"encoding/hex"
 73+
 74+	"github.com/tdewolff/minify/v2"
 75+	"github.com/tdewolff/minify/v2/css"
 76+```
 77+
 78+**Step 2: Create bundleCSS function**
 79+
 80+Add before `main()` function:
 81+
 82+```go
 83+// bundleCSS concatenates, minifies, and hashes all CSS files
 84+// Returns the filename of the bundled CSS (e.g., "styles.a3f7b2c1.css")
 85+func (c *Config) bundleCSS() (string, error) {
 86+	c.Logger.Info("bundling CSS files")
 87+
 88+	// Initialize minifier
 89+	m := minify.New()
 90+	m.AddFunc("text/css", css.Minify)
 91+
 92+	var buf bytes.Buffer
 93+
 94+	// 1. Read pgit.css from embedded static FS
 95+	pgitCSS, err := staticFS.ReadFile("static/pgit.css")
 96+	if err != nil {
 97+		return "", fmt.Errorf("failed to read pgit.css: %w", err)
 98+	}
 99+	buf.Write(pgitCSS)
100+	buf.WriteString("\n")
101+
102+	// 2. Generate vars.css content
103+	varsCSS := style(*c.Theme)
104+	buf.WriteString(varsCSS)
105+	buf.WriteString("\n")
106+
107+	// 3. Generate syntax.css content
108+	var syntaxBuf bytes.Buffer
109+	err = c.Formatter.WriteCSS(&syntaxBuf, c.Theme)
110+	if err != nil {
111+		return "", fmt.Errorf("failed to generate syntax.css: %w", err)
112+	}
113+	buf.Write(syntaxBuf.Bytes())
114+
115+	// 4. Minify the concatenated CSS
116+	minified, err := m.Bytes("text/css", buf.Bytes())
117+	if err != nil {
118+		return "", fmt.Errorf("failed to minify CSS: %w", err)
119+	}
120+
121+	// 5. Generate content hash (first 8 chars of SHA256)
122+	hash := sha256.Sum256(minified)
123+	hashStr := hex.EncodeToString(hash[:8])
124+
125+	// 6. Create filename with hash
126+	filename := fmt.Sprintf("styles.%s.css", hashStr)
127+
128+	// 7. Write to output directory
129+	outPath := filepath.Join(c.Outdir, filename)
130+	err = os.WriteFile(outPath, minified, 0644)
131+	if err != nil {
132+		return "", fmt.Errorf("failed to write CSS bundle: %w", err)
133+	}
134+
135+	c.Logger.Info("CSS bundle created", "filename", filename, "size", len(minified))
136+
137+	return filename, nil
138+}
139+```
140+
141+**Step 3: Verify function compiles**
142+
143+Run: `go build -o /dev/null .`
144+
145+Expected: Build succeeds
146+
147+**Step 4: Commit**
148+
149+```bash
150+jj commit -m "feat: add CSS bundling function"
151+```
152+
153+---
154+
155+## Task 4: Integrate CSS bundling into main build flow
156+
157+**Files:**
158+- Modify: `main.go:1349-1367` (main function end)
159+
160+**Step 1: Call bundleCSS before writeRepo**
161+
162+Locate this section in main():
163+```go
164+	config.writeRepo()
165+	err = config.copyStatic("static")
166+	bail(err)
167+```
168+
169+Replace with:
170+```go
171+	// Bundle CSS files before generating site
172+	cssFile, err := config.bundleCSS()
173+	bail(err)
174+	config.CSSFile = cssFile
175+
176+	config.writeRepo()
177+	
178+	// Note: copyStatic is no longer needed for CSS files since they're bundled
179+	// but we keep it for any other static assets
180+	err = config.copyStatic("static")
181+	bail(err)
182+```
183+
184+**Step 2: Remove old CSS generation code**
185+
186+Remove this section at the end of main():
187+```go
188+	styles := style(*theme)
189+	err = os.WriteFile(filepath.Join(out, "vars.css"), []byte(styles), 0644)
190+	if err != nil {
191+		panic(err)
192+	}
193+
194+	fp := filepath.Join(out, "syntax.css")
195+	w, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
196+	if err != nil {
197+		bail(err)
198+	}
199+	err = formatter.WriteCSS(w, theme)
200+	if err != nil {
201+		bail(err)
202+	}
203+```
204+
205+**Step 3: Verify build succeeds**
206+
207+Run: `go build -o pgit .`
208+
209+Expected: Build succeeds
210+
211+**Step 4: Commit**
212+
213+```bash
214+jj commit -m "feat: integrate CSS bundling into build flow"
215+```
216+
217+---
218+
219+## Task 5: Update base layout template
220+
221+**Files:**
222+- Modify: `html/base.layout.tmpl:13-15`
223+
224+**Step 1: Replace CSS link tags**
225+
226+Current:
227+```html
228+    <link rel="stylesheet" href="{{.Repo.RootRelative}}pgit.css" />
229+    <link rel="stylesheet" href="{{.Repo.RootRelative}}vars.css" />
230+```
231+
232+Replace with:
233+```html
234+    <link rel="stylesheet" href="{{.Repo.RootRelative}}{{.Repo.CSSFile}}" />
235+```
236+
237+**Step 2: Verify template syntax**
238+
239+No build step for templates - will be validated at runtime
240+
241+**Step 3: Commit**
242+
243+```bash
244+jj commit -m "feat: update template to use bundled CSS"
245+```
246+
247+---
248+
249+## Task 6: Test the implementation
250+
251+**Files:**
252+- Test: Run build and verify output
253+
254+**Step 1: Build the binary**
255+
256+Run: `go build -o pgit .`
257+
258+Expected: Clean build
259+
260+**Step 2: Run test-site target**
261+
262+Run: `make test-site`
263+
264+Expected: Build completes successfully
265+
266+**Step 3: Verify CSS bundle was created**
267+
268+Run: `ls -la testdata.site/*.css`
269+
270+Expected: Single file `styles.{hash}.css` exists (no pgit.css, vars.css, syntax.css)
271+
272+**Step 4: Verify file content**
273+
274+Run: `head -5 testdata.site/styles.*.css`
275+
276+Expected: Minified CSS (no whitespace, all on few lines)
277+
278+**Step 5: Verify HTML references correct file**
279+
280+Run: `grep -o 'styles\.[a-f0-9]*\.css' testdata.site/index.html`
281+
282+Expected: Matches the filename from step 3
283+
284+**Step 6: Check file size reduction**
285+
286+Run: `wc -c static/pgit.css` and compare with bundled file
287+
288+Expected: Bundled file should be smaller than original pgit.css alone (due to minification)
289+
290+**Step 7: Commit**
291+
292+```bash
293+jj commit -m "test: verify CSS bundling works correctly"
294+```
295+
296+---
297+
298+## Task 7: Verify visual parity
299+
300+**Files:**
301+- Test: Compare rendered output
302+
303+**Step 1: Start a local server**
304+
305+Run: `cd testdata.site && python3 -m http.server 8080 &`
306+
307+**Step 2: Check a page renders correctly**
308+
309+Open browser or use curl: `curl -s http://localhost:8080/index.html | head -20`
310+
311+Expected: HTML loads, CSS is referenced correctly
312+
313+**Step 3: Verify CSS loads**
314+
315+Run: `curl -s http://localhost:8080/styles.*.css | head -5`
316+
317+Expected: Returns minified CSS content
318+
319+**Step 4: Stop test server**
320+
321+Run: `pkill -f "http.server 8080"`
322+
323+**Step 5: Commit**
324+
325+```bash
326+jj commit -m "test: verify visual parity and CSS loading"
327+```
328+
329+---
330+
331+## Task 8: Clean up (optional)
332+
333+**Files:**
334+- Modify: `main.go` (remove unused code if desired)
335+
336+**Step 1: Review if copyStatic is still needed**
337+
338+If `static/` only contained CSS files, we can remove the `copyStatic` call entirely.
339+
340+Check: `ls static/`
341+
342+If only CSS files, remove:
343+```go
344+	err = config.copyStatic("static")
345+	bail(err)
346+```
347+
348+**Step 2: Commit if changes made**
349+
350+```bash
351+jj commit -m "cleanup: remove unused copyStatic call"
352+```
353+
354+---
355+
356+## Task 9: Final verification
357+
358+**Files:**
359+- Test: Full build pipeline
360+
361+**Step 1: Clean and rebuild**
362+
363+Run:
364+```bash
365+make clean
366+make build
367+make test-site
368+```
369+
370+Expected: All steps succeed
371+
372+**Step 2: Verify final output**
373+
374+Run: `ls -la testdata.site/ | grep -E '\.(css|html)$'`
375+
376+Expected:
377+- One CSS file: `styles.{hash}.css`
378+- Multiple HTML files
379+- No `pgit.css`, `vars.css`, or `syntax.css`
380+
381+**Step 3: Final commit**
382+
383+```bash
384+jj commit -m "feat: complete CSS build pipeline improvement"
385+```
386+
387+---
388+
389+## Summary
390+
391+After completing all tasks:
392+
393+1. ✅ All three CSS files concatenated in correct order (pgit.css → vars.css → syntax.css)
394+2. ✅ Result is minified using tdewolff/minify
395+3. ✅ Filename includes 8-char content hash (e.g., `styles.a3f7b2c1.css`)
396+4. ✅ HTML templates reference the hashed CSS file via `{{.Repo.CSSFile}}`
397+5. ✅ Site renders identically to before
398+6. ✅ Build completes without errors
399+
400+**Files changed:**
401+- `go.mod` - Added tdewolff/minify dependency
402+- `main.go` - Added bundleCSS function and integrated into build flow
403+- `html/base.layout.tmpl` - Updated to use bundled CSS
M go.mod
+104, -4
  1@@ -1,17 +1,117 @@
  2 module github.com/picosh/pgit
  3 
  4-go 1.24
  5+go 1.25.0
  6 
  7 require (
  8 	github.com/alecthomas/chroma/v2 v2.13.0
  9-	github.com/dustin/go-humanize v1.0.0
 10+	github.com/dustin/go-humanize v1.0.1
 11 	github.com/gogs/git-module v1.6.0
 12 	github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab
 13 )
 14 
 15 require (
 16+	dario.cat/mergo v1.0.2 // indirect
 17+	github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
 18+	github.com/99designs/gqlgen v0.17.89 // indirect
 19+	github.com/99designs/keyring v1.2.2 // indirect
 20+	github.com/MichaelMure/go-term-text v0.3.1 // indirect
 21+	github.com/Microsoft/go-winio v0.6.2 // indirect
 22+	github.com/ProtonMail/go-crypto v1.4.1 // indirect
 23+	github.com/RoaringBitmap/roaring v1.9.4 // indirect
 24+	github.com/VividCortex/ewma v1.2.0 // indirect
 25+	github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
 26+	github.com/agnivade/levenshtein v1.2.1 // indirect
 27+	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
 28+	github.com/awesome-gocui/gocui v1.1.0 // indirect
 29+	github.com/bits-and-blooms/bitset v1.24.4 // indirect
 30+	github.com/blevesearch/bleve v1.0.14 // indirect
 31+	github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
 32+	github.com/blevesearch/mmap-go v1.2.0 // indirect
 33+	github.com/blevesearch/segment v0.9.1 // indirect
 34+	github.com/blevesearch/snowballstem v0.9.0 // indirect
 35+	github.com/blevesearch/zap/v11 v11.0.14 // indirect
 36+	github.com/blevesearch/zap/v12 v12.0.14 // indirect
 37+	github.com/blevesearch/zap/v13 v13.0.6 // indirect
 38+	github.com/blevesearch/zap/v14 v14.0.5 // indirect
 39+	github.com/blevesearch/zap/v15 v15.0.3 // indirect
 40+	github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
 41+	github.com/cloudflare/circl v1.6.3 // indirect
 42+	github.com/couchbase/vellum v1.0.2 // indirect
 43+	github.com/cyphar/filepath-securejoin v0.6.1 // indirect
 44+	github.com/danieljoos/wincred v1.2.3 // indirect
 45+	github.com/davecgh/go-spew v1.1.1 // indirect
 46 	github.com/dlclark/regexp2 v1.11.0 // indirect
 47+	github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
 48+	github.com/emirpasic/gods v1.18.1 // indirect
 49+	github.com/fatih/color v1.19.0 // indirect
 50+	github.com/gdamore/encoding v1.0.1 // indirect
 51+	github.com/gdamore/tcell/v2 v2.13.8 // indirect
 52+	github.com/git-bug/git-bug v0.10.1 // indirect
 53+	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 54+	github.com/go-git/go-billy/v5 v5.8.0 // indirect
 55+	github.com/go-git/go-git/v5 v5.17.2 // indirect
 56+	github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
 57+	github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
 58+	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 59+	github.com/golang/protobuf v1.5.4 // indirect
 60+	github.com/golang/snappy v1.0.0 // indirect
 61+	github.com/google/go-querystring v1.2.0 // indirect
 62+	github.com/google/uuid v1.6.0 // indirect
 63+	github.com/gorilla/mux v1.8.1 // indirect
 64+	github.com/gorilla/websocket v1.5.3 // indirect
 65+	github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
 66+	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 67+	github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
 68+	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
 69+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 70+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 71+	github.com/kevinburke/ssh_config v1.6.0 // indirect
 72+	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 73+	github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
 74+	github.com/mattn/go-colorable v0.1.14 // indirect
 75+	github.com/mattn/go-isatty v0.0.20 // indirect
 76+	github.com/mattn/go-runewidth v0.0.22 // indirect
 77 	github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect
 78-	github.com/stretchr/testify v1.8.1 // indirect
 79-	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
 80+	github.com/mschoch/smat v0.2.0 // indirect
 81+	github.com/mtibben/percent v0.2.1 // indirect
 82+	github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
 83+	github.com/pjbgf/sha1cd v0.5.0 // indirect
 84+	github.com/pkg/errors v0.9.1 // indirect
 85+	github.com/pmezard/go-difflib v1.0.0 // indirect
 86+	github.com/rivo/uniseg v0.4.7 // indirect
 87+	github.com/sergi/go-diff v1.4.0 // indirect
 88+	github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed // indirect
 89+	github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf // indirect
 90+	github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
 91+	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
 92+	github.com/skeema/knownhosts v1.3.2 // indirect
 93+	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
 94+	github.com/sosodev/duration v1.4.0 // indirect
 95+	github.com/spf13/cobra v1.10.2 // indirect
 96+	github.com/spf13/pflag v1.0.10 // indirect
 97+	github.com/steveyen/gtreap v0.1.0 // indirect
 98+	github.com/stretchr/testify v1.11.1 // indirect
 99+	github.com/tdewolff/minify/v2 v2.24.12 // indirect
100+	github.com/tdewolff/parse/v2 v2.8.11 // indirect
101+	github.com/vbauerster/mpb/v8 v8.12.0 // indirect
102+	github.com/vektah/gqlparser/v2 v2.5.32 // indirect
103+	github.com/willf/bitset v1.1.11 // indirect
104+	github.com/xanzy/ssh-agent v0.3.3 // indirect
105+	gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect
106+	go.etcd.io/bbolt v1.4.3 // indirect
107+	golang.org/x/crypto v0.49.0 // indirect
108+	golang.org/x/mod v0.34.0 // indirect
109+	golang.org/x/net v0.52.0 // indirect
110+	golang.org/x/oauth2 v0.36.0 // indirect
111+	golang.org/x/sync v0.20.0 // indirect
112+	golang.org/x/sys v0.42.0 // indirect
113+	golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd // indirect
114+	golang.org/x/term v0.41.0 // indirect
115+	golang.org/x/text v0.35.0 // indirect
116+	golang.org/x/time v0.15.0 // indirect
117+	golang.org/x/tools v0.43.0 // indirect
118+	golang.org/x/vuln v1.1.4 // indirect
119+	google.golang.org/protobuf v1.36.11 // indirect
120+	gopkg.in/warnings.v0 v0.1.2 // indirect
121+	gopkg.in/yaml.v3 v3.0.1 // indirect
122 )
M go.sum
+335, -0
  1@@ -1,9 +1,86 @@
  2+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
  3+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
  4+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
  5+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
  6+github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8=
  7+github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ=
  8+github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
  9+github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
 10+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 11+github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0=
 12+github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o=
 13+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 14+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 15+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 16+github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
 17+github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
 18+github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 19+github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
 20+github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
 21+github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
 22+github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
 23+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
 24+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
 25+github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
 26+github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
 27 github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
 28 github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 29 github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
 30 github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
 31 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 32 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 33+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
 34+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
 35+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 36+github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII=
 37+github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg=
 38+github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
 39+github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
 40+github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
 41+github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4=
 42+github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ=
 43+github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc=
 44+github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc=
 45+github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
 46+github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
 47+github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
 48+github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
 49+github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
 50+github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
 51+github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
 52+github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
 53+github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
 54+github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
 55+github.com/blevesearch/zap/v11 v11.0.14 h1:IrDAvtlzDylh6H2QCmS0OGcN9Hpf6mISJlfKjcwJs7k=
 56+github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY=
 57+github.com/blevesearch/zap/v12 v12.0.14 h1:2o9iRtl1xaRjsJ1xcqTyLX414qPAwykHNV7wNVmbp3w=
 58+github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg=
 59+github.com/blevesearch/zap/v13 v13.0.6 h1:r+VNSVImi9cBhTNNR+Kfl5uiGy8kIbb0JMz/h8r6+O4=
 60+github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw=
 61+github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67njR0NU=
 62+github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
 63+github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
 64+github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
 65+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
 66+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
 67+github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
 68+github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
 69+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 70+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 71+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 72+github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
 73+github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
 74+github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
 75+github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
 76+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 77+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 78+github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
 79+github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
 80+github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
 81+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
 82+github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
 83+github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
 84+github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
 85 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 86 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 87 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 88@@ -11,29 +88,287 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK
 89 github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 90 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 91 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 92+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 93+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 94+github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
 95+github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
 96+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
 97+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 98+github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
 99+github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
100+github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
101+github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
102+github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
103+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
104+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
105+github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
106+github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
107+github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
108+github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
109+github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
110+github.com/git-bug/git-bug v0.10.1 h1:mjJK/wrfKWUze5EkVpJj+3N3LzSfeujqqH1qq8x4aco=
111+github.com/git-bug/git-bug v0.10.1/go.mod h1:43BRtb/Nr6QEJlNLkLY/64vyopxA0y5nvjSNsktnan8=
112+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
113+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
114+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
115+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
116+github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
117+github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
118+github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104=
119+github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
120+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
121+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
122+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
123+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
124 github.com/gogs/git-module v1.6.0 h1:71GdRM9/pFxGgSUz8t2DKmm3RYuHUnTjsOuFInJXnkM=
125 github.com/gogs/git-module v1.6.0/go.mod h1:8jFYhDxLUwEOhM2709l2CJXmoIIslobU1xszpT0NcAI=
126+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
127+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
128+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
129+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
130+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
131+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
132+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
133+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
134+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
135+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
136 github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
137 github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
138+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
139+github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
140+github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
141+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
142+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
143+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
144+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
145+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
146+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
147+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
148+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
149+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
150+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
151+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
152+github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
153+github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
154+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
155+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
156+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
157 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
158 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
159+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
160+github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg=
161+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
162+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
163+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
164+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
165+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
166+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
167+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
168+github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
169+github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
170+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
171+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
172+github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
173+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
174+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
175+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
176+github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
177+github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
178+github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
179+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
180+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
181+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
182+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
183+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
184+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
185+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
186+github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk=
187+github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
188 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
189 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
190+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
191+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
192+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
193+github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
194+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
195+github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
196+github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
197+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
198+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
199+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
200+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
201+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
202+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
203+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
204+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
205+github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
206+github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
207+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
208+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
209 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
210 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
211+github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
212+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
213+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
214+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
215+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
216+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
217+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
218+github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
219+github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
220+github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
221+github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed h1:KT7hI8vYXgU0s2qaMkrfq9tCA1w/iEPgfredVP+4Tzw=
222+github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
223+github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf h1:o1uxfymjZ7jZ4MsgCErcwWGtVKSiNAXtS59Lhs6uI/g=
224+github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
225+github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
226+github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
227+github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=
228+github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=
229+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
230+github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
231+github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
232+github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
233+github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
234+github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
235+github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
236+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
237+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
238+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
239+github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
240+github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
241+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
242+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
243+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
244+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
245+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
246+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
247+github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
248+github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
249 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
250 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
251 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
252+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
253 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
254+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
255 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
256 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
257 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
258 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
259+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
260+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
261+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
262+github.com/tdewolff/minify/v2 v2.24.12 h1:YXJxVJmz7vxgnEv1v8J/EI4x+Uw4MMohcRFK7TFOjmk=
263+github.com/tdewolff/minify/v2 v2.24.12/go.mod h1:exq1pjdrh9uAICdfVKQwqz6MsJmWmQahZuTC6pTO6ro=
264+github.com/tdewolff/parse/v2 v2.8.11 h1:SGyjEy3xEqd+W9WVzTlTQ5GkP/en4a1AZNZVJ1cvgm0=
265+github.com/tdewolff/parse/v2 v2.8.11/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
266+github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
267+github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
268+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
269+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
270+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
271+github.com/vbauerster/mpb/v8 v8.12.0 h1:+gneY3ifzc88tKDzOtfG8k8gfngCx615S2ZmFM4liWg=
272+github.com/vbauerster/mpb/v8 v8.12.0/go.mod h1:V02YIuMVo301Y1VE9VtZlD8s84OMsk+EKN6mwvf/588=
273+github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
274+github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
275+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
276+github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
277+github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
278+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
279+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
280+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
281+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
282+gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24=
283+gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8=
284+go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
285+go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
286+go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
287+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
288+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
289+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
290+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
291+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
292+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
293+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
294+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
295+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
296+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
297+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
298+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
299+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
300+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
301+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
302+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
303+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
304+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
305+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
306+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
307+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
308+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
309+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
310 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
311 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
312 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
315+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
316+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
317+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
318+golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
319+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
320+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
324+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
327+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
328+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
329+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
330+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
331+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
332+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
333+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
334+golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd h1:QbR6Giw8AyR6v6Vff72jiZRUdZnetfgYRndQuKa806k=
335+golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
336+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
337+golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
338+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
339+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
340+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
341+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
342+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
343+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
344+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
345+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
346+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
347+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
348+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
349+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
350+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
351+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
352+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
353+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
354+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
355+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
356+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
357+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
358+golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
359+golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
360+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
361+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
362+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
363 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
364+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
365+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
366+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
367+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
368+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
369+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
370+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
371 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
372+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
373 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
374 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
375 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
M html/base.layout.tmpl
+1, -2
 1@@ -10,8 +10,7 @@
 2 
 3     {{template "meta" .}}
 4 
 5-    <link rel="stylesheet" href="{{.Repo.RootRelative}}vars.css" />
 6-    <link rel="stylesheet" href="{{.Repo.RootRelative}}pgit.css" />
 7+    <link rel="stylesheet" href="{{.Repo.RootRelative}}{{.Repo.CSSFile}}" />
 8   </head>
 9   <body>
10     <header>{{template "header" .}}</header>
M html/header.partial.tmpl
+1, -0
1@@ -14,6 +14,7 @@
2     {{if eq .Active "refs"}}<span class="active">refs</span>{{else}}<a href="{{.SiteURLs.RefsURL}}">refs</a>{{end}}
3     {{if eq .Active "code"}}<span class="active">code</span>{{else}}<a href="{{.RevData.TreeURL}}">code</a>{{end}}
4     {{if eq .Active "commits"}}<span class="active">commits</span>{{else}}<a href="{{.RevData.LogURL}}">commits</a>{{end}}
5+    {{if .SiteURLs.IssuesURL}}{{if eq .Active "issues"}}<span class="active">issues</span>{{else}}<a href="{{.SiteURLs.IssuesURL}}">issues</a>{{end}}{{end}}
6     <details class="nav-menu__ref-selector">
7       <summary>{{.RevData.Name}}</summary>
8       <div class="ref-selector__dropdown">
A html/issue_detail.page.tmpl
+51, -0
 1@@ -0,0 +1,51 @@
 2+{{template "base" .}}
 3+
 4+{{define "title"}}{{.Issue.Title}} - Issue #{{.Issue.ID}} - {{.Repo.RepoName}}{{end}}
 5+{{define "meta"}}{{end}}
 6+
 7+{{define "content"}}
 8+<div class="issue-detail">
 9+  <div class="issue-detail__header">
10+    <div class="issue-detail__title-row">
11+      <h1 class="issue-detail__title">{{.Issue.Title}}</h1>
12+      <span class="issue-detail__status issue-detail__status--{{.Issue.Status}}">{{.Issue.Status}}</span>
13+    </div>
14+    <div class="issue-detail__meta">
15+      <span class="issue-id">#{{.Issue.ID}}</span>
16+      <span class="issue-author">opened by {{.Issue.Author}}</span>
17+      <span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
18+    </div>
19+    {{if .Issue.Labels}}
20+    <div class="issue-detail__labels">
21+      <span>Labels:</span>
22+      {{range .Issue.Labels}}<span class="issue-label">{{.}}</span>{{end}}
23+    </div>
24+    {{end}}
25+  </div>
26+
27+  <div class="issue-description">
28+    <div class="issue-comment">
29+      <div class="issue-comment__header">
30+        <span class="issue-comment__author">{{.Issue.Author}}</span>
31+        <span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
32+      </div>
33+      <div class="issue-comment__body markdown">{{.Issue.Description}}</div>
34+    </div>
35+  </div>
36+
37+  {{if .Issue.Comments}}
38+  <div class="issue-comments">
39+    <h3>{{len .Issue.Comments}} Comment{{if ne (len .Issue.Comments) 1}}s{{end}}</h3>
40+    {{range .Issue.Comments}}
41+    <div class="issue-comment">
42+      <div class="issue-comment__header">
43+        <span class="issue-comment__author">{{.Author}}</span>
44+        <span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
45+      </div>
46+      <div class="issue-comment__body markdown">{{.Body}}</div>
47+    </div>
48+    {{end}}
49+  </div>
50+  {{end}}
51+</div>
52+{{end}}
A html/issues_list.page.tmpl
+63, -0
 1@@ -0,0 +1,63 @@
 2+{{template "base" .}}
 3+
 4+{{define "title"}}{{if eq .Filter "label"}}Issues labeled "{{.Label}}"{{else if eq .Filter "closed"}}Closed Issues{{else}}Open Issues{{end}} - {{.Repo.RepoName}}{{end}}
 5+{{define "meta"}}{{end}}
 6+
 7+{{define "content"}}
 8+<div class="issues-page">
 9+  <div class="issues-header">
10+    <h1>
11+      {{if eq .Filter "label"}}
12+        Issues labeled "{{.Label}}"
13+      {{else if eq .Filter "closed"}}
14+        Closed Issues
15+      {{else}}
16+        Open Issues
17+      {{end}}
18+    </h1>
19+    
20+    <nav class="issues-nav">
21+      {{if eq .Filter "open"}}
22+        <span class="active">{{.OpenCount}} Open</span>
23+        <a href="{{.ClosedIssuesURL}}">{{.ClosedCount}} Closed</a>
24+      {{else if eq .Filter "closed"}}
25+        <a href="{{.OpenIssuesURL}}">{{.OpenCount}} Open</a>
26+        <span class="active">{{.ClosedCount}} Closed</span>
27+      {{else}}
28+        <a href="{{.OpenIssuesURL}}">{{.OpenCount}} Open</a>
29+        <a href="{{.ClosedIssuesURL}}">{{.ClosedCount}} Closed</a>
30+      {{end}}
31+      
32+      {{if .AllLabels}}
33+      <details class="issues-nav__label-selector">
34+        <summary>Labels</summary>
35+        <div class="label-selector__dropdown">
36+          {{range .AllLabels}}
37+            <a href="{{$.Repo.RootRelative}}issues/label/{{.}}/index.html">{{.}}</a>
38+          {{end}}
39+        </div>
40+      </details>
41+      {{end}}
42+    </nav>
43+  </div>
44+
45+  <div class="issues-list">
46+    {{range .Issues}}
47+    <div class="issue-item">
48+      <div class="issue-item__main">
49+        <a href="{{.URL}}" class="issue-item__title">{{.Title}}</a>
50+        <span class="issue-meta">#{{.ID}}{{if gt .CommentCount 0}} · {{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}{{end}}</span>
51+      </div>
52+      <div class="issue-item__stats">
53+        <span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
54+        {{if .Labels}}
55+        <span class="issue-labels-text">{{range $i, $label := .Labels}}{{if $i}}, {{end}}{{$label}}{{end}}</span>
56+        {{end}}
57+      </div>
58+    </div>
59+    {{else}}
60+    <p class="no-issues">No issues found.</p>
61+    {{end}}
62+  </div>
63+</div>
64+{{end}}
M html/summary.page.tmpl
+1, -1
1@@ -1,6 +1,6 @@
2 {{template "base" .}}
3 
4-{{define "title"}}{{.Repo.RepoName}}{{if .Repo.Desc}}- {{.Repo.Desc}}{{end}}{{end}}
5+{{define "title"}}{{.Repo.RepoName}}{{end}}
6 {{define "meta"}}
7 <link rel="stylesheet" href="{{.Repo.RootRelative}}syntax.css" />
8 {{end}}
A issues.go
+268, -0
  1@@ -0,0 +1,268 @@
  2+package main
  3+
  4+import (
  5+	"fmt"
  6+	"html/template"
  7+	"net/url"
  8+	"path/filepath"
  9+
 10+	"github.com/git-bug/git-bug/entities/bug"
 11+	"github.com/git-bug/git-bug/repository"
 12+)
 13+
 14+// IssueData represents a git-bug issue for templates
 15+type IssueData struct {
 16+	ID           string
 17+	FullID       string
 18+	Title        string
 19+	Status       string // "open" or "closed"
 20+	Author       string
 21+	CreatedAt    string
 22+	HumanDate    string
 23+	Labels       []string
 24+	CommentCount int // excludes original description
 25+	Description  template.HTML
 26+	Comments     []CommentData
 27+	URL          template.URL
 28+}
 29+
 30+// CommentData represents a comment on an issue
 31+type CommentData struct {
 32+	Author    string
 33+	CreatedAt string
 34+	HumanDate string
 35+	Body      template.HTML
 36+}
 37+
 38+// IssuesListPageData for list pages (open, closed, by label)
 39+type IssuesListPageData struct {
 40+	*PageData
 41+	Filter          string // "open", "closed", or "label"
 42+	OpenCount       int
 43+	ClosedCount     int
 44+	Issues          []*IssueData
 45+	Label           string       // set when Filter == "label"
 46+	AllLabels       []string     // all unique labels from open issues
 47+	OpenIssuesURL   template.URL // URL to open issues list
 48+	ClosedIssuesURL template.URL // URL to closed issues list
 49+}
 50+
 51+// IssueDetailPageData for individual issue pages
 52+type IssueDetailPageData struct {
 53+	*PageData
 54+	Issue *IssueData
 55+}
 56+
 57+// Active returns the active navigation item
 58+func (i *IssuesListPageData) Active() string  { return "issues" }
 59+func (i *IssueDetailPageData) Active() string { return "issues" }
 60+
 61+// loadIssues reads all issues from git-bug in the repository
 62+func (c *Config) loadIssues() ([]*IssueData, error) {
 63+	c.Logger.Info("loading issues from git-bug", "repoPath", c.RepoPath)
 64+
 65+	// Open the repository with git-bug's repo opener
 66+	repo, err := repository.OpenGoGitRepo(c.RepoPath, "", nil)
 67+	if err != nil {
 68+		return nil, fmt.Errorf("failed to open repository: %w", err)
 69+	}
 70+
 71+	// Read all bugs directly from the repository (without cache)
 72+	var issues []*IssueData
 73+
 74+	for streamedBug := range bug.ReadAll(repo) {
 75+		if streamedBug.Err != nil {
 76+			c.Logger.Warn("failed to read bug", "error", streamedBug.Err)
 77+			continue
 78+		}
 79+
 80+		b := streamedBug.Entity
 81+		snap := b.Compile()
 82+
 83+		// Count comments (excluding the original description)
 84+		commentCount := len(snap.Comments) - 1
 85+		if commentCount < 0 {
 86+			commentCount = 0
 87+		}
 88+
 89+		// Build labels slice
 90+		labels := make([]string, len(snap.Labels))
 91+		for i, label := range snap.Labels {
 92+			labels[i] = string(label)
 93+		}
 94+
 95+		// Build comments (skip first comment which is the description)
 96+		var comments []CommentData
 97+		for i, comment := range snap.Comments {
 98+			if i == 0 {
 99+				// Skip the original description
100+				continue
101+			}
102+			comments = append(comments, CommentData{
103+				Author:    comment.Author.Name(),
104+				CreatedAt: comment.FormatTime(),
105+				HumanDate: comment.FormatTimeRel(),
106+				Body:      c.renderMarkdown(comment.Message),
107+			})
108+		}
109+
110+		// Get description from first comment
111+		var description template.HTML
112+		if len(snap.Comments) > 0 {
113+			description = c.renderMarkdown(snap.Comments[0].Message)
114+		}
115+
116+		fullID := b.Id().String()
117+		issue := &IssueData{
118+			ID:           getShortID(fullID),
119+			FullID:       fullID,
120+			Title:        snap.Title,
121+			Status:       snap.Status.String(),
122+			Author:       snap.Author.Name(),
123+			CreatedAt:    snap.CreateTime.Format("Mon Jan 2 15:04:05 2006 -0700"),
124+			HumanDate:    humanizeTime(snap.CreateTime),
125+			Labels:       labels,
126+			CommentCount: commentCount,
127+			Description:  description,
128+			Comments:     comments,
129+			URL:          c.getIssueURL(fullID),
130+		}
131+		issues = append(issues, issue)
132+	}
133+
134+	return issues, nil
135+}
136+
137+// getIssueURL generates the URL for an issue detail page
138+func (c *Config) getIssueURL(issueID string) template.URL {
139+	url := fmt.Sprintf("%sissues/%s.html", c.RootRelative, issueID)
140+	return template.URL(url)
141+}
142+
143+// getIssuesListURL generates URL for issues list pages
144+func (c *Config) getIssuesListURL(filter string, label string) template.URL {
145+	var path string
146+	switch filter {
147+	case "open", "closed":
148+		path = filepath.Join("issues", filter, "index.html")
149+	case "label":
150+		// URL-encode the label
151+		encodedLabel := url.PathEscape(label)
152+		path = filepath.Join("issues", "label", encodedLabel, "index.html")
153+	default:
154+		path = filepath.Join("issues", "open", "index.html")
155+	}
156+	return c.compileURL("/", path)
157+}
158+
159+// writeIssueListPage generates an issues list page
160+func (c *Config) writeIssueListPage(data *PageData, filter string, label string, issues []*IssueData, openCount, closedCount int, allLabels []string) {
161+	c.Logger.Info("writing issues list", "filter", filter, "label", label, "count", len(issues))
162+
163+	pageData := &IssuesListPageData{
164+		PageData:        data,
165+		Filter:          filter,
166+		OpenCount:       openCount,
167+		ClosedCount:     closedCount,
168+		Issues:          issues,
169+		Label:           label,
170+		AllLabels:       allLabels,
171+		OpenIssuesURL:   c.getIssuesListURL("open", ""),
172+		ClosedIssuesURL: c.getIssuesListURL("closed", ""),
173+	}
174+
175+	var subdir string
176+	switch filter {
177+	case "open":
178+		subdir = "issues/open"
179+	case "closed":
180+		subdir = "issues/closed"
181+	case "label":
182+		encodedLabel := url.PathEscape(label)
183+		subdir = filepath.Join("issues/label", encodedLabel)
184+	}
185+
186+	c.writeHtml(&WriteData{
187+		Filename: "index.html",
188+		Subdir:   subdir,
189+		Template: "html/issues_list.page.tmpl",
190+		Data:     pageData,
191+	})
192+}
193+
194+// writeIssueDetailPage generates an individual issue page
195+func (c *Config) writeIssueDetailPage(data *PageData, issue *IssueData) {
196+	c.Logger.Info("writing issue detail", "id", issue.ID, "title", issue.Title)
197+
198+	pageData := &IssueDetailPageData{
199+		PageData: data,
200+		Issue:    issue,
201+	}
202+
203+	c.writeHtml(&WriteData{
204+		Filename: fmt.Sprintf("%s.html", issue.FullID),
205+		Subdir:   "issues",
206+		Template: "html/issue_detail.page.tmpl",
207+		Data:     pageData,
208+	})
209+}
210+
211+// writeIssues generates all issue-related pages
212+func (c *Config) writeIssues(pageData *PageData) error {
213+	// Load all issues from git-bug
214+	issues, err := c.loadIssues()
215+	if err != nil {
216+		return fmt.Errorf("failed to load issues: %w", err)
217+	}
218+
219+	// If no issues, skip generation
220+	if len(issues) == 0 {
221+		c.Logger.Info("no git-bug issues found, skipping issue generation")
222+		return nil
223+	}
224+
225+	c.Logger.Info("loaded issues", "count", len(issues))
226+
227+	// Categorize issues
228+	var openIssues, closedIssues []*IssueData
229+	labelIssues := make(map[string][]*IssueData)
230+	allLabels := make(map[string]bool)
231+
232+	for _, issue := range issues {
233+		if issue.Status == "open" {
234+			openIssues = append(openIssues, issue)
235+			// Collect labels from open issues
236+			for _, label := range issue.Labels {
237+				allLabels[label] = true
238+				labelIssues[label] = append(labelIssues[label], issue)
239+			}
240+		} else {
241+			closedIssues = append(closedIssues, issue)
242+		}
243+	}
244+
245+	openCount := len(openIssues)
246+	closedCount := len(closedIssues)
247+
248+	// Build sorted label list
249+	var sortedLabels []string
250+	for label := range allLabels {
251+		sortedLabels = append(sortedLabels, label)
252+	}
253+
254+	// Generate individual issue pages for all issues
255+	for _, issue := range issues {
256+		c.writeIssueDetailPage(pageData, issue)
257+	}
258+
259+	// Generate list pages
260+	c.writeIssueListPage(pageData, "open", "", openIssues, openCount, closedCount, sortedLabels)
261+	c.writeIssueListPage(pageData, "closed", "", closedIssues, openCount, closedCount, sortedLabels)
262+
263+	// Generate label filter pages (only for open issues)
264+	for label, issues := range labelIssues {
265+		c.writeIssueListPage(pageData, "label", label, issues, openCount, closedCount, sortedLabels)
266+	}
267+
268+	return nil
269+}
M main.go
+258, -28
  1@@ -2,10 +2,13 @@ package main
  2 
  3 import (
  4 	"bytes"
  5+	"crypto/sha256"
  6 	"embed"
  7+	"encoding/hex"
  8 	"flag"
  9 	"fmt"
 10 	"html/template"
 11+	"io"
 12 	"log/slog"
 13 	"math"
 14 	"os"
 15@@ -17,12 +20,18 @@ import (
 16 	"unicode/utf8"
 17 
 18 	"github.com/alecthomas/chroma/v2"
 19+	"github.com/alecthomas/chroma/v2/formatters"
 20 	formatterHtml "github.com/alecthomas/chroma/v2/formatters/html"
 21 	"github.com/alecthomas/chroma/v2/lexers"
 22 	"github.com/alecthomas/chroma/v2/styles"
 23 	"github.com/dustin/go-humanize"
 24 	git "github.com/gogs/git-module"
 25 	"github.com/gomarkdown/markdown"
 26+	"github.com/gomarkdown/markdown/ast"
 27+	"github.com/gomarkdown/markdown/html"
 28+	"github.com/gomarkdown/markdown/parser"
 29+	"github.com/tdewolff/minify/v2"
 30+	"github.com/tdewolff/minify/v2/css"
 31 )
 32 
 33 //go:embed html/*.tmpl
 34@@ -40,8 +49,8 @@ type Config struct {
 35 	// optional params
 36 	// generate logs anad tree based on the git revisions provided
 37 	Revs []string
 38-	// description of repo used in the header of site
 39-	Desc string
 40+	// description of repo used in the header of site (HTML)
 41+	Desc template.HTML
 42 	// maximum number of commits that we will process in descending order
 43 	MaxCommits int
 44 	// name of the readme file
 45@@ -51,6 +60,8 @@ type Config struct {
 46 	// We offer a way to disable showing the latest commit in the output
 47 	// for those who want a faster build time
 48 	HideTreeLastCommit bool
 49+	// enable git-bug issue generation
 50+	Issues bool
 51 
 52 	// user-defined urls
 53 	HomeURL  template.URL
 54@@ -71,6 +82,8 @@ type Config struct {
 55 	// chroma style
 56 	Theme     *chroma.Style
 57 	Formatter *formatterHtml.Formatter
 58+	// CSS bundle filename (with content hash)
 59+	CSSFile string
 60 }
 61 
 62 type RevInfo interface {
 63@@ -169,6 +182,7 @@ type SiteURLs struct {
 64 	CloneURL   template.URL
 65 	SummaryURL template.URL
 66 	RefsURL    template.URL
 67+	IssuesURL  template.URL
 68 }
 69 
 70 type PageData struct {
 71@@ -308,10 +322,121 @@ func isMarkdownFile(filename string) bool {
 72 	return ext == ".md"
 73 }
 74 
 75-// renderMarkdown converts markdown text to HTML.
 76-func renderMarkdown(mdText string) template.HTML {
 77-	md := []byte(mdText)
 78-	htmlBytes := markdown.ToHTML(md, nil, nil)
 79+// chromaMarkdownRenderer is a custom HTML renderer that applies Chroma syntax highlighting to code blocks
 80+type chromaMarkdownRenderer struct {
 81+	defaultRenderer *html.Renderer
 82+	theme           *chroma.Style
 83+}
 84+
 85+func (r *chromaMarkdownRenderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
 86+	// Only process code blocks when entering (not when exiting)
 87+	if entering {
 88+		if codeBlock, ok := node.(*ast.CodeBlock); ok {
 89+			// Extract language from Info field (e.g., "go" from "```go")
 90+			lang := extractLanguage(codeBlock.Info)
 91+
 92+			// Get the code content from Literal
 93+			code := string(codeBlock.Literal)
 94+
 95+			// Use Chroma to highlight
 96+			highlighted, err := r.highlightCode(lang, code)
 97+			if err == nil {
 98+				w.Write([]byte(highlighted))
 99+				return ast.SkipChildren
100+			}
101+			// Fall back to default rendering if highlighting fails
102+		}
103+	}
104+
105+	// Use default renderer for all other nodes
106+	return r.defaultRenderer.RenderNode(w, node, entering)
107+}
108+
109+func (r *chromaMarkdownRenderer) RenderHeader(w io.Writer, ast ast.Node) {
110+	r.defaultRenderer.RenderHeader(w, ast)
111+}
112+
113+func (r *chromaMarkdownRenderer) RenderFooter(w io.Writer, ast ast.Node) {
114+	r.defaultRenderer.RenderFooter(w, ast)
115+}
116+
117+func (r *chromaMarkdownRenderer) highlightCode(lang, code string) (string, error) {
118+	// Get appropriate lexer
119+	var lexer chroma.Lexer
120+	if lang != "" {
121+		lexer = lexers.Get(lang)
122+	}
123+	if lexer == nil {
124+		lexer = lexers.Analyse(code)
125+	}
126+	if lexer == nil {
127+		lexer = lexers.Get("plaintext")
128+	}
129+
130+	// Tokenize
131+	iterator, err := lexer.Tokenise(nil, code)
132+	if err != nil {
133+		return "", err
134+	}
135+
136+	// Create formatter WITHOUT line numbers for markdown code blocks
137+	formatter := formatters.Get("html")
138+	if formatter == nil {
139+		return "", fmt.Errorf("failed to get HTML formatter")
140+	}
141+
142+	// Format with Chroma
143+	var buf bytes.Buffer
144+	err = formatter.Format(&buf, r.theme, iterator)
145+	if err != nil {
146+		return "", err
147+	}
148+
149+	// Chroma's HTML formatter wraps output in <pre class="chroma"><code>...</code></pre>
150+	// We need to strip the outer <pre> wrapper since gomarkdown already adds it,
151+	// but keep the <code class="chroma"> tag for styling
152+	highlighted := buf.String()
153+
154+	// Remove Chroma's <pre class="chroma"> prefix and </pre> suffix
155+	// This leaves us with <code class="chroma">...</code> which is what we want
156+	highlighted = strings.TrimPrefix(highlighted, "<pre class=\"chroma\">")
157+	highlighted = strings.TrimSuffix(highlighted, "</pre>")
158+
159+	return highlighted, nil
160+}
161+
162+func extractLanguage(info []byte) string {
163+	if len(info) == 0 {
164+		return "" // No language specified
165+	}
166+	// Info may contain additional text after the language (e.g., "go filename.go")
167+	parts := strings.Fields(string(info))
168+	if len(parts) > 0 {
169+		return parts[0] // First part is the language
170+	}
171+	return ""
172+}
173+
174+// renderMarkdown converts markdown text to HTML with Chroma syntax highlighting for code blocks.
175+func (c *Config) renderMarkdown(mdText string) template.HTML {
176+	// Parse markdown with fenced code block support
177+	extensions := parser.CommonExtensions | parser.FencedCode
178+	p := parser.NewWithExtensions(extensions)
179+	doc := p.Parse([]byte(mdText))
180+
181+	// Create default HTML renderer
182+	htmlFlags := html.CommonFlags
183+	opts := html.RendererOptions{Flags: htmlFlags}
184+	defaultRenderer := html.NewRenderer(opts)
185+
186+	// Create custom renderer with Chroma support
187+	customRenderer := &chromaMarkdownRenderer{
188+		defaultRenderer: defaultRenderer,
189+		theme:           c.Theme,
190+	}
191+
192+	// Render to HTML
193+	htmlBytes := markdown.Render(doc, customRenderer)
194 	return template.HTML(htmlBytes)
195 }
196 
197@@ -339,6 +464,31 @@ func readmeFile(repo *Config) string {
198 	return strings.ToLower(repo.Readme)
199 }
200 
201+// readDescription reads the repository description from .git/description (cloned repo)
202+// or ./description (bare repo). Returns empty string if not found.
203+func readDescription(repoPath string) string {
204+	// Try .git/description first (cloned repo)
205+	descPath := filepath.Join(repoPath, ".git", "description")
206+	data, err := os.ReadFile(descPath)
207+	if err != nil {
208+		// Try ./description (bare repo)
209+		descPath = filepath.Join(repoPath, "description")
210+		data, err = os.ReadFile(descPath)
211+		if err != nil {
212+			return ""
213+		}
214+	}
215+
216+	desc := strings.TrimSpace(string(data))
217+
218+	// Filter out the default git description
219+	if desc == "Unnamed repository; edit this file 'description' to name the repository." {
220+		return ""
221+	}
222+
223+	return desc
224+}
225+
226 func (c *Config) writeHtml(writeData *WriteData) {
227 	ts, err := template.ParseFS(
228 		embedFS,
229@@ -449,7 +599,7 @@ func (c *Config) writeHTMLTreeFile(pageData *PageData, treeItem *TreeItem) strin
230 		treeItem.NumLines = len(strings.Split(str, "\n"))
231 		// Use markdown rendering for .md files, syntax highlighting for others
232 		if isMarkdownFile(treeItem.Entry.Name()) {
233-			contents = string(renderMarkdown(str))
234+			contents = string(c.renderMarkdown(str))
235 		} else {
236 			contents, err = c.parseText(treeItem.Entry.Name(), string(b))
237 			bail(err)
238@@ -612,9 +762,15 @@ func (c *Config) getURLs() *SiteURLs {
239 		CloneURL:   c.CloneURL,
240 		RefsURL:    c.getRefsURL(),
241 		SummaryURL: c.getSummaryURL(),
242+		IssuesURL:  c.getIssuesURL(),
243 	}
244 }
245 
246+func (c *Config) getIssuesURL() template.URL {
247+	url := c.RootRelative + "issues/open/index.html"
248+	return template.URL(url)
249+}
250+
251 func getShortID(id string) string {
252 	return id[:7]
253 }
254@@ -738,11 +894,20 @@ func (c *Config) writeRepo() *BranchOutput {
255 		SiteURLs: c.getURLs(),
256 		Refs:     refInfoList,
257 	}
258+
259+	// Generate issue pages if enabled
260+	if c.Issues {
261+		err := c.writeIssues(data)
262+		if err != nil {
263+			c.Logger.Warn("failed to write issues", "error", err)
264+		}
265+	}
266+
267 	c.writeRefs(data, refInfoList)
268 	// Convert README markdown to HTML for summary page
269 	var readmeHTML template.HTML
270 	if isMarkdownFile(readmeFile(c)) {
271-		readmeHTML = renderMarkdown(mainOutput.Readme)
272+		readmeHTML = c.renderMarkdown(mainOutput.Readme)
273 	} else {
274 		readmeHTML = template.HTML(mainOutput.Readme)
275 	}
276@@ -1085,7 +1250,6 @@ func style(theme chroma.Style) string {
277 	kw := theme.Get(chroma.Keyword)
278 	nv := theme.Get(chroma.NameVariable)
279 	cm := theme.Get(chroma.Comment)
280-	ln := theme.Get(chroma.LiteralNumber)
281 	return fmt.Sprintf(`:root {
282   --bg-color: %s;
283   --text-color: %s;
284@@ -1099,10 +1263,67 @@ func style(theme chroma.Style) string {
285 		cm.Colour.String(),
286 		nv.Colour.String(),
287 		kw.Colour.String(),
288-		ln.Colour.String(),
289+		nv.Colour.String(),
290 	)
291 }
292 
293+// bundleCSS concatenates, minifies, and hashes all CSS files
294+// Returns the filename of the bundled CSS (e.g., "styles.a3f7b2c1.css")
295+func (c *Config) bundleCSS() (string, error) {
296+	c.Logger.Info("bundling CSS files")
297+
298+	// Initialize minifier
299+	m := minify.New()
300+	m.AddFunc("text/css", css.Minify)
301+
302+	var buf bytes.Buffer
303+
304+	// 1. Read pgit.css from embedded static FS
305+	pgitCSS, err := staticFS.ReadFile("static/pgit.css")
306+	if err != nil {
307+		return "", fmt.Errorf("failed to read pgit.css: %w", err)
308+	}
309+	buf.Write(pgitCSS)
310+	buf.WriteString("\n")
311+
312+	// 2. Generate vars.css content
313+	varsCSS := style(*c.Theme)
314+	buf.WriteString(varsCSS)
315+	buf.WriteString("\n")
316+
317+	// 3. Generate syntax.css content
318+	var syntaxBuf bytes.Buffer
319+	err = c.Formatter.WriteCSS(&syntaxBuf, c.Theme)
320+	if err != nil {
321+		return "", fmt.Errorf("failed to generate syntax.css: %w", err)
322+	}
323+	buf.Write(syntaxBuf.Bytes())
324+
325+	// 4. Minify the concatenated CSS
326+	minified, err := m.Bytes("text/css", buf.Bytes())
327+	if err != nil {
328+		return "", fmt.Errorf("failed to minify CSS: %w", err)
329+	}
330+
331+	// 5. Generate content hash (first 8 chars of SHA256)
332+	hash := sha256.Sum256(minified)
333+	hashStr := hex.EncodeToString(hash[:8])
334+
335+	// 6. Create filename with hash
336+	filename := fmt.Sprintf("styles.%s.css", hashStr)
337+
338+	// 7. Write to output directory
339+	outPath := filepath.Join(c.Outdir, filename)
340+	err = os.WriteFile(outPath, minified, 0644)
341+	if err != nil {
342+		return "", fmt.Errorf("failed to write CSS bundle: %w", err)
343+	}
344+
345+	c.Logger.Info("CSS bundle created", "filename", filename, "size", len(minified))
346+
347+	return filename, nil
348+}
349+
350 func main() {
351 	var outdir = flag.String("out", "./public", "output directory")
352 	var rpath = flag.String("repo", ".", "path to git repo")
353@@ -1115,6 +1336,7 @@ func main() {
354 	var rootRelativeFlag = flag.String("root-relative", "/", "html root relative")
355 	var maxCommitsFlag = flag.Int("max-commits", 0, "maximum number of commits to generate")
356 	var hideTreeLastCommitFlag = flag.Bool("hide-tree-last-commit", false, "dont calculate last commit for each file in the tree")
357+	var issuesFlag = flag.Bool("issues", false, "enable git-bug issue generation")
358 
359 	flag.Parse()
360 
361@@ -1143,6 +1365,26 @@ func main() {
362 		formatterHtml.WithClasses(true),
363 	)
364 
365+	// Create a temporary config to use for rendering markdown
366+	tempConfig := &Config{
367+		Theme:    theme,
368+		Logger:   logger,
369+		RepoPath: repoPath,
370+	}
371+
372+	// Determine description: --desc flag overrides git description
373+	var descHTML template.HTML
374+	if *descFlag != "" {
375+		// Use --desc flag value, process as markdown
376+		descHTML = tempConfig.renderMarkdown(*descFlag)
377+	} else {
378+		// Try to read from git description file
379+		gitDesc := readDescription(repoPath)
380+		if gitDesc != "" {
381+			descHTML = tempConfig.renderMarkdown(gitDesc)
382+		}
383+	}
384+
385 	config := &Config{
386 		Outdir:             out,
387 		RepoPath:           repoPath,
388@@ -1153,9 +1395,10 @@ func main() {
389 		Logger:             logger,
390 		CloneURL:           template.URL(*cloneFlag),
391 		HomeURL:            template.URL(*homeFlag),
392-		Desc:               *descFlag,
393+		Desc:               descHTML,
394 		MaxCommits:         *maxCommitsFlag,
395 		HideTreeLastCommit: *hideTreeLastCommitFlag,
396+		Issues:             *issuesFlag,
397 		RootRelative:       *rootRelativeFlag,
398 		Formatter:          formatter,
399 	}
400@@ -1165,25 +1408,12 @@ func main() {
401 		bail(fmt.Errorf("you must provide --revs"))
402 	}
403 
404-	config.writeRepo()
405-	err = config.copyStatic("static")
406+	// Bundle CSS files before generating site
407+	cssFile, err := config.bundleCSS()
408 	bail(err)
409+	config.CSSFile = cssFile
410 
411-	styles := style(*theme)
412-	err = os.WriteFile(filepath.Join(out, "vars.css"), []byte(styles), 0644)
413-	if err != nil {
414-		panic(err)
415-	}
416-
417-	fp := filepath.Join(out, "syntax.css")
418-	w, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
419-	if err != nil {
420-		bail(err)
421-	}
422-	err = formatter.WriteCSS(w, theme)
423-	if err != nil {
424-		bail(err)
425-	}
426+	config.writeRepo()
427 
428 	url := filepath.Join("/", "index.html")
429 	config.Logger.Info("root url", "url", url)
M static/pgit.css
+312, -1
  1@@ -9,7 +9,7 @@
  2   --border: #6272a4;
  3   --link-color: #8be9fd;
  4   --hover: #ff79c6;
  5-  --visited: #bd93f9;
  6+  --visited: #8be9fd;
  7   --white: #f2f2f2;
  8   --white-light: #f2f2f2;
  9   --white-dark: #e8e8e8;
 10@@ -19,6 +19,8 @@
 11   --pre: #252525;
 12   --blockquote: #bd93f9;
 13   --blockquote-bg: #353548;
 14+  --text-red: #ff5555;
 15+  --text-green: #50fa7b;
 16 }
 17 
 18 /* ==== BASE RESET ==== */
 19@@ -316,6 +318,11 @@ sup {
 20   margin-top: var(--grid-height);
 21 }
 22 
 23+.site-header__desc > p {
 24+  padding: 0;
 25+  margin: 0;
 26+}
 27+
 28 /* Navigation */
 29 .nav-menu {
 30   display: flex;
 31@@ -1029,6 +1036,15 @@ sup {
 32   .last-commit-bar__info {
 33     white-space: normal;
 34   }
 35+
 36+  /* Issue detail title row becomes column on mobile */
 37+  .issue-detail__title-row {
 38+    flex-direction: column;
 39+  }
 40+
 41+  .issue-detail__status {
 42+    align-self: flex-start;
 43+  }
 44 }
 45 
 46 @media only screen and (max-width: 900px) {
 47@@ -1067,3 +1083,298 @@ sup {
 48 .file-list-summary__item {
 49   margin: var(--grid-height) 0;
 50 }
 51+
 52+/* ==== ISSUES PAGES ==== */
 53+
 54+/* Issues List Page */
 55+.issues-page {
 56+  margin-top: var(--line-height);
 57+}
 58+
 59+.issues-header {
 60+  border-bottom: 1px solid var(--border);
 61+}
 62+
 63+.issues-header h1 {
 64+  font-size: 1rem;
 65+  font-weight: bold;
 66+  text-transform: uppercase;
 67+  margin-bottom: var(--grid-height);
 68+}
 69+
 70+/* Issues Navigation - matches top-level nav-menu */
 71+.issues-nav {
 72+  display: flex;
 73+  align-items: center;
 74+  gap: 1.5rem;
 75+  margin: 1rem 0;
 76+  flex-wrap: wrap;
 77+}
 78+
 79+.issues-nav a,
 80+.issues-nav span.active {
 81+  color: var(--grey-light);
 82+  font-weight: 600;
 83+  text-transform: uppercase;
 84+  font-size: 0.9rem;
 85+  text-decoration: none;
 86+  padding-bottom: 0.4rem;
 87+  border-bottom: 2px solid transparent;
 88+  transition: all 0.2s ease;
 89+}
 90+
 91+.issues-nav a:hover,
 92+.issues-nav span.active {
 93+  color: var(--text-color);
 94+  border-bottom: 2px solid var(--link-color);
 95+}
 96+
 97+/* Label Selector Dropdown */
 98+.issues-nav__label-selector {
 99+  margin-left: auto;
100+  position: relative;
101+}
102+
103+.issues-nav__label-selector summary {
104+  color: var(--grey-light);
105+  font-weight: 600;
106+  text-transform: uppercase;
107+  font-size: 0.9rem;
108+  padding-bottom: 0.4rem;
109+  border-bottom: 2px solid transparent;
110+  cursor: pointer;
111+  list-style: none;
112+  display: flex;
113+  align-items: center;
114+  gap: 0.25rem;
115+}
116+
117+.issues-nav__label-selector summary:hover {
118+  color: var(--text-color);
119+}
120+
121+.issues-nav__label-selector summary::-webkit-details-marker {
122+  display: none;
123+}
124+
125+.issues-nav__label-selector summary::after {
126+  content: "▼";
127+  font-size: 0.7rem;
128+}
129+
130+.issues-nav__label-selector[open] summary::after {
131+  content: "▲";
132+}
133+
134+.label-selector__dropdown {
135+  position: absolute;
136+  top: 100%;
137+  right: 0;
138+  min-width: 150px;
139+  background-color: var(--bg-color);
140+  border: 1px solid var(--border);
141+  border-radius: 4px;
142+  padding: 0.5rem 0;
143+  margin-top: 0.25rem;
144+  z-index: 100;
145+  max-height: 300px;
146+  overflow-y: auto;
147+}
148+
149+.label-selector__dropdown a {
150+  display: block;
151+  padding: 0.25rem 1rem;
152+  color: var(--text-color);
153+  font-weight: 600;
154+  text-transform: uppercase;
155+  font-size: 0.9rem;
156+  text-decoration: none;
157+  border-bottom: none;
158+  padding-bottom: 0.25rem;
159+}
160+
161+.label-selector__dropdown a:hover {
162+  background-color: var(--pre);
163+  color: var(--link-color);
164+  text-decoration: none;
165+}
166+
167+.issues-list {
168+  display: flex;
169+  flex-direction: column;
170+}
171+
172+.issue-item {
173+  display: flex;
174+  justify-content: space-between;
175+  align-items: flex-start;
176+  padding: 0.25rem 0.5rem;
177+  border-bottom: 1px solid var(--grey);
178+  transition: background-color 0.2s ease;
179+  margin: 0 -0.5rem;
180+}
181+
182+.issue-item:hover {
183+  background-color: var(--pre);
184+}
185+
186+.issue-item__main {
187+  flex: 1;
188+  margin-right: 1rem;
189+  display: flex;
190+  flex-direction: column;
191+  gap: 0.1rem;
192+}
193+
194+.issue-item__title {
195+  font-size: 1rem;
196+  font-weight: bold;
197+  text-decoration: none;
198+  display: block;
199+  line-height: var(--line-height);
200+}
201+
202+.issue-item__title:hover {
203+  text-decoration: underline;
204+}
205+
206+.issue-meta {
207+  color: var(--grey-light);
208+  font-family: monospace;
209+  font-size: 0.8rem;
210+  line-height: var(--line-height);
211+}
212+
213+.issue-item__stats {
214+  display: flex;
215+  flex-direction: column;
216+  align-items: flex-end;
217+  text-align: right;
218+  font-size: 0.8rem;
219+  color: var(--grey-light);
220+  line-height: var(--line-height);
221+}
222+
223+.issue-date {
224+  white-space: nowrap;
225+}
226+
227+.issue-labels-text {
228+  color: var(--grey-light);
229+}
230+
231+.no-issues {
232+  color: var(--grey-light);
233+  font-style: italic;
234+  padding: var(--line-height) 0;
235+}
236+
237+/* Issue Detail Page */
238+.issue-detail {}
239+
240+.issue-detail__header {
241+  margin-bottom: var(--line-height);
242+  padding-bottom: var(--grid-height);
243+  border-bottom: 1px solid var(--border);
244+}
245+
246+.issue-detail__title-row {
247+  display: flex;
248+  justify-content: space-between;
249+  align-items: flex-start;
250+  margin-bottom: var(--grid-height);
251+}
252+
253+.issue-detail__title {
254+  font-size: 1rem;
255+  font-weight: bold;
256+  line-height: var(--line-height);
257+  margin: 0 1rem 0 0;
258+  flex: 1;
259+  word-wrap: break-word;
260+  overflow-wrap: break-word;
261+}
262+
263+.issue-detail__status {
264+  font-size: 0.9rem;
265+  font-weight: 600;
266+  text-transform: uppercase;
267+  white-space: nowrap;
268+  flex-shrink: 0;
269+}
270+
271+.issue-detail__status--open {
272+  color: var(--text-red);
273+}
274+
275+.issue-detail__status--closed {
276+  color: var(--text-green);
277+}
278+
279+.issue-detail__meta {
280+  display: flex;
281+  gap: 0.5rem;
282+  align-items: center;
283+  font-size: 0.8rem;
284+  color: var(--grey-light);
285+  flex-wrap: wrap;
286+}
287+
288+.issue-detail__labels {
289+  font-size: 0.9rem;
290+}
291+
292+.issue-detail__labels>span:first-child {
293+  color: var(--grey-light);
294+  margin-right: 0.5rem;
295+}
296+
297+.issue-label:not(:last-child)::after {
298+  content: "•";
299+  margin: 0 0.4rem;
300+  color: var(--grey-light);
301+}
302+
303+.issue-description {
304+  margin-bottom: var(--line-height);
305+}
306+
307+.issue-comment {
308+  padding: var(--grid-height);
309+  border: 1px solid var(--border);
310+  border-radius: 4px;
311+  margin-bottom: var(--grid-height);
312+  background: var(--pre);
313+}
314+
315+.issue-comment__header {
316+  display: flex;
317+  justify-content: space-between;
318+  align-items: center;
319+  margin-bottom: var(--grid-height);
320+  padding-bottom: var(--grid-height);
321+  border-bottom: 1px solid var(--grey);
322+  font-size: 0.9rem;
323+}
324+
325+.issue-comment__author {
326+  font-weight: bold;
327+  color: var(--text-color);
328+}
329+
330+.issue-comment__date {
331+  color: var(--grey-light);
332+  font-size: 0.8rem;
333+}
334+
335+.issue-comment__body {
336+  line-height: var(--line-height);
337+}
338+
339+.issue-comments h3 {
340+  font-size: 1rem;
341+  font-weight: bold;
342+  text-transform: uppercase;
343+  margin: var(--line-height) 0 var(--grid-height);
344+  color: var(--white-dark);
345+}
M testdata/clocks/bugs-edit
+1, -1
1@@ -1 +1 @@
2-8
3+11
A testdata/git-bug/cache/bugs
+0, -0
A testdata/git-bug/cache/identities
+0, -0
A testdata/git-bug/clocks/bugs-create
+1, -0
1@@ -0,0 +1 @@
2+3
A testdata/git-bug/clocks/bugs-edit
+1, -0
1@@ -0,0 +1 @@
2+8
A testdata/git-bug/indexes/bugs/index_meta.json
+1, -0
1@@ -0,0 +1 @@
2+{"storage":"boltdb","index_type":"upside_down"}
A testdata/git-bug/indexes/bugs/store
+0, -0
A testdata/git-bug/indexes/identities/index_meta.json
+1, -0
1@@ -0,0 +1 @@
2+{"storage":"boltdb","index_type":"upside_down"}
A testdata/git-bug/indexes/identities/store
+0, -0
A testdata/objects/08/c02df5b0671205265f93a9904acce34eaa2f97
+0, -0
A testdata/objects/0c/00144bd2dd8259a944a183cfa06b15137c265d
+0, -0
A testdata/objects/1c/ace9abd3ad97a683b33778e370a1ff66049582
+0, -0
A testdata/objects/30/eea86454a6363629d3a3c15b259a885f1d8986
+0, -0
A testdata/objects/43/146bcff2558b064440efbc4d67d230ec5f1582
+0, -0
A testdata/objects/53/8d4ac9d583525e7eae6039db47d0f7532c2ae0
+0, -0
A testdata/objects/62/216754e378ef45e78db0213a1b1957996747f5
+0, -0
A testdata/objects/73/d5426c99b8d6eecc40bb556fc976b99df6bdfb
+0, -0
A testdata/objects/8e/7324d3bbdf7ac7d833cd1838434d48d0157d7f
+0, -0
M testdata/refs/bugs/872a52d8a57756003bb29a33a1527824a1058f7e1fbb764b4eb24f9fad408c75
+1, -1
1@@ -1 +1 @@
2-3a2e74a39caacee6610381c6de17a3a8ebada53c
3+62216754e378ef45e78db0213a1b1957996747f5