Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
19b56f0
chore: ignore .worktrees/ directory
ridhoassuryadi Jun 19, 2026
dd13f56
feat(workspace): extend RadasConfig with optional WorkspaceConfig
ridhoassuryadi Jun 19, 2026
23f328f
feat(workspace): add Mode detection and Project data model
ridhoassuryadi Jun 19, 2026
6c8fe64
feat(workspace): add ProjectDetector interface, Registry, and radasym…
ridhoassuryadi Jun 19, 2026
75fd3ce
feat(workspace): add scanner, parser, and extract Project to its own …
ridhoassuryadi Jun 19, 2026
12fce46
feat(workspace): add graph builder, traversal, and cycle detection
ridhoassuryadi Jun 19, 2026
c182cad
feat(workspace): add ASCII tree and DOT graphviz exporters
ridhoassuryadi Jun 19, 2026
e881da2
feat(workspace): add SVG/PNG renderer via goccy/go-graphviz
ridhoassuryadi Jun 19, 2026
ad3bbd0
feat(workspace): add web viewer (HTTP + cytoscape assets)
ridhoassuryadi Jun 19, 2026
29a072e
feat(workspace): add workspace command group (init/list/show/graph/va…
ridhoassuryadi Jun 19, 2026
b9843da
test(workspace): add end-to-end integration test + CHANGELOG entry
ridhoassuryadi Jun 19, 2026
29d445c
chore: also register workspace in cmd/root.go for future package cons…
ridhoassuryadi Jun 19, 2026
5643f57
feat(workspace): add content-addressable cache (hasher, local store, …
ridhoassuryadi Jun 19, 2026
f3117e6
feat(workspace): add task runner, cache, and affected detection (Phas…
ridhoassuryadi Jun 19, 2026
4df1013
docs(workspace): add Phase C design spec for code generator
ridhoassuryadi Jun 19, 2026
4a7e09c
docs(workspace): add Phase C implementation plan
ridhoassuryadi Jun 19, 2026
da9f61b
feat(generator): add template definition types and parser
ridhoassuryadi Jun 19, 2026
4f236ca
test(generator): improve test coverage for parse defaults and field a…
ridhoassuryadi Jun 19, 2026
d8b6b2b
docs(generator): add godoc comments and improve error message assertions
ridhoassuryadi Jun 19, 2026
2523c98
feat(generator): add engine with .gotpl rendering support
ridhoassuryadi Jun 19, 2026
0808f6f
feat(generator): add Generate method for template output and file wri…
ridhoassuryadi Jun 19, 2026
0aaa0a0
feat(generator): add variable resolution with --var overrides and def…
ridhoassuryadi Jun 19, 2026
064a5a8
style(generator): fix code quality issues from review
ridhoassuryadi Jun 19, 2026
992a1d3
feat(generator): add local template registry scanner
ridhoassuryadi Jun 19, 2026
1d0db22
fix(generator): move validation to main resolution loop, add tests
ridhoassuryadi Jun 19, 2026
41eaa72
feat(generator): add Registry.Add method for remote template fetching
ridhoassuryadi Jun 19, 2026
549f22b
fix(generator): deduplicate Scan/scanDir, add godocs, fix test quality
ridhoassuryadi Jun 19, 2026
2dab74d
feat(workspace): add generate command for template-based code generation
ridhoassuryadi Jun 19, 2026
c714c75
feat(workspace): add template list command
ridhoassuryadi Jun 19, 2026
18249a2
feat(workspace): add template add command for remote template install…
ridhoassuryadi Jun 19, 2026
02cf60d
feat(workspace): add template create command for local template scaff…
ridhoassuryadi Jun 19, 2026
56da1fe
docs: add Phase C CHANGELOG entry for v4.5.0-workspace-phase-c
ridhoassuryadi Jun 19, 2026
c86893f
test(generator): add integration test for full pipeline
ridhoassuryadi Jun 19, 2026
4afa325
feat(ai): add provider interface and event types
ridhoassuryadi Jun 19, 2026
11ed225
feat(ai): add OpenAI streaming provider implementation
ridhoassuryadi Jun 19, 2026
6c35e99
fix(ai): add package doc, fix event test, add ToolCall JSON tags
ridhoassuryadi Jun 19, 2026
26c6627
feat(ai): add tool registry with register/execute/definitions
ridhoassuryadi Jun 19, 2026
a782462
feat(ai): add chat loop with iteration limit and cost tracking
ridhoassuryadi Jun 20, 2026
4aa1045
feat(ai): add config parsing and system prompt builder
ridhoassuryadi Jun 20, 2026
71aa2c7
fix(ai): fix TOCTOU in ToolRegistry.Execute, strengthen concurrent test
ridhoassuryadi Jun 20, 2026
3f884f7
fix(ai): implement tool-call streaming, fix tool def shape, preserve …
ridhoassuryadi Jun 20, 2026
bc5f42f
feat(tui): add root model with tab switching and styles
ridhoassuryadi Jun 20, 2026
0845c38
feat(tui): replace dashboard stub with full view
ridhoassuryadi Jun 20, 2026
7a2274c
fix(ai): address critical issues in chat loop, env var resolution, hi…
ridhoassuryadi Jun 20, 2026
c15d8f1
fix(tui): correct active tab indicator and footer
ridhoassuryadi Jun 20, 2026
4aa3355
feat(tui): replace chat view stub with full chat implementation
ridhoassuryadi Jun 20, 2026
d08d95b
feat(tui): add :command parser and help overlay
ridhoassuryadi Jun 20, 2026
80d68c3
fix(tui): forward WindowSizeMsg to sub-views, use ai.Role constants
ridhoassuryadi Jun 20, 2026
8e52df8
test(ai): add integration test for chat loop, system prompt, cost tra…
ridhoassuryadi Jun 20, 2026
c9c5a78
docs: add Phase D CHANGELOG entry for v4.6.0-workspace-phase-d
ridhoassuryadi Jun 20, 2026
61d226d
feat(tui): wire TUI into root command for no-args + TTY
ridhoassuryadi Jun 20, 2026
2fd77ba
feat(tui): add offline command interpreter for chat input
ridhoassuryadi Jun 20, 2026
263476b
chore(cli): gitignore pkg/ (Go module cache)
ridhoassuryadi Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,7 @@ terraform.tfvars*
.moon/cache
.moon/docker

# Worktrees
.worktrees/

# Specific directories for individual projects
5 changes: 4 additions & 1 deletion apps/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@

# Build artifacts
bin/
release/
release/

# Go module cache (downloaded deps)
pkg/
52 changes: 52 additions & 0 deletions apps/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Changelog

## [Unreleased]

## [v4.6.0-workspace-phase-d] - 2026-06-20

### Added

- Terminal UI (TUI) with bubbletea — dashboard and AI chat views
- AI assistant with tool-calling capability (OpenAI/OpenRouter)
- Multi-provider AI support (configurable via radas.yml)
- Tool registry with workspace operations (run, generate, scan, read)
- `:command` mode for direct CLI execution within TUI
- Cost tracking per session with configurable ceiling
- System prompt builder with workspace context injection
- Help overlay and keybinding reference
- Streaming chat with iterative tool execution
- env-var resolution for API keys ($OPENROUTER_KEY, etc.)

## [v4.5.0-workspace-phase-c] - 2026-06-20

### Added

- Code generator engine with .gotpl template rendering (text/template, missingkey=error)
- Template definition parser (YAML-based template.yml with variables and outputs)
- Variable resolution with --var overrides, defaults, and validation patterns
- Local template registry with Scan() for discovering templates
- Remote template fetching via git clone (Add method)
- `workspace generate <name>` command with --output-dir, --var, --force, --non-interactive flags
- `workspace template list` command to show available templates
- `workspace template add <url>` command to install remote templates
- `workspace template create <name>` command to scaffold new templates
- Integration test exercising the full generate pipeline

### Added

- `workspace run <task> [--project|--all|--affected]` — topological task execution with cache (Phase B)
- `workspace affected [--base|--json]` — list projects affected by git changes
- `workspace cache status|clear` — local cache management
- `internal/cache/` — content-addressable cache (SHA256, FS-backed at `~/.radas/cache/`)
- `internal/runner/` — pipeline resolution, layered Kahn's scheduler, parallel batch executor, summary table
- `internal/graph/affected.go` — git diff → affected project set with transitive expansion
- `workspace` command group (Phase A: Monorepo Manager)
- `workspace init` — generate radas.yml with workspace section
- `workspace list` — table of all projects
- `workspace show <name>` — project details + dependencies
- `workspace graph [--ascii|--output=svg|png|json|--web]` — dependency visualization
- `workspace validate` — check for cycles and stale patterns
- `internal/workspace/` package: scanner, detectors (radasyml/go/node), parser
- `internal/project/` package: Project data model
- `internal/graph/` package: dominikbraun/graph wrapper, ASCII/DOT/SVG renderers, web viewer
- Configuration: `RadasConfig.Workspace` (optional block; presence enables workspace mode)
6 changes: 5 additions & 1 deletion apps/cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import (
"github.com/raizora/radas/v4/cmd/frontend"
"github.com/raizora/radas/v4/cmd/infra"
"github.com/raizora/radas/v4/cmd/rootcmd"
"github.com/raizora/radas/v4/cmd/workspace"
)

var rootCmd = &cobra.Command{
Use: "radas",
Short: "RADAS CLI - tool to simplify daily developer activities",
Long: `RADAS CLI is a command line interface that helps developers from various teams
(Frontend, Backend, DevOps, Design) to handle their daily activities with ease.`,
(Frontend, Backend, DevOps, Design) to handle their daily activities with ease.
When run with no arguments in a terminal, it launches the TUI dashboard.`,
RunE: runTUI,
}

// Execute runs the root command
Expand Down Expand Up @@ -42,4 +45,5 @@ func init() {
rootCmd.AddCommand(rootcmd.RebuildCmd)
rootCmd.AddCommand(rootcmd.PullCmd)
rootCmd.AddCommand(rootcmd.ScanCmd)
rootCmd.AddCommand(workspace.Cmd)
}
35 changes: 35 additions & 0 deletions apps/cli/cmd/tui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"fmt"
"os"

"github.com/raizora/radas/v4/internal/ai"
"github.com/raizora/radas/v4/internal/tui"
"github.com/spf13/cobra"
)

func isTerminal(fd uintptr) bool {
stat, _ := os.Stdout.Stat()
return (stat.Mode() & os.ModeCharDevice) != 0
}

func runTUI(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return cmd.Help()
}
if !isTerminal(os.Stdout.Fd()) {
return cmd.Help()
}

aiConfig, err := ai.LoadAIConfigFromRadasYML()
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to load AI config: %v\n", err)
aiConfig = nil
}

projects := []string{}
templates := []string{}

return tui.Start(projects, templates, aiConfig)
}
14 changes: 14 additions & 0 deletions apps/cli/cmd/workspace/affected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package workspace

import "github.com/spf13/cobra"

var affectedCmd = &cobra.Command{
Use: "affected",
Short: "List projects affected by changes between git refs",
RunE: func(cmd *cobra.Command, args []string) error { return runAffected(cmd) },
}

func init() {
affectedCmd.Flags().String("base", "main", "base ref")
affectedCmd.Flags().Bool("json", false, "output as JSON")
}
24 changes: 24 additions & 0 deletions apps/cli/cmd/workspace/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package workspace

import "github.com/spf13/cobra"

var cacheCmd = &cobra.Command{
Use: "cache",
Short: "Manage the local task cache",
}

var cacheStatusCmd = &cobra.Command{
Use: "status",
Short: "Show cache size and entry count",
RunE: func(cmd *cobra.Command, args []string) error { return runCacheStatus(cmd) },
}

var cacheClearCmd = &cobra.Command{
Use: "clear",
Short: "Remove all cache entries",
RunE: func(cmd *cobra.Command, args []string) error { return runCacheClear(cmd) },
}

func init() {
cacheCmd.AddCommand(cacheStatusCmd, cacheClearCmd)
}
66 changes: 66 additions & 0 deletions apps/cli/cmd/workspace/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package workspace

import (
"fmt"

"github.com/raizora/radas/v4/internal/generator"
"github.com/spf13/cobra"
)

var (
generateOutDir string
generateVars []string
generateForce bool
generateNonInteractive bool
generateTemplateDir string
)

var generateCmd = &cobra.Command{
Use: "generate <template-name>",
Short: "Generate code from a template",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
overrides := make(map[string]string)
for _, v := range generateVars {
parts := splitVar(v)
if parts == nil {
return fmt.Errorf("invalid --var format %q, expected key=value", v)
}
overrides[parts[0]] = parts[1]
}

err := generator.GenerateTemplateWith(generator.GenerateSettings{
TemplateName: args[0],
Overrides: overrides,
OutDir: generateOutDir,
Force: generateForce,
NonInteractive: generateNonInteractive,
TemplateDir: generateTemplateDir,
})
if err != nil {
return err
}

cmd.Println("Generated from template:", args[0])
return nil
},
}

func init() {
Cmd.AddCommand(generateCmd)

generateCmd.Flags().StringVarP(&generateOutDir, "output-dir", "o", ".", "Output directory")
generateCmd.Flags().StringArrayVar(&generateVars, "var", nil, "Template variable (key=value)")
generateCmd.Flags().BoolVarP(&generateForce, "force", "f", false, "Overwrite existing files")
generateCmd.Flags().BoolVar(&generateNonInteractive, "non-interactive", false, "Skip prompts, use defaults")
generateCmd.Flags().StringVarP(&generateTemplateDir, "template-dir", "T", "./templates", "Template directory")
}

func splitVar(v string) []string {
for i := 0; i < len(v); i++ {
if v[i] == '=' {
return []string{v[:i], v[i+1:]}
}
}
return nil
}
16 changes: 16 additions & 0 deletions apps/cli/cmd/workspace/graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package workspace

import "github.com/spf13/cobra"

var graphCmd = &cobra.Command{
Use: "graph",
Short: "Visualize the workspace dependency graph",
RunE: func(cmd *cobra.Command, args []string) error { return runGraph(cmd) },
}

func init() {
graphCmd.Flags().StringP("output", "o", "", "output format: svg|png|json")
graphCmd.Flags().String("file", "", "output file path")
graphCmd.Flags().Bool("web", false, "open interactive browser viewer")
graphCmd.Flags().Bool("ascii", false, "force ASCII output (default)")
}
7 changes: 7 additions & 0 deletions apps/cli/cmd/workspace/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package workspace

import "path/filepath"

func filepathDir(p string) string {
return filepath.Dir(p)
}
9 changes: 9 additions & 0 deletions apps/cli/cmd/workspace/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package workspace

import "github.com/spf13/cobra"

var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize workspace mode in the current directory",
RunE: func(cmd *cobra.Command, args []string) error { return runInit(cmd) },
}
76 changes: 76 additions & 0 deletions apps/cli/cmd/workspace/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package workspace

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)

func TestEndToEndWorkspace(t *testing.T) {
root := t.TempDir()
os.WriteFile(filepath.Join(root, "radas.yml"),
[]byte("name: e2e\nworkspace:\n projects: [apps/*, libs/*]\n"), 0644)
for _, p := range []struct {
path, name, typ, dep string
}{
{"apps/api", "api", "backend-api", "[shared]"},
{"apps/web", "web", "frontend-web", "[shared]"},
{"libs/shared", "shared", "lib", ""},
} {
d := filepath.Join(root, p.path)
os.MkdirAll(d, 0755)
yml := "name: " + p.name + "\ntype: " + p.typ + "\n"
if p.dep != "" {
yml += "workspace:\n depends_on: " + p.dep + "\n"
}
os.WriteFile(filepath.Join(d, "radas.yml"), []byte(yml), 0644)
}
oldDir, _ := os.Getwd()
os.Chdir(root)
defer os.Chdir(oldDir)

// 1. list shows all 3 projects
var buf bytes.Buffer
listCmd.SetOut(&buf)
if err := runList(listCmd); err != nil {
t.Fatal(err)
}
for _, n := range []string{"api", "web", "shared"} {
if !strings.Contains(buf.String(), n) {
t.Errorf("list missing %q: %s", n, buf.String())
}
}

// 2. show displays project details
buf.Reset()
showCmd.SetOut(&buf)
if err := runShow(showCmd, []string{"api"}); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "Dependencies") {
t.Errorf("show missing Dependencies: %s", buf.String())
}

// 3. graph --ascii produces output
buf.Reset()
graphCmd.SetOut(&buf)
graphCmd.SetArgs([]string{"--ascii"})
if err := runGraph(graphCmd); err != nil {
t.Fatal(err)
}
if buf.Len() == 0 {
t.Error("graph produced empty output")
}

// 4. validate reports OK
buf.Reset()
validateCmd.SetOut(&buf)
if err := runValidate(validateCmd); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "OK") {
t.Errorf("validate: %s", buf.String())
}
}
9 changes: 9 additions & 0 deletions apps/cli/cmd/workspace/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package workspace

import "github.com/spf13/cobra"

var listCmd = &cobra.Command{
Use: "list",
Short: "List all projects in the workspace",
RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd) },
}
25 changes: 25 additions & 0 deletions apps/cli/cmd/workspace/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package workspace

import "github.com/spf13/cobra"

var runCmd = &cobra.Command{
Use: "run <task>",
Short: "Run a task with topological scheduling, parallel execution, and cache",
Long: `Run a task across the workspace. Examples:

radas workspace run test --project=api
radas workspace run build --all
radas workspace run test --affected --base=main
radas workspace run build --no-cache`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { return runRun(cmd, args) },
}

func init() {
runCmd.Flags().String("project", "", "run task only in this project")
runCmd.Flags().Bool("all", false, "run task in all projects that have it")
runCmd.Flags().Bool("affected", false, "run task only in projects affected by changes")
runCmd.Flags().String("base", "main", "base ref for affected detection (used with --affected)")
runCmd.Flags().Bool("no-cache", false, "force re-execution, bypass cache")
runCmd.Flags().Int("max-parallel", 4, "max parallel task execution")
}
Loading