Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a551a9
fix: Replace echo with printf for reliable color generation
adalton Dec 9, 2025
9b2f77f
fix(operator): detect Job deadline exceeded and update session status
adalton Dec 9, 2025
1518867
Updated agenticsessions-crd.yaml
adalton Dec 9, 2025
e134d19
feat(backend): add per-repo input/output/autoPush support to SimpleRepo
adalton Dec 10, 2025
f242b99
fix(operator): improve repo config parsing and validation
adalton Dec 10, 2025
c3cc4c8
feat(runner): implement per-repo autoPush flag and system prompt enha…
adalton Dec 10, 2025
bce5697
feat(frontend): update types and UI for multi-repo support (Commit 5)
adalton Dec 10, 2025
06119d9
feat(frontend): update types and UI for per-repo auto-push (Commit 6)
adalton Dec 10, 2025
eaa1772
feat(operator): add startup migration for v2 repo format (Commit 7)
adalton Dec 10, 2025
671a39e
Fix lint error
adalton Dec 11, 2025
ac3a25b
feat(operator): implement PR feedback for v2 repo migration (RHOAIENG…
adalton Dec 12, 2025
beb46b0
refactor(operator): address PR feedback for migration implementation
adalton Dec 12, 2025
79277a5
refactor: address critical PR feedback (Priority 1 items)
adalton Dec 12, 2025
5a8ec58
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 12, 2025
0f95f06
docs(backend): clarify NormalizeRepo autoPush behavior
adalton Dec 15, 2025
49b605e
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 16, 2025
fa15b9c
fix: resolve merge conflicts from main integration
adalton Dec 16, 2025
acd350b
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 17, 2025
94a6449
update langfuse deployment, configure logging (#463)
sallyom Dec 17, 2025
c9dcf8c
build(deps): bump next from 15.5.7 to 15.5.9 in /components/frontend …
dependabot[bot] Dec 17, 2025
98ab358
Add google workspace MCP to runner with necessary oauth additions (#469)
MichaelClifford Dec 17, 2025
246b861
refactor(operator): address PR #457 code review feedback
adalton Dec 17, 2025
4dde3cd
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 17, 2025
c03c98f
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 18, 2025
f9e1cf2
Merge branch 'main' into andalton/RHOAIENG-37639
adalton Dec 19, 2025
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
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,12 @@ Langfuse supports OpenTelemetry as of 2025:
- REQUIRED: Always use `GetK8sClientsForRequest(c)` to get user-scoped K8s clients
- REQUIRED: Return `401 Unauthorized` if user token is missing or invalid
- Exception: Backend service account ONLY for CR writes and token minting (handlers/sessions.go:227, handlers/sessions.go:449)
- Exception: Operator service account for startup migrations (operator/internal/handlers/migration.go:29-47)
- V1→V2 repo format migration runs once at operator startup
- Updates existing CRs the operator already has RBAC access to
- Only modifies data structure format, not repository content
- Active sessions (Running/Creating) are skipped to avoid interference
- See ADR-0002 for detailed security model documentation

2. **Never Panic in Production Code**
- FORBIDDEN: `panic()` in handlers, reconcilers, or any production path
Expand Down
83 changes: 59 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner deploy clean
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner deploy clean test-runner test-runner-autopush
.PHONY: local-up local-down local-clean local-status local-rebuild local-reload-backend local-reload-frontend local-reload-operator local-sync-version
.PHONY: local-dev-token
.PHONY: local-logs local-logs-backend local-logs-frontend local-logs-operator local-shell local-shell-frontend
Expand Down Expand Up @@ -47,14 +47,20 @@ GIT_VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
BUILD_USER := $(shell whoami)@$(shell hostname)

# Colors for output
# Colors for output (use printf to properly interpret escape sequences)
# Use like: @printf "$(COLOR_BLUE)Text here$(COLOR_RESET)\n"
COLOR_RESET := \033[0m
COLOR_BOLD := \033[1m
COLOR_GREEN := \033[32m
COLOR_YELLOW := \033[33m
COLOR_BLUE := \033[34m
COLOR_RED := \033[31m

# Helper to echo with colors (use this instead of echo)
define echo_color
@echo "$(1)"
endef

# Platform flag
ifneq ($(PLATFORM),)
PLATFORM_FLAG := --platform=$(PLATFORM)
Expand All @@ -64,30 +70,41 @@ endif

##@ General

test-colors: ## Test color output rendering
@echo "$(COLOR_BOLD)Testing Color Output$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_RED)✗ Red text (errors)$(COLOR_RESET)"
@echo "$(COLOR_GREEN)✓ Green text (success)$(COLOR_RESET)"
@echo "$(COLOR_BLUE)▶ Blue text (info)$(COLOR_RESET)"
@echo "$(COLOR_YELLOW)⚠ Yellow text (warnings)$(COLOR_RESET)"
@echo "$(COLOR_BOLD)Bold text$(COLOR_RESET)"
@echo ""
@echo "If you see color codes like \033[1m instead of colors, your terminal may not support ANSI colors"

help: ## Display this help message
@echo '$(COLOR_BOLD)Ambient Code Platform - Development Makefile$(COLOR_RESET)'
@echo ''
@echo '$(COLOR_BOLD)Quick Start:$(COLOR_RESET)'
@echo ' $(COLOR_GREEN)make local-up$(COLOR_RESET) Start local development environment'
@echo ' $(COLOR_GREEN)make local-status$(COLOR_RESET) Check status of local environment'
@echo ' $(COLOR_GREEN)make local-logs$(COLOR_RESET) View logs from all components'
@echo ' $(COLOR_GREEN)make local-down$(COLOR_RESET) Stop local environment'
@echo ''
@echo '$(COLOR_BOLD)Quality Assurance:$(COLOR_RESET)'
@echo ' $(COLOR_GREEN)make validate-makefile$(COLOR_RESET) Validate Makefile quality (runs in CI)'
@echo ' $(COLOR_GREEN)make makefile-health$(COLOR_RESET) Run comprehensive health check'
@echo ''
@echo "$(COLOR_BOLD)Ambient Code Platform - Development Makefile$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_BOLD)Quick Start:$(COLOR_RESET)"
@echo " $(COLOR_GREEN)make local-up$(COLOR_RESET) Start local development environment"
@echo " $(COLOR_GREEN)make local-status$(COLOR_RESET) Check status of local environment"
@echo " $(COLOR_GREEN)make local-logs$(COLOR_RESET) View logs from all components"
@echo " $(COLOR_GREEN)make local-down$(COLOR_RESET) Stop local environment"
@echo ""
@echo "$(COLOR_BOLD)Quality Assurance:$(COLOR_RESET)"
@echo " $(COLOR_GREEN)make validate-makefile$(COLOR_RESET) Validate Makefile quality (runs in CI)"
@echo " $(COLOR_GREEN)make makefile-health$(COLOR_RESET) Run comprehensive health check"
@echo ""
@awk 'BEGIN {FS = ":.*##"; printf "$(COLOR_BOLD)Available Targets:$(COLOR_RESET)\n"} /^[a-zA-Z_-]+:.*?##/ { printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2 } /^##@/ { printf "\n$(COLOR_BOLD)%s$(COLOR_RESET)\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
@echo ''
@echo '$(COLOR_BOLD)Configuration Variables:$(COLOR_RESET)'
@echo ' CONTAINER_ENGINE=$(CONTAINER_ENGINE) (docker or podman)'
@echo ' NAMESPACE=$(NAMESPACE)'
@echo ' PLATFORM=$(PLATFORM)'
@echo ''
@echo '$(COLOR_BOLD)Examples:$(COLOR_RESET)'
@echo ' make local-up CONTAINER_ENGINE=docker'
@echo ' make local-reload-backend'
@echo ' make build-all PLATFORM=linux/arm64'
@echo ""
@echo "$(COLOR_BOLD)Configuration Variables:$(COLOR_RESET)"
@echo " CONTAINER_ENGINE=$(CONTAINER_ENGINE) (docker or podman)"
@echo " NAMESPACE=$(NAMESPACE)"
@echo " PLATFORM=$(PLATFORM)"
@echo ""
@echo "$(COLOR_BOLD)Examples:$(COLOR_RESET)"
@echo " make local-up CONTAINER_ENGINE=docker"
@echo " make local-reload-backend"
@echo " make build-all PLATFORM=linux/arm64"

##@ Building

Expand Down Expand Up @@ -145,6 +162,24 @@ build-runner: ## Build Claude Code runner image
-t $(RUNNER_IMAGE) -f claude-code-runner/Dockerfile .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Runner built: $(RUNNER_IMAGE)"

test-runner: build-runner ## Run runner tests in container
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Running runner tests in container..."
@$(CONTAINER_ENGINE) run --rm \
-v $(PWD)/components/runners/claude-code-runner:/app/test-runner:Z \
-w /app/test-runner \
$(RUNNER_IMAGE) \
bash -c "pip install pytest pytest-asyncio && python -m pytest tests/ -v"
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Runner tests passed"

test-runner-autopush: build-runner ## Run autoPush tests in container
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Running autoPush tests in container..."
@$(CONTAINER_ENGINE) run --rm \
-v $(PWD)/components/runners/claude-code-runner:/app/test-runner:Z \
-w /app/test-runner \
$(RUNNER_IMAGE) \
bash -c "pip install pytest pytest-asyncio && python -m pytest tests/test_repo_autopush.py -v"
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) autoPush tests passed"

##@ Git Hooks

setup-hooks: ## Install git hooks for branch protection
Expand Down
9 changes: 5 additions & 4 deletions components/backend/handlers/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"ambient-code-backend/git"
"ambient-code-backend/pathutil"
"ambient-code-backend/types"

"github.com/gin-gonic/gin"
)
Expand Down Expand Up @@ -270,7 +271,7 @@ func ContentGitConfigureRemote(c *gin.Context) {
// This is best-effort - don't fail if fetch fails
branch := body.Branch
if branch == "" {
branch = "main"
branch = types.DefaultBranch
}
cmd := exec.CommandContext(c.Request.Context(), "git", "fetch", "origin", branch)
cmd.Dir = abs
Expand Down Expand Up @@ -684,7 +685,7 @@ func ContentGitMergeStatus(c *gin.Context) {
}

if branch == "" {
branch = "main"
branch = types.DefaultBranch
}

// Check if git repo exists
Expand Down Expand Up @@ -734,7 +735,7 @@ func ContentGitPull(c *gin.Context) {
}

if body.Branch == "" {
body.Branch = "main"
body.Branch = types.DefaultBranch
}

if err := GitPullRepo(c.Request.Context(), abs, body.Branch); err != nil {
Expand Down Expand Up @@ -769,7 +770,7 @@ func ContentGitPushToBranch(c *gin.Context) {
}

if body.Branch == "" {
body.Branch = "main"
body.Branch = types.DefaultBranch
}

if body.Message == "" {
Expand Down
76 changes: 76 additions & 0 deletions components/backend/handlers/helpers.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package handlers

import (
"ambient-code-backend/types"
"context"
"fmt"
"log"
"math"
"strings"
"time"

authv1 "k8s.io/api/authorization/v1"
Expand Down Expand Up @@ -74,3 +76,77 @@ func ValidateSecretAccess(ctx context.Context, k8sClient kubernetes.Interface, n

return nil
}

// ParseRepoMap parses a repository map (from CR spec.repos[]) into a SimpleRepo struct.
// This helper is exported for testing purposes.
// Supports both legacy format (url/branch) and new format (input/output/autoPush).
func ParseRepoMap(m map[string]interface{}) (types.SimpleRepo, error) {
r := types.SimpleRepo{}

// Check for new format (input/output/autoPush)
if inputMap, hasInput := m["input"].(map[string]interface{}); hasInput {
// New format
input := &types.RepoLocation{}
if url, ok := inputMap["url"].(string); ok {
input.URL = url
}
if branch, ok := inputMap["branch"].(string); ok && strings.TrimSpace(branch) != "" {
input.Branch = types.StringPtr(branch)
}
r.Input = input

// Parse output if present
if outputMap, hasOutput := m["output"].(map[string]interface{}); hasOutput {
output := &types.RepoLocation{}
if url, ok := outputMap["url"].(string); ok {
output.URL = url
}
if branch, ok := outputMap["branch"].(string); ok && strings.TrimSpace(branch) != "" {
output.Branch = types.StringPtr(branch)
}
r.Output = output
}

// Parse autoPush if present
if autoPush, ok := m["autoPush"].(bool); ok {
r.AutoPush = types.BoolPtr(autoPush)
}

if strings.TrimSpace(r.Input.URL) == "" {
return r, fmt.Errorf("input.url is required")
}

// Validate that output differs from input (if output is specified)
if r.Output != nil {
inputURL := strings.TrimSpace(r.Input.URL)
outputURL := strings.TrimSpace(r.Output.URL)
inputBranch := ""
outputBranch := ""
if r.Input.Branch != nil {
inputBranch = strings.TrimSpace(*r.Input.Branch)
}
if r.Output.Branch != nil {
outputBranch = strings.TrimSpace(*r.Output.Branch)
}

// Output must differ from input in either URL or branch
if inputURL == outputURL && inputBranch == outputBranch {
return r, fmt.Errorf("output repository must differ from input (different URL or branch required)")
}
}
} else {
// Legacy format
if url, ok := m["url"].(string); ok {
r.URL = url
}
if branch, ok := m["branch"].(string); ok && strings.TrimSpace(branch) != "" {
r.Branch = types.StringPtr(branch)
}

if strings.TrimSpace(r.URL) == "" {
return r, fmt.Errorf("url is required")
}
}

return r, nil
}
Loading
Loading