Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ permissions:

jobs:
auto-release:
uses: open-cli-collective/.github/.github/workflows/auto-release.yml@v1
uses: open-cli-collective/.github/.github/workflows/auto-release.yml@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
# push is live (false); workflow_dispatch honors the checkbox. Compare
# against both true and 'true' because a workflow_dispatch boolean input
Expand Down
26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/go-build@v1
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/go-build@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
go-version-file: go.mod

Expand All @@ -43,8 +43,8 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/go-test@v1
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/go-test@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
go-version-file: go.mod

Expand All @@ -53,8 +53,8 @@ jobs:
env:
GOFLAGS: -tags=keyring_nopassage,keyring_no1password
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/go-test@v1
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/go-test@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
go-version-file: go.mod

Expand All @@ -64,30 +64,30 @@ jobs:
CGO_ENABLED: "0"
GOFLAGS: -tags=keyring_nopassage
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/go-test@v1
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/go-test@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
go-version-file: go.mod
target: test-static-smoke

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/go-lint@v1
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/go-lint@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
go-version-file: go.mod

identity-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: open-cli-collective/.github/actions/identity-check@v1 # distribution.md §8.2
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- uses: open-cli-collective/.github/actions/identity-check@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github; distribution.md §8.2

pr-title:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: open-cli-collective/.github/actions/pr-title@v1
- uses: open-cli-collective/.github/actions/pr-title@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
title: ${{ github.event.pull_request.title }}
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ permissions:

jobs:
release:
uses: open-cli-collective/.github/.github/workflows/release.yml@v1
uses: open-cli-collective/.github/.github/workflows/release.yml@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github
with:
# tag push is live (false); workflow_dispatch honors the checkbox. Compare
# against both true and 'true' because a workflow_dispatch boolean input
Expand Down
4 changes: 4 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ return typed domain data so command and view code remain replaceable shells.
`internal/architecture/command_boundaries_test.go` enforces these dependency
directions with narrow allowances for command-tree integration tests and keeps
review/response application runtime contracts out of `internal/cmd/cmdruntime`.
The architecture checks enforce package ownership and dependency direction;
the command-runtime checks leave helper names free to change, the
planned-action payload check leaves its source filename free to change, and
the thread-lifecycle checks do not prescribe per-file call counts.

Review behavior should be protected through named acceptance harnesses rather
than cloned broad assertions. The command-level harness verifies `cr review`
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ require (
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/huh v1.0.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/ansi v0.9.3
github.com/creack/pty v1.1.24
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.6.0
github.com/open-cli-collective/cli-common v0.4.1
github.com/spf13/cobra v1.10.2
go.yaml.in/yaml/v3 v3.0.5
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.51.0
)

Expand All @@ -29,7 +30,6 @@ require (
github.com/byteness/keyring v1.11.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pq
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
Expand Down
2 changes: 1 addition & 1 deletion internal/agents/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"strings"

"github.com/gobwas/glob"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"

"github.com/open-cli-collective/codereview-cli/internal/gitprovider"
"github.com/open-cli-collective/codereview-cli/internal/modelprefs"
Expand Down
45 changes: 2 additions & 43 deletions internal/architecture/command_boundaries_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package architecture_test

import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
Expand Down Expand Up @@ -114,7 +113,7 @@ func TestApplicationPackagesStayOutOfCommandAndViewLayers(t *testing.T) {
}
}

func TestCommandRuntimeDoesNotOwnApplicationRuntimeContracts(t *testing.T) {
func TestCommandRuntimeStaysWithinCommandLayer(t *testing.T) {
repoRoot := repoRootFromTest(t)
modulePath := "github.com/open-cli-collective/codereview-cli"
cmdRuntimeDir := filepath.Join(repoRoot, "internal", "cmd", "cmdruntime")
Expand All @@ -134,21 +133,6 @@ func TestCommandRuntimeDoesNotOwnApplicationRuntimeContracts(t *testing.T) {
modulePath + "/internal/credentials": true,
modulePath + "/internal/gitprovider": true,
}
allowedExports := map[string]bool{
"ConfigPath": true,
"MapRunError": true,
"MissingResponderError": true,
"ReadOptionalSecretIngress": true,
"ReadSecretIngress": true,
}
allowedFunctions := map[string]bool{
"ConfigPath": true,
"ingressName": true,
"MapRunError": true,
"MissingResponderError": true,
"ReadOptionalSecretIngress": true,
"ReadSecretIngress": true,
}
fset := token.NewFileSet()
err := filepath.WalkDir(cmdRuntimeDir, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
Expand All @@ -157,7 +141,7 @@ func TestCommandRuntimeDoesNotOwnApplicationRuntimeContracts(t *testing.T) {
if entry.IsDir() || filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
return nil
}
parsed, err := parser.ParseFile(fset, path, nil, 0)
parsed, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
if err != nil {
return err
}
Expand All @@ -171,31 +155,6 @@ func TestCommandRuntimeDoesNotOwnApplicationRuntimeContracts(t *testing.T) {
t.Fatalf("%s imports %s; cmdruntime should stay limited to command-layer config/error helpers", pos, importPath)
}
}
for _, decl := range parsed.Decls {
switch typed := decl.(type) {
case *ast.FuncDecl:
if typed.Name == nil {
continue
}
if !allowedFunctions[typed.Name.Name] {
pos := fset.Position(typed.Pos())
t.Fatalf("%s declares %s; cmdruntime should only keep the approved command-layer helper surface", pos, typed.Name.Name)
}
if ast.IsExported(typed.Name.Name) && !allowedExports[typed.Name.Name] {
pos := fset.Position(typed.Pos())
t.Fatalf("%s exports %s; cmdruntime should only export command-layer config/error helpers", pos, typed.Name.Name)
}
case *ast.GenDecl:
if typed.Tok == token.IMPORT {
continue
}
pos := fset.Position(typed.Pos())
t.Fatalf("%s declares %s; cmdruntime should not own top-level %s beyond imports", pos, typed.Tok.String(), typed.Tok.String())
default:
pos := fset.Position(decl.Pos())
t.Fatalf("%s declares unsupported top-level syntax in cmdruntime", pos)
}
}
return nil
})
if err != nil {
Expand Down
15 changes: 8 additions & 7 deletions internal/architecture/plannedactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"testing"
)

func TestPayloadStructsAreOwnedByPlannedActions(t *testing.T) {
func TestPayloadStructsAreDefinedOnceInPlannedActions(t *testing.T) {
repoRoot := repoRootFromTest(t)
want := map[string]bool{
"InlineCommentPayload": false,
Expand Down Expand Up @@ -44,12 +44,12 @@ func TestPayloadStructsAreOwnedByPlannedActions(t *testing.T) {
if _, tracked := want[typeSpec.Name.Name]; !tracked {
continue
}
rel := filepath.ToSlash(mustRel(t, repoRoot, path))
if rel != "internal/plannedactions/plannedactions.go" {
t.Fatalf("%s declares %s; payload structs belong in internal/plannedactions", rel, typeSpec.Name.Name)
relDir := filepath.ToSlash(mustRel(t, repoRoot, filepath.Dir(path)))
if relDir != "internal/plannedactions" {
t.Fatalf("%s declares %s; payload structs belong in the internal/plannedactions package", relDir, typeSpec.Name.Name)
}
if want[typeSpec.Name.Name] {
t.Fatalf("%s declares %s more than once", rel, typeSpec.Name.Name)
t.Fatalf("%s declares %s more than once", relDir, typeSpec.Name.Name)
}
want[typeSpec.Name.Name] = true
}
Expand Down Expand Up @@ -109,15 +109,16 @@ func TestPlannedActionPayloadJSONIsLedgerPrivate(t *testing.T) {
return err
}
rel := filepath.ToSlash(mustRel(t, repoRoot, path))
relDir := filepath.ToSlash(mustRel(t, repoRoot, filepath.Dir(path)))
ast.Inspect(parsed, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.SelectorExpr:
if node.Sel.Name == "PayloadJSON" {
t.Fatalf("%s exposes raw planned-action payload JSON", rel)
}
case *ast.BasicLit:
if strings.Contains(node.Value, "payload_json") && rel != "internal/ledger/ledger.go" {
t.Fatalf("%s accesses ledger payload_json outside ledger", rel)
if strings.Contains(node.Value, "payload_json") && relDir != "internal/ledger" {
t.Fatalf("%s accesses ledger payload_json outside the internal/ledger package", rel)
}
}
return true
Expand Down
70 changes: 70 additions & 0 deletions internal/architecture/stdlib_imports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package architecture_test

import (
"bytes"
"go/parser"
"go/token"
"io/fs"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
)

func TestSelectedProductionPackagesStayStdlibOnly(t *testing.T) {
repoRoot := repoRootFromTest(t)
stdlib := standardLibraryImports(t, repoRoot)
for _, relDir := range []string{"internal/gate", "internal/fsatomic", "internal/marker"} {
t.Run(relDir, func(t *testing.T) {
checkStdlibOnlyPackage(t, repoRoot, relDir, stdlib)
})
}
}

func checkStdlibOnlyPackage(t *testing.T, repoRoot, relDir string, stdlib map[string]struct{}) {
t.Helper()
root := filepath.Join(repoRoot, filepath.FromSlash(relDir))
fset := token.NewFileSet()
err := filepath.WalkDir(root, func(filePath string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || filepath.Ext(filePath) != ".go" || strings.HasSuffix(filePath, "_test.go") {
return nil
}
parsed, err := parser.ParseFile(fset, filePath, nil, parser.ImportsOnly)
if err != nil {
return err
}
for _, imported := range parsed.Imports {
importedPath, err := strconv.Unquote(imported.Path.Value)
if err != nil {
return err
}
rel := filepath.ToSlash(mustRel(t, repoRoot, filePath))
if _, ok := stdlib[importedPath]; !ok {
t.Fatalf("%s imports %q, want standard library only", rel, importedPath)
}
}
return nil
})
if err != nil {
t.Fatalf("WalkDir(%s): %v", root, err)
}
}

func standardLibraryImports(t *testing.T, repoRoot string) map[string]struct{} {
t.Helper()
cmd := exec.Command("go", "list", "std")
cmd.Dir = repoRoot
output, err := cmd.Output()
if err != nil {
t.Fatalf("go list std: %v", err)
}
imports := make(map[string]struct{})
for _, path := range bytes.Fields(output) {
imports[string(path)] = struct{}{}
}
return imports
}
30 changes: 0 additions & 30 deletions internal/architecture/thread_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,36 +120,6 @@ func TestPackagesStayOnLayeredSeams(t *testing.T) {
}
}

func TestReviewAndRespondUseSharedThreadAnalysisBatch(t *testing.T) {
repoRoot := repoRootFromTest(t)
for _, rel := range []string{"internal/pipeline/pipeline.go", "internal/threadrespond/threadrespond.go"} {
path := filepath.Join(repoRoot, filepath.FromSlash(rel))
parsed, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
if err != nil {
t.Fatalf("ParseFile(%s): %v", path, err)
}
calls := map[string]int{}
ast.Inspect(parsed, func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
selector, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
pkg, ok := selector.X.(*ast.Ident)
if ok && pkg.Name == "threadanalysis" {
calls[selector.Sel.Name]++
}
return true
})
if calls["AnalyzeThreads"] != 1 || calls["AnalyzeThread"] != 0 || calls["ResponseActions"] != 1 {
t.Fatalf("%s threadanalysis calls = %#v, want one shared batch and response conversion with no caller-owned single-thread loop", rel, calls)
}
}
}

func checkPackageImports(t *testing.T, repoRoot, dir string, blocked map[string]bool) {
t.Helper()
root := filepath.Join(repoRoot, filepath.FromSlash(dir))
Expand Down
2 changes: 1 addition & 1 deletion internal/benchmark/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"regexp"
"strings"

"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"

"github.com/open-cli-collective/codereview-cli/internal/config"
"github.com/open-cli-collective/codereview-cli/internal/modelprefs"
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/configcmd/configcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/open-cli-collective/cli-common/credstore"
"github.com/open-cli-collective/cli-common/statedirtest"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"

"github.com/open-cli-collective/codereview-cli/internal/agents"
"github.com/open-cli-collective/codereview-cli/internal/cmd/cmdtest"
Expand Down
Loading