From ada025c5b1a5a6e06342e3abb0b4835ca185f6c3 Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Tue, 9 Jun 2026 12:01:28 -0400 Subject: [PATCH 1/6] Set up sidecar demo with task 1 broken test fixture. Adds internal/racefixture with a failing TestSum for the video demo loop and routes lint and test-changed validation to the sidecar. Co-authored-by: Cursor --- .chunk/config.json | 6 ++++-- internal/racefixture/racefixture.go | 6 ++++++ internal/racefixture/racefixture_test.go | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 internal/racefixture/racefixture.go create mode 100644 internal/racefixture/racefixture_test.go diff --git a/.chunk/config.json b/.chunk/config.json index 2833c948..3a56f3ea 100644 --- a/.chunk/config.json +++ b/.chunk/config.json @@ -15,7 +15,8 @@ "role": "precheck", "fileExt": ".go", "timeout": 300, - "limit": 3 + "limit": 3, + "remote": true }, { "name": "lint", @@ -23,7 +24,8 @@ "role": "gate", "fileExt": ".go", "timeout": 60, - "limit": 3 + "limit": 3, + "remote": true }, { "name": "format", diff --git a/internal/racefixture/racefixture.go b/internal/racefixture/racefixture.go new file mode 100644 index 00000000..112d9c05 --- /dev/null +++ b/internal/racefixture/racefixture.go @@ -0,0 +1,6 @@ +package racefixture + +// Sum returns the sum of a and b. +func Sum(a, b int) int { + return a + b +} diff --git a/internal/racefixture/racefixture_test.go b/internal/racefixture/racefixture_test.go new file mode 100644 index 00000000..6b660537 --- /dev/null +++ b/internal/racefixture/racefixture_test.go @@ -0,0 +1,9 @@ +package racefixture + +import "testing" + +func TestSum(t *testing.T) { + if got := Sum(1, 2); got != 99 { + t.Fatalf("Sum(1, 2) = %d, want 3", got) + } +} From daa4de73ee1197e4160b6b2f8fbc97b15e5f2e28 Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Tue, 9 Jun 2026 13:45:49 -0400 Subject: [PATCH 2/6] place failure in `Sum` function --- internal/racefixture/racefixture.go | 2 +- internal/racefixture/racefixture_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/racefixture/racefixture.go b/internal/racefixture/racefixture.go index 112d9c05..b1e5d8a0 100644 --- a/internal/racefixture/racefixture.go +++ b/internal/racefixture/racefixture.go @@ -2,5 +2,5 @@ package racefixture // Sum returns the sum of a and b. func Sum(a, b int) int { - return a + b + return a - b } diff --git a/internal/racefixture/racefixture_test.go b/internal/racefixture/racefixture_test.go index 6b660537..1c4cde18 100644 --- a/internal/racefixture/racefixture_test.go +++ b/internal/racefixture/racefixture_test.go @@ -3,7 +3,7 @@ package racefixture import "testing" func TestSum(t *testing.T) { - if got := Sum(1, 2); got != 99 { + if got := Sum(1, 2); got != 3 { t.Fatalf("Sum(1, 2) = %d, want 3", got) } } From c6089cd4488348bcda652615f7f54c357930483c Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Thu, 11 Jun 2026 14:18:05 -0400 Subject: [PATCH 3/6] Fix sidecar validate failures: Sum bug, time-bomb tests, remote {{CHANGED_PACKAGES}} - racefixture.Sum: return a + b (was a - b), unbreaking TestSum. - build-prompt acceptance tests: pin --since 2025-01-01 in the six tests that relied on the default 3-month window, which had slid past the fixed 2026-03-0x fixture dates and dropped all comments/reviewers. - validate.RunRemote: expand template variables (e.g. {{CHANGED_PACKAGES}}) against the local working tree before sending commands to the sidecar. Previously only the local path expanded them, so the literal template reached the remote shell and go-task failed to parse it. Threads the local workDir through the three call sites and adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- acceptance/build_prompt_test.go | 6 +++++ internal/cmd/validate.go | 6 ++--- internal/racefixture/racefixture.go | 2 +- internal/validate/validate.go | 11 ++++++--- internal/validate/validate_test.go | 38 ++++++++++++++++++++++------- 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/acceptance/build_prompt_test.go b/acceptance/build_prompt_test.go index 1677174b..3fef7772 100644 --- a/acceptance/build_prompt_test.go +++ b/acceptance/build_prompt_test.go @@ -48,6 +48,7 @@ func TestBuildPromptHappyPath(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--top", "2", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) @@ -348,6 +349,7 @@ func TestBuildPromptBotFiltering(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--top", "5", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) @@ -491,6 +493,7 @@ func TestBuildPromptTopNFiltering(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--top", "2", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) @@ -575,6 +578,7 @@ func TestBuildPromptDefaultTop(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) @@ -848,6 +852,7 @@ func TestBuildPromptTopOne(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--top", "1", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) @@ -902,6 +907,7 @@ func TestBuildPromptMaxCommentsEffect(t *testing.T) { "build-prompt", "--org", "test-org", "--repos", "test-repo", + "--since", "2025-01-01", "--max-comments", "1", "--output", filepath.Join(workDir, "review-prompt.md"), }, env, workDir) diff --git a/internal/cmd/validate.go b/internal/cmd/validate.go index c1393d5d..d38e9dff 100644 --- a/internal/cmd/validate.go +++ b/internal/cmd/validate.go @@ -320,7 +320,7 @@ func runValidate(ctx context.Context, client *circleci.Client, rc config.Resolve if err != nil { return err } - return validate.RunRemote(ctx, execFn, cfg, name, dest, statusFn, streams) + return validate.RunRemote(ctx, execFn, cfg, name, workDir, dest, statusFn, streams) } // Per-command remote routing: commands with Remote:true go to the sidecar, @@ -333,7 +333,7 @@ func runValidate(ctx context.Context, client *circleci.Client, rc config.Resolve if err != nil { return err } - return validate.RunRemote(ctx, execFn, cfg, name, dest, statusFn, streams) + return validate.RunRemote(ctx, execFn, cfg, name, workDir, dest, statusFn, streams) } statusFn(iostream.LevelInfo, fmt.Sprintf("running %s locally (not marked remote)", name)) // Named command is not marked remote; fall through to local execution. @@ -503,7 +503,7 @@ func runSplitCommands(ctx context.Context, client *circleci.Client, sidecarID st streams.ErrPrintf("warning: %v (%q); run 'chunk sidecar env build' to set up the workspace; running %s locally instead\n", wsErr, dest, commandNames(remoteCfg.Commands)) localCfg.Commands = append(remoteCfg.Commands, localCfg.Commands...) } else { - runErr = validate.RunRemote(ctx, execFn, remoteCfg, "", dest, statusFn, streams) + runErr = validate.RunRemote(ctx, execFn, remoteCfg, "", workDir, dest, statusFn, streams) } } if len(localCfg.Commands) > 0 { diff --git a/internal/racefixture/racefixture.go b/internal/racefixture/racefixture.go index b1e5d8a0..112d9c05 100644 --- a/internal/racefixture/racefixture.go +++ b/internal/racefixture/racefixture.go @@ -2,5 +2,5 @@ package racefixture // Sum returns the sum of a and b. func Sum(a, b int) int { - return a - b + return a + b } diff --git a/internal/validate/validate.go b/internal/validate/validate.go index 557eb663..bb15a086 100644 --- a/internal/validate/validate.go +++ b/internal/validate/validate.go @@ -108,7 +108,7 @@ func RunDryRun(cfg *config.ProjectConfig, name string, status iostream.StatusFun // RunRemote runs commands on a remote sidecar via SSH. // If name is non-empty, only the named command is run. -func RunRemote(ctx context.Context, execFn func(ctx context.Context, script string) (stdout, stderr string, exitCode int, err error), cfg *config.ProjectConfig, name, dest string, status iostream.StatusFunc, streams iostream.Streams) error { +func RunRemote(ctx context.Context, execFn func(ctx context.Context, script string) (stdout, stderr string, exitCode int, err error), cfg *config.ProjectConfig, name, workDir, dest string, status iostream.StatusFunc, streams iostream.Streams) error { commands := cfg.Commands if name != "" { c := cfg.FindCommand(name) @@ -118,8 +118,13 @@ func RunRemote(ctx context.Context, execFn func(ctx context.Context, script stri commands = []config.Command{*c} } for _, c := range commands { - script := "cd " + shellEscape(dest) + " && " + c.Run - status(iostream.LevelInfo, fmt.Sprintf("Running %s (remote): %s", c.Name, c.Run)) + // Expand template variables (e.g. {{CHANGED_PACKAGES}}) against the + // local working tree before sending the command to the sidecar; the + // synced remote tree mirrors it. Without this the literal template + // reaches the remote shell and tools like go-task fail to parse it. + run := expandCommand(workDir, c.Run) + script := "cd " + shellEscape(dest) + " && " + run + status(iostream.LevelInfo, fmt.Sprintf("Running %s (remote): %s", c.Name, run)) stdout, stderr, exitCode, err := execFn(ctx, script) if err != nil { return fmt.Errorf("remote %s: %w", c.Name, err) diff --git a/internal/validate/validate_test.go b/internal/validate/validate_test.go index dfd2618f..774c546d 100644 --- a/internal/validate/validate_test.go +++ b/internal/validate/validate_test.go @@ -281,7 +281,7 @@ func TestRunRemote(t *testing.T) { }} streams, out, _ := newStreams() - assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "/workspace", func(iostream.Level, string) {}, streams)) + assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "", "/workspace", func(iostream.Level, string) {}, streams)) assert.Assert(t, strings.Contains(out.String(), "remote output"), "got: %s", out.String()) assert.Equal(t, execCount, 2) }) @@ -296,7 +296,7 @@ func TestRunRemote(t *testing.T) { }} streams, _, _ := newStreams() - err := RunRemote(context.Background(), execFn, cfg, "", "/workspace", func(iostream.Level, string) {}, streams) + err := RunRemote(context.Background(), execFn, cfg, "", "", "/workspace", func(iostream.Level, string) {}, streams) assert.ErrorContains(t, err, "remote test failed") }) @@ -310,7 +310,7 @@ func TestRunRemote(t *testing.T) { }} streams, out, _ := newStreams() - assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "/workspace", func(iostream.Level, string) {}, streams)) + assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "", "/workspace", func(iostream.Level, string) {}, streams)) assert.Equal(t, out.Len(), 0) }) @@ -327,7 +327,7 @@ func TestRunRemote(t *testing.T) { }} streams, _, _ := newStreams() - assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "test", "/workspace", func(iostream.Level, string) {}, streams)) + assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "test", "", "/workspace", func(iostream.Level, string) {}, streams)) assert.Equal(t, len(capturedScripts), 1) assert.Assert(t, strings.Contains(capturedScripts[0], "echo test"), "got: %s", capturedScripts[0]) }) @@ -342,7 +342,7 @@ func TestRunRemote(t *testing.T) { }} streams, _, _ := newStreams() - err := RunRemote(context.Background(), execFn, cfg, "lint", "/workspace", func(iostream.Level, string) {}, streams) + err := RunRemote(context.Background(), execFn, cfg, "lint", "", "/workspace", func(iostream.Level, string) {}, streams) assert.ErrorContains(t, err, `"lint" not configured`) }) @@ -358,9 +358,29 @@ func TestRunRemote(t *testing.T) { }} streams, _, _ := newStreams() - assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "/custom/path", func(iostream.Level, string) {}, streams)) + assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", "", "/custom/path", func(iostream.Level, string) {}, streams)) assert.Assert(t, strings.HasPrefix(capturedScript, "cd '/custom/path' &&"), "got: %s", capturedScript) }) + + t.Run("expands CHANGED_PACKAGES before sending remotely", func(t *testing.T) { + var capturedScript string + execFn := func(_ context.Context, script string) (string, string, int, error) { + capturedScript = script + return "", "", 0, nil + } + + cfg := &config.ProjectConfig{Commands: []config.Command{ + {Name: "test-changed", Run: "task test -- {{CHANGED_PACKAGES}}"}, + }} + streams, _, _ := newStreams() + + // workDir is not a git repo, so expansion falls back to "./...". + // The literal template must never reach the remote shell, where + // tools like go-task would fail to parse it. + assert.NilError(t, RunRemote(context.Background(), execFn, cfg, "", t.TempDir(), "/workspace", func(iostream.Level, string) {}, streams)) + assert.Assert(t, strings.Contains(capturedScript, "task test -- ./..."), "got: %s", capturedScript) + assert.Assert(t, !strings.Contains(capturedScript, "{{CHANGED_PACKAGES}}"), "template should be expanded, got: %s", capturedScript) + }) } // TestRunRemoteSSH tests RunRemote end-to-end with a real fake SSH server, @@ -404,7 +424,7 @@ func TestRunRemoteSSH(t *testing.T) { }} streams, out, _ := newStreams() - assert.NilError(t, RunRemote(context.Background(), execCallback(t, session), cfg, "", "/workspace/repo", func(iostream.Level, string) {}, streams)) + assert.NilError(t, RunRemote(context.Background(), execCallback(t, session), cfg, "", "", "/workspace/repo", func(iostream.Level, string) {}, streams)) assert.Assert(t, strings.Contains(out.String(), "hello from remote"), "got: %s", out.String()) assert.Equal(t, len(sshSrv.Commands()), 1) }) @@ -429,7 +449,7 @@ func TestRunRemoteSSH(t *testing.T) { }} streams, _, _ := newStreams() - err = RunRemote(context.Background(), execCallback(t, session), cfg, "", "/workspace/repo", func(iostream.Level, string) {}, streams) + err = RunRemote(context.Background(), execCallback(t, session), cfg, "", "", "/workspace/repo", func(iostream.Level, string) {}, streams) assert.ErrorContains(t, err, "remote test failed") }) @@ -454,7 +474,7 @@ func TestRunRemoteSSH(t *testing.T) { }} streams, _, _ := newStreams() - err = RunRemote(context.Background(), execCallback(t, session), cfg, "", "/workspace/repo", func(iostream.Level, string) {}, streams) + err = RunRemote(context.Background(), execCallback(t, session), cfg, "", "", "/workspace/repo", func(iostream.Level, string) {}, streams) assert.ErrorContains(t, err, "remote install failed") assert.Equal(t, len(sshSrv.Commands()), 1) }) From 203fb7054c977c10bbf736e96c2be97817ccc30f Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Thu, 11 Jun 2026 14:26:08 -0400 Subject: [PATCH 4/6] Point validation.sidecarImage at go-uv-lint snapshot Snapshot fb982185 captures the green sidecar environment, including uv installed to /usr/local/bin (required by the harness pylint step in `task lint`). Verified: a fresh sidecar booted from this snapshot runs `chunk validate lint` green (golangci-lint 0 issues, pylint 10.00/10). Co-Authored-By: Claude Opus 4.8 (1M context) --- .chunk/config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.chunk/config.json b/.chunk/config.json index 3a56f3ea..0593a30e 100644 --- a/.chunk/config.json +++ b/.chunk/config.json @@ -41,8 +41,9 @@ "repo": "chunk-cli" }, "validation": { - "sidecarImage": "ac042eab-a45f-452e-a4f5-1d56e1b026bd" + "sidecarImage": "fb982185-d7bd-483d-a0f4-7204dbac62f3" }, + "orgID": "f22b6566-597d-46d5-ba74-99ef5bb3d85c", "environment": { "stack": "go", "setup": [ @@ -61,6 +62,5 @@ ], "image": "cimg/go", "image_version": "1.26.2" - }, - "orgID": "f22b6566-597d-46d5-ba74-99ef5bb3d85c" + } } From e61ead4227c0759cca51143af525406f562a9c52 Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Thu, 11 Jun 2026 14:27:40 -0400 Subject: [PATCH 5/6] `Sum(a, b)` func will now do subtraction --- internal/racefixture/racefixture.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/racefixture/racefixture.go b/internal/racefixture/racefixture.go index 112d9c05..b1e5d8a0 100644 --- a/internal/racefixture/racefixture.go +++ b/internal/racefixture/racefixture.go @@ -2,5 +2,5 @@ package racefixture // Sum returns the sum of a and b. func Sum(a, b int) int { - return a + b + return a - b } From 81d6d1a826bc246afb52f31554e1a6ec659c5023 Mon Sep 17 00:00:00 2001 From: "Ryan E. Hamilton" Date: Thu, 11 Jun 2026 14:36:05 -0400 Subject: [PATCH 6/6] Fix Sum to add instead of subtract racefixture.Sum returned a - b, failing TestSum (Sum(1,2) = -1, want 3). Restore the documented addition behavior so the remote validate gate passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/racefixture/racefixture.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/racefixture/racefixture.go b/internal/racefixture/racefixture.go index b1e5d8a0..112d9c05 100644 --- a/internal/racefixture/racefixture.go +++ b/internal/racefixture/racefixture.go @@ -2,5 +2,5 @@ package racefixture // Sum returns the sum of a and b. func Sum(a, b int) int { - return a - b + return a + b }