diff --git a/.repomixignore b/.repomixignore new file mode 100644 index 00000000..46cb2339 --- /dev/null +++ b/.repomixignore @@ -0,0 +1,66 @@ +# Repomix Ignore Patterns - Production Optimized +# Designed to balance completeness with token efficiency for AI agent steering + +# Test files - reduce noise while preserving architecture +**/*_test.go +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +**/*.spec.tsx +**/test_*.py +tests/ +cypress/ +e2e/cypress/screenshots/ +e2e/cypress/videos/ + +# Generated lock files - auto-generated, high token cost, low value +**/package-lock.json +**/go.sum +**/poetry.lock +**/Pipfile.lock + +# Documentation duplicates - MkDocs builds site/ from docs/ +site/ + +# Virtual environments and dependencies - massive token waste +# Python virtual environments +**/.venv +**/.venv/ +**/.venv-*/ +**/venv +**/venv/ +**/env +**/env/ +**/.env-*/ +**/virtualenv/ +**/.virtualenv/ + +# Node.js and Go dependencies +**/node_modules/ +**/vendor/ + +# Build artifacts - generated output, not source +**/.next/ +**/dist/ +**/build/ +**/__pycache__/ +**/*.pyc +**/*.pyo +**/*.so +**/*.dylib + +# OS and IDE files +**/.DS_Store +**/.idea/ +**/.vscode/ +**/*.swp +**/*.swo + +# E2E artifacts +e2e/cypress/screenshots/ +e2e/cypress/videos/ + +# Temporary files +**/*.tmp +**/*.temp +**/tmp/ diff --git a/docs/implementation-plans/claude-md-optimization.md b/docs/implementation-plans/claude-md-optimization.md new file mode 100644 index 00000000..ff573259 --- /dev/null +++ b/docs/implementation-plans/claude-md-optimization.md @@ -0,0 +1,683 @@ +# CLAUDE.md Optimization Plan + +**Status:** Ready for Execution +**Created:** 2024-11-21 +**Prerequisite:** Memory system implementation complete (issue #357) +**Estimated Time:** 20-30 minutes +**Context Required:** None (coldstartable) + +## Executive Summary + +This plan optimizes `CLAUDE.md` to work as a "routing layer" that points to the new memory system files, rather than containing all context inline. This reduces cognitive load when CLAUDE.md is loaded (which happens every session) while making deep context available on-demand. + +**Core Principle:** CLAUDE.md becomes a table of contents with mandatory reading (universal rules) and optional deep dives (memory files). + +## Goal + +Transform CLAUDE.md from: +- ❌ Monolithic context file with all patterns inline +- ❌ ~2000+ lines of detailed examples +- ❌ Historical decision explanations + +To: +- ✅ Routing layer with memory system guide +- ✅ Universal rules that always apply +- ✅ Signposts to deeper context ("For X, load Y") +- ✅ ~1200-1500 lines focused on essentials + +## What Stays vs. What Moves + +### STAYS in CLAUDE.md (Universal Rules) + +**Keep if it:** +- ✅ NEVER has exceptions (e.g., "NEVER push to main") +- ✅ Applies to ALL work (e.g., branch verification) +- ✅ Is a routing decision (e.g., "For backend work, load X") +- ✅ Is a build/deploy command (e.g., `make dev-start`) + +**Examples:** +- MANDATORY branch verification before file changes +- NEVER change GitHub repo visibility without permission +- Pre-push linting workflow (ALWAYS run before push) +- Project overview and architecture +- Build commands and development setup +- Critical backend/frontend rules (5-10 per component) + +### MOVES to Memory Files (Deep Context) + +**Move if it:** +- ❌ Shows HOW to implement (examples → patterns) +- ❌ Explains WHY we decided (rationale → ADRs) +- ❌ Is component-specific deep pattern (→ context files) +- ❌ Has conditions/scenarios (→ context/pattern files) + +**Examples:** +- Detailed Go handler patterns → `.claude/context/backend-development.md` +- React Query examples → `.claude/patterns/react-query-usage.md` +- "Why user tokens?" explanation → `docs/adr/0002-user-token-authentication.md` +- K8s client decision tree → `.claude/patterns/k8s-client-usage.md` + +## Content Mapping + +| Current CLAUDE.md Section | Stays? | Moves To | Replaced With | +|---------------------------|--------|----------|---------------| +| Project Overview | ✅ Stay | - | Keep as-is | +| Development Commands | ✅ Stay | - | Keep as-is | +| Backend Development Standards | ⚠️ Slim | backend-development.md | Critical rules + link | +| Frontend Development Standards | ⚠️ Slim | frontend-development.md | Critical rules + link | +| Backend/Operator Patterns | ❌ Move | patterns/*.md | "See patterns/" | +| Deep code examples | ❌ Move | context/*.md | "Load context file" | +| Security patterns | ⚠️ Slim | security-standards.md | Critical rules + link | +| Testing Strategy | ✅ Stay | - | Keep as-is | +| ADR-like explanations | ❌ Move | docs/adr/*.md | "See ADR-NNNN" | + +## Implementation Steps + +### Step 1: Add Memory System Guide Section + +**Location:** After "Table of Contents", before "Jeremy's Current Context" + +**Insert this complete section:** + +```markdown +## Memory System Guide + +The platform uses a structured "memory system" to provide context on-demand instead of loading everything upfront. This section explains what memory files exist and when to use them. + +### Memory System Overview + +| Memory Type | Location | Use When | Example Prompt | +|-------------|----------|----------|----------------| +| **Context Files** | `.claude/context/` | Working in specific area of codebase | "Claude, load backend-development context and help me add an endpoint" | +| **ADRs** | `docs/adr/` | Understanding why architectural decisions were made | "Claude, check ADR-0002 and explain user token authentication" | +| **Repomix Views** | `repomix-analysis/` | Deep codebase exploration and tracing flows | "Claude, load backend-focused repomix (04) and trace session creation" | +| **Decision Log** | `docs/decisions.md` | Quick timeline of what changed when | "Claude, check decision log for multi-repo support changes" | +| **Patterns** | `.claude/patterns/` | Applying established code patterns | "Claude, use the error-handling pattern in this handler" | + +### Available Context Files + +#### Backend Development +**File:** `.claude/context/backend-development.md` + +**Contains:** +- Go handler patterns and best practices +- K8s client usage (user token vs. service account) +- Authentication and authorization patterns +- Error handling for handlers and middleware +- Type-safe unstructured resource access + +**Load when:** +- Adding new API endpoints +- Modifying backend handlers +- Working with Kubernetes resources +- Implementing authentication/authorization + +**Example prompt:** +``` +Claude, load the backend-development context file and help me add +a new endpoint for updating project settings with proper RBAC validation. +``` + +#### Frontend Development +**File:** `.claude/context/frontend-development.md` + +**Contains:** +- Next.js App Router patterns +- Shadcn UI component usage +- React Query data fetching patterns +- TypeScript best practices (zero `any` types) +- Component organization and colocation + +**Load when:** +- Creating new UI components +- Implementing data fetching +- Adding new pages/routes +- Working with forms or dialogs + +**Example prompt:** +``` +Claude, load the frontend-development context and help me create +a new page for RFE workflow visualization with proper React Query hooks. +``` + +#### Security Standards +**File:** `.claude/context/security-standards.md` + +**Contains:** +- Token handling and redaction patterns +- RBAC enforcement patterns +- Input validation strategies +- Container security settings +- Security review checklist + +**Load when:** +- Implementing authentication/authorization +- Handling sensitive data +- Security reviews +- Adding RBAC checks + +**Example prompt:** +``` +Claude, reference the security-standards context and review this PR +for token handling issues and RBAC violations. +``` + +### Available Patterns + +#### Error Handling +**File:** `.claude/patterns/error-handling.md` + +**Contains:** +- Backend handler error patterns (404, 400, 403, 500) +- Operator reconciliation error handling +- Python runner error patterns +- Anti-patterns to avoid + +**Apply when:** Adding error handling to handlers, operators, or runners + +#### K8s Client Usage +**File:** `.claude/patterns/k8s-client-usage.md` + +**Contains:** +- User-scoped client vs. service account decision tree +- Common patterns for list/create/delete operations +- Validation-then-escalate pattern for writes +- Anti-patterns and security violations + +**Apply when:** Working with Kubernetes API, implementing RBAC + +#### React Query Usage +**File:** `.claude/patterns/react-query-usage.md` + +**Contains:** +- Query hooks for GET operations +- Mutation hooks for create/update/delete +- Optimistic updates and cache invalidation +- Polling and dependent queries + +**Apply when:** Implementing frontend data fetching or mutations + +### Available Architectural Decision Records (ADRs) + +ADRs document WHY architectural decisions were made, not just WHAT was implemented. + +**Location:** `docs/adr/` + +**Current ADRs:** +- [ADR-0001](../adr/0001-kubernetes-native-architecture.md): Kubernetes-Native Architecture +- [ADR-0002](../adr/0002-user-token-authentication.md): User Token Authentication for API Operations +- [ADR-0003](../adr/0003-multi-repo-support.md): Multi-Repository Support in AgenticSessions +- [ADR-0004](../adr/0004-go-backend-python-runner.md): Go Backend with Python Claude Runner +- [ADR-0005](../adr/0005-nextjs-shadcn-react-query.md): Next.js with Shadcn UI and React Query + +**Example usage:** +``` +Claude, check ADR-0002 (User Token Authentication) and explain why we +validate user permissions before using the service account to create resources. +``` + +### Repomix Views Guide + +**File:** `.claude/repomix-guide.md` + +Contains usage guide for the 7 pre-generated repomix views (architecture-only, backend-focused, frontend-focused, etc.). + +**Example usage:** +``` +Claude, load the backend-focused repomix view (04) and trace how +AgenticSession creation flows from the API handler to the operator. +``` + +### Decision Log + +**File:** `docs/decisions.md` + +Chronological record of major decisions with brief rationale. + +**Example usage:** +``` +Claude, check the decision log for when multi-repo support was added +and what gotchas were discovered. +``` + +### How to Use the Memory System + +#### Scenario 1: Backend API Work + +**Prompt:** +``` +Claude, load the backend-development context file and the backend-focused +repomix view (04). Help me add a new endpoint for listing RFE workflows +with proper pagination and RBAC validation. +``` + +**What Claude loads:** +- Backend development patterns +- K8s client usage patterns +- Existing handler examples from repomix +- RBAC patterns from security context + +#### Scenario 2: Frontend Feature + +**Prompt:** +``` +Claude, load the frontend-development context and the react-query-usage pattern. +Help me add optimistic updates to the session deletion flow. +``` + +**What Claude loads:** +- Next.js and Shadcn UI patterns +- React Query mutation patterns +- Optimistic update examples + +#### Scenario 3: Security Review + +**Prompt:** +``` +Claude, reference the security-standards context file and review handlers/sessions.go +for token handling issues, RBAC violations, and input validation problems. +``` + +**What Claude loads:** +- Security patterns and anti-patterns +- Token redaction requirements +- RBAC enforcement checklist + +#### Scenario 4: Understanding Architecture + +**Prompt:** +``` +Claude, check ADR-0001 (Kubernetes-Native Architecture) and explain +why we chose CRDs and Operators instead of traditional microservices. +``` + +**What Claude loads:** +- Decision context and alternatives considered +- Trade-offs and consequences +- Implementation notes + +### Quick Reference: Task → Memory File Mapping + +``` +Task Type → Load This Memory File +────────────────────────────────────────────────────────── +Backend endpoint work → backend-development.md + k8s-client-usage.md +Frontend UI work → frontend-development.md + react-query-usage.md +Security review → security-standards.md +Error handling → error-handling.md +Why did we choose X? → docs/adr/NNNN-*.md (relevant ADR) +What changed when? → docs/decisions.md +Deep codebase exploration → repomix-analysis/*.xml + repomix-guide.md +Applying a pattern → .claude/patterns/*.md +``` + +### Memory System Maintenance + +**Weekly:** +- Add new decisions to `docs/decisions.md` + +**Monthly:** +- Update context files with new patterns discovered +- Add ADRs for significant architectural changes +- Regenerate repomix views if major codebase changes + +**Quarterly:** +- Review ADRs for accuracy (mark deprecated if needed) +- Update pattern catalog +- Audit context files for outdated information + +--- +``` + +**Action:** Insert this entire section into CLAUDE.md after the Table of Contents. + +### Step 2: Update Backend Development Standards Section + +**Current location:** "## Backend and Operator Development Standards" + +**Changes:** + +1. **Add introductory paragraph with context file link:** + +```markdown +## Backend and Operator Development Standards + +**For detailed patterns and examples, load:** `.claude/context/backend-development.md` + +**This section contains CRITICAL RULES that always apply.** For deep patterns, code examples, and detailed explanations, use the context file above. +``` + +2. **Keep only critical rules, slim down examples:** + +**KEEP:** +- Critical Rules (Never Violate) - all 5 rules +- Package Organization - structure only, no examples +- Pre-Commit Checklist + +**SLIM DOWN (add link instead):** + +Replace detailed code examples with: + +```markdown +### Kubernetes Client Patterns + +**CRITICAL RULE:** Always use user-scoped clients for API operations. + +**For detailed patterns and examples:** Load `.claude/patterns/k8s-client-usage.md` + +**Quick reference:** +- User-scoped clients (`reqK8s`, `reqDyn`): For all user-initiated operations +- Service account clients (`K8sClient`, `DynamicClient`): ONLY for privileged operations after validation + +**Pattern:** +```go +// 1. Get user-scoped clients +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + return +} + +// 2. Use for operations +list, err := reqDyn.Resource(gvr).Namespace(project).List(ctx, v1.ListOptions{}) +``` + +**For complete patterns:** See `.claude/patterns/k8s-client-usage.md` +``` + +**REMOVE (now in context files):** +- All detailed code examples (>20 lines) +- Anti-patterns sections (move to patterns file) +- "How to" sections with step-by-step (move to context) + +### Step 3: Update Frontend Development Standards Section + +**Current location:** "## Frontend Development Standards" + +**Changes:** + +1. **Add introductory paragraph:** + +```markdown +## Frontend Development Standards + +**For detailed patterns and examples, load:** `.claude/context/frontend-development.md` + +**This section contains CRITICAL RULES that always apply.** See `components/frontend/DESIGN_GUIDELINES.md` and the context file for complete patterns. +``` + +2. **Keep Critical Rules (Quick Reference) section as-is** - these 5 rules are non-negotiable + +3. **Slim down Pre-Commit Checklist** with link: + +```markdown +### Pre-Commit Checklist for Frontend + +**Quick checklist:** +- [ ] Zero `any` types +- [ ] All UI uses Shadcn components +- [ ] All data operations use React Query +- [ ] `npm run build` passes with 0 errors, 0 warnings + +**For complete checklist:** See `.claude/context/frontend-development.md` or `components/frontend/DESIGN_GUIDELINES.md` +``` + +4. **Replace Reference Files section:** + +```markdown +### Reference Files + +**For detailed frontend patterns:** +- `.claude/context/frontend-development.md` - Component patterns, React Query, TypeScript +- `.claude/patterns/react-query-usage.md` - Data fetching patterns +- `components/frontend/DESIGN_GUIDELINES.md` - Comprehensive design guidelines +- `components/frontend/COMPONENT_PATTERNS.md` - Architecture patterns +``` + +### Step 4: Add Quick Links to Other Sections + +**Security-related sections:** Add link to security context + +**Example for "Production Considerations → Security" section:** + +```markdown +### Security + +**For detailed security patterns:** Load `.claude/context/security-standards.md` + +**Critical requirements:** +- API keys stored in Kubernetes Secrets +- RBAC: Namespace-scoped isolation +- OAuth integration: OpenShift OAuth for cluster-based authentication +- Network policies: Component isolation + +**See also:** +- ADR-0002: User Token Authentication +- Pattern: Token handling and redaction +``` + +**Testing sections:** Add link to E2E guide + +```markdown +### E2E Tests (Cypress + Kind) + +**Full guide:** `docs/testing/e2e-guide.md` + +**Quick reference:** +- Purpose: Automated end-to-end testing in Kubernetes +- Location: `e2e/` +- Command: `make e2e-test CONTAINER_ENGINE=podman` +``` + +### Step 5: Update Table of Contents + +**Add new section to TOC:** + +```markdown +## Table of Contents + +- [Memory System Guide](#memory-system-guide) ← NEW +- [Dynamic Framework Selection](#dynamic-framework-selection) +- [Core Operating Philosophy](#core-operating-philosophy) +- [Strategic Analysis Framework](#strategic-analysis-framework) +[... rest of TOC ...] +``` + +### Step 6: Validate Changes + +After making changes, verify: + +1. **Memory system section is complete:** + ```bash + grep -A 50 "## Memory System Guide" CLAUDE.md + ``` + +2. **Context file links are present:** + ```bash + grep "\.claude/context/" CLAUDE.md + grep "\.claude/patterns/" CLAUDE.md + grep "docs/adr/" CLAUDE.md + ``` + +3. **Critical rules still present:** + ```bash + grep "CRITICAL RULE" CLAUDE.md + grep "NEVER" CLAUDE.md | head -10 + ``` + +4. **File size reduced (should be ~1200-1500 lines):** + ```bash + wc -l CLAUDE.md + # Before: ~2000+ lines + # After: ~1200-1500 lines + ``` + +## Before/After Comparison + +### BEFORE (Current CLAUDE.md) + +```markdown +### Kubernetes Client Patterns + +**User-Scoped Clients** (for API operations): + +```go +// ALWAYS use for user-initiated operations (list, get, create, update, delete) +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return +} +// Use reqDyn for CR operations in user's authorized namespaces +list, err := reqDyn.Resource(gvr).Namespace(project).List(ctx, v1.ListOptions{}) +``` + +**Backend Service Account Clients** (limited use cases): + +```go +// ONLY use for: +// 1. Writing CRs after validation (handlers/sessions.go:417) +// 2. Minting tokens/secrets for runners (handlers/sessions.go:449) +// 3. Cross-namespace operations backend is authorized for +// Available as: DynamicClient, K8sClient (package-level in handlers/) +created, err := DynamicClient.Resource(gvr).Namespace(project).Create(ctx, obj, v1.CreateOptions{}) +``` + +**Never**: + +- ❌ Fall back to service account when user token is invalid +- ❌ Use service account for list/get operations on behalf of users +- ❌ Skip RBAC checks by using elevated permissions + +[... continues with many more examples ...] +``` + +### AFTER (Optimized CLAUDE.md) + +```markdown +### Kubernetes Client Patterns + +**CRITICAL RULE:** Always use user-scoped clients for API operations. + +**For detailed patterns and decision trees:** Load `.claude/patterns/k8s-client-usage.md` + +**Quick reference:** +- User-scoped clients (`reqK8s`, `reqDyn`): For all user-initiated operations +- Service account clients (`K8sClient`, `DynamicClient`): ONLY for privileged operations after RBAC validation + +**Basic pattern:** +```go +// Get user-scoped clients +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + return +} +// Use for operations +list, err := reqDyn.Resource(gvr).Namespace(project).List(ctx, v1.ListOptions{}) +``` + +**For complete patterns, anti-patterns, and examples:** See `.claude/patterns/k8s-client-usage.md` + +**See also:** ADR-0002 (User Token Authentication) for the rationale behind this approach. +``` + +## Content Removal Guidelines + +### Safe to Remove (Already in Memory Files) + +After memory system is implemented, these can be removed from CLAUDE.md: + +1. **Detailed code examples >20 lines** + - Already in context files or pattern files + - Keep only ~5-10 line snippets showing the pattern + +2. **Step-by-step "how to" sections** + - E.g., "Adding a New API Endpoint" with detailed steps + - Keep the file references, remove the detailed steps + +3. **Anti-patterns with explanations** + - Move to pattern files with full examples + - Keep only "NEVER do X" in CLAUDE.md + +4. **Historical context about decisions** + - E.g., "We chose X because Y and considered Z" + - Move to ADRs with full context + +5. **Common mistakes sections** + - Move to pattern files + - Keep only critical mistakes in CLAUDE.md + +### MUST Keep in CLAUDE.md + +1. **Universal rules with no exceptions** + - NEVER change repo visibility + - MANDATORY branch verification + - ALWAYS run linters before push + +2. **Critical security rules** + - No panics in production + - User token required for API operations + - Token redaction in logs + +3. **Build and deployment commands** + - `make dev-start` + - `make build-all` + - Component-specific commands + +4. **Project structure overview** + - High-level architecture + - Component relationships + - Key directories + +## Validation Checklist + +After completing all steps: + +- [ ] Memory System Guide section added after Table of Contents +- [ ] All context file links are correct and reference existing files +- [ ] Backend section slimmed down with links to context files +- [ ] Frontend section slimmed down with links to context files +- [ ] Critical rules still present and easy to find +- [ ] File size reduced by ~30-40% (from ~2000 to ~1200-1500 lines) +- [ ] No broken links (all referenced memory files exist) +- [ ] Table of Contents updated +- [ ] Test with Claude Code in new session: + ``` + Claude, load the backend-development context and help me understand + the K8s client usage patterns. + ``` + +## Success Criteria + +**This plan is complete when:** + +1. ✅ Memory System Guide section added to CLAUDE.md +2. ✅ Backend/Frontend sections updated with context file links +3. ✅ Detailed examples removed (now in memory files) +4. ✅ CLAUDE.md is ~1200-1500 lines (down from ~2000+) +5. ✅ All critical rules still present and prominent +6. ✅ Claude Code can successfully reference memory files in new session +7. ✅ File validated with checklist above + +## Rollback Plan + +If optimization causes issues: + +1. Revert CLAUDE.md: `git checkout HEAD -- CLAUDE.md` +2. Memory files are additive, so they don't need rollback +3. Re-run this plan with adjustments + +## Next Steps After Implementation + +1. **Test in practice:** Use memory file references for 1 week +2. **Gather feedback:** Are context files useful? Any missing patterns? +3. **Iterate:** Add new patterns as discovered +4. **Monthly review:** Update context files with new patterns + +--- + +**End of Implementation Plan** + +This plan is coldstartable - all changes are specified with exact content. No additional research or decisions needed during implementation. diff --git a/docs/implementation-plans/memory-system-implementation.md b/docs/implementation-plans/memory-system-implementation.md new file mode 100644 index 00000000..cbff3558 --- /dev/null +++ b/docs/implementation-plans/memory-system-implementation.md @@ -0,0 +1,3424 @@ +# Memory System Implementation Plan + +**Status:** Ready for Execution +**Created:** 2024-11-21 +**Estimated Time:** 30-45 minutes +**Context Required:** None (coldstartable) + +## Executive Summary + +This plan implements a structured "memory system" for the Ambient Code Platform repository to provide Claude Code with better context loading capabilities. Instead of relying solely on the comprehensive CLAUDE.md file (which is always loaded), this system creates: + +1. **Scenario-specific context files** - Loadable on-demand for backend, frontend, and security work +2. **Architectural Decision Records (ADRs)** - Document WHY decisions were made +3. **Repomix usage guide** - How to use the 7 existing repomix views effectively +4. **Decision log** - Lightweight chronological record of major decisions +5. **Code pattern catalog** - Reusable patterns with examples + +**Why This Matters:** Claude Code can load targeted context when needed rather than processing everything upfront. This improves response accuracy for specialized tasks while keeping the main CLAUDE.md focused on universal rules. + +## Implementation Order + +Execute in this order for maximum value: + +1. ✅ Context files (`.claude/context/`) - Immediate value for daily development +2. ✅ ADR infrastructure (`docs/adr/`) - Captures architectural knowledge +3. ✅ Repomix guide (`.claude/repomix-guide.md`) - Leverages existing assets +4. ✅ Decision log (`docs/decisions.md`) - Lightweight decision tracking +5. ✅ Pattern catalog (`.claude/patterns/`) - Codifies best practices + +--- + +## Component 1: Context Files + +### Overview + +Create scenario-specific context files that Claude can reference when working in different areas of the codebase. + +### Implementation + +**Step 1.1:** Create directory structure + +```bash +mkdir -p .claude/context +``` + +**Step 1.2:** Create backend development context + +**File:** `.claude/context/backend-development.md` + +```markdown +# Backend Development Context + +**When to load:** Working on Go backend API, handlers, or Kubernetes integration + +## Quick Reference + +- **Language:** Go 1.21+ +- **Framework:** Gin (HTTP router) +- **K8s Client:** client-go + dynamic client +- **Primary Files:** `components/backend/handlers/*.go`, `components/backend/types/*.go` + +## Critical Rules + +### Authentication & Authorization + +**ALWAYS use user-scoped clients for API operations:** + +\```go +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return +} +\``` + +**FORBIDDEN:** Using backend service account (`DynamicClient`, `K8sClient`) for user-initiated operations + +**Backend service account ONLY for:** +- Writing CRs after validation (handlers/sessions.go:417) +- Minting tokens/secrets for runners (handlers/sessions.go:449) +- Cross-namespace operations backend is authorized for + +### Token Security + +**NEVER log tokens:** +```go +// ❌ BAD +log.Printf("Token: %s", token) + +// ✅ GOOD +log.Printf("Processing request with token (len=%d)", len(token)) +``` + +**Token redaction in logs:** See `server/server.go:22-34` for custom formatter + +### Error Handling + +**Pattern for handler errors:** + +\```go +// Resource not found +if errors.IsNotFound(err) { + c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"}) + return +} + +// Generic error +if err != nil { + log.Printf("Failed to create session %s in project %s: %v", name, project, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"}) + return +} +\``` + +### Type-Safe Unstructured Access + +**FORBIDDEN:** Direct type assertions +```go +// ❌ BAD - will panic if type is wrong +spec := obj.Object["spec"].(map[string]interface{}) +``` + +**REQUIRED:** Use unstructured helpers +```go +// ✅ GOOD +spec, found, err := unstructured.NestedMap(obj.Object, "spec") +if !found || err != nil { + return fmt.Errorf("spec not found") +} +``` + +## Common Tasks + +### Adding a New API Endpoint + +1. **Define route:** `routes.go` with middleware chain +2. **Create handler:** `handlers/[resource].go` +3. **Validate project context:** Use `ValidateProjectContext()` middleware +4. **Get user clients:** `GetK8sClientsForRequest(c)` +5. **Perform operation:** Use `reqDyn` for K8s resources +6. **Return response:** Structured JSON with appropriate status code + +### Adding a New Custom Resource Field + +1. **Update CRD:** `components/manifests/base/[resource]-crd.yaml` +2. **Update types:** `components/backend/types/[resource].go` +3. **Update handlers:** Extract/validate new field in handlers +4. **Update operator:** Handle new field in reconciliation +5. **Test:** Create sample CR with new field + +## Pre-Commit Checklist + +- [ ] All user operations use `GetK8sClientsForRequest` +- [ ] No tokens in logs +- [ ] Errors logged with context +- [ ] Type-safe unstructured access +- [ ] `gofmt -w .` applied +- [ ] `go vet ./...` passes +- [ ] `golangci-lint run` passes + +## Key Files + +- `handlers/sessions.go` - AgenticSession lifecycle (3906 lines) +- `handlers/middleware.go` - Auth, RBAC validation +- `handlers/helpers.go` - Utility functions (StringPtr, BoolPtr) +- `types/session.go` - Type definitions +- `server/server.go` - Server setup, token redaction + +## Recent Issues & Learnings + +- **2024-11-15:** Fixed token leak in logs - never log raw tokens +- **2024-11-10:** Multi-repo support added - `mainRepoIndex` specifies working directory +- **2024-10-20:** Added RBAC validation middleware - always check permissions +``` + +**Step 1.3:** Create frontend development context + +**File:** `.claude/context/frontend-development.md` + +```markdown +# Frontend Development Context + +**When to load:** Working on NextJS application, UI components, or React Query integration + +## Quick Reference + +- **Framework:** Next.js 14 (App Router) +- **UI Library:** Shadcn UI (built on Radix UI primitives) +- **Styling:** Tailwind CSS +- **Data Fetching:** TanStack React Query +- **Primary Directory:** `components/frontend/src/` + +## Critical Rules (Zero Tolerance) + +### 1. Zero `any` Types + +**FORBIDDEN:** +```typescript +// ❌ BAD +function processData(data: any) { ... } +``` + +**REQUIRED:** +```typescript +// ✅ GOOD - use proper types +function processData(data: AgenticSession) { ... } + +// ✅ GOOD - use unknown if type truly unknown +function processData(data: unknown) { + if (isAgenticSession(data)) { ... } +} +``` + +### 2. Shadcn UI Components Only + +**FORBIDDEN:** Creating custom UI components from scratch for buttons, inputs, dialogs, etc. + +**REQUIRED:** Use `@/components/ui/*` components + +```typescript +// ❌ BAD + + +// ✅ GOOD +import { Button } from "@/components/ui/button" + +``` + +**Available Shadcn components:** button, card, dialog, form, input, select, table, toast, etc. +**Check:** `components/frontend/src/components/ui/` for full list + +### 3. React Query for ALL Data Operations + +**FORBIDDEN:** Manual `fetch()` calls in components + +**REQUIRED:** Use hooks from `@/services/queries/*` + +```typescript +// ❌ BAD +const [sessions, setSessions] = useState([]) +useEffect(() => { + fetch('/api/sessions').then(r => r.json()).then(setSessions) +}, []) + +// ✅ GOOD +import { useSessions } from "@/services/queries/sessions" +const { data: sessions, isLoading } = useSessions(projectName) +``` + +### 4. Use `type` Over `interface` + +**REQUIRED:** Always prefer `type` for type definitions + +```typescript +// ❌ AVOID +interface User { name: string } + +// ✅ PREFERRED +type User = { name: string } +``` + +### 5. Colocate Single-Use Components + +**FORBIDDEN:** Creating components in shared directories if only used once + +**REQUIRED:** Keep page-specific components with their pages + +``` +app/ + projects/ + [projectName]/ + sessions/ + _components/ # Components only used in sessions pages + session-card.tsx + page.tsx # Uses session-card +``` + +## Common Patterns + +### Page Structure + +```typescript +// app/projects/[projectName]/sessions/page.tsx +import { useSessions } from "@/services/queries/sessions" +import { Button } from "@/components/ui/button" +import { Card } from "@/components/ui/card" + +export default function SessionsPage({ + params, +}: { + params: { projectName: string } +}) { + const { data: sessions, isLoading, error } = useSessions(params.projectName) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ if (!sessions?.length) return
No sessions found
+ + return ( +
+ {sessions.map(session => ( + + {/* ... */} + + ))} +
+ ) +} +``` + +### React Query Hook Pattern + +```typescript +// services/queries/sessions.ts +import { useQuery, useMutation } from "@tanstack/react-query" +import { sessionApi } from "@/services/api/sessions" + +export function useSessions(projectName: string) { + return useQuery({ + queryKey: ["sessions", projectName], + queryFn: () => sessionApi.list(projectName), + }) +} + +export function useCreateSession(projectName: string) { + return useMutation({ + mutationFn: (data: CreateSessionRequest) => + sessionApi.create(projectName, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["sessions", projectName] }) + }, + }) +} +``` + +## Pre-Commit Checklist + +- [ ] Zero `any` types (or justified with eslint-disable) +- [ ] All UI uses Shadcn components +- [ ] All data operations use React Query +- [ ] Components under 200 lines +- [ ] Single-use components colocated +- [ ] All buttons have loading states +- [ ] All lists have empty states +- [ ] All nested pages have breadcrumbs +- [ ] `npm run build` passes with 0 errors, 0 warnings +- [ ] All types use `type` instead of `interface` + +## Key Files + +- `components/frontend/DESIGN_GUIDELINES.md` - Comprehensive patterns +- `components/frontend/COMPONENT_PATTERNS.md` - Architecture patterns +- `src/components/ui/` - Shadcn UI components +- `src/services/queries/` - React Query hooks +- `src/services/api/` - API client layer + +## Recent Issues & Learnings + +- **2024-11-18:** Migrated all data fetching to React Query - no more manual fetch calls +- **2024-11-15:** Enforced Shadcn UI only - removed custom button components +- **2024-11-10:** Added breadcrumb pattern for nested pages +``` + +**Step 1.4:** Create security standards context + +**File:** `.claude/context/security-standards.md` + +```markdown +# Security Standards Quick Reference + +**When to load:** Working on authentication, authorization, RBAC, or handling sensitive data + +## Critical Security Rules + +### Token Handling + +**1. User Token Authentication Required** + +```go +// ALWAYS for user-initiated operations +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return +} +``` + +**2. Token Redaction in Logs** + +**FORBIDDEN:** +```go +log.Printf("Authorization: Bearer %s", token) +log.Printf("Request headers: %v", headers) +``` + +**REQUIRED:** +```go +log.Printf("Token length: %d", len(token)) +// Redact in URL paths +path = strings.Split(path, "?")[0] + "?token=[REDACTED]" +``` + +**Token Redaction Pattern:** See `server/server.go:22-34` + +```go +// Custom log formatter that redacts tokens +func customRedactingFormatter(param gin.LogFormatterParams) string { + path := param.Path + if strings.Contains(path, "token=") { + path = strings.Split(path, "?")[0] + "?token=[REDACTED]" + } + // ... rest of formatting +} +``` + +### RBAC Enforcement + +**1. Always Check Permissions Before Operations** + +```go +ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Group: "vteam.ambient-code", + Resource: "agenticsessions", + Verb: "list", + Namespace: project, + }, + }, +} +res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{}) +if err != nil || !res.Status.Allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized"}) + return +} +``` + +**2. Namespace Isolation** + +- Each project maps to a Kubernetes namespace +- User token must have permissions in that namespace +- Never bypass namespace checks + +### Container Security + +**Always Set SecurityContext for Job Pods** + +```go +SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: boolPtr(false), + ReadOnlyRootFilesystem: boolPtr(false), // Only if temp files needed + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, +}, +``` + +### Input Validation + +**1. Validate All User Input** + +```go +// Validate resource names (K8s DNS label requirements) +if !isValidK8sName(name) { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid name format"}) + return +} + +// Validate URLs for repository inputs +if _, err := url.Parse(repoURL); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid repository URL"}) + return +} +``` + +**2. Sanitize for Log Injection** + +```go +// Prevent log injection with newlines +name = strings.ReplaceAll(name, "\n", "") +name = strings.ReplaceAll(name, "\r", "") +``` + +## Common Security Patterns + +### Pattern 1: Extracting Bearer Token + +```go +rawAuth := c.GetHeader("Authorization") +parts := strings.SplitN(rawAuth, " ", 2) +if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid Authorization header"}) + return +} +token := strings.TrimSpace(parts[1]) +// NEVER log token itself +log.Printf("Processing request with token (len=%d)", len(token)) +``` + +### Pattern 2: Validating Project Access + +```go +func ValidateProjectContext() gin.HandlerFunc { + return func(c *gin.Context) { + projectName := c.Param("projectName") + + // Get user-scoped K8s client + reqK8s, _ := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + // Check if user can access namespace + ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Resource: "namespaces", + Verb: "get", + Name: projectName, + }, + }, + } + res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{}) + if err != nil || !res.Status.Allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Access denied to project"}) + c.Abort() + return + } + + c.Set("project", projectName) + c.Next() + } +} +``` + +### Pattern 3: Minting Service Account Tokens + +```go +// Only backend service account can create tokens for runner pods +tokenRequest := &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + ExpirationSeconds: int64Ptr(3600), + }, +} + +tokenResponse, err := K8sClient.CoreV1().ServiceAccounts(namespace).CreateToken( + ctx, + serviceAccountName, + tokenRequest, + v1.CreateOptions{}, +) +if err != nil { + return fmt.Errorf("failed to create token: %w", err) +} + +// Store token in secret (never log it) +secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%s-token", sessionName), + Namespace: namespace, + }, + StringData: map[string]string{ + "token": tokenResponse.Status.Token, + }, +} +``` + +## Security Checklist + +Before committing code that handles: + +**Authentication:** +- [ ] Using user token (GetK8sClientsForRequest) for user operations +- [ ] Returning 401 if token is invalid/missing +- [ ] Not falling back to service account on auth failure + +**Authorization:** +- [ ] RBAC check performed before resource access +- [ ] Using correct namespace for permission check +- [ ] Returning 403 if user lacks permissions + +**Secrets & Tokens:** +- [ ] No tokens in logs (use len(token) instead) +- [ ] No tokens in error messages +- [ ] Tokens stored in Kubernetes Secrets +- [ ] Token redaction in request logs + +**Input Validation:** +- [ ] All user input validated +- [ ] Resource names validated (K8s DNS label format) +- [ ] URLs parsed and validated +- [ ] Log injection prevented + +**Container Security:** +- [ ] SecurityContext set on all Job pods +- [ ] AllowPrivilegeEscalation: false +- [ ] Capabilities dropped (ALL) +- [ ] OwnerReferences set for cleanup + +## Recent Security Issues + +- **2024-11-15:** Fixed token leak in logs - added custom redacting formatter +- **2024-10-20:** Added RBAC validation middleware - prevent unauthorized access +- **2024-10-10:** Fixed privilege escalation risk - added SecurityContext to Job pods + +## Security Review Resources + +- OWASP Top 10: https://owasp.org/www-project-top-ten/ +- Kubernetes Security Best Practices: https://kubernetes.io/docs/concepts/security/ +- RBAC Documentation: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +``` + +### Success Criteria + +- [ ] `.claude/context/` directory created +- [ ] Three context files created (backend, frontend, security) +- [ ] Each file contains actionable, copy-paste ready examples +- [ ] Files reference specific line numbers in codebase where patterns are implemented + +--- + +## Component 2: ADR Infrastructure + +### Overview + +Architectural Decision Records (ADRs) document WHY decisions were made, not just WHAT was implemented. This is invaluable for understanding when to deviate from patterns vs. follow them strictly. + +### Implementation + +**Step 2.1:** Create directory structure + +```bash +mkdir -p docs/adr +``` + +**Step 2.2:** Create ADR template + +**File:** `docs/adr/template.md` + +```markdown +# ADR-NNNN: [Short Title of Decision] + +**Status:** [Proposed | Accepted | Deprecated | Superseded by ADR-XXXX] +**Date:** YYYY-MM-DD +**Deciders:** [List of people involved] +**Technical Story:** [Link to issue/PR if applicable] + +## Context and Problem Statement + +[Describe the context and problem. What forces are at play? What constraints exist? What problem are we trying to solve?] + +## Decision Drivers + +* [Driver 1 - e.g., Performance requirements] +* [Driver 2 - e.g., Security constraints] +* [Driver 3 - e.g., Team expertise] +* [Driver 4 - e.g., Cost considerations] + +## Considered Options + +* [Option 1] +* [Option 2] +* [Option 3] + +## Decision Outcome + +Chosen option: "[Option X]", because [justification. Why this option over others? What were the decisive factors?] + +### Consequences + +**Positive:** + +* [Positive consequence 1 - e.g., Improved performance] +* [Positive consequence 2 - e.g., Better security] + +**Negative:** + +* [Negative consequence 1 - e.g., Increased complexity] +* [Negative consequence 2 - e.g., Higher learning curve] + +**Risks:** + +* [Risk 1 - e.g., Third-party dependency risk] +* [Risk 2 - e.g., Scaling limitations] + +## Implementation Notes + +[How this was actually implemented. Gotchas discovered during implementation. Deviations from original plan.] + +**Key Files:** +* [file.go:123] - [What this implements] +* [component.tsx:456] - [What this implements] + +**Patterns Established:** +* [Pattern 1] +* [Pattern 2] + +## Validation + +How do we know this decision was correct? + +* [Metric 1 - e.g., Response time improved by 40%] +* [Metric 2 - e.g., Security audit passed] +* [Outcome 1 - e.g., Team velocity increased] + +## Links + +* [Related ADR-XXXX] +* [Related issue #XXX] +* [Supersedes ADR-YYYY] +* [External reference] +``` + +**Step 2.3:** Create README for ADR index + +**File:** `docs/adr/README.md` + +```markdown +# Architectural Decision Records (ADRs) + +This directory contains Architectural Decision Records (ADRs) documenting significant architectural decisions made for the Ambient Code Platform. + +## What is an ADR? + +An ADR captures: +- **Context:** What problem were we solving? +- **Options:** What alternatives did we consider? +- **Decision:** What did we choose and why? +- **Consequences:** What are the trade-offs? + +ADRs are immutable once accepted. If a decision changes, we create a new ADR that supersedes the old one. + +## When to Create an ADR + +Create an ADR for decisions that: +- Affect the overall architecture +- Are difficult or expensive to reverse +- Impact multiple components or teams +- Involve significant trade-offs +- Will be questioned in the future ("Why did we do it this way?") + +**Examples:** +- Choosing a programming language or framework +- Selecting a database or messaging system +- Defining authentication/authorization approach +- Establishing API design patterns +- Multi-tenancy architecture decisions + +**Not ADR-worthy:** +- Trivial implementation choices +- Decisions easily reversed +- Component-internal decisions with no external impact + +## ADR Workflow + +1. **Propose:** Copy `template.md` to `NNNN-title.md` with status "Proposed" +2. **Discuss:** Share with team, gather feedback +3. **Decide:** Update status to "Accepted" or "Rejected" +4. **Implement:** Reference ADR in PRs +5. **Learn:** Update "Implementation Notes" with gotchas discovered + +## ADR Status Meanings + +- **Proposed:** Decision being considered, open for discussion +- **Accepted:** Decision made and being implemented +- **Deprecated:** Decision no longer relevant but kept for historical context +- **Superseded by ADR-XXXX:** Decision replaced by a newer ADR + +## Current ADRs + +| ADR | Title | Status | Date | +|-----|-------|--------|------| +| [0001](0001-kubernetes-native-architecture.md) | Kubernetes-Native Architecture | Accepted | 2024-11-21 | +| [0002](0002-user-token-authentication.md) | User Token Authentication for API Operations | Accepted | 2024-11-21 | +| [0003](0003-multi-repo-support.md) | Multi-Repository Support in AgenticSessions | Accepted | 2024-11-21 | +| [0004](0004-go-backend-python-runner.md) | Go Backend with Python Claude Runner | Accepted | 2024-11-21 | +| [0005](0005-nextjs-shadcn-react-query.md) | Next.js with Shadcn UI and React Query | Accepted | 2024-11-21 | + +## References + +- [ADR GitHub Organization](https://adr.github.io/) - ADR best practices +- [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) - Original proposal by Michael Nygard +``` + +**Step 2.4:** Create 5 critical ADRs + +**File:** `docs/adr/0001-kubernetes-native-architecture.md` + +```markdown +# ADR-0001: Kubernetes-Native Architecture + +**Status:** Accepted +**Date:** 2024-11-21 +**Deciders:** Platform Architecture Team +**Technical Story:** Initial platform architecture design + +## Context and Problem Statement + +We needed to build an AI automation platform that could: +- Execute long-running AI agent sessions +- Isolate execution environments for security +- Scale based on demand +- Integrate with existing OpenShift/Kubernetes infrastructure +- Support multi-tenancy + +How should we architect the platform to meet these requirements? + +## Decision Drivers + +* **Multi-tenancy requirement:** Need strong isolation between projects +* **Enterprise context:** Red Hat runs on OpenShift/Kubernetes +* **Resource management:** AI sessions have varying resource needs +* **Security:** Must prevent cross-project access and resource interference +* **Scalability:** Need to handle variable workload +* **Operational excellence:** Leverage existing K8s operational expertise + +## Considered Options + +1. **Kubernetes-native with CRDs and Operators** +2. **Traditional microservices on VMs** +3. **Serverless functions (e.g., AWS Lambda, OpenShift Serverless)** +4. **Container orchestration with Docker Swarm** + +## Decision Outcome + +Chosen option: "Kubernetes-native with CRDs and Operators", because: + +1. **Natural multi-tenancy:** K8s namespaces provide isolation +2. **Declarative resources:** CRDs allow users to declare desired state +3. **Built-in scaling:** K8s handles pod scheduling and resource allocation +4. **Enterprise alignment:** Matches Red Hat's OpenShift expertise +5. **Operational maturity:** Established patterns for monitoring, logging, RBAC + +### Consequences + +**Positive:** + +* Strong multi-tenant isolation via namespaces +* Declarative API via Custom Resources (AgenticSession, ProjectSettings, RFEWorkflow) +* Automatic cleanup via OwnerReferences +* RBAC integration for authorization +* Native integration with OpenShift OAuth +* Horizontal scaling of operator and backend components +* Established operational patterns (logs, metrics, events) + +**Negative:** + +* Higher learning curve for developers unfamiliar with K8s +* Requires K8s cluster for all deployments (including local dev) +* Operator complexity vs. simpler stateless services +* CRD versioning and migration challenges +* Resource overhead of K8s control plane + +**Risks:** + +* CRD API changes require careful migration planning +* Operator bugs can affect many sessions simultaneously +* K8s version skew between dev/prod environments + +## Implementation Notes + +**Architecture Components:** + +1. **Custom Resources (CRDs):** + - AgenticSession: Represents AI execution session + - ProjectSettings: Project-scoped configuration + - RFEWorkflow: Multi-agent refinement workflows + +2. **Operator Pattern:** + - Watches CRs and reconciles desired state + - Creates Kubernetes Jobs for session execution + - Updates CR status with results + +3. **Job-Based Execution:** + - Each AgenticSession spawns a Kubernetes Job + - Job runs Claude Code runner pod + - Results stored in CR status, PVCs for workspace + +4. **Multi-Tenancy:** + - Each project = one K8s namespace + - RBAC enforces access control + - Backend validates user tokens before CR operations + +**Key Files:** +* `components/manifests/base/*-crd.yaml` - CRD definitions +* `components/operator/internal/handlers/sessions.go` - Operator reconciliation +* `components/backend/handlers/sessions.go` - API to CR translation + +## Validation + +**Success Metrics:** + +* ✅ Multi-tenant isolation validated via RBAC tests +* ✅ Sessions scale from 1 to 50+ concurrent executions +* ✅ Zero cross-project access violations in testing +* ✅ Operator handles CRD updates without downtime + +**Lessons Learned:** + +* OwnerReferences critical for automatic cleanup +* Status subresource prevents race conditions in updates +* Job monitoring requires separate goroutine per session +* Local dev requires kind/CRC for K8s environment + +## Links + +* [Kubernetes Operator Pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) +* [Custom Resource Definitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +* Related: ADR-0002 (User Token Authentication) +``` + +**File:** `docs/adr/0002-user-token-authentication.md` + +```markdown +# ADR-0002: User Token Authentication for API Operations + +**Status:** Accepted +**Date:** 2024-11-21 +**Deciders:** Security Team, Platform Team +**Technical Story:** Security audit revealed RBAC bypass via service account + +## Context and Problem Statement + +The backend API needs to perform Kubernetes operations (list sessions, create CRs, etc.) on behalf of users. How should we authenticate and authorize these operations? + +**Initial implementation:** Backend used its own service account for all operations, checking user identity separately. + +**Problem discovered:** This bypassed Kubernetes RBAC, creating a security risk where backend could access resources the user couldn't. + +## Decision Drivers + +* **Security requirement:** Enforce Kubernetes RBAC at API boundary +* **Multi-tenancy:** Users should only access their authorized namespaces +* **Audit trail:** K8s audit logs should reflect actual user actions +* **Least privilege:** Backend should not have elevated permissions for user operations +* **Trust boundary:** Backend is the entry point, must validate properly + +## Considered Options + +1. **User token for all operations (user-scoped K8s client)** +2. **Backend service account with custom RBAC layer** +3. **Impersonation (backend impersonates user identity)** +4. **Hybrid: User token for reads, service account for writes** + +## Decision Outcome + +Chosen option: "User token for all operations", because: + +1. **Leverages K8s RBAC:** No need to duplicate authorization logic +2. **Security principle:** User operations use user permissions +3. **Audit trail:** K8s logs show actual user, not service account +4. **Least privilege:** Backend only uses service account when necessary +5. **Simplicity:** One pattern for user operations, exceptions documented + +**Exception:** Backend service account ONLY for: +- Writing CRs after user authorization validated (handlers/sessions.go:417) +- Minting service account tokens for runner pods (handlers/sessions.go:449) +- Cross-namespace operations backend is explicitly authorized for + +### Consequences + +**Positive:** + +* Kubernetes RBAC enforced automatically +* No custom authorization layer to maintain +* Audit logs reflect actual user identity +* RBAC violations fail at K8s API, not at backend +* Easy to debug permission issues (use `kubectl auth can-i`) + +**Negative:** + +* Must extract and validate user token on every request +* Token expiration can cause mid-request failures +* Slightly higher latency (extra K8s API call for RBAC check) +* Backend needs pattern to fall back to service account for specific operations + +**Risks:** + +* Token handling bugs could expose security vulnerabilities +* Token logging could leak credentials +* Service account fallback could be misused + +## Implementation Notes + +**Pattern 1: Extract User Token from Request** + +```go +func GetK8sClientsForRequest(c *gin.Context) (*kubernetes.Clientset, dynamic.Interface) { + rawAuth := c.GetHeader("Authorization") + parts := strings.SplitN(rawAuth, " ", 2) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + return nil, nil + } + token := strings.TrimSpace(parts[1]) + + config := &rest.Config{ + Host: K8sConfig.Host, + BearerToken: token, + TLSClientConfig: rest.TLSClientConfig{ + CAData: K8sConfig.CAData, + }, + } + + k8sClient, _ := kubernetes.NewForConfig(config) + dynClient, _ := dynamic.NewForConfig(config) + return k8sClient, dynClient +} +``` + +**Pattern 2: Use User-Scoped Client in Handlers** + +```go +func ListSessions(c *gin.Context) { + project := c.Param("projectName") + + reqK8s, reqDyn := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return + } + + // Use reqDyn for operations - RBAC enforced by K8s + list, err := reqDyn.Resource(gvr).Namespace(project).List(ctx, v1.ListOptions{}) + // ... +} +``` + +**Pattern 3: Service Account for Privileged Operations** + +```go +func CreateSession(c *gin.Context) { + // 1. Validate user has permission (using user token) + reqK8s, reqDyn := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + + // 2. Validate request body + var req CreateSessionRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + + // 3. Check user can create in this namespace + ssar := &authv1.SelfSubjectAccessReview{...} + res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{}) + if err != nil || !res.Status.Allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized"}) + return + } + + // 4. NOW use service account to write CR (after validation) + obj := &unstructured.Unstructured{...} + created, err := DynamicClient.Resource(gvr).Namespace(project).Create(ctx, obj, v1.CreateOptions{}) + // ... +} +``` + +**Security Measures:** + +* Token redaction in logs (server/server.go:22-34) +* Never log token values, only length: `log.Printf("tokenLen=%d", len(token))` +* Token extraction in dedicated function for consistency +* Return 401 immediately if token invalid + +**Key Files:** +* `handlers/middleware.go:GetK8sClientsForRequest()` - Token extraction +* `handlers/sessions.go:227` - User validation then SA create pattern +* `server/server.go:22-34` - Token redaction formatter + +## Validation + +**Security Testing:** + +* ✅ User cannot list sessions in unauthorized namespaces +* ✅ User cannot create sessions without RBAC permissions +* ✅ K8s audit logs show user identity, not service account +* ✅ Token expiration properly handled with 401 response +* ✅ No tokens found in application logs + +**Performance Impact:** + +* Negligible (<5ms) latency increase for RBAC validation +* No additional K8s API calls (RBAC check happens in K8s) + +## Links + +* Related: ADR-0001 (Kubernetes-Native Architecture) +* [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) +* [Token Review API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/) +``` + +**File:** `docs/adr/0003-multi-repo-support.md` + +```markdown +# ADR-0003: Multi-Repository Support in AgenticSessions + +**Status:** Accepted +**Date:** 2024-11-21 +**Deciders:** Product Team, Engineering Team +**Technical Story:** User request for cross-repo analysis and modification + +## Context and Problem Statement + +Users needed to execute AI sessions that operate across multiple Git repositories simultaneously. For example: +- Analyze dependencies between frontend and backend repos +- Make coordinated changes across microservices +- Generate documentation that references multiple codebases + +Original design: AgenticSession operated on a single repository. + +How should we extend AgenticSessions to support multiple repositories while maintaining simplicity and clear semantics? + +## Decision Drivers + +* **User need:** Cross-repo analysis and modification workflows +* **Clarity:** Need clear semantics for which repo is "primary" +* **Workspace model:** Claude Code expects a single working directory +* **Git operations:** Push/PR creation needs per-repo configuration +* **Status tracking:** Need to track per-repo outcomes (pushed vs. abandoned) +* **Backward compatibility:** Don't break single-repo workflows + +## Considered Options + +1. **Multiple repos with mainRepoIndex (chosen)** +2. **Separate sessions per repo with orchestration layer** +3. **Multi-root workspace (multiple working directories)** +4. **Merge all repos into monorepo temporarily** + +## Decision Outcome + +Chosen option: "Multiple repos with mainRepoIndex", because: + +1. **Claude Code compatibility:** Single working directory aligns with claude-code CLI +2. **Clear semantics:** mainRepoIndex explicitly specifies "primary" repo +3. **Flexibility:** Can reference other repos via relative paths +4. **Status tracking:** Per-repo pushed/abandoned status in CR +5. **Backward compatible:** Single-repo sessions just have one entry in repos array + +### Consequences + +**Positive:** + +* Enables cross-repo workflows (analysis, coordinated changes) +* Per-repo push status provides clear outcome tracking +* mainRepoIndex makes "primary repository" explicit +* Backward compatible with single-repo sessions +* Supports different git configs per repo (fork vs. direct push) + +**Negative:** + +* Increased complexity in session CR structure +* Clone order matters (mainRepo must be cloned first to establish working directory) +* File paths between repos can be confusing for users +* Workspace cleanup more complex with multiple repos + +**Risks:** + +* Users might not understand which repo is "main" +* Large number of repos could cause workspace size issues +* Git credentials management across repos more complex + +## Implementation Notes + +**AgenticSession Spec Structure:** + +```yaml +apiVersion: vteam.ambient-code/v1alpha1 +kind: AgenticSession +metadata: + name: multi-repo-session +spec: + prompt: "Analyze API compatibility between frontend and backend" + + # repos is an array of repository configurations + repos: + - input: + url: "https://github.com/org/frontend" + branch: "main" + output: + type: "fork" + targetBranch: "feature-update" + createPullRequest: true + + - input: + url: "https://github.com/org/backend" + branch: "main" + output: + type: "direct" + pushBranch: "feature-update" + + # mainRepoIndex specifies which repo is the working directory (0-indexed) + mainRepoIndex: 0 # frontend is the main repo + + interactive: false + timeout: 3600 +``` + +**Status Structure:** + +```yaml +status: + phase: "Completed" + startTime: "2024-11-21T10:00:00Z" + completionTime: "2024-11-21T10:30:00Z" + + # Per-repo status tracking + repoStatuses: + - repoURL: "https://github.com/org/frontend" + status: "pushed" + message: "PR #123 created" + + - repoURL: "https://github.com/org/backend" + status: "abandoned" + message: "No changes made" +``` + +**Clone Implementation Pattern:** + +```python +# components/runners/claude-code-runner/wrapper.py + +def clone_repositories(repos, main_repo_index, workspace): + """Clone repos in correct order: mainRepo first, others after.""" + + # Clone main repo first to establish working directory + main_repo = repos[main_repo_index] + main_path = clone_repo(main_repo["input"]["url"], workspace) + os.chdir(main_path) # Set as working directory + + # Clone other repos relative to workspace + for i, repo in enumerate(repos): + if i == main_repo_index: + continue + clone_repo(repo["input"]["url"], workspace) + + return main_path +``` + +**Key Files:** +* `components/backend/types/session.go:RepoConfig` - Repo configuration types +* `components/backend/handlers/sessions.go:227` - Multi-repo validation +* `components/runners/claude-code-runner/wrapper.py:clone_repositories` - Clone logic +* `components/operator/internal/handlers/sessions.go:150` - Status tracking + +**Patterns Established:** + +* mainRepoIndex defaults to 0 if not specified +* repos array must have at least one entry +* Per-repo output configuration (fork vs. direct push) +* Per-repo status tracking (pushed, abandoned, error) + +## Validation + +**Testing Scenarios:** + +* ✅ Single-repo session (backward compatibility) +* ✅ Two-repo session with mainRepoIndex=0 +* ✅ Two-repo session with mainRepoIndex=1 +* ✅ Cross-repo file analysis +* ✅ Per-repo push status correctly reported +* ✅ Clone failure in secondary repo doesn't block main repo + +**User Feedback:** + +* Positive: Enables new workflow patterns (monorepo analysis) +* Confusion: Initially unclear which repo is "main" +* Resolution: Added documentation and examples + +## Links + +* Related: ADR-0001 (Kubernetes-Native Architecture) +* Implementation PR: #XXX +* User documentation: `docs/user-guide/multi-repo-sessions.md` +``` + +**File:** `docs/adr/0004-go-backend-python-runner.md` + +```markdown +# ADR-0004: Go Backend with Python Claude Runner + +**Status:** Accepted +**Date:** 2024-11-21 +**Deciders:** Architecture Team +**Technical Story:** Technology stack selection for platform components + +## Context and Problem Statement + +We need to choose programming languages for two distinct components: + +1. **Backend API:** HTTP server managing Kubernetes resources, authentication, project management +2. **Claude Code Runner:** Executes claude-code CLI in Job pods + +What languages should we use for each component, and should they be the same or different? + +## Decision Drivers + +* **Backend needs:** HTTP routing, K8s client-go, RBAC, high concurrency +* **Runner needs:** Claude Code SDK, file manipulation, git operations +* **Performance:** Backend handles many concurrent requests +* **Developer experience:** Team expertise, library ecosystems +* **Operational:** Container size, startup time, resource usage +* **Maintainability:** Type safety, tooling, debugging + +## Considered Options + +1. **Go backend + Python runner (chosen)** +2. **All Python (FastAPI backend + Python runner)** +3. **All Go (Go backend + Go wrapper for claude-code)** +4. **Polyglot (Node.js backend + Python runner)** + +## Decision Outcome + +Chosen option: "Go backend + Python runner", because: + +**Go for Backend:** +1. **K8s ecosystem:** client-go is canonical K8s library +2. **Performance:** Low latency HTTP handling, efficient concurrency +3. **Type safety:** Compile-time checks for K8s resources +4. **Deployment:** Single static binary, fast startup +5. **Team expertise:** Red Hat strong Go background + +**Python for Runner:** +1. **Claude Code SDK:** Official SDK is Python-first (`claude-code-sdk`) +2. **Anthropic ecosystem:** Python has best library support +3. **Scripting flexibility:** Git operations, file manipulation easier in Python +4. **Dynamic execution:** Easier to handle varying prompts and workflows + +### Consequences + +**Positive:** + +* **Backend:** + - Fast HTTP response times (<10ms for simple operations) + - Small container images (~20MB for Go binary) + - Excellent K8s client-go integration + - Strong typing prevents many bugs + +* **Runner:** + - Native Claude Code SDK support + - Rich Python ecosystem for git/file operations + - Easy to extend with custom agent behaviors + - Rapid iteration on workflow logic + +**Negative:** + +* **Maintenance:** + - Two language ecosystems to maintain + - Different tooling (go vs. pip/uv) + - Different testing frameworks + +* **Development:** + - Context switching between languages + - Cannot share code between backend and runner + - Different error handling patterns + +**Risks:** + +* Python runner startup slower than Go (~1-2s vs. <100ms) +* Python container images larger (~500MB vs. ~20MB) +* Dependency vulnerabilities in Python ecosystem + +## Implementation Notes + +**Backend (Go):** + +```go +// Fast HTTP routing with Gin +r := gin.Default() +r.GET("/api/projects/:project/sessions", handlers.ListSessions) + +// Type-safe K8s client +clientset, _ := kubernetes.NewForConfig(config) +sessions, err := clientset.CoreV1().Pods(namespace).List(ctx, v1.ListOptions{}) +``` + +**Technology Stack:** +- Framework: Gin (HTTP routing) +- K8s client: client-go + dynamic client +- Testing: table-driven tests with testify + +**Runner (Python):** + +```python +# Claude Code SDK integration +from claude_code import AgenticSession + +session = AgenticSession(prompt=prompt, workspace=workspace) +result = session.run() +``` + +**Technology Stack:** +- SDK: claude-code-sdk (>=0.0.23) +- API client: anthropic (>=0.68.0) +- Git: GitPython +- Package manager: uv (preferred over pip) + +**Key Files:** +* `components/backend/` - Go backend +* `components/runners/claude-code-runner/` - Python runner +* `components/backend/go.mod` - Go dependencies +* `components/runners/claude-code-runner/requirements.txt` - Python dependencies + +**Build Optimization:** + +* Go: Multi-stage Docker build, static binary +* Python: uv for fast dependency resolution, layer caching + +## Validation + +**Performance Metrics:** + +* Backend response time: <10ms for simple operations +* Backend concurrency: Handles 100+ concurrent requests +* Runner startup: ~2s (acceptable for long-running sessions) +* Container build time: <2min for both components + +**Developer Feedback:** + +* Positive: Go backend very stable, easy to debug +* Positive: Python runner easy to extend +* Concern: Context switching between languages +* Mitigation: Clear component boundaries reduce switching + +## Links + +* Related: ADR-0001 (Kubernetes-Native Architecture) +* [client-go documentation](https://github.com/kubernetes/client-go) +* [Claude Code SDK](https://github.com/anthropics/claude-code-sdk) +``` + +**File:** `docs/adr/0005-nextjs-shadcn-react-query.md` + +```markdown +# ADR-0005: Next.js with Shadcn UI and React Query + +**Status:** Accepted +**Date:** 2024-11-21 +**Deciders:** Frontend Team +**Technical Story:** Frontend technology stack selection + +## Context and Problem Statement + +We need to build a modern web UI for the Ambient Code Platform with: +- Server-side rendering for fast initial loads +- Rich interactive components (session monitoring, project management) +- Real-time updates for session status +- Type-safe API integration +- Responsive design with accessible components + +What frontend framework and UI library should we use? + +## Decision Drivers + +* **Modern patterns:** Server components, streaming, type safety +* **Developer experience:** Good tooling, active community +* **UI quality:** Professional design system, accessibility +* **Performance:** Fast initial load, efficient updates +* **Data fetching:** Caching, optimistic updates, real-time sync +* **Team expertise:** React knowledge on team + +## Considered Options + +1. **Next.js 14 + Shadcn UI + React Query (chosen)** +2. **Create React App + Material-UI + Redux** +3. **Remix + Chakra UI + React Query** +4. **Svelte/SvelteKit + Custom components** + +## Decision Outcome + +Chosen option: "Next.js 14 + Shadcn UI + React Query", because: + +**Next.js 14 (App Router):** +1. **Server components:** Reduced client bundle size +2. **Streaming:** Progressive page rendering +3. **File-based routing:** Intuitive project structure +4. **TypeScript:** First-class type safety +5. **Industry momentum:** Large ecosystem, active development + +**Shadcn UI:** +1. **Copy-paste components:** Own your component code +2. **Built on Radix UI:** Accessibility built-in +3. **Tailwind CSS:** Utility-first styling +4. **Customizable:** Full control over styling +5. **No runtime dependency:** Just copy components you need + +**React Query:** +1. **Declarative data fetching:** Clean component code +2. **Automatic caching:** Reduces API calls +3. **Optimistic updates:** Better UX +4. **Real-time sync:** Easy integration with WebSockets +5. **DevTools:** Excellent debugging experience + +### Consequences + +**Positive:** + +* **Performance:** + - Server components reduce client JS by ~40% + - React Query caching reduces redundant API calls + - Streaming improves perceived performance + +* **Developer Experience:** + - TypeScript end-to-end (API to UI) + - Shadcn components copy-pasted and owned + - React Query hooks simplify data management + - Next.js DevTools for debugging + +* **User Experience:** + - Fast initial page loads (SSR) + - Smooth client-side navigation + - Accessible components (WCAG 2.1 AA) + - Responsive design (mobile-first) + +**Negative:** + +* **Learning curve:** + - Next.js App Router is new (released 2023) + - Server vs. client component mental model + - React Query concepts (queries, mutations, invalidation) + +* **Complexity:** + - More moving parts than simple SPA + - Server component restrictions (no hooks, browser APIs) + - Hydration errors if server/client mismatch + +**Risks:** + +* Next.js App Router still evolving (breaking changes possible) +* Shadcn UI components need manual updates (not npm package) +* React Query cache invalidation can be tricky + +## Implementation Notes + +**Project Structure:** + +``` +components/frontend/src/ +├── app/ # Next.js App Router pages +│ ├── projects/ +│ │ └── [projectName]/ +│ │ ├── sessions/ +│ │ │ ├── page.tsx # Sessions list +│ │ │ └── [sessionName]/ +│ │ │ └── page.tsx # Session detail +│ │ └── layout.tsx +│ └── layout.tsx +├── components/ +│ ├── ui/ # Shadcn UI components (owned) +│ │ ├── button.tsx +│ │ ├── card.tsx +│ │ └── dialog.tsx +│ └── [feature]/ # Feature-specific components +├── services/ +│ ├── api/ # API client layer +│ │ └── sessions.ts +│ └── queries/ # React Query hooks +│ └── sessions.ts +└── lib/ + └── utils.ts +``` + +**Key Patterns:** + +**1. Server Component for Initial Data** + +```typescript +// app/projects/[projectName]/sessions/page.tsx +export default async function SessionsPage({ + params, +}: { + params: { projectName: string } +}) { + // Fetch on server for initial render + const sessions = await sessionApi.list(params.projectName) + + return +} +``` + +**2. Client Component with React Query** + +```typescript +// components/sessions/sessions-list.tsx +'use client' + +import { useSessions } from "@/services/queries/sessions" + +export function SessionsList({ + initialData, + projectName +}: { + initialData: Session[] + projectName: string +}) { + const { data: sessions, isLoading } = useSessions(projectName, { + initialData, // Use server data initially + refetchInterval: 5000, // Poll every 5s + }) + + return ( +
+ {sessions.map(session => ( + + ))} +
+ ) +} +``` + +**3. Mutations with Optimistic Updates** + +```typescript +// services/queries/sessions.ts +export function useCreateSession(projectName: string) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (data: CreateSessionRequest) => + sessionApi.create(projectName, data), + + onMutate: async (newSession) => { + // Cancel outgoing refetches + await queryClient.cancelQueries({ queryKey: ["sessions", projectName] }) + + // Snapshot previous value + const previous = queryClient.getQueryData(["sessions", projectName]) + + // Optimistically update + queryClient.setQueryData(["sessions", projectName], (old: Session[]) => [ + ...old, + { ...newSession, status: { phase: "Pending" } }, + ]) + + return { previous } + }, + + onError: (err, variables, context) => { + // Rollback on error + queryClient.setQueryData(["sessions", projectName], context?.previous) + }, + + onSuccess: () => { + // Refetch after success + queryClient.invalidateQueries({ queryKey: ["sessions", projectName] }) + }, + }) +} +``` + +**4. Shadcn Component Usage** + +```typescript +import { Button } from "@/components/ui/button" +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card" +import { Dialog, DialogTrigger, DialogContent } from "@/components/ui/dialog" + +export function SessionCard({ session }: { session: Session }) { + return ( + + + {session.metadata.name} + + + + + + + + {/* Session details */} + + + + + ) +} +``` + +**Technology Versions:** + +- Next.js: 14.x (App Router) +- React: 18.x +- Shadcn UI: Latest (no version, copy-paste) +- TanStack React Query: 5.x +- Tailwind CSS: 3.x +- TypeScript: 5.x + +**Key Files:** +* `components/frontend/DESIGN_GUIDELINES.md` - Comprehensive patterns +* `components/frontend/src/components/ui/` - Shadcn components +* `components/frontend/src/services/queries/` - React Query hooks +* `components/frontend/src/app/` - Next.js pages + +## Validation + +**Performance Metrics:** + +* Initial page load: <2s (Lighthouse score >90) +* Client bundle size: <200KB (with code splitting) +* Time to Interactive: <3s +* API call reduction: 60% fewer calls (React Query caching) + +**Developer Feedback:** + +* Positive: React Query simplifies data management significantly +* Positive: Shadcn components easy to customize +* Challenge: Server component restrictions initially confusing +* Resolution: Clear guidelines in DESIGN_GUIDELINES.md + +**User Feedback:** + +* Fast perceived performance (streaming) +* Smooth interactions (optimistic updates) +* Accessible (keyboard navigation, screen readers) + +## Links + +* Related: ADR-0004 (Go Backend with Python Runner) +* [Next.js 14 Documentation](https://nextjs.org/docs) +* [Shadcn UI](https://ui.shadcn.com/) +* [TanStack React Query](https://tanstack.com/query/latest) +* Frontend Guidelines: `components/frontend/DESIGN_GUIDELINES.md` +``` + +### Success Criteria + +- [ ] `docs/adr/` directory created +- [ ] ADR template created with complete structure +- [ ] ADR README with index and workflow instructions +- [ ] 5 ADRs created documenting critical architectural decisions +- [ ] Each ADR includes context, options, decision, and consequences + +--- + +## Component 3: Repomix Usage Guide + +### Overview + +You already have 7 repomix views of the codebase! Create a guide for when to use each one. + +### Implementation + +**File:** `.claude/repomix-guide.md` + +```markdown +# Repomix Context Switching Guide + +**Purpose:** Quick reference for loading the right repomix view based on the task. + +## Available Views + +The `repomix-analysis/` directory contains 7 pre-generated codebase views optimized for different scenarios: + +| File | Size | Use When | +|------|------|----------| +| `01-full-context.xml` | 2.1MB | Deep dive into specific component implementation | +| `02-production-optimized.xml` | 4.2MB | General development work, most common use case | +| `03-architecture-only.xml` | 737KB | Understanding system design, new team member onboarding | +| `04-backend-focused.xml` | 403KB | Backend API work (Go handlers, K8s integration) | +| `05-frontend-focused.xml` | 767KB | UI development (NextJS, React Query, Shadcn) | +| `06-ultra-compressed.xml` | 10MB | Quick overview, exploring unfamiliar areas | +| `07-metadata-rich.xml` | 849KB | File structure analysis, refactoring planning | + +## Usage Patterns + +### Scenario 1: Backend Development + +**Task:** Adding a new API endpoint for project settings + +**Command:** +``` +"Claude, reference the backend-focused repomix view (04-backend-focused.xml) and help me add a new endpoint for updating project settings." +``` + +**Why this view:** +- Contains all backend handlers and types +- Includes K8s client patterns +- Focused context without frontend noise + +### Scenario 2: Frontend Development + +**Task:** Creating a new UI component for RFE workflows + +**Command:** +``` +"Claude, load the frontend-focused repomix view (05-frontend-focused.xml) and help me create a new component for displaying RFE workflow steps." +``` + +**Why this view:** +- All React components and pages +- Shadcn UI patterns +- React Query hooks + +### Scenario 3: Architecture Understanding + +**Task:** Explaining the system to a new team member + +**Command:** +``` +"Claude, using the architecture-only repomix view (03-architecture-only.xml), explain how the operator watches for AgenticSession creation and spawns jobs." +``` + +**Why this view:** +- High-level component structure +- CRD definitions +- Component relationships +- No implementation details + +### Scenario 4: Cross-Component Analysis + +**Task:** Tracing a request from frontend through backend to operator + +**Command:** +``` +"Claude, use the production-optimized repomix view (02-production-optimized.xml) and trace the flow of creating an AgenticSession from UI click to Job creation." +``` + +**Why this view:** +- Balanced coverage of all components +- Includes key implementation files +- Not overwhelmed with test files + +### Scenario 5: Quick Exploration + +**Task:** Finding where a specific feature is implemented + +**Command:** +``` +"Claude, use the ultra-compressed repomix view (06-ultra-compressed.xml) to help me find where multi-repo support is implemented." +``` + +**Why this view:** +- Fast to process +- Good for keyword searches +- Covers entire codebase breadth + +### Scenario 6: Refactoring Planning + +**Task:** Planning to break up large handlers/sessions.go file + +**Command:** +``` +"Claude, analyze the metadata-rich repomix view (07-metadata-rich.xml) and suggest how to split handlers/sessions.go into smaller modules." +``` + +**Why this view:** +- File size and structure metadata +- Module boundaries +- Import relationships + +### Scenario 7: Deep Implementation Dive + +**Task:** Debugging a complex operator reconciliation issue + +**Command:** +``` +"Claude, load the full-context repomix view (01-full-context.xml) and help me understand why the operator is creating duplicate jobs for the same session." +``` + +**Why this view:** +- Complete implementation details +- All edge case handling +- Full operator logic + +## Best Practices + +### Start Broad, Then Narrow + +1. **First pass:** Use `03-architecture-only.xml` to understand where the feature lives +2. **Second pass:** Use component-specific view (`04-backend` or `05-frontend`) +3. **Deep dive:** Use `01-full-context.xml` for specific implementation details + +### Combine with Context Files + +For even better results, combine repomix views with context files: + +``` +"Claude, load the backend-focused repomix view (04) and the backend-development context file, then help me add user token authentication to the new endpoint." +``` + +### Regenerate Periodically + +Repomix views are snapshots in time. Regenerate monthly (or after major changes): + +```bash +# Full regeneration +cd repomix-analysis +./regenerate-all.sh # If you create this script + +# Or manually +repomix --output 02-production-optimized.xml --config repomix-production.json +``` + +**Tip:** Add to monthly maintenance calendar. + +## Quick Reference Table + +| Task Type | Repomix View | Context File | +|-----------|--------------|--------------| +| Backend API work | 04-backend-focused | backend-development.md | +| Frontend UI work | 05-frontend-focused | frontend-development.md | +| Security review | 02-production-optimized | security-standards.md | +| Architecture overview | 03-architecture-only | - | +| Quick exploration | 06-ultra-compressed | - | +| Refactoring | 07-metadata-rich | - | +| Deep debugging | 01-full-context | (component-specific) | + +## Maintenance + +**When to regenerate:** +- After major architectural changes +- Monthly (scheduled) +- Before major refactoring efforts +- When views feel "stale" (>2 months old) + +**How to regenerate:** +See `.repomixignore` for exclusion patterns. Adjust as needed to balance completeness with token efficiency. +``` + +### Success Criteria + +- [ ] `.claude/repomix-guide.md` created +- [ ] All 7 repomix views documented with use cases +- [ ] Practical examples for each scenario +- [ ] Quick reference table for task-to-view mapping + +--- + +## Component 4: Decision Log + +### Overview + +Lightweight chronological log of major decisions. Easier to maintain than full ADRs. + +### Implementation + +**File:** `docs/decisions.md` + +```markdown +# Decision Log + +Chronological record of significant technical and architectural decisions for the Ambient Code Platform. For formal ADRs, see `docs/adr/`. + +**Format:** +- **Date:** When the decision was made +- **Decision:** What was decided +- **Why:** Brief rationale (1-2 sentences) +- **Impact:** What changed as a result +- **Related:** Links to ADRs, PRs, issues + +--- + +## 2024-11-21: User Token Authentication for All API Operations + +**Decision:** Backend must use user-provided bearer token for all Kubernetes operations on behalf of users. Service account only for privileged operations (writing CRs after validation, minting tokens). + +**Why:** Ensures Kubernetes RBAC is enforced at API boundary, preventing security bypass. Backend should not have elevated permissions for user operations. + +**Impact:** +- All handlers now use `GetK8sClientsForRequest(c)` to extract user token +- Return 401 if token is invalid or missing +- K8s audit logs now reflect actual user identity +- Added token redaction in logs to prevent credential leaks + +**Related:** +- ADR-0002 (User Token Authentication) +- Security context: `.claude/context/security-standards.md` +- Implementation: `components/backend/handlers/middleware.go` + +--- + +## 2024-11-15: Multi-Repo Support in AgenticSessions + +**Decision:** Added support for multiple repositories in a single AgenticSession with `mainRepoIndex` to specify the primary working directory. + +**Why:** Users needed to perform cross-repo analysis and make coordinated changes across multiple codebases (e.g., frontend + backend). + +**Impact:** +- AgenticSession spec now has `repos` array instead of single `repo` +- Added `mainRepoIndex` field (defaults to 0) +- Per-repo status tracking: `pushed` or `abandoned` +- Clone order matters: mainRepo cloned first to establish working directory + +**Related:** +- ADR-0003 (Multi-Repository Support) +- Implementation: `components/backend/types/session.go` +- Runner logic: `components/runners/claude-code-runner/wrapper.py` + +**Gotchas:** +- Git operations need absolute paths to handle multiple repos +- Clone order affects workspace initialization +- Need explicit cleanup if clone fails + +--- + +## 2024-11-10: Frontend Migration to React Query + +**Decision:** Migrated all frontend data fetching from manual `fetch()` calls to TanStack React Query hooks. + +**Why:** React Query provides automatic caching, optimistic updates, and real-time synchronization out of the box. Eliminates boilerplate state management. + +**Impact:** +- Created `services/queries/` directory with hooks for each resource +- Removed manual `useState` + `useEffect` data fetching patterns +- Added optimistic updates for create/delete operations +- Reduced API calls by ~60% through intelligent caching + +**Related:** +- Frontend context: `.claude/context/frontend-development.md` +- Pattern file: `.claude/patterns/react-query-usage.md` +- Implementation: `components/frontend/src/services/queries/` + +--- + +## 2024-11-05: Adopted Shadcn UI Component Library + +**Decision:** Standardized on Shadcn UI for all UI components. Forbidden to create custom components for buttons, inputs, dialogs, etc. + +**Why:** Shadcn provides accessible, customizable components built on Radix UI primitives. "Copy-paste" model means we own the code and can customize fully. + +**Impact:** +- All existing custom button/input components replaced with Shadcn equivalents +- Added DESIGN_GUIDELINES.md enforcing "Shadcn UI only" rule +- Improved accessibility (WCAG 2.1 AA compliance) +- Consistent design language across the platform + +**Related:** +- ADR-0005 (Next.js with Shadcn UI and React Query) +- Frontend guidelines: `components/frontend/DESIGN_GUIDELINES.md` +- Available components: `components/frontend/src/components/ui/` + +--- + +## 2024-10-20: Kubernetes Job-Based Session Execution + +**Decision:** Execute AgenticSessions as Kubernetes Jobs instead of long-running Deployments. + +**Why:** Jobs provide better lifecycle management for batch workloads. Automatic cleanup on completion, restart policies for failures, and clear success/failure status. + +**Impact:** +- Operator creates Job (not Deployment) for each session +- Jobs have OwnerReferences pointing to AgenticSession CR +- Automatic cleanup when session CR is deleted +- Job status mapped to AgenticSession status + +**Related:** +- ADR-0001 (Kubernetes-Native Architecture) +- Operator implementation: `components/operator/internal/handlers/sessions.go` + +**Gotchas:** +- Jobs cannot be updated once created (must delete and recreate) +- Job pods need proper OwnerReferences for cleanup +- Monitoring requires separate goroutine per job + +--- + +## 2024-10-15: Go for Backend, Python for Runner + +**Decision:** Use Go for the backend API server, Python for the Claude Code runner. + +**Why:** Go provides excellent Kubernetes client-go integration and performance for the API. Python has first-class Claude Code SDK support and is better for scripting git operations. + +**Impact:** +- Backend built with Go + Gin framework +- Runner built with Python + claude-code-sdk +- Two separate container images +- Different build and test tooling for each component + +**Related:** +- ADR-0004 (Go Backend with Python Runner) +- Backend: `components/backend/` +- Runner: `components/runners/claude-code-runner/` + +--- + +## 2024-10-01: CRD-Based Architecture + +**Decision:** Define AgenticSession, ProjectSettings, and RFEWorkflow as Kubernetes Custom Resources (CRDs). + +**Why:** CRDs provide declarative API, automatic RBAC integration, and versioning. Operator pattern allows reconciliation of desired state. + +**Impact:** +- Created three CRDs with proper validation schemas +- Operator watches CRs and reconciles state +- Backend translates HTTP API to CR operations +- Users can interact via kubectl or web UI + +**Related:** +- ADR-0001 (Kubernetes-Native Architecture) +- CRD definitions: `components/manifests/base/*-crd.yaml` + +--- + +## Template for New Entries + +Copy this template when adding new decisions: + +```markdown +## YYYY-MM-DD: [Decision Title] + +**Decision:** [One sentence: what was decided] + +**Why:** [1-2 sentences: rationale] + +**Impact:** +- [Change 1] +- [Change 2] +- [Change 3] + +**Related:** +- [Link to ADR if exists] +- [Link to implementation] +- [Link to context file] + +**Gotchas:** (optional) +- [Gotcha 1] +- [Gotcha 2] +``` +``` + +### Success Criteria + +- [ ] `docs/decisions.md` created +- [ ] Includes template for new entries +- [ ] 7-10 initial entries covering major decisions +- [ ] Each entry links to relevant ADRs, code, and context files + +--- + +## Component 5: Pattern Catalog + +### Overview + +Document recurring code patterns with concrete examples from the codebase. + +### Implementation + +**Step 5.1:** Create directory structure + +```bash +mkdir -p .claude/patterns +``` + +**Step 5.2:** Create error handling pattern + +**File:** `.claude/patterns/error-handling.md` + +```markdown +# Error Handling Patterns + +Consistent error handling patterns across backend and operator components. + +## Backend Handler Errors + +### Pattern 1: Resource Not Found + +```go +// handlers/sessions.go:350 +func GetSession(c *gin.Context) { + projectName := c.Param("projectName") + sessionName := c.Param("sessionName") + + reqK8s, reqDyn := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + return + } + + obj, err := reqDyn.Resource(gvr).Namespace(projectName).Get(ctx, sessionName, v1.GetOptions{}) + if errors.IsNotFound(err) { + c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"}) + return + } + if err != nil { + log.Printf("Failed to get session %s/%s: %v", projectName, sessionName, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve session"}) + return + } + + c.JSON(http.StatusOK, obj) +} +``` + +**Key points:** +- Check `errors.IsNotFound(err)` for 404 scenarios +- Log errors with context (project, session name) +- Return generic error messages to user (don't expose internals) +- Use appropriate HTTP status codes + +### Pattern 2: Validation Errors + +```go +// handlers/sessions.go:227 +func CreateSession(c *gin.Context) { + var req CreateSessionRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + return + } + + // Validate resource name format + if !isValidK8sName(req.Name) { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid name: must be a valid Kubernetes DNS label", + }) + return + } + + // Validate required fields + if req.Prompt == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Prompt is required"}) + return + } + + // ... create session +} +``` + +**Key points:** +- Validate early, return 400 Bad Request +- Provide specific error messages for validation failures +- Check K8s naming requirements (DNS labels) + +### Pattern 3: Authorization Errors + +```go +// handlers/sessions.go:250 +ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Group: "vteam.ambient-code", + Resource: "agenticsessions", + Verb: "create", + Namespace: projectName, + }, + }, +} + +res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{}) +if err != nil { + log.Printf("Authorization check failed: %v", err) + c.JSON(http.StatusForbidden, gin.H{"error": "Authorization check failed"}) + return +} + +if !res.Status.Allowed { + log.Printf("User not authorized to create sessions in %s", projectName) + c.JSON(http.StatusForbidden, gin.H{"error": "You do not have permission to create sessions in this project"}) + return +} +``` + +**Key points:** +- Always check RBAC before operations +- Return 403 Forbidden for authorization failures +- Log authorization failures for security auditing + +## Operator Reconciliation Errors + +### Pattern 1: Resource Deleted During Processing + +```go +// operator/internal/handlers/sessions.go:85 +func handleAgenticSessionEvent(obj *unstructured.Unstructured) error { + name := obj.GetName() + namespace := obj.GetNamespace() + + // Verify resource still exists (race condition check) + currentObj, err := config.DynamicClient.Resource(gvr).Namespace(namespace).Get(ctx, name, v1.GetOptions{}) + if errors.IsNotFound(err) { + log.Printf("AgenticSession %s/%s no longer exists, skipping reconciliation", namespace, name) + return nil // NOT an error - resource was deleted + } + if err != nil { + return fmt.Errorf("failed to get current object: %w", err) + } + + // ... continue reconciliation with currentObj +} +``` + +**Key points:** +- `IsNotFound` during reconciliation is NOT an error (resource deleted) +- Return `nil` to avoid retries for deleted resources +- Log the skip for debugging purposes + +### Pattern 2: Job Creation Failures + +```go +// operator/internal/handlers/sessions.go:125 +job := buildJobSpec(sessionName, namespace, spec) + +createdJob, err := config.K8sClient.BatchV1().Jobs(namespace).Create(ctx, job, v1.CreateOptions{}) +if err != nil { + log.Printf("Failed to create job for session %s/%s: %v", namespace, sessionName, err) + + // Update session status to reflect error + updateAgenticSessionStatus(namespace, sessionName, map[string]interface{}{ + "phase": "Error", + "message": fmt.Sprintf("Failed to create job: %v", err), + }) + + return fmt.Errorf("failed to create job: %w", err) +} + +log.Printf("Created job %s for session %s/%s", createdJob.Name, namespace, sessionName) +``` + +**Key points:** +- Log failures with full context +- Update CR status to reflect error state +- Return error to trigger retry (if appropriate) +- Include wrapped error for debugging (`%w`) + +### Pattern 3: Status Update Failures (Non-Fatal) + +```go +// operator/internal/handlers/sessions.go:200 +if err := updateAgenticSessionStatus(namespace, sessionName, map[string]interface{}{ + "phase": "Running", + "startTime": time.Now().Format(time.RFC3339), +}); err != nil { + log.Printf("Warning: failed to update status for %s/%s: %v", namespace, sessionName, err) + // Continue - job was created successfully, status update is secondary +} +``` + +**Key points:** +- Status updates are often non-fatal (job still created) +- Log as warning, not error +- Don't return error if primary operation succeeded + +## Python Runner Errors + +### Pattern: Graceful Error Handling with Status Updates + +```python +# components/runners/claude-code-runner/wrapper.py +try: + result = run_claude_session(prompt, workspace, interactive) + + # Update CR with success + update_session_status(namespace, name, { + "phase": "Completed", + "results": result, + "completionTime": datetime.utcnow().isoformat() + "Z", + }) + +except GitError as e: + logger.error(f"Git operation failed: {e}") + update_session_status(namespace, name, { + "phase": "Error", + "message": f"Git operation failed: {str(e)}", + }) + sys.exit(1) + +except ClaudeAPIError as e: + logger.error(f"Claude API error: {e}") + update_session_status(namespace, name, { + "phase": "Error", + "message": f"AI service error: {str(e)}", + }) + sys.exit(1) + +except Exception as e: + logger.error(f"Unexpected error: {e}", exc_info=True) + update_session_status(namespace, name, { + "phase": "Error", + "message": f"Unexpected error: {str(e)}", + }) + sys.exit(1) +``` + +**Key points:** +- Catch specific exceptions first, generic last +- Always update CR status before exiting +- Use `exc_info=True` for unexpected errors (full traceback) +- Exit with non-zero code on errors (K8s Job will show failure) + +## Anti-Patterns (DO NOT USE) + +### ❌ Panic in Production Code + +```go +// NEVER DO THIS in handlers or operator +if err != nil { + panic(fmt.Sprintf("Failed to create session: %v", err)) +} +``` + +**Why wrong:** Crashes the entire process, affects all requests/sessions. +**Use instead:** Return errors, update status, log failures. + +### ❌ Silent Failures + +```go +// NEVER DO THIS +if err := doSomething(); err != nil { + // Ignore error, continue +} +``` + +**Why wrong:** Hides bugs, makes debugging impossible. +**Use instead:** At minimum, log the error. Better: return or update status. + +### ❌ Exposing Internal Errors to Users + +```go +// DON'T DO THIS +if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": fmt.Sprintf("Database query failed: %v", err), // Exposes internals + }) +} +``` + +**Why wrong:** Leaks implementation details, security risk. +**Use instead:** Generic user message, detailed log message. + +```go +// DO THIS +if err != nil { + log.Printf("Database query failed: %v", err) // Detailed log + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve session", // Generic user message + }) +} +``` + +## Quick Reference + +| Scenario | HTTP Status | Log Level | Return Error? | +|----------|-------------|-----------|---------------| +| Resource not found | 404 | Info | No | +| Invalid input | 400 | Info | No | +| Auth failure | 401/403 | Warning | No | +| K8s API error | 500 | Error | No (user), Yes (operator) | +| Unexpected error | 500 | Error | Yes | +| Status update failure (after success) | - | Warning | No | +| Resource deleted during processing | - | Info | No (return nil) | +``` + +**Step 5.3:** Create K8s client usage pattern + +**File:** `.claude/patterns/k8s-client-usage.md` + +```markdown +# Kubernetes Client Usage Patterns + +When to use user-scoped clients vs. backend service account clients. + +## The Two Client Types + +### 1. User-Scoped Clients (reqK8s, reqDyn) + +**Created from user's bearer token** extracted from HTTP request. + +```go +reqK8s, reqDyn := GetK8sClientsForRequest(c) +if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return +} +``` + +**Use for:** +- ✅ Listing resources in user's namespaces +- ✅ Getting specific resources +- ✅ RBAC permission checks +- ✅ Any operation "on behalf of user" + +**Permissions:** Limited to what the user is authorized for via K8s RBAC. + +### 2. Backend Service Account Clients (K8sClient, DynamicClient) + +**Created from backend service account credentials** (usually cluster-scoped). + +```go +// Package-level variables in handlers/ +var K8sClient *kubernetes.Clientset +var DynamicClient dynamic.Interface +``` + +**Use for:** +- ✅ Writing CRs **after** user authorization validated +- ✅ Minting service account tokens for runner pods +- ✅ Cross-namespace operations backend is authorized for +- ✅ Cleanup operations (deleting resources backend owns) + +**Permissions:** Elevated (often cluster-admin or namespace-admin). + +## Decision Tree + +``` +┌─────────────────────────────────────────┐ +│ Is this a user-initiated operation? │ +└───────────────┬─────────────────────────┘ + │ + ┌───────┴───────┐ + │ │ + YES NO + │ │ + ▼ ▼ +┌──────────────┐ ┌───────────────┐ +│ Use User │ │ Use Service │ +│ Token Client │ │ Account Client│ +│ │ │ │ +│ reqK8s │ │ K8sClient │ +│ reqDyn │ │ DynamicClient │ +└──────────────┘ └───────────────┘ +``` + +## Common Patterns + +### Pattern 1: List Resources (User Operation) + +```go +// handlers/sessions.go:180 +func ListSessions(c *gin.Context) { + projectName := c.Param("projectName") + + // ALWAYS use user token for list operations + reqK8s, reqDyn := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + return + } + + gvr := types.GetAgenticSessionResource() + list, err := reqDyn.Resource(gvr).Namespace(projectName).List(ctx, v1.ListOptions{}) + if err != nil { + log.Printf("Failed to list sessions: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list sessions"}) + return + } + + c.JSON(http.StatusOK, gin.H{"items": list.Items}) +} +``` + +**Why user token:** User should only see sessions they have permission to view. + +### Pattern 2: Create Resource (Validate Then Escalate) + +```go +// handlers/sessions.go:227 +func CreateSession(c *gin.Context) { + projectName := c.Param("projectName") + + // Step 1: Get user-scoped clients for validation + reqK8s, reqDyn := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + + // Step 2: Validate request body + var req CreateSessionRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + + // Step 3: Check user has permission to create in this namespace + ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Group: "vteam.ambient-code", + Resource: "agenticsessions", + Verb: "create", + Namespace: projectName, + }, + }, + } + res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{}) + if err != nil || !res.Status.Allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized to create sessions"}) + return + } + + // Step 4: NOW use service account to write CR + // (backend SA has permission to write CRs in project namespaces) + obj := buildSessionObject(req, projectName) + created, err := DynamicClient.Resource(gvr).Namespace(projectName).Create(ctx, obj, v1.CreateOptions{}) + if err != nil { + log.Printf("Failed to create session: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"}) + return + } + + c.JSON(http.StatusCreated, gin.H{"message": "Session created", "name": created.GetName()}) +} +``` + +**Why this pattern:** +1. Validate user identity and permissions (user token) +2. Validate request is well-formed +3. Check RBAC authorization +4. **Then** use service account to perform the write + +**This prevents:** User bypassing RBAC by using backend's elevated permissions. + +### Pattern 3: Minting Tokens for Runner Pods + +```go +// handlers/sessions.go:449 (in createRunnerJob function) +func createRunnerJob(sessionName, namespace string, spec map[string]interface{}) error { + // Create service account for this session + sa := &corev1.ServiceAccount{ + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%s-sa", sessionName), + Namespace: namespace, + }, + } + + // MUST use backend service account to create SA + _, err := K8sClient.CoreV1().ServiceAccounts(namespace).Create(ctx, sa, v1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create service account: %w", err) + } + + // Mint token for the service account + tokenRequest := &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + ExpirationSeconds: int64Ptr(3600), + }, + } + + // MUST use backend service account to mint tokens + tokenResponse, err := K8sClient.CoreV1().ServiceAccounts(namespace).CreateToken( + ctx, + sa.Name, + tokenRequest, + v1.CreateOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to create token: %w", err) + } + + // Store token in secret + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%s-token", sessionName), + Namespace: namespace, + }, + StringData: map[string]string{ + "token": tokenResponse.Status.Token, // NEVER log this + }, + } + + _, err = K8sClient.CoreV1().Secrets(namespace).Create(ctx, secret, v1.CreateOptions{}) + return err +} +``` + +**Why service account:** Only backend SA has permission to mint tokens. Users should not be able to mint arbitrary tokens. + +### Pattern 4: Cross-Namespace Operations + +```go +// handlers/projects.go (hypothetical) +func ListAllProjects(c *gin.Context) { + // User wants to list all projects they can access across all namespaces + + reqK8s, _ := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + + // List namespaces user can access (use user token) + nsList, err := reqK8s.CoreV1().Namespaces().List(ctx, v1.ListOptions{ + LabelSelector: "vteam.ambient-code/project=true", + }) + if err != nil { + log.Printf("Failed to list namespaces: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list projects"}) + return + } + + // Return list of accessible projects + projects := make([]string, 0, len(nsList.Items)) + for _, ns := range nsList.Items { + projects = append(projects, ns.Name) + } + + c.JSON(http.StatusOK, gin.H{"projects": projects}) +} +``` + +**Why user token:** User should only see namespaces they have access to. Using service account would show ALL namespaces. + +## Anti-Patterns (DO NOT USE) + +### ❌ Using Service Account for List Operations + +```go +// NEVER DO THIS +func ListSessions(c *gin.Context) { + projectName := c.Param("projectName") + + // ❌ BAD: Using service account bypasses RBAC + list, err := DynamicClient.Resource(gvr).Namespace(projectName).List(ctx, v1.ListOptions{}) + + c.JSON(http.StatusOK, gin.H{"items": list.Items}) +} +``` + +**Why wrong:** User could access resources they don't have permission to see. + +### ❌ Falling Back to Service Account on Auth Failure + +```go +// NEVER DO THIS +func GetSession(c *gin.Context) { + reqK8s, reqDyn := GetK8sClientsForRequest(c) + + // ❌ BAD: Falling back to service account if user token invalid + if reqK8s == nil { + log.Println("User token invalid, using service account") + reqDyn = DynamicClient // SECURITY VIOLATION + } + + obj, _ := reqDyn.Resource(gvr).Namespace(project).Get(ctx, name, v1.GetOptions{}) + c.JSON(http.StatusOK, obj) +} +``` + +**Why wrong:** Bypasses authentication entirely. User with invalid token shouldn't get access via backend SA. + +### ❌ Not Checking RBAC Before Service Account Operations + +```go +// NEVER DO THIS +func CreateSession(c *gin.Context) { + var req CreateSessionRequest + c.ShouldBindJSON(&req) + + // ❌ BAD: Using service account without checking user permissions + obj := buildSessionObject(req, projectName) + created, _ := DynamicClient.Resource(gvr).Namespace(projectName).Create(ctx, obj, v1.CreateOptions{}) + + c.JSON(http.StatusCreated, created) +} +``` + +**Why wrong:** User can create resources they don't have permission to create. + +## Quick Reference + +| Operation | Use User Token | Use Service Account | +|-----------|----------------|---------------------| +| List resources in namespace | ✅ | ❌ | +| Get specific resource | ✅ | ❌ | +| RBAC permission check | ✅ | ❌ | +| Create CR (after RBAC validation) | ❌ | ✅ | +| Update CR status | ❌ | ✅ | +| Delete resource user created | ✅ | ⚠️ (can use either) | +| Mint service account token | ❌ | ✅ | +| Create Job for session | ❌ | ✅ | +| Cleanup orphaned resources | ❌ | ✅ | + +**Legend:** +- ✅ Correct choice +- ❌ Wrong choice (security violation) +- ⚠️ Context-dependent + +## Validation Checklist + +Before merging code that uses K8s clients: + +- [ ] User operations use `GetK8sClientsForRequest(c)` +- [ ] Return 401 if user client creation fails +- [ ] RBAC check performed before using service account to write +- [ ] Service account used ONLY for privileged operations +- [ ] No fallback to service account on auth failures +- [ ] Tokens never logged (use `len(token)` instead) +``` + +**Step 5.4:** Create React Query usage pattern + +**File:** `.claude/patterns/react-query-usage.md` + +```markdown +# React Query Usage Patterns + +Standard patterns for data fetching, mutations, and cache management in the frontend. + +## Core Principles + +1. **ALL data fetching uses React Query** - No manual `fetch()` in components +2. **Queries for reads** - `useQuery` for GET operations +3. **Mutations for writes** - `useMutation` for POST/PUT/DELETE +4. **Cache invalidation** - Invalidate queries after mutations +5. **Optimistic updates** - Update UI before server confirms + +## File Structure + +``` +src/services/ +├── api/ # API client layer (pure functions) +│ ├── sessions.ts # sessionApi.list(), .create(), .delete() +│ ├── projects.ts +│ └── common.ts # Shared fetch logic, error handling +└── queries/ # React Query hooks + ├── sessions.ts # useSessions(), useCreateSession() + ├── projects.ts + └── common.ts # Query client config +``` + +**Separation of concerns:** +- `api/`: Pure API functions (no React, no hooks) +- `queries/`: React Query hooks that use API functions + +## Pattern 1: Query Hook (List Resources) + +```typescript +// services/queries/sessions.ts +import { useQuery } from "@tanstack/react-query" +import { sessionApi } from "@/services/api/sessions" + +export function useSessions(projectName: string) { + return useQuery({ + queryKey: ["sessions", projectName], + queryFn: () => sessionApi.list(projectName), + staleTime: 5000, // Consider data fresh for 5s + refetchInterval: 10000, // Poll every 10s for updates + }) +} +``` + +**Usage in component:** + +```typescript +// app/projects/[projectName]/sessions/page.tsx +'use client' + +import { useSessions } from "@/services/queries/sessions" + +export function SessionsList({ projectName }: { projectName: string }) { + const { data: sessions, isLoading, error } = useSessions(projectName) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ if (!sessions?.length) return
No sessions found
+ + return ( +
+ {sessions.map(session => ( + + ))} +
+ ) +} +``` + +**Key points:** +- `queryKey` includes all parameters that affect the query +- `staleTime` prevents unnecessary refetches +- `refetchInterval` for polling (optional) +- Destructure `data`, `isLoading`, `error` for UI states + +## Pattern 2: Query Hook (Single Resource) + +```typescript +// services/queries/sessions.ts +export function useSession(projectName: string, sessionName: string) { + return useQuery({ + queryKey: ["sessions", projectName, sessionName], + queryFn: () => sessionApi.get(projectName, sessionName), + enabled: !!sessionName, // Only run if sessionName provided + staleTime: 3000, + }) +} +``` + +**Usage:** + +```typescript +// app/projects/[projectName]/sessions/[sessionName]/page.tsx +'use client' + +export function SessionDetailPage({ params }: { + params: { projectName: string; sessionName: string } +}) { + const { data: session, isLoading } = useSession( + params.projectName, + params.sessionName + ) + + if (isLoading) return
Loading session...
+ if (!session) return
Session not found
+ + return +} +``` + +**Key points:** +- `enabled: !!sessionName` prevents query if parameter missing +- More specific queryKey for targeted cache invalidation + +## Pattern 3: Create Mutation with Optimistic Update + +```typescript +// services/queries/sessions.ts +import { useMutation, useQueryClient } from "@tanstack/react-query" + +export function useCreateSession(projectName: string) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (data: CreateSessionRequest) => + sessionApi.create(projectName, data), + + // Optimistic update: show immediately before server confirms + onMutate: async (newSession) => { + // Cancel any outgoing refetches (prevent overwriting optimistic update) + await queryClient.cancelQueries({ + queryKey: ["sessions", projectName] + }) + + // Snapshot current value + const previousSessions = queryClient.getQueryData([ + "sessions", + projectName + ]) + + // Optimistically update cache + queryClient.setQueryData( + ["sessions", projectName], + (old: AgenticSession[] | undefined) => [ + ...(old || []), + { + metadata: { name: newSession.name }, + spec: newSession, + status: { phase: "Pending" }, // Optimistic status + }, + ] + ) + + // Return context with snapshot + return { previousSessions } + }, + + // Rollback on error + onError: (err, variables, context) => { + queryClient.setQueryData( + ["sessions", projectName], + context?.previousSessions + ) + + // Show error toast/notification + console.error("Failed to create session:", err) + }, + + // Refetch after success (get real data from server) + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["sessions", projectName] + }) + }, + }) +} +``` + +**Usage:** + +```typescript +// components/sessions/create-session-dialog.tsx +'use client' + +import { useCreateSession } from "@/services/queries/sessions" +import { Button } from "@/components/ui/button" + +export function CreateSessionDialog({ projectName }: { projectName: string }) { + const createSession = useCreateSession(projectName) + + const handleSubmit = (data: CreateSessionRequest) => { + createSession.mutate(data) + } + + return ( +
+ {/* form fields */} + +
+ ) +} +``` + +**Key points:** +- `onMutate`: Optimistic update (runs before server call) +- `onError`: Rollback on failure +- `onSuccess`: Invalidate queries to refetch real data +- Use `isPending` for loading states + +## Pattern 4: Delete Mutation + +```typescript +// services/queries/sessions.ts +export function useDeleteSession(projectName: string) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (sessionName: string) => + sessionApi.delete(projectName, sessionName), + + // Optimistic delete + onMutate: async (sessionName) => { + await queryClient.cancelQueries({ + queryKey: ["sessions", projectName] + }) + + const previousSessions = queryClient.getQueryData([ + "sessions", + projectName + ]) + + // Remove from cache + queryClient.setQueryData( + ["sessions", projectName], + (old: AgenticSession[] | undefined) => + old?.filter(s => s.metadata.name !== sessionName) || [] + ) + + return { previousSessions } + }, + + onError: (err, sessionName, context) => { + queryClient.setQueryData( + ["sessions", projectName], + context?.previousSessions + ) + }, + + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["sessions", projectName] + }) + }, + }) +} +``` + +**Usage:** + +```typescript +const deleteSession = useDeleteSession(projectName) + + +``` + +## Pattern 5: Dependent Queries + +```typescript +// services/queries/sessions.ts +export function useSessionResults( + projectName: string, + sessionName: string +) { + // First, get the session + const sessionQuery = useSession(projectName, sessionName) + + // Then, get results (only if session is completed) + const resultsQuery = useQuery({ + queryKey: ["sessions", projectName, sessionName, "results"], + queryFn: () => sessionApi.getResults(projectName, sessionName), + enabled: sessionQuery.data?.status.phase === "Completed", + }) + + return { + session: sessionQuery.data, + results: resultsQuery.data, + isLoading: sessionQuery.isLoading || resultsQuery.isLoading, + } +} +``` + +**Key points:** +- `enabled` depends on first query's data +- Second query doesn't run until first succeeds + +## Pattern 6: Polling Until Condition Met + +```typescript +// services/queries/sessions.ts +export function useSessionWithPolling( + projectName: string, + sessionName: string +) { + return useQuery({ + queryKey: ["sessions", projectName, sessionName], + queryFn: () => sessionApi.get(projectName, sessionName), + refetchInterval: (query) => { + const session = query.state.data + + // Stop polling if completed or error + if (session?.status.phase === "Completed" || + session?.status.phase === "Error") { + return false // Stop polling + } + + return 3000 // Poll every 3s while running + }, + }) +} +``` + +**Key points:** +- Dynamic `refetchInterval` based on query data +- Return `false` to stop polling +- Return number (ms) to continue polling + +## API Client Layer Pattern + +```typescript +// services/api/sessions.ts +import { API_BASE_URL } from "@/config" +import type { AgenticSession, CreateSessionRequest } from "@/types/session" + +async function fetchWithAuth(url: string, options: RequestInit = {}) { + const token = getAuthToken() // From auth context or storage + + const response = await fetch(url, { + ...options, + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + ...options.headers, + }, + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || "Request failed") + } + + return response.json() +} + +export const sessionApi = { + list: async (projectName: string): Promise => { + const data = await fetchWithAuth( + `${API_BASE_URL}/projects/${projectName}/agentic-sessions` + ) + return data.items || [] + }, + + get: async ( + projectName: string, + sessionName: string + ): Promise => { + return fetchWithAuth( + `${API_BASE_URL}/projects/${projectName}/agentic-sessions/${sessionName}` + ) + }, + + create: async ( + projectName: string, + data: CreateSessionRequest + ): Promise => { + return fetchWithAuth( + `${API_BASE_URL}/projects/${projectName}/agentic-sessions`, + { + method: "POST", + body: JSON.stringify(data), + } + ) + }, + + delete: async (projectName: string, sessionName: string): Promise => { + return fetchWithAuth( + `${API_BASE_URL}/projects/${projectName}/agentic-sessions/${sessionName}`, + { + method: "DELETE", + } + ) + }, +} +``` + +**Key points:** +- Shared `fetchWithAuth` for token injection +- Pure functions (no React, no hooks) +- Type-safe inputs and outputs +- Centralized error handling + +## Cache Invalidation Strategies + +### Strategy 1: Invalidate Parent Query After Mutation + +```typescript +onSuccess: () => { + // Invalidate list after creating/deleting item + queryClient.invalidateQueries({ + queryKey: ["sessions", projectName] + }) +} +``` + +### Strategy 2: Invalidate Multiple Related Queries + +```typescript +onSuccess: () => { + // Invalidate both list and individual session + queryClient.invalidateQueries({ + queryKey: ["sessions", projectName] + }) + queryClient.invalidateQueries({ + queryKey: ["sessions", projectName, sessionName] + }) +} +``` + +### Strategy 3: Exact vs. Fuzzy Matching + +```typescript +// Exact match: Only ["sessions", "project-1"] +queryClient.invalidateQueries({ + queryKey: ["sessions", "project-1"], + exact: true, +}) + +// Fuzzy match: All queries starting with ["sessions", "project-1"] +// Includes ["sessions", "project-1", "session-1"], etc. +queryClient.invalidateQueries({ + queryKey: ["sessions", "project-1"] +}) +``` + +## Anti-Patterns (DO NOT USE) + +### ❌ Manual fetch() in Components + +```typescript +// NEVER DO THIS +const [sessions, setSessions] = useState([]) + +useEffect(() => { + fetch('/api/sessions') + .then(r => r.json()) + .then(setSessions) +}, []) +``` + +**Why wrong:** No caching, no automatic refetching, manual state management. +**Use instead:** React Query hooks. + +### ❌ Not Using Query Keys Properly + +```typescript +// BAD: Same query key for different data +useQuery({ + queryKey: ["sessions"], // Missing projectName! + queryFn: () => sessionApi.list(projectName), +}) +``` + +**Why wrong:** Cache collisions, wrong data shown. +**Use instead:** Include all parameters in query key. + +### ❌ Mutating State Directly in onSuccess + +```typescript +// BAD: Manually updating state instead of cache +onSuccess: (newSession) => { + setSessions([...sessions, newSession]) // Wrong! +} +``` + +**Why wrong:** Bypasses React Query cache, causes sync issues. +**Use instead:** Invalidate queries or update cache via `setQueryData`. + +## Quick Reference + +| Pattern | Hook | When to Use | +|---------|------|-------------| +| List resources | `useQuery` | GET /resources | +| Get single resource | `useQuery` | GET /resources/:id | +| Create resource | `useMutation` | POST /resources | +| Update resource | `useMutation` | PUT /resources/:id | +| Delete resource | `useMutation` | DELETE /resources/:id | +| Polling | `useQuery` + `refetchInterval` | Real-time updates | +| Optimistic update | `onMutate` | Instant UI feedback | +| Dependent query | `enabled` | Query depends on another | + +## Validation Checklist + +Before merging frontend code: + +- [ ] All data fetching uses React Query (no manual fetch) +- [ ] Query keys include all relevant parameters +- [ ] Mutations invalidate related queries +- [ ] Loading and error states handled +- [ ] Optimistic updates for create/delete (where appropriate) +- [ ] API client layer is pure functions (no hooks) +``` + +### Success Criteria + +- [ ] `.claude/patterns/` directory created +- [ ] Three pattern files created (error-handling, k8s-client-usage, react-query-usage) +- [ ] Each pattern includes concrete examples from the codebase +- [ ] Anti-patterns documented with explanations +- [ ] Quick reference tables for easy lookup + +--- + +## Validation & Next Steps + +### Overall Success Criteria + +Once all components are implemented: + +- [ ] `.claude/context/` with 3 context files (backend, frontend, security) +- [ ] `docs/adr/` with template, README, and 5 ADRs +- [ ] `.claude/repomix-guide.md` with usage guide +- [ ] `docs/decisions.md` with decision log and template +- [ ] `.claude/patterns/` with 3 pattern files + +### How to Use This Plan + +**Option 1: Execute Yourself** + +1. Create directories: `mkdir -p .claude/context docs/adr .claude/patterns` +2. Copy file content from this plan into each file +3. Review and customize for your specific needs +4. Commit: `git add .claude/ docs/ && git commit -m "feat: implement memory system for better Claude context"` + +**Option 2: Have Claude Execute** + +``` +Claude, execute the memory system implementation plan in docs/implementation-plans/memory-system-implementation.md +``` + +**Option 3: Incremental Implementation** + +Implement one component at a time: +1. Start with context files (immediate value) +2. Add ADRs (captures knowledge) +3. Add repomix guide (leverages existing assets) +4. Add decision log (lightweight tracking) +5. Add pattern catalog (codifies best practices) + +### Maintenance Schedule + +**Weekly:** +- [ ] Add new decisions to `docs/decisions.md` as they're made + +**Monthly:** +- [ ] Review and update context files with new patterns +- [ ] Add new ADRs for significant architectural changes +- [ ] Regenerate repomix views if codebase has changed significantly + +**Quarterly:** +- [ ] Review ADRs for accuracy (mark deprecated if needed) +- [ ] Update pattern catalog with new patterns discovered +- [ ] Audit context files for outdated information + +### Measuring Success + +You'll know this system is working when: + +1. **Claude gives more accurate responses** - Especially for security and architecture questions +2. **Onboarding is faster** - New team members (or Claude sessions) understand context quickly +3. **Decisions are traceable** - "Why did we do it this way?" has documented answers +4. **Patterns are reused** - Less reinventing the wheel, more consistent code + +--- + +## Appendix: Example Claude Prompts + +Once this system is in place, you can use prompts like: + +**Backend Work:** +``` +Claude, load the backend-development context file and the backend-focused repomix view (04). +Help me add a new endpoint for listing RFE workflows in a project. +``` + +**Security Review:** +``` +Claude, reference the security-standards context file and review this PR for token handling issues. +``` + +**Architecture Question:** +``` +Claude, check ADR-0002 (User Token Authentication) and explain why we use user tokens instead of service accounts for API operations. +``` + +**Pattern Application:** +``` +Claude, use the error-handling pattern file and help me add proper error handling to this handler function. +``` + +**Cross-Component Analysis:** +``` +Claude, load the production-optimized repomix view and trace how an AgenticSession creation flows from frontend to backend to operator. +``` + +--- + +**End of Implementation Plan** + +This plan is now ready to be executed by you or by Claude Code. All file content is copy-paste ready, and success criteria are clearly defined for each component. diff --git a/repomix-analysis/01-full-context.xml b/repomix-analysis/01-full-context.xml new file mode 100644 index 00000000..f907e79e --- /dev/null +++ b/repomix-analysis/01-full-context.xml @@ -0,0 +1,64201 @@ +This file is a merged representation of the entire codebase, combined into a single document by Repomix. + + +This section contains a summary of this file. + + +This file contains a packed representation of the entire repository's contents. +It is designed to be easily consumable by AI systems for analysis, code review, +or other automated processes. + + + +The content is organized as follows: +1. This summary section +2. Repository information +3. Directory structure +4. Repository files (if enabled) +5. Multiple file entries, each consisting of: + - File path as an attribute + - Full contents of the file + + + +- This file should be treated as read-only. Any changes should be made to the + original repository files, not this packed version. +- When processing this file, use the file path to distinguish + between different files in the repository. +- Be aware that this file may contain sensitive information. Handle it with + the same level of security as you would the original repository. + + + +- Some files may have been excluded based on .gitignore rules and Repomix's configuration +- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files +- Files matching patterns in .gitignore are excluded +- Files matching default ignore patterns are excluded +- Files are sorted by Git change count (files with more changes are at the bottom) + + + + + +.claude/ + commands/ + speckit.analyze.md + speckit.checklist.md + speckit.clarify.md + speckit.constitution.md + speckit.implement.md + speckit.plan.md + speckit.specify.md + speckit.tasks.md +.cursor/ + commands/ + analyze.md + clarify.md + constitution.md + implement.md + plan.md + specify.md + tasks.md +.github/ + ISSUE_TEMPLATE/ + bug_report.md + documentation.md + epic.md + feature_request.md + outcome.md + story.md + workflows/ + ai-assessment-comment-labeler.yml + amber-dependency-sync.yml + auto-assign-todo.yml + claude-code-review.yml + claude.yml + components-build-deploy.yml + dependabot-auto-merge.yml + docs.yml + e2e.yml + frontend-lint.yml + go-lint.yml + outcome-metrics.yml + prod-release-deploy.yaml + project-automation.yml + test-local-dev.yml + dependabot.yml +.specify/ + memory/ + orginal/ + architecture.md + capabilities.md + constitution_update_checklist.md + constitution.md + scripts/ + bash/ + check-prerequisites.sh + check-task-prerequisites.sh + common.sh + create-new-feature.sh + get-feature-paths.sh + setup-plan.sh + update-agent-context.sh + templates/ + agent-file-template.md + checklist-template.md + plan-template.md + spec-template.md + tasks-template.md +agent-bullpen/ + archie-architect.md + aria-ux_architect.md + casey-content_strategist.md + dan-senior_director.md + diego-program_manager.md + emma-engineering_manager.md + felix-ux_feature_lead.md + jack-delivery_owner.md + lee-team_lead.md + neil-test_engineer.md + olivia-product_owner.md + phoenix-pxe_specialist.md + sam-scrum_master.md + taylor-team_member.md + tessa-writing_manager.md + uma-ux_team_lead.md +agents/ + amber.md + parker-product_manager.md + ryan-ux_researcher.md + stella-staff_engineer.md + steve-ux_designer.md + terry-technical_writer.md +components/ + backend/ + git/ + operations.go + github/ + app.go + token.go + handlers/ + content.go + github_auth.go + health.go + helpers.go + middleware.go + permissions.go + projects.go + repo.go + secrets.go + sessions.go + jira/ + integration.go + k8s/ + resources.go + server/ + k8s.go + server.go + types/ + common.go + project.go + session.go + websocket/ + handlers.go + hub.go + .env.example + .gitignore + .golangci.yml + Dockerfile + Dockerfile.dev + go.mod + main.go + Makefile + README.md + routes.go + frontend/ + public/ + file.svg + globe.svg + next.svg + vercel.svg + window.svg + src/ + app/ + api/ + auth/ + github/ + disconnect/ + route.ts + install/ + route.ts + status/ + route.ts + user/ + callback/ + route.ts + cluster-info/ + route.ts + me/ + route.ts + projects/ + [name]/ + access/ + route.ts + agentic-sessions/ + [sessionName]/ + clone/ + route.ts + content-pod/ + route.ts + content-pod-status/ + route.ts + git/ + configure-remote/ + route.ts + create-branch/ + route.ts + list-branches/ + route.ts + merge-status/ + route.ts + pull/ + route.ts + push/ + route.ts + status/ + route.ts + synchronize/ + route.ts + github/ + abandon/ + route.ts + diff/ + route.ts + push/ + route.ts + k8s-resources/ + route.ts + messages/ + route.ts + repos/ + [repoName]/ + route.ts + route.ts + spawn-content-pod/ + route.ts + start/ + route.ts + stop/ + route.ts + workflow/ + metadata/ + route.ts + route.ts + workspace/ + [...path]/ + route.ts + route.ts + route.ts + route.ts + integration-secrets/ + route.ts + keys/ + [keyId]/ + route.ts + route.ts + permissions/ + [subjectType]/ + [subjectName]/ + route.ts + route.ts + repo/ + blob/ + route.ts + tree/ + route.ts + runner-secrets/ + config/ + route.ts + route.ts + secrets/ + route.ts + settings/ + route.ts + users/ + forks/ + route.ts + route.ts + route.ts + version/ + route.ts + workflows/ + ootb/ + route.ts + integrations/ + github/ + setup/ + page.tsx + IntegrationsClient.tsx + page.tsx + projects/ + [name]/ + keys/ + error.tsx + loading.tsx + page.tsx + permissions/ + page.tsx + sessions/ + [sessionName]/ + components/ + accordions/ + artifacts-accordion.tsx + repositories-accordion.tsx + workflows-accordion.tsx + modals/ + add-context-modal.tsx + commit-changes-dialog.tsx + custom-workflow-dialog.tsx + manage-remote-dialog.tsx + k8s-resource-tree.tsx + hooks/ + use-file-operations.ts + use-git-operations.ts + use-workflow-management.ts + lib/ + message-adapter.ts + types.ts + error.tsx + loading.tsx + not-found.tsx + page.tsx + session-header.tsx + new/ + model-configuration.tsx + page.tsx + repository-dialog.tsx + repository-list.tsx + page.tsx + settings/ + page.tsx + error.tsx + loading.tsx + not-found.tsx + page.tsx + error.tsx + loading.tsx + page.tsx + error.tsx + favicon.ico + globals.css + layout.tsx + loading.tsx + page.tsx + components/ + layouts/ + page-container.tsx + sidebar-layout.tsx + providers/ + query-provider.tsx + session/ + MessagesTab.tsx + OverviewTab.tsx + ResultsTab.tsx + WorkspaceTab.tsx + ui/ + accordion.tsx + alert.tsx + avatar.tsx + badge.tsx + button.tsx + card.tsx + checkbox.tsx + dialog.tsx + dropdown-menu.tsx + form.tsx + input.tsx + label.tsx + message.tsx + popover.tsx + progress.tsx + resizable.tsx + select.tsx + separator.tsx + skeleton.tsx + stream-message.tsx + system-message.tsx + table.tsx + tabs.tsx + textarea.tsx + thinking-message.tsx + toast.tsx + toaster.tsx + tool-message.tsx + tooltip.tsx + workspace-sections/ + index.ts + sessions-section.tsx + settings-section.tsx + sharing-section.tsx + breadcrumbs.tsx + clone-session-dialog.tsx + confirmation-dialog.tsx + create-session-dialog.tsx + create-workspace-dialog.tsx + editable-session-name.tsx + empty-state.tsx + error-message.tsx + file-tree.tsx + form-field-wrapper.tsx + github-connection-card.tsx + loading-button.tsx + multi-agent-selection.tsx + navigation.tsx + page-header.tsx + project-selector.tsx + project-subpage-header.tsx + RepoBrowser.tsx + session-details-modal.tsx + simple-data-table.tsx + skeletons.tsx + status-badge.tsx + user-bubble.tsx + hooks/ + index.ts + use-async-action.ts + use-clipboard.ts + use-cluster-info.ts + use-debounce.ts + use-local-storage.ts + use-toast.tsx + lib/ + agents.ts + auth.ts + config.ts + env.ts + query-client.ts + utils.ts + services/ + api/ + auth.ts + client.ts + cluster.ts + github.ts + index.ts + keys.ts + projects.ts + repo.ts + secrets.ts + sessions.ts + version.ts + workflows.ts + workspace.ts + queries/ + index.ts + use-auth.ts + use-cluster.ts + use-github.ts + use-keys.ts + use-projects.ts + use-repo.ts + use-secrets.ts + use-sessions.ts + use-version.ts + use-workflows.ts + use-workspace.ts + types/ + api/ + auth.ts + common.ts + github.ts + index.ts + projects.ts + sessions.ts + components/ + forms.ts + index.ts + agentic-session.ts + bot.ts + index.ts + project-settings.ts + project.ts + utils/ + session-helpers.ts + .dockerignore + .env.example + .gitignore + COMPONENT_PATTERNS.md + components.json + DESIGN_GUIDELINES.md + Dockerfile + Dockerfile.dev + eslint.config.mjs + next.config.js + package.json + postcss.config.mjs + README.md + tailwind.config.js + tsconfig.json + manifests/ + base/ + crds/ + agenticsessions-crd.yaml + kustomization.yaml + projectsettings-crd.yaml + rbac/ + aggregate-agenticsessions-admin.yaml + aggregate-projectsettings-admin.yaml + ambient-project-admin-clusterrole.yaml + ambient-project-edit-clusterrole.yaml + ambient-project-view-clusterrole.yaml + ambient-users-list-projects-clusterrolebinding.yaml + backend-clusterrole.yaml + backend-clusterrolebinding.yaml + backend-sa.yaml + cluster-roles.yaml + frontend-rbac.yaml + kustomization.yaml + operator-clusterrole.yaml + operator-clusterrolebinding.yaml + operator-sa.yaml + README.md + service-account.yaml + backend-deployment.yaml + frontend-deployment.yaml + kustomization.yaml + namespace.yaml + operator-deployment.yaml + without-rbac-kustomization.yaml + workspace-pvc.yaml + overlays/ + e2e/ + backend-ingress.yaml + frontend-ingress.yaml + frontend-test-patch.yaml + image-pull-policy-patch.yaml + kustomization.yaml + namespace-patch.yaml + operator-config.yaml + pvc-patch.yaml + secrets.yaml + test-user.yaml + local-dev/ + backend-clusterrole-patch.yaml + backend-deployment-patch.yaml + backend-patch.yaml + backend-rbac.yaml + backend-route.yaml + build-configs.yaml + dev-users.yaml + frontend-auth.yaml + frontend-deployment-patch.yaml + frontend-patch.yaml + frontend-route.yaml + kustomization.yaml + operator-clusterrole-patch.yaml + operator-config-crc.yaml + operator-patch.yaml + operator-rbac.yaml + pvc-patch.yaml + production/ + backend-route.yaml + frontend-oauth-deployment-patch.yaml + frontend-oauth-patch.yaml + frontend-oauth-service-patch.yaml + github-app-secret.yaml + kustomization.yaml + namespace-patch.yaml + operator-config-openshift.yaml + route.yaml + .gitignore + deploy.sh + env.example + GIT_AUTH_SETUP.md + README.md + operator/ + internal/ + config/ + config.go + handlers/ + namespaces.go + projectsettings.go + sessions.go + preflight/ + vertex.go + services/ + infrastructure.go + types/ + resources.go + .gitignore + .golangci.yml + Dockerfile + go.mod + main.go + README.md + runners/ + claude-code-runner/ + Dockerfile + pyproject.toml + wrapper.py + runner-shell/ + runner_shell/ + core/ + __init__.py + context.py + protocol.py + shell.py + transport_ws.py + __init__.py + __init__.py + pyproject.toml + README.md + scripts/ + local-dev/ + .gitignore + crc-dev-sync.sh + crc-start.sh + crc-stop.sh + crc-test.sh + INSTALLATION.md + MIGRATION_GUIDE.md + OPERATOR_INTEGRATION_PLAN.md + README.md + STATUS.md + README.md +diagrams/ + rfe-council-workflow-2025-08-mermaid.mmd + rfe-refinement-flow.mmd + rfe-refinement-flow.png + ux-feature-workflow.md +docs/ + implementation-plans/ + amber-implementation.md + labs/ + basic/ + lab-1-first-rfe.md + index.md + reference/ + constitution.md + glossary.md + index.md + testing/ + e2e-guide.md + user-guide/ + getting-started.md + index.md + working-with-amber.md + CLAUDE_CODE_RUNNER.md + GITHUB_APP_SETUP.md + index.md + OPENSHIFT_DEPLOY.md + OPENSHIFT_OAUTH.md + README.md +e2e/ + scripts/ + cleanup.sh + deploy.sh + run-tests.sh + setup-kind.sh + wait-for-ready.sh + cypress.config.ts + package.json + README.md + tsconfig.json +hack/ + automated-deployer.yaml +Prompts/ + bug-assessment.prompt.yml + feature-assessment.prompt.yml + general-assessment.prompt.yml +scripts/ + sync-amber-dependencies.py +.gitignore +.repomixignore +BRANCH_PROTECTION.md +CLAUDE.md +CONTRIBUTING.md +LICENSE +Makefile +mkdocs.yml +README.md +requirements-docs.txt +rhoai-ux-agents-vTeam.md + + + +This section contains the contents of the repository's files. + + +--- +description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +Goal: Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/tasks` has successfully produced a complete `tasks.md`. + +STRICTLY READ-ONLY: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually). + +Constitution Authority: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/analyze`. + +Execution steps: + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: + - SPEC = FEATURE_DIR/spec.md + - PLAN = FEATURE_DIR/plan.md + - TASKS = FEATURE_DIR/tasks.md + Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). + +2. Load artifacts: + - Parse spec.md sections: Overview/Context, Functional Requirements, Non-Functional Requirements, User Stories, Edge Cases (if present). + - Parse plan.md: Architecture/stack choices, Data Model references, Phases, Technical constraints. + - Parse tasks.md: Task IDs, descriptions, phase grouping, parallel markers [P], referenced file paths. + - Load constitution `.specify/memory/constitution.md` for principle validation. + +3. Build internal semantic models: + - Requirements inventory: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" -> `user-can-upload-file`). + - User story/action inventory. + - Task coverage mapping: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases). + - Constitution rule set: Extract principle names and any MUST/SHOULD normative statements. + +4. Detection passes: + A. Duplication detection: + - Identify near-duplicate requirements. Mark lower-quality phrasing for consolidation. + B. Ambiguity detection: + - Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria. + - Flag unresolved placeholders (TODO, TKTK, ???, , etc.). + C. Underspecification: + - Requirements with verbs but missing object or measurable outcome. + - User stories missing acceptance criteria alignment. + - Tasks referencing files or components not defined in spec/plan. + D. Constitution alignment: + - Any requirement or plan element conflicting with a MUST principle. + - Missing mandated sections or quality gates from constitution. + E. Coverage gaps: + - Requirements with zero associated tasks. + - Tasks with no mapped requirement/story. + - Non-functional requirements not reflected in tasks (e.g., performance, security). + F. Inconsistency: + - Terminology drift (same concept named differently across files). + - Data entities referenced in plan but absent in spec (or vice versa). + - Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note). + - Conflicting requirements (e.g., one requires to use Next.js while other says to use Vue as the framework). + +5. Severity assignment heuristic: + - CRITICAL: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality. + - HIGH: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion. + - MEDIUM: Terminology drift, missing non-functional task coverage, underspecified edge case. + - LOW: Style/wording improvements, minor redundancy not affecting execution order. + +6. Produce a Markdown report (no file writes) with sections: + + ### Specification Analysis Report + | ID | Category | Severity | Location(s) | Summary | Recommendation | + |----|----------|----------|-------------|---------|----------------| + | A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version | + (Add one row per finding; generate stable IDs prefixed by category initial.) + + Additional subsections: + - Coverage Summary Table: + | Requirement Key | Has Task? | Task IDs | Notes | + - Constitution Alignment Issues (if any) + - Unmapped Tasks (if any) + - Metrics: + * Total Requirements + * Total Tasks + * Coverage % (requirements with >=1 task) + * Ambiguity Count + * Duplication Count + * Critical Issues Count + +7. At end of report, output a concise Next Actions block: + - If CRITICAL issues exist: Recommend resolving before `/implement`. + - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions. + - Provide explicit command suggestions: e.g., "Run /specify with refinement", "Run /plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'". + +8. Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.) + +Behavior rules: +- NEVER modify files. +- NEVER hallucinate missing sections—if absent, report them. +- KEEP findings deterministic: if rerun without changes, produce consistent IDs and counts. +- LIMIT total findings in the main table to 50; aggregate remainder in a summarized overflow note. +- If zero issues found, emit a success report with coverage statistics and proceed recommendation. + +Context: $ARGUMENTS + + + +--- +description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. + +Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. + +Execution steps: + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: + - `FEATURE_DIR` + - `FEATURE_SPEC` + - (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.) + - If JSON parsing fails, abort and instruct user to re-run `/specify` or verify feature branch environment. + +2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked). + + Functional Scope & Behavior: + - Core user goals & success criteria + - Explicit out-of-scope declarations + - User roles / personas differentiation + + Domain & Data Model: + - Entities, attributes, relationships + - Identity & uniqueness rules + - Lifecycle/state transitions + - Data volume / scale assumptions + + Interaction & UX Flow: + - Critical user journeys / sequences + - Error/empty/loading states + - Accessibility or localization notes + + Non-Functional Quality Attributes: + - Performance (latency, throughput targets) + - Scalability (horizontal/vertical, limits) + - Reliability & availability (uptime, recovery expectations) + - Observability (logging, metrics, tracing signals) + - Security & privacy (authN/Z, data protection, threat assumptions) + - Compliance / regulatory constraints (if any) + + Integration & External Dependencies: + - External services/APIs and failure modes + - Data import/export formats + - Protocol/versioning assumptions + + Edge Cases & Failure Handling: + - Negative scenarios + - Rate limiting / throttling + - Conflict resolution (e.g., concurrent edits) + + Constraints & Tradeoffs: + - Technical constraints (language, storage, hosting) + - Explicit tradeoffs or rejected alternatives + + Terminology & Consistency: + - Canonical glossary terms + - Avoided synonyms / deprecated terms + + Completion Signals: + - Acceptance criteria testability + - Measurable Definition of Done style indicators + + Misc / Placeholders: + - TODO markers / unresolved decisions + - Ambiguous adjectives ("robust", "intuitive") lacking quantification + + For each category with Partial or Missing status, add a candidate question opportunity unless: + - Clarification would not materially change implementation or validation strategy + - Information is better deferred to planning phase (note internally) + +3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints: + - Maximum of 5 total questions across the whole session. + - Each question must be answerable with EITHER: + * A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR + * A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words"). + - Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation. + - Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved. + - Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness). + - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests. + - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic. + +4. Sequential questioning loop (interactive): + - Present EXACTLY ONE question at a time. + - For multiple‑choice questions render options as a Markdown table: + + | Option | Description | + |--------|-------------| + | A | + + +--- +description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts. + +Follow this execution flow: + +1. Load the existing constitution template at `.specify/memory/constitution.md`. + - Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`. + **IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly. + +2. Collect/derive values for placeholders: + - If user input (conversation) supplies a value, use it. + - Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded). + - For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous. + - `CONSTITUTION_VERSION` must increment according to semantic versioning rules: + * MAJOR: Backward incompatible governance/principle removals or redefinitions. + * MINOR: New principle/section added or materially expanded guidance. + * PATCH: Clarifications, wording, typo fixes, non-semantic refinements. + - If version bump type ambiguous, propose reasoning before finalizing. + +3. Draft the updated constitution content: + - Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left). + - Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance. + - Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious. + - Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations. + +4. Consistency propagation checklist (convert prior checklist into active validations): + - Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles. + - Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints. + - Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline). + - Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required. + - Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed. + +5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update): + - Version change: old → new + - List of modified principles (old title → new title if renamed) + - Added sections + - Removed sections + - Templates requiring updates (✅ updated / ⚠ pending) with file paths + - Follow-up TODOs if any placeholders intentionally deferred. + +6. Validation before final output: + - No remaining unexplained bracket tokens. + - Version line matches report. + - Dates ISO format YYYY-MM-DD. + - Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate). + +7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite). + +8. Output a final summary to the user with: + - New version and bump rationale. + - Any files flagged for manual follow-up. + - Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`). + +Formatting & Style Requirements: +- Use Markdown headings exactly as in the template (do not demote/promote levels). +- Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks. +- Keep a single blank line between sections. +- Avoid trailing whitespace. + +If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps. + +If critical info missing (e.g., ratification date truly unknown), insert `TODO(): explanation` and include in the Sync Impact Report under deferred items. + +Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file. + + + +--- +description: Execute the implementation plan by processing and executing all tasks defined in tasks.md +--- + +The user input can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. + +2. Load and analyze the implementation context: + - **REQUIRED**: Read tasks.md for the complete task list and execution plan + - **REQUIRED**: Read plan.md for tech stack, architecture, and file structure + - **IF EXISTS**: Read data-model.md for entities and relationships + - **IF EXISTS**: Read contracts/ for API specifications and test requirements + - **IF EXISTS**: Read research.md for technical decisions and constraints + - **IF EXISTS**: Read quickstart.md for integration scenarios + +3. Parse tasks.md structure and extract: + - **Task phases**: Setup, Tests, Core, Integration, Polish + - **Task dependencies**: Sequential vs parallel execution rules + - **Task details**: ID, description, file paths, parallel markers [P] + - **Execution flow**: Order and dependency requirements + +4. Execute implementation following the task plan: + - **Phase-by-phase execution**: Complete each phase before moving to the next + - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together + - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks + - **File-based coordination**: Tasks affecting the same files must run sequentially + - **Validation checkpoints**: Verify each phase completion before proceeding + +5. Implementation execution rules: + - **Setup first**: Initialize project structure, dependencies, configuration + - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios + - **Core development**: Implement models, services, CLI commands, endpoints + - **Integration work**: Database connections, middleware, logging, external services + - **Polish and validation**: Unit tests, performance optimization, documentation + +6. Progress tracking and error handling: + - Report progress after each completed task + - Halt execution if any non-parallel task fails + - For parallel tasks [P], continue with successful tasks, report failed ones + - Provide clear error messages with context for debugging + - Suggest next steps if implementation cannot proceed + - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. + +7. Completion validation: + - Verify all required tasks are completed + - Check that implemented features match the original specification + - Validate that tests pass and coverage meets requirements + - Confirm the implementation follows the technical plan + - Report final status with summary of completed work + +Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/tasks` first to regenerate the task list. + + + +--- +description: Execute the implementation planning workflow using the plan template to generate design artifacts. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +Given the implementation details provided as an argument, do this: + +1. Run `.specify/scripts/bash/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute. + - BEFORE proceeding, inspect FEATURE_SPEC for a `## Clarifications` section with at least one `Session` subheading. If missing or clearly ambiguous areas remain (vague adjectives, unresolved critical choices), PAUSE and instruct the user to run `/clarify` first to reduce rework. Only continue if: (a) Clarifications exist OR (b) an explicit user override is provided (e.g., "proceed without clarification"). Do not attempt to fabricate clarifications yourself. +2. Read and analyze the feature specification to understand: + - The feature requirements and user stories + - Functional and non-functional requirements + - Success criteria and acceptance criteria + - Any technical constraints or dependencies mentioned + +3. Read the constitution at `.specify/memory/constitution.md` to understand constitutional requirements. + +4. Execute the implementation plan template: + - Load `.specify/templates/plan-template.md` (already copied to IMPL_PLAN path) + - Set Input path to FEATURE_SPEC + - Run the Execution Flow (main) function steps 1-9 + - The template is self-contained and executable + - Follow error handling and gate checks as specified + - Let the template guide artifact generation in $SPECS_DIR: + * Phase 0 generates research.md + * Phase 1 generates data-model.md, contracts/, quickstart.md + * Phase 2 generates tasks.md + - Incorporate user-provided details from arguments into Technical Context: $ARGUMENTS + - Update Progress Tracking as you complete each phase + +5. Verify execution completed: + - Check Progress Tracking shows all phases complete + - Ensure all required artifacts were generated + - Confirm no ERROR states in execution + +6. Report results with branch name, file paths, and generated artifacts. + +Use absolute paths with the repository root for all file operations to avoid path issues. + + + +--- +description: Create or update the feature specification from a natural language feature description. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command. + +Given that feature description, do this: + +1. Run the script `.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. + **IMPORTANT** You must only ever run this script once. The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for. +2. Load `.specify/templates/spec-template.md` to understand required sections. +3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings. +4. Report completion with branch name, spec file path, and readiness for the next phase. + +Note: The script creates and checks out the new branch and initializes the spec file before writing. + + + +--- +description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. +--- + +The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). + +User input: + +$ARGUMENTS + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. +2. Load and analyze available design documents: + - Always read plan.md for tech stack and libraries + - IF EXISTS: Read data-model.md for entities + - IF EXISTS: Read contracts/ for API endpoints + - IF EXISTS: Read research.md for technical decisions + - IF EXISTS: Read quickstart.md for test scenarios + + Note: Not all projects have all documents. For example: + - CLI tools might not have contracts/ + - Simple libraries might not need data-model.md + - Generate tasks based on what's available + +3. Generate tasks following the template: + - Use `.specify/templates/tasks-template.md` as the base + - Replace example tasks with actual tasks based on: + * **Setup tasks**: Project init, dependencies, linting + * **Test tasks [P]**: One per contract, one per integration scenario + * **Core tasks**: One per entity, service, CLI command, endpoint + * **Integration tasks**: DB connections, middleware, logging + * **Polish tasks [P]**: Unit tests, performance, docs + +4. Task generation rules: + - Each contract file → contract test task marked [P] + - Each entity in data-model → model creation task marked [P] + - Each endpoint → implementation task (not parallel if shared files) + - Each user story → integration test marked [P] + - Different files = can be parallel [P] + - Same file = sequential (no [P]) + +5. Order tasks by dependencies: + - Setup before everything + - Tests before implementation (TDD) + - Models before services + - Services before endpoints + - Core before integration + - Everything before polish + +6. Include parallel execution examples: + - Group [P] tasks that can run together + - Show actual Task agent commands + +7. Create FEATURE_DIR/tasks.md with: + - Correct feature name from implementation plan + - Numbered tasks (T001, T002, etc.) + - Clear file paths for each task + - Dependency notes + - Parallel execution guidance + +Context for task generation: $ARGUMENTS + +The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context. + + + +--- +name: 🐛 Bug Report +about: Create a report to help us improve +title: 'Bug: [Brief description]' +labels: ["bug", "needs-triage"] +assignees: [] +--- + +## 🐛 Bug Description + +**Summary:** A clear and concise description of what the bug is. + +**Expected Behavior:** What you expected to happen. + +**Actual Behavior:** What actually happened. + +## 🔄 Steps to Reproduce + +1. Go to '...' +2. Click on '...' +3. Scroll down to '...' +4. See error + +## 🖼️ Screenshots + +If applicable, add screenshots to help explain your problem. + +## 🌍 Environment + +**Component:** [RAT System / Ambient Agentic Runner / vTeam Tools] + +**Version/Commit:** [e.g. v1.2.3 or commit hash] + +**Operating System:** [e.g. macOS 14.0, Ubuntu 22.04, Windows 11] + +**Browser:** [if applicable - Chrome 119, Firefox 120, Safari 17] + +**Python Version:** [if applicable - e.g. 3.11.5] + +**Kubernetes Version:** [if applicable - e.g. 1.28.2] + +## 📋 Additional Context + +**Error Messages:** [Paste any error messages or logs] + +``` +[Error logs here] +``` + +**Configuration:** [Any relevant configuration details] + +**Recent Changes:** [Any recent changes that might be related] + +## 🔍 Possible Solution + +[If you have suggestions on how to fix the bug] + +## ✅ Acceptance Criteria + +- [ ] Bug is reproduced and root cause identified +- [ ] Fix is implemented and tested +- [ ] Regression tests added to prevent future occurrences +- [ ] Documentation updated if needed +- [ ] Fix is verified in staging environment + +## 🏷️ Labels + + +- **Priority:** [low/medium/high/critical] +- **Complexity:** [trivial/easy/medium/hard] +- **Component:** [frontend/backend/operator/tools/docs] + + + +--- +name: 📚 Documentation +about: Improve or add documentation +title: 'Docs: [Brief description]' +labels: ["documentation", "good-first-issue"] +assignees: [] +--- + +## 📚 Documentation Request + +**Type of Documentation:** +- [ ] API Documentation +- [ ] User Guide +- [ ] Developer Guide +- [ ] Tutorial +- [ ] README Update +- [ ] Code Comments +- [ ] Architecture Documentation +- [ ] Troubleshooting Guide + +## 📋 Current State + +**What documentation exists?** [Link to current docs or state "None"] + +**What's missing or unclear?** [Specific gaps or confusing sections] + +**Who is the target audience?** [End users, developers, operators, etc.] + +## 🎯 Proposed Documentation + +**Scope:** What should be documented? + +**Format:** [Markdown, Wiki, Code comments, etc.] + +**Location:** Where should this documentation live? + +**Outline:** [Provide a rough outline of the content structure] + +## 📊 Content Requirements + +**Must Include:** +- [ ] Clear overview/introduction +- [ ] Prerequisites or requirements +- [ ] Step-by-step instructions +- [ ] Code examples +- [ ] Screenshots/diagrams (if applicable) +- [ ] Troubleshooting section +- [ ] Related links/references + +**Nice to Have:** +- [ ] Video walkthrough +- [ ] Interactive examples +- [ ] FAQ section +- [ ] Best practices + +## 🔧 Technical Details + +**Component:** [RAT System / Ambient Agentic Runner / vTeam Tools] + +**Related Code:** [Link to relevant source code or features] + +**Dependencies:** [Any tools or knowledge needed to write this documentation] + +## 👥 Audience & Use Cases + +**Primary Audience:** [Who will read this documentation?] + +**User Journey:** [When/why would someone need this documentation?] + +**Skill Level:** [Beginner/Intermediate/Advanced] + +## ✅ Definition of Done + +- [ ] Documentation written and reviewed +- [ ] Code examples tested and verified +- [ ] Screenshots/diagrams created (if needed) +- [ ] Documentation integrated into existing structure +- [ ] Cross-references and links updated +- [ ] Spelling and grammar checked +- [ ] Technical accuracy verified by subject matter expert + +## 📝 Additional Context + +**Examples:** [Link to similar documentation that works well] + +**Style Guide:** [Any specific style requirements] + +**Related Issues:** [Link to related documentation requests] + +## 🏷️ Labels + + +- **Priority:** [low/medium/high] +- **Effort:** [S/M/L] +- **Type:** [new-docs/update-docs/fix-docs] +- **Audience:** [user/developer/operator] + + + +--- +name: 🚀 Epic +about: Create a new epic under a business outcome +title: 'Epic: [Brief description]' +labels: ["epic"] +assignees: [] +--- + +## 🎯 Epic Overview + +**Parent Outcome:** [Link to outcome issue] + +**Brief Description:** What major capability will this epic deliver? + +## 📋 Scope & Requirements + +**Functional Requirements:** +- [ ] Requirement 1 +- [ ] Requirement 2 +- [ ] Requirement 3 + +**Non-Functional Requirements:** +- [ ] Performance: [Specific targets] +- [ ] Security: [Security considerations] +- [ ] Scalability: [Scale requirements] + +## 🏗️ Implementation Approach + +**Architecture:** [High-level architectural approach] + +**Technology Stack:** [Key technologies/frameworks] + +**Integration Points:** [Systems this epic integrates with] + +## 📊 Stories & Tasks + +This epic will be implemented through the following stories: + +- [ ] Story: [Link to story issue] +- [ ] Story: [Link to story issue] +- [ ] Story: [Link to story issue] + +## 🧪 Testing Strategy + +- [ ] Unit tests +- [ ] Integration tests +- [ ] End-to-end tests +- [ ] Performance tests +- [ ] Security tests + +## ✅ Definition of Done + +- [ ] All stories under this epic are completed +- [ ] Code review completed and approved +- [ ] All tests passing +- [ ] Documentation updated +- [ ] Feature deployed to production +- [ ] Stakeholder demo completed + +## 📅 Timeline + +**Target Completion:** [Date or milestone] +**Dependencies:** [List any blocking epics or external dependencies] + +## 📝 Notes + +[Technical notes, architectural decisions, or implementation details] + + + +--- +name: ✨ Feature Request +about: Suggest an idea for this project +title: 'Feature: [Brief description]' +labels: ["enhancement", "needs-triage"] +assignees: [] +--- + +## 🚀 Feature Description + +**Is your feature request related to a problem?** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +## 💡 Proposed Solution + +**Detailed Description:** How should this feature work? + +**User Experience:** How will users interact with this feature? + +**API Changes:** [If applicable] What API changes are needed? + +## 🎯 Use Cases + +**Primary Use Case:** Who will use this and why? + +**User Stories:** +- As a [user type], I want [functionality] so that [benefit] +- As a [user type], I want [functionality] so that [benefit] + +## 🔧 Technical Considerations + +**Component:** [RAT System / Ambient Agentic Runner / vTeam Tools / Infrastructure] + +**Implementation Approach:** [High-level technical approach] + +**Dependencies:** [Any new dependencies or integrations needed] + +**Breaking Changes:** [Will this introduce breaking changes?] + +## 📊 Success Metrics + +How will we measure the success of this feature? + +- [ ] Metric 1: [Quantifiable measure] +- [ ] Metric 2: [Quantifiable measure] +- [ ] User feedback: [Qualitative measure] + +## 🔄 Alternatives Considered + +**Alternative 1:** [Description and why it was rejected] + +**Alternative 2:** [Description and why it was rejected] + +**Do nothing:** [Consequences of not implementing this feature] + +## 📋 Additional Context + +**Screenshots/Mockups:** [Add any visual aids] + +**Related Issues:** [Link to related issues or discussions] + +**External References:** [Links to similar features in other projects] + +## ✅ Acceptance Criteria + +- [ ] Feature requirements clearly defined +- [ ] Technical design reviewed and approved +- [ ] Implementation completed and tested +- [ ] Documentation updated +- [ ] User acceptance testing passed +- [ ] Feature flag implemented (if applicable) + +## 🏷️ Labels + + +- **Priority:** [low/medium/high] +- **Effort:** [S/M/L/XL] +- **Component:** [frontend/backend/operator/tools/docs] +- **Type:** [new-feature/enhancement/improvement] + + + +--- +name: 💼 Outcome +about: Create a new business outcome that groups related epics +title: 'Outcome: [Brief description]' +labels: ["outcome"] +assignees: [] +--- + +## 🎯 Business Outcome + +**Brief Description:** What business value will this outcome deliver? + +## 📊 Success Metrics + +- [ ] Metric 1: [Quantifiable measure] +- [ ] Metric 2: [Quantifiable measure] +- [ ] Metric 3: [Quantifiable measure] + +## 🎨 Scope & Context + +**Problem Statement:** What problem does this solve? + +**User Impact:** Who benefits and how? + +**Strategic Alignment:** How does this align with business objectives? + +## 🗺️ Related Epics + +This outcome will be delivered through the following epics: + +- [ ] Epic: [Link to epic issue] +- [ ] Epic: [Link to epic issue] +- [ ] Epic: [Link to epic issue] + +## ✅ Definition of Done + +- [ ] All epics under this outcome are completed +- [ ] Success metrics are achieved and validated +- [ ] User acceptance testing passed +- [ ] Documentation updated +- [ ] Stakeholder sign-off obtained + +## 📅 Timeline + +**Target Completion:** [Date or milestone] +**Dependencies:** [List any blocking outcomes or external dependencies] + +## 📝 Notes + +[Additional context, assumptions, or constraints] + + + +--- +name: 📋 Story +about: Create a new development story under an epic +title: 'Story: [Brief description]' +labels: ["story"] +assignees: [] +--- + +## 🎯 Story Overview + +**Parent Epic:** [Link to epic issue] + +**User Story:** As a [user type], I want [functionality] so that [benefit]. + +## 📋 Acceptance Criteria + +- [ ] Given [context], when [action], then [expected result] +- [ ] Given [context], when [action], then [expected result] +- [ ] Given [context], when [action], then [expected result] + +## 🔧 Technical Requirements + +**Implementation Details:** +- [ ] [Specific technical requirement] +- [ ] [Specific technical requirement] +- [ ] [Specific technical requirement] + +**API Changes:** [If applicable, describe API changes] + +**Database Changes:** [If applicable, describe schema changes] + +**UI/UX Changes:** [If applicable, describe interface changes] + +## 🧪 Test Plan + +**Unit Tests:** +- [ ] Test case 1 +- [ ] Test case 2 + +**Integration Tests:** +- [ ] Integration scenario 1 +- [ ] Integration scenario 2 + +**Manual Testing:** +- [ ] Test scenario 1 +- [ ] Test scenario 2 + +## ✅ Definition of Done + +- [ ] Code implemented and tested +- [ ] Unit tests written and passing +- [ ] Integration tests written and passing +- [ ] Code review completed +- [ ] Documentation updated +- [ ] Feature tested in staging environment +- [ ] All acceptance criteria met + +## 📅 Estimation & Timeline + +**Story Points:** [Estimation in story points] +**Target Completion:** [Sprint or date] + +## 🔗 Dependencies + +**Depends On:** [List any blocking stories or external dependencies] +**Blocks:** [List any stories that depend on this one] + +## 📝 Notes + +[Implementation notes, technical considerations, or edge cases] + + + +name: Auto-assign Issues to Todo + +on: + issues: + types: [opened] + +permissions: + issues: write + repository-projects: write + contents: read + +jobs: + add-to-project-and-set-todo: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/red-hat-data-services/projects/12 + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set issue to Todo status + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + console.log('Issue already added to project by previous step. Todo status assignment handled by GitHub project automation.'); + // Note: The actions/add-to-project action handles the basic addition + // Additional status field manipulation can be done here if needed + // but GitHub projects often have their own automation rules + + + +name: Dependabot Auto-Merge + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + pull-requests: write + contents: write + checks: read + +jobs: + dependabot: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-approve Dependabot PRs + if: | + steps.metadata.outputs.update-type == 'version-update:semver-patch' || + steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: | + gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Enable auto-merge for Dependabot PRs + if: | + steps.metadata.outputs.update-type == 'version-update:semver-patch' || + steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: | + gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + + +name: Outcome Metrics Dashboard + +on: + schedule: + - cron: '0 6 * * 1' # Weekly on Mondays at 6 AM UTC + workflow_dispatch: # Allow manual triggers + +jobs: + generate-metrics: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Generate outcome metrics report + uses: actions/github-script@v8 + with: + script: | + const { owner, repo } = context.repo; + + // Get all outcomes + const outcomes = await github.rest.search.issuesAndPullRequests({ + q: `repo:${owner}/${repo} is:issue label:outcome` + }); + + let metricsReport = `# 📊 Outcome Metrics Report\n\n`; + metricsReport += `*Generated: ${new Date().toISOString().split('T')[0]}*\n\n`; + metricsReport += `## Summary\n\n`; + metricsReport += `- **Total Outcomes**: ${outcomes.data.total_count}\n`; + + let completedOutcomes = 0; + let activeOutcomes = 0; + let plannedOutcomes = 0; + + for (const outcome of outcomes.data.items) { + // Get epics for this outcome + const epics = await github.rest.search.issuesAndPullRequests({ + q: `repo:${owner}/${repo} is:issue label:epic "Parent Outcome: #${outcome.number}"` + }); + + const totalEpics = epics.data.total_count; + const closedEpics = epics.data.items.filter(epic => epic.state === 'closed').length; + const progressPercent = totalEpics > 0 ? Math.round((closedEpics / totalEpics) * 100) : 0; + + if (progressPercent === 100) { + completedOutcomes++; + } else if (progressPercent > 0) { + activeOutcomes++; + } else { + plannedOutcomes++; + } + + metricsReport += `\n## ${outcome.title}\n`; + metricsReport += `- **Progress**: ${closedEpics}/${totalEpics} epics (${progressPercent}%)\n`; + metricsReport += `- **Status**: ${outcome.state}\n`; + metricsReport += `- **Link**: [#${outcome.number}](${outcome.html_url})\n`; + + if (totalEpics > 0) { + metricsReport += `\n### Epics Breakdown\n`; + for (const epic of epics.data.items) { + const status = epic.state === 'closed' ? '✅' : '🔄'; + metricsReport += `${status} [${epic.title}](${epic.html_url})\n`; + } + } + } + + metricsReport += `\n## Overall Status\n`; + metricsReport += `- **Completed**: ${completedOutcomes}\n`; + metricsReport += `- **Active**: ${activeOutcomes}\n`; + metricsReport += `- **Planned**: ${plannedOutcomes}\n`; + + // Create or update metrics issue + try { + const existingIssue = await github.rest.search.issuesAndPullRequests({ + q: `repo:${owner}/${repo} is:issue in:title "Outcome Metrics Dashboard"` + }); + + if (existingIssue.data.total_count > 0) { + // Update existing dashboard issue + await github.rest.issues.update({ + owner, + repo, + issue_number: existingIssue.data.items[0].number, + body: metricsReport + }); + console.log('Updated existing metrics dashboard'); + } else { + // Create new dashboard issue + await github.rest.issues.create({ + owner, + repo, + title: '📊 Outcome Metrics Dashboard', + body: metricsReport, + labels: ['metrics', 'dashboard'] + }); + console.log('Created new metrics dashboard'); + } + } catch (error) { + console.error('Error managing metrics dashboard:', error); + } + + + +name: Project Automation + +on: + issues: + types: [opened, edited, labeled, unlabeled, closed, reopened] + issue_comment: + types: [created] + +jobs: + auto-add-to-project: + runs-on: ubuntu-latest + if: github.event_name == 'issues' && github.event.action == 'opened' + steps: + - name: Add issue to project + uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/red-hat-data-services/projects/12 + github-token: ${{ secrets.GITHUB_TOKEN }} + + hierarchy-validation: + runs-on: ubuntu-latest + if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'labeled') + steps: + - name: Check hierarchy labels + uses: actions/github-script@v8 + with: + script: | + const { owner, repo, number } = context.issue; + const issue = await github.rest.issues.get({ + owner, + repo, + issue_number: number + }); + + const labels = issue.data.labels.map(l => l.name); + const hasOutcome = labels.includes('outcome'); + const hasEpic = labels.includes('epic'); + const hasStory = labels.includes('story'); + + // Validate hierarchy rules + const hierarchyCount = [hasOutcome, hasEpic, hasStory].filter(Boolean).length; + + if (hierarchyCount > 1) { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: number, + body: '⚠️ **Hierarchy Validation**: Issues should have only one hierarchy label (outcome, epic, or story). Please remove conflicting labels.' + }); + } + + // Auto-assign project fields based on hierarchy + if (hasOutcome) { + console.log('Outcome detected - should be added to outcome view'); + } else if (hasEpic) { + console.log('Epic detected - should be linked to outcome'); + } else if (hasStory) { + console.log('Story detected - should be linked to epic'); + } + + update-outcome-progress: + runs-on: ubuntu-latest + if: github.event_name == 'issues' && (github.event.action == 'closed' || github.event.action == 'reopened') + steps: + - name: Update parent outcome progress + uses: actions/github-script@v8 + with: + script: | + const { owner, repo, number } = context.issue; + const issue = await github.rest.issues.get({ + owner, + repo, + issue_number: number + }); + + const labels = issue.data.labels.map(l => l.name); + const isEpic = labels.includes('epic'); + + if (isEpic && issue.data.body) { + // Look for parent outcome reference in the body + const outcomeMatch = issue.data.body.match(/\*\*Parent Outcome:\*\* #(\d+)/); + if (outcomeMatch) { + const outcomeNumber = parseInt(outcomeMatch[1]); + + // Get all epics for this outcome + const epics = await github.rest.search.issuesAndPullRequests({ + q: `repo:${owner}/${repo} is:issue label:epic "Parent Outcome: #${outcomeNumber}"` + }); + + const totalEpics = epics.data.total_count; + const closedEpics = epics.data.items.filter(epic => epic.state === 'closed').length; + const progressPercent = totalEpics > 0 ? Math.round((closedEpics / totalEpics) * 100) : 0; + + // Comment on outcome with progress update + await github.rest.issues.createComment({ + owner, + repo, + issue_number: outcomeNumber, + body: `📊 **Progress Update**: ${closedEpics}/${totalEpics} epics completed (${progressPercent}%)` + }); + } + } + + + +name: Test Local Development Environment + +on: + pull_request: + +jobs: + test-local-dev-simulation: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Validate local dev scripts + run: | + echo "Validating local development scripts..." + # Check if scripts exist and are executable + test -f components/scripts/local-dev/crc-start.sh + test -f components/scripts/local-dev/crc-test.sh + test -f components/scripts/local-dev/crc-stop.sh + + # Validate script syntax + bash -n components/scripts/local-dev/crc-start.sh + bash -n components/scripts/local-dev/crc-test.sh + bash -n components/scripts/local-dev/crc-stop.sh + + echo "All local development scripts are valid" + + - name: Test Makefile targets + run: | + echo "Testing Makefile targets..." + # Test that the targets exist (dry run) + make -n dev-start + make -n dev-test + make -n dev-stop + echo "All Makefile targets are valid" + + + +version: 2 +updates: + # Python dependencies + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + # Auto-merge minor and patch updates + pull-request-branch-name: + separator: "-" + commit-message: + prefix: "deps" + include: "scope" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + # Auto-merge minor and patch updates + pull-request-branch-name: + separator: "-" + commit-message: + prefix: "ci" + include: "scope" + + + +# Multi-Tenant Kubernetes Operators: Namespace-per-Tenant Patterns + +## Executive Summary + +This document outlines architectural patterns for implementing multi-tenant AI session management platforms using Kubernetes operators with namespace-per-tenant isolation. The research reveals three critical architectural pillars: **isolation**, **fair resource usage**, and **tenant autonomy**. Modern approaches have evolved beyond simple namespace isolation to incorporate hierarchical namespaces, virtual clusters, and Internal Kubernetes Platforms (IKPs). + +## 1. Best Practices for Namespace-as-Tenant Boundaries + +### Core Multi-Tenancy Model + +The **namespaces-as-a-service** model assigns each tenant a dedicated set of namespaces within a shared cluster. This approach requires implementing multiple isolation layers: + +```yaml +# Tenant CRD Example +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenants.platform.ai +spec: + group: platform.ai + versions: + - name: v1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + namespaces: + type: array + items: + type: string + resourceQuota: + type: object + properties: + cpu: { type: string } + memory: { type: string } + storage: { type: string } + rbacConfig: + type: object + properties: + users: { type: array } + serviceAccounts: { type: array } +``` + +### Three Pillars of Multi-Tenancy + +1. **Isolation**: Network policies, RBAC, and resource boundaries +2. **Fair Resource Usage**: Resource quotas and limits per tenant +3. **Tenant Autonomy**: Self-service namespace provisioning and management + +### Evolution Beyond Simple Namespace Isolation + +Modern architectures combine multiple approaches: +- **Hierarchical Namespaces**: Parent-child relationships with policy inheritance +- **Virtual Clusters**: Isolated control planes within shared infrastructure +- **Internal Kubernetes Platforms (IKPs)**: Pre-configured tenant environments + +## 2. Namespace Lifecycle Management from Custom Operators + +### Controller-Runtime Reconciliation Pattern + +```go +// TenantReconciler manages tenant namespace lifecycle +type TenantReconciler struct { + client.Client + Scheme *runtime.Scheme + Log logr.Logger +} + +func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + tenant := &platformv1.Tenant{} + if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Ensure tenant namespaces exist + for _, nsName := range tenant.Spec.Namespaces { + if err := r.ensureNamespace(ctx, nsName, tenant); err != nil { + return ctrl.Result{}, err + } + } + + // Apply RBAC configurations + if err := r.applyRBAC(ctx, tenant); err != nil { + return ctrl.Result{}, err + } + + // Set resource quotas + if err := r.applyResourceQuotas(ctx, tenant); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *TenantReconciler) ensureNamespace(ctx context.Context, nsName string, tenant *platformv1.Tenant) error { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsName, + Labels: map[string]string{ + "tenant.platform.ai/name": tenant.Name, + "tenant.platform.ai/managed": "true", + }, + }, + } + + // Set owner reference for cleanup + if err := ctrl.SetControllerReference(tenant, ns, r.Scheme); err != nil { + return err + } + + return r.Client.Create(ctx, ns) +} +``` + +### Automated Tenant Provisioning + +The reconciliation loop handles: +- **Namespace Creation**: Dynamic provisioning based on tenant specifications +- **Policy Application**: Automatic application of RBAC, network policies, and quotas +- **Cleanup Management**: Owner references ensure proper garbage collection + +### Hierarchical Namespace Controller Integration + +```yaml +# HNC Configuration for tenant hierarchy +apiVersion: hnc.x-k8s.io/v1alpha2 +kind: HierarchicalNamespace +metadata: + name: tenant-a-dev + namespace: tenant-a +spec: + parent: tenant-a +--- +apiVersion: hnc.x-k8s.io/v1alpha2 +kind: HNCConfiguration +metadata: + name: config +spec: + types: + - apiVersion: v1 + kind: ResourceQuota + mode: Propagate + - apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + mode: Propagate +``` + +## 3. Cross-Namespace Resource Management and Communication + +### Controlled Cross-Namespace Access + +```go +// ServiceDiscovery manages cross-tenant service communication +type ServiceDiscovery struct { + client.Client + allowedConnections map[string][]string +} + +func (sd *ServiceDiscovery) EnsureNetworkPolicies(ctx context.Context, tenant *platformv1.Tenant) error { + for _, ns := range tenant.Spec.Namespaces { + policy := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-isolation", + Namespace: ns, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, // Apply to all pods + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + networkingv1.PolicyTypeEgress, + }, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "tenant.platform.ai/name": tenant.Name, + }, + }, + }, + }, + }, + }, + }, + } + + if err := sd.Client.Create(ctx, policy); err != nil { + return err + } + } + return nil +} +``` + +### Shared Platform Services Pattern + +```yaml +# Cross-tenant service access via dedicated namespace +apiVersion: v1 +kind: Namespace +metadata: + name: platform-shared + labels: + platform.ai/shared: "true" +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-platform-access + namespace: platform-shared +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + tenant.platform.ai/managed: "true" +``` + +## 4. Security Considerations and RBAC Patterns + +### Multi-Layer Security Architecture + +#### Role-Based Access Control (RBAC) + +```yaml +# Tenant-specific RBAC template +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: "{{ .TenantNamespace }}" + name: tenant-admin +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] + resourceNames: ["{{ .TenantNamespace }}"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: tenant-admin-binding + namespace: "{{ .TenantNamespace }}" +subjects: +- kind: User + name: "{{ .TenantUser }}" + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: tenant-admin + apiGroup: rbac.authorization.k8s.io +``` + +#### Network Isolation Strategies + +```go +// NetworkPolicyManager ensures tenant network isolation +func (npm *NetworkPolicyManager) CreateTenantIsolation(ctx context.Context, tenant *platformv1.Tenant) error { + // Default deny all policy + denyAll := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-deny-all", + Namespace: tenant.Spec.PrimaryNamespace, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + networkingv1.PolicyTypeEgress, + }, + }, + } + + // Allow intra-tenant communication + allowIntraTenant := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-intra-tenant", + Namespace: tenant.Spec.PrimaryNamespace, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + networkingv1.PolicyTypeEgress, + }, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "tenant.platform.ai/name": tenant.Name, + }, + }, + }, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "tenant.platform.ai/name": tenant.Name, + }, + }, + }, + }, + }, + }, + }, + } + + return npm.applyPolicies(ctx, denyAll, allowIntraTenant) +} +``` + +### DNS Isolation + +```yaml +# CoreDNS configuration for tenant DNS isolation +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns-custom + namespace: kube-system +data: + tenant-isolation.server: | + platform.ai:53 { + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + ttl 30 + } + k8s_external hostname + prometheus :9153 + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + import /etc/coredns/custom/*.server + } +``` + +## 5. Resource Quota and Limit Management + +### Dynamic Resource Allocation + +```go +// ResourceQuotaManager handles per-tenant resource allocation +type ResourceQuotaManager struct { + client.Client + defaultQuotas map[string]resource.Quantity +} + +func (rqm *ResourceQuotaManager) ApplyTenantQuotas(ctx context.Context, tenant *platformv1.Tenant) error { + for _, ns := range tenant.Spec.Namespaces { + quota := &corev1.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-quota", + Namespace: ns, + }, + Spec: corev1.ResourceQuotaSpec{ + Hard: corev1.ResourceList{ + corev1.ResourceCPU: tenant.Spec.ResourceQuota.CPU, + corev1.ResourceMemory: tenant.Spec.ResourceQuota.Memory, + corev1.ResourceRequestsStorage: tenant.Spec.ResourceQuota.Storage, + corev1.ResourcePods: resource.MustParse("50"), + corev1.ResourceServices: resource.MustParse("10"), + corev1.ResourcePersistentVolumeClaims: resource.MustParse("5"), + }, + }, + } + + if err := ctrl.SetControllerReference(tenant, quota, rqm.Scheme); err != nil { + return err + } + + if err := rqm.Client.Create(ctx, quota); err != nil { + return err + } + } + return nil +} +``` + +### Resource Monitoring and Alerting + +```yaml +# Prometheus rules for tenant resource monitoring +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: tenant-resource-alerts + namespace: monitoring +spec: + groups: + - name: tenant.rules + rules: + - alert: TenantResourceQuotaExceeded + expr: | + ( + kube_resourcequota{type="used"} / + kube_resourcequota{type="hard"} + ) > 0.9 + for: 5m + labels: + severity: warning + tenant: "{{ $labels.namespace }}" + annotations: + summary: "Tenant {{ $labels.namespace }} approaching resource limit" + description: "Resource {{ $labels.resource }} is at {{ $value }}% of quota" +``` + +## 6. Monitoring and Observability Across Tenant Namespaces + +### Multi-Tenant Metrics Collection + +```go +// MetricsCollector aggregates tenant-specific metrics +type MetricsCollector struct { + client.Client + metricsClient metrics.Interface +} + +func (mc *MetricsCollector) CollectTenantMetrics(ctx context.Context) (*TenantMetrics, error) { + tenants := &platformv1.TenantList{} + if err := mc.List(ctx, tenants); err != nil { + return nil, err + } + + metrics := &TenantMetrics{ + Tenants: make(map[string]TenantResourceUsage), + } + + for _, tenant := range tenants.Items { + usage, err := mc.getTenantUsage(ctx, &tenant) + if err != nil { + continue + } + metrics.Tenants[tenant.Name] = *usage + } + + return metrics, nil +} + +func (mc *MetricsCollector) getTenantUsage(ctx context.Context, tenant *platformv1.Tenant) (*TenantResourceUsage, error) { + var totalCPU, totalMemory resource.Quantity + + for _, ns := range tenant.Spec.Namespaces { + nsMetrics, err := mc.metricsClient.MetricsV1beta1(). + NodeMetricses(). + List(ctx, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("namespace=%s", ns), + }) + if err != nil { + return nil, err + } + + // Aggregate metrics across namespace + for _, metric := range nsMetrics.Items { + totalCPU.Add(metric.Usage[corev1.ResourceCPU]) + totalMemory.Add(metric.Usage[corev1.ResourceMemory]) + } + } + + return &TenantResourceUsage{ + CPU: totalCPU, + Memory: totalMemory, + }, nil +} +``` + +### Observability Dashboard Configuration + +```yaml +# Grafana dashboard for tenant metrics +apiVersion: v1 +kind: ConfigMap +metadata: + name: tenant-dashboard + namespace: monitoring +data: + dashboard.json: | + { + "dashboard": { + "title": "Multi-Tenant Resource Usage", + "panels": [ + { + "title": "CPU Usage by Tenant", + "type": "graph", + "targets": [ + { + "expr": "sum by (tenant) (rate(container_cpu_usage_seconds_total{namespace=~\"tenant-.*\"}[5m]))", + "legendFormat": "{{ tenant }}" + } + ] + }, + { + "title": "Memory Usage by Tenant", + "type": "graph", + "targets": [ + { + "expr": "sum by (tenant) (container_memory_usage_bytes{namespace=~\"tenant-.*\"})", + "legendFormat": "{{ tenant }}" + } + ] + } + ] + } + } +``` + +## 7. Common Pitfalls and Anti-Patterns to Avoid + +### Pitfall 1: Inadequate RBAC Scope + +**Anti-Pattern**: Using cluster-wide permissions for namespace-scoped operations + +```go +// BAD: Cluster-wide RBAC for tenant operations +//+kubebuilder:rbac:groups=*,resources=*,verbs=* + +// GOOD: Namespace-scoped RBAC +//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=* +//+kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=* +``` + +### Pitfall 2: Shared CRD Limitations + +**Problem**: CRDs are cluster-scoped, creating challenges for tenant-specific schemas + +**Solution**: Use tenant-aware CRD designs with validation + +```yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: aisessions.platform.ai +spec: + group: platform.ai + scope: Namespaced # Critical for multi-tenancy + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + tenantId: + type: string + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + required: ["tenantId"] +``` + +### Pitfall 3: Resource Leak in Reconciliation + +**Anti-Pattern**: Not cleaning up orphaned resources + +```go +// BAD: No cleanup logic +func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Create resources but no cleanup + return ctrl.Result{}, nil +} + +// GOOD: Proper cleanup with finalizers +func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + tenant := &platformv1.Tenant{} + if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Handle deletion + if tenant.DeletionTimestamp != nil { + return r.handleDeletion(ctx, tenant) + } + + // Add finalizer if not present + if !controllerutil.ContainsFinalizer(tenant, TenantFinalizer) { + controllerutil.AddFinalizer(tenant, TenantFinalizer) + return ctrl.Result{}, r.Update(ctx, tenant) + } + + // Normal reconciliation logic + return r.reconcileNormal(ctx, tenant) +} +``` + +### Pitfall 4: Excessive Reconciliation + +**Anti-Pattern**: Triggering unnecessary reconciliations + +```go +// BAD: Watching too many resources without filtering +func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&platformv1.Tenant{}). + Owns(&corev1.Namespace{}). + Owns(&corev1.ResourceQuota{}). + Complete(r) // This watches ALL namespaces and quotas +} + +// GOOD: Filtered watches with predicates +func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&platformv1.Tenant{}). + Owns(&corev1.Namespace{}). + Owns(&corev1.ResourceQuota{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + }). + WithEventFilter(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + // Only reconcile if spec changed + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + }, + }). + Complete(r) +} +``` + +### Pitfall 5: Missing Network Isolation + +**Anti-Pattern**: Assuming namespace boundaries provide network isolation + +```yaml +# BAD: No network policies = flat networking +# Pods can communicate across all namespaces + +# GOOD: Explicit network isolation +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: tenant-namespace +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +``` + +## 8. CRD Design for Tenant-Scoped Resources + +### Tenant Resource Hierarchy + +```yaml +# Primary Tenant CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenants.platform.ai +spec: + group: platform.ai + scope: Cluster # Tenant management is cluster-scoped + versions: + - name: v1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + displayName: + type: string + adminUsers: + type: array + items: + type: string + namespaces: + type: array + items: + type: object + properties: + name: + type: string + purpose: + type: string + enum: ["development", "staging", "production"] + resourceQuotas: + type: object + properties: + cpu: + type: string + pattern: "^[0-9]+(m|[0-9]*\\.?[0-9]*)?$" + memory: + type: string + pattern: "^[0-9]+([EPTGMK]i?)?$" + storage: + type: string + pattern: "^[0-9]+([EPTGMK]i?)?$" + status: + type: object + properties: + phase: + type: string + enum: ["Pending", "Active", "Terminating", "Failed"] + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + reason: + type: string + message: + type: string + lastTransitionTime: + type: string + format: date-time + namespaceStatus: + type: object + additionalProperties: + type: object + properties: + ready: + type: boolean + resourceUsage: + type: object + properties: + cpu: + type: string + memory: + type: string + storage: + type: string + +--- +# AI Session CRD (namespace-scoped) +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: aisessions.platform.ai +spec: + group: platform.ai + scope: Namespaced # Sessions are tenant-scoped + versions: + - name: v1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + tenantRef: + type: object + properties: + name: + type: string + required: ["name"] + sessionType: + type: string + enum: ["analysis", "automation", "research"] + aiModel: + type: string + enum: ["claude-3-sonnet", "claude-3-haiku", "gpt-4"] + resources: + type: object + properties: + cpu: + type: string + default: "500m" + memory: + type: string + default: "1Gi" + timeout: + type: string + default: "30m" + required: ["tenantRef", "sessionType"] + status: + type: object + properties: + phase: + type: string + enum: ["Pending", "Running", "Completed", "Failed", "Terminated"] + startTime: + type: string + format: date-time + completionTime: + type: string + format: date-time + results: + type: object + properties: + outputData: + type: string + metrics: + type: object + properties: + tokensUsed: + type: integer + executionTime: + type: string +``` + +## 9. Architectural Recommendations for AI Session Management Platform + +### Multi-Tenant Operator Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Platform Control Plane │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Tenant Operator │ │Session Operator │ │Resource Manager │ │ +│ │ │ │ │ │ │ │ +│ │ - Namespace │ │ - AI Sessions │ │ - Quotas │ │ +│ │ Lifecycle │ │ - Job Creation │ │ - Monitoring │ │ +│ │ - RBAC Setup │ │ - Status Mgmt │ │ - Alerting │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Tenant Namespaces │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ tenant-a │ │ tenant-b │ │ tenant-c │ │ shared-svc │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ AI Sessions │ │ AI Sessions │ │ AI Sessions │ │ Monitoring │ │ +│ │ Workloads │ │ Workloads │ │ Workloads │ │ Logging │ │ +│ │ Storage │ │ Storage │ │ Storage │ │ Metrics │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Key Architectural Decisions + +1. **Namespace-per-Tenant**: Each tenant receives dedicated namespaces for workload isolation +2. **Hierarchical Resource Management**: Parent tenant CRDs manage child AI session resources +3. **Cross-Namespace Service Discovery**: Controlled communication via shared service namespaces +4. **Resource Quota Inheritance**: Tenant-level quotas automatically applied to all namespaces +5. **Automated Lifecycle Management**: Full automation of provisioning, scaling, and cleanup + +This architectural framework provides a robust foundation for building scalable, secure, and maintainable multi-tenant AI platforms on Kubernetes, leveraging proven patterns while avoiding common pitfalls in operator development. + + + +# Ambient Agentic Runner - User Capabilities + +## What You Can Do + +### Website Analysis & Research + +#### Analyze User Experience +You describe a website you want analyzed. The AI agent visits the site, explores its interface, and provides you with detailed insights about navigation flow, design patterns, accessibility features, and user journey friction points. You receive a comprehensive report with specific recommendations for improvements. + +#### Competitive Intelligence Gathering +You provide competitor websites. The AI agent systematically explores each site, documenting their features, pricing models, value propositions, and market positioning. You get a comparative analysis highlighting strengths, weaknesses, and opportunities for differentiation. + +#### Content Strategy Research +You specify topics or industries to research. The AI agent browses relevant websites, extracts content themes, analyzes messaging strategies, and identifies trending topics. You receive insights about content gaps, audience targeting approaches, and engagement patterns. + +### Automated Data Collection + +#### Product Catalog Extraction +You point to e-commerce sites. The AI agent navigates through product pages, collecting item details, prices, descriptions, and specifications. You get structured data ready for analysis or import into your systems. + +#### Contact Information Gathering +You provide business directories or company websites. The AI agent finds and extracts contact details, addresses, social media links, and key personnel information. You receive organized contact databases for outreach campaigns. + +#### News & Updates Monitoring +You specify websites to monitor. The AI agent regularly checks for new content, press releases, or announcements. You get summaries of important updates and changes relevant to your interests. + +### Quality Assurance & Testing + +#### Website Functionality Verification +You describe user workflows to test. The AI agent performs the actions, checking if forms submit correctly, links work, and features respond as expected. You receive test results with screenshots documenting any issues found. + +#### Cross-Browser Compatibility Checks +You specify pages to verify. The AI agent tests how content displays and functions across different browser configurations. You get a compatibility report highlighting rendering issues or functional problems. + +#### Performance & Load Time Analysis +You provide URLs to assess. The AI agent measures page load times, identifies slow-loading elements, and evaluates responsiveness. You receive performance metrics with optimization suggestions. + +### Market Research & Intelligence + +#### Pricing Strategy Analysis +You identify competitor products or services. The AI agent explores pricing pages, captures pricing tiers, and documents feature comparisons. You get insights into market pricing patterns and positioning strategies. + +#### Technology Stack Discovery +You specify companies to research. The AI agent analyzes their websites to identify technologies, frameworks, and third-party services in use. You receive technology profiles useful for partnership or integration decisions. + +#### Customer Sentiment Research +You point to review sites or forums. The AI agent reads customer feedback, identifies common complaints and praises, and synthesizes sentiment patterns. You get actionable insights about market perceptions and customer needs. + +### Content & Documentation + +#### Website Content Audit +You specify sections to review. The AI agent systematically reads through content, checking for outdated information, broken references, or inconsistencies. You receive an audit report with specific items needing attention. + +#### Documentation Completeness Check +You provide documentation sites. The AI agent verifies that all advertised features are documented, examples work, and links are valid. You get a gap analysis highlighting missing or incomplete documentation. + +#### SEO & Metadata Analysis +You specify pages to analyze. The AI agent examines page titles, descriptions, heading structures, and keyword usage. You receive SEO recommendations for improving search visibility. + +## How It Works for You + +### Starting a Session +1. You open the web interface +2. You describe what you want to accomplish +3. You provide the website URL to analyze +4. You adjust any preferences (optional) +5. You submit your request + +### During Execution +- You see real-time status updates +- You can monitor progress indicators +- You have visibility into what the AI is doing +- You can stop the session if needed + +### Getting Results +- You receive comprehensive findings in readable format +- You get actionable insights and recommendations +- You can export or copy results for your use +- You have a complete record of the analysis + +## Session Examples + +### Example: E-commerce Competitor Analysis +**You provide:** "Analyze this competitor's online store and identify their unique selling points" +**You receive:** Detailed analysis of product range, pricing strategy, promotional tactics, customer engagement features, checkout process, and differentiation opportunities. + +### Example: Website Accessibility Audit +**You provide:** "Check if this website meets accessibility standards" +**You receive:** Report on keyboard navigation, screen reader compatibility, color contrast issues, alt text presence, ARIA labels, and specific accessibility improvements needed. + +### Example: Lead Generation Research +**You provide:** "Find potential clients in the renewable energy sector" +**You receive:** List of companies with their websites, contact information, company size, recent news, and relevant decision-makers for targeted outreach. + +### Example: Content Gap Analysis +**You provide:** "Compare our documentation with competitors" +**You receive:** Comparison of documentation completeness, topics covered, example quality, and specific areas where your documentation could be enhanced. + +## Benefits You Experience + +### Time Savings +- Hours of manual research completed in minutes +- Parallel analysis of multiple websites +- Automated repetitive checking tasks +- Consistent and thorough exploration + +### Comprehensive Coverage +- No important details missed +- Systematic exploration of all sections +- Multiple perspectives considered +- Deep analysis beyond surface level + +### Actionable Insights +- Specific recommendations provided +- Practical next steps identified +- Clear priority areas highlighted +- Data-driven decision support + +### Consistent Quality +- Same thoroughness every time +- Objective analysis without bias +- Standardized reporting format +- Reliable and repeatable process + + + +# Constitution Update Checklist + +When amending the constitution (`/memory/constitution.md`), ensure all dependent documents are updated to maintain consistency. + +## Templates to Update + +### When adding/modifying ANY article: +- [ ] `/templates/plan-template.md` - Update Constitution Check section +- [ ] `/templates/spec-template.md` - Update if requirements/scope affected +- [ ] `/templates/tasks-template.md` - Update if new task types needed +- [ ] `/.claude/commands/plan.md` - Update if planning process changes +- [ ] `/.claude/commands/tasks.md` - Update if task generation affected +- [ ] `/CLAUDE.md` - Update runtime development guidelines + +### Article-specific updates: + +#### Article I (Library-First): +- [ ] Ensure templates emphasize library creation +- [ ] Update CLI command examples +- [ ] Add llms.txt documentation requirements + +#### Article II (CLI Interface): +- [ ] Update CLI flag requirements in templates +- [ ] Add text I/O protocol reminders + +#### Article III (Test-First): +- [ ] Update test order in all templates +- [ ] Emphasize TDD requirements +- [ ] Add test approval gates + +#### Article IV (Integration Testing): +- [ ] List integration test triggers +- [ ] Update test type priorities +- [ ] Add real dependency requirements + +#### Article V (Observability): +- [ ] Add logging requirements to templates +- [ ] Include multi-tier log streaming +- [ ] Update performance monitoring sections + +#### Article VI (Versioning): +- [ ] Add version increment reminders +- [ ] Include breaking change procedures +- [ ] Update migration requirements + +#### Article VII (Simplicity): +- [ ] Update project count limits +- [ ] Add pattern prohibition examples +- [ ] Include YAGNI reminders + +## Validation Steps + +1. **Before committing constitution changes:** + - [ ] All templates reference new requirements + - [ ] Examples updated to match new rules + - [ ] No contradictions between documents + +2. **After updating templates:** + - [ ] Run through a sample implementation plan + - [ ] Verify all constitution requirements addressed + - [ ] Check that templates are self-contained (readable without constitution) + +3. **Version tracking:** + - [ ] Update constitution version number + - [ ] Note version in template footers + - [ ] Add amendment to constitution history + +## Common Misses + +Watch for these often-forgotten updates: +- Command documentation (`/commands/*.md`) +- Checklist items in templates +- Example code/commands +- Domain-specific variations (web vs mobile vs CLI) +- Cross-references between documents + +## Template Sync Status + +Last sync check: 2025-07-16 +- Constitution version: 2.1.1 +- Templates aligned: ❌ (missing versioning, observability details) + +--- + +*This checklist ensures the constitution's principles are consistently applied across all project documentation.* + + + +#!/usr/bin/env bash +set -e +JSON_MODE=false +for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" +eval $(get_feature_paths) +check_feature_branch "$CURRENT_BRANCH" || exit 1 +if [[ ! -d "$FEATURE_DIR" ]]; then echo "ERROR: Feature directory not found: $FEATURE_DIR"; echo "Run /specify first."; exit 1; fi +if [[ ! -f "$IMPL_PLAN" ]]; then echo "ERROR: plan.md not found in $FEATURE_DIR"; echo "Run /plan first."; exit 1; fi +if $JSON_MODE; then + docs=(); [[ -f "$RESEARCH" ]] && docs+=("research.md"); [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md"); ([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/"); [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md"); + json_docs=$(printf '"%s",' "${docs[@]}"); json_docs="[${json_docs%,}]"; printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" +else + echo "FEATURE_DIR:$FEATURE_DIR"; echo "AVAILABLE_DOCS:"; check_file "$RESEARCH" "research.md"; check_file "$DATA_MODEL" "data-model.md"; check_dir "$CONTRACTS_DIR" "contracts/"; check_file "$QUICKSTART" "quickstart.md"; fi + + + +#!/usr/bin/env bash +set -e +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" +eval $(get_feature_paths) +check_feature_branch "$CURRENT_BRANCH" || exit 1 +echo "REPO_ROOT: $REPO_ROOT"; echo "BRANCH: $CURRENT_BRANCH"; echo "FEATURE_DIR: $FEATURE_DIR"; echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "TASKS: $TASKS" + + + +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// Health returns a simple health check handler +func Health(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) +} + + + +package handlers + +import ( + "encoding/base64" + "encoding/json" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + authv1 "k8s.io/api/authorization/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// Dependencies injected from main package +var ( + BaseKubeConfig *rest.Config + K8sClientMw *kubernetes.Clientset +) + +// Helper functions and types +var ( + BoolPtr = func(b bool) *bool { return &b } + StringPtr = func(s string) *string { return &s } +) + +// ContentListItem represents a content list item for file browsing +type ContentListItem struct { + Name string `json:"name"` + Path string `json:"path"` + IsDir bool `json:"isDir"` + Size int64 `json:"size"` + ModifiedAt string `json:"modifiedAt"` +} + +// GetK8sClientsForRequest returns K8s typed and dynamic clients using the caller's token when provided. +// It supports both Authorization: Bearer and X-Forwarded-Access-Token and NEVER falls back to the backend service account. +// Returns nil, nil if no valid user token is provided - all API operations require user authentication. +func GetK8sClientsForRequest(c *gin.Context) (*kubernetes.Clientset, dynamic.Interface) { + // Prefer Authorization header (Bearer ) + rawAuth := c.GetHeader("Authorization") + rawFwd := c.GetHeader("X-Forwarded-Access-Token") + tokenSource := "none" + token := rawAuth + + if token != "" { + tokenSource = "authorization" + parts := strings.SplitN(token, " ", 2) + if len(parts) == 2 && strings.ToLower(parts[0]) == "bearer" { + token = strings.TrimSpace(parts[1]) + } else { + token = strings.TrimSpace(token) + } + } + // Fallback to X-Forwarded-Access-Token + if token == "" { + if rawFwd != "" { + tokenSource = "x-forwarded-access-token" + } + token = rawFwd + } + + // Debug: basic auth header state (do not log token) + hasAuthHeader := strings.TrimSpace(rawAuth) != "" + hasFwdToken := strings.TrimSpace(rawFwd) != "" + + if token != "" && BaseKubeConfig != nil { + cfg := *BaseKubeConfig + cfg.BearerToken = token + // Ensure we do NOT fall back to the in-cluster SA token or other auth providers + cfg.BearerTokenFile = "" + cfg.AuthProvider = nil + cfg.ExecProvider = nil + cfg.Username = "" + cfg.Password = "" + + kc, err1 := kubernetes.NewForConfig(&cfg) + dc, err2 := dynamic.NewForConfig(&cfg) + + if err1 == nil && err2 == nil { + + // Best-effort update last-used for service account tokens + updateAccessKeyLastUsedAnnotation(c) + return kc, dc + } + // Token provided but client build failed – treat as invalid token + log.Printf("Failed to build user-scoped k8s clients (source=%s tokenLen=%d) typedErr=%v dynamicErr=%v for %s", tokenSource, len(token), err1, err2, c.FullPath()) + return nil, nil + } else { + // No token provided + log.Printf("No user token found for %s (hasAuthHeader=%t hasFwdToken=%t)", c.FullPath(), hasAuthHeader, hasFwdToken) + return nil, nil + } +} + +// updateAccessKeyLastUsedAnnotation attempts to update the ServiceAccount's last-used annotation +// when the incoming token is a ServiceAccount JWT. Uses the backend service account client strictly +// for this telemetry update and only for SAs labeled app=ambient-access-key. Best-effort; errors ignored. +func updateAccessKeyLastUsedAnnotation(c *gin.Context) { + // Parse Authorization header + rawAuth := c.GetHeader("Authorization") + parts := strings.SplitN(rawAuth, " ", 2) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + return + } + token := strings.TrimSpace(parts[1]) + if token == "" { + return + } + + // Decode JWT payload (second segment) + segs := strings.Split(token, ".") + if len(segs) < 2 { + return + } + payloadB64 := segs[1] + // JWT uses base64url without padding; add padding if necessary + if m := len(payloadB64) % 4; m != 0 { + payloadB64 += strings.Repeat("=", 4-m) + } + data, err := base64.URLEncoding.DecodeString(payloadB64) + if err != nil { + return + } + var payload map[string]interface{} + if err := json.Unmarshal(data, &payload); err != nil { + return + } + // Expect sub like: system:serviceaccount:: + sub, _ := payload["sub"].(string) + const prefix = "system:serviceaccount:" + if !strings.HasPrefix(sub, prefix) { + return + } + rest := strings.TrimPrefix(sub, prefix) + parts2 := strings.SplitN(rest, ":", 2) + if len(parts2) != 2 { + return + } + ns := parts2[0] + saName := parts2[1] + + // Backend client must exist + if K8sClientMw == nil { + return + } + + // Ensure the SA is an Ambient access key (label check) before writing + saObj, err := K8sClientMw.CoreV1().ServiceAccounts(ns).Get(c.Request.Context(), saName, v1.GetOptions{}) + if err != nil { + return + } + if saObj.Labels == nil || saObj.Labels["app"] != "ambient-access-key" { + return + } + + // Patch the annotation + now := time.Now().Format(time.RFC3339) + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]string{ + "ambient-code.io/last-used-at": now, + }, + }, + } + b, err := json.Marshal(patch) + if err != nil { + return + } + _, err = K8sClientMw.CoreV1().ServiceAccounts(ns).Patch(c.Request.Context(), saName, types.MergePatchType, b, v1.PatchOptions{}) + if err != nil && !errors.IsNotFound(err) { + log.Printf("Failed to update last-used annotation for SA %s/%s: %v", ns, saName, err) + } +} + +// ExtractServiceAccountFromAuth extracts namespace and ServiceAccount name from the Authorization Bearer JWT 'sub' claim +// Returns (namespace, saName, true) when a SA subject is present, otherwise ("","",false) +func ExtractServiceAccountFromAuth(c *gin.Context) (string, string, bool) { + rawAuth := c.GetHeader("Authorization") + parts := strings.SplitN(rawAuth, " ", 2) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + return "", "", false + } + token := strings.TrimSpace(parts[1]) + if token == "" { + return "", "", false + } + segs := strings.Split(token, ".") + if len(segs) < 2 { + return "", "", false + } + payloadB64 := segs[1] + if m := len(payloadB64) % 4; m != 0 { + payloadB64 += strings.Repeat("=", 4-m) + } + data, err := base64.URLEncoding.DecodeString(payloadB64) + if err != nil { + return "", "", false + } + var payload map[string]interface{} + if err := json.Unmarshal(data, &payload); err != nil { + return "", "", false + } + sub, _ := payload["sub"].(string) + const prefix = "system:serviceaccount:" + if !strings.HasPrefix(sub, prefix) { + return "", "", false + } + rest := strings.TrimPrefix(sub, prefix) + parts2 := strings.SplitN(rest, ":", 2) + if len(parts2) != 2 { + return "", "", false + } + return parts2[0], parts2[1], true +} + +// ValidateProjectContext is middleware for project context validation +func ValidateProjectContext() gin.HandlerFunc { + return func(c *gin.Context) { + // Allow token via query parameter for websocket/agent callers + if c.GetHeader("Authorization") == "" && c.GetHeader("X-Forwarded-Access-Token") == "" { + if qp := strings.TrimSpace(c.Query("token")); qp != "" { + c.Request.Header.Set("Authorization", "Bearer "+qp) + } + } + // Require user/API key token; do not fall back to service account + if c.GetHeader("Authorization") == "" && c.GetHeader("X-Forwarded-Access-Token") == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User token required"}) + c.Abort() + return + } + reqK8s, _ := GetK8sClientsForRequest(c) + if reqK8s == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"}) + c.Abort() + return + } + // Prefer project from route param; fallback to header for backward compatibility + projectHeader := c.Param("projectName") + if projectHeader == "" { + projectHeader = c.GetHeader("X-OpenShift-Project") + } + if projectHeader == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Project is required in path /api/projects/:projectName or X-OpenShift-Project header"}) + c.Abort() + return + } + + // Ensure the caller has at least list permission on agenticsessions in the namespace + ssar := &authv1.SelfSubjectAccessReview{ + Spec: authv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authv1.ResourceAttributes{ + Group: "vteam.ambient-code", + Resource: "agenticsessions", + Verb: "list", + Namespace: projectHeader, + }, + }, + } + res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(c.Request.Context(), ssar, v1.CreateOptions{}) + if err != nil { + log.Printf("validateProjectContext: SSAR failed for %s: %v", projectHeader, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform access review"}) + c.Abort() + return + } + if !res.Status.Allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized to access project"}) + c.Abort() + return + } + + // Store project in context for handlers + c.Set("project", projectHeader) + c.Next() + } +} + + + +package server + +import ( + "fmt" + "os" + + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +var ( + K8sClient *kubernetes.Clientset + DynamicClient dynamic.Interface + Namespace string + StateBaseDir string + PvcBaseDir string + BaseKubeConfig *rest.Config +) + +// InitK8sClients initializes Kubernetes clients and configuration +func InitK8sClients() error { + var config *rest.Config + var err error + + // Try in-cluster config first + if config, err = rest.InClusterConfig(); err != nil { + // If in-cluster config fails, try kubeconfig + kubeconfig := os.Getenv("KUBECONFIG") + if kubeconfig == "" { + kubeconfig = fmt.Sprintf("%s/.kube/config", os.Getenv("HOME")) + } + + if config, err = clientcmd.BuildConfigFromFlags("", kubeconfig); err != nil { + return fmt.Errorf("failed to create Kubernetes config: %v", err) + } + } + + // Create standard Kubernetes client + K8sClient, err = kubernetes.NewForConfig(config) + if err != nil { + return fmt.Errorf("failed to create Kubernetes client: %v", err) + } + + // Create dynamic client for CRD operations + DynamicClient, err = dynamic.NewForConfig(config) + if err != nil { + return fmt.Errorf("failed to create dynamic client: %v", err) + } + + // Save base config for per-request impersonation/user-token clients + BaseKubeConfig = config + + return nil +} + +// InitConfig initializes configuration from environment variables +func InitConfig() { + // Get namespace from environment or use default + Namespace = os.Getenv("NAMESPACE") + if Namespace == "" { + Namespace = "default" + } + + // Get state storage base directory + StateBaseDir = os.Getenv("STATE_BASE_DIR") + if StateBaseDir == "" { + StateBaseDir = "/workspace" + } + + // Get PVC base directory for RFE workspaces + PvcBaseDir = os.Getenv("PVC_BASE_DIR") + if PvcBaseDir == "" { + PvcBaseDir = "/workspace" + } +} + + + +# vTeam Backend - .env.example +# Copy this file to .env and adjust values as needed. + +######################################## +# Server & Runtime +######################################## +# HTTP port for the backend server (default: 8080) +PORT=8080 + +# Kubernetes namespace the backend should consider as default (used for logs/metrics and fallbacks) +NAMESPACE=default + +# Local state storage base directory (used for persisting minimal session state) +STATE_BASE_DIR=/data/state + +# Base path where RFE workspace files are mounted (PVC) +PVC_BASE_DIR=/workspace + +# Path to kubeconfig for out-of-cluster development. Leave empty for in-cluster. +# Example: /Users/you/.kube/config +KUBECONFIG= + +######################################## +# GitHub App Integration +######################################## +# If set, enables GitHub App flows. If empty, GitHub App features are disabled. +GITHUB_APP_ID= + +# Path to the GitHub App private key PEM file (default used in container image) +GITHUB_APP_PRIVATE_KEY_PATH=/etc/github-app/private-key.pem + +######################################## +# Runner / Jobs +######################################## +# Optional: Explicit WebSocket URL for runners to connect back to the backend. +# If empty, a URL is constructed using BACKEND_SERVICE and project/session info. +BACKEND_WS_BASE= + +# Kubernetes Service DNS name for the backend (used to build default WS URL for runners) +BACKEND_SERVICE=ambient-code-backend + +# Container image for the runner job (required to launch sessions) +# Example: ghcr.io/your-org/runner-shell:latest +RUNNER_IMAGE= + +######################################## +# Spec Kit bootstrap (workspace initialization) +######################################## +# Version tag of Spec Kit to download when initializing a workspace +SPEC_KIT_VERSION=v0.0.50 + +# Template name prefix used to construct the Spec Kit asset URL +SPEC_KIT_TEMPLATE_NAME=spec-kit-template-claude-sh + + + +# Go build outputs +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Go workspace file +go.work +go.work.sum + +# Dependency directories +vendor/ + + +# Binary output +backend +main + +# Profiling files +*.prof +*.cpu +*.mem + +# Air live reload tool +tmp/ + +# Gin debug logs +gin-bin +gin.log + +# Debug logs +debug.log + +# Coverage reports +coverage.html +coverage.out + +ambient-code-backend + +.env +private-key.pem + + + +# Development Dockerfile for Go backend (simplified, no Air) +FROM golang:1.24-alpine + +WORKDIR /app + +# Install git and build dependencies +RUN apk add --no-cache git build-base + +# Set environment variables +ENV AGENTS_DIR=/app/agents +ENV CGO_ENABLED=0 +ENV GOOS=linux + +# Expose port +EXPOSE 8080 + +# Simple development mode - just run the Go app directly +# Note: Source code will be mounted as volume at runtime +CMD ["sh", "-c", "while [ ! -f main.go ]; do echo 'Waiting for source sync...'; sleep 2; done && go run ."] + + + +# Makefile for ambient-code-backend + +.PHONY: help build test test-unit test-contract test-integration clean run docker-build docker-run + +# Default target +help: ## Show this help message + @echo "Available targets:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' + +# Build targets +build: ## Build the backend binary + go build -o backend . + +clean: ## Clean build artifacts + rm -f backend main + go clean + +# Test targets +test: test-unit test-contract ## Run all tests (excluding integration tests) + +test-unit: ## Run unit tests + go test ./tests/unit/... -v + +test-contract: ## Run contract tests + go test ./tests/contract/... -v + +test-integration: ## Run integration tests (requires Kubernetes cluster) + @echo "Running integration tests (requires Kubernetes cluster access)..." + go test ./tests/integration/... -v -timeout=5m + +test-integration-short: ## Run integration tests with short timeout + go test ./tests/integration/... -v -short + +test-all: test test-integration ## Run all tests including integration tests + +# Test with specific configuration +test-integration-local: ## Run integration tests with local configuration + @echo "Running integration tests with local configuration..." + TEST_NAMESPACE=ambient-code-test \ + CLEANUP_RESOURCES=true \ + go test ./tests/integration/... -v -timeout=5m + +test-integration-ci: ## Run integration tests for CI (no cleanup for debugging) + @echo "Running integration tests for CI..." + TEST_NAMESPACE=ambient-code-ci \ + CLEANUP_RESOURCES=false \ + go test ./tests/integration/... -v -timeout=10m -json + +test-permissions: ## Run permission and RBAC integration tests specifically + @echo "Running permission boundary and RBAC tests..." + TEST_NAMESPACE=ambient-code-test \ + CLEANUP_RESOURCES=true \ + go test ./tests/integration/ -v -run TestPermission -timeout=5m + +test-permissions-verbose: ## Run permission tests with detailed output + @echo "Running permission tests with verbose output..." + TEST_NAMESPACE=ambient-code-test \ + CLEANUP_RESOURCES=true \ + go test ./tests/integration/ -v -run TestPermission -timeout=5m -count=1 + +# Coverage targets +test-coverage: ## Run tests with coverage + go test ./tests/unit/... ./tests/contract/... -coverprofile=coverage.out + go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +# Development targets +run: ## Run the backend server locally + go run . + +dev: ## Run with live reload (requires air: go install github.com/cosmtrek/air@latest) + air + +# Docker targets +docker-build: ## Build Docker image + docker build -t ambient-code-backend . + +docker-run: ## Run Docker container + docker run -p 8080:8080 ambient-code-backend + +# Linting and formatting +fmt: ## Format Go code + go fmt ./... + +vet: ## Run go vet + go vet ./... + +lint: ## Run golangci-lint (requires golangci-lint to be installed) + golangci-lint run + +# Dependency management +deps: ## Download dependencies + go mod download + +deps-update: ## Update dependencies + go get -u ./... + go mod tidy + +deps-verify: ## Verify dependencies + go mod verify + +# Installation targets for development tools +install-tools: ## Install development tools + @echo "Installing development tools..." + go install github.com/cosmtrek/air@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +# Kubernetes-specific targets for integration testing +k8s-setup: ## Setup local Kubernetes for testing (requires kubectl and kind) + @echo "Setting up local Kubernetes cluster for testing..." + kind create cluster --name ambient-test || true + kubectl config use-context kind-ambient-test + @echo "Installing test CRDs..." + kubectl apply -f ../manifests/crds/ || echo "Warning: Could not install CRDs" + +k8s-teardown: ## Teardown local Kubernetes test cluster + @echo "Tearing down test cluster..." + kind delete cluster --name ambient-test || true + +# Pre-commit hooks +pre-commit: fmt vet test ## Run pre-commit checks + +# Build information +version: ## Show version information + @echo "Go version: $(shell go version)" + @echo "Git commit: $(shell git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" + @echo "Build time: $(shell date)" + +# Environment validation +check-env: ## Check environment setup for development + @echo "Checking environment..." + @go version >/dev/null 2>&1 || (echo "❌ Go not installed"; exit 1) + @echo "✅ Go installed: $(shell go version)" + @kubectl version --client >/dev/null 2>&1 || echo "⚠️ kubectl not found (needed for integration tests)" + @docker version >/dev/null 2>&1 || echo "⚠️ Docker not found (needed for container builds)" + @echo "Environment check complete" + + + + + + + + + + + + + + + + + + + + + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function POST(request: Request) { + const headers = await buildForwardHeadersAsync(request) + const resp = await fetch(`${BACKEND_URL}/auth/github/disconnect`, { + method: 'POST', + headers, + }) + const text = await resp.text() + return new Response(text, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function POST(request: Request) { + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + + const resp = await fetch(`${BACKEND_URL}/auth/github/install`, { + method: 'POST', + headers, + body, + }) + + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function GET(request: Request) { + const headers = await buildForwardHeadersAsync(request) + + const resp = await fetch(`${BACKEND_URL}/auth/github/status`, { + method: 'GET', + headers, + }) + + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function GET(request: Request) { + const headers = await buildForwardHeadersAsync(request) + const url = new URL(request.url) + const resp = await fetch(`${BACKEND_URL}/auth/github/user/callback${url.search}`, { + method: 'GET', + headers, + redirect: 'manual', + }) + + // Forward redirects from backend (e.g., to /integrations) + const location = resp.headers.get('location') + if (location && [301, 302, 303, 307, 308].includes(resp.status)) { + return new Response(null, { status: resp.status, headers: { Location: location } }) + } + + const text = await resp.text() + return new Response(text, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { buildForwardHeadersAsync } from '@/lib/auth'; + +export async function GET(request: Request) { + try { + // Use the shared helper so dev oc whoami and env fallbacks apply uniformly + const headers = await buildForwardHeadersAsync(request); + const userId = headers['X-Forwarded-User'] || ''; + const email = headers['X-Forwarded-Email'] || ''; + const username = headers['X-Forwarded-Preferred-Username'] || ''; + const token = headers['X-Forwarded-Access-Token'] || ''; + + if (!userId && !username && !email && !token) { + return Response.json({ authenticated: false }, { status: 200 }); + } + + return Response.json({ + authenticated: true, + userId, + email, + username, + displayName: username || email || userId, + }); + } catch (error) { + console.error('Error reading user headers:', error); + return Response.json({ authenticated: false }, { status: 200 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/access`, { headers }); + const data = await resp.json().catch(() => ({})); + return Response.json(data, { status: resp.status }); + } catch (error) { + console.error('Error performing access check:', error); + return Response.json({ error: 'Failed to perform access check' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> } +) { + try { + const { name, sessionName } = await params; + const body = await request.text(); + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/clone`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...headers }, + body, + }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error cloning agentic session:', error); + return Response.json({ error: 'Failed to clone agentic session' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/github/abandon`, { + method: 'POST', + headers: { ...headers, 'Content-Type': 'application/json' }, + body, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + const url = new URL(request.url) + const repoIndex = url.searchParams.get('repoIndex') + const repoPath = url.searchParams.get('repoPath') + const qs = new URLSearchParams() + if (repoIndex) qs.set('repoIndex', repoIndex) + if (repoPath) qs.set('repoPath', repoPath) + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/github/diff?${qs.toString()}`, { headers }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/github/push`, { + method: 'POST', + headers: { ...headers, 'Content-Type': 'application/json' }, + body, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/sessions/${encodeURIComponent(sessionName)}/messages`, { + method: 'GET', + headers, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/sessions/${encodeURIComponent(sessionName)}/messages`, { + method: 'POST', + headers: { ...headers, 'Content-Type': 'application/json' }, + body, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> } +) { + try { + const { name, sessionName } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/stop`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...headers }, + }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error stopping agentic session:', error); + return Response.json({ error: 'Failed to stop agentic session' }, { status: 500 }); + } +} + + + +import { buildForwardHeadersAsync } from '@/lib/auth' +import { BACKEND_URL } from '@/lib/config'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string; path: string[] }> }, +) { + const { name, sessionName, path } = await params + const headers = await buildForwardHeadersAsync(request) + const rel = path.join('/') + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/workspace/${encodeURIComponent(rel)}`, { headers }) + const contentType = resp.headers.get('content-type') || 'application/octet-stream' + const buf = await resp.arrayBuffer() + return new Response(buf, { status: resp.status, headers: { 'Content-Type': contentType } }) +} + + +export async function PUT( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string; path: string[] }> }, +) { + const { name, sessionName, path } = await params + const headers = await buildForwardHeadersAsync(request) + const rel = path.join('/') + const contentType = request.headers.get('content-type') || 'text/plain; charset=utf-8' + const textBody = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/workspace/${encodeURIComponent(rel)}`, { + method: 'PUT', + headers: { ...headers, 'Content-Type': contentType }, + body: textBody, + }) + const respBody = await resp.text() + return new Response(respBody, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { buildForwardHeadersAsync } from '@/lib/auth' +import { BACKEND_URL } from '@/lib/config'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string; sessionName: string }> }, +) { + const { name, sessionName } = await params + const headers = await buildForwardHeadersAsync(request) + // Per-job content service (sidecar) name + const url = new URL(request.url) + const subpath = url.searchParams.get('path') + const query = subpath ? `?path=${encodeURIComponent(subpath)}` : '' + const resp = await fetch( + `${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}/workspace${query}`, + { headers }, + ) + const contentType = resp.headers.get('content-type') || 'application/json' + const body = await resp.text() + return new Response(body, { status: resp.status, headers: { 'Content-Type': contentType } }) +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +type Ctx = { params: Promise<{ name: string; sessionName: string }> }; + +// GET /api/projects/[name]/agentic-sessions/[sessionName] +export async function GET(request: Request, { params }: Ctx) { + try { + const { name, sessionName } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}`, { headers }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error fetching agentic session:', error); + return Response.json({ error: 'Failed to fetch agentic session' }, { status: 500 }); + } +} + +// PUT /api/projects/[name]/agentic-sessions/[sessionName] +export async function PUT(request: Request, { params }: Ctx) { + try { + const { name, sessionName } = await params; + const body = await request.text(); + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...headers }, + body, + }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error updating agentic session:', error); + return Response.json({ error: 'Failed to update agentic session' }, { status: 500 }); + } +} + +// DELETE /api/projects/[name]/agentic-sessions/[sessionName] +export async function DELETE(request: Request, { params }: Ctx) { + try { + const { name, sessionName } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/agentic-sessions/${encodeURIComponent(sessionName)}`, { + method: 'DELETE', + headers, + }); + if (response.status === 204) return new Response(null, { status: 204 }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error deleting agentic session:', error); + return Response.json({ error: 'Failed to delete agentic session' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// DELETE /api/projects/[name]/keys/[keyId] - Delete project access key +export async function DELETE( + request: Request, + { params }: { params: Promise<{ name: string; keyId: string }> } +) { + try { + const { name, keyId } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/keys/${encodeURIComponent(keyId)}`, { + method: 'DELETE', + headers, + }); + + if (!response.ok && response.status !== 204) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + return new Response(null, { status: 204 }); + } catch (error) { + console.error('Error deleting project key:', error); + return Response.json({ error: 'Failed to delete project key' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name]/keys - List project access keys +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/keys`, { headers }); + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + const data = await response.json(); + return Response.json(data); + } catch (error) { + console.error('Error fetching project keys:', error); + return Response.json({ error: 'Failed to fetch project keys' }, { status: 500 }); + } +} + +// POST /api/projects/[name]/keys - Create project access key +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const body = await request.json(); + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/keys`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + const data = await response.json(); + return Response.json(data, { status: 201 }); + } catch (error) { + console.error('Error creating project key:', error); + return Response.json({ error: 'Failed to create project key' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// DELETE /api/projects/[name]/permissions/[subjectType]/[subjectName] - Remove permission +export async function DELETE( + request: Request, + { params }: { params: Promise<{ name: string; subjectType: string; subjectName: string }> } +) { + try { + const { name, subjectType, subjectName } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/permissions/${encodeURIComponent(subjectType)}/${encodeURIComponent(subjectName)}`, { + method: 'DELETE', + headers, + }); + + if (!response.ok && response.status !== 204) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + return new Response(null, { status: 204 }); + } catch (error) { + console.error('Error removing project permission:', error); + return Response.json({ error: 'Failed to remove project permission' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name]/permissions - List project permissions (users & groups) +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/permissions`, { headers }); + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + const data = await response.json(); + return Response.json(data); + } catch (error) { + console.error('Error fetching project permissions:', error); + return Response.json({ error: 'Failed to fetch project permissions' }, { status: 500 }); + } +} + +// POST /api/projects/[name]/permissions - Add permission assignment +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const body = await request.json(); + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}/permissions`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + const data = await response.json(); + return Response.json(data, { status: 201 }); + } catch (error) { + console.error('Error adding project permission:', error); + return Response.json({ error: 'Failed to add project permission' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name]/runner-secrets/config +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/runner-secrets/config`, { headers }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error getting runner secrets config:', error); + return Response.json({ error: 'Failed to get runner secrets config' }, { status: 500 }); + } +} + +// PUT /api/projects/[name]/runner-secrets/config +export async function PUT( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const body = await request.text(); + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/runner-secrets/config`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...headers }, + body, + }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error updating runner secrets config:', error); + return Response.json({ error: 'Failed to update runner secrets config' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name]/runner-secrets +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/runner-secrets`, { headers }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error getting runner secrets:', error); + return Response.json({ error: 'Failed to get runner secrets' }, { status: 500 }); + } +} + +// PUT /api/projects/[name]/runner-secrets +export async function PUT( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const body = await request.text(); + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/runner-secrets`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...headers }, + body, + }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error updating runner secrets:', error); + return Response.json({ error: 'Failed to update runner secrets' }, { status: 500 }); + } +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name]/secrets - List Opaque secrets in a project +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + const response = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/secrets`, { headers }); + const text = await response.text(); + return new Response(text, { status: response.status, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Error listing secrets:', error); + return Response.json({ error: 'Failed to list secrets' }, { status: 500 }); + } +} + + + +import { NextRequest, NextResponse } from "next/server"; +import { BACKEND_URL } from "@/lib/config"; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name: projectName } = await params; + + // Forward the request to the backend + const response = await fetch(`${BACKEND_URL}/projects/${projectName}/settings`, { + method: "GET", + headers: { + "Content-Type": "application/json", + // Forward authentication headers from the client request + "X-User-ID": request.headers.get("X-User-ID") || "", + "X-User-Groups": request.headers.get("X-User-Groups") || "", + }, + }); + + // Forward the response from backend + const data = await response.text(); + + return new NextResponse(data, { + status: response.status, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error) { + console.error("Failed to fetch project settings:", error); + return NextResponse.json( + { error: "Failed to fetch project settings" }, + { status: 500 } + ); + } +} + +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name: projectName } = await params; + const body = await request.text(); + + // Forward the request to the backend + const response = await fetch(`${BACKEND_URL}/projects/${projectName}/settings`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + // Forward authentication headers from the client request + "X-User-ID": request.headers.get("X-User-ID") || "", + "X-User-Groups": request.headers.get("X-User-Groups") || "", + }, + body: body, + }); + + // Forward the response from backend + const data = await response.text(); + + return new NextResponse(data, { + status: response.status, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error) { + console.error("Failed to update project settings:", error); + return NextResponse.json( + { error: "Failed to update project settings" }, + { status: 500 } + ); + } +} + + + +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> }, +) { + const { name } = await params + const headers = await buildForwardHeadersAsync(request) + const url = new URL(request.url) + const qs = url.search + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/users/forks${qs}`, { headers, cache: 'no-store' }) + // Pass through upstream response body and content-type + const contentType = resp.headers.get('content-type') || 'application/json' + return new Response(resp.body, { status: resp.status, headers: { 'Content-Type': contentType, 'Cache-Control': 'no-store' } }) +} + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string }> }, +) { + const { name } = await params + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/users/forks`, { + method: 'POST', + headers, + body, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + + + +import { BACKEND_URL } from '@/lib/config'; +import { buildForwardHeadersAsync } from '@/lib/auth'; + +// GET /api/projects/[name] - Get project by name +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}`, { headers }); + if (!response.ok) { + throw new Error(`Backend responded with status: ${response.status}`); + } + const data = await response.json(); + return Response.json(data); + } catch (error) { + console.error('Error fetching project:', error); + return Response.json({ error: 'Failed to fetch project' }, { status: 500 }); + } +} + +// PUT /api/projects/[name] - Update project +export async function PUT( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const body = await request.json(); + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}`, { + method: 'PUT', + headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + const data = await response.json(); + return Response.json(data); + } catch (error) { + console.error('Error updating project:', error); + return Response.json({ error: 'Failed to update project' }, { status: 500 }); + } +} + +// DELETE /api/projects/[name] - Delete project +export async function DELETE( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects/${name}`, { + method: 'DELETE', + headers, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + return Response.json(errorData, { status: response.status }); + } + + if (response.status === 204) { + return new Response(null, { status: 204 }); + } + + const data = await response.json(); + return Response.json(data); + } catch (error) { + console.error('Error deleting project:', error); + return Response.json({ error: 'Failed to delete project' }, { status: 500 }); + } +} + + + +import { NextRequest, NextResponse } from "next/server"; +import { BACKEND_URL } from "@/lib/config"; +import { buildForwardHeadersAsync } from "@/lib/auth"; + +export async function GET(request: NextRequest) { + try { + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects`, { + method: 'GET', + headers, + }); + + // Forward the response from backend + const data = await response.text(); + + return new NextResponse(data, { + status: response.status, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error) { + console.error("Failed to fetch projects:", error); + return NextResponse.json( + { error: "Failed to fetch projects" }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + try { + const body = await request.text(); + + const headers = await buildForwardHeadersAsync(request); + + const response = await fetch(`${BACKEND_URL}/projects`, { + method: 'POST', + headers, + body: body, + }); + + // Forward the response from backend + const data = await response.text(); + + return new NextResponse(data, { + status: response.status, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error) { + console.error("Failed to create project:", error); + return NextResponse.json( + { error: "Failed to create project" }, + { status: 500 } + ); + } +} + + + +import React from 'react' +import IntegrationsClient from '@/app/integrations/IntegrationsClient' + +export const dynamic = 'force-dynamic' +export const revalidate = 0 + +export default function IntegrationsPage() { + const appSlug = process.env.GITHUB_APP_SLUG + return +} + + + +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } + + + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } + + + +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } + + + +"use client"; + +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { CheckIcon, ChevronRightIcon, Dot } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; + + + +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState } = useFormContext() + const formState = useFormState({ name: fieldContext.name }) + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId() + + return ( + +
+ + ) +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField() + + return ( +