@@ -111,6 +111,18 @@ import GitHubProfileCard from '@site/src/components/GitHubProfileCard';
111111 // Track displayed items to avoid duplicates across categories
112112 const displayedUrls = new Set ( ) ;
113113
114+ // Split bot activity into homebrew and other
115+ const homebrewActivity = botActivity . filter (
116+ ( activity ) =>
117+ activity . repo === "ublue-os/homebrew-tap" ||
118+ activity . repo === "ublue-os/homebrew-experimental-tap" ,
119+ ) ;
120+ const otherBotActivity = botActivity . filter (
121+ ( activity ) =>
122+ activity . repo !== "ublue-os/homebrew-tap" &&
123+ activity . repo !== "ublue-os/homebrew-experimental-tap" ,
124+ ) ;
125+
114126 // Generate area sections with planned vs opportunistic subsections
115127 const areaSections = areaCategories
116128 . map ( ( [ categoryName , categoryLabels ] ) => {
@@ -131,10 +143,27 @@ import GitHubProfileCard from '@site/src/components/GitHubProfileCard';
131143 return `` ;
132144 } )
133145 . join ( " " ) ;
134- return `### ${ cleanCategoryName } \n\n${ labelBadges } \n\n${ section } ` ;
146+
147+ // Add Homebrew updates as a subsection under Development category
148+ let fullSection = `### ${ cleanCategoryName } \n\n${ labelBadges } \n\n${ section } ` ;
149+ if (
150+ ( cleanCategoryName === "Development" ||
151+ categoryName . includes ( "Development" ) ) &&
152+ homebrewActivity . length > 0
153+ ) {
154+ const homebrewSection =
155+ generateHomebrewUpdatesSection ( homebrewActivity ) ;
156+ // Extract just the content (remove the ## heading and adjust remaining headings)
157+ const homebrewContent = homebrewSection
158+ . replace ( / ^ # # H o m e b r e w P a c k a g e U p d a t e s \n \n / , "" )
159+ . replace ( / ^ # # # / gm, "##### " ) ; // Convert ### to ##### for proper nesting
160+ fullSection += `\n\n#### Homebrew Package Updates\n\n${ homebrewContent } ` ;
161+ }
162+
163+ return fullSection ;
135164 } )
136165 . filter ( ( section ) => section )
137- . join ( "\n\n" ) ;
166+ . join ( "\n\n---\n\n " ) ;
138167
139168 // Generate kind sections with planned vs opportunistic subsections
140169 const kindSections = kindCategories
@@ -158,13 +187,15 @@ import GitHubProfileCard from '@site/src/components/GitHubProfileCard';
158187 . join ( " " ) ;
159188 return `### ${ cleanCategoryName } \n\n${ labelBadges } \n\n${ section } ` ;
160189 } )
161- . join ( "\n\n" ) ;
190+ . join ( "\n\n---\n\n " ) ;
162191
163192 // Combine with section headers
164193 const categorySections = `# Focus Area
165194
166195${ areaSections }
167196
197+ ---
198+
168199# Work by Type
169200
170201${ kindSections } `;
@@ -173,8 +204,8 @@ ${kindSections}`;
173204 const allItems = [ ...plannedItems , ...opportunisticItems ] ;
174205 const uncategorizedSection = generateUncategorizedSection ( allItems ) ;
175206
176- // Generate bot activity section
177- const botSection = generateBotActivitySection ( botActivity ) ;
207+ // Generate bot activity section (non-homebrew only, since homebrew is now under Development)
208+ const botSection = generateBotActivitySection ( otherBotActivity ) ;
178209
179210 // Generate contributors section
180211 const contributorsSection = generateContributorsSection (
@@ -407,7 +438,172 @@ function generateUncategorizedSection(items) {
407438 return `- ${ title } by [@\u200B${ author } ](https://github.com/${ author } ) in [#${ number } ](${ url } )` ;
408439 } ) ;
409440
410- return `## Other\n\n${ lines . join ( "\n" ) } ` ;
441+ return `---\n\n## Other\n\n${ lines . join ( "\n" ) } ` ;
442+ }
443+
444+ /**
445+ * Generate Homebrew package updates section
446+ * Hybrid format: Badge summary + compact table
447+ *
448+ * @param {Array } homebrewActivity - Array of {repo, bot, count, items}
449+ * @returns {string } Markdown section with badges and table
450+ */
451+ function generateHomebrewUpdatesSection ( homebrewActivity ) {
452+ // Calculate totals by tap
453+ let experimentalCount = 0 ;
454+ let productionCount = 0 ;
455+
456+ homebrewActivity . forEach ( ( activity ) => {
457+ if ( activity . repo === "ublue-os/homebrew-experimental-tap" ) {
458+ experimentalCount += activity . count ;
459+ } else if ( activity . repo === "ublue-os/homebrew-tap" ) {
460+ productionCount += activity . count ;
461+ }
462+ } ) ;
463+
464+ const totalCount = experimentalCount + productionCount ;
465+
466+ // Parse package updates from PR titles
467+ const packageUpdates = parseHomebrewPackageUpdates ( homebrewActivity ) ;
468+
469+ // Generate badges - production first to highlight it
470+ const badges = [ ] ;
471+ if ( productionCount > 0 ) {
472+ badges . push (
473+ `` ,
474+ ) ;
475+ }
476+ if ( experimentalCount > 0 ) {
477+ badges . push (
478+ `` ,
479+ ) ;
480+ }
481+
482+ // Generate summary table - production first
483+ const summaryTable = `| Tap | Updates |
484+ |-----|---------|
485+ | production-tap | ${ productionCount } |
486+ | experimental-tap | ${ experimentalCount } |` ;
487+
488+ // Generate detailed package tables - production first
489+ let detailSections = "" ;
490+
491+ // Production tap details
492+ if ( Object . keys ( packageUpdates . production ) . length > 0 ) {
493+ const prodTable = generatePackageTable (
494+ packageUpdates . production ,
495+ "production" ,
496+ ) ;
497+ detailSections += `<details>
498+ <summary>View all production-tap updates (${ productionCount } )</summary>
499+
500+ ${ prodTable }
501+
502+ </details>` ;
503+ }
504+
505+ // Experimental tap details
506+ if ( Object . keys ( packageUpdates . experimental ) . length > 0 ) {
507+ const expTable = generatePackageTable (
508+ packageUpdates . experimental ,
509+ "experimental" ,
510+ ) ;
511+ if ( detailSections ) detailSections += "\n\n" ;
512+ detailSections += `<details>
513+ <summary>View all experimental-tap updates (${ experimentalCount } )</summary>
514+
515+ ${ expTable }
516+
517+ </details>` ;
518+ }
519+
520+ return `## Homebrew Package Updates
521+
522+ ${ badges . join ( " " ) }
523+
524+ **${ totalCount } automated updates** this month via GitHub Actions. Homebrew tap version bumps ensure Bluefin users always have access to the latest stable releases.
525+
526+ ### Quick Summary
527+
528+ ${ summaryTable }
529+
530+ ${ detailSections } `;
531+ }
532+
533+ /**
534+ * Parse package names and versions from homebrew PR titles
535+ *
536+ * @param {Array } homebrewActivity - Array of {repo, bot, count, items}
537+ * @returns {Object } { experimental: {pkg: [versions]}, production: {pkg: [versions]} }
538+ */
539+ function parseHomebrewPackageUpdates ( homebrewActivity ) {
540+ const updates = {
541+ experimental : { } ,
542+ production : { } ,
543+ } ;
544+
545+ homebrewActivity . forEach ( ( activity ) => {
546+ const tapKey =
547+ activity . repo === "ublue-os/homebrew-experimental-tap"
548+ ? "experimental"
549+ : "production" ;
550+
551+ activity . items . forEach ( ( item ) => {
552+ const title = item . content . title ;
553+ // Pattern: "package-name version" or "package-name: version"
554+ // Example: "opencode-desktop-linux 1.1.18"
555+ const match = title . match ( / ^ ( [ a - z 0 - 9 - ] + ) \s + ( [ 0 - 9 ] + \. [ 0 - 9 . ] + ) / i) ;
556+
557+ if ( match ) {
558+ const pkgName = match [ 1 ] ;
559+ const version = match [ 2 ] ;
560+
561+ if ( ! updates [ tapKey ] [ pkgName ] ) {
562+ updates [ tapKey ] [ pkgName ] = [ ] ;
563+ }
564+
565+ updates [ tapKey ] [ pkgName ] . push ( {
566+ version,
567+ prNumber : item . content . number ,
568+ prUrl : item . content . url ,
569+ } ) ;
570+ }
571+ } ) ;
572+ } ) ;
573+
574+ return updates ;
575+ }
576+
577+ /**
578+ * Generate package update table for a tap
579+ *
580+ * @param {Object } packages - {pkgName: [{version, prNumber, prUrl}]}
581+ * @param {string } tapName - "experimental" or "main"
582+ * @returns {string } Markdown table
583+ */
584+ function generatePackageTable ( packages , tapName ) {
585+ // Sort packages by update count (descending)
586+ const sortedPackages = Object . entries ( packages ) . sort (
587+ ( a , b ) => b [ 1 ] . length - a [ 1 ] . length ,
588+ ) ;
589+
590+ const rows = sortedPackages . map ( ( [ pkgName , versions ] ) => {
591+ // Show version progression or single version
592+ const versionStr =
593+ versions . length > 1
594+ ? `${ versions [ 0 ] . version } → ${ versions [ versions . length - 1 ] . version } (${ versions . length } updates)`
595+ : versions [ 0 ] . version ;
596+
597+ // Link to first PR (or could link to all)
598+ const prLink = `[#${ versions [ 0 ] . prNumber } ](${ versions [ 0 ] . prUrl } )` ;
599+
600+ return `| ${ pkgName } | ${ versionStr } | ${ prLink } |` ;
601+ } ) ;
602+
603+ const header = `| Package | Versions | PR |
604+ |---------|----------|-----|` ;
605+
606+ return [ header , ...rows ] . join ( "\n" ) ;
411607}
412608
413609/**
@@ -424,7 +620,9 @@ function generateBotActivitySection(botActivity) {
424620 const table = generateBotActivityTable ( botActivity ) ;
425621 const details = generateBotDetailsList ( botActivity ) ;
426622
427- return `## 🤖 Bot Activity
623+ return `---
624+
625+ ## 🤖 Bot Activity
428626
429627${ table }
430628
0 commit comments