feat: fix-visibility (#34)

Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com>
diff --git a/cmd/aspect/build/BUILD.bazel b/cmd/aspect/build/BUILD.bazel
index 6176f45..2acd656 100644
--- a/cmd/aspect/build/BUILD.bazel
+++ b/cmd/aspect/build/BUILD.bazel
@@ -9,7 +9,9 @@
         "//pkg/aspect/build",
         "//pkg/aspect/build/bep",
         "//pkg/bazel",
+        "//pkg/hooks",
         "//pkg/ioutils",
+        "//pkg/plugins/fix_visibility",
         "@com_github_spf13_cobra//:cobra",
     ],
 )
diff --git a/cmd/aspect/build/build.go b/cmd/aspect/build/build.go
index 670654b..e7a88ca 100644
--- a/cmd/aspect/build/build.go
+++ b/cmd/aspect/build/build.go
@@ -12,13 +12,20 @@
 	"aspect.build/cli/pkg/aspect/build"
 	"aspect.build/cli/pkg/aspect/build/bep"
 	"aspect.build/cli/pkg/bazel"
+	"aspect.build/cli/pkg/hooks"
 	"aspect.build/cli/pkg/ioutils"
+	"aspect.build/cli/pkg/plugins/fix_visibility"
 )
 
 // NewDefaultBuildCmd creates a new build cobra command with the default
 // dependencies.
 func NewDefaultBuildCmd() *cobra.Command {
-	return NewBuildCmd(ioutils.DefaultStreams, bazel.New(), bep.NewBESBackend())
+	return NewBuildCmd(
+		ioutils.DefaultStreams,
+		bazel.New(),
+		bep.NewBESBackend(),
+		hooks.New(),
+	)
 }
 
 // NewBuildCmd creates a new build cobra command.
@@ -26,15 +33,24 @@
 	streams ioutils.Streams,
 	bzl bazel.Spawner,
 	besBackend bep.BESBackend,
+	hooks *hooks.Hooks,
 ) *cobra.Command {
-	b := build.New(streams, bzl, besBackend)
+	// TODO(f0rmiga): this should also be part of the plugin design, as
+	// registering BEP event subscribers should not be hardcoded here.
+	var fixVisibilityPlugin build.Plugin = fix_visibility.NewDefaultPlugin()
+	besBackend.RegisterSubscriber(fixVisibilityPlugin.BEPEventCallback)
+	hooks.RegisterPostBuild(fixVisibilityPlugin.PostBuildHook)
+
+	b := build.New(streams, bzl, besBackend, hooks)
 
 	cmd := &cobra.Command{
 		Use:   "build",
 		Short: "Builds the specified targets, using the options.",
 		Long: "Invokes bazel build on the specified targets. " +
 			"See 'bazel help target-syntax' for details and examples on how to specify targets to build.",
-		RunE: b.Run,
+		RunE: func(cmd *cobra.Command, args []string) (exitErr error) {
+			return b.Run(cmd.Context(), cmd, args)
+		},
 	}
 
 	return cmd
diff --git a/cmd/aspect/main.go b/cmd/aspect/main.go
index 0e62da7..b7c645c 100644
--- a/cmd/aspect/main.go
+++ b/cmd/aspect/main.go
@@ -7,6 +7,7 @@
 package main
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"os"
@@ -28,7 +29,7 @@
 	//         - tools/bazel file and put our bootstrap code in there
 	//
 	cmd := root.NewDefaultRootCmd()
-	if err := cmd.Execute(); err != nil {
+	if err := cmd.ExecuteContext(context.Background()); err != nil {
 		var exitErr *aspecterrors.ExitError
 		if errors.As(err, &exitErr) {
 			if exitErr.Err != nil {
diff --git a/go.bzl b/go.bzl
index 31c8773..55ea223 100644
--- a/go.bzl
+++ b/go.bzl
@@ -41,6 +41,13 @@
         version = "v0.0.0-20180808171621-7fddfc383310",
     )
     go_repository(
+        name = "com_github_bazelbuild_bazel_gazelle",
+        importpath = "github.com/bazelbuild/bazel-gazelle",
+        sum = "h1:Ks6YN+WkOv2lYWlvf7ksxUpLvrDbBHPBXXUrBFQ3BZM=",
+        version = "v0.23.0",
+    )
+
+    go_repository(
         name = "com_github_bazelbuild_bazelisk",
         build_naming_convention = "go_default_library",
         importpath = "github.com/bazelbuild/bazelisk",
@@ -48,6 +55,13 @@
         version = "v1.10.1",
     )
     go_repository(
+        name = "com_github_bazelbuild_buildtools",
+        importpath = "github.com/bazelbuild/buildtools",
+        sum = "h1:Et1IIXrXwhpDvR5wH9REPEZ0sUtzUoJSq19nfmBqzBY=",
+        version = "v0.0.0-20200718160251-b1667ff58f71",
+    )
+
+    go_repository(
         name = "com_github_bazelbuild_rules_go",
         importpath = "github.com/bazelbuild/rules_go",
         sum = "h1:fNtx0dJpG5ENGdMj3/GICoi/7z+ixB3IIW5rERTzOgM=",
@@ -72,6 +86,13 @@
         version = "v0.0.3-0.20200106085610-5cbc8cc4026c",
     )
     go_repository(
+        name = "com_github_bmatcuk_doublestar",
+        importpath = "github.com/bmatcuk/doublestar",
+        sum = "h1:oC24CykoSAB8zd7XgruHo33E0cHJf/WhQA/7BeXj+x0=",
+        version = "v1.2.2",
+    )
+
+    go_repository(
         name = "com_github_burntsushi_toml",
         importpath = "github.com/BurntSushi/toml",
         sum = "h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=",
@@ -96,6 +117,25 @@
         version = "v1.1.0",
     )
     go_repository(
+        name = "com_github_chzyer_logex",
+        importpath = "github.com/chzyer/logex",
+        sum = "h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=",
+        version = "v1.1.10",
+    )
+    go_repository(
+        name = "com_github_chzyer_readline",
+        importpath = "github.com/chzyer/readline",
+        sum = "h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=",
+        version = "v0.0.0-20180603132655-2972be24d48e",
+    )
+    go_repository(
+        name = "com_github_chzyer_test",
+        importpath = "github.com/chzyer/test",
+        sum = "h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=",
+        version = "v0.0.0-20180213035817-a1ea475d72b1",
+    )
+
+    go_repository(
         name = "com_github_client9_misspell",
         importpath = "github.com/client9/misspell",
         sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=",
@@ -462,6 +502,13 @@
         version = "v4.20.0+incompatible",
     )
     go_repository(
+        name = "com_github_juju_ansiterm",
+        importpath = "github.com/juju/ansiterm",
+        sum = "h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=",
+        version = "v0.0.0-20180109212912-720a0952cc2a",
+    )
+
+    go_repository(
         name = "com_github_julienschmidt_httprouter",
         importpath = "github.com/julienschmidt/httprouter",
         sum = "h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=",
@@ -516,12 +563,26 @@
         version = "v0.1.0",
     )
     go_repository(
+        name = "com_github_lunixbochs_vtclean",
+        importpath = "github.com/lunixbochs/vtclean",
+        sum = "h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=",
+        version = "v0.0.0-20180621232353-2d01aacdc34a",
+    )
+
+    go_repository(
         name = "com_github_magiconair_properties",
         importpath = "github.com/magiconair/properties",
         sum = "h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=",
         version = "v1.8.5",
     )
     go_repository(
+        name = "com_github_manifoldco_promptui",
+        importpath = "github.com/manifoldco/promptui",
+        sum = "h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=",
+        version = "v0.8.0",
+    )
+
+    go_repository(
         name = "com_github_mattn_go_colorable",
         importpath = "github.com/mattn/go-colorable",
         sum = "h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=",
@@ -1012,8 +1073,8 @@
     go_repository(
         name = "org_golang_x_mod",
         importpath = "golang.org/x/mod",
-        sum = "h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=",
-        version = "v0.3.0",
+        sum = "h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=",
+        version = "v0.4.1",
     )
     go_repository(
         name = "org_golang_x_net",
diff --git a/go.mod b/go.mod
index 95b6572..c669003 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,14 @@
 go 1.16
 
 require (
+	github.com/bazelbuild/bazel-gazelle v0.23.0
 	github.com/bazelbuild/bazelisk v1.10.1
 	github.com/bazelbuild/rules_go v0.28.0
 	github.com/fatih/color v1.12.0
 	github.com/golang/mock v1.3.1
 	github.com/golang/protobuf v1.5.2
 	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/manifoldco/promptui v0.8.0
 	github.com/mattn/go-isatty v0.0.13
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
diff --git a/go.sum b/go.sum
index 59cbf2e..3c4d288 100644
--- a/go.sum
+++ b/go.sum
@@ -19,16 +19,27 @@
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/bazelbuild/bazel-gazelle v0.23.0 h1:Ks6YN+WkOv2lYWlvf7ksxUpLvrDbBHPBXXUrBFQ3BZM=
+github.com/bazelbuild/bazel-gazelle v0.23.0/go.mod h1:3mHi4TYn0QxwdMKPJfj3FKhZxYgWm46DjWQQPOg20BY=
 github.com/bazelbuild/bazelisk v1.10.1 h1:2EWA2lRrt/k8B5ASt0mlTDQ+7mnzvKdF6ShNWLbk0o0=
 github.com/bazelbuild/bazelisk v1.10.1/go.mod h1:s3ZIQZj3l9iCk/03rBgjhYvqz0c5SPRvoQCZuz8Lw/4=
+github.com/bazelbuild/buildtools v0.0.0-20200718160251-b1667ff58f71/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
+github.com/bazelbuild/rules_go v0.0.0-20190719190356-6dae44dc5cab/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
 github.com/bazelbuild/rules_go v0.28.0 h1:fNtx0dJpG5ENGdMj3/GICoi/7z+ixB3IIW5rERTzOgM=
 github.com/bazelbuild/rules_go v0.28.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/bmatcuk/doublestar v1.2.2/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -85,6 +96,7 @@
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -130,6 +142,8 @@
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
+github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -141,13 +155,18 @@
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
+github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
+github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
 github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@@ -278,6 +297,7 @@
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -306,6 +326,7 @@
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -314,6 +335,7 @@
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/pkg/aspect/build/BUILD.bazel b/pkg/aspect/build/BUILD.bazel
index 15ff92c..fe73fd8 100644
--- a/pkg/aspect/build/BUILD.bazel
+++ b/pkg/aspect/build/BUILD.bazel
@@ -9,8 +9,10 @@
         "//pkg/aspect/build/bep",
         "//pkg/aspecterrors",
         "//pkg/bazel",
+        "//pkg/hooks",
         "//pkg/ioutils",
         "@com_github_spf13_cobra//:cobra",
+        "@go_googleapis//google/devtools/build/v1:build_go_proto",
     ],
 )
 
@@ -22,6 +24,7 @@
         "//pkg/aspect/build/bep/mock",
         "//pkg/aspecterrors",
         "//pkg/bazel/mock",
+        "//pkg/hooks",
         "//pkg/ioutils",
         "@com_github_golang_mock//gomock",
         "@com_github_onsi_gomega//:gomega",
diff --git a/pkg/aspect/build/bep/bes_backend.go b/pkg/aspect/build/bep/bes_backend.go
index 7acb21d..dfb37e1 100644
--- a/pkg/aspect/build/bep/bes_backend.go
+++ b/pkg/aspect/build/bep/bes_backend.go
@@ -190,10 +190,3 @@
 	next     *subscriberNode
 	callback CallbackFn
 }
-
-// Plugin is the interface that wraps the BEPEventsSubscriber used to verify
-// whether an Aspect plugin registers itself to receive the Build Event Protocol
-// events.
-type Plugin interface {
-	BEPEventsSubscriber() CallbackFn
-}
diff --git a/pkg/aspect/build/build.go b/pkg/aspect/build/build.go
index edb2297..65cb217 100644
--- a/pkg/aspect/build/build.go
+++ b/pkg/aspect/build/build.go
@@ -8,14 +8,17 @@
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"time"
 
 	"github.com/spf13/cobra"
+	buildv1 "google.golang.org/genproto/googleapis/devtools/build/v1"
 
 	"aspect.build/cli/pkg/aspect/build/bep"
 	"aspect.build/cli/pkg/aspecterrors"
 	"aspect.build/cli/pkg/bazel"
+	"aspect.build/cli/pkg/hooks"
 	"aspect.build/cli/pkg/ioutils"
 )
 
@@ -24,6 +27,7 @@
 	ioutils.Streams
 	bzl        bazel.Spawner
 	besBackend bep.BESBackend
+	hooks      *hooks.Hooks
 }
 
 // New creates a Build command.
@@ -31,19 +35,34 @@
 	streams ioutils.Streams,
 	bzl bazel.Spawner,
 	besBackend bep.BESBackend,
+	hooks *hooks.Hooks,
 ) *Build {
 	return &Build{
 		Streams:    streams,
 		bzl:        bzl,
 		besBackend: besBackend,
+		hooks:      hooks,
 	}
 }
 
 // Run runs the aspect build command, calling `bazel build` with a local Build
 // Event Protocol backend used by Aspect plugins to subscribe to build events.
-func (b *Build) Run(_ *cobra.Command, args []string) error {
-	// TODO: register the BEP subscribers here with:
-	// besBackend.RegisterSubscriber(plugin.BEPEventsSubscriber())
+func (b *Build) Run(ctx context.Context, cmd *cobra.Command, args []string) (exitErr error) {
+	// TODO(f0rmiga): this is a hook for the build command and should be discussed
+	// as part of the plugin design.
+	defer func(ctx context.Context) {
+		errs := b.hooks.ExecutePostBuild(ctx).Errors()
+		if len(errs) > 0 {
+			for _, err := range errs {
+				fmt.Fprintf(b.Streams.Stderr, "Error: failed to run build command: %v\n", err)
+			}
+			var err *aspecterrors.ExitError
+			if errors.As(exitErr, &err) {
+				err.ExitCode = 1
+			}
+		}
+	}(ctx)
+
 	if err := b.besBackend.Setup(); err != nil {
 		return fmt.Errorf("failed to run build command: %w", err)
 	}
@@ -55,22 +74,33 @@
 	defer b.besBackend.GracefulStop()
 
 	besBackendFlag := fmt.Sprintf("--bes_backend=grpc://%s", b.besBackend.Addr())
-	cmd := append([]string{"build", besBackendFlag}, args...)
-	if exitCode, err := b.bzl.Spawn(cmd); exitCode != 0 {
-		err = &aspecterrors.ExitError{
-			Err:      err, // err can be nil, so don't wrap it with the full context.
-			ExitCode: exitCode,
-		}
-		return err
-	}
+	exitCode, bazelErr := b.bzl.Spawn(append([]string{"build", besBackendFlag}, args...))
 
+	// Process the subscribers errors before the Bazel one.
 	subscriberErrors := b.besBackend.Errors()
 	if len(subscriberErrors) > 0 {
 		for _, err := range subscriberErrors {
 			fmt.Fprintf(b.Streams.Stderr, "Error: failed to run build command: %v\n", err)
 		}
-		return &aspecterrors.ExitError{ExitCode: 1}
+		exitCode = 1
+	}
+
+	if exitCode != 0 {
+		err := &aspecterrors.ExitError{ExitCode: exitCode}
+		if bazelErr != nil {
+			err.Err = bazelErr
+		}
+		return err
 	}
 
 	return nil
 }
+
+// Plugin defines only the methods for the build command.
+type Plugin interface {
+	// BEPEventsSubscriber is used to verify whether an Aspect plugin registers
+	// itself to receive the Build Event Protocol events.
+	BEPEventCallback(event *buildv1.BuildEvent) error
+	// TODO(f0rmiga): test the build hooks after implementing the plugin system.
+	PostBuildHook(ctx context.Context) error
+}
diff --git a/pkg/aspect/build/build_test.go b/pkg/aspect/build/build_test.go
index 3a916eb..1cd88c4 100644
--- a/pkg/aspect/build/build_test.go
+++ b/pkg/aspect/build/build_test.go
@@ -7,6 +7,7 @@
 package build_test
 
 import (
+	"context"
 	"fmt"
 	"strings"
 	"testing"
@@ -18,6 +19,7 @@
 	bep_mock "aspect.build/cli/pkg/aspect/build/bep/mock"
 	"aspect.build/cli/pkg/aspecterrors"
 	bazel_mock "aspect.build/cli/pkg/bazel/mock"
+	"aspect.build/cli/pkg/hooks"
 	"aspect.build/cli/pkg/ioutils"
 )
 
@@ -57,8 +59,10 @@
 			Errors().
 			Times(0)
 
-		b := build.New(streams, spawner, besBackend)
-		err := b.Run(nil, []string{"//..."})
+		hooks := hooks.New()
+		b := build.New(streams, spawner, besBackend, hooks)
+		ctx := context.Background()
+		err := b.Run(ctx, nil, []string{"//..."})
 
 		g.Expect(err).To(MatchError(fmt.Errorf("failed to run build command: %w", setupErr)))
 	})
@@ -99,8 +103,10 @@
 			Errors().
 			Times(0)
 
-		b := build.New(streams, spawner, besBackend)
-		err := b.Run(nil, []string{"//..."})
+		hooks := hooks.New()
+		b := build.New(streams, spawner, besBackend, hooks)
+		ctx := context.Background()
+		err := b.Run(ctx, nil, []string{"//..."})
 
 		g.Expect(err).To(MatchError(fmt.Errorf("failed to run build command: %w", serveWaitErr)))
 	})
@@ -143,10 +149,12 @@
 		besBackend.
 			EXPECT().
 			Errors().
-			Times(0)
+			Times(1)
 
-		b := build.New(streams, spawner, besBackend)
-		err := b.Run(nil, []string{"//..."})
+		hooks := hooks.New()
+		b := build.New(streams, spawner, besBackend, hooks)
+		ctx := context.Background()
+		err := b.Run(ctx, nil, []string{"//..."})
 
 		g.Expect(err).To(MatchError(expectErr))
 	})
@@ -192,8 +200,10 @@
 			}).
 			Times(1)
 
-		b := build.New(streams, spawner, besBackend)
-		err := b.Run(nil, []string{"//..."})
+		hooks := hooks.New()
+		b := build.New(streams, spawner, besBackend, hooks)
+		ctx := context.Background()
+		err := b.Run(ctx, nil, []string{"//..."})
 
 		g.Expect(err).To(MatchError(&aspecterrors.ExitError{ExitCode: 1}))
 		g.Expect(stderr.String()).To(Equal("Error: failed to run build command: error 1\nError: failed to run build command: error 2\n"))
@@ -236,8 +246,10 @@
 			Return([]error{}).
 			Times(1)
 
-		b := build.New(streams, spawner, besBackend)
-		err := b.Run(nil, []string{"//..."})
+		hooks := hooks.New()
+		b := build.New(streams, spawner, besBackend, hooks)
+		ctx := context.Background()
+		err := b.Run(ctx, nil, []string{"//..."})
 
 		g.Expect(err).To(BeNil())
 	})
diff --git a/pkg/hooks/BUILD.bazel b/pkg/hooks/BUILD.bazel
new file mode 100644
index 0000000..182b928
--- /dev/null
+++ b/pkg/hooks/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "hooks",
+    srcs = ["hooks.go"],
+    importpath = "aspect.build/cli/pkg/hooks",
+    visibility = ["//visibility:public"],
+    deps = ["//pkg/aspecterrors"],
+)
diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go
new file mode 100644
index 0000000..dc7215d
--- /dev/null
+++ b/pkg/hooks/hooks.go
@@ -0,0 +1,61 @@
+/*
+Copyright © 2021 Aspect Build Systems Inc
+
+Not licensed for re-use.
+*/
+
+package hooks
+
+import (
+	"context"
+
+	"aspect.build/cli/pkg/aspecterrors"
+)
+
+type Hooks struct {
+	postBuild *hookList
+}
+
+func New() *Hooks {
+	return &Hooks{
+		postBuild: &hookList{},
+	}
+}
+
+func (hooks *Hooks) RegisterPostBuild(fn PostBuildFn) {
+	hooks.postBuild.insert(fn)
+}
+
+func (hooks *Hooks) ExecutePostBuild(ctx context.Context) *aspecterrors.ErrorList {
+	errors := &aspecterrors.ErrorList{}
+	node := hooks.postBuild.head
+	for node != nil {
+		if err := node.fn.(PostBuildFn)(ctx); err != nil {
+			errors.Insert(err)
+		}
+		node = node.next
+	}
+	return errors
+}
+
+type PostBuildFn func(ctx context.Context) error
+
+type hookList struct {
+	head *hookNode
+	tail *hookNode
+}
+
+func (l *hookList) insert(fn interface{}) {
+	node := &hookNode{fn: fn}
+	if l.head == nil {
+		l.head = node
+	} else {
+		l.tail.next = node
+	}
+	l.tail = node
+}
+
+type hookNode struct {
+	next *hookNode
+	fn   interface{}
+}
diff --git a/pkg/plugins/fix_visibility/BUILD.bazel b/pkg/plugins/fix_visibility/BUILD.bazel
new file mode 100644
index 0000000..61618ef
--- /dev/null
+++ b/pkg/plugins/fix_visibility/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "fix_visibility",
+    srcs = ["plugin.go"],
+    importpath = "aspect.build/cli/pkg/plugins/fix_visibility",
+    visibility = ["//cmd/aspect/build:__pkg__"],
+    deps = [
+        "@bazel_gazelle//label:go_default_library",
+        "@com_github_manifoldco_promptui//:promptui",
+        "@com_github_mattn_go_isatty//:go-isatty",
+        "@go_googleapis//google/devtools/build/v1:build_go_proto",
+    ],
+)
diff --git a/pkg/plugins/fix_visibility/plugin.go b/pkg/plugins/fix_visibility/plugin.go
new file mode 100644
index 0000000..f9bcc35
--- /dev/null
+++ b/pkg/plugins/fix_visibility/plugin.go
@@ -0,0 +1,180 @@
+/*
+Copyright © 2021 Aspect Build Systems Inc
+
+Not licensed for re-use.
+*/
+
+package fix_visibility
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"regexp"
+	"time"
+
+	"github.com/bazelbuild/bazel-gazelle/label"
+	"github.com/manifoldco/promptui"
+	isatty "github.com/mattn/go-isatty"
+	buildv1 "google.golang.org/genproto/googleapis/devtools/build/v1"
+)
+
+type FixVisibilityPlugin struct {
+	stdout            io.Writer
+	buildozer         Runner
+	isInteractiveMode bool
+	applyFixPrompt    promptui.Prompt
+	targetsToFix      *fixOrderedSet
+}
+
+func NewDefaultPlugin() *FixVisibilityPlugin {
+	isInteractiveMode := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
+	applyFixPrompt := promptui.Prompt{
+		Label:     "Would you like to apply the visibility fixes",
+		IsConfirm: true,
+	}
+	return NewPlugin(os.Stdout, &buildozer{}, isInteractiveMode, applyFixPrompt)
+}
+
+func NewPlugin(
+	stdout io.Writer,
+	buildozer Runner,
+	isInteractiveMode bool,
+	applyFixPrompt promptui.Prompt,
+) *FixVisibilityPlugin {
+	return &FixVisibilityPlugin{
+		stdout:            stdout,
+		buildozer:         buildozer,
+		isInteractiveMode: isInteractiveMode,
+		targetsToFix:      &fixOrderedSet{nodes: make(map[fixNode]struct{})},
+		applyFixPrompt:    applyFixPrompt,
+	}
+}
+
+var visibilityIssueRegex = regexp.MustCompile(`.*target '(.*)' is not visible from target '(.*)'.*`)
+var visibilityIssueSubstring = []byte("is not visible from target")
+
+func (plugin *FixVisibilityPlugin) BEPEventCallback(event *buildv1.BuildEvent) error {
+	bazelEvent := event.GetBazelEvent()
+	if bazelEvent != nil {
+		if !bytes.Contains(bazelEvent.Value, visibilityIssueSubstring) {
+			return nil
+		}
+		matches := visibilityIssueRegex.FindSubmatch(bazelEvent.Value)
+		if len(matches) != 3 {
+			return nil
+		}
+		plugin.targetsToFix.insert(string(matches[1]), string(matches[2]))
+	}
+	return nil
+}
+
+const removePrivateVisibilityBuildozerCommand = "remove visibility //visibility:private"
+
+func (plugin *FixVisibilityPlugin) PostBuildHook(ctx context.Context) error {
+	if plugin.targetsToFix.size == 0 {
+		return nil
+	}
+
+	// TODO(f0rmiga): make the timeout configurable via the plugin configuration
+	// file.
+	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+	defer cancel()
+
+	// TODO(f0rmiga): check if buildozer is installed, otherwise return an error.
+	for node := plugin.targetsToFix.head; node != nil; node = node.next {
+		fromLabel, err := label.Parse(node.from)
+		if err != nil {
+			return fmt.Errorf("failed to fix visibility: %w", err)
+		}
+		fromLabel.Name = "__pkg__"
+
+		hasPrivateVisibility, err := plugin.hasPrivateVisibility(ctx, node.toFix)
+		if err != nil {
+			return fmt.Errorf("failed to fix visibility: %w", err)
+		}
+
+		var applyFix bool
+		if plugin.isInteractiveMode {
+			_, err := plugin.applyFixPrompt.Run()
+			applyFix = err == nil
+		}
+
+		addVisibilityBuildozerCommand := fmt.Sprintf("add visibility %s", fromLabel)
+		if applyFix {
+			if _, err := plugin.buildozer.Run(ctx, addVisibilityBuildozerCommand, node.toFix); err != nil {
+				return fmt.Errorf("failed to fix visibility: %w", err)
+			}
+			if hasPrivateVisibility {
+				if _, err := plugin.buildozer.Run(ctx, removePrivateVisibilityBuildozerCommand, node.toFix); err != nil {
+					return fmt.Errorf("failed to fix visibility: %w", err)
+				}
+			}
+		} else {
+			fmt.Fprintf(plugin.stdout, "To fix the visibility errors, run:\n")
+			fmt.Fprintf(plugin.stdout, "buildozer '%s' %s\n", addVisibilityBuildozerCommand, node.toFix)
+			if hasPrivateVisibility {
+				fmt.Fprintf(plugin.stdout, "buildozer '%s' %s\n", removePrivateVisibilityBuildozerCommand, node.toFix)
+			}
+		}
+	}
+
+	return nil
+}
+
+func (plugin *FixVisibilityPlugin) hasPrivateVisibility(ctx context.Context, toFix string) (bool, error) {
+	visibility, err := plugin.buildozer.Run(ctx, "print visibility", toFix)
+	if err != nil {
+		return false, fmt.Errorf("failed to check if target has private visibility: %w", err)
+	}
+	return bytes.Contains(visibility, []byte("//visibility:private")), nil
+}
+
+type fixOrderedSet struct {
+	head  *fixNode
+	tail  *fixNode
+	nodes map[fixNode]struct{}
+	size  int
+}
+
+func (s *fixOrderedSet) insert(toFix, from string) {
+	node := fixNode{
+		toFix: toFix,
+		from:  from,
+	}
+	if _, exists := s.nodes[node]; !exists {
+		s.nodes[node] = struct{}{}
+		if s.head == nil {
+			s.head = &node
+		} else {
+			s.tail.next = &node
+		}
+		s.tail = &node
+		s.size++
+	}
+}
+
+type fixNode struct {
+	next  *fixNode
+	toFix string
+	from  string
+}
+
+type Runner interface {
+	Run(ctx context.Context, args ...string) ([]byte, error)
+}
+
+type buildozer struct{}
+
+func (b *buildozer) Run(ctx context.Context, args ...string) ([]byte, error) {
+	cmd := exec.CommandContext(ctx, "buildozer", args...)
+	var stdout bytes.Buffer
+	cmd.Stdout = &stdout
+	if err := cmd.Run(); err != nil {
+		return stdout.Bytes(), fmt.Errorf("failed to run buildozer: %w", err)
+	}
+	return stdout.Bytes(), nil
+}