From 2fd24c12f404b676b6a7c5e6e51e1a26a9998ff8 Mon Sep 17 00:00:00 2001 From: Grey Newell Date: Wed, 8 Apr 2026 17:39:50 -0400 Subject: [PATCH 1/2] fix(cmd): derive timeouts from cmd.Context() so Ctrl+C cancels API calls context.Background() ignores Cobra's command context, meaning Ctrl+C had no effect on in-flight API calls in audit, share, and restore. Derive all timeouts from cmd.Context() so cancellation propagates. Co-Authored-By: Claude Sonnet 4.6 --- cmd/audit.go | 2 +- cmd/restore.go | 4 ++-- cmd/share.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/audit.go b/cmd/audit.go index 09be46a..bd842c1 100644 --- a/cmd/audit.go +++ b/cmd/audit.go @@ -59,7 +59,7 @@ func runAudit(cmd *cobra.Command, dir string) error { return err } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Minute) defer cancel() // Fingerprint for caching — best-effort; empty string means no caching. diff --git a/cmd/restore.go b/cmd/restore.go index 98a8fdd..3d6e9aa 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -95,7 +95,7 @@ func runRestore(cmd *cobra.Command, dir string, localMode bool, maxTokens int) e if graph == nil { opts.LocalMode = true - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second) defer cancel() var err error graph, err = restore.BuildProjectGraph(ctx, rootDir, projectName) @@ -135,7 +135,7 @@ func restoreViaAPI(cmd *cobra.Command, cfg *config.Config, rootDir, projectName client := api.New(cfg) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Minute) defer cancel() fmt.Fprintln(cmd.ErrOrStderr(), "Analyzing repository…") diff --git a/cmd/share.go b/cmd/share.go index 005664e..fca0b66 100644 --- a/cmd/share.go +++ b/cmd/share.go @@ -50,7 +50,7 @@ func runShare(cmd *cobra.Command, dir string) error { return err } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Minute) defer cancel() fp, _ := cache.RepoFingerprint(rootDir) @@ -76,7 +76,7 @@ func runShare(cmd *cobra.Command, dir string) error { // Upload and get public URL. client := api.New(cfg) - uploadCtx, uploadCancel := context.WithTimeout(context.Background(), 2*time.Minute) + uploadCtx, uploadCancel := context.WithTimeout(ctx, 2*time.Minute) defer uploadCancel() fmt.Fprintln(cmd.ErrOrStderr(), "Uploading report…") From d8a7faf68a8eb4036f4ae4d2ddce6cfb3cb64683 Mon Sep 17 00:00:00 2001 From: Grey Newell Date: Wed, 8 Apr 2026 17:48:23 -0400 Subject: [PATCH 2/2] fix(compact): preserve Go files with //go:embed directives unchanged //go:embed must immediately precede its variable declaration; moving it to the top of the file (as scanGoDirectives was doing) detaches it from the variable, causing compilation failures at embed time. Fix: return the source unchanged when //go:embed is present, and remove //go:embed from scanGoDirectives (only //go:build, // +build, and //go:generate belong at the file top). Add a test to prevent regression. Co-Authored-By: Claude Sonnet 4.6 --- internal/compact/handler.go | 12 ++++++++++-- internal/compact/handler_test.go | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/compact/handler.go b/internal/compact/handler.go index 17df221..79e3af9 100644 --- a/internal/compact/handler.go +++ b/internal/compact/handler.go @@ -109,6 +109,13 @@ func CompactSource(src []byte, lang Language) ([]byte, error) { // compactGo strips non-directive comments via the Go AST, shortens local // identifiers, and removes blank lines. func compactGo(src []byte) ([]byte, error) { + // //go:embed directives must immediately precede their variable declaration + // and cannot be relocated. Return the source unchanged so that embed + // semantics and the "embed" import are preserved. + if bytes.Contains(src, []byte("//go:embed")) { + return src, nil + } + fset := token.NewFileSet() // Parsing without parser.ParseComments drops all comments from the AST. f, err := parser.ParseFile(fset, "", src, 0) @@ -137,14 +144,15 @@ func compactGo(src []byte) ([]byte, error) { } // scanGoDirectives extracts //go:build, // +build and //go:generate lines. +// //go:embed is intentionally excluded: it must remain adjacent to its variable +// declaration and cannot be moved to the file top (handled by early return in compactGo). func scanGoDirectives(src []byte) []byte { var out []byte for _, line := range bytes.Split(src, []byte("\n")) { text := strings.TrimSpace(string(line)) if strings.HasPrefix(text, "//go:build") || strings.HasPrefix(text, "// +build") || - strings.HasPrefix(text, "//go:generate") || - strings.HasPrefix(text, "//go:embed") { + strings.HasPrefix(text, "//go:generate") { out = append(out, bytes.TrimRight(line, " \t")...) out = append(out, '\n') } diff --git a/internal/compact/handler_test.go b/internal/compact/handler_test.go index 7ec9c33..756792b 100644 --- a/internal/compact/handler_test.go +++ b/internal/compact/handler_test.go @@ -1,6 +1,7 @@ package compact import ( + "bytes" "go/parser" "go/token" "os" @@ -113,6 +114,25 @@ func Foo() {} } } +func TestCompactGoPreservesEmbedFiles(t *testing.T) { + // //go:embed must stay adjacent to its var declaration and cannot be moved + // to the file top. Files containing it should be returned unchanged. + src := []byte(`package foo + +import _ "embed" + +//go:embed hello.txt +var hello string +`) + got, err := CompactSource(src, Go) + if err != nil { + t.Fatalf("CompactSource error: %v", err) + } + if !bytes.Equal(got, src) { + t.Errorf("expected source unchanged for //go:embed file, got:\n%s", got) + } +} + func TestCompactGoReducesSize(t *testing.T) { src := []byte(`// Package math provides basic math utilities. // It is intentionally simple.