Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# ggcode


## Quick Reference

| Item | Value |
|------|-------|
| Module | `github.com/topcheer/ggcode` |
| Go version | 1.26.2 (see `go.mod`) |
| Build tag | **`-tags goolm`** required for ALL `go build` / `go test` |
| Current release | v1.3.188 |
| Platform | Linux / macOS / Windows (amd64 + arm64) |

## Validation Commands

```bash
# CI-equivalent check (what pre-commit hook runs)
make verify-ci

# Quick build
go build -tags goolm -o /tmp/ggcode ./cmd/ggcode

# Run tests (use memory limits on shared/CI machines)
GOMEMLIMIT=2GiB GOGC=50 go test -tags goolm -p 1 -parallel 1 -timeout 600s ./...

# Lint
go vet -tags goolm ./...

# Cross-platform build check
CGO_ENABLED=0 go build -tags goolm ./...
```

**Key rule**: Always use `-tags goolm`. Without it, CGO-only packages (e.g. `go-olm`) will fail to compile.

## Major Directories

| Path | Purpose |
|------|---------|
| `cmd/ggcode/` | Main CLI entrypoint, root command, resume picker |
| `cmd/ggcode-installer/` | Go-based binary installer |
| `internal/agent/` | Agent loop, tool execution, autopilot strategist, compaction |
| `internal/tui/` | Bubble Tea terminal UI, panels, slash commands, i18n |
| `internal/provider/` | LLM provider adapters (OpenAI, Anthropic, Gemini), retry, error formatting |
| `internal/config/` | Config schema, vendor/endpoint resolution, built-in vendor defaults, i18n display names |
| `internal/session/` | JSONL session store, debounce, index, checkpoints |
| `internal/context/` | Context manager, token counting, compaction |
| `internal/tool/` | Built-in tools (file edit, search, run_command, browser, etc.) |
| `internal/im/` | IM adapters (QQ, Telegram, Discord, Slack, Feishu, WeChat, etc.) |
| `internal/permission/` | Permission modes, dangerous command detection |
| `internal/a2a/` | Agent-to-agent protocol, mDNS discovery |
| `internal/mcp/` | MCP server/client integration |
| `internal/debug/` | Debug logging system (category-based ring buffer) |
| `internal/safego/` | Panic recovery for goroutines |
| `internal/util/` | Shell detection, path helpers, common utilities |
| `mobile/flutter/` | Flutter mobile app (iOS + Android) |
| `desktop/ggcode-desktop-wails/` | Desktop app (Wails: Go backend + web frontend) |
| `desktop/ggcode-desktop/` | Legacy desktop builds (no active go.mod) |
| `docs/` | Documentation, architecture notes, release process |

## Architecture

### Provider System
- Config uses `vendor` / `endpoint` / `model` schema (not old `provider/providers`)
- Built-in vendors: ZAI, Anthropic, OpenAI, Google, Kimi, Aliyun, Ark, MiniMax, MiMo, GitHub Copilot, etc.
- `ResolveEndpointSelection()` resolves active config to `ResolvedEndpoint` with display names
- Built-in vendor/endpoint display names are i18n-aware (`vendor_display_i18n.go`)
- Coding plan providers return 429 for both transient limits AND quota exhaustion — `isQuotaExhaustedError()` distinguishes them

### TUI Layout
- When any panel is open, conversation is hidden; panel fills full height
- `renderContextBox` forces full height; `renderContextBoxAuto` for compact elements (status bar)
- Composer/input position stays fixed regardless of panel content height

### Error Handling
- `FriendlyError()` — detailed error classification for retry decisions
- `UserFacingErrorLang()` — user-facing messages with i18n (zh-CN/en)
- Both detect quota exhaustion patterns from all coding plan providers
- Non-streaming `Chat()` paths in all 3 providers have `debug.Log` on errors

## Release Process

**Full playbook**: `docs/release-process.md`

Quick checklist:
1. Create `docs/releases/vX.Y.Z.md`
2. Run `cd mobile/flutter && bash scripts/version_sync.sh X.Y.Z` (bumps 4 files)
3. `make verify-ci`
4. `git commit -m "release: vX.Y.Z"` → push main
5. `git tag vX.Y.Z` → push tag
6. Monitor CI, Release, Mobile Release, CodeQL — all must pass

**Do NOT** push tag before mobile version sync. TestFlight/Google Play reject duplicate build numbers.

## Runtime Modes

| Mode | Behavior |
|------|----------|
| `supervised` | Default; asks confirmation for tool calls |
| `plan` | Read-only exploration only |
| `auto` | Safe operations auto-allowed |
| `bypass` | Almost everything allowed |
| `autopilot` | Bypass + autonomous goal-directed execution |

## Coding Conventions

- **Build tag**: All `go build`/`go test` must use `-tags goolm`
- **Debug logging**: Use `debug.Log(category, format, args...)` — never `log.Printf` for diagnostics
- **Goroutine safety**: Use `safego.Recover("name")` or `safego.Go("name", fn)` for all goroutines
- **Circular imports**: `debug` imports `util`; `util` cannot import `debug` — use injectable callback (`SetDebugLogFn`)
- **Panel rendering**: All full-screen panels use `renderContextBox`; compact elements use `renderContextBoxAuto`
- **Error handling**: Don't swallow errors silently — add `debug.Log` on error paths
- **i18n**: TUI uses `tr(lang, key)` system; register catalogs via `registerCatalog(en, zh)` in `init()`
- **Windows shell**: PowerShell is primary (`-NoProfile -ExecutionPolicy Bypass`); Git Bash is fallback. Detection cached with `sync.Once`. Dangerous PowerShell patterns in `dangerous.go`.
- **Sub-agent model**: `spawn_agent` accepts optional `model` param; inherits parent runtime model if omitted. Sub-agents and swarm teammates always use current runtime provider, not startup-time snapshot.
- **Inline tool call detection**: `hasInlineToolCall` scans first 4KB of model response to detect non-native tool calls (nudge limited to 2 attempts).
- **Pre-commit hook**: Runs `gofmt`, `go vet`, `go build` on staged files

## Testing

```bash
# CI-safe (unit + Tier 1 integration)
go test -tags "goolm,integration" ./cmd/... ./internal/...

# Full suite (needs API key + external services)
go test -tags 'goolm,integration,integration_local,integration_service' ./...
```

Test memory limits: `GOMEMLIMIT=2GiB GOGC=50` on shared/CI machines.
Use `-p 1 -parallel 1` to avoid OOM on large packages.
130 changes: 2 additions & 128 deletions GGCODE.md
Original file line number Diff line number Diff line change
@@ -1,129 +1,3 @@
# ggcode
# ggcode project memory


## Quick Reference

| Item | Value |
|------|-------|
| Module | `github.com/topcheer/ggcode` |
| Go version | 1.26.2 (see `go.mod`) |
| Build tag | **`-tags goolm`** required for ALL `go build` / `go test` |
| Current release | v1.3.188 |
| Platform | Linux / macOS / Windows (amd64 + arm64) |

## Validation Commands

```bash
# CI-equivalent check (what pre-commit hook runs)
make verify-ci

# Quick build
go build -tags goolm -o /tmp/ggcode ./cmd/ggcode

# Run tests (use memory limits on shared/CI machines)
GOMEMLIMIT=2GiB GOGC=50 go test -tags goolm -p 1 -parallel 1 -timeout 600s ./...

# Lint
go vet -tags goolm ./...

# Cross-platform build check
CGO_ENABLED=0 go build -tags goolm ./...
```

**Key rule**: Always use `-tags goolm`. Without it, CGO-only packages (e.g. `go-olm`) will fail to compile.

## Major Directories

| Path | Purpose |
|------|---------|
| `cmd/ggcode/` | Main CLI entrypoint, root command, resume picker |
| `cmd/ggcode-installer/` | Go-based binary installer |
| `internal/agent/` | Agent loop, tool execution, autopilot strategist, compaction |
| `internal/tui/` | Bubble Tea terminal UI, panels, slash commands, i18n |
| `internal/provider/` | LLM provider adapters (OpenAI, Anthropic, Gemini), retry, error formatting |
| `internal/config/` | Config schema, vendor/endpoint resolution, built-in vendor defaults, i18n display names |
| `internal/session/` | JSONL session store, debounce, index, checkpoints |
| `internal/context/` | Context manager, token counting, compaction |
| `internal/tool/` | Built-in tools (file edit, search, run_command, browser, etc.) |
| `internal/im/` | IM adapters (QQ, Telegram, Discord, Slack, Feishu, WeChat, etc.) |
| `internal/permission/` | Permission modes, dangerous command detection |
| `internal/a2a/` | Agent-to-agent protocol, mDNS discovery |
| `internal/mcp/` | MCP server/client integration |
| `internal/debug/` | Debug logging system (category-based ring buffer) |
| `internal/safego/` | Panic recovery for goroutines |
| `internal/util/` | Shell detection, path helpers, common utilities |
| `mobile/flutter/` | Flutter mobile app (iOS + Android) |
| `desktop/ggcode-desktop-wails/` | Desktop app (Wails: Go backend + web frontend) |
| `desktop/ggcode-desktop/` | Legacy desktop builds (no active go.mod) |
| `docs/` | Documentation, architecture notes, release process |

## Architecture

### Provider System
- Config uses `vendor` / `endpoint` / `model` schema (not old `provider/providers`)
- Built-in vendors: ZAI, Anthropic, OpenAI, Google, Kimi, Aliyun, Ark, MiniMax, MiMo, GitHub Copilot, etc.
- `ResolveEndpointSelection()` resolves active config to `ResolvedEndpoint` with display names
- Built-in vendor/endpoint display names are i18n-aware (`vendor_display_i18n.go`)
- Coding plan providers return 429 for both transient limits AND quota exhaustion — `isQuotaExhaustedError()` distinguishes them

### TUI Layout
- When any panel is open, conversation is hidden; panel fills full height
- `renderContextBox` forces full height; `renderContextBoxAuto` for compact elements (status bar)
- Composer/input position stays fixed regardless of panel content height

### Error Handling
- `FriendlyError()` — detailed error classification for retry decisions
- `UserFacingErrorLang()` — user-facing messages with i18n (zh-CN/en)
- Both detect quota exhaustion patterns from all coding plan providers
- Non-streaming `Chat()` paths in all 3 providers have `debug.Log` on errors

## Release Process

**Full playbook**: `docs/release-process.md`

Quick checklist:
1. Create `docs/releases/vX.Y.Z.md`
2. Run `cd mobile/flutter && bash scripts/version_sync.sh X.Y.Z` (bumps 4 files)
3. `make verify-ci`
4. `git commit -m "release: vX.Y.Z"` → push main
5. `git tag vX.Y.Z` → push tag
6. Monitor CI, Release, Mobile Release, CodeQL — all must pass

**Do NOT** push tag before mobile version sync. TestFlight/Google Play reject duplicate build numbers.

## Runtime Modes

| Mode | Behavior |
|------|----------|
| `supervised` | Default; asks confirmation for tool calls |
| `plan` | Read-only exploration only |
| `auto` | Safe operations auto-allowed |
| `bypass` | Almost everything allowed |
| `autopilot` | Bypass + autonomous goal-directed execution |

## Coding Conventions

- **Build tag**: All `go build`/`go test` must use `-tags goolm`
- **Debug logging**: Use `debug.Log(category, format, args...)` — never `log.Printf` for diagnostics
- **Goroutine safety**: Use `safego.Recover("name")` or `safego.Go("name", fn)` for all goroutines
- **Circular imports**: `debug` imports `util`; `util` cannot import `debug` — use injectable callback (`SetDebugLogFn`)
- **Panel rendering**: All full-screen panels use `renderContextBox`; compact elements use `renderContextBoxAuto`
- **Error handling**: Don't swallow errors silently — add `debug.Log` on error paths
- **i18n**: TUI uses `tr(lang, key)` system; register catalogs via `registerCatalog(en, zh)` in `init()`
- **Windows shell**: PowerShell is primary (`-NoProfile -ExecutionPolicy Bypass`); Git Bash is fallback. Detection cached with `sync.Once`. Dangerous PowerShell patterns in `dangerous.go`.
- **Sub-agent model**: `spawn_agent` accepts optional `model` param; inherits parent runtime model if omitted. Sub-agents and swarm teammates always use current runtime provider, not startup-time snapshot.
- **Inline tool call detection**: `hasInlineToolCall` scans first 4KB of model response to detect non-native tool calls (nudge limited to 2 attempts).
- **Pre-commit hook**: Runs `gofmt`, `go vet`, `go build` on staged files

## Testing

```bash
# CI-safe (unit + Tier 1 integration)
go test -tags "goolm,integration" ./cmd/... ./internal/...

# Full suite (needs API key + external services)
go test -tags 'goolm,integration,integration_local,integration_service' ./...
```

Test memory limits: `GOMEMLIMIT=2GiB GOGC=50` on shared/CI machines.
Use `-p 1 -parallel 1` to avoid OOM on large packages.
This repository's agent instructions live in [AGENTS.md](AGENTS.md) — the cross-CLI standard adopted by most coding agents. This stub keeps legacy `GGCODE.md` lookups working; do not duplicate content here.
13 changes: 8 additions & 5 deletions internal/memory/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ var projectPathHints = []projectPathHint{
{Path: "python", Description: "Python wrapper that installs the GitHub Release binary"},
}

// GenerateProjectMemory builds a GGCODE.md document using the current repo state.
// GenerateProjectMemory builds the AGENTS.md starter document from
// heuristically detected repo facts. /init presents it to the agent as a
// hint block - the agent explores the repository first and writes the final
// AGENTS.md from what it actually found (see handleInitCommand).
func GenerateProjectMemory(root string) (string, error) {
absRoot, err := filepath.Abs(root)
if err != nil {
Expand All @@ -46,7 +49,7 @@ func GenerateProjectMemory(root string) (string, error) {
conventions := detectConventions(absRoot)

var b strings.Builder
b.WriteString("# GGCODE.md\n\n")
b.WriteString("# AGENTS.md\n\n")
b.WriteString("> Generated by `/init` from the current repository snapshot. Review and edit when project conventions change.\n\n")

b.WriteString("## Project Snapshot\n")
Expand Down Expand Up @@ -118,7 +121,7 @@ func detectReadmeSummary(root string) string {
// #1615: centered HTML banners (<p align=center>, <img>, </p>)
// are the first "paragraph" in many READMEs (this repo's own) -
// the tag soup became the project Summary injected into every
// session's GGCODE.md. Skip HTML-tag lines and empty-out
// session's AGENTS.md. Skip HTML-tag lines and empty-out
// tag-only paragraphs so the first REAL text paragraph wins.
if strings.HasPrefix(line, "<") {
continue
Expand Down Expand Up @@ -149,7 +152,7 @@ func detectTechStack(root string) []string {
// #1636-A: same gate as detectImportantPaths (#1593-A) - the specific
// stack claims below (npm release wrapper, plugin-system phrases) are
// ggcode's OWN facts; a user repo with python/ or an MCP-mentioning
// README got them written into GGCODE.md as MUST-follow context.
// README got them written into AGENTS.md as MUST-follow context.
// Correct silence beats wrong facts; foreign repos keep only the
// generic Go-codebase line.
module := detectGoModule(root)
Expand Down Expand Up @@ -186,7 +189,7 @@ func detectImportantPaths(root string) []string {
// #1593-A: the hints describe THIS repository's layout. Almost every
// standard Go repo has internal/agent, internal/tool, docs/, python/
// namesakes - emitting these descriptions for a user's own repo wrote
// factually WRONG entries into GGCODE.md, which is injected into every
// factually WRONG entries into AGENTS.md, which is injected into every
// subsequent session ("MUST follow"). Gate on the module name; foreign
// repos get no path claims at all (correct silence beats wrong facts).
module := detectGoModule(root)
Expand Down
2 changes: 1 addition & 1 deletion internal/memory/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var CompatibilitySubdirRules = []string{
".github/copilot-instructions.md",
}

const DefaultProjectMemoryFilename = "GGCODE.md"
const DefaultProjectMemoryFilename = "AGENTS.md"

// LoadProjectMemory reads supported project bootstrap documents from the
// global config dir (~/.ggcode/) and the current working directory only.
Expand Down
2 changes: 1 addition & 1 deletion internal/memory/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestResolveProjectMemoryInitTarget_CurrentDirOnly(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
// Should target the current working dir, not walk up to git root
if target != filepath.Join(subDir, "GGCODE.md") {
if target != filepath.Join(subDir, "AGENTS.md") {
t.Fatalf("expected current-dir target, got %q", target)
}
if len(existing) != 0 {
Expand Down
29 changes: 24 additions & 5 deletions internal/tui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,13 +691,32 @@ func (m *Model) handleInitCommand() tea.Cmd {
m.chatWriteSystem(nextSystemID(), m.t("init.generate_failed", err))
return nil
}
// Build init prompt directly
var prompt string
// Exploration-first init prompt (#AGENTS.md standard): the agent must
// understand the repository BEFORE writing the memory file, instead of
// dumping the heuristic snapshot verbatim. The detected facts below are
// hints to verify, not the final content.
verb := "Create"
if existed {
prompt = fmt.Sprintf("Update project memory file at %s with the following content:\n\n%s", targetPath, content)
} else {
prompt = fmt.Sprintf("Create project memory file at %s with the following content:\n\n%s", targetPath, content)
verb = "Update"
}
prompt := fmt.Sprintf(`%s the project memory file at %s (AGENTS.md - the cross-CLI agent instructions standard).

FIRST understand this repository - do NOT write the file from a generic template:
1. Read the README and docs/ overview: what does this application do?
2. Read build manifests (go.mod / package.json / Cargo.toml / ...) and map entrypoints (cmd/, main.*, src/) to major directories and their responsibilities.
3. Read Makefile / CI workflows (.github/workflows, ...) to learn the REAL build, test, and lint commands.
4. Skim a few representative source and test files to infer conventions (logging, error handling, i18n, testing style).

THEN %s AGENTS.md with durable guidance for coding agents:
- Project snapshot: what the app is, module path, stack
- Validation: exact build/test/lint commands as verified in CI/Makefile
- Architecture: major directories and what lives where
- Coding conventions: rules this repo actually follows
- Concise (~100-150 lines), durable guidance only - no one-off task plans

Heuristically detected hints (VERIFY each against the repository; discard anything wrong or stale):

%s`, verb, targetPath, verb, content)

m.chatWriteUser(nextChatID(), "/init")
m.appendUserMessage("/init")
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ var SlashCommandDescriptions = map[string]string{
"/chat": "Open LAN chat panel",
"/nick": "Set LAN chat nickname, role, and team",
"/image": "Attach an image",
"/init": "Create GGCODE.md",
"/init": "Create AGENTS.md",
"/exit": "Exit ggcode",
"/quit": "Exit ggcode",
"/compact": "Compress conversation history",
Expand Down
Loading
Loading