diff --git a/.github/workflows/validate_changed_files.sh b/.github/workflows/validate_changed_files.sh
deleted file mode 100755
index d6edd6fa6..000000000
--- a/.github/workflows/validate_changed_files.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env bash
-
-set -o errexit -o nounset -o pipefail
-
-INVALID_FILE_PATHS=('pkg/plugin/sdk')        # Array of filepaths that are not allowed
-VALID_FILE_PATHS=('pkg/plugin/sdk/v1alpha3') # Array of filepaths that are allowed
-
-# by default the only local branch will be pull/PR#/merge
-# fetch only the latest commit from the 2 branches in question to avoid fetching the entire repo which could be costly
-
-git fetch --depth 1 origin "${GITHUB_BASE_REF}"
-git fetch --depth 1 origin "${GITHUB_REF}"
-
-git diff --name-only "origin/${GITHUB_BASE_REF}..${GITHUB_SHA}" | while read -r file; do
-
-    # check if filepath matches a valid path. If so move to the next change
-    for valid_path in "${VALID_FILE_PATHS[@]}"; do
-        if [[ "${file}" == "${valid_path}"* ]]; then
-            continue 2
-        fi
-    done
-
-    # check if filepath matches an invalid path
-    for invalid_path in "${INVALID_FILE_PATHS[@]}"; do
-        if [[ "${file}" == "${invalid_path}"* ]]; then
-            echo "Branch contains changes to versioned SDK files."
-            echo "Add the 'allow sdk change' tag to the PR and re-trigger CI to bypass this check."
-            exit 1
-        fi
-    done
-done
diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml
deleted file mode 100644
index 274c5522d..000000000
--- a/.github/workflows/validation.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Validation
-on:
-  push:
-    branches: [main]
-  pull_request:
-    branches: [main]
-  # Allows you to run this workflow manually from the Actions tab.
-  workflow_dispatch:
-concurrency:
-  # Cancel previous actions from the same PR or branch except 'main' branch.
-  # See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info.
-  group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}}
-  cancel-in-progress: ${{ github.ref_name != 'main' }}
-jobs:
-  validation:
-    name: Validation
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - name: Validate changed files
-        if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'allow sdk change')
-        run: ./.github/workflows/validate_changed_files.sh
-      - uses: bazelbuild/setup-bazelisk@v1
-      - name: Go cache
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cache/go-build
-            ~/go/pkg/mod
-          key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }}
-          restore-keys: ${{ runner.os }}-go-
-      - name: Tidy up repository
-        run: ./.github/workflows/tidy_up_repository.sh
diff --git a/WORKSPACE b/WORKSPACE
index cfd61d03e..cdc68022d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -220,6 +220,7 @@ http_archive(
         "//:patches/bazelbuild_bazel-gazelle_aspect-cli.patch",
         "//:patches/bazelbuild_bazel-gazelle_aspect-walk-subdir.patch",
         "//:patches/bazelbuild_bazel-gazelle_aspect-gitignore.patch",
+        "//:patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch",
     ],
     sha256 = "872f1532567cdc53dc8e9f4681cd45021cd6787e2bde8a022bcec24a5867ce4c",
     # Ensure this version always matches the go.mod version.
diff --git a/cmd/aspect/root/root.go b/cmd/aspect/root/root.go
index 2fb40be6d..a75274cc9 100644
--- a/cmd/aspect/root/root.go
+++ b/cmd/aspect/root/root.go
@@ -102,7 +102,7 @@ func HandleVersionFlags(streams ioutils.Streams, args []string, bzl bazel.Bazel)
 		if err != nil {
 			aspecterrors.HandleError(err)
 		}
-		fmt.Fprintf(streams.Stdout, version)
+		fmt.Fprint(streams.Stdout, version)
 		os.Exit(0)
 	}
 }
diff --git a/gazelle/common/git/gitignore.go b/gazelle/common/git/gitignore.go
index eec2f1ddb..a5d2055fd 100644
--- a/gazelle/common/git/gitignore.go
+++ b/gazelle/common/git/gitignore.go
@@ -2,7 +2,9 @@ package git
 
 import (
 	"bufio"
+	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path"
 	"strings"
@@ -36,6 +38,11 @@ func collectIgnoreFiles(c *config.Config, rel string) {
 	}
 	c.Exts[lastConfiguredExt] = rel
 
+	ents := c.Exts[common.ASPECT_DIR_ENTRIES].(map[string]fs.DirEntry)
+	if _, hasIgnore := ents[".gitignore"]; !hasIgnore {
+		return
+	}
+
 	// Find and add .gitignore files from this directory
 	ignoreFilePath := path.Join(c.RepoRoot, rel, ".gitignore")
 	ignoreReader, ignoreErr := os.Open(ignoreFilePath)
@@ -43,8 +50,10 @@ func collectIgnoreFiles(c *config.Config, rel string) {
 		BazelLog.Tracef("Add ignore file %s/.gitignore", rel)
 		defer ignoreReader.Close()
 		addIgnore(c, rel, ignoreReader)
-	} else if !os.IsNotExist(ignoreErr) {
-		BazelLog.Errorf("Failed to open %s/.gitignore: %v", rel, ignoreErr)
+	} else {
+		msg := fmt.Sprintf("Failed to open %s/.gitignore: %v", rel, ignoreErr)
+		BazelLog.Error(msg)
+		fmt.Printf("%s\n", msg)
 	}
 }
 
diff --git a/gazelle/common/regex.go b/gazelle/common/regex.go
index 585b1ecdd..4fc7b229f 100644
--- a/gazelle/common/regex.go
+++ b/gazelle/common/regex.go
@@ -9,18 +9,13 @@ import (
 var regexCache = make(map[string]*regexp.Regexp)
 var regexMutex sync.Mutex
 
-func ParseRegex(regexStr string) (*regexp.Regexp, error) {
+func ParseRegex(regexStr string) *regexp.Regexp {
 	regexMutex.Lock()
 	defer regexMutex.Unlock()
 
 	if regexCache[regexStr] == nil {
-		re, err := regexp.Compile(regexStr)
-		if err != nil {
-			return nil, err
-		}
-
-		regexCache[regexStr] = re
+		regexCache[regexStr] = regexp.MustCompile(regexStr)
 	}
 
-	return regexCache[regexStr], nil
+	return regexCache[regexStr]
 }
diff --git a/gazelle/common/set.go b/gazelle/common/set.go
index c86e9af4c..616c4ac8c 100644
--- a/gazelle/common/set.go
+++ b/gazelle/common/set.go
@@ -14,7 +14,26 @@ type LabelSet struct {
 }
 
 func LabelComparator(a, b interface{}) int {
-	return utils.StringComparator(a.(label.Label).String(), b.(label.Label).String())
+	al := a.(label.Label)
+	bl := b.(label.Label)
+
+	if al.Relative && !bl.Relative {
+		return -1
+	} else if !al.Relative && bl.Relative {
+		return +1
+	}
+
+	c := utils.StringComparator(al.Repo, bl.Repo)
+	if c != 0 {
+		return c
+	}
+
+	c = utils.StringComparator(al.Pkg, bl.Pkg)
+	if c != 0 {
+		return c
+	}
+
+	return utils.StringComparator(al.Name, bl.Name)
 }
 
 func NewLabelSet(from label.Label) *LabelSet {
diff --git a/gazelle/common/treesitter/BUILD.bazel b/gazelle/common/treesitter/BUILD.bazel
index e536e3241..f1ba9d7c8 100644
--- a/gazelle/common/treesitter/BUILD.bazel
+++ b/gazelle/common/treesitter/BUILD.bazel
@@ -6,11 +6,13 @@ go_library(
         "filters.go",
         "parser.go",
         "queries.go",
+        "query.go",
         "traversal.go",
     ],
     importpath = "aspect.build/cli/gazelle/common/treesitter",
     visibility = ["//visibility:public"],
     deps = [
+        "//gazelle/common",
         "//gazelle/common/treesitter/grammars/json",
         "//gazelle/common/treesitter/grammars/kotlin",
         "//gazelle/common/treesitter/grammars/starlark",
diff --git a/gazelle/common/treesitter/filters.go b/gazelle/common/treesitter/filters.go
index e94376013..bfe52eb82 100644
--- a/gazelle/common/treesitter/filters.go
+++ b/gazelle/common/treesitter/filters.go
@@ -1,8 +1,7 @@
 package treesitter
 
 import (
-	"regexp"
-
+	common "aspect.build/cli/gazelle/common"
 	sitter "github.com/smacker/go-tree-sitter"
 )
 
@@ -17,7 +16,7 @@ import (
 // Predicates implemented here:
 //   - eq?
 //   - match?
-func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.QueryCursor, input []byte) bool {
+func matchesAllPredicates(q *sitterQuery, m *sitter.QueryMatch, qc *sitter.QueryCursor, input []byte) bool {
 	qm := &sitter.QueryMatch{
 		ID:           m.ID,
 		PatternIndex: m.PatternIndex,
@@ -79,7 +78,7 @@ func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.Quer
 			isPositive := operator == "match?"
 
 			expectedCaptureName := q.CaptureNameForId(steps[1].ValueId)
-			regex := regexp.MustCompile(q.StringValueForId(steps[2].ValueId))
+			regex := common.ParseRegex(q.StringValueForId(steps[2].ValueId))
 
 			for _, c := range m.Captures {
 				captureName := q.CaptureNameForId(c.Index)
@@ -87,7 +86,7 @@ func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.Quer
 					continue
 				}
 
-				if regex.Match([]byte(c.Node.Content(input))) != isPositive {
+				if regex.MatchString(c.Node.Content(input)) != isPositive {
 					return false
 				}
 			}
diff --git a/gazelle/common/treesitter/queries.go b/gazelle/common/treesitter/queries.go
index f47f6c620..f5824d7cd 100644
--- a/gazelle/common/treesitter/queries.go
+++ b/gazelle/common/treesitter/queries.go
@@ -13,15 +13,15 @@ import (
 var ErrorsQuery = `(ERROR) @error`
 
 // A cache of parsed queries per language
-var queryCache = make(map[LanguageGrammar]map[string]*sitter.Query)
+var queryCache = make(map[LanguageGrammar]map[string]*sitterQuery)
 var queryMutex sync.Mutex
 
-func parseQuery(lang LanguageGrammar, queryStr string) *sitter.Query {
+func parseQuery(lang LanguageGrammar, queryStr string) *sitterQuery {
 	queryMutex.Lock()
 	defer queryMutex.Unlock()
 
 	if queryCache[lang] == nil {
-		queryCache[lang] = make(map[string]*sitter.Query)
+		queryCache[lang] = make(map[string]*sitterQuery)
 	}
 	if queryCache[lang][queryStr] == nil {
 		queryCache[lang][queryStr] = mustNewQuery(lang, queryStr)
@@ -39,7 +39,7 @@ func (tree TreeAst) QueryStrings(query, returnVar string) []string {
 
 	// Execute the query.
 	qc := sitter.NewQueryCursor()
-	qc.Exec(sitterQuery, rootNode)
+	qc.Exec(sitterQuery.q, rootNode)
 
 	// Collect string from the query results.
 	for {
@@ -82,7 +82,7 @@ func (tree TreeAst) Query(query string) <-chan ASTQueryResult {
 	// Execute the query.
 	go func() {
 		qc := sitter.NewQueryCursor()
-		qc.Exec(q, rootNode)
+		qc.Exec(q.q, rootNode)
 
 		for {
 			m, ok := qc.NextMatch()
@@ -104,7 +104,7 @@ func (tree TreeAst) Query(query string) <-chan ASTQueryResult {
 	return out
 }
 
-func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitter.Query) map[string]string {
+func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitterQuery) map[string]string {
 	captures := make(map[string]string, len(m.Captures))
 	for _, c := range m.Captures {
 		name := q.CaptureNameForId(c.Index)
@@ -120,11 +120,11 @@ func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitter.Query)
 
 // Find and read the `from` QueryCapture from a QueryMatch.
 // Filter matches based on captures value using "equals-{name}" vars.
-func fetchQueryMatch(query *sitter.Query, name string, m *sitter.QueryMatch, sourceCode []byte) *sitter.QueryCapture {
+func fetchQueryMatch(query *sitterQuery, name string, m *sitter.QueryMatch, sourceCode []byte) *sitter.QueryCapture {
 	var result *sitter.QueryCapture
 
-	for ci, c := range m.Captures {
-		cn := query.CaptureNameForId(uint32(ci))
+	for _, c := range m.Captures {
+		cn := query.CaptureNameForId(c.Index)
 
 		// Filters where a capture must equal a specific value.
 		if strings.HasPrefix(cn, "equals-") {
@@ -145,10 +145,10 @@ func fetchQueryMatch(query *sitter.Query, name string, m *sitter.QueryMatch, sou
 	return result
 }
 
-func mustNewQuery(lang LanguageGrammar, queryStr string) *sitter.Query {
-	treeQ, err := sitter.NewQuery([]byte(queryStr), toSitterLanguage(lang))
+func mustNewTreeQuery(lang LanguageGrammar, query string) *sitter.Query {
+	treeQ, err := sitter.NewQuery([]byte(query), toSitterLanguage(lang))
 	if err != nil {
-		BazelLog.Fatalf("Failed to create query for %q: %v", queryStr, err)
+		BazelLog.Fatalf("Failed to create query for %q: %v", query, err)
 	}
 	return treeQ
 }
@@ -166,7 +166,7 @@ func (tree TreeAst) QueryErrors() []error {
 
 	// Execute the import query
 	qc := sitter.NewQueryCursor()
-	qc.Exec(query, node)
+	qc.Exec(query.q, node)
 
 	// Collect import statements from the query results
 	for {
@@ -201,7 +201,7 @@ func (tree TreeAst) QueryErrors() []error {
 			msg := pre + line
 			arw := strings.Repeat(" ", len(pre)+colI) + "^"
 
-			errors = append(errors, fmt.Errorf(msg+"\n"+arw))
+			errors = append(errors, fmt.Errorf("%s\n%s", msg, arw))
 		}
 	}
 
diff --git a/gazelle/common/treesitter/query.go b/gazelle/common/treesitter/query.go
new file mode 100644
index 000000000..fadb4fe10
--- /dev/null
+++ b/gazelle/common/treesitter/query.go
@@ -0,0 +1,53 @@
+package treesitter
+
+import sitter "github.com/smacker/go-tree-sitter"
+
+// Basic wrapper around sitter.Query to cache tree-sitter cgo calls.
+type sitterQuery struct {
+	q *sitter.Query
+
+	// Pre-computed and cached query data
+	stringValues      []string
+	captureNames      []string
+	predicatePatterns [][][]sitter.QueryPredicateStep
+}
+
+func mustNewQuery(lang LanguageGrammar, query string) *sitterQuery {
+	q := mustNewTreeQuery(lang, query)
+
+	captureNames := make([]string, q.CaptureCount())
+	for i := uint32(0); i < q.CaptureCount(); i++ {
+		captureNames[i] = q.CaptureNameForId(i)
+	}
+
+	stringValues := make([]string, q.StringCount())
+	for i := uint32(0); i < q.StringCount(); i++ {
+		stringValues[i] = q.StringValueForId(i)
+	}
+
+	predicatePatterns := make([][][]sitter.QueryPredicateStep, q.PatternCount())
+	for i := uint32(0); i < q.PatternCount(); i++ {
+		predicatePatterns[i] = q.PredicatesForPattern(i)
+	}
+
+	return &sitterQuery{
+		q:                 q,
+		stringValues:      stringValues,
+		captureNames:      captureNames,
+		predicatePatterns: predicatePatterns,
+	}
+}
+
+// Cached query data accessors mirroring the tree-sitter Query signatures.
+
+func (q *sitterQuery) StringValueForId(id uint32) string {
+	return q.stringValues[id]
+}
+
+func (q *sitterQuery) CaptureNameForId(id uint32) string {
+	return q.captureNames[id]
+}
+
+func (q *sitterQuery) PredicatesForPattern(patternIndex uint32) [][]sitter.QueryPredicateStep {
+	return q.predicatePatterns[patternIndex]
+}
diff --git a/gazelle/common/walk.go b/gazelle/common/walk.go
index 98205633e..ff5a51045 100644
--- a/gazelle/common/walk.go
+++ b/gazelle/common/walk.go
@@ -14,6 +14,7 @@ type GazelleWalkFunc func(path string) error
 
 // Must align with patched bazel-gazelle
 const ASPECT_WALKSUBDIR = "__aspect:walksubdir"
+const ASPECT_DIR_ENTRIES = "__aspect:direntries"
 
 // Read any configuration regarding walk options.
 func ReadWalkConfig(c *config.Config, rel string, f *rule.File) bool {
diff --git a/gazelle/js/config.go b/gazelle/js/config.go
index bef33babc..8b3a5b7b8 100644
--- a/gazelle/js/config.go
+++ b/gazelle/js/config.go
@@ -305,7 +305,7 @@ func (c *JsGazelleConfig) GetNpmPackageGenerationMode() NpmPackageMode {
 
 // Set the pnpm-workspace.yaml file path.
 func (c *JsGazelleConfig) SetPnpmLockfile(pnpmLockPath string) {
-	c.pnpmLockPath = pnpmLockPath
+	c.pnpmLockPath = path.Clean(pnpmLockPath)
 }
 func (c *JsGazelleConfig) PnpmLockfile() string {
 	return c.pnpmLockPath
diff --git a/gazelle/js/configure.go b/gazelle/js/configure.go
index 30adda66c..f6b7b093b 100644
--- a/gazelle/js/configure.go
+++ b/gazelle/js/configure.go
@@ -3,6 +3,7 @@ package gazelle
 import (
 	"flag"
 	"fmt"
+	"io/fs"
 	"log"
 	"os"
 	"path"
@@ -89,11 +90,10 @@ func (ts *typeScriptLang) Configure(c *config.Config, rel string, f *rule.File)
 
 	if f != nil {
 		ts.readDirectives(c, rel, f)
-
-		// Read configurations relative to the current BUILD file.
-		ts.readConfigurations(c, rel)
 	}
 
+	ts.readConfigurations(c, rel)
+
 	common.ReadWalkConfig(c, rel, f)
 
 	git.ReadGitConfig(c, rel, f)
@@ -102,16 +102,29 @@ func (ts *typeScriptLang) Configure(c *config.Config, rel string, f *rule.File)
 func (ts *typeScriptLang) readConfigurations(c *config.Config, rel string) {
 	config := c.Exts[LanguageName].(*JsGazelleConfig)
 
+	ents := c.Exts[common.ASPECT_DIR_ENTRIES].(map[string]fs.DirEntry)
+
 	// pnpm
-	lockfilePath := path.Join(c.RepoRoot, rel, config.PnpmLockfile())
-	if _, err := os.Stat(lockfilePath); err == nil {
-		ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, config.PnpmLockfile()))
+	pnpmLockPath := config.PnpmLockfile()
+	pnpmLockDir := path.Dir(pnpmLockPath)
+	if pnpmLockDir == "." {
+		// Common case of the lockfile not being within a subdirectory.
+		// Can use the fs.DirEntry list of this directory to check if the lockfile exists.
+		if ents[pnpmLockPath] != nil {
+			ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, pnpmLockPath))
+		}
+	} else if rootDirEntry := strings.Split(pnpmLockDir, "/")[0]; ents[rootDirEntry] != nil {
+		// If the lockfile is in a subdirectory then check the fs.DirEntry of the subdirectory.
+		// If the subdirectory exists then perform the expensive os.Stat to check if the file exists.
+		lockfilePath := path.Join(c.RepoRoot, rel, pnpmLockPath)
+		if _, err := os.Stat(lockfilePath); err == nil {
+			ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, pnpmLockPath))
+		}
 	}
 
 	// tsconfig
 	// TODO: add support for alternate tsconfig names
-	configPath := path.Join(c.RepoRoot, rel, config.defaultTsconfigName)
-	if _, err := os.Stat(configPath); err == nil {
+	if _, hasTsconfig := ents[config.defaultTsconfigName]; hasTsconfig {
 		ts.tsconfig.AddTsConfigFile(c.RepoRoot, rel, config.defaultTsconfigName)
 	}
 }
diff --git a/gazelle/js/generate.go b/gazelle/js/generate.go
index e091ac585..d76807931 100644
--- a/gazelle/js/generate.go
+++ b/gazelle/js/generate.go
@@ -399,11 +399,12 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin
 	// Check for name-collisions with the rule being generated.
 	colError := gazelle.CheckCollisionErrors(targetName, TsProjectKind, sourceRuleKinds, args)
 	if colError != nil {
-		return nil, fmt.Errorf(colError.Error()+" "+
+		return nil, fmt.Errorf("%v "+
 			"Use the '# aspect:%s' directive to change the naming convention.\n\n"+
 			"For example:\n"+
 			"\t# aspect:%s {dirname}_js\n"+
 			"\t# aspect:%s {dirname}_js_tests",
+			colError.Error(),
 			Directive_LibraryNamingConvention,
 			Directive_LibraryNamingConvention,
 			Directive_TestsNamingConvention,
@@ -540,6 +541,13 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin
 				existing.DelAttr("resolve_json_module")
 			}
 
+			// Reflect the tsconfig resolve_json_module in the ts_project rule
+			if tsconfig.Jsx != typescript.JsxNone {
+				sourceRule.SetAttr("preserve_jsx", tsconfig.Jsx == typescript.JsxPreserve)
+			} else if existing != nil {
+				existing.DelAttr("preserve_jsx")
+			}
+
 			// Reflect the tsconfig out_dir in the ts_project rule
 			if tsconfig.OutDir != "" && tsconfig.OutDir != "." {
 				sourceRule.SetAttr("out_dir", tsconfig.OutDir)
@@ -560,6 +568,7 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin
 			existing.DelAttr("declaration")
 			existing.DelAttr("declaration_map")
 			existing.DelAttr("out_dir")
+			existing.DelAttr("preserve_jsx")
 			existing.DelAttr("resolve_json_module")
 			existing.DelAttr("source_map")
 			existing.DelAttr("root_dir")
@@ -695,7 +704,7 @@ func parseSourceFile(rootDir, filePath string) (parser.ParseResult, []error) {
 		return parser.ParseResult{}, []error{err}
 	}
 
-	return parser.ParseSource(filePath, string(content))
+	return parser.ParseSource(filePath, content)
 }
 
 func (ts *typeScriptLang) addFileLabel(importPath string, label *label.Label) {
diff --git a/gazelle/js/parser/parser.go b/gazelle/js/parser/parser.go
index 5874c363c..2b73cfce2 100644
--- a/gazelle/js/parser/parser.go
+++ b/gazelle/js/parser/parser.go
@@ -55,12 +55,11 @@ var importQueries = map[string]string{
 
 var tripleSlashRe = regexp.MustCompile(`^///\s*<reference\s+(?:lib|path|types)\s*=\s*"(?P<lib>[^"]+)"`)
 
-func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) {
+func ParseSource(filePath string, sourceCode []byte) (ParseResult, []error) {
 	imports := make([]string, 0, 5)
 	modules := make([]string, 0)
 	errs := make([]error, 0)
 
-	sourceCode := []byte(sourceCodeStr)
 	lang := filenameToLanguage(filePath)
 
 	// Parse the source code
@@ -109,9 +108,9 @@ func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) {
 						}
 					}
 				}
-			} else if isTripleSlashDirective(node, sourceCode) {
-				typesImport := getTripleSlashDirectiveModule(node, sourceCode)
-				if typesImport != "" {
+			} else if isPotentialTripleSlashDirective(node, sourceCode) {
+				typesImport, isTripleSlash := getTripleSlashDirectiveModule(node, sourceCode)
+				if isTripleSlash {
 					imports = append(imports, typesImport)
 				}
 			}
@@ -146,9 +145,10 @@ func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) {
 	return result, errs
 }
 
-// Determine if a node is a triple slash directive.
+// Determine if a node is potentially a triple-slash directive.
+// To be used before running a more expensive check and triple-slash directive extraction.
 // See: https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
-func isTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool {
+func isPotentialTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool {
 	nodeType := node.Type()
 	if nodeType != "comment" {
 		return false
@@ -162,16 +162,15 @@ func isTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool {
 // Extract a /// <reference> from a comment node
 // Note: could also potentially use a treesitter query such as:
 // /  `(program (comment) @result (#match? @c "^///\\s*<reference\\s+(lib|types|path)\\s*=\\s*\"[^\"]+\""))`
-func getTripleSlashDirectiveModule(node *sitter.Node, sourceCode []byte) string {
+func getTripleSlashDirectiveModule(node *sitter.Node, sourceCode []byte) (string, bool) {
 	comment := node.Content(sourceCode)
 	submatches := tripleSlashRe.FindAllStringSubmatchIndex(comment, -1)
 	if len(submatches) != 1 {
-		Log.Errorf("Invalid triple slash directive: %q", comment)
-		return ""
+		return "", false
 	}
 
 	lib := tripleSlashRe.ExpandString(make([]byte, 0), "$lib", comment, submatches[0])
-	return string(lib)
+	return string(lib), len(lib) > 0
 }
 
 // Determine if a node is an import/export statement that may contain a `from` value.
diff --git a/gazelle/js/parser/parser_test.go b/gazelle/js/parser/parser_test.go
index 8455c1d3f..698106456 100644
--- a/gazelle/js/parser/parser_test.go
+++ b/gazelle/js/parser/parser_test.go
@@ -328,7 +328,7 @@ func equal[T comparable](a, b []T) bool {
 func TestTreesitterParser(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			res, _ := ParseSource(tc.filename, tc.ts)
+			res, _ := ParseSource(tc.filename, []byte(tc.ts))
 
 			if !equal(res.Imports, tc.expectedImports) {
 				t.Errorf("Unexpected import results\nactual:  %#v;\nexpected: %#v\ntypescript code:\n%v", res.Imports, tc.expectedImports, tc.ts)
diff --git a/gazelle/js/pnpm/parser.go b/gazelle/js/pnpm/parser.go
index 2fca2a54e..13784be42 100644
--- a/gazelle/js/pnpm/parser.go
+++ b/gazelle/js/pnpm/parser.go
@@ -29,13 +29,13 @@ func ParsePnpmLockFileDependencies(lockfilePath string) WorkspacePackageVersionM
 var lockVersionRegex = regexp.MustCompile(`^\s*lockfileVersion: '?(?P<Version>\d\.\d)'?`)
 
 func parsePnpmLockVersion(yamlFileContent []byte) (string, error) {
-	match := lockVersionRegex.FindStringSubmatch(string(yamlFileContent))
+	match := lockVersionRegex.FindSubmatch(yamlFileContent)
 
 	if len(match) != 2 {
 		return "", fmt.Errorf("failed to find lockfile version in: %q", string(yamlFileContent))
 	}
 
-	return match[1], nil
+	return string(match[1]), nil
 }
 
 func parsePnpmLockDependencies(yamlFileContent []byte) (WorkspacePackageVersionMap, error) {
diff --git a/gazelle/js/resolve.go b/gazelle/js/resolve.go
index 528bbe551..752997355 100644
--- a/gazelle/js/resolve.go
+++ b/gazelle/js/resolve.go
@@ -228,14 +228,17 @@ func (ts *typeScriptLang) Resolve(
 
 		// Support this target representing a project or a package
 		var imports *treeset.Set
-		if packageInfo, isProjectInfo := importData.(*TsPackageInfo); isProjectInfo {
+		if packageInfo, isPackageInfo := importData.(*TsPackageInfo); isPackageInfo {
 			imports = packageInfo.imports
 
 			if packageInfo.source != nil {
 				deps.Add(packageInfo.source)
 			}
+		} else if projectInfo, isProjectInfo := importData.(*TsProjectInfo); isProjectInfo {
+			imports = projectInfo.imports
 		} else {
-			imports = importData.(*TsProjectInfo).imports
+			BazelLog.Infof("%s //%s:%s with no/unknown package info", r.Kind(), from.Pkg, r.Name())
+			break
 		}
 
 		err := ts.resolveImports(c, ix, deps, imports, from)
@@ -252,7 +255,12 @@ func (ts *typeScriptLang) Resolve(
 			r.SetAttr("deps", deps.Labels())
 		}
 	case NpmPackageKind:
-		packageInfo := importData.(*TsPackageInfo)
+		packageInfo, isPackageInfo := importData.(*TsPackageInfo)
+		if !isPackageInfo {
+			BazelLog.Infof("%s //%s:%s with no/unknown package info", r.Kind(), from.Pkg, r.Name())
+			break
+		}
+
 		srcs := packageInfo.sources.Values()
 
 		deps := common.NewLabelSet(from)
diff --git a/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out b/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out
index a88539be9..78ca6e3e1 100644
--- a/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out
+++ b/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out
@@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
 ts_project(
     name = "preserve",
     srcs = ["lib.tsx"],
+    preserve_jsx = True,
     tsconfig = ":tsconfig",
 )
 
diff --git a/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out
index 9d0df53ed..6eb5dc4c9 100644
--- a/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out
+++ b/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out
@@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
 ts_project(
     name = "react-jsx",
     srcs = ["rjsx.tsx"],
+    preserve_jsx = False,
     tsconfig = ":tsconfig",
     deps = ["//:node_modules/react"],
 )
diff --git a/gazelle/js/tests/tsconfig_jsx/react/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/BUILD.out
index c54763a25..5f5c0a0b8 100644
--- a/gazelle/js/tests/tsconfig_jsx/react/BUILD.out
+++ b/gazelle/js/tests/tsconfig_jsx/react/BUILD.out
@@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
 ts_project(
     name = "react",
     srcs = ["r.tsx"],
+    preserve_jsx = False,
     tsconfig = ":tsconfig",
     deps = ["//:node_modules/react"],
 )
diff --git a/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out
index 4c22af78f..65b56f56e 100644
--- a/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out
+++ b/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out
@@ -3,5 +3,6 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
 ts_project(
     name = "no-jsx",
     srcs = ["s.ts"],
+    preserve_jsx = False,
     tsconfig = "//react:tsconfig",
 )
diff --git a/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out
index 30890a4ae..b9e9b666f 100644
--- a/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out
+++ b/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out
@@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
 ts_project(
     name = "sub",
     srcs = ["s.tsx"],
+    preserve_jsx = False,
     tsconfig = "//react:tsconfig",
     deps = ["//:node_modules/react"],
 )
diff --git a/gazelle/js/typescript/config.go b/gazelle/js/typescript/config.go
index 8ec392b55..2ed27e095 100644
--- a/gazelle/js/typescript/config.go
+++ b/gazelle/js/typescript/config.go
@@ -7,6 +7,7 @@ import (
 
 	node "aspect.build/cli/gazelle/js/node"
 	pnpm "aspect.build/cli/gazelle/js/pnpm"
+	BazelLog "aspect.build/cli/pkg/logger"
 )
 
 type workspacePath struct {
@@ -46,6 +47,8 @@ func (tc *TsWorkspace) AddTsConfigFile(root, rel, fileName string) {
 		return
 	}
 
+	BazelLog.Debugf("Adding tsconfig file %s/%s", rel, fileName)
+
 	tc.cm.configFiles[rel] = &workspacePath{
 		root:     root,
 		rel:      rel,
@@ -78,13 +81,19 @@ func (tc *TsWorkspace) GetTsConfigFile(rel string) *TsConfig {
 		return nil
 	}
 
+	BazelLog.Debugf("Parsed tsconfig file %s/%s", p.rel, p.fileName)
+
 	return c
 }
 
 // A `TsConfigResolver` to resolve imports from *within* tsconfig files
 // to real paths such as resolved the tsconfig `extends`.
 func (tc *TsWorkspace) tsConfigResolver(dir, rel string) []string {
-	possible := []string{path.Join(dir, rel)}
+	possible := []string{}
+
+	if isRelativePath(rel) {
+		possible = append(possible, path.Join(dir, rel))
+	}
 
 	if p := tc.cm.pnpmProjects.GetProject(dir); p != nil {
 		pkg, subFile := node.ParseImportPath(rel)
diff --git a/gazelle/js/typescript/tsconfig.go b/gazelle/js/typescript/tsconfig.go
index 7d4cd0bf5..e07bf68af 100644
--- a/gazelle/js/typescript/tsconfig.go
+++ b/gazelle/js/typescript/tsconfig.go
@@ -149,9 +149,6 @@ func parseTsConfigJSONFile(parsed map[string]*TsConfig, resolver TsConfigResolve
 
 	content, err := os.ReadFile(path.Join(root, tsconfig))
 	if err != nil {
-		if os.IsNotExist(err) {
-			err = nil
-		}
 		return nil, err
 	}
 
diff --git a/gazelle/kotlin/generate.go b/gazelle/kotlin/generate.go
index b05a5e6ad..5e8b45b85 100644
--- a/gazelle/kotlin/generate.go
+++ b/gazelle/kotlin/generate.go
@@ -210,7 +210,7 @@ func parseFile(rootDir, filePath string) (*parser.ParseResult, []error) {
 	}
 
 	p := parser.NewParser()
-	return p.Parse(filePath, string(content))
+	return p.Parse(filePath, content)
 }
 
 func (kt *kotlinLang) collectSourceFiles(cfg *kotlinconfig.KotlinConfig, args language.GenerateArgs) *treeset.Set {
diff --git a/gazelle/kotlin/parser/parser.go b/gazelle/kotlin/parser/parser.go
index 98721158a..9ba0797a6 100644
--- a/gazelle/kotlin/parser/parser.go
+++ b/gazelle/kotlin/parser/parser.go
@@ -18,7 +18,7 @@ type ParseResult struct {
 }
 
 type Parser interface {
-	Parse(filePath, source string) (*ParseResult, []error)
+	Parse(filePath string, source []byte) (*ParseResult, []error)
 }
 
 type treeSitterParser struct {
@@ -31,7 +31,7 @@ func NewParser() Parser {
 	return &p
 }
 
-func (p *treeSitterParser) Parse(filePath, source string) (*ParseResult, []error) {
+func (p *treeSitterParser) Parse(filePath string, sourceCode []byte) (*ParseResult, []error) {
 	var result = &ParseResult{
 		File:    filePath,
 		Imports: make([]string, 0),
@@ -39,8 +39,6 @@ func (p *treeSitterParser) Parse(filePath, source string) (*ParseResult, []error
 
 	errs := make([]error, 0)
 
-	sourceCode := []byte(source)
-
 	tree, err := treeutils.ParseSourceCode(treeutils.Kotlin, filePath, sourceCode)
 	if err != nil {
 		errs = append(errs, err)
diff --git a/gazelle/kotlin/parser/parser_test.go b/gazelle/kotlin/parser/parser_test.go
index 63a6f86d1..53f11ba5d 100644
--- a/gazelle/kotlin/parser/parser_test.go
+++ b/gazelle/kotlin/parser/parser_test.go
@@ -69,7 +69,7 @@ func TestTreesitterParser(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
-			res, _ := NewParser().Parse(tc.filename, tc.kt)
+			res, _ := NewParser().Parse(tc.filename, []byte(tc.kt))
 
 			if !equal(res.Imports, tc.imports) {
 				t.Errorf("Imports...\nactual:  %#v;\nexpected: %#v\nkotlin code:\n%v", res.Imports, tc.imports, tc.kt)
@@ -82,24 +82,24 @@ func TestTreesitterParser(t *testing.T) {
 	}
 
 	t.Run("main detection", func(t *testing.T) {
-		res, _ := NewParser().Parse("main.kt", "fun main() {}")
+		res, _ := NewParser().Parse("main.kt", []byte("fun main() {}"))
 		if !res.HasMain {
 			t.Errorf("main method should be detected")
 		}
 
-		res, _ = NewParser().Parse("x.kt", `
+		res, _ = NewParser().Parse("x.kt", []byte(`
 package my.demo
 fun main() {}
-		`)
+		`))
 		if !res.HasMain {
 			t.Errorf("main method should be detected with package")
 		}
 
-		res, _ = NewParser().Parse("x.kt", `
+		res, _ = NewParser().Parse("x.kt", []byte(`
 package my.demo
 import kotlin.text.*
 fun main() {}
-		`)
+		`))
 		if !res.HasMain {
 			t.Errorf("main method should be detected with imports")
 		}
diff --git a/go.bzl b/go.bzl
index 21165acef..8bdd02470 100644
--- a/go.bzl
+++ b/go.bzl
@@ -2308,15 +2308,15 @@ def deps():
         name = "org_golang_google_genproto_googleapis_api",
         build_file_proto_mode = "disable_global",
         importpath = "google.golang.org/genproto/googleapis/api",
-        sum = "h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=",
-        version = "v0.0.0-20240521202816-d264139d666e",
+        sum = "h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=",
+        version = "v0.0.0-20240604185151-ef581f913117",
     )
     go_repository(
         name = "org_golang_google_genproto_googleapis_rpc",
         build_file_proto_mode = "disable_global",
         importpath = "google.golang.org/genproto/googleapis/rpc",
-        sum = "h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=",
-        version = "v0.0.0-20240521202816-d264139d666e",
+        sum = "h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=",
+        version = "v0.0.0-20240528184218-531527333157",
     )
     go_repository(
         name = "org_golang_google_grpc",
@@ -2344,29 +2344,29 @@ def deps():
         name = "org_golang_x_crypto",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/crypto",
-        sum = "h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=",
-        version = "v0.25.0",
+        sum = "h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=",
+        version = "v0.28.0",
     )
     go_repository(
         name = "org_golang_x_exp",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/exp",
-        sum = "h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA=",
-        version = "v0.0.0-20240716175740-e3f259677ff7",
+        sum = "h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=",
+        version = "v0.0.0-20241009180824-f66d83c29e7c",
     )
     go_repository(
         name = "org_golang_x_mod",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/mod",
-        sum = "h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=",
-        version = "v0.20.0",
+        sum = "h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=",
+        version = "v0.21.0",
     )
     go_repository(
         name = "org_golang_x_net",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/net",
-        sum = "h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=",
-        version = "v0.27.0",
+        sum = "h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=",
+        version = "v0.30.0",
     )
     go_repository(
         name = "org_golang_x_oauth2",
@@ -2386,22 +2386,22 @@ def deps():
         name = "org_golang_x_sys",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/sys",
-        sum = "h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=",
-        version = "v0.25.0",
+        sum = "h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=",
+        version = "v0.26.0",
     )
     go_repository(
         name = "org_golang_x_term",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/term",
-        sum = "h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=",
-        version = "v0.22.0",
+        sum = "h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=",
+        version = "v0.25.0",
     )
     go_repository(
         name = "org_golang_x_text",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/text",
-        sum = "h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=",
-        version = "v0.16.0",
+        sum = "h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=",
+        version = "v0.19.0",
     )
     go_repository(
         name = "org_golang_x_time",
@@ -2415,8 +2415,8 @@ def deps():
         build_directives = ["gazelle:exclude **/testdata/**/*"],
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/tools",
-        sum = "h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=",
-        version = "v0.23.0",
+        sum = "h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=",
+        version = "v0.26.0",
     )
     go_repository(
         name = "org_golang_x_tools_go_vcs",
diff --git a/go.mod b/go.mod
index 1fdc907ff..feb5e58b8 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	github.com/twmb/murmur3 v1.1.8
 	github.com/yargevad/filepathx v1.0.0
 	go.starlark.net v0.0.0-20240725214946-42030a7cedce
-	golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 // indirect
+	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
 	golang.org/x/sync v0.8.0
 	google.golang.org/genproto v0.0.0-20240528184218-531527333157
 	google.golang.org/grpc v1.64.1
@@ -133,15 +133,15 @@ require (
 	github.com/yuin/goldmark-emoji v1.0.3 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.9.0 // indirect
-	golang.org/x/crypto v0.25.0 // indirect
-	golang.org/x/mod v0.20.0 // indirect
-	golang.org/x/net v0.27.0 // indirect
-	golang.org/x/sys v0.25.0 // indirect
-	golang.org/x/term v0.22.0 // indirect
-	golang.org/x/text v0.16.0 // indirect
+	golang.org/x/crypto v0.28.0 // indirect
+	golang.org/x/mod v0.21.0 // indirect
+	golang.org/x/net v0.30.0 // indirect
+	golang.org/x/sys v0.26.0 // indirect
+	golang.org/x/term v0.25.0 // indirect
+	golang.org/x/text v0.19.0 // indirect
 	golang.org/x/tools/go/vcs v0.1.0-deprecated // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 0c4c693b2..a9c9fec26 100644
--- a/go.sum
+++ b/go.sum
@@ -342,15 +342,15 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
-golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
-golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA=
-golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
+golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
-golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -360,8 +360,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -394,15 +394,15 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
-golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
+golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
+golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -410,15 +410,15 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
-golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 golang.org/x/tools/go/vcs v0.1.0-deprecated h1:cOIJqWBl99H1dH5LWizPa+0ImeeJq3t3cJjaeOWUAL4=
 golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -427,10 +427,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE=
 google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
 google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
 google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
 google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
diff --git a/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch b/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch
new file mode 100644
index 000000000..4b8d6b98f
--- /dev/null
+++ b/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch
@@ -0,0 +1,69 @@
+diff --git a/config/config.go b/config/config.go
+index 1841650..f34ad5e 100644
+--- a/config/config.go
++++ b/config/config.go
+@@ -122,6 +122,7 @@ type MappedKind struct {
+ 
+ const ASPECT_WALKSUBDIR = "__aspect:walksubdir"
+ const ASPECT_GITIGNORE = "__aspect:gitignore"
++const ASPECT_DIR_ENTRIES = "__aspect:direntries"
+ 
+ func New() *Config {
+ 	return &Config{
+diff --git a/walk/walk.go b/walk/walk.go
+index 1ba1b47..19e99ef 100644
+--- a/walk/walk.go
++++ b/walk/walk.go
+@@ -144,6 +144,14 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri
+ 		return ents[i].Name() < ents[j].Name()
+ 	})
+ 
++	// PATCH(fs.DirEntry map) ---
++	entsMap := make(map[string]fs.DirEntry, len(trie.children))
++	for _, node := range trie.children {
++		entsMap[(*node.entry).Name()] = *node.entry
++	}
++	c.Exts[config.ASPECT_DIR_ENTRIES] = entsMap
++	// END PATCH(fs.DirEntry map) ---
++
+ 	// Absolute path to the directory being visited
+ 	dir := filepath.Join(c.RepoRoot, rel)
+ 
+@@ -158,7 +166,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri
+ 		haveError = true
+ 	}
+ 
+-	c = configure(cexts, knownDirectives, c, rel, f)
++	configure(cexts, knownDirectives, c, rel, f)
+ 	wc := getWalkConfig(c)
+ 
+ 	if wc.isExcluded(rel) {
+@@ -203,7 +211,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri
+ 		if subRel := path.Join(rel, sub); updateRels.shouldVisit(subRel, shouldUpdate) {
+ 			// PATCH ---
+ 			// Merge the returned 'subFiles' if 'mergeFiles' is true
+-			subFiles, mergeFiles := visit(c, cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate)
++			subFiles, mergeFiles := visit(c.Clone(), cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate)
+ 			if mergeFiles {
+ 				for _, f := range subFiles {
+ 					regularFiles = append(regularFiles, path.Join(sub, f))
+@@ -330,10 +338,7 @@ func loadBuildFile(c *config.Config, pkg, dir string, ents []fs.DirEntry) (*rule
+ 	return rule.LoadFile(path, pkg)
+ }
+ 
+-func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) *config.Config {
+-	if rel != "" {
+-		c = c.Clone()
+-	}
++func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) {
+ 	if f != nil {
+ 		for _, d := range f.Directives {
+ 			if !knownDirectives[d.Key] {
+@@ -349,7 +354,6 @@ func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *co
+ 	for _, cext := range cexts {
+ 		cext.Configure(c, rel, f)
+ 	}
+-	return c
+ }
+ 
+ func findGenFiles(wc *walkConfig, f *rule.File) []string {
diff --git a/pkg/aspect/configure/BUILD.bazel b/pkg/aspect/configure/BUILD.bazel
index ed3dcbdee..15ad5357a 100644
--- a/pkg/aspect/configure/BUILD.bazel
+++ b/pkg/aspect/configure/BUILD.bazel
@@ -8,6 +8,7 @@ go_library(
         "fix.go",
         "fix-update.go",
         "gazelle.go",
+        "go_deps.go",
         "metaresolver.go",
         "print.go",
         "profiler.go",
@@ -21,7 +22,9 @@ go_library(
         "//gazelle/python",
         "//pkg/aspect/configure/internal/wspace",
         "//pkg/aspecterrors",
+        "//pkg/bazel",
         "//pkg/ioutils",
+        "//pkg/logger",
         "@bazel_gazelle//config:go_default_library",
         "@bazel_gazelle//flag:go_default_library",
         "@bazel_gazelle//label:go_default_library",
diff --git a/pkg/aspect/configure/configure.go b/pkg/aspect/configure/configure.go
index e374698c8..e30193095 100644
--- a/pkg/aspect/configure/configure.go
+++ b/pkg/aspect/configure/configure.go
@@ -75,6 +75,17 @@ func (c *Configure) addDefaultLanguages() {
 
 	viper.SetDefault("configure.languages.go", false)
 	if viper.GetBool("configure.languages.go") {
+		if os.Getenv(GO_REPOSITORY_CONFIG_ENV) == "" {
+			goConfigPath, err := determineGoRepositoryConfigPath()
+			if err != nil {
+				log.Fatalf("ERROR: unable to determine go_repository config path: %v", err)
+			}
+
+			if goConfigPath != "" {
+				os.Setenv(GO_REPOSITORY_CONFIG_ENV, goConfigPath)
+			}
+		}
+
 		c.AddLanguage("go", golang.NewLanguage)
 	}
 
@@ -140,6 +151,11 @@ configure:
 		fixArgs = append(fixArgs, "--memprofile="+memprofile)
 	}
 
+	go_repo_config := os.Getenv(GO_REPOSITORY_CONFIG_ENV)
+	if go_repo_config != "" {
+		fixArgs = append(fixArgs, "--repo_config="+go_repo_config)
+	}
+
 	// Append additional args including specific directories to fix.
 	fixArgs = append(fixArgs, args...)
 
diff --git a/pkg/aspect/configure/diff.go b/pkg/aspect/configure/diff.go
index f89e1935d..3db457f3b 100644
--- a/pkg/aspect/configure/diff.go
+++ b/pkg/aspect/configure/diff.go
@@ -76,6 +76,19 @@ func diffFile(c *config.Config, f *rule.File) error {
 	} else {
 		diff.ToFile = outPath
 	}
+	// Trim off trailing newline from the end of the file.  Modern diff tools typically
+	// handle this, however difflib does not and is no longer maintained, so we need to
+	// handle it here.
+	if len(diff.A) > 0 {
+		if diff.A[len(diff.A)-1] == "\n" {
+			diff.A = diff.A[:len(diff.A)-1]
+		}
+	}
+	if len(diff.B) > 0 {
+		if diff.B[len(diff.B)-1] == "\n" {
+			diff.B = diff.B[:len(diff.B)-1]
+		}
+	}
 
 	uc := getUpdateConfig(c)
 	var out io.Writer = os.Stdout
diff --git a/pkg/aspect/configure/go_deps.go b/pkg/aspect/configure/go_deps.go
new file mode 100644
index 000000000..001a69f40
--- /dev/null
+++ b/pkg/aspect/configure/go_deps.go
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 Aspect Build Systems, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configure
+
+import (
+	"fmt"
+	"os"
+	"path"
+	"strings"
+
+	"aspect.build/cli/pkg/bazel"
+	"aspect.build/cli/pkg/ioutils"
+	BazelLog "aspect.build/cli/pkg/logger"
+)
+
+// The @gazelle go_deps extension name.
+// https://github.com/bazel-contrib/bazel-gazelle/blob/v0.39.1/internal/bzlmod/go_deps.bzl#L827
+const GO_DEPS_EXTENSION_NAME = "go_deps"
+
+// The repository name for the gazelle repo_config.
+// https://github.com/bazel-contrib/bazel-gazelle/blob/v0.39.1/internal/bzlmod/go_deps.bzl#L648-L654
+const GO_REPOSITORY_CONFIG_REPO_NAME = "bazel_gazelle_go_repository_config"
+
+// An environment variable to set the full path to the gazelle repo_config
+const GO_REPOSITORY_CONFIG_ENV = GO_REPOSITORY_CONFIG_REPO_NAME
+
+// bazel 8 switches the bzlmod separator to "+"
+// See https://github.com/bazelbuild/bazel/issues/23127
+var BZLMOD_REPO_SEPARATORS = []string{"~", "+"}
+
+func determineGoRepositoryConfigPath() (string, error) {
+	// TODO(jason): look into a store of previous invocations for relevant logs
+	bzl := bazel.WorkspaceFromWd
+
+	var out strings.Builder
+	streams := ioutils.Streams{Stdout: &out, Stderr: nil}
+	if err := bzl.RunCommand(streams, nil, "info", "output_base"); err != nil {
+		return "", fmt.Errorf("unable to locate output_base: %w", err)
+	}
+
+	outputBase := strings.TrimSpace(out.String())
+	if outputBase == "" {
+		return "", fmt.Errorf("unable to locate output_base on path")
+	}
+
+	var goDepsRepoName string
+	for _, sep := range BZLMOD_REPO_SEPARATORS {
+		repoName := fmt.Sprintf("gazelle%s%s%s%s%s/WORKSPACE", sep, sep, GO_DEPS_EXTENSION_NAME, sep, GO_REPOSITORY_CONFIG_REPO_NAME)
+		repoPath := path.Join(outputBase, "external", repoName)
+
+		_, err := os.Stat(repoPath)
+		if err == nil {
+			goDepsRepoName = repoPath
+			break
+		}
+	}
+
+	if goDepsRepoName == "" {
+		// Assume no matches means rules_go is not being used in bzlmod
+		// or the gazelle `go_deps` extension is not being used
+		BazelLog.Infof("No %s found in output_base: %s", GO_REPOSITORY_CONFIG_REPO_NAME, outputBase)
+		return "", nil
+	}
+
+	BazelLog.Infof("Found %s(s): %v", GO_REPOSITORY_CONFIG_REPO_NAME, goDepsRepoName)
+
+	return goDepsRepoName, nil
+}
diff --git a/pkg/bazel/BUILD.bazel b/pkg/bazel/BUILD.bazel
index 17f805875..0512b3c3c 100644
--- a/pkg/bazel/BUILD.bazel
+++ b/pkg/bazel/BUILD.bazel
@@ -6,6 +6,7 @@ go_library(
         "bazel.go",
         "bazel_flags.go",
         "bazelisk.go",
+        "bazelisk-core.go",
     ],
     importpath = "aspect.build/cli/pkg/bazel",
     visibility = ["//visibility:public"],
diff --git a/pkg/bazel/bazelisk-core.go b/pkg/bazel/bazelisk-core.go
new file mode 100644
index 000000000..ec1cf44e2
--- /dev/null
+++ b/pkg/bazel/bazelisk-core.go
@@ -0,0 +1,260 @@
+// VENDORED https://github.com/bazelbuild/bazelisk/blob/v1.17.0/core/core.go
+//
+// Minor changes made to align with the ./bazelisk.go API, which is a mix of custom code
+// and vendored code significantly different then the origin bazelisk/core/core.go.
+//
+// DO NOT MODIFY ... without diffing with the upstream file.
+
+// Package core contains the core Bazelisk logic, as well as abstractions for Bazel repositories.
+package bazel
+
+// TODO: split this file into multiple smaller ones in dedicated packages (e.g. execution, incompatible, ...).
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"os/signal"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"sync"
+	"syscall"
+
+	"aspect.build/cli/buildinfo"
+	"aspect.build/cli/pkg/ioutils"
+	"github.com/bazelbuild/bazelisk/platforms"
+	"github.com/bazelbuild/bazelisk/versions"
+)
+
+const (
+	bazelReal      = "BAZEL_REAL"
+	skipWrapperEnv = "BAZELISK_SKIP_WRAPPER"
+	wrapperPath    = "./tools/bazel"
+	rcFileName     = ".bazeliskrc"
+	maxDirLength   = 255
+)
+
+var (
+	fileConfig     map[string]string
+	fileConfigOnce sync.Once
+)
+
+func (bazelisk *Bazelisk) getUserAgent() string {
+	agent := bazelisk.GetEnvOrConfig("BAZELISK_USER_AGENT")
+	if len(agent) > 0 {
+		return agent
+	}
+	return fmt.Sprintf("Aspect/%s", buildinfo.Current().Version())
+}
+
+// GetEnvOrConfig reads a configuration value from the environment, but fall back to reading it from .bazeliskrc.
+func (bazelisk *Bazelisk) GetEnvOrConfig(name string) string {
+	if val, found := os.LookupEnv(name); found {
+		return val
+	}
+
+	fileConfigOnce.Do(bazelisk.loadFileConfig)
+
+	return fileConfig[name]
+}
+
+// loadFileConfig locates available .bazeliskrc configuration files, parses them with a precedence order preference,
+// and updates a global configuration map with their contents. This routine should be executed exactly once.
+func (bazelisk *Bazelisk) loadFileConfig() {
+	var rcFilePaths []string
+
+	if userRC, err := locateUserConfigFile(); err == nil {
+		rcFilePaths = append(rcFilePaths, userRC)
+	}
+	if workspaceRC, err := bazelisk.locateWorkspaceConfigFile(); err == nil {
+		rcFilePaths = append(rcFilePaths, workspaceRC)
+	}
+
+	fileConfig = make(map[string]string)
+	for _, rcPath := range rcFilePaths {
+		config, err := parseFileConfig(rcPath)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		for key, value := range config {
+			fileConfig[key] = value
+		}
+	}
+}
+
+// MODIFIED to use the Bazelisk struct
+// locateWorkspaceConfigFile locates a .bazeliskrc file in the current workspace root.
+func (bazelisk *Bazelisk) locateWorkspaceConfigFile() (string, error) {
+	return filepath.Join(bazelisk.workspaceRoot, rcFileName), nil
+}
+
+// locateUserConfigFile locates a .bazeliskrc file in the user's home directory.
+func locateUserConfigFile() (string, error) {
+	home, err := os.UserHomeDir()
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(home, rcFileName), nil
+}
+
+// parseFileConfig parses a .bazeliskrc file as a map of key-value configuration values.
+func parseFileConfig(rcFilePath string) (map[string]string, error) {
+	config := make(map[string]string)
+
+	contents, err := os.ReadFile(rcFilePath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			// Non-critical error.
+			return config, nil
+		}
+		return nil, err
+	}
+
+	for _, line := range strings.Split(string(contents), "\n") {
+		if strings.HasPrefix(line, "#") {
+			// comments
+			continue
+		}
+		parts := strings.SplitN(line, "=", 2)
+		if len(parts) < 2 {
+			continue
+		}
+		key := strings.TrimSpace(parts[0])
+		config[key] = strings.TrimSpace(parts[1])
+	}
+
+	return config, nil
+}
+
+// isValidWorkspace returns true iff the supplied path is the workspace root, defined by the presence of
+// a file named WORKSPACE or WORKSPACE.bazel
+// see https://github.com/bazelbuild/bazel/blob/8346ea4cfdd9fbd170d51a528fee26f912dad2d5/src/main/cpp/workspace_layout.cc#L37
+func isValidWorkspace(path string) bool {
+	info, err := os.Stat(path)
+	if err != nil {
+		return false
+	}
+
+	return !info.IsDir()
+}
+
+func parseBazelForkAndVersion(bazelForkAndVersion string) (string, string, error) {
+	var bazelFork, bazelVersion string
+
+	versionInfo := strings.Split(bazelForkAndVersion, "/")
+
+	if len(versionInfo) == 1 {
+		bazelFork, bazelVersion = versions.BazelUpstream, versionInfo[0]
+	} else if len(versionInfo) == 2 {
+		bazelFork, bazelVersion = versionInfo[0], versionInfo[1]
+	} else {
+		return "", "", fmt.Errorf("invalid version \"%s\", could not parse version with more than one slash", bazelForkAndVersion)
+	}
+
+	return bazelFork, bazelVersion, nil
+}
+
+func copyFile(src, dst string, perm os.FileMode) error {
+	srcFile, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer srcFile.Close()
+
+	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm)
+	if err != nil {
+		return err
+	}
+	defer dstFile.Close()
+
+	_, err = io.Copy(dstFile, srcFile)
+
+	return err
+}
+
+func linkLocalBazel(baseDirectory string, bazelPath string) (string, error) {
+	normalizedBazelPath := dirForURL(bazelPath)
+	destinationDir := filepath.Join(baseDirectory, normalizedBazelPath, "bin")
+	err := os.MkdirAll(destinationDir, 0755)
+	if err != nil {
+		return "", fmt.Errorf("could not create directory %s: %v", destinationDir, err)
+	}
+	destinationPath := filepath.Join(destinationDir, "bazel"+platforms.DetermineExecutableFilenameSuffix())
+	if _, err := os.Stat(destinationPath); err != nil {
+		err = os.Symlink(bazelPath, destinationPath)
+		// If can't create Symlink, fallback to copy
+		if err != nil {
+			err = copyFile(bazelPath, destinationPath, 0755)
+			if err != nil {
+				return "", fmt.Errorf("could not copy file from %s to %s: %v", bazelPath, destinationPath, err)
+			}
+		}
+	}
+	return destinationPath, nil
+}
+
+func prependDirToPathList(cmd *exec.Cmd, dir string) {
+	found := false
+	for idx, val := range cmd.Env {
+		splits := strings.Split(val, "=")
+		if len(splits) != 2 {
+			continue
+		}
+		if strings.EqualFold(splits[0], "PATH") {
+			found = true
+			cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
+			break
+		}
+	}
+
+	if !found {
+		cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir))
+	}
+}
+
+func (bazelisk *Bazelisk) runBazel(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) (int, error) {
+	cmd := bazelisk.makeBazelCmd(bazel, args, streams, env, wd)
+
+	err := cmd.Start()
+	if err != nil {
+		return 1, fmt.Errorf("could not start Bazel: %v", err)
+	}
+
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+	go func() {
+		s := <-c
+
+		// Only forward SIGTERM to our child process.
+		if s != os.Interrupt {
+			cmd.Process.Kill()
+		}
+	}()
+
+	err = cmd.Wait()
+	if err != nil {
+		if exitError, ok := err.(*exec.ExitError); ok {
+			waitStatus := exitError.Sys().(syscall.WaitStatus)
+			return waitStatus.ExitStatus(), nil
+		}
+		return 1, fmt.Errorf("could not launch Bazel: %v", err)
+	}
+	return 0, nil
+}
+
+func dirForURL(url string) string {
+	// Replace all characters that might not be allowed in filenames with "-".
+	dir := regexp.MustCompile("[[:^alnum:]]").ReplaceAllString(url, "-")
+	// Work around length limit on some systems by truncating and then appending
+	// a sha256 hash of the URL.
+	if len(dir) > maxDirLength {
+		suffix := fmt.Sprintf("...%x", sha256.Sum256([]byte(url)))
+		dir = dir[:maxDirLength-len(suffix)] + suffix
+	}
+	return dir
+}
diff --git a/pkg/bazel/bazelisk.go b/pkg/bazel/bazelisk.go
index 18cc1dab0..bdf35c61e 100644
--- a/pkg/bazel/bazelisk.go
+++ b/pkg/bazel/bazelisk.go
@@ -19,21 +19,15 @@ package bazel
 import (
 	"bufio"
 	"fmt"
-	"io"
 	"log"
 	"os"
 	"os/exec"
-	"os/signal"
 	"path/filepath"
-	"regexp"
 	"strings"
-	"sync"
-	"syscall"
 
 	"github.com/bazelbuild/bazelisk/core"
 	"github.com/bazelbuild/bazelisk/httputil"
 	"github.com/bazelbuild/bazelisk/platforms"
-	"github.com/bazelbuild/bazelisk/versions"
 	"github.com/mitchellh/go-homedir"
 
 	"aspect.build/cli/buildinfo"
@@ -43,17 +37,8 @@ import (
 )
 
 const (
-	bazelReal          = "BAZEL_REAL"
-	skipWrapperEnv     = "BAZELISK_SKIP_WRAPPER"
 	aspectReentrantEnv = "ASPECT_REENTRANT"
 	useBazelVersionEnv = "USE_BAZEL_VERSION"
-	wrapperPath        = "./tools/bazel"
-	rcFileName         = ".bazeliskrc"
-)
-
-var (
-	fileConfig     map[string]string
-	fileConfigOnce sync.Once
 )
 
 type Bazelisk struct {
@@ -107,31 +92,10 @@ func (bazelisk *Bazelisk) GetBazelPath(repos *core.Repositories) (string, error)
 		return "", fmt.Errorf("could not expand home directory in path: %v", err)
 	}
 
-	// If the Bazel version is an absolute path to a Bazel binary in the filesystem, we can
-	// use it directly. In that case, we don't know which exact version it is, though.
-	resolvedBazelVersion := "unknown"
-
 	// If we aren't using a local Bazel binary, we'll have to parse the version string and
 	// download the version that the user wants.
 	if !filepath.IsAbs(bazelPath) {
-		bazelFork, bazelVersion, err := parseBazelForkAndVersion(bazelVersionString)
-		if err != nil {
-			return "", fmt.Errorf("could not parse Bazel fork and version: %v", err)
-		}
-
-		var downloader core.DownloadFunc
-		resolvedBazelVersion, downloader, err = repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion)
-		if err != nil {
-			return "", fmt.Errorf("could not resolve the version '%s' to an actual version number: %v", bazelVersion, err)
-		}
-
-		bazelForkOrURL := dirForURL(baseUrl)
-		if len(bazelForkOrURL) == 0 {
-			bazelForkOrURL = bazelFork
-		}
-
-		baseDirectory := filepath.Join(bazeliskHome, "downloads", bazelForkOrURL)
-		bazelPath, err = bazelisk.downloadBazel(resolvedBazelVersion, baseDirectory, repos, baseUrl, downloader)
+		bazelPath, err = downloadBazel(bazelVersionString, baseUrl, bazeliskHome, repos)
 		if err != nil {
 			return "", fmt.Errorf("could not download Bazel: %v", err)
 		}
@@ -169,100 +133,6 @@ func (bazelisk *Bazelisk) Run(args []string, repos *core.Repositories, streams i
 	return nil
 }
 
-func (bazelisk *Bazelisk) getUserAgent() string {
-	agent := bazelisk.GetEnvOrConfig("BAZELISK_USER_AGENT")
-	if len(agent) > 0 {
-		return agent
-	}
-	return fmt.Sprintf("Aspect/%s", buildinfo.Current().Version())
-}
-
-// GetConfig reads a configuration value from .bazeliskrc.
-func (bazelisk *Bazelisk) GetConfig(name string) string {
-	fileConfigOnce.Do(bazelisk.loadFileConfig)
-
-	return fileConfig[name]
-}
-
-// GetEnvOrConfig reads a configuration value from the environment, but fall back to reading it from .bazeliskrc.
-func (bazelisk *Bazelisk) GetEnvOrConfig(name string) string {
-	if val, found := os.LookupEnv(name); found {
-		return val
-	}
-
-	fileConfigOnce.Do(bazelisk.loadFileConfig)
-
-	return fileConfig[name]
-}
-
-// loadFileConfig locates available .bazeliskrc configuration files, parses them with a precedence order preference,
-// and updates a global configuration map with their contents. This routine should be executed exactly once.
-func (bazelisk *Bazelisk) loadFileConfig() {
-	var rcFilePaths []string
-
-	if userRC, err := locateUserConfigFile(); err == nil {
-		rcFilePaths = append(rcFilePaths, userRC)
-	}
-	if workspaceRC, err := bazelisk.locateWorkspaceConfigFile(); err == nil {
-		rcFilePaths = append(rcFilePaths, workspaceRC)
-	}
-
-	fileConfig = make(map[string]string)
-	for _, rcPath := range rcFilePaths {
-		config, err := parseFileConfig(rcPath)
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		for key, value := range config {
-			fileConfig[key] = value
-		}
-	}
-}
-
-// locateWorkspaceConfigFile locates a .bazeliskrc file in the current workspace root.
-func (bazelisk *Bazelisk) locateWorkspaceConfigFile() (string, error) {
-	return filepath.Join(bazelisk.workspaceRoot, rcFileName), nil
-}
-
-// locateUserConfigFile locates a .bazeliskrc file in the user's home directory.
-func locateUserConfigFile() (string, error) {
-	home, err := os.UserHomeDir()
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(home, rcFileName), nil
-}
-
-// parseFileConfig parses a .bazeliskrc file as a map of key-value configuration values.
-func parseFileConfig(rcFilePath string) (map[string]string, error) {
-	config := make(map[string]string)
-
-	contents, err := os.ReadFile(rcFilePath)
-	if err != nil {
-		if os.IsNotExist(err) {
-			// Non-critical error.
-			return config, nil
-		}
-		return nil, err
-	}
-
-	for _, line := range strings.Split(string(contents), "\n") {
-		if strings.HasPrefix(line, "#") {
-			// comments
-			continue
-		}
-		parts := strings.SplitN(line, "=", 2)
-		if len(parts) < 2 {
-			continue
-		}
-		key := strings.TrimSpace(parts[0])
-		config[key] = strings.TrimSpace(parts[1])
-	}
-
-	return config, nil
-}
-
 type aspectRuntimeInfo struct {
 	Reentrant bool
 	Version   string
@@ -350,9 +220,11 @@ func (bazelisk *Bazelisk) getBazelVersion() (string, string, error) {
 	// Same as upstream bazelisk at this point:
 	// https://github.com/bazelbuild/bazelisk/blob/c9081741bc1420d601140a4232b5c48872370fdc/core/core.go#L344
 
+	workspaceRoot := bazelisk.workspaceRoot
+
 	// Load the version from the .bazelversion file if we know the workspace root and it exists
-	if len(bazelisk.workspaceRoot) != 0 {
-		bazelVersionPath := filepath.Join(bazelisk.workspaceRoot, ".bazelversion")
+	if len(workspaceRoot) != 0 {
+		bazelVersionPath := filepath.Join(workspaceRoot, ".bazelversion")
 		if _, err := os.Stat(bazelVersionPath); err == nil {
 			f, err := os.Open(bazelVersionPath)
 			if err != nil {
@@ -395,75 +267,41 @@ func (bazelisk *Bazelisk) getBazelVersion() (string, string, error) {
 	return "", "", fmt.Errorf("invalid fallback version format %q (effectively %q)", fallbackVersionFormat, fmt.Sprintf("%s:%s", fallbackVersionMode, fallbackVersion))
 }
 
-func parseBazelForkAndVersion(bazelForkAndVersion string) (string, string, error) {
-	var bazelFork, bazelVersion string
+func downloadBazel(bazelVersionString, baseURL string, bazeliskHome string, repos *core.Repositories) (string, error) {
+	bazelFork, bazelVersion, err := parseBazelForkAndVersion(bazelVersionString)
+	if err != nil {
+		return "", fmt.Errorf("could not parse Bazel fork and version: %v", err)
+	}
 
-	versionInfo := strings.Split(bazelForkAndVersion, "/")
+	resolvedBazelVersion, downloader, err := repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion)
+	if err != nil {
+		return "", fmt.Errorf("could not resolve the version '%s' to an actual version number: %v", bazelVersion, err)
+	}
 
-	if len(versionInfo) == 1 {
-		bazelFork, bazelVersion = versions.BazelUpstream, versionInfo[0]
-	} else if len(versionInfo) == 2 {
-		bazelFork, bazelVersion = versionInfo[0], versionInfo[1]
-	} else {
-		return "", "", fmt.Errorf("invalid version \"%s\", could not parse version with more than one slash", bazelForkAndVersion)
+	bazelForkOrURL := dirForURL(baseURL)
+	if len(bazelForkOrURL) == 0 {
+		bazelForkOrURL = bazelFork
 	}
 
-	return bazelFork, bazelVersion, nil
+	baseDirectory := filepath.Join(bazeliskHome, "downloads", bazelForkOrURL)
+	bazelPath, err := downloadBazelIfNecessary(resolvedBazelVersion, baseDirectory, repos, baseURL, downloader)
+	return bazelPath, err
 }
 
-func (bazelisk *Bazelisk) downloadBazel(version string, baseDirectory string, repos *core.Repositories, baseUrl string, downloader core.DownloadFunc) (string, error) {
+func downloadBazelIfNecessary(version string, baseDirectory string, repos *core.Repositories, baseURL string, downloader core.DownloadFunc) (string, error) {
 	pathSegment, err := platforms.DetermineBazelFilename(version, false)
 	if err != nil {
 		return "", fmt.Errorf("could not determine path segment to use for Bazel binary: %v", err)
 	}
 
+	destDir := filepath.Join(baseDirectory, pathSegment, "bin")
 	destFile := "bazel" + platforms.DetermineExecutableFilenameSuffix()
-	destinationDir := filepath.Join(baseDirectory, pathSegment, "bin")
 
-	if baseUrl != "" {
-		return repos.DownloadFromBaseURL(baseUrl, version, destinationDir, destFile)
+	if baseURL != "" {
+		return repos.DownloadFromBaseURL(baseURL, version, destDir, destFile)
 	}
 
-	return downloader(destinationDir, destFile)
-}
-
-func copyFile(src, dst string, perm os.FileMode) error {
-	srcFile, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-	defer srcFile.Close()
-
-	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm)
-	if err != nil {
-		return err
-	}
-	defer dstFile.Close()
-
-	_, err = io.Copy(dstFile, srcFile)
-
-	return err
-}
-
-func linkLocalBazel(baseDirectory string, bazelPath string) (string, error) {
-	normalizedBazelPath := dirForURL(bazelPath)
-	destinationDir := filepath.Join(baseDirectory, normalizedBazelPath, "bin")
-	err := os.MkdirAll(destinationDir, 0755)
-	if err != nil {
-		return "", fmt.Errorf("could not create directory %s: %v", destinationDir, err)
-	}
-	destinationPath := filepath.Join(destinationDir, "bazel"+platforms.DetermineExecutableFilenameSuffix())
-	if _, err := os.Stat(destinationPath); err != nil {
-		err = os.Symlink(bazelPath, destinationPath)
-		// If can't create Symlink, fallback to copy
-		if err != nil {
-			err = copyFile(bazelPath, destinationPath, 0755)
-			if err != nil {
-				return "", fmt.Errorf("could not copy file from %s to %s: %v", bazelPath, destinationPath, err)
-			}
-		}
-	}
-	return destinationPath, nil
+	return downloader(destDir, destFile)
 }
 
 func (bazelisk *Bazelisk) maybeDelegateToWrapper(bazel string) string {
@@ -479,25 +317,6 @@ func (bazelisk *Bazelisk) maybeDelegateToWrapper(bazel string) string {
 	return wrapper
 }
 
-func prependDirToPathList(cmd *exec.Cmd, dir string) {
-	found := false
-	for idx, val := range cmd.Env {
-		splits := strings.Split(val, "=")
-		if len(splits) != 2 {
-			continue
-		}
-		if strings.EqualFold(splits[0], "PATH") {
-			found = true
-			cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
-			break
-		}
-	}
-
-	if !found {
-		cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir))
-	}
-}
-
 func (bazelisk *Bazelisk) makeBazelCmd(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) *exec.Cmd {
 	execPath := bazelisk.maybeDelegateToWrapper(bazel)
 
@@ -519,58 +338,3 @@ func (bazelisk *Bazelisk) makeBazelCmd(bazel string, args []string, streams iout
 	cmd.Stderr = streams.Stderr
 	return cmd
 }
-
-func (bazelisk *Bazelisk) runBazel(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) (int, error) {
-	cmd := bazelisk.makeBazelCmd(bazel, args, streams, env, wd)
-
-	err := cmd.Start()
-	if err != nil {
-		return 1, fmt.Errorf("could not start Bazel: %v", err)
-	}
-
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
-	go func() {
-		s := <-c
-
-		// Only forward SIGTERM to our child process.
-		if s != os.Interrupt {
-			cmd.Process.Kill()
-		}
-	}()
-
-	err = cmd.Wait()
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			waitStatus := exitError.Sys().(syscall.WaitStatus)
-			return waitStatus.ExitStatus(), nil
-		}
-		return 1, fmt.Errorf("could not launch Bazel: %v", err)
-	}
-	return 0, nil
-}
-
-// insertArgs will insert newArgs in baseArgs. If baseArgs contains the
-// "--" argument, newArgs will be inserted before that. Otherwise, newArgs
-// is appended.
-func insertArgs(baseArgs []string, newArgs []string) []string {
-	var result []string
-	inserted := false
-	for _, arg := range baseArgs {
-		if !inserted && arg == "--" {
-			result = append(result, newArgs...)
-			inserted = true
-		}
-		result = append(result, arg)
-	}
-
-	if !inserted {
-		result = append(result, newArgs...)
-	}
-	return result
-}
-
-func dirForURL(url string) string {
-	// Replace all characters that might not be allowed in filenames with "-".
-	return regexp.MustCompile("[[:^alnum:]]").ReplaceAllString(url, "-")
-}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index fb114b857..1ec8be440 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -112,6 +112,12 @@ func Warnf(format string, args ...interface{}) {
 	logger.Printf(format, args...)
 }
 
+func Error(msg string) {
+	if level > ErrorLevel {
+		return
+	}
+	logger.Print(msg)
+}
 func Errorf(format string, args ...interface{}) {
 	if level > ErrorLevel {
 		return
@@ -122,6 +128,9 @@ func Errorf(format string, args ...interface{}) {
 func Fatalf(format string, args ...interface{}) {
 	logger.Fatalf(format+"\n", args...)
 }
+func Fatal(msg string) {
+	logger.Fatal(msg + "\n")
+}
 
 func IsLevelEnabled(l Level) bool {
 	return level > l
diff --git a/pkg/plugin/sdk/v1alpha1/plugin/grpc.go b/pkg/plugin/sdk/v1alpha1/plugin/grpc.go
index ceefbbe1f..650d06e5e 100644
--- a/pkg/plugin/sdk/v1alpha1/plugin/grpc.go
+++ b/pkg/plugin/sdk/v1alpha1/plugin/grpc.go
@@ -21,6 +21,7 @@ package plugin
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	goplugin "github.com/hashicorp/go-plugin"
@@ -180,7 +181,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) {
 		return "", err
 	}
 	if res.Error != nil && res.Error.Happened {
-		return "", fmt.Errorf(res.Error.Message)
+		return "", errors.New(res.Error.Message)
 	}
 	return res.Result, nil
 }
diff --git a/pkg/plugin/sdk/v1alpha2/plugin/grpc.go b/pkg/plugin/sdk/v1alpha2/plugin/grpc.go
index 8d4790f18..ec80d54d0 100644
--- a/pkg/plugin/sdk/v1alpha2/plugin/grpc.go
+++ b/pkg/plugin/sdk/v1alpha2/plugin/grpc.go
@@ -21,6 +21,7 @@ package plugin
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	goplugin "github.com/hashicorp/go-plugin"
@@ -352,7 +353,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) {
 		return "", err
 	}
 	if res.Error != nil && res.Error.Happened {
-		return "", fmt.Errorf(res.Error.Message)
+		return "", errors.New(res.Error.Message)
 	}
 	return res.Result, nil
 }
diff --git a/pkg/plugin/sdk/v1alpha3/plugin/grpc.go b/pkg/plugin/sdk/v1alpha3/plugin/grpc.go
index b343b7577..18011e9f8 100644
--- a/pkg/plugin/sdk/v1alpha3/plugin/grpc.go
+++ b/pkg/plugin/sdk/v1alpha3/plugin/grpc.go
@@ -21,6 +21,7 @@ package plugin
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"sync"
 
@@ -347,7 +348,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) {
 		return "", err
 	}
 	if res.Error != nil && res.Error.Happened {
-		return "", fmt.Errorf(res.Error.Message)
+		return "", errors.New(res.Error.Message)
 	}
 	return res.Result, nil
 }
diff --git a/pkg/plugin/sdk/v1alpha4/plugin/grpc.go b/pkg/plugin/sdk/v1alpha4/plugin/grpc.go
index 497114052..ed2b9a997 100644
--- a/pkg/plugin/sdk/v1alpha4/plugin/grpc.go
+++ b/pkg/plugin/sdk/v1alpha4/plugin/grpc.go
@@ -21,6 +21,7 @@ package plugin
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"sync"
 
@@ -348,7 +349,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) {
 		return "", err
 	}
 	if res.Error != nil && res.Error.Happened {
-		return "", fmt.Errorf(res.Error.Message)
+		return "", errors.New(res.Error.Message)
 	}
 	return res.Result, nil
 }