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
17 changes: 16 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ concurrency:
cancel-in-progress: true

env:
GO_VERSION: "1.25.x"
GO_VERSION: "1.26.x"
GOLANGCI_VERSION: "v2.12.2"
GOVULNCHECK_VERSION: "v1.6.0"

jobs:
build-test-lint:
Expand Down Expand Up @@ -59,11 +60,25 @@ jobs:
- name: Format and imports check
run: golangci-lint fmt --diff ./...

- name: Vulnerability scan
run: |
go install golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}
govulncheck ./...

- name: Build
run: go build -trimpath ./...

- name: Unit tests with race detector
run: go test -race -count=1 -coverprofile=coverage.out ./...

- name: Warm-cache TUI p95 latency
run: make perf

- name: Binary integration smoke test
run: go test -race -count=1 -tags=e2e ./tests/e2e

- name: Install pinned kind
run: go install sigs.k8s.io/kind@v0.32.0

- name: Real two-cluster fan-out test
run: make e2e-kind KIND="$(go env GOPATH)/bin/kind"
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Thumbs.db
*.key
*.pem
*.pfx
kubeconfig
/kubeconfig
*.kubeconfig
secrets/
.secrets/
Expand Down
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ version: "2"
run:
timeout: 5m
tests: true
build-tags:
- e2e
- kind

linters:
default: none
Expand Down
18 changes: 16 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ PKG := github.com/ArdurAI/sith
CMD := ./cmd/sith
BIN_DIR := bin
GOLANGCI ?= golangci-lint
GOVULNCHECK ?= govulncheck
KIND ?= kind

KIND_NODE_IMAGE ?= kindest/node:v1.36.1@sha256:3489c7674813ba5d8b1a9977baea8a6e553784dab7b84759d1014dbd78f7ebd5

VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
Expand All @@ -16,7 +20,7 @@ LDFLAGS := -s -w \
-X $(PKG)/internal/buildinfo.Commit=$(COMMIT) \
-X $(PKG)/internal/buildinfo.Date=$(DATE)

.PHONY: all build test e2e lint fmt fmt-check vet tidy clean run ci help
.PHONY: all build test perf e2e e2e-kind lint vuln fmt fmt-check vet tidy clean run ci help

all: build

Expand All @@ -27,12 +31,22 @@ build: ## Build the sith binary into bin/
test: ## Run unit tests with the race detector and report coverage
go test -race -count=1 -coverprofile=coverage.out ./...

perf: ## Enforce the warm-cache TUI p95 latency budget without race overhead
go test -count=1 -run '^TestWarmViewP95UnderOneHundredMilliseconds$$' ./internal/tui

e2e: ## Build and exercise the real binary as a subprocess
go test -race -count=1 -tags=e2e ./tests/e2e

e2e-kind: ## Exercise adapter and binary against two real kind clusters
KIND_BIN="$(KIND)" KIND_NODE_IMAGE="$(KIND_NODE_IMAGE)" \
go test -race -count=1 -timeout=15m -tags='e2e kind' -run '^TestKindFleetFanout$$' ./tests/e2e

lint: ## Run golangci-lint (v2)
$(GOLANGCI) run ./...

vuln: ## Scan reachable Go call paths for known vulnerabilities
$(GOVULNCHECK) ./...

fmt: ## Format code (gofmt + goimports via golangci-lint v2 formatters)
$(GOLANGCI) fmt ./...

Expand All @@ -53,7 +67,7 @@ clean: ## Remove build and coverage artifacts
run: build ## Build then run sith version
$(BIN_DIR)/$(BINARY) version

ci: fmt-check vet lint test e2e build ## Run the full CI gate locally
ci: fmt-check vet lint vuln test perf e2e build ## Run the full CI gate locally

help: ## List targets
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | \
Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Sith

**Status: Slice 0 foundation.** The local-first CLI walking skeleton is runnable; Kubernetes
context discovery arrives in Slice 1.
**Status: Slice 2 cache-first fleet client.** The CLI discovers every context resolved by
client-go, hydrates a local in-memory fleet cache through per-context watches, and serves Tier-1
reads and cross-cluster search from normalized snapshots with explicit freshness and coverage.

Sith is ArdurAI's single-binary, local-first Kubernetes fleet tool: **k9s for your whole fleet**.
It is designed to aggregate every kubeconfig context without an account, telemetry, or cluster
Expand All @@ -10,21 +11,57 @@ governed hub.

## Build and run

Sith requires a supported Go 1.25 toolchain.
Sith requires a supported Go 1.26 toolchain.

```bash
make build
./bin/sith # interactive terminal: cache-first fleet view
./bin/sith tui # explicit equivalent
./bin/sith version
./bin/sith version --output json
./bin/sith clusters
./bin/sith get pods -A --all-clusters
./bin/sith search 'image:*log4j*'
./bin/sith correlate 'deploy/payments status!=Healthy'
```

Slice 0 intentionally returns a typed empty fleet through the stubbed `fleet.Source` seam. Run the
full local quality gate with a pinned golangci-lint v2.12.2 on `PATH`:
`sith clusters` follows standard client-go loading rules: set `KUBECONFIG` to an OS path-list or
use the default `~/.kube/config`. Exec-credential helpers run locally, exactly as they do for
`kubectl`; Sith does not copy kubeconfigs or credentials elsewhere.

Scripted `get` calls require either `--all-clusters` or one explicit `--context`. Text, JSON, YAML,
wide, and source-abstract name outputs are supported. Search and correlation run over the same
normalized in-memory records; partial results name stale/unreachable contexts. The cache is not
persisted to disk, so raw workload specifications do not become a new plaintext credential-adjacent
artifact.

The TUI opens only when stdin and stdout are terminals; redirected bare invocations remain
script-safe and print help. Tier-1 lenses are Pods, Deployments, Events, and Nodes. Use `:` for
lens/context commands (including `:<kind>` for an API-discovered generic resource rendered with
the server's print columns), `/` to filter the current lens, `Ctrl-K` for whole-fleet fuzzy/
structured search, number keys for cluster scope, `c` for coverage, and `Ctrl-R` for a non-blocking
refresh.
The UI uses Bubble Tea v2.0.8 core only; tables and search remain local so no optional styling or
component dependency enters the binary.

Each active lens holds one Kubernetes watch per reachable context after its initial list. A
two-minute safety rediscovery recovers contexts that were offline at launch; it is not the primary
resource refresh path. Very large context/lens counts therefore trade API-server connection and
relist cost for continuous low-latency deltas.

Run the full local quality gate with golangci-lint v2.12.2 and govulncheck v1.6.0 on `PATH`:

```bash
make ci
```

The real multi-cluster gate creates two temporary kind clusters with a digest-pinned node image,
checks one additional unreachable context, and removes both clusters afterward. It requires a
running Docker engine and kind v0.32.0, and consumes additional CI time, disk, and memory:

```bash
make e2e-kind
```

The architecture, threat model, ADRs, and roadmap live under [`docs/`](docs/). Build-session
checkpoints are recorded under [`sessions/`](sessions/).
54 changes: 53 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,65 @@
module github.com/ArdurAI/sith

go 1.25.0
go 1.26.0

require (
charm.land/bubbletea/v2 v2.0.8
github.com/spf13/cobra v1.10.2
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/term v0.43.0
k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2
sigs.k8s.io/yaml v1.6.0
)

require (
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.36.2 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
)
Loading