Documentation
¶
Overview ¶
Package feature provides analysis features for tower visualizations.
Overview ¶
Beyond basic rendering, Stacktower can analyze dependency graphs to surface insights about the software ecosystem. This package provides:
- Nebraska ranking: Identify critical maintainers (inspired by XKCD #2347)
- Brittle detection: Flag potentially unmaintained dependencies
Nebraska Ranking ¶
The RankNebraska function identifies the maintainers whose packages are most critical to a project's foundation—the "Nebraska guys" from the famous XKCD comic about that one random person maintaining infrastructure everyone depends on.
The scoring algorithm considers:
Depth: Packages deeper in the tower (more dependencies above) score higher. These foundational packages have larger "blast radius" if abandoned.
Role weight: Owners get 3x weight, leads get 1.5x, regular maintainers get 1x. This reflects the difference in bus factor impact.
Shared credit: When multiple maintainers share a package, the depth score is divided among them, reflecting distributed responsibility.
Usage:
rankings := feature.RankNebraska(g, 10) // Top 10 maintainers
for _, r := range rankings {
fmt.Printf("%s (score: %.1f)\n", r.Maintainer, r.Score)
for _, pkg := range r.Packages {
fmt.Printf(" - %s (%s, depth %d)\n", pkg.Package, pkg.Role, pkg.Depth)
}
}
Roles ¶
The package recognizes three maintainer roles:
- RoleOwner: The repository owner (highest weight)
- RoleLead: First listed maintainer who isn't owner (medium weight)
- RoleMaintainer: All other maintainers (standard weight)
Role information comes from node metadata (repo_owner, repo_maintainers) populated by the GitHub enrichment during dependency parsing.
Brittle Detection ¶
The IsBrittle function identifies packages that may be at risk:
- Archived repositories
- No commits in over 2 years
- Single maintainer with no recent activity
Brittle packages are highlighted in visualizations to draw attention to potential maintenance risks in the dependency tree.
Visualization Integration ¶
These features integrate with the rendering pipeline:
rankings := feature.RankNebraska(g, 5)
svg := sink.RenderSVG(layout,
sink.WithNebraska(rankings), // Adds ranking panel
sink.WithPopups(), // Shows brittle warnings in popups
)
The SVG renderer adds interactive highlighting—hovering over a maintainer in the Nebraska panel highlights all their packages in the tower.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CountMaintainers ¶
func IsBrittle ¶
IsBrittle returns true if a node represents a package that is potentially unmaintained or risky to depend on. It checks for archived repositories, long periods of inactivity, and low maintainer counts.
Example ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/dag"
"github.com/matzehuels/stacktower/pkg/render/tower/feature"
)
func main() {
// Check if a package is potentially unmaintained
node := &dag.Node{
ID: "old-package",
Row: 1,
Meta: dag.Metadata{
"repo_archived": true,
"repo_last_commit": "2020-01-01",
},
}
if feature.IsBrittle(node) {
fmt.Println("Package is brittle (archived or unmaintained)")
}
}
Output: Package is brittle (archived or unmaintained)
Example (Healthy) ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/dag"
"github.com/matzehuels/stacktower/pkg/render/tower/feature"
)
func main() {
// Well-maintained package with recent activity
node := &dag.Node{
ID: "active-package",
Row: 1,
Meta: dag.Metadata{
"repo_archived": false,
"repo_last_commit": "2024-12-01", // Recent
"repo_maintainers": []string{"dev1", "dev2", "dev3"},
"repo_stars": 1000,
},
}
if !feature.IsBrittle(node) {
fmt.Println("Package appears healthy")
}
}
Output: Package appears healthy
Example (StaleWithFewMaintainers) ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/dag"
"github.com/matzehuels/stacktower/pkg/render/tower/feature"
)
func main() {
// Package with no recent activity and single maintainer
node := &dag.Node{
ID: "stale-package",
Row: 1,
Meta: dag.Metadata{
"repo_archived": false,
"repo_last_commit": "2022-01-01", // Over 1 year old
"repo_maintainers": []string{"solo-dev"},
"repo_stars": 50, // Low star count
},
}
if feature.IsBrittle(node) {
fmt.Println("Package may be at risk")
}
}
Output: Package may be at risk
Types ¶
type NebraskaRanking ¶
type NebraskaRanking struct {
Maintainer string
Score float64
Packages []PackageRole
}
func RankNebraska ¶
func RankNebraska(g *dag.DAG, topN int) []NebraskaRanking
RankNebraska identifies the most influential maintainers in the dependency graph using the Nebraska ranking algorithm. Maintainers are scored based on the "depth" of their packages in the tower (i.e., how many things depend on them).
Example ¶
package main
import (
"fmt"
"github.com/matzehuels/stacktower/pkg/dag"
"github.com/matzehuels/stacktower/pkg/render/tower/feature"
)
func main() {
// Create a dependency graph with GitHub metadata
g := dag.New(nil)
// Root application (row 0)
_ = g.AddNode(dag.Node{ID: "myapp", Row: 0})
// Mid-level dependencies (row 1)
_ = g.AddNode(dag.Node{
ID: "framework",
Row: 1,
Meta: dag.Metadata{
"repo_owner": "alice",
"repo_maintainers": []string{"alice", "bob"},
"repo_url": "https://github.com/alice/framework",
},
})
// Foundation packages (row 2) - deeper = more critical
_ = g.AddNode(dag.Node{
ID: "http-client",
Row: 2,
Meta: dag.Metadata{
"repo_owner": "charlie",
"repo_maintainers": []string{"charlie"},
"repo_url": "https://github.com/charlie/http-client",
},
})
_ = g.AddNode(dag.Node{
ID: "json-parser",
Row: 2,
Meta: dag.Metadata{
"repo_owner": "alice",
"repo_maintainers": []string{"alice", "dave"},
"repo_url": "https://github.com/alice/json-parser",
},
})
// Add edges
_ = g.AddEdge(dag.Edge{From: "myapp", To: "framework"})
_ = g.AddEdge(dag.Edge{From: "framework", To: "http-client"})
_ = g.AddEdge(dag.Edge{From: "framework", To: "json-parser"})
// Rank the top 3 maintainers
rankings := feature.RankNebraska(g, 3)
for _, r := range rankings {
fmt.Printf("%s (score: %.1f)\n", r.Maintainer, r.Score)
for _, pkg := range r.Packages {
fmt.Printf(" - %s (%s, depth %d)\n", pkg.Package, pkg.Role, pkg.Depth)
}
}
}
Output: charlie (score: 6.0) - http-client (owner, depth 2) alice (score: 4.5) - json-parser (owner, depth 2) - framework (owner, depth 1) dave (score: 1.5) - json-parser (lead, depth 2)