Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
78fb1c8
review-dev
yanmxa Apr 15, 2026
5e6fc4d
round 1
yanmxa Apr 16, 2026
941cbca
refactor: restructure internal packages and consolidate runtime loop
yanmxa Apr 16, 2026
b85a326
refactor: update the arch
yanmxa Apr 16, 2026
0826da9
docs: update architecture diagram
yanmxa Apr 16, 2026
cc89e57
docs: expand architecture documentation
yanmxa Apr 16, 2026
f3eaf53
refactor: rename provider to llm, hooks to hook, add system/agentinpu…
yanmxa Apr 16, 2026
f61d862
refactor: extract agent, user, and output into dedicated sub-packages
yanmxa Apr 16, 2026
787216c
refactor: move all UI components under internal/app/ui and rename han…
yanmxa Apr 16, 2026
64b9a6e
refactor: relocate theme/selector packages and update imports across …
yanmxa Apr 16, 2026
f375ffa
refactor: inline RenderTrackerList and remove agent/view wrapper
yanmxa Apr 16, 2026
43a5194
refactor: restructure internal packages for clearer boundaries
yanmxa Apr 16, 2026
873127c
refactor: move update handlers back to app package and update imports
yanmxa Apr 16, 2026
34a43ea
refactor: embed Model in agentui.State and searchui.State to eliminat…
yanmxa Apr 16, 2026
d25fb23
refactor: extract provider domain state from providerui.State to app …
yanmxa Apr 16, 2026
163cab6
refactor: clean up app/user package boundaries and remove dead code
yanmxa Apr 16, 2026
b3cca1e
refactor: fix mcpui split-brain and migrate editor to kit package
yanmxa Apr 16, 2026
b93aa61
refactor: remove dead code and fix package doc
yanmxa Apr 16, 2026
c3ea0ca
refactor: extract domain state from overlay packages to app model
yanmxa Apr 16, 2026
7f6755a
Merge branch 'review-refactor' into refactor-main
yanmxa Apr 16, 2026
5f2dcdc
refactor: inject Registry into mcpui and pluginui, eliminating global…
yanmxa Apr 16, 2026
29bca43
refactor: fix layer violations, eliminate lateral deps, and extract d…
yanmxa Apr 16, 2026
8fb8299
refactor: relocate mode/history/suggest packages and extract plan dom…
yanmxa Apr 16, 2026
69df8f6
refactor: convert approval to value type, extract agent config and pe…
yanmxa Apr 16, 2026
ed6e326
refactor: extract ListNav and SaveLevel to kit, DRY up selector navig…
yanmxa Apr 16, 2026
54ae777
refactor: adopt kit.ListNav in toolui, mcpui, and sessionui selectors
yanmxa Apr 16, 2026
e1526ea
refactor: move pendingPermBridge into agentSession struct
yanmxa Apr 16, 2026
09ffa18
refactor: split output/toolui into user/toolui (selector) and output/…
yanmxa Apr 16, 2026
9006af5
Revert "refactor: split output/toolui into user/toolui (selector) and…
yanmxa Apr 16, 2026
1ce3534
refactor: encapsulate user.Model, inline queue, extract searchui runt…
yanmxa Apr 16, 2026
e556a7a
refactor: flatten package hierarchy and consolidate file naming
yanmxa Apr 16, 2026
46f2659
refactor: consolidate output/session handling, extract modal and life…
yanmxa Apr 16, 2026
213a4b1
refactor: extract hooks, output bridge, and run init/print helpers
yanmxa Apr 16, 2026
40f96b3
refactor: simplify run.go and clean up init.go
yanmxa Apr 16, 2026
f55ce37
refactor: remove dead code from model.go
yanmxa Apr 16, 2026
bad4e2e
refactor: consolidate app files and simplify naming
yanmxa Apr 17, 2026
3c759df
refactor: rename config to setting, move modal to output, simplify hooks
yanmxa Apr 17, 2026
b493b2d
docs: update architecture diagram and add user/on_agent.go
yanmxa Apr 17, 2026
7075677
refactor: move system to core/, inline searchui/skillui/agentui into …
yanmxa Apr 17, 2026
bf8cb35
refactor: inline memory and sessionui packages into user handlers
yanmxa Apr 17, 2026
297b831
refactor: inline approval, mcpui, and providerui into user on_* handlers
yanmxa Apr 17, 2026
60f9526
refactor: inline pluginui and providerui into user on_* handlers
yanmxa Apr 17, 2026
a2ff7d3
refactor: rename sub-package files to on_* convention, consolidate mo…
yanmxa Apr 17, 2026
8affb06
refactor: inline compact, conversation, and modal into output on_* ha…
yanmxa Apr 17, 2026
2c28d5e
refactor: remove output/toolui package, inline into on_tool handlers
yanmxa Apr 17, 2026
734ed08
refactor: inline output/render into output on_* files, update hooks
yanmxa Apr 17, 2026
34a4d94
docs: rename hooks.md to hook.md
yanmxa Apr 17, 2026
506f678
docs: improve hook.md content and formatting
yanmxa Apr 17, 2026
b7f502c
docs: expand hook.md with additional examples and details
yanmxa Apr 17, 2026
149faf1
docs: fix typo in architecture.md
yanmxa Apr 17, 2026
a7dd9e7
refactor: move runtime loop into app/runtime, delete top-level runtim…
yanmxa Apr 17, 2026
ecfd333
refactor: move tool exec to app/, consolidate LLM types, expand hook …
yanmxa Apr 17, 2026
8c849d7
refactor: extract compact handler to app/, simplify MCP registry and …
yanmxa Apr 17, 2026
4544b91
refactor: remove core/hook, simplify hook engine, add token limits an…
yanmxa Apr 17, 2026
b447ada
refactor: simplify bridges and mode, expand runtime model and docs
yanmxa Apr 17, 2026
1dcb6c9
refactor: extract permission types, remove core/event, expand core/agent
yanmxa Apr 17, 2026
29d79cd
refactor: consolidate output render files and user UI render files
yanmxa Apr 17, 2026
fdfe157
refactor: remove internal/permission, add tool/perm/decision_test.go
yanmxa Apr 17, 2026
1ed5d95
refactor: restructure app layer, extract plugin/provider views, simpl…
yanmxa Apr 17, 2026
66a075b
foratm
yanmxa Apr 17, 2026
5215e27
update
yanmxa Apr 17, 2026
d4a0885
update
yanmxa Apr 17, 2026
c240cf3
refactor 1
yanmxa Apr 17, 2026
091e05d
update2
yanmxa Apr 17, 2026
a314065
update
yanmxa Apr 17, 2026
e6885a8
refactor: add conv/stream and tool render, extract session bridge and…
yanmxa Apr 17, 2026
4b2ac5b
refactor: extract submit/suggestion to input, session ops to runtime
yanmxa Apr 17, 2026
658f01e
refactor: extract bridge layers and input controllers
yanmxa Apr 17, 2026
3246fbe
refactor: merge notify/trigger bridges, simplify submit and output ef…
yanmxa Apr 18, 2026
37c5276
refactor: consolidate dispatch, remove approval/command/submit, add l…
yanmxa Apr 18, 2026
dec6e3c
update
yanmxa Apr 18, 2026
147122d
udpate
yanmxa Apr 18, 2026
d22377e
update
yanmxa Apr 18, 2026
e066840
update
yanmxa Apr 18, 2026
6f7c5dc
update3
yanmxa Apr 18, 2026
7114765
refactor: move singletons out of Env, access them directly
yanmxa Apr 18, 2026
faf768c
desgin parttern
yanmxa Apr 18, 2026
a1ec9d4
update
yanmxa Apr 18, 2026
7ba126d
update2
yanmxa Apr 18, 2026
ea24262
refactor: inject Services struct into model, eliminate Env singleton …
yanmxa Apr 18, 2026
244c668
update2
yanmxa Apr 18, 2026
bdbad09
update3
yanmxa Apr 19, 2026
19cd4f6
update
yanmxa Apr 19, 2026
c24a199
update3
yanmxa Apr 19, 2026
f2b7660
update
yanmxa Apr 19, 2026
d55f44d
update3
yanmxa Apr 19, 2026
e13ab25
update3
yanmxa Apr 19, 2026
1ad5e3f
update3
yanmxa Apr 19, 2026
5429cf9
update3
yanmxa Apr 19, 2026
6751196
udpate resume
yanmxa Apr 19, 2026
4a4674e
refactor: rename command_controller to slash_command, use allowlist f…
yanmxa Apr 19, 2026
b862019
fix
yanmxa Apr 19, 2026
200d288
refactor: optimize system prompt — conditional guidelines, semantic e…
yanmxa Apr 19, 2026
8013db3
refactor: trim system prompt content and add IsSubagent conditional g…
yanmxa Apr 19, 2026
b6e782b
refactor: simplify system prompt layers — merge generic into base, co…
yanmxa Apr 19, 2026
2c8d0cc
fix
yanmxa Apr 19, 2026
9cea17f
refactor: remove Summary from session layer, simplify compact and sys…
yanmxa Apr 19, 2026
c4a37c2
refactor: optimize system prompts and compact flow
yanmxa Apr 19, 2026
1ac2d81
fix
yanmxa Apr 19, 2026
326676a
fix
yanmxa Apr 19, 2026
16aaa63
update
yanmxa Apr 20, 2026
c8afb77
push
yanmxa Apr 20, 2026
c1e0f01
push
yanmxa Apr 20, 2026
3d71e36
push
yanmxa Apr 21, 2026
812395b
benchemark
yanmxa Apr 21, 2026
8efaefb
Improve token display, simplify agent continuation, and add extension…
yanmxa Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ gen_*
# OS
.DS_Store

# Dependencies
vendor/

# Local config
.gen/*
!.gen/skills/
.claude/
.mcp.json
CLAUDE.md
CLAUDE.local.md
.autodev/
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,16 @@ GenCode stores configuration in `~/.gen/`:

## 📊 Benchmark: GenCode vs Claude Code

Compared with [Claude Code](https://claude.ai/code) v2.1.96 on Apple Silicon, same model (`claude-sonnet-4-20250514`):
Compared with [Claude Code](https://claude.ai/code) v2.1.112 on Apple Silicon, same model (`claude-sonnet-4-6`):

| Metric | GenCode | Claude Code | Advantage |
|--------|---------|-------------|-----------|
| Download size | 12 MB | 62 MB (+ Node.js 112 MB) | **5x smaller** |
| Disk footprint | 38 MB | 174 MB | **4.6x smaller** |
| Startup time | ~0.02s | ~0.18s | **9x faster** |
| Startup memory | ~33 MB | ~185 MB | **5.6x less** |
| Simple task | ~5.2s / 39 MB | ~11.9s / 282 MB | **2.3x faster, 7.2x less memory** |
| Tool-use task | ~3.6s / 40 MB | ~14.6s / 281 MB | **4.1x faster, 7.1x less memory** |
| Download size | 12 MB | 63 MB (+ Node.js 112 MB) | **5x smaller** |
| Disk footprint | 38 MB | 175 MB | **4.6x smaller** |
| Startup time | ~0.01s | ~0.20s | **20x faster** |
| Startup memory | ~32 MB | ~189 MB | **5.8x less** |
| Simple task | ~2.4s / 39 MB | ~10.4s / 286 MB | **4.3x faster, 7.3x less memory** |
| Tool-use task | ~3.3s / 39 MB | ~26.0s / 285 MB | **7.9x faster, 7.2x less memory** |

Both tools have comparable features (hooks, skills, plugins, session, MCP, etc.). The performance gap comes from Go's native compilation vs Node.js V8/JIT/GC runtime overhead.

Expand Down
96 changes: 36 additions & 60 deletions cmd/gen/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import (

"github.com/spf13/cobra"

"github.com/yanmxa/gencode/internal/agent"
"github.com/yanmxa/gencode/internal/client"
"github.com/yanmxa/gencode/internal/config"
"github.com/yanmxa/gencode/internal/runtime"
"github.com/yanmxa/gencode/internal/message"
"github.com/yanmxa/gencode/internal/provider"
"github.com/yanmxa/gencode/internal/system"
"github.com/yanmxa/gencode/internal/core"
"github.com/yanmxa/gencode/internal/core/system"
"github.com/yanmxa/gencode/internal/llm"
"github.com/yanmxa/gencode/internal/setting"
"github.com/yanmxa/gencode/internal/subagent"
"github.com/yanmxa/gencode/internal/tool"
)

Expand Down Expand Up @@ -75,16 +73,16 @@ func runHeadlessAgent() error {
}()

// Initialize provider
store, _ := provider.NewStore()
store, _ := llm.NewStore()
if store == nil {
return fmt.Errorf("no provider store available")
}

currentModel := store.GetCurrentModel()
var llmProvider provider.LLMProvider
var llmProvider llm.Provider

if currentModel != nil {
p, err := provider.GetProvider(ctx, currentModel.Provider, currentModel.AuthMethod)
p, err := llm.GetProvider(ctx, currentModel.Provider, currentModel.AuthMethod)
if err != nil {
return fmt.Errorf("failed to connect provider: %w", err)
}
Expand All @@ -103,12 +101,12 @@ func runHeadlessAgent() error {
}

// Initialize agent registry
if err := agent.Initialize(cwd); err != nil {
if err := subagent.Initialize(subagent.Options{CWD: cwd}); err != nil {
return fmt.Errorf("failed to initialize agent registry: %w", err)
}

// Get agent configuration
agentCfg, ok := agent.DefaultRegistry.Get(agentRunOpts.agentType)
agentCfg, ok := subagent.Default().Get(agentRunOpts.agentType)
if !ok {
return fmt.Errorf("unknown agent type: %s", agentRunOpts.agentType)
}
Expand All @@ -119,68 +117,46 @@ func runHeadlessAgent() error {
toolSet.Allow = []string(agentCfg.Tools)
}

// Set up the loop
sys := &system.System{
sys := system.Build(system.Config{
Cwd: cwd,
IsGit: config.IsGitRepo(cwd),
}

loop := &runtime.Loop{
Client: &client.Client{
Provider: llmProvider,
Model: modelID,
MaxTokens: 16384,
},
System: sys,
Tool: toolSet,
}
IsGit: setting.IsGitRepo(cwd),
})

// Add user prompt
loop.AddUser(agentRunOpts.prompt, nil)
client := llm.NewClient(llmProvider, modelID, 16384)

// Print status
fmt.Printf("Agent: %s\n", agentRunOpts.agentType)
fmt.Printf("Prompt: %s\n", agentRunOpts.prompt)
fmt.Println("---")
schemas := toolSet.Tools()
tools := tool.AdaptToolRegistry(schemas, func() string { return cwd })

// Run turns
maxTurns := agentRunOpts.maxTurns
if maxTurns <= 0 {
maxTurns = 50
}

totalTurns := 0
for totalTurns < maxTurns {
if ctx.Err() != nil {
break
}
ag := core.NewAgent(core.Config{
LLM: client,
System: sys,
Tools: tools,
CWD: cwd,
MaxTurns: maxTurns,
OutboxBuf: -1,
})

// Run one turn
result, err := loop.Run(ctx, runtime.RunOptions{
MaxTurns: 1,
OnResponse: func(resp *message.CompletionResponse) {
if resp.Content != "" {
fmt.Println(resp.Content)
}
},
OnToolStart: func(tc message.ToolCall) bool {
fmt.Printf("[%s] %s\n", tc.Name, tc.ID)
return true
},
})
totalTurns++
ag.Append(ctx, core.UserMessage(agentRunOpts.prompt, nil))

if err != nil {
return fmt.Errorf("agent failed: %w", err)
}
fmt.Printf("Agent: %s\n", agentRunOpts.agentType)
fmt.Printf("Prompt: %s\n", agentRunOpts.prompt)
fmt.Println("---")

// Check for end_turn (agent completed its work)
if result.StopReason == "end_turn" {
break
}
result, err := ag.ThinkAct(ctx)
if err != nil {
return fmt.Errorf("agent failed: %w", err)
}

if result.Content != "" {
fmt.Println(result.Content)
}

fmt.Printf("\n---\nDone: %d turns\n", totalTurns)
fmt.Printf("\n---\nDone: %d turns, %d tool uses\n", result.Turns, result.ToolUses)
return nil
}

Expand Down
35 changes: 12 additions & 23 deletions cmd/gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,42 @@ import (

"github.com/yanmxa/gencode/internal/app"
"github.com/yanmxa/gencode/internal/log"
"github.com/yanmxa/gencode/internal/config"
"github.com/yanmxa/gencode/internal/setting"
"github.com/yanmxa/gencode/internal/session"

// Import providers for registration
_ "github.com/yanmxa/gencode/internal/provider/alibaba"
_ "github.com/yanmxa/gencode/internal/provider/anthropic"
_ "github.com/yanmxa/gencode/internal/provider/google"
_ "github.com/yanmxa/gencode/internal/provider/moonshot"
_ "github.com/yanmxa/gencode/internal/provider/openai"
_ "github.com/yanmxa/gencode/internal/llm/alibaba"
_ "github.com/yanmxa/gencode/internal/llm/anthropic"
_ "github.com/yanmxa/gencode/internal/llm/google"
_ "github.com/yanmxa/gencode/internal/llm/moonshot"
_ "github.com/yanmxa/gencode/internal/llm/openai"
)

var version = "1.13.2"

// cliOpts holds all CLI flag values in one place.
var cliOpts struct {
print string // -p/--print: non-interactive print mode
plan bool
cont bool // --continue
resume bool // --resume
cont bool // --continue
resume bool // --resume

fork bool // --fork: fork from continued/resumed session
pluginDir string
}

func init() {
// Load .env file if it exists (silent fail if not found)
_ = godotenv.Load()

// Initialize logging (enabled via GEN_DEBUG=1)
_ = log.Init()

// Set app version for session entries.
session.AppVersion = version
session.SetAppVersion(version)

// Register flags
rootCmd.Flags().StringVarP(&cliOpts.print, "print", "p", "", "Non-interactive print mode with prompt")
rootCmd.Flags().BoolVar(&cliOpts.plan, "plan", false, "Enter plan mode")
rootCmd.Flags().BoolVarP(&cliOpts.cont, "continue", "c", false, "Resume the most recent session")
rootCmd.Flags().BoolVarP(&cliOpts.resume, "resume", "r", false, "Select and resume a previous session")
rootCmd.Flags().BoolVar(&cliOpts.fork, "fork", false, "Fork from the continued/resumed session into a new session")
rootCmd.Flags().StringVar(&cliOpts.pluginDir, "plugin-dir", "", "Load plugins from a specific directory")
rootCmd.PersistentFlags().StringVar(&cliOpts.pluginDir, "plugin-dir", "", "Load plugins from a specific directory")

// Register subcommands
rootCmd.AddCommand(versionCmd)
Expand Down Expand Up @@ -95,17 +90,15 @@ Non-interactive mode:

prompt := strings.Join(args, " ")

opts := config.RunOptions{
opts := setting.RunOptions{
Print: printPrompt,
Prompt: prompt,
PluginDir: cliOpts.pluginDir,
PlanMode: cliOpts.plan,
Continue: cliOpts.cont,
Resume: cliOpts.resume,
ResumeID: resumeID,
Fork: cliOpts.fork,
}
if err := app.RunWithOptions(opts); err != nil {
if err := app.Run(opts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
Expand Down Expand Up @@ -159,14 +152,11 @@ Print Mode (non-interactive):
Interactive Mode:
gen Start chat
gen "Explain this code" Start chat with initial prompt
gen --plan "design login" Start in plan mode

Session:
gen -c, --continue Resume the most recent session
gen -r, --resume Select and resume a previous session
gen -r <session-id> Resume a specific session by ID
gen -c --fork Fork the most recent session into a new one
gen -r --fork Select a session and fork it
gen --plugin-dir <path> Load plugins from a specific directory

Commands:
Expand All @@ -192,7 +182,6 @@ Examples:
gen Start interactive chat
gen "Explain this code" Interactive with initial prompt
gen -p "Explain this code" Print response and exit
gen --plan "design login" Plan mode
gen -c Resume previous session
gen version Show version

Expand Down
43 changes: 9 additions & 34 deletions cmd/gen/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import (
"strings"

"github.com/spf13/cobra"
"github.com/yanmxa/gencode/internal/app/mcpui"
appmemory "github.com/yanmxa/gencode/internal/app/memory"
"github.com/yanmxa/gencode/internal/app/kit"
"github.com/yanmxa/gencode/internal/mcp"
)

Expand Down Expand Up @@ -102,18 +101,18 @@ Examples:
return fmt.Errorf("%s transport requires a URL: gen mcp add --transport %s <name> <url>", mcpTransport, mcpTransport)
}
config.URL = args[1]
config.Headers = parseKeyValues(mcpHeaders, ":")
config.Headers = mcp.ParseKeyValues(mcpHeaders, ":")

default:
return fmt.Errorf("unsupported transport type: %s", mcpTransport)
}

config.Env = parseKeyValues(mcpEnvVars, "=")
config.Env = mcp.ParseKeyValues(mcpEnvVars, "=")

// Save configuration
cwd, _ := os.Getwd()
loader := mcp.NewConfigLoader(cwd)
scope := parseScope(mcpScope)
scope := mcp.ParseScope(mcpScope)

if err := loader.SaveServer(name, config, scope); err != nil {
return fmt.Errorf("failed to save server: %w", err)
Expand Down Expand Up @@ -143,7 +142,7 @@ Example:

cwd, _ := os.Getwd()
loader := mcp.NewConfigLoader(cwd)
scope := parseScope(mcpScope)
scope := mcp.ParseScope(mcpScope)

if err := loader.SaveServer(name, config, scope); err != nil {
return fmt.Errorf("failed to save server: %w", err)
Expand All @@ -162,16 +161,16 @@ var mcpEditCmd = &cobra.Command{
name := args[0]
cwd, _ := os.Getwd()

if err := mcp.Initialize(cwd); err != nil {
if err := mcp.Initialize(mcp.Options{CWD: cwd}); err != nil {
return fmt.Errorf("failed to load MCP configs: %w", err)
}

info, err := mcpui.PrepareServerEdit(name)
info, err := mcp.Default().EditConfig(name)
if err != nil {
return err
}

editor := appmemory.GetEditor()
editor := kit.GetEditor()
editorCmd := exec.Command(editor, info.TempFile)
editorCmd.Stdin = os.Stdin
editorCmd.Stdout = os.Stdout
Expand All @@ -182,7 +181,7 @@ var mcpEditCmd = &cobra.Command{
return fmt.Errorf("editor failed: %w", err)
}

if err := mcpui.ApplyServerEdit(info); err != nil {
if err := mcp.Default().SaveConfig(info); err != nil {
return err
}

Expand Down Expand Up @@ -280,27 +279,3 @@ var mcpRemoveCmd = &cobra.Command{
},
}

func parseScope(s string) mcp.Scope {
switch strings.ToLower(s) {
case "user", "global":
return mcp.ScopeUser
case "project":
return mcp.ScopeProject
default:
return mcp.ScopeLocal
}
}

// parseKeyValues parses a slice of "key=value" or "key:value" strings into a map
func parseKeyValues(items []string, sep string) map[string]string {
if len(items) == 0 {
return nil
}
result := make(map[string]string, len(items))
for _, item := range items {
if key, value, ok := strings.Cut(item, sep); ok {
result[strings.TrimSpace(key)] = strings.TrimSpace(value)
}
}
return result
}
Loading
Loading