Skip to content

Add knowledge base for coding agents#2889

Open
andre15silva wants to merge 1 commit intoantinomyhq:mainfrom
andre15silva:codeset
Open

Add knowledge base for coding agents#2889
andre15silva wants to merge 1 commit intoantinomyhq:mainfrom
andre15silva:codeset

Conversation

@andre15silva
Copy link
Copy Markdown

What does this PR do?

If you use or plan to use AI coding agents on this repo, this PR will make them noticeably more effective.

The core contribution is the knowledge base — files under .claude/docs/ and .codex/docs/ that capture repo-specific knowledge extracted from the codebase and its history:

  • Historical insights: past bugs, their root causes, and how they were fixed
  • Edit checklists: which tests to run, which constants to check before modifying a file
  • Pitfalls: known failure modes, their consequences, and how to avoid them
  • Co-change relationships: files that historically need to be updated together

When an agent opens a file, it calls .claude/docs/get_context.py <file> to retrieve the relevant context before making changes. The AGENTS.md and CLAUDE.md files are entry points that instruct agents to do this.

For example, before touching crates/forge_main/src/ui.rs, an agent would immediately know:

# crates/forge_main/src/ui.rs

### Description
This file contains the terminal UI layer (struct UI) used by the forge CLI/TUI. It orchestrates interactive sessions: building prompts, driving the event loop, handling subcommands, invoking API methods, rendering output via MarkdownFormat and SharedSpinner, and running background hydration tasks. The UI also contains helper formatting functions (e.g., format_mcp_server and format_mcp_headers) and many of the high-level CLI command handlers that present results or interactive pickers.

The UI is designed around an API factory closure (new_api in the struct) that allows the UI to re-create an API instance with a freshly read ForgeConfig. This enables the '/new' command to reflect config changes without restarting the process. The run/run_inner loop accommodates three modes: subcommand-only execution, one-shot prompt/piped input execution, and an interactive prompt loop. It carefully handles Ctrl+C interruptions, ReadLine errors (treated as fatal when originating from TTY issues), and tracks usage and spinner state around operations.

UI contains logic that must be respected when editing: it spawns background tasks to hydrate caches (get_models, get_tools, get_agents, hydrate_channel) to optimize responsiveness but previous commits show these tasks can race with short-lived command execution; the code therefore separates heavy init (on_new) from light init (init_state) for commands that should not spawn background tasks. The UI integrates with global tracking (TRACKER) and the spinner/writer orchestration that synchronizes header rendering with tool output — these interactions are sensitive to ordering and notification semantics.
### Edit Checklist
Tests to run: `cargo test -p forge_main --lib`, `cargo test --workspace`, `cargo clippy --workspace --all-targets -- -D warnings`
Data/constants: `forge_config::ForgeConfig (UI reads config at init and rebinds API via new_api)`, `Porcelain truncation rules and TitleFormat strings (used by shell-plugin)`
### Historical Insights
- [State Management] CLI session flags and agent handling moved to session-level (multiple commits)
  Problem: Multiple UI flows and CLI commands previously relied on a global persisted 'operating agent'/'operating model' and environment variables, leading to confusing behaviour for session-scoped agent selection.
  Root cause: Agent selection was stored as a global persistent app configuration and retrieved in various places (API calls, env variables, CLI plugin), causing multiple places to be updated for agent/session changes.
  Solution: UI now uses new API methods (get_active_agent / set_active_agent / get_default_provider / get_agent_model) and sets CLI-provided agent at startup; conversation initialization respects CLI flags; UI helpers get_provider/get_agent_model added to centralize per-agent provider/model resolution. Many command handlers now accept session-scoped agent info.
  Commits: `94ac901`, `a90be20`, `2070dba`, `fc3dedd`, `d9207fc`
  Constructs: `init_forge_services`, `init_conversation`, `get_provider`, `get_agent_model`, `on_show_config`, `on_show_providers`, `on_show_tools`, `on_info`, `prompt`, `on_command`
- [Configuration] UI now accepts pre-read ForgeConfig and new_api factory receives ForgeConfig
  Problem: Refactor changed how config is seeded into UI and how new API instances are created for /new conversations.
  Root cause: Config was being read lazily; the startup path now pre-reads config to surface errors early.
  Solution: UI::init signature changed to accept ForgeConfig and new_api is Fn(ForgeConfig) -> A; UI stores config for reads to avoid calling api.get_config repeatedly for startup-read-only fields.
  Commits: `5bd0b94`
  Constructs: `UI::init`, `UI::on_new`, `UI::on_env`
- [Error Handling] Show configured providers only and use provider.is_configured filter
  Problem: Some provider lists included non-configured providers where expected only configured ones.
  Root cause: Earlier helper get_configured_providers was removed; flows didn't consistently filter providers.
  Solution: Filter providers via .is_configured() at call sites and use select_provider_from_list which shows 'logged in' column as appropriate.
  Commits: `7fc0c5e`
  Constructs: `UI::select_provider`, `UI::select_provider_from_list`
- [UI] Adopt porcelain tables + fzf selection for multiple UI flows and centralize provider/model selection helpers
  Problem: Multiple in-app selectors used different formatting approaches and dialoguer; selection UX needed consistency with shell plugin and fzf capabilities (header rows, starting cursor).
  Root cause: Fragmented selection code paths and dialoguer removal.
  Solution: Refactor to build Info -> Porcelain tables and pass to ForgeSelect rows (fzf-wrapped) with header_lines and starting cursor. Add helper select_provider_from_list to centralize provider selection logic and adapt many flows: provider login/logout, agent selection, model selection. Also remove CliModel/CliProvider types from production code and keep test-only wrappers where necessary.
  Commits: `7fc0c5e`, `2ba208b`
  Constructs: `UI::select_provider_from_list`, `UI::select_provider`, `UI::select_model`, `UI::select_model (async_recursion)`, `UI::on_agent_change`, `UI::display_banner`
- [Configuration] Trim trailing slash from URL param values during provider login
  Problem: User supplied base URLs with a trailing slash resulted in malformed composed provider URLs.
  Root cause: UI accepted free-text URL params and did not normalize trailing slashes before persisting/using the param.
  Solution: Trim trailing '/' from param_value with trim_end_matches before storing.
  Commits: `b282602`
  Constructs: `provider param input mapping`
- [UX] Truncate model names in porcelain output
  Problem: Porcelain output for models could produce excessively long names causing misaligned UIs in shell plugin.
  Root cause: Porcelain output pipeline didn't truncate long fields.
  Solution: Apply Porcelain::truncate(1, 40) when writing the configuration info headers for porcelain to keep columns readable.
  Commits: `a437d86`
  Constructs: `UI::writeln (porcelain path)`
- [Security] Hide raw API key in UI after creating Forge auth credentials
  Problem: CLI printed the Forge API key/token directly to the UI on creation, exposing secrets in terminal output.
  Root cause: Previous behavior surfaced auth.token in writeln_title calls.
  Solution: Do not print the API key. Instead, show the path to the credentials file where keys are stored (via Environment::credentials_path()) and introduce init_forge_services() to create credentials and display the path.
  Constructs: `init_forge_services`, `prompt`, `configure_provider`
- [Error Handling] Improve zsh plugin doctor output and error messages
  Problem: Doctor script output used ambiguous symbols and error messages for install scripts had unhelpful formatting.
  Root cause: Script used [!!] and [--] symbols and UI code formatted the execution exit code with debug {:?} rather than string interpolation.
  Solution: Normalized symbols to [ERROR]/[WARN], changed doctor script formatting to clearer dimmed detail prefixes, and improved execute_zsh_script_with_streaming to produce a human-friendly exit code string in error messages. UI now prints actionable warnings when zsh doctor completes successfully instructing to restart shell.
  Commits: `5becac1`
  Constructs: `on_zsh_doctor`, `execute_zsh_script_with_streaming`
- [Parsing / Formatting] Porcelain output adjustments and skipping header rows
  Problem: Porcelain-mode tabular output had incorrect column mapping and skipped wrong number of rows
  Root cause: Porcelain formatting changed semantics (long vs short, columns) and code used inconsistent skip/drop counts and title positioning
  Solution: Introduce Porcelain module and modify UI to call Porcelain::from(&info).into_long()/.skip(n)/.drop_col(0) or .skip(2) depending on context; adjust when to include title and how many rows to skip
  Commits: `ed24862`, `9b3b618`, `c71b2a4`
  Constructs: `write_info_or_porcelain (removed)`, `UI::on_show_* helpers that call Porcelain`, `Porcelain::from`
- [State Management] Allow model selection when provider activation completes without selection; atomic provider+model write support
  Problem: Provider activation flows could end with inconsistent state when provider credential flows completed but model selection wasn't performed atomically, leading to a situation where the UI believed a model had been selected while the persisted config had mismatched provider/model.
  Root cause: UI previously called set_default_provider and set_default_model as separate operations; cancellation during model selection sometimes left provider set without an appropriate model. Additionally, provider activation could complete model selection and the UI needed to detect that.
  Solution: Add API path set_default_provider_and_model to perform an atomic write for provider+model. Adjust UI flows: display_credential_success no longer prompts for active provider choice; on_provider_selection returns bool indicating whether provider was saved; on_model_selection accepts optional provider_to_activate and uses set_default_provider_and_model when present. Also allow short-circuit when provider activation already set default model.
  Commits: `bfd9f7f`, `44d22ee`
  Constructs: `UI::on_model_selection`, `UI::on_provider_selection`, `display_credential_success`, `get_models`, `init_state`
- [Usability] Activate provider+model atomically when provided
  Problem: Changing provider then prompting for model selection created a race/extra interaction when a preselected model is known.
  Root cause: Activation flow separately set provider and then launched interactive model selection; losing opportunity to set both atomically.
  Solution: Added activate_provider_with_model(provider, model: Option<ModelId>) to optionally skip interactive model prompt and set model directly. CLI set subcommand exposes --model to preselect model when setting provider.
  Commits: `03741f7`
  Constructs: `activate_provider_with_model`, `finalize_provider_activation`
- [UX] Make Info display keys fixed-width per section
  Problem: Info display alignment varied and was visually inconsistent across sections.
  Root cause: Key column width was not computed per-section, causing misaligned colons and values.
  Solution: Compute max key width for items under each Title and pad keys within that section; updated Info formatting and tests to assert consistent padding.
  Commits: `95a0bb5`
  Constructs: `on_show_tools`, `on_show_config`, `on_info`, `display_banner`
- [State Management] Prevent partial config write when model selection is cancelled
  Problem: During onboarding/provider activation the provider was persisted immediately. If model selection was cancelled afterwards the config ended up partially updated (provider set without model), causing user-visible inconsistent state.
  Root cause: The code wrote the provider to disk (set_default_provider) before verifying that a model selection had been confirmed. User cancellation after provider selection therefore left an incomplete config.
  Solution: Delay writing the provider into the config until after model selection completes successfully. Functions that previously returned () were changed to return Option/boolean to allow early return when user cancels. The provider activation flow now validates preselected models, scopes model selection to the activated provider, and aborts without writing on cancel.
  Commits: `6a36b83`, `b8866ef`
  Constructs: `on_provider_selection`, `activate_provider`, `on_model_selection`, `select_model`, `validate_model`
- [UX/Presentation] Reorder conversation info key/value for better readability
  Problem: Conversation listing showed Id before Updated causing minor UX confusion
  Root cause: Field insertion order produced unintuitive ordering
  Solution: Swap add_key_value calls so Updated displays before Id
  Commits: `cb93ac7`
  Constructs: `UI::on_show_conversations`
- [Error Handling] Propagate Readline/TTY errors as quick exit
  Problem: Readline/TUI errors were printed as warnings but the UI continued, leading to confusing state. Under missing TTY the first prompt should cause process exit.
  Root cause: Readline errors were being swallowed and converted to messages instead of short-circuiting.
  Solution: Editor.prompt now maps errors to ReadLineError; UI checks for ReadLineError and returns Err to exit quickly when prompt can't access TTY.
  Commits: `92321fe`
  Constructs: `prompt handling`, `ReadLineError`
### Key Constructs
- **format_mcp_server** (function): Formats an MCP server config for display while redacting sensitive parameters; returns the command/URL string.
  Used by MCP listing and import flows; keep redaction behavior (env keys masked) intact when updating MCP display.
- **format_mcp_headers** (function): Formats HTTP headers for display with values redacted; returns None when no headers.
  Used to display header summary for HTTP MCP servers; behavior must remain consistent when updating MCP UI.
- **UI** (class): Main UI struct that holds API factory, console, spinner, state, and other UI components.
  This is the main entrypoint for interactive behavior; altering fields or their types requires updating many call sites and the new_api closure semantics.
  - `crates/forge_main/src/lib.rs`: called at lines 32
  - `crates/forge_main/src/main.rs`: called at lines 10, 131
- **init** (function): Initializes UI with Cli, pre-read ForgeConfig, and an API factory closure (Fn(ForgeConfig) -> A).
  Centralizes startup wiring and creates Console and Spinner; important because the new_api factory is used again on /new to reflect config changes.
  - `crates/forge_main/src/main.rs`: called at lines 131
- **run** (function): Top-level async entry that calls run_inner and prints/logs full error chains on failure.
  Wraps run_inner to ensure any runtime errors are surfaced to the user and tracked; keep error printing behavior consistent.
  - `crates/forge_main/src/main.rs`: called at lines 134
- **run_inner** (function): Core interactive loop: handles subcommands, direct prompt/piped input, and the main prompt loop with Ctrl+C handling and per-command error reporting.
  Contains the main control flow. Editing this may change UX and interruption semantics; preserve how errors from ReadLine are treated and how spinner is started/stopped around command execution.
- **prompt** (function): Builds ForgePrompt with usage/model/agent and delegates to console.prompt to obtain a SlashCommand.
  Generates input context for the editor; changes here affect prompt behavior and how usage/model selection is shown to the user.
- **on_new** (function): Handler for creating a fresh conversation context: re-reads config, rebinds api via new_api, initializes state, optionally sets the agent passed via CLI, resets CLI conversation flags, and triggers spinner/banner/hydration and trackers.
  Per commit history this function was a source of race conditions and heavy init; it intentionally spawns background hydration tasks and thus should remain distinct from light-weight init routines.
(+3 more constructs)
### Tests
Files: `crates/forge_main/src/**/tests (various unit tests in ui/info/porcelain changes)`
### Related Files
- `crates/forge_api/src/forge_api.rs` [co-change] | Rel: Primary backend API that the UI calls into for business operations (models, providers, chat, config updates). | Check: When changing UI workflows that call API methods (e.g., update_config, set_active_agent, get_models), verify behavior and signatures in the API implementation.
- `crates/forge_main/src/cli.rs` [co-change] | Rel: Defines the CLI structure and TopLevelCommand enum consumed by the UI's handle_subcommands. | Check: If altering subcommand handling or adding flags, ensure cli.rs and shell-plugin remain aligned.
- `crates/forge_app/src/services.rs` | Rel: Business logic that services API calls; UI expects certain semantics from service-backed operations (commit generation, tool lists, provider models). | Check: If changing how warnings/errors are surfaced from services, UI handlers should adapt how they display those messages.
- `shell-plugin/forge.plugin.zsh` [co-change] | Rel: Zsh plugin integration depends on CLI semantics and porcelain output formatting from UI. | Check: Any CLI or porcelain output changes require verifying plugin compatibility.
### Semantic Overview
Tags: `ui`, `interactive`, `cli`, `prompt`, `spinner`, `provider-selection`
Entities: `Conversation`, `Agent`, `Model`, `Provider`, `McpConfig`, `Workspace`
- starts interactive prompt loop and processes SlashCommand objects
- runs subcommands and one-off prompt/piped input in non-interactive mode
- hydrates API caches in background to speed up subsequent calls
- delegates provider/model resolution to the API and persists active agent
- manages spinner and streaming output synchronization
### Pitfalls
- Spawn heavy background hydration unconditionally from light-weight command paths
  Consequence: Can race with short-lived commands and produce 'JoinHandle polled after completion' or other panics; causes flaky behavior in scripts/automation
  Prevention: Respect the distinction between on_new (heavy init with hydrate_caches) and init_state(false) (light init). When adding or moving initialization logic, preserve this separation.
- Modifying CLI command shapes or top-level subcommands without updating the zsh plugin
  Consequence: Shell plugin completion and shortcuts break; users' shell integration may become incompatible
  Prevention: When changing CLI command names/structure, update shell-plugin/forge.plugin.zsh and verify compatibility.
- Changing prompt error handling semantics (e.g., swallowing ReadLineError)
  Consequence: TTY/readline failures may be hidden and the process may continue in an inconsistent state; interactive session should exit on unrecoverable TTY errors
  Prevention: Keep ReadLineError propagation path intact so the first prompt failure can short-circuit run_inner and exit appropriately.
- Breaking formatting/porcelain output conventions
  Consequence: Shell pickers and machine-parsing consumers receive unexpected columns/lengths; porcelain tests and shell-plugin behavior can fail
  Prevention: When modifying output, ensure porcelain/truncation rules and header row counts remain consistent and adjust shell-plugin expectations.
### Reading Guide
Start: `UI struct and UI::init to understand how the UI is constructed and how the API factory (new_api) is used.`
Key: run_inner: central control flow for interactive and non-interactive modes, on_new and init_state flows: differences between heavy and light initialization, prompt: how prompts are composed with usage/model/agent, hydrate_caches: background warming tasks that affect startup performance and potential races, handle_subcommands: where top-level CLI commands are implemented
Skip: Large match arms for specific subcommands (they handle many display details) when making unrelated changes

For detailed results and evaluation artifacts, see:

We'd love to hear whether this is useful for your workflow, and we're happy to address any concerns (the content, the delivery method, or anything else). Happy to discuss here or at codeset.ai.

Would also be open to integrate this into something that forgecoder itself can use, either in a similar delivery mode or via some other plugin format.

These files are plain text, do not affect runtime behavior, and introduce no dependency on Codeset services. They can be safely ignored or removed at any time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants