SaylorTwift HF Staff commited on
Commit
eb51191
·
verified ·
1 Parent(s): 61154ac

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .config/nextest.toml +10 -0
  2. .devcontainer/devcontainer.json +39 -0
  3. .gitattributes +1 -35
  4. .gitignore +48 -0
  5. .ignore +2 -0
  6. .rustfmt.toml +6 -0
  7. AGENTS.md +246 -0
  8. Cargo.lock +0 -0
  9. Cargo.toml +166 -0
  10. Cross.toml +75 -0
  11. LICENSE +201 -0
  12. README.md +1171 -0
  13. _config.yml +3 -0
  14. clippy.toml +3 -0
  15. diesel.toml +5 -0
  16. flake.lock +27 -0
  17. flake.nix +159 -0
  18. forge.schema.json +1037 -0
  19. insta.yaml +4 -0
  20. package-lock.json +1966 -0
  21. package.json +37 -0
  22. plans/2025-04-02-system-context-rendering-final.md +344 -0
  23. plans/2025-04-02-system-context-rendering-v1.md +171 -0
  24. plans/2025-04-02-system-context-rendering-v2.md +315 -0
  25. plans/2025-04-06-retry-config-migration.md +90 -0
  26. plans/2025-04-11-tool-call-context-implementation.md +113 -0
  27. plans/2025-04-16-model-selection-command.md +39 -0
  28. plans/2025-04-26-large-file-read-range-support-v3.md +239 -0
  29. plans/2025-04-27-large-file-read-range-support-v4.md +316 -0
  30. plans/2025-06-07-tool-service-migration-v1.md +197 -0
  31. plans/2025-09-07-shell-env-variable-support-v1.md +65 -0
  32. plans/2025-09-08-agent-loader-cwd-extension-v1.md +115 -0
  33. plans/2025-09-12-forge-dump-autoopen-env-var-v2.md +167 -0
  34. plans/2025-09-13-dynamic-agent-slash-commands-v3.md +144 -0
  35. plans/2025-09-13-forge-history-file-env-var-v2.md +207 -0
  36. plans/2025-09-14-explicit-conversation-id-generation-v1.md +173 -0
  37. plans/2025-09-27-AppConfigRepository_Implementation-v1.md +122 -0
  38. plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v1.md +135 -0
  39. plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v2.md +138 -0
  40. plans/2026-03-31-slim-environment-and-add-get-config-v1.md +229 -0
  41. plans/2026-04-05-config-init-at-startup-v1.md +193 -0
  42. plans/agent-context-compaction-2025-03-24-concise.md +72 -0
  43. plans/agent-context-compaction-2025-03-24.md +328 -0
  44. plans/agent-context-compaction-2025-03-25-concise.md +220 -0
  45. plans/agent-context-compaction-2025-03-25.md +217 -0
  46. renovate.json +8 -0
  47. rust-analyzer.toml +23 -0
  48. rust-toolchain.toml +3 -0
  49. shell-plugin/forge.theme.zsh +28 -0
  50. vertex.json +112 -0
.config/nextest.toml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ [profile.default]
2
+ # Set default test timeouts - mark tests as SLOW after 1 sec.
3
+ slow-timeout = { period = "1s", terminate-after = 30, grace-period = "0s" }
4
+
5
+
6
+ # Show only failed tests during test execution
7
+ status-level = "fail"
8
+
9
+ # Show only failed tests in the final summary
10
+ final-status-level = "fail"
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image": "mcr.microsoft.com/devcontainers/rust:2-1-bullseye",
3
+ "remoteUser": "vscode",
4
+ "updateRemoteUserUID": true,
5
+ "features": {
6
+ "ghcr.io/devcontainers/features/node:2": {
7
+ "version": "lts"
8
+ },
9
+ "ghcr.io/devcontainers/features/github-cli:1": {},
10
+ "ghcr.io/devcontainers/features/git:1": {
11
+ "ppa": true,
12
+ "version": "latest"
13
+ },
14
+ "ghcr.io/devcontainers/features/common-utils:2": {
15
+ "installZsh": true,
16
+ "installOhMyZsh": true,
17
+ "upgradePackages": true,
18
+ "username": "vscode",
19
+ "uid": "automatic",
20
+ "gid": "automatic"
21
+ }
22
+ },
23
+ "containerEnv": {
24
+ "SHELL": "/usr/bin/zsh"
25
+ },
26
+ "customizations": {
27
+ "vscode": {
28
+ "settings": {
29
+ "terminal.integrated.defaultProfile.linux": "zsh",
30
+ "terminal.integrated.profiles.linux": {
31
+ "zsh": {
32
+ "path": "/usr/bin/zsh"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ },
38
+ "postCreateCommand": "sudo apt-get update && sudo apt-get install -y fzf fd-find && npm i -g forgecode && rustup component add clippy rustfmt && cargo install cargo-insta && cargo install cargo-nextest && cargo install ast-grep && chsh -s /usr/bin/zsh"
39
+ }
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.zsh eol=lf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Cargo
2
+ # will have compiled files and executables
3
+ debug/
4
+ target/
5
+ jobs/**
6
+
7
+
8
+ # These are backup files generated by rustfmt
9
+ **/*.rs.bk
10
+ .idea
11
+ # MSVC Windows builds of rustc generate these, which store debugging information
12
+ *.pdb
13
+
14
+ # RustRover
15
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
16
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
17
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
18
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
19
+ #.idea/
20
+ .aider*
21
+ .env
22
+ .DS_Store
23
+ # Database files
24
+ *.db
25
+ *.db-*
26
+ *.db-shm
27
+ *.db-wal
28
+ *.sqlite
29
+ *.sqlite3
30
+
31
+ # Backup files
32
+ *.bak
33
+ *.rs.bak
34
+ .tasks
35
+ *.new
36
+ .vscode/
37
+ .fastembed_cache/
38
+ *.log*
39
+ *-dump.json
40
+ *-dump.html
41
+ .mcp.json
42
+ *.d.ts
43
+ *.js
44
+ *.map
45
+ **/.forge/request.body.json
46
+ node_modules/
47
+ bench/__pycache__
48
+ .ai/
.ignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # ignore files for auto-complete
2
+ *.snap
.rustfmt.toml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ unstable_features = true
2
+ struct_lit_width = 60
3
+ imports_granularity = "Module"
4
+ group_imports = "StdExternalCrate"
5
+ wrap_comments = true
6
+ comment_width = 80
AGENTS.md ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Guidelines
2
+
3
+ This document contains guidelines and best practices for AI agents working with this codebase.
4
+
5
+ ## Error Management
6
+
7
+ - Use `anyhow::Result` for error handling in services and repositories.
8
+ - Create domain errors using `thiserror`.
9
+ - Never implement `From` for converting domain errors, manually convert them
10
+
11
+ ## Writing Tests
12
+
13
+ - All tests should be written in three discrete steps:
14
+
15
+ ```rust,ignore
16
+ use pretty_assertions::assert_eq; // Always use pretty assertions
17
+
18
+ fn test_foo() {
19
+ let setup = ...; // Instantiate a fixture or setup for the test
20
+ let actual = ...; // Execute the fixture to create an output
21
+ let expected = ...; // Define a hand written expected result
22
+ assert_eq!(actual, expected); // Assert that the actual result matches the expected result
23
+ }
24
+ ```
25
+
26
+ - Use `pretty_assertions` for better error messages.
27
+
28
+ - Use fixtures to create test data.
29
+
30
+ - Use `assert_eq!` for equality checks.
31
+
32
+ - Use `assert!(...)` for boolean checks.
33
+
34
+ - Use unwraps in test functions and anyhow::Result in fixtures.
35
+
36
+ - Keep the boilerplate to a minimum.
37
+
38
+ - Use words like `fixture`, `actual` and `expected` in test functions.
39
+
40
+ - Fixtures should be generic and reusable.
41
+
42
+ - Test should always be written in the same file as the source code.
43
+
44
+ - Use `new`, Default and derive_setters::Setters to create `actual`, `expected` and specially `fixtures`. For example:
45
+
46
+ **Good:**
47
+
48
+ ```rust,ignore
49
+ User::default().age(12).is_happy(true).name("John")
50
+ User::new("Job").age(12).is_happy()
51
+ User::test() // Special test constructor
52
+ ```
53
+
54
+ **Bad:**
55
+
56
+ ```rust,ignore
57
+ User {name: "John".to_string(), is_happy: true, age: 12}
58
+ User::with_name("Job") // Bad name, should stick to User::new() or User::test()
59
+ ```
60
+
61
+ - Use `unwrap()` unless the error information is useful. Use `expect` instead of `panic!` when error message is useful. For example:
62
+
63
+ **Good:**
64
+
65
+ ```rust,ignore
66
+ users.first().expect("List should not be empty")
67
+ ```
68
+
69
+ **Bad:**
70
+
71
+ ```rust,ignore
72
+ if let Some(user) = users.first() {
73
+ // ...
74
+ } else {
75
+ panic!("List should not be empty")
76
+ }
77
+ ```
78
+
79
+ - Prefer using `assert_eq` on full objects instead of asserting each field:
80
+
81
+ **Good:**
82
+
83
+ ```rust,ignore
84
+ assert_eq!(actual, expected);
85
+ ```
86
+
87
+ **Bad:**
88
+
89
+ ```rust,ignore
90
+ assert_eq!(actual.a, expected.a);
91
+ assert_eq!(actual.b, expected.b);
92
+ ```
93
+
94
+ ## Verification
95
+
96
+ Always verify changes by running tests and linting the codebase
97
+
98
+ 1. Run crate specific tests to ensure they pass.
99
+
100
+ ```
101
+ cargo insta test --accept
102
+ ```
103
+
104
+ 2. **Build Guidelines**:
105
+ - **NEVER** run `cargo build --release` unless absolutely necessary (e.g., performance testing, creating binaries for distribution)
106
+ - For verification, use `cargo check` (fastest), `cargo insta test`, or `cargo build` (debug mode)
107
+ - Release builds take significantly longer and are rarely needed for development verification
108
+
109
+ ## Writing Domain Types
110
+
111
+ - Use `derive_setters` to derive setters and use the `strip_option` and the `into` attributes on the struct types.
112
+
113
+ ## Documentation
114
+
115
+ - **Always** write Rust docs (`///`) for all public methods, functions, structs, enums, and traits.
116
+ - Document parameters with `# Arguments` and errors with `# Errors` sections when applicable.
117
+ - **Do not include code examples** - docs are for LLMs, not humans. Focus on clear, concise functionality descriptions.
118
+
119
+ ## Refactoring
120
+
121
+ - If asked to fix failing tests, always confirm whether to update the implementation or the tests.
122
+
123
+ ## Git Operations
124
+
125
+ - Safely assume git is pre-installed
126
+ - Safely assume github cli (gh) is pre-installed
127
+ - Always use `Co-Authored-By: ForgeCode <noreply@forgecode.dev>` for git commits and Github comments
128
+
129
+ ## Service Implementation Guidelines
130
+
131
+ Services should follow clean architecture principles and maintain clear separation of concerns:
132
+
133
+ ### Core Principles
134
+
135
+ - **No service-to-service dependencies**: Services should never depend on other services directly
136
+ - **Infrastructure dependency**: Services should depend only on infrastructure abstractions when needed
137
+ - **Single type parameter**: Services should take at most one generic type parameter for infrastructure
138
+ - **No trait objects**: Avoid `Box<dyn ...>` - use concrete types and generics instead
139
+ - **Constructor pattern**: Implement `new()` without type bounds - apply bounds only on methods that need them
140
+ - **Compose dependencies**: Use the `+` operator to combine multiple infrastructure traits into a single bound
141
+ - **Arc<T> for infrastructure**: Store infrastructure as `Arc<T>` for cheap cloning and shared ownership
142
+ - **Tuple struct pattern**: For simple services with single dependency, use tuple structs `struct Service<T>(Arc<T>)`
143
+
144
+ ### Examples
145
+
146
+ #### Simple Service (No Infrastructure)
147
+
148
+ ```rust,ignore
149
+ pub struct UserValidationService;
150
+
151
+ impl UserValidationService {
152
+ pub fn new() -> Self { ... }
153
+
154
+ pub fn validate_email(&self, email: &str) -> Result<()> {
155
+ // Validation logic here
156
+ ...
157
+ }
158
+
159
+ pub fn validate_age(&self, age: u32) -> Result<()> {
160
+ // Age validation logic here
161
+ ...
162
+ }
163
+ }
164
+ ```
165
+
166
+ #### Service with Infrastructure Dependency
167
+
168
+ ```rust,ignore
169
+ // Infrastructure trait (defined in infrastructure layer)
170
+ pub trait UserRepository {
171
+ fn find_by_email(&self, email: &str) -> Result<Option<User>>;
172
+ fn save(&self, user: &User) -> Result<()>;
173
+ }
174
+
175
+ // Service with single generic parameter using Arc
176
+ pub struct UserService<R> {
177
+ repository: Arc<R>,
178
+ }
179
+
180
+ impl<R> UserService<R> {
181
+ // Constructor without type bounds, takes Arc<R>
182
+ pub fn new(repository: Arc<R>) -> Self { ... }
183
+ }
184
+
185
+ impl<R: UserRepository> UserService<R> {
186
+ // Business logic methods have type bounds where needed
187
+ pub fn create_user(&self, email: &str, name: &str) -> Result<User> { ... }
188
+ pub fn find_user(&self, email: &str) -> Result<Option<User>> { ... }
189
+ }
190
+ ```
191
+
192
+ #### Tuple Struct Pattern for Simple Services
193
+
194
+ ```rust,ignore
195
+ // Infrastructure traits
196
+ pub trait FileReader {
197
+ async fn read_file(&self, path: &Path) -> Result<String>;
198
+ }
199
+
200
+ pub trait Environment {
201
+ fn max_file_size(&self) -> u64;
202
+ }
203
+
204
+ // Tuple struct for simple single dependency service
205
+ pub struct FileService<F>(Arc<F>);
206
+
207
+ impl<F> FileService<F> {
208
+ // Constructor without bounds
209
+ pub fn new(infra: Arc<F>) -> Self { ... }
210
+ }
211
+
212
+ impl<F: FileReader + Environment> FileService<F> {
213
+ // Business logic methods with composed trait bounds
214
+ pub async fn read_with_validation(&self, path: &Path) -> Result<String> { ... }
215
+ }
216
+ ```
217
+
218
+ ### Anti-patterns to Avoid
219
+
220
+ ```rust,ignore
221
+ // BAD: Service depending on another service
222
+ pub struct BadUserService<R, E> {
223
+ repository: R,
224
+ email_service: E, // Don't do this!
225
+ }
226
+
227
+ // BAD: Using trait objects
228
+ pub struct BadUserService {
229
+ repository: Box<dyn UserRepository>, // Avoid Box<dyn>
230
+ }
231
+
232
+ // BAD: Multiple infrastructure dependencies with separate type parameters
233
+ pub struct BadUserService<R, C, L> {
234
+ repository: R,
235
+ cache: C,
236
+ logger: L, // Too many generic parameters - hard to use and test
237
+ }
238
+
239
+ impl<R: UserRepository, C: Cache, L: Logger> BadUserService<R, C, L> {
240
+ // BAD: Constructor with type bounds makes it hard to use
241
+ pub fn new(repository: R, cache: C, logger: L) -> Self { ... }
242
+ }
243
+
244
+ // BAD: Usage becomes cumbersome
245
+ let service = BadUserService::<PostgresRepo, RedisCache, FileLogger>::new(...);
246
+ ```
Cargo.lock ADDED
The diff for this file is too large to render. See raw diff
 
Cargo.toml ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [workspace]
2
+ members = ["crates/*"]
3
+ resolver = "2"
4
+
5
+
6
+ [workspace.package]
7
+ version = "0.1.0"
8
+ rust-version = "1.94"
9
+ edition = "2024"
10
+
11
+ [profile.release]
12
+ lto = true
13
+ codegen-units = 1
14
+ opt-level = 3
15
+ strip = true
16
+
17
+ [workspace.dependencies]
18
+ anyhow = "1.0.102"
19
+ async-recursion = "1.1.1"
20
+ async-stream = "0.3"
21
+ async-trait = "0.1.89"
22
+ aws-config = { version = "1.8.13", features = ["behavior-version-latest", "sso"], default-features = false }
23
+ aws-sdk-bedrockruntime = { version = "1.129.0", features = ["behavior-version-latest"], default-features = false }
24
+ aws-credential-types = "1.2.14"
25
+ aws-smithy-types = "1.4.3"
26
+ aws-smithy-runtime-api = "1.11.3"
27
+ aws-smithy-async = { version = "1.2.11", features = ["rt-tokio"] }
28
+ aws-smithy-runtime = { version = "1.10", features = ["connector-hyper-0-14-x", "tls-rustls"] }
29
+ base64 = "0.23.0"
30
+ bstr = "1.12.1"
31
+ bytes = "1.11.1"
32
+ chrono = { version = "0.4.44", features = ["serde"] }
33
+ clap = { version = "4.6.0", features = ["derive"] }
34
+ clap_complete = "4.6.0"
35
+ colored = "3.1.1"
36
+ console = "0.16.3"
37
+ convert_case = "0.11.0"
38
+ derive_more = { version = "2.1.1", features = ["from", "display", "debug", "deref", "as_ref", "try_into"] }
39
+ enable-ansi-support = "0.3.1"
40
+ derive_setters = "0.1.9"
41
+ dirs = "6.0.0"
42
+ dissimilar = "1.0.9"
43
+ dotenvy = "0.15.7"
44
+ futures = "0.3.32"
45
+ gh-workflow = "0.8.1"
46
+ glob = "0.3.3"
47
+ grep-searcher = "0.1.14"
48
+ grep-regex = "0.1.13"
49
+ handlebars = "6.4.0"
50
+ html2md = "0.2.15"
51
+ http = "1.2.0"
52
+ ignore = "0.4.23"
53
+ is_ci = "1.2.0"
54
+ indexmap = "2.13.0"
55
+ infer = "0.22.0"
56
+ insta = { version = "1.47.2", features = ["json", "yaml"] }
57
+ lazy_static = "1.4.0"
58
+ machineid-rs = "1.2.4"
59
+ mockito = "1.7.2"
60
+ nom = "8.0.0"
61
+ nu-ansi-term = "0.50.1"
62
+ posthog-rs = "0.22.0"
63
+ pretty_assertions = "1.4.1"
64
+ proc-macro2 = "1.0"
65
+ quote = "1.0"
66
+ rustyline = "18.0.0"
67
+ regex = "1.12.3"
68
+ reqwest = { version = "0.12.23", features = [
69
+ "json",
70
+ "rustls-tls",
71
+ "hickory-dns",
72
+ "http2",
73
+ ], default-features = false }
74
+ rustls = { version = "0.23", features = ["ring"], default-features = false }
75
+ include_dir = "0.7.4"
76
+ schemars = "1.2"
77
+ serde = { version = "1.0.217", features = ["derive"] }
78
+ serde_json = "1.0.143"
79
+ serde_yml = "0.0.13"
80
+ sha2 = "0.11"
81
+ similar = { version = "3.0", features = ["inline"] }
82
+ strip-ansi-escapes = "0.2.1"
83
+ strum = "0.28.0"
84
+ strum_macros = "0.28.0"
85
+ syn = { version = "3.0.0", features = ["derive", "parsing"] }
86
+ sysinfo = "0.38.3"
87
+ tempfile = "3.27.0"
88
+ termimad = "0.34.1"
89
+ tiny_http = "0.12.0"
90
+ syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "regex-onig"] }
91
+ thiserror = "2.0.18"
92
+ toml_edit = { version = "0.25", features = ["serde"] }
93
+ tokio = { version = "1.51.0", features = [
94
+ "macros",
95
+ "rt-multi-thread",
96
+ "sync",
97
+ "time",
98
+ "fs",
99
+ "process",
100
+ "signal",
101
+ "io-util",
102
+ ] }
103
+ tokio-stream = "0.1.18"
104
+ tokio-util = "0.7"
105
+ tonic = { version = "0.14.5", features = ["tls-webpki-roots"] }
106
+ tracing = "0.1.44"
107
+ tracing-appender = "0.2.3"
108
+ tracing-subscriber = { version = "0.3.23", features = ["env-filter", "json"] }
109
+ url = { version = "2.5.8", features = ["serde"] }
110
+ terminal_size = "0.4"
111
+ unicode-width = "0.2"
112
+ backon = "1.5.2"
113
+ eserde = "0.1.7"
114
+ uuid = { version = "1.23.0", features = [
115
+ "v4",
116
+ "fast-rng",
117
+ "serde",
118
+ ] }
119
+ whoami = "2.1.0"
120
+ fnv_rs = "0.4.3"
121
+ merge = { version = "0.2", features = ["derive"] }
122
+ hex = "0.4.3"
123
+ rmcp = { version = "1.0.0", features = [
124
+ "client",
125
+ "transport-child-process",
126
+ "transport-streamable-http-client-reqwest",
127
+ "auth",
128
+ ] }
129
+ open = "5.3.2"
130
+ nucleo = "0.5.0"
131
+ nucleo-picker = "0.11.1"
132
+ gray_matter = "0.3.2"
133
+ num-format = "0.4"
134
+ humantime = "2.1.0"
135
+ dashmap = "7.0.0-rc2"
136
+ async-openai = { version = "0.41.0", default-features = false, features = ["response-types"] } # Using only types, not the API client - reduces dependencies
137
+ gix = "0.86"
138
+ google-cloud-auth = "1.8.0" # Google Cloud authentication with automatic token refresh
139
+
140
+ # Internal crates
141
+ forge_embed = { path = "crates/forge_embed" }
142
+ forge_api = { path = "crates/forge_api" }
143
+ forge_app = { path = "crates/forge_app" }
144
+ forge_ci = { path = "crates/forge_ci" }
145
+ forge_display = { path = "crates/forge_display" }
146
+ forge_domain = { path = "crates/forge_domain" }
147
+ forge_fs = { path = "crates/forge_fs" }
148
+ forge_infra = { path = "crates/forge_infra" }
149
+ forge_repo = { path = "crates/forge_repo" }
150
+ forge_main = { path = "crates/forge_main" }
151
+ forge_services = { path = "crates/forge_services" }
152
+ forge_snaps = { path = "crates/forge_snaps" }
153
+ forge_spinner = { path = "crates/forge_spinner" }
154
+ forge_stream = { path = "crates/forge_stream" }
155
+ forge_template = { path = "crates/forge_template" }
156
+ forge_tool_macros = { path = "crates/forge_tool_macros" }
157
+ forge_tracker = { path = "crates/forge_tracker" }
158
+ forge_walker = { path = "crates/forge_walker" }
159
+ forge_json_repair = { path = "crates/forge_json_repair" }
160
+ forge_select = { path = "crates/forge_select" }
161
+ forge_test_kit = { path = "crates/forge_test_kit" }
162
+
163
+ forge_markdown_stream = { path = "crates/forge_markdown_stream" }
164
+ forge_config = { path = "crates/forge_config" }
165
+ forge_eventsource = { path = "crates/forge_eventsource" }
166
+ forge_eventsource_stream = { path = "crates/forge_eventsource_stream" }
Cross.toml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build]
2
+ # Build the build script in the container to avoid GLIBC mismatch
3
+ build-std = false
4
+ default-target = "x86_64-unknown-linux-gnu"
5
+
6
+ [build.env]
7
+ passthrough = [
8
+ "APP_VERSION",
9
+ "POSTHOG_API_SECRET",
10
+ ]
11
+
12
+ # Install modern protoc in all Cross containers
13
+ # apt-get's protobuf-compiler is too old (proto2 only), so we download from GitHub.
14
+ #
15
+ # The default Ubuntu mirrors baked into cross images (archive.archive.ubuntu.com,
16
+ # security.archive.ubuntu.com) are frequently unreachable from GitHub Actions
17
+ # runners. Switch them to the Azure-hosted mirror (azure.archive.ubuntu.com),
18
+ # which is co-located with GitHub's Linux runners and is reliable.
19
+ [target.x86_64-unknown-linux-musl]
20
+ image = "ghcr.io/cross-rs/x86_64-unknown-linux-musl:edge"
21
+ pre-build = [
22
+ "sed -i 's|http://[a-z.]*archive.ubuntu.com|http://azure.archive.ubuntu.com|g; s|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null || true",
23
+ "apt-get update && apt-get install -y unzip",
24
+ "curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip",
25
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local bin/protoc",
26
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local 'include/*'",
27
+ "chmod +x /usr/local/bin/protoc",
28
+ ]
29
+
30
+ [target.aarch64-unknown-linux-musl]
31
+ image = "ghcr.io/cross-rs/aarch64-unknown-linux-musl:edge"
32
+ pre-build = [
33
+ "sed -i 's|http://[a-z.]*archive.ubuntu.com|http://azure.archive.ubuntu.com|g; s|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null || true",
34
+ "dpkg --add-architecture amd64",
35
+ "apt-get update && apt-get install -y unzip:amd64",
36
+ "curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip",
37
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local bin/protoc",
38
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local 'include/*'",
39
+ "chmod +x /usr/local/bin/protoc",
40
+ ]
41
+
42
+ [target.x86_64-unknown-linux-gnu]
43
+ image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:edge"
44
+ pre-build = [
45
+ "sed -i 's|http://[a-z.]*archive.ubuntu.com|http://azure.archive.ubuntu.com|g; s|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null || true",
46
+ "apt-get update && apt-get install -y unzip",
47
+ "curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip",
48
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local bin/protoc",
49
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local 'include/*'",
50
+ "chmod +x /usr/local/bin/protoc",
51
+ ]
52
+
53
+ [target.aarch64-unknown-linux-gnu]
54
+ image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge"
55
+ pre-build = [
56
+ "sed -i 's|http://[a-z.]*archive.ubuntu.com|http://azure.archive.ubuntu.com|g; s|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null || true",
57
+ "dpkg --add-architecture amd64",
58
+ "apt-get update && apt-get install -y unzip:amd64",
59
+ "curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip",
60
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local bin/protoc",
61
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local 'include/*'",
62
+ "chmod +x /usr/local/bin/protoc",
63
+ ]
64
+
65
+ [target.aarch64-linux-android]
66
+ image = "ghcr.io/cross-rs/aarch64-linux-android:edge"
67
+ pre-build = [
68
+ "sed -i 's|http://[a-z.]*archive.ubuntu.com|http://azure.archive.ubuntu.com|g; s|http://security.ubuntu.com|http://azure.archive.ubuntu.com|g' /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null || true",
69
+ "dpkg --add-architecture amd64",
70
+ "apt-get update && apt-get install -y unzip:amd64",
71
+ "curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip",
72
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local bin/protoc",
73
+ "unzip -o protoc-28.3-linux-x86_64.zip -d /usr/local 'include/*'",
74
+ "chmod +x /usr/local/bin/protoc",
75
+ ]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2025 Tailcall
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md ADDED
@@ -0,0 +1,1171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - agent
5
+ - coding-agent
6
+ - cli
7
+ - llm
8
+ - tool-use
9
+ ---
10
+
11
+ > **This is a source mirror.** This Hugging Face repository is not a model checkpoint — it's a read-only mirror of the [`tailcallhq/forgecode`](https://github.com/tailcallhq/forgecode) GitHub repository, Tailcall's terminal coding agent (a single musl binary). It is hosted here for visibility on the Hub; the canonical repository, issue tracker, and pull requests all live on GitHub. Please file issues and contribute there, not here.
12
+
13
+ <h1 align="center">⚒️ Forge: AI-Enhanced Terminal Development Environment</h1>
14
+ <p align="center">A comprehensive coding agent that integrates AI capabilities with your development environment</p>
15
+
16
+ <p align="center"><code>curl -fsSL https://forgecode.dev/cli | sh</code></p>
17
+
18
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/tailcallhq/forgecode/ci.yml?style=for-the-badge)](https://github.com/tailcallhq/forgecode/actions)
19
+ [![GitHub Release](https://img.shields.io/github/v/release/tailcallhq/forgecode?style=for-the-badge)](https://github.com/tailcallhq/forgecode/releases)
20
+ [![Discord](https://img.shields.io/discord/1044859667798568962?style=for-the-badge&cacheSeconds=120&logo=discord)](https://discord.gg/kRZBPpkgwq)
21
+ [![CLA assistant](https://cla-assistant.io/readme/badge/tailcallhq/forgecode?style=for-the-badge)](https://cla-assistant.io/tailcallhq/forgecode)
22
+
23
+ ![Code-Forge Demo](https://assets.antinomy.ai/images/forge_demo_2x.gif)
24
+
25
+ ---
26
+
27
+ <details>
28
+ <summary><strong>Table&nbsp;of&nbsp;Contents</strong></summary>
29
+
30
+ - [Quickstart](#quickstart)
31
+ - [Usage Examples](#usage-examples)
32
+ - [Why Forge?](#why-forge)
33
+ - [How Forge Works: Three Modes](#how-forge-works-three-modes)
34
+ - [Interactive Mode (TUI)](#interactive-mode-tui)
35
+ - [One-Shot CLI Mode](#one-shot-cli-mode)
36
+ - [ZSH Plugin Mode (`:` prefix)](#zsh-plugin-mode--prefix)
37
+ - [ZSH Plugin: The `:` Prefix System](#zsh-plugin-the--prefix-system)
38
+ - [Agents](#agents)
39
+ - [Sending Prompts](#sending-prompts)
40
+ - [Attaching Files](#attaching-files)
41
+ - [Conversation Management](#conversation-management)
42
+ - [Git Integration](#git-integration)
43
+ - [Shell Command Tools](#shell-command-tools)
44
+ - [Session & Configuration](#session--configuration)
45
+ - [Skills](#skills)
46
+ - [Customizing Agent Behavior](#customizing-agent-behavior)
47
+ - [Semantic Search (Workspace)](#semantic-search-workspace)
48
+ - [Quick Reference: All `:` Commands](#quick-reference-all--commands)
49
+ - [Command-Line Options](#command-line-options)
50
+ - [Advanced Configuration](#advanced-configuration)
51
+ - [Provider Configuration](#provider-configuration)
52
+ - [Managing Provider Credentials](#managing-provider-credentials)
53
+ - [Deprecated: Environment Variables](#deprecated-environment-variables)
54
+ - [forge.yaml Configuration Options](#forgeyaml-configuration-options)
55
+ - [Environment Variables](#environment-variables)
56
+ - [MCP Configuration](#mcp-configuration)
57
+ - [Example Use Cases](#example-use-cases)
58
+ - [Usage in Multi-Agent Workflows](#usage-in-multi-agent-workflows)
59
+ - [Documentation](#documentation)
60
+ - [Community](#community)
61
+ - [Support Us](#support-us)
62
+
63
+ </details>
64
+
65
+ ---
66
+
67
+ ## Quickstart
68
+
69
+ To get started with Forge, run the command below:
70
+
71
+ ```bash
72
+ curl -fsSL https://forgecode.dev/cli | sh
73
+ ```
74
+
75
+ On first run, Forge will guide you through setting up your AI provider credentials using the interactive login flow. Alternatively, you can configure providers beforehand:
76
+
77
+ ```bash
78
+ # Configure your provider credentials interactively
79
+ forge provider login
80
+
81
+ # Then start Forge
82
+ forge
83
+ ```
84
+ That's it! Forge is now ready to assist you with your development tasks.
85
+
86
+ ## Usage Examples
87
+
88
+ Forge can be used in different ways depending on your needs. Here are some common usage patterns:
89
+
90
+ <details>
91
+ <summary><strong>Code Understanding</strong></summary>
92
+
93
+ ```
94
+ > Can you explain how the authentication system works in this codebase?
95
+ ```
96
+
97
+ Forge will analyze your project's structure, identify authentication-related files, and provide a detailed explanation of the authentication flow, including the relationships between different components.
98
+
99
+ </details>
100
+
101
+ <details>
102
+ <summary><strong>Implementing New Features</strong></summary>
103
+
104
+ ```
105
+ > I need to add a dark mode toggle to our React application. How should I approach this?
106
+ ```
107
+
108
+ Forge will suggest the best approach based on your current codebase, explain the steps needed, and even scaffold the necessary components and styles for you.
109
+
110
+ </details>
111
+
112
+ <details>
113
+ <summary><strong>Debugging Assistance</strong></summary>
114
+
115
+ ```
116
+ > I'm getting this error: "TypeError: Cannot read property 'map' of undefined". What might be causing it?
117
+ ```
118
+
119
+ Forge will analyze the error, suggest potential causes based on your code, and propose different solutions to fix the issue.
120
+
121
+ </details>
122
+
123
+ <details>
124
+ <summary><strong>Code Reviews</strong></summary>
125
+
126
+ ```
127
+ > Please review the code in src/components/UserProfile.js and suggest improvements
128
+ ```
129
+
130
+ Forge will analyze the code, identify potential issues, and suggest improvements for readability, performance, security, and maintainability.
131
+
132
+ </details>
133
+
134
+ <details>
135
+ <summary><strong>Learning New Technologies</strong></summary>
136
+
137
+ ```
138
+ > I want to integrate GraphQL into this Express application. Can you explain how to get started?
139
+ ```
140
+
141
+ Forge will provide a tailored tutorial on integrating GraphQL with Express, using your specific project structure as context.
142
+
143
+ </details>
144
+
145
+ <details>
146
+ <summary><strong>Database Schema Design</strong></summary>
147
+
148
+ ```
149
+ > I need to design a database schema for a blog with users, posts, comments, and categories
150
+ ```
151
+
152
+ Forge will suggest an appropriate schema design, including tables/collections, relationships, indexes, and constraints based on your project's existing database technology.
153
+
154
+ </details>
155
+
156
+ <details>
157
+ <summary><strong>Refactoring Legacy Code</strong></summary>
158
+
159
+ ```
160
+ > Help me refactor this class-based component to use React Hooks
161
+ ```
162
+
163
+ Forge can help modernize your codebase by walking you through refactoring steps and implementing them with your approval.
164
+
165
+ </details>
166
+
167
+ <details>
168
+ <summary><strong>Git Operations</strong></summary>
169
+
170
+ ```
171
+ > I need to merge branch 'feature/user-profile' into main but there are conflicts
172
+ ```
173
+
174
+ Forge can guide you through resolving git conflicts, explaining the differences and suggesting the best way to reconcile them.
175
+
176
+ </details>
177
+
178
+ ## Why Forge?
179
+
180
+ Forge is designed for developers who want to enhance their workflow with AI assistance while maintaining full control over their development environment.
181
+
182
+ - **Zero configuration** - Just add your API key and you're ready to go
183
+ - **Seamless integration** - Works right in your terminal, where you already work
184
+ - **Multi-provider support** - Use OpenAI, Anthropic, or other LLM providers
185
+ - **Secure by design** - Restricted shell mode limits file system access and prevents unintended changes
186
+ - **Open-source** - Transparent, extensible, and community-driven
187
+
188
+ Forge helps you code faster, solve complex problems, and learn new technologies without leaving your terminal.
189
+
190
+ ---
191
+
192
+ ## How Forge Works: Three Modes
193
+
194
+ Forge has three distinct ways to use it. Understanding this distinction upfront will save you confusion.
195
+
196
+ ### Interactive Mode (TUI)
197
+
198
+ Running `forge` with no arguments starts the interactive terminal UI, a persistent session where you type prompts and the AI responds in a conversational loop. This is the primary way to do multi-step work.
199
+
200
+ ```bash
201
+ forge # Start a new interactive session
202
+ forge conversation resume <id> # Resume a specific saved conversation in interactive mode
203
+ forge --conversation-id <id> # Same: resume conversation by ID
204
+ forge --agent <agent-id> # Start interactive session with a specific agent
205
+ forge -C /path/to/project # Start in a specific directory
206
+ forge --sandbox experiment-name # Create an isolated git worktree + branch, then start there
207
+ ```
208
+
209
+ Once inside interactive mode, type your prompt and press Enter. Forge reads files, writes patches, runs commands, and maintains context across the whole session.
210
+
211
+ ### One-Shot CLI Mode
212
+
213
+ Pass `-p` (or `--prompt`) to run a single prompt and exit. Forge does the work and returns to your shell. Useful for scripts, piping output, or quick tasks.
214
+
215
+ ```bash
216
+ forge -p "Explain the purpose of src/main.rs"
217
+ forge -p "Add error handling to the parse() function in lib.rs"
218
+ echo "What does this do?" | forge # Pipe input as the prompt
219
+ forge commit # Generate an AI commit message and commit (exits when done)
220
+ forge commit --preview # Generate commit message, print it, then exit
221
+ forge suggest "find large log files" # Translate natural language to a shell command, then exit
222
+ ```
223
+
224
+ > **Note:** `forge conversation resume <id>` opens the interactive TUI. It does **not** just print a message and exit. If you run it and see the cursor waiting, you are inside the interactive session. Type your prompt or press `Ctrl+C` to exit.
225
+
226
+ ### ZSH Plugin Mode (`:` prefix)
227
+
228
+ Install the ZSH plugin once with `forge setup`, then use `:` commands directly at your shell prompt without ever typing `forge`. This is the fastest mode for day-to-day development: send prompts, switch conversations, commit, and suggest commands without leaving your shell.
229
+
230
+ ```zsh
231
+ : refactor the auth module # Send a prompt to the active agent
232
+ :commit # AI-powered git commit
233
+ :suggest "find large log files" # Translate description → shell command in your buffer
234
+ :conversation # Browse saved conversations with interactive picker
235
+ ```
236
+
237
+ See the full [ZSH Plugin reference below](#zsh-plugin-the--prefix-system) for all commands and aliases.
238
+
239
+ ---
240
+
241
+ ## ZSH Plugin: The `:` Prefix System
242
+
243
+ When you install the ZSH plugin (`forge setup`), you get a `:` prefix command system at your shell prompt. This is the fastest way to use Forge during normal development; you never leave your shell.
244
+
245
+ **How it works:** Lines starting with `:` are intercepted before the shell sees them and routed to Forge. Everything else runs normally.
246
+
247
+ ```zsh
248
+ : <prompt> # Send a prompt to the active agent
249
+ :sage <prompt> # Send a prompt to a specific agent by name (sage, muse, forge, or any custom agent)
250
+ :agent <name> # Switch the active agent; opens interactive picker if no name given
251
+ ```
252
+
253
+ ### Agents
254
+
255
+ Forge ships with three built-in agents, each with a different role:
256
+
257
+ | Agent | Alias | Purpose | Modifies files? |
258
+ |---|---|---|---|
259
+ | `forge` | (default) | Implementation: builds features, fixes bugs, and runs tests | Yes |
260
+ | `sage` | `:ask` | Research: maps architecture, traces data flow, and reads code | No |
261
+ | `muse` | `:plan` | Planning: analyzes structure and writes implementation plans to `plans/` | No |
262
+
263
+ ### Sending Prompts
264
+
265
+ ```zsh
266
+ : refactor the auth module to use the new middleware
267
+ :sage how does the caching layer work? # sage = read-only research agent
268
+ :muse design a deployment strategy # muse = planning agent (writes to plans/)
269
+ :ask how does X work? # alias for :sage
270
+ :plan create a migration plan # alias for :muse
271
+ ```
272
+
273
+ The agent context persists. Typing `:sage` alone (no prompt text) switches the active agent to sage for all subsequent `: <prompt>` commands.
274
+
275
+ ### Attaching Files
276
+
277
+ Type `@` in a prompt, then press Tab to fuzzy-search and select files. The path is inserted as `@[filename]` and attached as context to the AI.
278
+
279
+ ```zsh
280
+ : review this code @[src/auth.rs] @[tests/auth_test.rs]
281
+ ```
282
+
283
+ ### Conversation Management
284
+
285
+ Forge saves every conversation. You can switch between them like switching directories.
286
+
287
+ ```zsh
288
+ :new # Start a fresh conversation (saves current for :conversation -)
289
+ :new <initial prompt> # Start a new conversation and immediately send a prompt
290
+ :conversation # Open interactive picker: browse and switch conversations with preview
291
+ :conversation <id> # Switch directly to a conversation by ID
292
+ :conversation - # Toggle between current and previous conversation (like cd -)
293
+ :clone # Branch the current conversation (try a different direction)
294
+ :clone <id> # Clone a specific conversation by ID
295
+ :rename <name> # Rename the current conversation
296
+ :conversation-rename # Rename a conversation via interactive picker
297
+ :retry # Retry the last prompt (useful if the AI misunderstood)
298
+ :copy # Copy the last AI response to clipboard as markdown
299
+ :dump # Export conversation as JSON
300
+ :dump html # Export conversation as formatted HTML
301
+ :compact # Manually compact context to free up token budget
302
+ ```
303
+
304
+ ### Git Integration
305
+
306
+ ```zsh
307
+ :commit # AI reads your diff, writes a commit message, and commits immediately
308
+ :commit <context> # Same, but pass extra context: :commit fix typo in readme
309
+ :commit-preview # AI generates the message and puts "git commit -m '...'" in your buffer
310
+ # so you can review/edit the message before pressing Enter
311
+ ```
312
+
313
+ ### Shell Command Tools
314
+
315
+ ```zsh
316
+ :suggest <description> # Translate natural language to a shell command and put it in your buffer
317
+ :edit # Open $EDITOR to compose a complex multi-line prompt, then send it
318
+ ```
319
+
320
+ ### Session & Configuration
321
+
322
+ Some commands change settings for the current session only. Others persist to your config file (`~/forge/.forge.toml`). The distinction matters:
323
+
324
+ ```zsh
325
+ # Session-only (reset when you close the terminal; not saved to config)
326
+ :model <model-id> # Change model for this session only
327
+ :reasoning-effort <level> # Set reasoning effort: none/minimal/low/medium/high/xhigh/max
328
+ :agent <id> # Switch active agent for this session
329
+
330
+ # Persistent (saved to config file)
331
+ :config-model <model-id> # Set default model globally (alias: :cm)
332
+ :config-provider # Switch provider globally (alias: :provider, :p)
333
+ :config-reasoning-effort <lvl> # Set default reasoning effort globally (alias: :cre)
334
+ :config-commit-model <id> # Set model used for :commit (alias: :ccm)
335
+ :config-suggest-model <id> # Set model used for :suggest (alias: :csm)
336
+ :config-reload # Reset session overrides back to global config (alias: :cr)
337
+
338
+ # View & edit config
339
+ :info # Show current session info (model, agent, conversation ID)
340
+ :config # Display effective resolved configuration in TOML format
341
+ :config-edit # Open config file in $EDITOR (alias: :ce)
342
+ :tools # List available tools for the current agent
343
+ :skill # List available skills
344
+ ```
345
+
346
+ ### Skills
347
+
348
+ Skills are reusable workflows the AI can invoke as tools. Forge ships three built-in skills:
349
+
350
+ - **`create-skill`**: scaffold a new custom skill
351
+ - **`execute-plan`**: execute a plan file from `plans/`
352
+ - **`github-pr-description`**: generate a PR description from your diff
353
+
354
+ Use `:skill` to list available skills. The AI invokes them automatically when relevant, or you can ask explicitly: `: generate a PR description using the github-pr-description skill`.
355
+
356
+ **Custom skills** live in `SKILL.md` files with YAML front-matter. Precedence (highest first):
357
+
358
+ | Location | Path | Scope |
359
+ |---|---|---|
360
+ | Project-local | `.forge/skills/<name>/SKILL.md` | This project only |
361
+ | Global | `~/forge/skills/<name>/SKILL.md` | All projects |
362
+ | Built-in | Embedded in binary | Always available |
363
+
364
+ Project-local skills override global ones, which override built-in ones. To scaffold a new skill, ask: `: create a new skill`.
365
+
366
+ ### Customizing Agent Behavior
367
+
368
+ **`AGENTS.md`:** Create this file in your project root (or `~/forge/AGENTS.md` globally) to give all agents persistent instructions such as coding conventions, commit message style, and things to avoid. Forge reads it automatically at the start of every conversation.
369
+
370
+ **Custom agents:** Place a `.md` file with YAML front-matter in `.forge/agents/` (project) or `~/forge/agents/` (global) to define additional agents with their own models, tools, and system prompts. Project-local agents override global ones. The built-in agent files in `crates/forge_repo/src/agents/` are good examples of the format.
371
+
372
+ **Custom commands:** Place YAML files in `.forge/commands/` (project) or `~/forge/commands/` (global) to define shortcut commands available via `:commandname`. Commands can also be defined inline in `forge.yaml` under the `commands:` key.
373
+
374
+ ### Semantic Search (Workspace)
375
+
376
+ ```zsh
377
+ :sync # Index your codebase for semantic search
378
+ :workspace-init # Initialize workspace for indexing
379
+ :workspace-status # Show indexing status
380
+ :workspace-info # Show workspace details
381
+ ```
382
+
383
+ After running `:sync`, the AI can search your codebase by meaning rather than exact text matches. Indexing sends file content to the workspace server, which defaults to `https://api.forgecode.dev`. Set `FORGE_WORKSPACE_SERVER_URL` to override this if self-hosting.
384
+
385
+ ### Quick Reference: All `:` Commands
386
+
387
+
388
+ | Command | Alias | What it does |
389
+ |---|---|---|
390
+ | `: <prompt>` | | Send prompt to active agent |
391
+ | `:new` | `:n` | Start new conversation |
392
+ | `:conversation` | `:c` | Browse/switch conversations (interactive picker) |
393
+ | `:conversation -` | | Toggle to previous conversation |
394
+ | `:clone` | | Branch current conversation |
395
+ | `:rename <name>` | `:rn` | Rename current conversation |
396
+ | `:conversation-rename` | | Rename conversation (interactive picker) |
397
+ | `:retry` | `:r` | Retry last prompt |
398
+ | `:copy` | | Copy last response to clipboard |
399
+ | `:dump` | `:d` | Export conversation as JSON |
400
+ | `:compact` | | Compact context |
401
+ | `:commit` | | AI commit (immediate) |
402
+ | `:commit-preview` | | AI commit (review first) |
403
+ | `:suggest <desc>` | `:s` | Translate natural language to command |
404
+ | `:edit` | `:ed` | Compose prompt in $EDITOR |
405
+ | `:sage <prompt>` | `:ask` | Q&A / code understanding agent |
406
+ | `:muse <prompt>` | `:plan` | Planning agent |
407
+ | `:agent <name>` | `:a` | Switch active agent (interactive picker if no name given) |
408
+ | `:model <id>` | `:m` | Set model for this session only |
409
+ | `:config-model <id>` | `:cm` | Set default model (persistent) |
410
+ | `:reasoning-effort <lvl>` | `:re` | Set reasoning effort for session |
411
+ | `:config-reload` | `:cr` | Reset session overrides to global config |
412
+ | `:info` | `:i` | Show session info |
413
+ | `:sync` | `:workspace-sync` | Index codebase for semantic search |
414
+ | `:tools` | `:t` | List available tools |
415
+ | `:skill` | | List available skills |
416
+ | `:login` | `:provider-login` | Login to a provider |
417
+ | `:logout` | | Logout from a provider |
418
+ | `:keyboard-shortcuts` | `:kb` | Show keyboard shortcuts |
419
+ | `:doctor` | | Run shell environment diagnostics |
420
+
421
+ ---
422
+
423
+ ## Command-Line Options
424
+
425
+ Here's a quick reference of Forge's command-line options:
426
+
427
+ | Option | Description |
428
+ | ----------------------------------- | ------------------------------------------------------------------------ |
429
+ | `-p, --prompt <PROMPT>` | Direct prompt to process without entering interactive mode |
430
+ | `-e, --event <EVENT>` | Dispatch an event to the workflow in JSON format |
431
+ | `--conversation <CONVERSATION>` | Path to a JSON file containing the conversation to execute |
432
+ | `--conversation-id <ID>` | Resume or continue an existing conversation by ID |
433
+ | `--agent <AGENT>` | Agent ID to use for this session |
434
+ | `-C, --directory <DIR>` | Change to this directory before starting |
435
+ | `--sandbox <NAME>` | Create an isolated git worktree + branch for safe experimentation |
436
+ | `--verbose` | Enable verbose logging output |
437
+ | `-h, --help` | Print help information |
438
+ | `-V, --version` | Print version |
439
+
440
+ ### Subcommands
441
+
442
+ ```bash
443
+ # Conversations
444
+ forge conversation list # List all saved conversations
445
+ forge conversation resume <id> # Resume a conversation in interactive mode
446
+ forge conversation new # Create a new conversation ID (prints it)
447
+ forge conversation dump <id> # Export conversation as JSON
448
+ forge conversation compact <id> # Compact conversation context
449
+ forge conversation retry <id> # Retry last message
450
+ forge conversation clone <id> # Clone a conversation
451
+ forge conversation rename <id> <name> # Rename a conversation
452
+ forge conversation delete <id> # Delete a conversation permanently
453
+ forge conversation info <id> # Show conversation details
454
+ forge conversation stats <id> # Show token usage statistics
455
+ forge conversation show <id> # Show last assistant message
456
+
457
+ # Commits
458
+ forge commit # Generate AI commit message and commit
459
+ forge commit --preview # Generate commit message only (prints it)
460
+ forge commit fix the auth bug # Pass extra context for the commit message
461
+
462
+ # Shell command suggestion
463
+ forge suggest "list files by size" # Translate description to a shell command
464
+
465
+ # Providers
466
+ forge provider login # Add or update provider credentials (interactive)
467
+ forge provider logout # Remove provider credentials
468
+ forge list provider # List supported providers
469
+
470
+ # Models & agents
471
+ forge list model # List available models
472
+ forge list agent # List available agents
473
+
474
+ # Workspace / semantic search
475
+ forge workspace sync # Index current directory for semantic search
476
+ forge workspace init # Initialize workspace
477
+ forge workspace status # Show indexing status
478
+ forge workspace query <text> # Query the semantic index
479
+
480
+ # MCP servers
481
+ forge mcp list # List configured MCP servers
482
+ forge mcp import # Add a server from JSON
483
+ forge mcp show # Show server configuration
484
+ forge mcp remove # Remove a server
485
+ forge mcp reload # Reload all servers and rebuild caches
486
+
487
+ # Other
488
+ forge info # Show config, active model, environment
489
+ forge list tool --agent <id> # List tools for a specific agent
490
+ forge doctor # Run shell environment diagnostics
491
+ forge update # Update forge to the latest version
492
+ forge setup # Install ZSH plugin (updates .zshrc)
493
+ ```
494
+
495
+ ## Advanced Configuration
496
+
497
+ ### Provider Configuration
498
+
499
+ Forge supports multiple AI providers. The recommended way to configure providers is using the interactive login command:
500
+
501
+ ```bash
502
+ forge provider login
503
+ ```
504
+
505
+ This will:
506
+
507
+ 1. Show you a list of available providers
508
+ 2. Guide you through entering the required credentials
509
+
510
+ #### Managing Provider Credentials
511
+
512
+ ```bash
513
+ # Login to a provider (add or update credentials)
514
+ forge provider login
515
+
516
+ # Remove provider credentials
517
+ forge provider logout
518
+
519
+ # List supported providers
520
+ forge provider list
521
+ ```
522
+
523
+ #### Deprecated: Environment Variables
524
+
525
+ > **⚠️ DEPRECATED**: Using `.env` files for provider configuration is deprecated and will be removed in a future version. Please use `forge provider login` instead.
526
+
527
+ For backward compatibility, Forge still supports environment variables. On first run, any credentials found in environment variables will be automatically migrated to file-based storage.
528
+
529
+ <details>
530
+ <summary><strong>Legacy Environment Variable Setup (Deprecated)</strong></summary>
531
+
532
+ <details>
533
+ <summary><strong>OpenRouter</strong></summary>
534
+
535
+ ```bash
536
+ # .env
537
+ OPENROUTER_API_KEY=<your_openrouter_api_key>
538
+ ```
539
+
540
+ </details>
541
+
542
+ <details>
543
+ <summary><strong>Requesty</strong></summary>
544
+
545
+ ```bash
546
+ # .env
547
+ REQUESTY_API_KEY=<your_requesty_api_key>
548
+ ```
549
+
550
+ </details>
551
+
552
+ <details>
553
+ <summary><strong>x-ai</strong></summary>
554
+
555
+ ```bash
556
+ # .env
557
+ XAI_API_KEY=<your_xai_api_key>
558
+ ```
559
+
560
+ </details>
561
+
562
+ <details>
563
+ <summary><strong>z.ai</strong></summary>
564
+
565
+ ```bash
566
+ # .env
567
+ ZAI_API_KEY=<your_zai_api_key>
568
+
569
+ # Or for coding plan subscription
570
+ ZAI_CODING_API_KEY=<your_zai_coding_api_key>
571
+ ```
572
+
573
+ </details>
574
+
575
+ <details>
576
+ <summary><strong>Cerebras</strong></summary>
577
+
578
+ ```bash
579
+ # .env
580
+ CEREBRAS_API_KEY=<your_cerebras_api_key>
581
+ ```
582
+
583
+ </details>
584
+
585
+ <details>
586
+ <summary><strong>Neuralwatt</strong></summary>
587
+
588
+ ```bash
589
+ # .env
590
+ NEURALWATT_API_KEY=<your_neuralwatt_api_key>
591
+ ```
592
+
593
+ </details>
594
+
595
+ <details>
596
+ <summary><strong>OrcaRouter</strong></summary>
597
+
598
+ ```bash
599
+ # .env
600
+ ORCAROUTER_API_KEY=<your_orcarouter_api_key>
601
+ ```
602
+
603
+ </details>
604
+
605
+ <details>
606
+ <summary><strong>Meta</strong></summary>
607
+
608
+ ```bash
609
+ # .env
610
+ META_API_KEY=<your_meta_model_api_key>
611
+ ```
612
+
613
+ ```yaml
614
+ # forge.yaml
615
+ model: muse-spark-1.1
616
+ ```
617
+
618
+ </details>
619
+
620
+ <details>
621
+ <summary><strong>IO Intelligence</strong></summary>
622
+
623
+ ```bash
624
+ # .env
625
+ IO_INTELLIGENCE_API_KEY=<your_io_intelligence_api_key>
626
+ ```
627
+
628
+ ```yaml
629
+ # forge.yaml
630
+ model: meta-llama/Llama-3.3-70B-Instruct
631
+ ```
632
+
633
+ </details>
634
+
635
+ <details>
636
+ <summary><strong>OpenAI</strong></summary>
637
+
638
+ ```bash
639
+ # .env
640
+ OPENAI_API_KEY=<your_openai_api_key>
641
+ ```
642
+
643
+ ```yaml
644
+ # forge.yaml
645
+ model: o3-mini-high
646
+ ```
647
+
648
+ </details>
649
+
650
+ <details>
651
+ <summary><strong>Anthropic</strong></summary>
652
+
653
+ ```bash
654
+ # .env
655
+ ANTHROPIC_API_KEY=<your_anthropic_api_key>
656
+ ```
657
+
658
+ ```yaml
659
+ # forge.yaml
660
+ model: claude-3.7-sonnet
661
+ ```
662
+
663
+ </details>
664
+
665
+ <details>
666
+ <summary><strong>Google Vertex AI</strong></summary>
667
+
668
+ **Setup Instructions:**
669
+
670
+ 1. **Install Google Cloud CLI** and authenticate:
671
+
672
+ ```bash
673
+ gcloud auth login
674
+ gcloud config set project YOUR_PROJECT_ID
675
+ ```
676
+
677
+ 2. **Get your authentication token**:
678
+
679
+ ```bash
680
+ gcloud auth print-access-token
681
+ ```
682
+
683
+ 3. **Use the token when logging in via Forge**:
684
+
685
+ ```bash
686
+ forge provider login
687
+ # Select Google Vertex AI and enter your credentials
688
+ ```
689
+
690
+ **Legacy `.env` setup:**
691
+
692
+ ```bash
693
+ # .env
694
+ PROJECT_ID=<your_project_id>
695
+ LOCATION=<your_location>
696
+ VERTEX_AI_AUTH_TOKEN=<your_auth_token>
697
+ ```
698
+
699
+ ```yaml
700
+ # forge.yaml
701
+ model: google/gemini-2.5-pro
702
+ ```
703
+
704
+ **Available Models:**
705
+ - Claude models: `claude-sonnet-4@20250514`
706
+ - Gemini models: `gemini-2.5-pro`, `gemini-2.0-flash`
707
+
708
+ Use the `/model` command in Forge CLI to see all available models.
709
+
710
+ </details>
711
+
712
+ <details>
713
+ <summary><strong>OpenAI-Compatible Providers</strong></summary>
714
+
715
+ ```bash
716
+ # .env
717
+ OPENAI_API_KEY=<your_provider_api_key>
718
+ OPENAI_URL=<your_provider_url>
719
+ ```
720
+
721
+ ```yaml
722
+ # forge.yaml
723
+ model: <provider-specific-model>
724
+ ```
725
+
726
+ </details>
727
+
728
+ <details>
729
+ <summary><strong>Groq</strong></summary>
730
+
731
+ ```bash
732
+ # .env
733
+ OPENAI_API_KEY=<your_groq_api_key>
734
+ OPENAI_URL=https://api.groq.com/openai/v1
735
+ ```
736
+
737
+ ```yaml
738
+ # forge.yaml
739
+ model: deepseek-r1-distill-llama-70b
740
+ ```
741
+
742
+ </details>
743
+
744
+ <details>
745
+ <summary><strong>Amazon Bedrock</strong></summary>
746
+
747
+ To use Amazon Bedrock models with Forge, you'll need to first set up the [Bedrock Access Gateway](https://github.com/aws-samples/bedrock-access-gateway):
748
+
749
+ 1. **Set up Bedrock Access Gateway**:
750
+
751
+ - Follow the deployment steps in the [Bedrock Access Gateway repo](https://github.com/aws-samples/bedrock-access-gateway)
752
+ - Create your own API key in Secrets Manager
753
+ - Deploy the CloudFormation stack
754
+ - Note your API Base URL from the CloudFormation outputs
755
+
756
+ 2. **Configure in Forge**:
757
+
758
+ ```bash
759
+ forge provider login
760
+ # Select OpenAI-compatible provider and enter your Bedrock Gateway details
761
+ ```
762
+
763
+ **Legacy `.env` setup:**
764
+
765
+ ```bash
766
+ # .env
767
+ OPENAI_API_KEY=<your_bedrock_gateway_api_key>
768
+ OPENAI_URL=<your_bedrock_gateway_base_url>
769
+ ```
770
+
771
+ ```yaml
772
+ # forge.yaml
773
+ model: anthropic.claude-3-opus
774
+ ```
775
+
776
+ </details>
777
+
778
+ <details>
779
+ <summary><strong>ForgeCode Services</strong></summary>
780
+
781
+ ```bash
782
+ # .env
783
+ FORGE_API_KEY=<your_forge_api_key>
784
+ ```
785
+
786
+ ```yaml
787
+ # forge.yaml
788
+ model: claude-3.7-sonnet
789
+ ```
790
+
791
+ </details>
792
+
793
+ </details>
794
+
795
+ ---
796
+
797
+ ### forge.yaml Configuration Options
798
+
799
+ ### Environment Variables
800
+
801
+ Forge supports several environment variables for advanced configuration and fine-tuning. These can be set in your `.env` file or system environment.
802
+
803
+ <details>
804
+ <summary><strong>Retry Configuration</strong></summary>
805
+
806
+ Control how Forge handles retry logic for failed requests:
807
+
808
+ ```bash
809
+ # .env
810
+ FORGE_RETRY_INITIAL_BACKOFF_MS=1000 # Initial backoff time in milliseconds (default: 1000)
811
+ FORGE_RETRY_BACKOFF_FACTOR=2 # Multiplier for backoff time (default: 2)
812
+ FORGE_RETRY_MAX_ATTEMPTS=3 # Maximum retry attempts (default: 3)
813
+ FORGE_SUPPRESS_RETRY_ERRORS=false # Suppress retry error messages (default: false)
814
+ FORGE_RETRY_STATUS_CODES=429,500,502 # HTTP status codes to retry (default: 429,500,502,503,504)
815
+ ```
816
+
817
+ </details>
818
+
819
+ <details>
820
+ <summary><strong>HTTP Configuration</strong></summary>
821
+
822
+ Fine-tune HTTP client behavior for API requests:
823
+
824
+ ```bash
825
+ # .env
826
+ FORGE_HTTP_CONNECT_TIMEOUT=30 # Connection timeout in seconds (default: 30)
827
+ FORGE_HTTP_READ_TIMEOUT=900 # Read timeout in seconds (default: 900)
828
+ FORGE_HTTP_POOL_IDLE_TIMEOUT=90 # Pool idle timeout in seconds (default: 90)
829
+ FORGE_HTTP_POOL_MAX_IDLE_PER_HOST=5 # Max idle connections per host (default: 5)
830
+ FORGE_HTTP_MAX_REDIRECTS=10 # Maximum redirects to follow (default: 10)
831
+ FORGE_HTTP_USE_HICKORY=false # Use Hickory DNS resolver (default: false)
832
+ FORGE_HTTP_TLS_BACKEND=default # TLS backend: "default" or "rustls" (default: "default")
833
+ FORGE_HTTP_MIN_TLS_VERSION=1.2 # Minimum TLS version: "1.0", "1.1", "1.2", "1.3"
834
+ FORGE_HTTP_MAX_TLS_VERSION=1.3 # Maximum TLS version: "1.0", "1.1", "1.2", "1.3"
835
+ FORGE_HTTP_ADAPTIVE_WINDOW=true # Enable HTTP/2 adaptive window (default: true)
836
+ FORGE_HTTP_KEEP_ALIVE_INTERVAL=60 # Keep-alive interval in seconds (default: 60, use "none"/"disabled" to disable)
837
+ FORGE_HTTP_KEEP_ALIVE_TIMEOUT=10 # Keep-alive timeout in seconds (default: 10)
838
+ FORGE_HTTP_KEEP_ALIVE_WHILE_IDLE=true # Keep-alive while idle (default: true)
839
+ FORGE_HTTP_ACCEPT_INVALID_CERTS=false # Accept invalid certificates (default: false) - USE WITH CAUTION
840
+ FORGE_HTTP_ROOT_CERT_PATHS=/path/to/cert1.pem,/path/to/cert2.crt # Paths to root certificate files (PEM, CRT, CER format), multiple paths separated by commas
841
+ ```
842
+
843
+ > **⚠️ Security Warning**: Setting `FORGE_HTTP_ACCEPT_INVALID_CERTS=true` disables SSL/TLS certificate verification, which can expose you to man-in-the-middle attacks. Only use this in development environments or when you fully trust the network and endpoints.
844
+
845
+ </details>
846
+
847
+ <details>
848
+ <summary><strong>API Configuration</strong></summary>
849
+
850
+ Override default API endpoints and provider/model settings:
851
+
852
+ ```bash
853
+ # .env
854
+ FORGE_API_URL=https://api.forgecode.dev # Custom Forge API URL (default: https://api.forgecode.dev)
855
+ FORGE_WORKSPACE_SERVER_URL=http://localhost:8080 # URL for the indexing server (default: https://api.forgecode.dev/)
856
+ ```
857
+
858
+ </details>
859
+
860
+ <details>
861
+ <summary><strong>Tool Configuration</strong></summary>
862
+
863
+ Configuring the tool calls settings:
864
+
865
+ ```bash
866
+ # .env
867
+ FORGE_TOOL_TIMEOUT=300 # Maximum execution time in seconds for a tool before it is terminated to prevent hanging the session. (default: 300)
868
+ FORGE_MAX_IMAGE_SIZE=10485760 # Maximum image file size in bytes for read_image operations (default: 10485760 - 10 MB)
869
+ FORGE_DUMP_AUTO_OPEN=false # Automatically open dump files in browser (default: false)
870
+ FORGE_DEBUG_REQUESTS=/path/to/debug/requests.json # Write debug HTTP request files to specified path (supports absolute and relative paths)
871
+ ```
872
+
873
+ </details>
874
+
875
+ <details>
876
+ <summary><strong>ZSH Plugin Configuration</strong></summary>
877
+
878
+ Configure the ZSH plugin behavior:
879
+
880
+ ```bash
881
+ # .env
882
+ FORGE_BIN=forge # Command to use for forge operations (default: "forge")
883
+ ```
884
+
885
+ The `FORGE_BIN` environment variable allows you to customize the command used by the ZSH plugin when transforming `:` prefixed commands. If not set, it defaults to `"forge"`.
886
+
887
+ </details>
888
+
889
+ <details>
890
+ <summary><strong>Display Configuration</strong></summary>
891
+
892
+ Configure display options for the Forge UI and ZSH theme:
893
+
894
+ ```bash
895
+ # .env
896
+ FORGE_CURRENCY_SYMBOL="$" # Currency symbol for cost display in ZSH theme (default: "$")
897
+ FORGE_CURRENCY_CONVERSION_RATE=1.0 # Conversion rate for currency display (default: 1.0)
898
+ NERD_FONT=1 # Enable Nerd Font icons in ZSH theme (default: auto-detected, set to "1" or "true" to enable, "0" or "false" to disable)
899
+ USE_NERD_FONT=1 # Alternative variable for enabling Nerd Font icons (same behavior as NERD_FONT)
900
+ ```
901
+
902
+ The `FORGE_CURRENCY_SYMBOL` and `FORGE_CURRENCY_CONVERSION_RATE` variables control how costs are displayed in the ZSH theme right prompt. Use these to customize the currency display for your region or preferred currency.
903
+
904
+ </details>
905
+
906
+ <details>
907
+ <summary><strong>System Configuration</strong></summary>
908
+
909
+ System-level environment variables (usually set automatically):
910
+
911
+ ```bash
912
+ # .env
913
+ FORGE_CONFIG=/custom/config/dir # Base directory for all Forge config files (default: ~/.forge)
914
+ FORGE_MAX_SEARCH_RESULT_BYTES=10240 # Maximum bytes for search results (default: 10240 - 10 KB)
915
+ FORGE_HISTORY_FILE=/path/to/history # Custom path for Forge history file (default: uses system default location)
916
+ FORGE_BANNER="Your custom banner text" # Custom banner text to display on startup (default: Forge ASCII art)
917
+ FORGE_MAX_CONVERSATIONS=100 # Maximum number of conversations to show in list (default: 100)
918
+ FORGE_MAX_LINE_LENGTH=2000 # Maximum characters per line for file read operations (default: 2000)
919
+ FORGE_STDOUT_MAX_LINE_LENGTH=2000 # Maximum characters per line for shell output (default: 2000)
920
+ SHELL=/bin/zsh # Shell to use for command execution (Unix/Linux/macOS)
921
+ COMSPEC=cmd.exe # Command processor to use (Windows)
922
+ ```
923
+
924
+ </details>
925
+
926
+ <details>
927
+ <summary><strong>Semantic Search Configuration</strong></summary>
928
+
929
+ Configure semantic search behavior for code understanding:
930
+
931
+ ```bash
932
+ # .env
933
+ FORGE_SEM_SEARCH_LIMIT=200 # Maximum number of results to return from initial vector search (default: 200)
934
+ FORGE_SEM_SEARCH_TOP_K=20 # Top-k parameter for relevance filtering during semantic search (default: 20)
935
+ ```
936
+
937
+ </details>
938
+
939
+ <details>
940
+ <summary><strong>Logging Configuration</strong></summary>
941
+
942
+ Configure logging verbosity and output:
943
+
944
+ ```bash
945
+ # .env
946
+ FORGE_LOG=forge=info # Log filter level (default: forge=debug when tracking disabled, forge=info when tracking enabled)
947
+ ```
948
+
949
+ The `FORGE_LOG` variable controls the logging level for Forge's internal operations using the standard tracing filter syntax. Common values:
950
+ - `forge=error` - Only errors
951
+ - `forge=warn` - Warnings and errors
952
+ - `forge=info` - Informational messages (default when tracking enabled)
953
+ - `forge=debug` - Debug information (default when tracking disabled)
954
+ - `forge=trace` - Detailed tracing
955
+
956
+ </details>
957
+
958
+ <details>
959
+ <summary><strong>Tracking Configuration</strong></summary>
960
+
961
+ Control tracking of user-identifying metadata in telemetry events:
962
+
963
+ ```bash
964
+ # .env
965
+ FORGE_TRACKER=false # Disable tracking enrichment metadata (default: true)
966
+ ```
967
+
968
+ The `FORGE_TRACKER` variable controls whether tracking enrichment metadata is included in telemetry events.
969
+
970
+ </details>
971
+
972
+ The `forge.yaml` file supports several advanced configuration options that let you customize Forge's behavior.
973
+
974
+ <details>
975
+ <summary><strong>Custom Rules</strong></summary>
976
+
977
+ Add your own guidelines that all agents should follow when generating responses.
978
+
979
+ ```yaml
980
+ # forge.yaml
981
+ custom_rules: |
982
+ 1. Always add comprehensive error handling to any code you write.
983
+ 2. Include unit tests for all new functions.
984
+ 3. Follow our team's naming convention: camelCase for variables, PascalCase for classes.
985
+ ```
986
+
987
+ </details>
988
+
989
+ <details>
990
+ <summary><strong>Commands</strong></summary>
991
+
992
+ Define custom commands as shortcuts for repetitive prompts:
993
+
994
+ ```yaml
995
+ # forge.yaml
996
+ commands:
997
+ - name: "refactor"
998
+ description: "Refactor selected code"
999
+ prompt: "Please refactor this code to improve readability and performance"
1000
+ ```
1001
+
1002
+ </details>
1003
+
1004
+ <details>
1005
+ <summary><strong>Model</strong></summary>
1006
+
1007
+ Specify the default AI model to use for all agents in the workflow.
1008
+
1009
+ ```yaml
1010
+ # forge.yaml
1011
+ model: "claude-3.7-sonnet"
1012
+ ```
1013
+
1014
+ </details>
1015
+
1016
+ <details>
1017
+ <summary><strong>Max Walker Depth</strong></summary>
1018
+
1019
+ Control how deeply Forge traverses your project directory structure when gathering context.
1020
+
1021
+ ```yaml
1022
+ # forge.yaml
1023
+ max_walker_depth: 3 # Limit directory traversal to 3 levels deep
1024
+ ```
1025
+
1026
+ </details>
1027
+
1028
+ <details>
1029
+ <summary><strong>Temperature</strong></summary>
1030
+
1031
+ Adjust the creativity and randomness in AI responses. Lower values (0.0-0.3) produce more focused, deterministic outputs, while higher values (0.7-2.0) generate more diverse and creative results.
1032
+
1033
+ ```yaml
1034
+ # forge.yaml
1035
+ temperature: 0.7 # Balanced creativity and focus
1036
+ ```
1037
+
1038
+ </details>
1039
+ <details>
1040
+ <summary><strong>Tool Max Failure Limit</strong></summary>
1041
+
1042
+ Control how many times a tool can fail before Forge forces completion to prevent infinite retry loops. This helps avoid situations where an agent gets stuck repeatedly trying the same failing operation.
1043
+
1044
+ ```yaml
1045
+ # forge.yaml
1046
+ max_tool_failure_per_turn: 3 # Allow up to 3 failures per tool before forcing completion
1047
+ ```
1048
+
1049
+ Set to a higher value if you want more retry attempts, or lower if you want faster failure detection.
1050
+
1051
+ </details>
1052
+
1053
+ <details>
1054
+ <summary><strong>Max Requests Per Turn</strong></summary>
1055
+
1056
+ Limit the maximum number of requests an agent can make in a single conversation turn. This prevents runaway conversations and helps control API usage and costs.
1057
+
1058
+ ```yaml
1059
+ # forge.yaml
1060
+ max_requests_per_turn: 50 # Allow up to 50 requests per turn
1061
+ ```
1062
+
1063
+ When this limit is reached, Forge will:
1064
+
1065
+ - Ask you if you wish to continue
1066
+ - If you respond with 'Yes', it will continue the conversation
1067
+ - If you respond with 'No', it will end the conversation
1068
+
1069
+ </details>
1070
+
1071
+ ---
1072
+
1073
+ <details>
1074
+ <summary><strong>Model Context Protocol (MCP)</strong></summary>
1075
+
1076
+ The MCP feature allows AI agents to communicate with external tools and services. This implementation follows Anthropic's [Model Context Protocol](https://docs.anthropic.com/en/docs/claude-code/tutorials#set-up-model-context-protocol-mcp) design.
1077
+
1078
+ ### MCP Configuration
1079
+
1080
+ Configure MCP servers using the CLI:
1081
+
1082
+ ```bash
1083
+ # List all MCP servers
1084
+ forge mcp list
1085
+
1086
+ # Import a server from JSON
1087
+ forge mcp import
1088
+
1089
+ # Show server configuration details
1090
+ forge mcp show
1091
+
1092
+ # Remove a server
1093
+ forge mcp remove
1094
+
1095
+ # Reload servers and rebuild caches
1096
+ forge mcp reload
1097
+ ```
1098
+
1099
+ Or manually create a `.mcp.json` file with the following structure:
1100
+
1101
+ ```json
1102
+ {
1103
+ "mcpServers": {
1104
+ "server_name": {
1105
+ "command": "command_to_execute",
1106
+ "args": ["arg1", "arg2"],
1107
+ "env": { "ENV_VAR": "value" }
1108
+ },
1109
+ "another_server": {
1110
+ "url": "http://localhost:3000/events"
1111
+ }
1112
+ }
1113
+ }
1114
+ ```
1115
+
1116
+ MCP configurations are read from two locations (project-local takes precedence):
1117
+
1118
+ 1. **Project-local:** `.mcp.json` in your project directory
1119
+ 2. **Global:** `~/forge/.mcp.json`
1120
+
1121
+ ### Example Use Cases
1122
+
1123
+ MCP can be used for various integrations:
1124
+
1125
+ - Web browser automation
1126
+ - External API interactions
1127
+ - Tool integration
1128
+ - Custom service connections
1129
+
1130
+ ### Usage in Multi-Agent Workflows
1131
+
1132
+ MCP tools can be used as part of multi-agent workflows, allowing specialized agents to interact with external systems as part of a collaborative problem-solving approach.
1133
+
1134
+ </details>
1135
+
1136
+ ---
1137
+
1138
+ ## Documentation
1139
+
1140
+ For comprehensive documentation on all features and capabilities, please visit the [documentation site](https://github.com/tailcallhq/forgecode/tree/main/docs).
1141
+
1142
+ ---
1143
+
1144
+ ## Installation
1145
+
1146
+ ```bash
1147
+ # YOLO
1148
+ curl -fsSL https://forgecode.dev/cli | sh
1149
+
1150
+ # Package managers
1151
+ nix run github:tailcallhq/forgecode # for latest dev branch
1152
+ ```
1153
+
1154
+ ---
1155
+
1156
+ ## Community
1157
+
1158
+ Join our vibrant Discord community to connect with other Forge users and contributors, get help with your projects, share ideas, and provide feedback!
1159
+
1160
+ [![Discord](https://img.shields.io/discord/1044859667798568962?style=for-the-badge&cacheSeconds=120&logo=discord)](https://discord.gg/kRZBPpkgwq)
1161
+
1162
+ ---
1163
+
1164
+ ## Support Us
1165
+
1166
+ Your support drives Forge's continued evolution! By starring our GitHub repository, you:
1167
+
1168
+ - Help others discover this powerful tool 🔍
1169
+ - Motivate our development team 💪
1170
+ - Enable us to prioritize new features 🛠️
1171
+ - Strengthen our open-source community 🌱
_config.yml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ remote_theme: pages-themes/hacker@v0.2.0
2
+ plugins:
3
+ - jekyll-remote-theme
clippy.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ disallowed-methods = [
2
+ { path = "std::string::String::from_utf8_lossy", reason = "Prefer bstr ByteSlice decoding so lossy UTF-8 handling stays explicit and consistent across byte-oriented paths.", replacement = "bstr::ByteSlice::to_str_lossy" },
3
+ ]
diesel.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [print_schema]
2
+ file = "crates/forge_repo/src/database/schema.rs"
3
+
4
+ [migrations_directory]
5
+ dir = "crates/forge_repo/src/database/migrations"
flake.lock ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodes": {
3
+ "nixpkgs": {
4
+ "locked": {
5
+ "lastModified": 1773282481,
6
+ "narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=",
7
+ "owner": "NixOS",
8
+ "repo": "nixpkgs",
9
+ "rev": "fe416aaedd397cacb33a610b33d60ff2b431b127",
10
+ "type": "github"
11
+ },
12
+ "original": {
13
+ "owner": "NixOS",
14
+ "ref": "nixos-unstable",
15
+ "repo": "nixpkgs",
16
+ "type": "github"
17
+ }
18
+ },
19
+ "root": {
20
+ "inputs": {
21
+ "nixpkgs": "nixpkgs"
22
+ }
23
+ }
24
+ },
25
+ "root": "root",
26
+ "version": 7
27
+ }
flake.nix ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ description = "forge: AI enabled pair programmer for Claude, GPT, O Series, Grok, Deepseek, Gemini and 300+ models";
3
+
4
+ inputs = {
5
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
+ };
7
+
8
+ outputs = { self, nixpkgs }:
9
+ let
10
+ systems = [
11
+ "x86_64-linux"
12
+ "aarch64-linux"
13
+ "x86_64-darwin"
14
+ "aarch64-darwin"
15
+ ];
16
+ forAllSystems = nixpkgs.lib.genAttrs systems;
17
+ in
18
+ {
19
+ formatter = forAllSystems (system:
20
+ let
21
+ pkgs = import nixpkgs { inherit system; };
22
+ in
23
+ pkgs.nixfmt-rfc-style
24
+ );
25
+
26
+ packages = forAllSystems (system:
27
+ let
28
+ pkgs = import nixpkgs { inherit system; };
29
+ lib = pkgs.lib;
30
+ src = lib.cleanSourceWith {
31
+ src = ./.;
32
+ filter = path: type:
33
+ lib.cleanSourceFilter path type
34
+ && baseNameOf path != "target"
35
+ && baseNameOf path != "result";
36
+ };
37
+ forge = pkgs.rustPlatform.buildRustPackage {
38
+ pname = "forge";
39
+ version = "0.1.0-dev";
40
+ inherit src;
41
+
42
+ cargoLock = {
43
+ lockFile = ./Cargo.lock;
44
+ allowBuiltinFetchGit = true;
45
+ };
46
+
47
+ cargoBuildFlags = [
48
+ "-p"
49
+ "forge_main"
50
+ "--bin"
51
+ "forge"
52
+ ];
53
+ cargoInstallFlags = [
54
+ "-p"
55
+ "forge_main"
56
+ "--bin"
57
+ "forge"
58
+ ];
59
+
60
+ nativeBuildInputs = [
61
+ pkgs.cmake
62
+ pkgs.nasm
63
+ pkgs.perl
64
+ pkgs.pkg-config
65
+ pkgs.protobuf
66
+ ];
67
+
68
+ buildInputs =
69
+ [ pkgs.sqlite ]
70
+ ++ lib.optionals pkgs.stdenv.isLinux [
71
+ pkgs.libxkbcommon
72
+ pkgs.libx11
73
+ pkgs.libxext
74
+ pkgs.libxfixes
75
+ pkgs.libxcb
76
+ pkgs.wayland
77
+ ]
78
+ ++ lib.optionals pkgs.stdenv.isDarwin [
79
+ pkgs.libiconv
80
+ pkgs.apple-sdk
81
+ ];
82
+
83
+ PROTOC = "${pkgs.protobuf}/bin/protoc";
84
+ PROTOC_INCLUDE = "${pkgs.protobuf}/include";
85
+ APP_VERSION = "0.1.0-dev";
86
+
87
+ doCheck = false;
88
+
89
+ meta = {
90
+ description = "forge: AI enabled pair programmer for Claude, GPT, O Series, Grok, Deepseek, Gemini and 300+ models";
91
+ homepage = "https://forgecode.dev";
92
+ license = lib.licenses.mit;
93
+ mainProgram = "forge";
94
+ platforms = lib.platforms.unix;
95
+ };
96
+ };
97
+ in
98
+ {
99
+ default = forge;
100
+ forge = forge;
101
+ }
102
+ );
103
+
104
+ apps = forAllSystems (system: {
105
+ default = {
106
+ type = "app";
107
+ program = "${self.packages.${system}.default}/bin/forge";
108
+ };
109
+ forge = {
110
+ type = "app";
111
+ program = "${self.packages.${system}.forge}/bin/forge";
112
+ };
113
+ });
114
+
115
+ devShells = forAllSystems (system:
116
+ let
117
+ pkgs = import nixpkgs { inherit system; };
118
+ lib = pkgs.lib;
119
+ in
120
+ {
121
+ default = pkgs.mkShell {
122
+ packages =
123
+ [
124
+ pkgs.cargo
125
+ pkgs.cargo-insta
126
+ pkgs.cargo-llvm-cov
127
+ pkgs.clippy
128
+ pkgs.cmake
129
+ pkgs.nasm
130
+ pkgs.perl
131
+ pkgs.pkg-config
132
+ pkgs.protobuf
133
+ pkgs.rust-analyzer
134
+ pkgs.rustc
135
+ pkgs.rustfmt
136
+ pkgs.sqlite
137
+ ]
138
+ ++ lib.optionals pkgs.stdenv.isLinux [
139
+ pkgs.libxkbcommon
140
+ pkgs.libx11
141
+ pkgs.libxext
142
+ pkgs.libxfixes
143
+ pkgs.libxcb
144
+ pkgs.wayland
145
+ ]
146
+ ++ lib.optionals pkgs.stdenv.isDarwin [
147
+ pkgs.libiconv
148
+ pkgs.darwin.apple_sdk.frameworks.AppKit
149
+ pkgs.darwin.apple_sdk.frameworks.CoreGraphics
150
+ pkgs.darwin.apple_sdk.frameworks.Foundation
151
+ ];
152
+
153
+ PROTOC = "${pkgs.protobuf}/bin/protoc";
154
+ PROTOC_INCLUDE = "${pkgs.protobuf}/include";
155
+ APP_VERSION = "0.1.0-dev";
156
+ };
157
+ });
158
+ };
159
+ }
forge.schema.json ADDED
@@ -0,0 +1,1037 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "ForgeConfig",
4
+ "description": "Top-level Forge configuration merged from all sources (defaults, file,\nenvironment).",
5
+ "type": "object",
6
+ "properties": {
7
+ "auto_dump": {
8
+ "description": "Format used when automatically creating a session dump after task\ncompletion; disabled when absent.",
9
+ "anyOf": [
10
+ {
11
+ "$ref": "#/$defs/AutoDumpFormat"
12
+ },
13
+ {
14
+ "type": "null"
15
+ }
16
+ ]
17
+ },
18
+ "auto_install_vscode_extension": {
19
+ "description": "Enables automatic VS Code extension installation when Forge runs inside\nVS Code and the extension is not already installed.",
20
+ "type": "boolean",
21
+ "default": false
22
+ },
23
+ "auto_open_dump": {
24
+ "description": "Whether to automatically open HTML dump files in the browser after\ncreation.",
25
+ "type": "boolean",
26
+ "default": false
27
+ },
28
+ "commit": {
29
+ "description": "Model and provider configuration used for commit message generation.",
30
+ "anyOf": [
31
+ {
32
+ "$ref": "#/$defs/ModelConfig"
33
+ },
34
+ {
35
+ "type": "null"
36
+ }
37
+ ]
38
+ },
39
+ "compact": {
40
+ "description": "Context compaction settings applied to all agents; falls back to each\nagent's individual setting when absent.",
41
+ "anyOf": [
42
+ {
43
+ "$ref": "#/$defs/Compact"
44
+ },
45
+ {
46
+ "type": "null"
47
+ }
48
+ ]
49
+ },
50
+ "currency_conversion_rate": {
51
+ "description": "Conversion rate applied to costs before display in the shell rprompt.\nThe raw USD cost is multiplied by this value, allowing costs to be shown\nin a local currency. Defaults to `1.0` (no conversion).",
52
+ "$ref": "#/$defs/double",
53
+ "default": 0.0
54
+ },
55
+ "currency_symbol": {
56
+ "description": "Currency symbol displayed in the shell rprompt next to the session cost\n(e.g. `\"$\"`, `\"€\"`, `\"₹\"`). Defaults to `\"$\"`.",
57
+ "type": "string",
58
+ "default": ""
59
+ },
60
+ "custom_history_path": {
61
+ "description": "Path to the conversation history file; defaults to the global history\nlocation when absent.",
62
+ "type": [
63
+ "string",
64
+ "null"
65
+ ]
66
+ },
67
+ "debug_requests": {
68
+ "description": "Directory where debug request files are written; disabled when absent.",
69
+ "type": [
70
+ "string",
71
+ "null"
72
+ ]
73
+ },
74
+ "http": {
75
+ "description": "HTTP client settings including proxy, TLS, and timeout configuration.",
76
+ "anyOf": [
77
+ {
78
+ "$ref": "#/$defs/HttpConfig"
79
+ },
80
+ {
81
+ "type": "null"
82
+ }
83
+ ]
84
+ },
85
+ "max_commit_count": {
86
+ "description": "Maximum number of recent commits included as context for commit message\ngeneration.",
87
+ "type": "integer",
88
+ "format": "uint",
89
+ "default": 0,
90
+ "minimum": 0
91
+ },
92
+ "max_conversations": {
93
+ "description": "Maximum number of conversations shown in the conversation list.",
94
+ "type": "integer",
95
+ "format": "uint",
96
+ "default": 0,
97
+ "minimum": 0
98
+ },
99
+ "max_extensions": {
100
+ "description": "Maximum number of file extensions included in the agent system prompt.",
101
+ "type": "integer",
102
+ "format": "uint",
103
+ "default": 0,
104
+ "minimum": 0
105
+ },
106
+ "max_fetch_chars": {
107
+ "description": "Maximum number of characters returned from a URL fetch.",
108
+ "type": "integer",
109
+ "format": "uint",
110
+ "default": 0,
111
+ "minimum": 0
112
+ },
113
+ "max_file_read_batch_size": {
114
+ "description": "Maximum number of files read in a single batch operation.",
115
+ "type": "integer",
116
+ "format": "uint",
117
+ "default": 0,
118
+ "minimum": 0
119
+ },
120
+ "max_file_size_bytes": {
121
+ "description": "Maximum file size in bytes permitted for read operations.",
122
+ "type": "integer",
123
+ "format": "uint64",
124
+ "default": 0,
125
+ "minimum": 0
126
+ },
127
+ "max_image_size_bytes": {
128
+ "description": "Maximum image file size in bytes permitted for read operations.",
129
+ "type": "integer",
130
+ "format": "uint64",
131
+ "default": 0,
132
+ "minimum": 0
133
+ },
134
+ "max_line_chars": {
135
+ "description": "Maximum number of characters per line when reading a file.",
136
+ "type": "integer",
137
+ "format": "uint",
138
+ "default": 0,
139
+ "minimum": 0
140
+ },
141
+ "max_parallel_file_reads": {
142
+ "description": "Maximum number of files read concurrently during batch operations.",
143
+ "type": "integer",
144
+ "format": "uint",
145
+ "default": 0,
146
+ "minimum": 0
147
+ },
148
+ "max_read_lines": {
149
+ "description": "Maximum number of lines read from a file in a single operation.",
150
+ "type": "integer",
151
+ "format": "uint64",
152
+ "default": 0,
153
+ "minimum": 0
154
+ },
155
+ "max_requests_per_turn": {
156
+ "description": "Maximum number of requests that can be made in a single turn.",
157
+ "type": [
158
+ "integer",
159
+ "null"
160
+ ],
161
+ "format": "uint",
162
+ "minimum": 0
163
+ },
164
+ "max_search_lines": {
165
+ "description": "Maximum number of lines returned by a single file search operation.",
166
+ "type": "integer",
167
+ "format": "uint",
168
+ "default": 0,
169
+ "minimum": 0
170
+ },
171
+ "max_search_result_bytes": {
172
+ "description": "Maximum number of bytes returned by a single file search operation.",
173
+ "type": "integer",
174
+ "format": "uint",
175
+ "default": 0,
176
+ "minimum": 0
177
+ },
178
+ "max_sem_search_results": {
179
+ "description": "Maximum number of candidate results returned from the initial semantic\nsearch vector query.",
180
+ "type": "integer",
181
+ "format": "uint",
182
+ "default": 0,
183
+ "minimum": 0
184
+ },
185
+ "max_stdout_line_chars": {
186
+ "description": "Maximum number of characters per line in shell command output.",
187
+ "type": "integer",
188
+ "format": "uint",
189
+ "default": 0,
190
+ "minimum": 0
191
+ },
192
+ "max_stdout_prefix_lines": {
193
+ "description": "Maximum number of lines captured from the leading portion of shell\ncommand output.",
194
+ "type": "integer",
195
+ "format": "uint",
196
+ "default": 0,
197
+ "minimum": 0
198
+ },
199
+ "max_stdout_suffix_lines": {
200
+ "description": "Maximum number of lines captured from the trailing portion of shell\ncommand output.",
201
+ "type": "integer",
202
+ "format": "uint",
203
+ "default": 0,
204
+ "minimum": 0
205
+ },
206
+ "max_tokens": {
207
+ "description": "Maximum tokens the model may generate per response for all agents\n(1–100,000).",
208
+ "type": [
209
+ "integer",
210
+ "null"
211
+ ],
212
+ "format": "uint32",
213
+ "minimum": 0
214
+ },
215
+ "max_tool_failure_per_turn": {
216
+ "description": "Maximum tool failures per turn before the orchestrator forces\ncompletion.",
217
+ "type": [
218
+ "integer",
219
+ "null"
220
+ ],
221
+ "format": "uint",
222
+ "minimum": 0
223
+ },
224
+ "merge_system_messages": {
225
+ "description": "When `true`, all system messages in the conversation are merged into a\nsingle leading system message before the request is sent. Enable this\nfor providers that reject requests containing system messages after\nuser or assistant turns (e.g. vLLM, NVIDIA NIM).",
226
+ "type": "boolean",
227
+ "default": false
228
+ },
229
+ "model_cache_ttl_secs": {
230
+ "description": "Time-to-live in seconds for the cached model API list.",
231
+ "type": "integer",
232
+ "format": "uint64",
233
+ "default": 0,
234
+ "minimum": 0
235
+ },
236
+ "providers": {
237
+ "description": "Additional provider definitions merged with the built-in provider list.\n\nEntries with an `id` matching a built-in provider override its fields;\nentries with a new `id` are appended and become available for model\nselection.",
238
+ "type": "array",
239
+ "items": {
240
+ "$ref": "#/$defs/ProviderEntry"
241
+ }
242
+ },
243
+ "reasoning": {
244
+ "description": "Reasoning configuration applied to all agents; controls effort level,\ntoken budget, and visibility of the model's thinking process.",
245
+ "anyOf": [
246
+ {
247
+ "$ref": "#/$defs/ReasoningConfig"
248
+ },
249
+ {
250
+ "type": "null"
251
+ }
252
+ ]
253
+ },
254
+ "research_subagent": {
255
+ "description": "Whether the deep research agent is available.\n\nWhen set to `true`, the Sage agent is added to the agent list and\nthe `:sage` app command is enabled. Defaults to `false`.",
256
+ "type": "boolean",
257
+ "default": false
258
+ },
259
+ "restricted": {
260
+ "description": "Whether restricted mode is active; when enabled, tool execution requires\nexplicit permission grants.",
261
+ "type": "boolean",
262
+ "default": false
263
+ },
264
+ "retry": {
265
+ "description": "Retry settings applied at the system level to all IO operations.",
266
+ "anyOf": [
267
+ {
268
+ "$ref": "#/$defs/RetryConfig"
269
+ },
270
+ {
271
+ "type": "null"
272
+ }
273
+ ]
274
+ },
275
+ "sem_search_top_k": {
276
+ "description": "Number of top results retained after re-ranking in semantic search.",
277
+ "type": "integer",
278
+ "format": "uint",
279
+ "default": 0,
280
+ "minimum": 0
281
+ },
282
+ "services_url": {
283
+ "description": "Base URL of the Forge services API used for semantic search and\nindexing.",
284
+ "type": "string",
285
+ "default": ""
286
+ },
287
+ "session": {
288
+ "description": "Default model and provider configuration used when not overridden by\nindividual agents.",
289
+ "anyOf": [
290
+ {
291
+ "$ref": "#/$defs/ModelConfig"
292
+ },
293
+ {
294
+ "type": "null"
295
+ }
296
+ ]
297
+ },
298
+ "subagents": {
299
+ "description": "Enables subagent support via the task tool; when true the forge agent\ngains access to the `task` tool for delegating work to specialised\nsub-agents, and the `sage` research-only agent tool is removed.\nWhen false the `task` tool is disabled and `sage` is available instead.",
300
+ "type": "boolean",
301
+ "default": false
302
+ },
303
+ "suggest": {
304
+ "description": "Model and provider configuration used for shell command suggestion\ngeneration.",
305
+ "anyOf": [
306
+ {
307
+ "$ref": "#/$defs/ModelConfig"
308
+ },
309
+ {
310
+ "type": "null"
311
+ }
312
+ ]
313
+ },
314
+ "temperature": {
315
+ "description": "Output randomness for all agents; lower values are deterministic, higher\nvalues are creative (0.0–2.0).",
316
+ "anyOf": [
317
+ {
318
+ "$ref": "#/$defs/double"
319
+ },
320
+ {
321
+ "type": "null"
322
+ }
323
+ ]
324
+ },
325
+ "tool_supported": {
326
+ "description": "Whether tool use is supported in the current environment; when false,\nall tool calls are disabled.",
327
+ "type": "boolean",
328
+ "default": false
329
+ },
330
+ "tool_timeout_secs": {
331
+ "description": "Maximum time in seconds a single tool call may run before being\ncancelled.",
332
+ "type": "integer",
333
+ "format": "uint64",
334
+ "default": 0,
335
+ "minimum": 0
336
+ },
337
+ "top_k": {
338
+ "description": "Top-k vocabulary cutoff for all agents; restricts sampling to the k\nhighest-probability tokens (1–1000).",
339
+ "type": [
340
+ "integer",
341
+ "null"
342
+ ],
343
+ "format": "uint32",
344
+ "minimum": 0
345
+ },
346
+ "top_p": {
347
+ "description": "Nucleus sampling threshold for all agents; limits token selection to the\ntop cumulative probability mass (0.0–1.0).",
348
+ "anyOf": [
349
+ {
350
+ "$ref": "#/$defs/double"
351
+ },
352
+ {
353
+ "type": "null"
354
+ }
355
+ ]
356
+ },
357
+ "updates": {
358
+ "description": "Configuration for automatic Forge updates.",
359
+ "anyOf": [
360
+ {
361
+ "$ref": "#/$defs/Update"
362
+ },
363
+ {
364
+ "type": "null"
365
+ }
366
+ ]
367
+ },
368
+ "use_forge_committer": {
369
+ "description": "Whether `forge commit` should override `GIT_COMMITTER_NAME` and\n`GIT_COMMITTER_EMAIL` with the Forge identity. Defaults to `true` via\nthe embedded `.forge.toml` defaults.",
370
+ "type": "boolean",
371
+ "default": false
372
+ },
373
+ "use_text_patch_fallback": {
374
+ "description": "Switches patch replacement fallback from the legacy fuzzy-search range\nlookup to the newer text-patch gRPC API.\nDefaults to `false` so patching continues to use the legacy fallback\nbehavior unless explicitly enabled in `forge.toml`.",
375
+ "type": "boolean",
376
+ "default": false
377
+ },
378
+ "verify_todos": {
379
+ "description": "Enables the pending todos hook that checks for incomplete todo items\nwhen a task ends and reminds the LLM about them.",
380
+ "type": "boolean",
381
+ "default": false
382
+ }
383
+ },
384
+ "$defs": {
385
+ "AutoDumpFormat": {
386
+ "description": "The output format used when auto-dumping a conversation on task completion.",
387
+ "oneOf": [
388
+ {
389
+ "description": "Dump as a JSON file",
390
+ "type": "string",
391
+ "const": "json"
392
+ },
393
+ {
394
+ "description": "Dump as an HTML file",
395
+ "type": "string",
396
+ "const": "html"
397
+ }
398
+ ]
399
+ },
400
+ "Compact": {
401
+ "description": "Configuration for automatic context compaction for all agents",
402
+ "type": "object",
403
+ "properties": {
404
+ "eviction_window": {
405
+ "description": "Maximum percentage of the context that can be summarized during\ncompaction. Valid values are between 0.0 and 1.0, where 0.0 means no\ncompaction and 1.0 allows summarizing all messages. Works alongside\nretention_window - the more conservative limit (fewer messages to\ncompact) takes precedence.",
406
+ "$ref": "#/$defs/double",
407
+ "default": 0.0
408
+ },
409
+ "max_tokens": {
410
+ "description": "Maximum number of tokens to keep after compaction",
411
+ "type": [
412
+ "integer",
413
+ "null"
414
+ ],
415
+ "format": "uint",
416
+ "minimum": 0
417
+ },
418
+ "message_threshold": {
419
+ "description": "Maximum number of messages before triggering compaction",
420
+ "type": [
421
+ "integer",
422
+ "null"
423
+ ],
424
+ "format": "uint",
425
+ "minimum": 0
426
+ },
427
+ "model": {
428
+ "description": "Model ID to use for compaction, useful when compacting with a\ncheaper/faster model. If not specified, the root level model will be\nused.",
429
+ "type": [
430
+ "string",
431
+ "null"
432
+ ]
433
+ },
434
+ "on_turn_end": {
435
+ "description": "Whether to trigger compaction when the last message is from a user",
436
+ "type": [
437
+ "boolean",
438
+ "null"
439
+ ]
440
+ },
441
+ "retention_window": {
442
+ "description": "Number of most recent messages to preserve during compaction.\nThese messages won't be considered for summarization. Works alongside\neviction_window - the more conservative limit (fewer messages to\ncompact) takes precedence.",
443
+ "type": "integer",
444
+ "format": "uint",
445
+ "default": 0,
446
+ "minimum": 0
447
+ },
448
+ "token_threshold": {
449
+ "description": "Maximum number of tokens before triggering compaction. This acts as an\nabsolute cap and is combined with\n`token_threshold_percentage` by taking the lower value.",
450
+ "type": [
451
+ "integer",
452
+ "null"
453
+ ],
454
+ "format": "uint",
455
+ "minimum": 0
456
+ },
457
+ "token_threshold_percentage": {
458
+ "description": "Maximum percentage of the model context window used to derive the token\nthreshold before triggering compaction. This is combined with\n`token_threshold` by taking the lower value.",
459
+ "anyOf": [
460
+ {
461
+ "$ref": "#/$defs/double"
462
+ },
463
+ {
464
+ "type": "null"
465
+ }
466
+ ]
467
+ },
468
+ "turn_threshold": {
469
+ "description": "Maximum number of conversation turns before triggering compaction",
470
+ "type": [
471
+ "integer",
472
+ "null"
473
+ ],
474
+ "format": "uint",
475
+ "minimum": 0
476
+ }
477
+ }
478
+ },
479
+ "Effort": {
480
+ "description": "Effort level for model reasoning.",
481
+ "oneOf": [
482
+ {
483
+ "description": "No reasoning; skips the thinking step entirely.",
484
+ "type": "string",
485
+ "const": "none"
486
+ },
487
+ {
488
+ "description": "Minimal reasoning; fastest and cheapest.",
489
+ "type": "string",
490
+ "const": "minimal"
491
+ },
492
+ {
493
+ "description": "Low reasoning effort.",
494
+ "type": "string",
495
+ "const": "low"
496
+ },
497
+ {
498
+ "description": "Medium reasoning effort; the default for most providers.",
499
+ "type": "string",
500
+ "const": "medium"
501
+ },
502
+ {
503
+ "description": "High reasoning effort.",
504
+ "type": "string",
505
+ "const": "high"
506
+ },
507
+ {
508
+ "description": "Extra-high reasoning effort (OpenAI / OpenRouter).",
509
+ "type": "string",
510
+ "const": "xhigh"
511
+ },
512
+ {
513
+ "description": "Maximum reasoning effort; only available on select Anthropic models.",
514
+ "type": "string",
515
+ "const": "max"
516
+ }
517
+ ]
518
+ },
519
+ "HttpConfig": {
520
+ "description": "HTTP client configuration.",
521
+ "type": "object",
522
+ "properties": {
523
+ "accept_invalid_certs": {
524
+ "description": "Accept invalid certificates",
525
+ "type": "boolean"
526
+ },
527
+ "adaptive_window": {
528
+ "description": "Adaptive window sizing for improved flow control",
529
+ "type": "boolean"
530
+ },
531
+ "connect_timeout_secs": {
532
+ "type": "integer",
533
+ "format": "uint64",
534
+ "minimum": 0
535
+ },
536
+ "hickory": {
537
+ "type": "boolean"
538
+ },
539
+ "keep_alive_interval_secs": {
540
+ "description": "Keep-alive interval in seconds",
541
+ "type": [
542
+ "integer",
543
+ "null"
544
+ ],
545
+ "format": "uint64",
546
+ "minimum": 0
547
+ },
548
+ "keep_alive_timeout_secs": {
549
+ "description": "Keep-alive timeout in seconds",
550
+ "type": "integer",
551
+ "format": "uint64",
552
+ "minimum": 0
553
+ },
554
+ "keep_alive_while_idle": {
555
+ "description": "Keep-alive while connection is idle",
556
+ "type": "boolean"
557
+ },
558
+ "max_redirects": {
559
+ "type": "integer",
560
+ "format": "uint",
561
+ "minimum": 0
562
+ },
563
+ "max_tls_version": {
564
+ "description": "Maximum TLS protocol version to use",
565
+ "anyOf": [
566
+ {
567
+ "$ref": "#/$defs/TlsVersion"
568
+ },
569
+ {
570
+ "type": "null"
571
+ }
572
+ ]
573
+ },
574
+ "min_tls_version": {
575
+ "description": "Minimum TLS protocol version to use",
576
+ "anyOf": [
577
+ {
578
+ "$ref": "#/$defs/TlsVersion"
579
+ },
580
+ {
581
+ "type": "null"
582
+ }
583
+ ]
584
+ },
585
+ "pool_idle_timeout_secs": {
586
+ "type": "integer",
587
+ "format": "uint64",
588
+ "minimum": 0
589
+ },
590
+ "pool_max_idle_per_host": {
591
+ "type": "integer",
592
+ "format": "uint",
593
+ "minimum": 0
594
+ },
595
+ "read_timeout_secs": {
596
+ "type": "integer",
597
+ "format": "uint64",
598
+ "minimum": 0
599
+ },
600
+ "root_cert_paths": {
601
+ "description": "Paths to root certificate files",
602
+ "type": [
603
+ "array",
604
+ "null"
605
+ ],
606
+ "items": {
607
+ "type": "string"
608
+ }
609
+ },
610
+ "tls_backend": {
611
+ "$ref": "#/$defs/TlsBackend"
612
+ }
613
+ },
614
+ "required": [
615
+ "connect_timeout_secs",
616
+ "read_timeout_secs",
617
+ "pool_idle_timeout_secs",
618
+ "pool_max_idle_per_host",
619
+ "max_redirects",
620
+ "hickory",
621
+ "tls_backend",
622
+ "adaptive_window",
623
+ "keep_alive_timeout_secs",
624
+ "keep_alive_while_idle",
625
+ "accept_invalid_certs"
626
+ ]
627
+ },
628
+ "InputModality": {
629
+ "description": "Represents input modalities that a model can accept",
630
+ "oneOf": [
631
+ {
632
+ "description": "Text input (all models support this)",
633
+ "type": "string",
634
+ "const": "text"
635
+ },
636
+ {
637
+ "description": "Image input (vision-capable models)",
638
+ "type": "string",
639
+ "const": "image"
640
+ }
641
+ ]
642
+ },
643
+ "Model": {
644
+ "type": "object",
645
+ "properties": {
646
+ "context_length": {
647
+ "type": [
648
+ "integer",
649
+ "null"
650
+ ],
651
+ "format": "uint64",
652
+ "minimum": 0
653
+ },
654
+ "description": {
655
+ "type": [
656
+ "string",
657
+ "null"
658
+ ]
659
+ },
660
+ "id": {
661
+ "type": "string"
662
+ },
663
+ "input_modalities": {
664
+ "description": "Input modalities supported by the model (defaults to text-only)",
665
+ "type": "array",
666
+ "default": [
667
+ "text"
668
+ ],
669
+ "items": {
670
+ "$ref": "#/$defs/InputModality"
671
+ }
672
+ },
673
+ "name": {
674
+ "type": [
675
+ "string",
676
+ "null"
677
+ ]
678
+ },
679
+ "supports_parallel_tool_calls": {
680
+ "description": "Whether the model supports parallel tool calls",
681
+ "type": [
682
+ "boolean",
683
+ "null"
684
+ ]
685
+ },
686
+ "supports_reasoning": {
687
+ "description": "Whether the model supports reasoning",
688
+ "type": [
689
+ "boolean",
690
+ "null"
691
+ ]
692
+ },
693
+ "tools_supported": {
694
+ "type": [
695
+ "boolean",
696
+ "null"
697
+ ]
698
+ }
699
+ },
700
+ "required": [
701
+ "id"
702
+ ]
703
+ },
704
+ "ModelConfig": {
705
+ "description": "Pairs a provider and model together for a specific operation.",
706
+ "type": "object",
707
+ "properties": {
708
+ "model_id": {
709
+ "description": "The model to use for this operation.",
710
+ "type": "string"
711
+ },
712
+ "provider_id": {
713
+ "description": "The provider to use for this operation.",
714
+ "type": "string"
715
+ }
716
+ },
717
+ "required": [
718
+ "provider_id",
719
+ "model_id"
720
+ ]
721
+ },
722
+ "ModelListConfig": {
723
+ "description": "Source of models for a provider: either a URL to fetch them from or a\nstatic list defined inline.",
724
+ "anyOf": [
725
+ {
726
+ "description": "URL template used to fetch the model list dynamically.",
727
+ "type": "string"
728
+ },
729
+ {
730
+ "description": "A static list of models defined directly in the configuration.",
731
+ "type": "array",
732
+ "items": {
733
+ "$ref": "#/$defs/Model"
734
+ }
735
+ }
736
+ ]
737
+ },
738
+ "ProviderAuthMethod": {
739
+ "description": "Authentication method supported by a provider.\n\nOnly the simple (non-OAuth) methods are available here; providers that\nrequire OAuth device or authorization-code flows must be configured via the\nfile-based `provider.json` override instead.",
740
+ "type": "string",
741
+ "enum": [
742
+ "api_key",
743
+ "google_adc"
744
+ ]
745
+ },
746
+ "ProviderEntry": {
747
+ "description": "A single provider entry defined inline in `forge.toml`.\n\nInline providers are merged with the built-in provider list; entries with\nthe same `id` override the corresponding built-in entry field-by-field,\nwhile entries with a new `id` are appended to the list.",
748
+ "type": "object",
749
+ "properties": {
750
+ "api_key_var": {
751
+ "description": "Environment variable holding the API key for this provider.",
752
+ "type": [
753
+ "string",
754
+ "null"
755
+ ]
756
+ },
757
+ "auth_methods": {
758
+ "description": "Authentication methods supported by this provider; defaults to\n`[\"api_key\"]` when omitted.",
759
+ "type": "array",
760
+ "items": {
761
+ "$ref": "#/$defs/ProviderAuthMethod"
762
+ }
763
+ },
764
+ "custom_headers": {
765
+ "description": "Additional HTTP headers sent with every request to this provider.",
766
+ "type": [
767
+ "object",
768
+ "null"
769
+ ],
770
+ "additionalProperties": {
771
+ "type": "string"
772
+ }
773
+ },
774
+ "id": {
775
+ "description": "Unique provider identifier used in model paths (e.g. `\"my_provider\"`).",
776
+ "type": "string"
777
+ },
778
+ "models": {
779
+ "description": "Model source: either a URL template for dynamic discovery or a static\nlist of models defined inline.",
780
+ "anyOf": [
781
+ {
782
+ "$ref": "#/$defs/ModelListConfig"
783
+ },
784
+ {
785
+ "type": "null"
786
+ }
787
+ ]
788
+ },
789
+ "provider_type": {
790
+ "description": "Provider category; defaults to `llm` when omitted.",
791
+ "anyOf": [
792
+ {
793
+ "$ref": "#/$defs/ProviderTypeEntry"
794
+ },
795
+ {
796
+ "type": "null"
797
+ }
798
+ ]
799
+ },
800
+ "response_type": {
801
+ "description": "Wire protocol used by this provider.",
802
+ "anyOf": [
803
+ {
804
+ "$ref": "#/$defs/ProviderResponseType"
805
+ },
806
+ {
807
+ "type": "null"
808
+ }
809
+ ]
810
+ },
811
+ "url": {
812
+ "description": "URL template for chat completions; may contain `{{VAR}}` placeholders\nthat are substituted from the credential's url params.",
813
+ "type": "string"
814
+ },
815
+ "url_param_vars": {
816
+ "description": "Environment variables whose values are substituted into `{{VAR}}`\nplaceholders in the `url` and `models` templates.",
817
+ "type": "array",
818
+ "items": {
819
+ "$ref": "#/$defs/ProviderUrlParam"
820
+ }
821
+ }
822
+ },
823
+ "required": [
824
+ "id",
825
+ "url"
826
+ ]
827
+ },
828
+ "ProviderResponseType": {
829
+ "description": "Wire protocol a provider uses for chat completions.",
830
+ "type": "string",
831
+ "enum": [
832
+ "OpenAI",
833
+ "OpenAIResponses",
834
+ "Anthropic",
835
+ "Bedrock",
836
+ "Google",
837
+ "OpenCode"
838
+ ]
839
+ },
840
+ "ProviderTypeEntry": {
841
+ "description": "Category of a provider.",
842
+ "oneOf": [
843
+ {
844
+ "description": "LLM provider for chat completions.",
845
+ "type": "string",
846
+ "const": "llm"
847
+ },
848
+ {
849
+ "description": "Context engine provider for code indexing and search.",
850
+ "type": "string",
851
+ "const": "context_engine"
852
+ }
853
+ ]
854
+ },
855
+ "ProviderUrlParam": {
856
+ "description": "A URL parameter variable for a provider, used to substitute template\nvariables in URL strings.",
857
+ "type": "object",
858
+ "properties": {
859
+ "name": {
860
+ "description": "The environment variable name used as the template variable key.",
861
+ "type": "string"
862
+ },
863
+ "optional": {
864
+ "description": "Whether this parameter is optional. When `true`, the parameter may be\nleft blank without causing an error.",
865
+ "type": "boolean"
866
+ },
867
+ "options": {
868
+ "description": "Optional preset values for this parameter shown as suggestions in the\nUI.",
869
+ "type": "array",
870
+ "items": {
871
+ "type": "string"
872
+ }
873
+ }
874
+ },
875
+ "required": [
876
+ "name"
877
+ ]
878
+ },
879
+ "ReasoningConfig": {
880
+ "description": "Controls the reasoning behaviour of a model, including effort level, token\nbudget, and visibility of the thinking process.",
881
+ "type": "object",
882
+ "properties": {
883
+ "effort": {
884
+ "description": "Controls the effort level of the model's reasoning.\nSupported by openrouter and forge provider.",
885
+ "anyOf": [
886
+ {
887
+ "$ref": "#/$defs/Effort"
888
+ },
889
+ {
890
+ "type": "null"
891
+ }
892
+ ]
893
+ },
894
+ "enabled": {
895
+ "description": "Enables reasoning at the \"medium\" effort level with no exclusions.\nSupported by openrouter, anthropic, and forge provider.",
896
+ "type": [
897
+ "boolean",
898
+ "null"
899
+ ]
900
+ },
901
+ "exclude": {
902
+ "description": "When true, the model thinks deeply but the reasoning is hidden from the\ncaller. Supported by openrouter and forge provider.",
903
+ "type": [
904
+ "boolean",
905
+ "null"
906
+ ]
907
+ },
908
+ "max_tokens": {
909
+ "description": "Controls how many tokens the model can spend thinking.\nShould be greater than 1024 but less than the overall max_tokens.\nSupported by openrouter, anthropic, and forge provider.",
910
+ "type": [
911
+ "integer",
912
+ "null"
913
+ ],
914
+ "format": "uint",
915
+ "minimum": 0
916
+ }
917
+ }
918
+ },
919
+ "RetryConfig": {
920
+ "description": "Configuration for retry mechanism.",
921
+ "type": "object",
922
+ "properties": {
923
+ "backoff_factor": {
924
+ "description": "Backoff multiplication factor for each retry attempt",
925
+ "type": "integer",
926
+ "format": "uint64",
927
+ "minimum": 0
928
+ },
929
+ "initial_backoff_ms": {
930
+ "description": "Initial backoff delay in milliseconds for retry operations",
931
+ "type": "integer",
932
+ "format": "uint64",
933
+ "minimum": 0
934
+ },
935
+ "max_attempts": {
936
+ "description": "Maximum number of retry attempts",
937
+ "type": "integer",
938
+ "format": "uint",
939
+ "minimum": 0
940
+ },
941
+ "max_delay_secs": {
942
+ "description": "Maximum delay between retries in seconds",
943
+ "type": [
944
+ "integer",
945
+ "null"
946
+ ],
947
+ "format": "uint64",
948
+ "minimum": 0
949
+ },
950
+ "min_delay_ms": {
951
+ "description": "Minimum delay in milliseconds between retry attempts",
952
+ "type": "integer",
953
+ "format": "uint64",
954
+ "minimum": 0
955
+ },
956
+ "status_codes": {
957
+ "description": "HTTP status codes that should trigger retries",
958
+ "type": "array",
959
+ "items": {
960
+ "type": "integer",
961
+ "format": "uint16",
962
+ "maximum": 65535,
963
+ "minimum": 0
964
+ }
965
+ },
966
+ "suppress_errors": {
967
+ "description": "Whether to suppress retry error logging and events",
968
+ "type": "boolean"
969
+ }
970
+ },
971
+ "required": [
972
+ "initial_backoff_ms",
973
+ "min_delay_ms",
974
+ "backoff_factor",
975
+ "max_attempts",
976
+ "status_codes",
977
+ "suppress_errors"
978
+ ]
979
+ },
980
+ "TlsBackend": {
981
+ "description": "TLS backend option.",
982
+ "type": "string",
983
+ "enum": [
984
+ "default",
985
+ "rustls"
986
+ ]
987
+ },
988
+ "TlsVersion": {
989
+ "description": "TLS version enum for configuring TLS protocol versions.",
990
+ "type": "string",
991
+ "enum": [
992
+ "1.0",
993
+ "1.1",
994
+ "1.2",
995
+ "1.3"
996
+ ]
997
+ },
998
+ "Update": {
999
+ "description": "Configuration for automatic forge updates",
1000
+ "type": "object",
1001
+ "properties": {
1002
+ "auto_update": {
1003
+ "description": "Whether to automatically install updates without prompting",
1004
+ "type": [
1005
+ "boolean",
1006
+ "null"
1007
+ ]
1008
+ },
1009
+ "frequency": {
1010
+ "description": "How frequently forge checks for updates: daily, weekly, always, or never",
1011
+ "anyOf": [
1012
+ {
1013
+ "$ref": "#/$defs/UpdateFrequency"
1014
+ },
1015
+ {
1016
+ "type": "null"
1017
+ }
1018
+ ]
1019
+ }
1020
+ }
1021
+ },
1022
+ "UpdateFrequency": {
1023
+ "description": "Frequency at which forge checks for updates",
1024
+ "type": "string",
1025
+ "enum": [
1026
+ "daily",
1027
+ "weekly",
1028
+ "never",
1029
+ "always"
1030
+ ]
1031
+ },
1032
+ "double": {
1033
+ "type": "number",
1034
+ "format": "double"
1035
+ }
1036
+ }
1037
+ }
insta.yaml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ test:
2
+ auto_accept: true
3
+ auto_accept_unseen: true
4
+ runner: nextest
package-lock.json ADDED
@@ -0,0 +1,1966 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "forge-code-evals",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "forge-code-evals",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@ai-sdk/google-vertex": "^5.0.0",
13
+ "@types/handlebars": "^4.0.40",
14
+ "@types/node": "^24.10.1",
15
+ "@types/tmp": "^0.2.6",
16
+ "@types/yargs": "^17.0.35",
17
+ "ai": "^7.0.0",
18
+ "chalk": "^6.0.0",
19
+ "csv-parse": "^7.0.0",
20
+ "handlebars": "^4.7.9",
21
+ "p-limit": "^7.2.0",
22
+ "pino": "^10.1.0",
23
+ "pino-pretty": "^13.1.2",
24
+ "strip-ansi": "^7.1.2",
25
+ "tmp": "^0.2.5",
26
+ "tsx": "^4.20.6",
27
+ "typescript": "^7.0.0",
28
+ "yaml": "^2.8.3",
29
+ "yargs": "^18.0.0",
30
+ "zod": "^4.0.0"
31
+ }
32
+ },
33
+ "node_modules/@ai-sdk/anthropic": {
34
+ "version": "4.0.35",
35
+ "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.35.tgz",
36
+ "integrity": "sha512-3GcRyB6FPxYVH0wxBfPm7RciaMcU1qVDrx6Kp+rONeEqS7aZA1yq3gS1wUfsH8Wp4XZ1GjYh0rYyhBM0hnv59w==",
37
+ "license": "Apache-2.0",
38
+ "dependencies": {
39
+ "@ai-sdk/provider": "4.0.6",
40
+ "@ai-sdk/provider-utils": "5.0.24"
41
+ },
42
+ "engines": {
43
+ "node": ">=22"
44
+ },
45
+ "peerDependencies": {
46
+ "zod": "^3.25.76 || ^4.1.8"
47
+ }
48
+ },
49
+ "node_modules/@ai-sdk/gateway": {
50
+ "version": "4.0.45",
51
+ "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.45.tgz",
52
+ "integrity": "sha512-8W4MYRS7FKkxtaagOC9FrIMfC8HUr1T5/AXjSj/3t5ADdMbTE0R10ZcK6VaE/LqM5qV9eWtvrkW2ZZKMpvrKnw==",
53
+ "license": "Apache-2.0",
54
+ "dependencies": {
55
+ "@ai-sdk/provider": "4.0.6",
56
+ "@ai-sdk/provider-utils": "5.0.24",
57
+ "@vercel/oidc": "3.2.0"
58
+ },
59
+ "engines": {
60
+ "node": ">=22"
61
+ },
62
+ "peerDependencies": {
63
+ "zod": "^3.25.76 || ^4.1.8"
64
+ }
65
+ },
66
+ "node_modules/@ai-sdk/google": {
67
+ "version": "4.0.38",
68
+ "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.38.tgz",
69
+ "integrity": "sha512-tPAuSgvfhEb1kVOnRg2ARxS5nTtcJFK/MFcuMIkopIR8ju1lo9QtFbpLsOn8oRmBRFQNe9z9TuBf73QdI/zrFg==",
70
+ "license": "Apache-2.0",
71
+ "dependencies": {
72
+ "@ai-sdk/provider": "4.0.6",
73
+ "@ai-sdk/provider-utils": "5.0.24"
74
+ },
75
+ "engines": {
76
+ "node": ">=22"
77
+ },
78
+ "peerDependencies": {
79
+ "zod": "^3.25.76 || ^4.1.8"
80
+ }
81
+ },
82
+ "node_modules/@ai-sdk/google-vertex": {
83
+ "version": "5.0.46",
84
+ "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-5.0.46.tgz",
85
+ "integrity": "sha512-sDVnBVeECEzwDLX7mNq89yoEDxut4PqCx0ymjftCK5RTkELXZauIeLiFWCrcp/WLZ51cyJ44I32Tw+G04m0GNg==",
86
+ "license": "Apache-2.0",
87
+ "dependencies": {
88
+ "@ai-sdk/anthropic": "4.0.35",
89
+ "@ai-sdk/google": "4.0.38",
90
+ "@ai-sdk/openai-compatible": "3.0.26",
91
+ "@ai-sdk/provider": "4.0.6",
92
+ "@ai-sdk/provider-utils": "5.0.24",
93
+ "google-auth-library": "^10.6.2"
94
+ },
95
+ "engines": {
96
+ "node": ">=22"
97
+ },
98
+ "peerDependencies": {
99
+ "zod": "^3.25.76 || ^4.1.8"
100
+ }
101
+ },
102
+ "node_modules/@ai-sdk/openai-compatible": {
103
+ "version": "3.0.26",
104
+ "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.26.tgz",
105
+ "integrity": "sha512-PQYWPJF6MQRurwVH9h5MlZXqfFufC1bsGgVQmEy174jF5MFDyPD3rnFeOXSZAYR3YOj81nuaBqjII239IEWpgw==",
106
+ "license": "Apache-2.0",
107
+ "dependencies": {
108
+ "@ai-sdk/provider": "4.0.6",
109
+ "@ai-sdk/provider-utils": "5.0.24"
110
+ },
111
+ "engines": {
112
+ "node": ">=22"
113
+ },
114
+ "peerDependencies": {
115
+ "zod": "^3.25.76 || ^4.1.8"
116
+ }
117
+ },
118
+ "node_modules/@ai-sdk/provider": {
119
+ "version": "4.0.6",
120
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.6.tgz",
121
+ "integrity": "sha512-YYXjvs8F3q/BdEn9tBDoDuQACotfR7c5foGw/ADsM6iAVC1JabqEjQSkwHv/Kg2vNIr1c2AVHcQb+JYsYk76Qw==",
122
+ "license": "Apache-2.0",
123
+ "dependencies": {
124
+ "json-schema": "^0.4.0"
125
+ },
126
+ "engines": {
127
+ "node": ">=22"
128
+ }
129
+ },
130
+ "node_modules/@ai-sdk/provider-utils": {
131
+ "version": "5.0.24",
132
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.24.tgz",
133
+ "integrity": "sha512-jiQzdyr4y25kKYEJcV0Re/UH0Oy0FMB60LNW3eo01hEHSkmo+NYDIhuY0y/BMdWdPD659du0/VmAbOxiXoLLUw==",
134
+ "license": "Apache-2.0",
135
+ "dependencies": {
136
+ "@ai-sdk/provider": "4.0.6",
137
+ "@standard-schema/spec": "^1.1.0",
138
+ "@workflow/serde": "4.1.0",
139
+ "eventsource-parser": "^3.0.8",
140
+ "undici": "^7.28.0"
141
+ },
142
+ "engines": {
143
+ "node": ">=22"
144
+ },
145
+ "peerDependencies": {
146
+ "zod": "^3.25.76 || ^4.1.8"
147
+ }
148
+ },
149
+ "node_modules/@esbuild/aix-ppc64": {
150
+ "version": "0.28.1",
151
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
152
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
153
+ "cpu": [
154
+ "ppc64"
155
+ ],
156
+ "license": "MIT",
157
+ "optional": true,
158
+ "os": [
159
+ "aix"
160
+ ],
161
+ "engines": {
162
+ "node": ">=18"
163
+ }
164
+ },
165
+ "node_modules/@esbuild/android-arm": {
166
+ "version": "0.28.1",
167
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
168
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
169
+ "cpu": [
170
+ "arm"
171
+ ],
172
+ "license": "MIT",
173
+ "optional": true,
174
+ "os": [
175
+ "android"
176
+ ],
177
+ "engines": {
178
+ "node": ">=18"
179
+ }
180
+ },
181
+ "node_modules/@esbuild/android-arm64": {
182
+ "version": "0.28.1",
183
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
184
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
185
+ "cpu": [
186
+ "arm64"
187
+ ],
188
+ "license": "MIT",
189
+ "optional": true,
190
+ "os": [
191
+ "android"
192
+ ],
193
+ "engines": {
194
+ "node": ">=18"
195
+ }
196
+ },
197
+ "node_modules/@esbuild/android-x64": {
198
+ "version": "0.28.1",
199
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
200
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
201
+ "cpu": [
202
+ "x64"
203
+ ],
204
+ "license": "MIT",
205
+ "optional": true,
206
+ "os": [
207
+ "android"
208
+ ],
209
+ "engines": {
210
+ "node": ">=18"
211
+ }
212
+ },
213
+ "node_modules/@esbuild/darwin-arm64": {
214
+ "version": "0.28.1",
215
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
216
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
217
+ "cpu": [
218
+ "arm64"
219
+ ],
220
+ "license": "MIT",
221
+ "optional": true,
222
+ "os": [
223
+ "darwin"
224
+ ],
225
+ "engines": {
226
+ "node": ">=18"
227
+ }
228
+ },
229
+ "node_modules/@esbuild/darwin-x64": {
230
+ "version": "0.28.1",
231
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
232
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
233
+ "cpu": [
234
+ "x64"
235
+ ],
236
+ "license": "MIT",
237
+ "optional": true,
238
+ "os": [
239
+ "darwin"
240
+ ],
241
+ "engines": {
242
+ "node": ">=18"
243
+ }
244
+ },
245
+ "node_modules/@esbuild/freebsd-arm64": {
246
+ "version": "0.28.1",
247
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
248
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
249
+ "cpu": [
250
+ "arm64"
251
+ ],
252
+ "license": "MIT",
253
+ "optional": true,
254
+ "os": [
255
+ "freebsd"
256
+ ],
257
+ "engines": {
258
+ "node": ">=18"
259
+ }
260
+ },
261
+ "node_modules/@esbuild/freebsd-x64": {
262
+ "version": "0.28.1",
263
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
264
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
265
+ "cpu": [
266
+ "x64"
267
+ ],
268
+ "license": "MIT",
269
+ "optional": true,
270
+ "os": [
271
+ "freebsd"
272
+ ],
273
+ "engines": {
274
+ "node": ">=18"
275
+ }
276
+ },
277
+ "node_modules/@esbuild/linux-arm": {
278
+ "version": "0.28.1",
279
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
280
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
281
+ "cpu": [
282
+ "arm"
283
+ ],
284
+ "license": "MIT",
285
+ "optional": true,
286
+ "os": [
287
+ "linux"
288
+ ],
289
+ "engines": {
290
+ "node": ">=18"
291
+ }
292
+ },
293
+ "node_modules/@esbuild/linux-arm64": {
294
+ "version": "0.28.1",
295
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
296
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
297
+ "cpu": [
298
+ "arm64"
299
+ ],
300
+ "license": "MIT",
301
+ "optional": true,
302
+ "os": [
303
+ "linux"
304
+ ],
305
+ "engines": {
306
+ "node": ">=18"
307
+ }
308
+ },
309
+ "node_modules/@esbuild/linux-ia32": {
310
+ "version": "0.28.1",
311
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
312
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
313
+ "cpu": [
314
+ "ia32"
315
+ ],
316
+ "license": "MIT",
317
+ "optional": true,
318
+ "os": [
319
+ "linux"
320
+ ],
321
+ "engines": {
322
+ "node": ">=18"
323
+ }
324
+ },
325
+ "node_modules/@esbuild/linux-loong64": {
326
+ "version": "0.28.1",
327
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
328
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
329
+ "cpu": [
330
+ "loong64"
331
+ ],
332
+ "license": "MIT",
333
+ "optional": true,
334
+ "os": [
335
+ "linux"
336
+ ],
337
+ "engines": {
338
+ "node": ">=18"
339
+ }
340
+ },
341
+ "node_modules/@esbuild/linux-mips64el": {
342
+ "version": "0.28.1",
343
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
344
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
345
+ "cpu": [
346
+ "mips64el"
347
+ ],
348
+ "license": "MIT",
349
+ "optional": true,
350
+ "os": [
351
+ "linux"
352
+ ],
353
+ "engines": {
354
+ "node": ">=18"
355
+ }
356
+ },
357
+ "node_modules/@esbuild/linux-ppc64": {
358
+ "version": "0.28.1",
359
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
360
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
361
+ "cpu": [
362
+ "ppc64"
363
+ ],
364
+ "license": "MIT",
365
+ "optional": true,
366
+ "os": [
367
+ "linux"
368
+ ],
369
+ "engines": {
370
+ "node": ">=18"
371
+ }
372
+ },
373
+ "node_modules/@esbuild/linux-riscv64": {
374
+ "version": "0.28.1",
375
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
376
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
377
+ "cpu": [
378
+ "riscv64"
379
+ ],
380
+ "license": "MIT",
381
+ "optional": true,
382
+ "os": [
383
+ "linux"
384
+ ],
385
+ "engines": {
386
+ "node": ">=18"
387
+ }
388
+ },
389
+ "node_modules/@esbuild/linux-s390x": {
390
+ "version": "0.28.1",
391
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
392
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
393
+ "cpu": [
394
+ "s390x"
395
+ ],
396
+ "license": "MIT",
397
+ "optional": true,
398
+ "os": [
399
+ "linux"
400
+ ],
401
+ "engines": {
402
+ "node": ">=18"
403
+ }
404
+ },
405
+ "node_modules/@esbuild/linux-x64": {
406
+ "version": "0.28.1",
407
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
408
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
409
+ "cpu": [
410
+ "x64"
411
+ ],
412
+ "license": "MIT",
413
+ "optional": true,
414
+ "os": [
415
+ "linux"
416
+ ],
417
+ "engines": {
418
+ "node": ">=18"
419
+ }
420
+ },
421
+ "node_modules/@esbuild/netbsd-arm64": {
422
+ "version": "0.28.1",
423
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
424
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
425
+ "cpu": [
426
+ "arm64"
427
+ ],
428
+ "license": "MIT",
429
+ "optional": true,
430
+ "os": [
431
+ "netbsd"
432
+ ],
433
+ "engines": {
434
+ "node": ">=18"
435
+ }
436
+ },
437
+ "node_modules/@esbuild/netbsd-x64": {
438
+ "version": "0.28.1",
439
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
440
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
441
+ "cpu": [
442
+ "x64"
443
+ ],
444
+ "license": "MIT",
445
+ "optional": true,
446
+ "os": [
447
+ "netbsd"
448
+ ],
449
+ "engines": {
450
+ "node": ">=18"
451
+ }
452
+ },
453
+ "node_modules/@esbuild/openbsd-arm64": {
454
+ "version": "0.28.1",
455
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
456
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
457
+ "cpu": [
458
+ "arm64"
459
+ ],
460
+ "license": "MIT",
461
+ "optional": true,
462
+ "os": [
463
+ "openbsd"
464
+ ],
465
+ "engines": {
466
+ "node": ">=18"
467
+ }
468
+ },
469
+ "node_modules/@esbuild/openbsd-x64": {
470
+ "version": "0.28.1",
471
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
472
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
473
+ "cpu": [
474
+ "x64"
475
+ ],
476
+ "license": "MIT",
477
+ "optional": true,
478
+ "os": [
479
+ "openbsd"
480
+ ],
481
+ "engines": {
482
+ "node": ">=18"
483
+ }
484
+ },
485
+ "node_modules/@esbuild/openharmony-arm64": {
486
+ "version": "0.28.1",
487
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
488
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
489
+ "cpu": [
490
+ "arm64"
491
+ ],
492
+ "license": "MIT",
493
+ "optional": true,
494
+ "os": [
495
+ "openharmony"
496
+ ],
497
+ "engines": {
498
+ "node": ">=18"
499
+ }
500
+ },
501
+ "node_modules/@esbuild/sunos-x64": {
502
+ "version": "0.28.1",
503
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
504
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
505
+ "cpu": [
506
+ "x64"
507
+ ],
508
+ "license": "MIT",
509
+ "optional": true,
510
+ "os": [
511
+ "sunos"
512
+ ],
513
+ "engines": {
514
+ "node": ">=18"
515
+ }
516
+ },
517
+ "node_modules/@esbuild/win32-arm64": {
518
+ "version": "0.28.1",
519
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
520
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
521
+ "cpu": [
522
+ "arm64"
523
+ ],
524
+ "license": "MIT",
525
+ "optional": true,
526
+ "os": [
527
+ "win32"
528
+ ],
529
+ "engines": {
530
+ "node": ">=18"
531
+ }
532
+ },
533
+ "node_modules/@esbuild/win32-ia32": {
534
+ "version": "0.28.1",
535
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
536
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
537
+ "cpu": [
538
+ "ia32"
539
+ ],
540
+ "license": "MIT",
541
+ "optional": true,
542
+ "os": [
543
+ "win32"
544
+ ],
545
+ "engines": {
546
+ "node": ">=18"
547
+ }
548
+ },
549
+ "node_modules/@esbuild/win32-x64": {
550
+ "version": "0.28.1",
551
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
552
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
553
+ "cpu": [
554
+ "x64"
555
+ ],
556
+ "license": "MIT",
557
+ "optional": true,
558
+ "os": [
559
+ "win32"
560
+ ],
561
+ "engines": {
562
+ "node": ">=18"
563
+ }
564
+ },
565
+ "node_modules/@pinojs/redact": {
566
+ "version": "0.4.0",
567
+ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
568
+ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
569
+ "license": "MIT"
570
+ },
571
+ "node_modules/@standard-schema/spec": {
572
+ "version": "1.1.0",
573
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
574
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
575
+ "license": "MIT"
576
+ },
577
+ "node_modules/@types/handlebars": {
578
+ "version": "4.1.0",
579
+ "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.1.0.tgz",
580
+ "integrity": "sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA==",
581
+ "deprecated": "This is a stub types definition. handlebars provides its own type definitions, so you do not need this installed.",
582
+ "license": "MIT",
583
+ "dependencies": {
584
+ "handlebars": "*"
585
+ }
586
+ },
587
+ "node_modules/@types/node": {
588
+ "version": "24.13.3",
589
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
590
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
591
+ "license": "MIT",
592
+ "dependencies": {
593
+ "undici-types": "~7.18.0"
594
+ }
595
+ },
596
+ "node_modules/@types/tmp": {
597
+ "version": "0.2.6",
598
+ "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz",
599
+ "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==",
600
+ "license": "MIT"
601
+ },
602
+ "node_modules/@types/yargs": {
603
+ "version": "17.0.35",
604
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
605
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
606
+ "license": "MIT",
607
+ "dependencies": {
608
+ "@types/yargs-parser": "*"
609
+ }
610
+ },
611
+ "node_modules/@types/yargs-parser": {
612
+ "version": "21.0.3",
613
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
614
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
615
+ "license": "MIT"
616
+ },
617
+ "node_modules/@typescript/typescript-aix-ppc64": {
618
+ "version": "7.0.2",
619
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
620
+ "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
621
+ "cpu": [
622
+ "ppc64"
623
+ ],
624
+ "license": "Apache-2.0",
625
+ "optional": true,
626
+ "os": [
627
+ "aix"
628
+ ],
629
+ "engines": {
630
+ "node": ">=16.20.0"
631
+ }
632
+ },
633
+ "node_modules/@typescript/typescript-darwin-arm64": {
634
+ "version": "7.0.2",
635
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
636
+ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
637
+ "cpu": [
638
+ "arm64"
639
+ ],
640
+ "license": "Apache-2.0",
641
+ "optional": true,
642
+ "os": [
643
+ "darwin"
644
+ ],
645
+ "engines": {
646
+ "node": ">=16.20.0"
647
+ }
648
+ },
649
+ "node_modules/@typescript/typescript-darwin-x64": {
650
+ "version": "7.0.2",
651
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
652
+ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
653
+ "cpu": [
654
+ "x64"
655
+ ],
656
+ "license": "Apache-2.0",
657
+ "optional": true,
658
+ "os": [
659
+ "darwin"
660
+ ],
661
+ "engines": {
662
+ "node": ">=16.20.0"
663
+ }
664
+ },
665
+ "node_modules/@typescript/typescript-freebsd-arm64": {
666
+ "version": "7.0.2",
667
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
668
+ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
669
+ "cpu": [
670
+ "arm64"
671
+ ],
672
+ "license": "Apache-2.0",
673
+ "optional": true,
674
+ "os": [
675
+ "freebsd"
676
+ ],
677
+ "engines": {
678
+ "node": ">=16.20.0"
679
+ }
680
+ },
681
+ "node_modules/@typescript/typescript-freebsd-x64": {
682
+ "version": "7.0.2",
683
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
684
+ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
685
+ "cpu": [
686
+ "x64"
687
+ ],
688
+ "license": "Apache-2.0",
689
+ "optional": true,
690
+ "os": [
691
+ "freebsd"
692
+ ],
693
+ "engines": {
694
+ "node": ">=16.20.0"
695
+ }
696
+ },
697
+ "node_modules/@typescript/typescript-linux-arm": {
698
+ "version": "7.0.2",
699
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
700
+ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
701
+ "cpu": [
702
+ "arm"
703
+ ],
704
+ "license": "Apache-2.0",
705
+ "optional": true,
706
+ "os": [
707
+ "linux"
708
+ ],
709
+ "engines": {
710
+ "node": ">=16.20.0"
711
+ }
712
+ },
713
+ "node_modules/@typescript/typescript-linux-arm64": {
714
+ "version": "7.0.2",
715
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
716
+ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
717
+ "cpu": [
718
+ "arm64"
719
+ ],
720
+ "license": "Apache-2.0",
721
+ "optional": true,
722
+ "os": [
723
+ "linux"
724
+ ],
725
+ "engines": {
726
+ "node": ">=16.20.0"
727
+ }
728
+ },
729
+ "node_modules/@typescript/typescript-linux-loong64": {
730
+ "version": "7.0.2",
731
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
732
+ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
733
+ "cpu": [
734
+ "loong64"
735
+ ],
736
+ "license": "Apache-2.0",
737
+ "optional": true,
738
+ "os": [
739
+ "linux"
740
+ ],
741
+ "engines": {
742
+ "node": ">=16.20.0"
743
+ }
744
+ },
745
+ "node_modules/@typescript/typescript-linux-mips64el": {
746
+ "version": "7.0.2",
747
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
748
+ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
749
+ "cpu": [
750
+ "mips64el"
751
+ ],
752
+ "license": "Apache-2.0",
753
+ "optional": true,
754
+ "os": [
755
+ "linux"
756
+ ],
757
+ "engines": {
758
+ "node": ">=16.20.0"
759
+ }
760
+ },
761
+ "node_modules/@typescript/typescript-linux-ppc64": {
762
+ "version": "7.0.2",
763
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
764
+ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
765
+ "cpu": [
766
+ "ppc64"
767
+ ],
768
+ "license": "Apache-2.0",
769
+ "optional": true,
770
+ "os": [
771
+ "linux"
772
+ ],
773
+ "engines": {
774
+ "node": ">=16.20.0"
775
+ }
776
+ },
777
+ "node_modules/@typescript/typescript-linux-riscv64": {
778
+ "version": "7.0.2",
779
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
780
+ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
781
+ "cpu": [
782
+ "riscv64"
783
+ ],
784
+ "license": "Apache-2.0",
785
+ "optional": true,
786
+ "os": [
787
+ "linux"
788
+ ],
789
+ "engines": {
790
+ "node": ">=16.20.0"
791
+ }
792
+ },
793
+ "node_modules/@typescript/typescript-linux-s390x": {
794
+ "version": "7.0.2",
795
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
796
+ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
797
+ "cpu": [
798
+ "s390x"
799
+ ],
800
+ "license": "Apache-2.0",
801
+ "optional": true,
802
+ "os": [
803
+ "linux"
804
+ ],
805
+ "engines": {
806
+ "node": ">=16.20.0"
807
+ }
808
+ },
809
+ "node_modules/@typescript/typescript-linux-x64": {
810
+ "version": "7.0.2",
811
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
812
+ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
813
+ "cpu": [
814
+ "x64"
815
+ ],
816
+ "license": "Apache-2.0",
817
+ "optional": true,
818
+ "os": [
819
+ "linux"
820
+ ],
821
+ "engines": {
822
+ "node": ">=16.20.0"
823
+ }
824
+ },
825
+ "node_modules/@typescript/typescript-netbsd-arm64": {
826
+ "version": "7.0.2",
827
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
828
+ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
829
+ "cpu": [
830
+ "arm64"
831
+ ],
832
+ "license": "Apache-2.0",
833
+ "optional": true,
834
+ "os": [
835
+ "netbsd"
836
+ ],
837
+ "engines": {
838
+ "node": ">=16.20.0"
839
+ }
840
+ },
841
+ "node_modules/@typescript/typescript-netbsd-x64": {
842
+ "version": "7.0.2",
843
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
844
+ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
845
+ "cpu": [
846
+ "x64"
847
+ ],
848
+ "license": "Apache-2.0",
849
+ "optional": true,
850
+ "os": [
851
+ "netbsd"
852
+ ],
853
+ "engines": {
854
+ "node": ">=16.20.0"
855
+ }
856
+ },
857
+ "node_modules/@typescript/typescript-openbsd-arm64": {
858
+ "version": "7.0.2",
859
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
860
+ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
861
+ "cpu": [
862
+ "arm64"
863
+ ],
864
+ "license": "Apache-2.0",
865
+ "optional": true,
866
+ "os": [
867
+ "openbsd"
868
+ ],
869
+ "engines": {
870
+ "node": ">=16.20.0"
871
+ }
872
+ },
873
+ "node_modules/@typescript/typescript-openbsd-x64": {
874
+ "version": "7.0.2",
875
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
876
+ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
877
+ "cpu": [
878
+ "x64"
879
+ ],
880
+ "license": "Apache-2.0",
881
+ "optional": true,
882
+ "os": [
883
+ "openbsd"
884
+ ],
885
+ "engines": {
886
+ "node": ">=16.20.0"
887
+ }
888
+ },
889
+ "node_modules/@typescript/typescript-sunos-x64": {
890
+ "version": "7.0.2",
891
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
892
+ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
893
+ "cpu": [
894
+ "x64"
895
+ ],
896
+ "license": "Apache-2.0",
897
+ "optional": true,
898
+ "os": [
899
+ "sunos"
900
+ ],
901
+ "engines": {
902
+ "node": ">=16.20.0"
903
+ }
904
+ },
905
+ "node_modules/@typescript/typescript-win32-arm64": {
906
+ "version": "7.0.2",
907
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
908
+ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
909
+ "cpu": [
910
+ "arm64"
911
+ ],
912
+ "license": "Apache-2.0",
913
+ "optional": true,
914
+ "os": [
915
+ "win32"
916
+ ],
917
+ "engines": {
918
+ "node": ">=16.20.0"
919
+ }
920
+ },
921
+ "node_modules/@typescript/typescript-win32-x64": {
922
+ "version": "7.0.2",
923
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
924
+ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
925
+ "cpu": [
926
+ "x64"
927
+ ],
928
+ "license": "Apache-2.0",
929
+ "optional": true,
930
+ "os": [
931
+ "win32"
932
+ ],
933
+ "engines": {
934
+ "node": ">=16.20.0"
935
+ }
936
+ },
937
+ "node_modules/@vercel/oidc": {
938
+ "version": "3.2.0",
939
+ "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz",
940
+ "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==",
941
+ "license": "Apache-2.0",
942
+ "engines": {
943
+ "node": ">= 20"
944
+ }
945
+ },
946
+ "node_modules/@workflow/serde": {
947
+ "version": "4.1.0",
948
+ "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz",
949
+ "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==",
950
+ "license": "Apache-2.0"
951
+ },
952
+ "node_modules/agent-base": {
953
+ "version": "7.1.4",
954
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
955
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
956
+ "license": "MIT",
957
+ "engines": {
958
+ "node": ">= 14"
959
+ }
960
+ },
961
+ "node_modules/ai": {
962
+ "version": "7.0.57",
963
+ "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.57.tgz",
964
+ "integrity": "sha512-MDnflmccMqafBrV5SB00YMqqTKuqpWrOaK74+dA21izk3HmeyHTakkeZRcdgz4iPEB6L2NtEZHOPKTivZp47lw==",
965
+ "license": "Apache-2.0",
966
+ "dependencies": {
967
+ "@ai-sdk/gateway": "4.0.45",
968
+ "@ai-sdk/provider": "4.0.6",
969
+ "@ai-sdk/provider-utils": "5.0.24"
970
+ },
971
+ "engines": {
972
+ "node": ">=22"
973
+ },
974
+ "peerDependencies": {
975
+ "zod": "^3.25.76 || ^4.1.8"
976
+ }
977
+ },
978
+ "node_modules/ansi-regex": {
979
+ "version": "6.2.2",
980
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
981
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
982
+ "license": "MIT",
983
+ "engines": {
984
+ "node": ">=12"
985
+ },
986
+ "funding": {
987
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
988
+ }
989
+ },
990
+ "node_modules/ansi-styles": {
991
+ "version": "6.2.3",
992
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
993
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
994
+ "license": "MIT",
995
+ "engines": {
996
+ "node": ">=12"
997
+ },
998
+ "funding": {
999
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1000
+ }
1001
+ },
1002
+ "node_modules/atomic-sleep": {
1003
+ "version": "1.0.0",
1004
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
1005
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
1006
+ "license": "MIT",
1007
+ "engines": {
1008
+ "node": ">=8.0.0"
1009
+ }
1010
+ },
1011
+ "node_modules/base64-js": {
1012
+ "version": "1.5.1",
1013
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
1014
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
1015
+ "funding": [
1016
+ {
1017
+ "type": "github",
1018
+ "url": "https://github.com/sponsors/feross"
1019
+ },
1020
+ {
1021
+ "type": "patreon",
1022
+ "url": "https://www.patreon.com/feross"
1023
+ },
1024
+ {
1025
+ "type": "consulting",
1026
+ "url": "https://feross.org/support"
1027
+ }
1028
+ ],
1029
+ "license": "MIT"
1030
+ },
1031
+ "node_modules/bignumber.js": {
1032
+ "version": "9.3.1",
1033
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
1034
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
1035
+ "license": "MIT",
1036
+ "engines": {
1037
+ "node": "*"
1038
+ }
1039
+ },
1040
+ "node_modules/buffer-equal-constant-time": {
1041
+ "version": "1.0.1",
1042
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
1043
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
1044
+ "license": "BSD-3-Clause"
1045
+ },
1046
+ "node_modules/chalk": {
1047
+ "version": "6.0.0",
1048
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz",
1049
+ "integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==",
1050
+ "license": "MIT",
1051
+ "engines": {
1052
+ "node": ">=22"
1053
+ },
1054
+ "funding": {
1055
+ "url": "https://github.com/chalk/chalk?sponsor=1"
1056
+ }
1057
+ },
1058
+ "node_modules/cliui": {
1059
+ "version": "9.0.1",
1060
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
1061
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
1062
+ "license": "ISC",
1063
+ "dependencies": {
1064
+ "string-width": "^7.2.0",
1065
+ "strip-ansi": "^7.1.0",
1066
+ "wrap-ansi": "^9.0.0"
1067
+ },
1068
+ "engines": {
1069
+ "node": ">=20"
1070
+ }
1071
+ },
1072
+ "node_modules/colorette": {
1073
+ "version": "2.0.20",
1074
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
1075
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
1076
+ "license": "MIT"
1077
+ },
1078
+ "node_modules/csv-parse": {
1079
+ "version": "7.0.2",
1080
+ "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-7.0.2.tgz",
1081
+ "integrity": "sha512-uKZghv9UmPkMVLYy//KZ9HFAIJsl7wkhoEdIL0+rhuSY9pZQlhaeGEDPIe+/w7eh81MOql8Q/9+inAGWG6ZHYA==",
1082
+ "license": "MIT"
1083
+ },
1084
+ "node_modules/data-uri-to-buffer": {
1085
+ "version": "4.0.1",
1086
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
1087
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
1088
+ "license": "MIT",
1089
+ "engines": {
1090
+ "node": ">= 12"
1091
+ }
1092
+ },
1093
+ "node_modules/dateformat": {
1094
+ "version": "4.6.3",
1095
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
1096
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
1097
+ "license": "MIT",
1098
+ "engines": {
1099
+ "node": "*"
1100
+ }
1101
+ },
1102
+ "node_modules/debug": {
1103
+ "version": "4.4.3",
1104
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1105
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1106
+ "license": "MIT",
1107
+ "dependencies": {
1108
+ "ms": "^2.1.3"
1109
+ },
1110
+ "engines": {
1111
+ "node": ">=6.0"
1112
+ },
1113
+ "peerDependenciesMeta": {
1114
+ "supports-color": {
1115
+ "optional": true
1116
+ }
1117
+ }
1118
+ },
1119
+ "node_modules/ecdsa-sig-formatter": {
1120
+ "version": "1.0.11",
1121
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1122
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1123
+ "license": "Apache-2.0",
1124
+ "dependencies": {
1125
+ "safe-buffer": "^5.0.1"
1126
+ }
1127
+ },
1128
+ "node_modules/emoji-regex": {
1129
+ "version": "10.6.0",
1130
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
1131
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
1132
+ "license": "MIT"
1133
+ },
1134
+ "node_modules/end-of-stream": {
1135
+ "version": "1.4.5",
1136
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
1137
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
1138
+ "license": "MIT",
1139
+ "dependencies": {
1140
+ "once": "^1.4.0"
1141
+ }
1142
+ },
1143
+ "node_modules/esbuild": {
1144
+ "version": "0.28.1",
1145
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
1146
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
1147
+ "hasInstallScript": true,
1148
+ "license": "MIT",
1149
+ "bin": {
1150
+ "esbuild": "bin/esbuild"
1151
+ },
1152
+ "engines": {
1153
+ "node": ">=18"
1154
+ },
1155
+ "optionalDependencies": {
1156
+ "@esbuild/aix-ppc64": "0.28.1",
1157
+ "@esbuild/android-arm": "0.28.1",
1158
+ "@esbuild/android-arm64": "0.28.1",
1159
+ "@esbuild/android-x64": "0.28.1",
1160
+ "@esbuild/darwin-arm64": "0.28.1",
1161
+ "@esbuild/darwin-x64": "0.28.1",
1162
+ "@esbuild/freebsd-arm64": "0.28.1",
1163
+ "@esbuild/freebsd-x64": "0.28.1",
1164
+ "@esbuild/linux-arm": "0.28.1",
1165
+ "@esbuild/linux-arm64": "0.28.1",
1166
+ "@esbuild/linux-ia32": "0.28.1",
1167
+ "@esbuild/linux-loong64": "0.28.1",
1168
+ "@esbuild/linux-mips64el": "0.28.1",
1169
+ "@esbuild/linux-ppc64": "0.28.1",
1170
+ "@esbuild/linux-riscv64": "0.28.1",
1171
+ "@esbuild/linux-s390x": "0.28.1",
1172
+ "@esbuild/linux-x64": "0.28.1",
1173
+ "@esbuild/netbsd-arm64": "0.28.1",
1174
+ "@esbuild/netbsd-x64": "0.28.1",
1175
+ "@esbuild/openbsd-arm64": "0.28.1",
1176
+ "@esbuild/openbsd-x64": "0.28.1",
1177
+ "@esbuild/openharmony-arm64": "0.28.1",
1178
+ "@esbuild/sunos-x64": "0.28.1",
1179
+ "@esbuild/win32-arm64": "0.28.1",
1180
+ "@esbuild/win32-ia32": "0.28.1",
1181
+ "@esbuild/win32-x64": "0.28.1"
1182
+ }
1183
+ },
1184
+ "node_modules/escalade": {
1185
+ "version": "3.2.0",
1186
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1187
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1188
+ "license": "MIT",
1189
+ "engines": {
1190
+ "node": ">=6"
1191
+ }
1192
+ },
1193
+ "node_modules/eventsource-parser": {
1194
+ "version": "3.0.8",
1195
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
1196
+ "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
1197
+ "license": "MIT",
1198
+ "engines": {
1199
+ "node": ">=18.0.0"
1200
+ }
1201
+ },
1202
+ "node_modules/extend": {
1203
+ "version": "3.0.2",
1204
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
1205
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
1206
+ "license": "MIT"
1207
+ },
1208
+ "node_modules/fast-copy": {
1209
+ "version": "4.0.3",
1210
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz",
1211
+ "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==",
1212
+ "license": "MIT"
1213
+ },
1214
+ "node_modules/fast-safe-stringify": {
1215
+ "version": "2.1.1",
1216
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
1217
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
1218
+ "license": "MIT"
1219
+ },
1220
+ "node_modules/fetch-blob": {
1221
+ "version": "3.2.0",
1222
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
1223
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
1224
+ "funding": [
1225
+ {
1226
+ "type": "github",
1227
+ "url": "https://github.com/sponsors/jimmywarting"
1228
+ },
1229
+ {
1230
+ "type": "paypal",
1231
+ "url": "https://paypal.me/jimmywarting"
1232
+ }
1233
+ ],
1234
+ "license": "MIT",
1235
+ "dependencies": {
1236
+ "node-domexception": "^1.0.0",
1237
+ "web-streams-polyfill": "^3.0.3"
1238
+ },
1239
+ "engines": {
1240
+ "node": "^12.20 || >= 14.13"
1241
+ }
1242
+ },
1243
+ "node_modules/formdata-polyfill": {
1244
+ "version": "4.0.10",
1245
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
1246
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
1247
+ "license": "MIT",
1248
+ "dependencies": {
1249
+ "fetch-blob": "^3.1.2"
1250
+ },
1251
+ "engines": {
1252
+ "node": ">=12.20.0"
1253
+ }
1254
+ },
1255
+ "node_modules/fsevents": {
1256
+ "version": "2.3.3",
1257
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1258
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1259
+ "hasInstallScript": true,
1260
+ "license": "MIT",
1261
+ "optional": true,
1262
+ "os": [
1263
+ "darwin"
1264
+ ],
1265
+ "engines": {
1266
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1267
+ }
1268
+ },
1269
+ "node_modules/gaxios": {
1270
+ "version": "7.1.5",
1271
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz",
1272
+ "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==",
1273
+ "license": "Apache-2.0",
1274
+ "dependencies": {
1275
+ "extend": "^3.0.2",
1276
+ "https-proxy-agent": "^7.0.1",
1277
+ "node-fetch": "^3.3.2"
1278
+ },
1279
+ "engines": {
1280
+ "node": ">=18"
1281
+ }
1282
+ },
1283
+ "node_modules/gcp-metadata": {
1284
+ "version": "8.1.2",
1285
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
1286
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
1287
+ "license": "Apache-2.0",
1288
+ "dependencies": {
1289
+ "gaxios": "^7.0.0",
1290
+ "google-logging-utils": "^1.0.0",
1291
+ "json-bigint": "^1.0.0"
1292
+ },
1293
+ "engines": {
1294
+ "node": ">=18"
1295
+ }
1296
+ },
1297
+ "node_modules/get-caller-file": {
1298
+ "version": "2.0.5",
1299
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
1300
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
1301
+ "license": "ISC",
1302
+ "engines": {
1303
+ "node": "6.* || 8.* || >= 10.*"
1304
+ }
1305
+ },
1306
+ "node_modules/get-east-asian-width": {
1307
+ "version": "1.6.0",
1308
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
1309
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
1310
+ "license": "MIT",
1311
+ "engines": {
1312
+ "node": ">=18"
1313
+ },
1314
+ "funding": {
1315
+ "url": "https://github.com/sponsors/sindresorhus"
1316
+ }
1317
+ },
1318
+ "node_modules/google-auth-library": {
1319
+ "version": "10.9.0",
1320
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz",
1321
+ "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==",
1322
+ "license": "Apache-2.0",
1323
+ "dependencies": {
1324
+ "base64-js": "^1.3.0",
1325
+ "ecdsa-sig-formatter": "^1.0.11",
1326
+ "gaxios": "^7.1.4",
1327
+ "gcp-metadata": "8.1.2",
1328
+ "google-logging-utils": "1.1.3",
1329
+ "jws": "^4.0.0"
1330
+ },
1331
+ "engines": {
1332
+ "node": ">=18"
1333
+ }
1334
+ },
1335
+ "node_modules/google-logging-utils": {
1336
+ "version": "1.1.3",
1337
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
1338
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
1339
+ "license": "Apache-2.0",
1340
+ "engines": {
1341
+ "node": ">=14"
1342
+ }
1343
+ },
1344
+ "node_modules/handlebars": {
1345
+ "version": "4.7.9",
1346
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
1347
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
1348
+ "license": "MIT",
1349
+ "dependencies": {
1350
+ "minimist": "^1.2.5",
1351
+ "neo-async": "^2.6.2",
1352
+ "source-map": "^0.6.1",
1353
+ "wordwrap": "^1.0.0"
1354
+ },
1355
+ "bin": {
1356
+ "handlebars": "bin/handlebars"
1357
+ },
1358
+ "engines": {
1359
+ "node": ">=0.4.7"
1360
+ },
1361
+ "optionalDependencies": {
1362
+ "uglify-js": "^3.1.4"
1363
+ }
1364
+ },
1365
+ "node_modules/help-me": {
1366
+ "version": "5.0.0",
1367
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
1368
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
1369
+ "license": "MIT"
1370
+ },
1371
+ "node_modules/https-proxy-agent": {
1372
+ "version": "7.0.6",
1373
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
1374
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
1375
+ "license": "MIT",
1376
+ "dependencies": {
1377
+ "agent-base": "^7.1.2",
1378
+ "debug": "4"
1379
+ },
1380
+ "engines": {
1381
+ "node": ">= 14"
1382
+ }
1383
+ },
1384
+ "node_modules/joycon": {
1385
+ "version": "3.1.1",
1386
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
1387
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
1388
+ "license": "MIT",
1389
+ "engines": {
1390
+ "node": ">=10"
1391
+ }
1392
+ },
1393
+ "node_modules/json-bigint": {
1394
+ "version": "1.0.0",
1395
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
1396
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
1397
+ "license": "MIT",
1398
+ "dependencies": {
1399
+ "bignumber.js": "^9.0.0"
1400
+ }
1401
+ },
1402
+ "node_modules/json-schema": {
1403
+ "version": "0.4.0",
1404
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
1405
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
1406
+ "license": "(AFL-2.1 OR BSD-3-Clause)"
1407
+ },
1408
+ "node_modules/jwa": {
1409
+ "version": "2.0.1",
1410
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
1411
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
1412
+ "license": "MIT",
1413
+ "dependencies": {
1414
+ "buffer-equal-constant-time": "^1.0.1",
1415
+ "ecdsa-sig-formatter": "1.0.11",
1416
+ "safe-buffer": "^5.0.1"
1417
+ }
1418
+ },
1419
+ "node_modules/jws": {
1420
+ "version": "4.0.1",
1421
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
1422
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
1423
+ "license": "MIT",
1424
+ "dependencies": {
1425
+ "jwa": "^2.0.1",
1426
+ "safe-buffer": "^5.0.1"
1427
+ }
1428
+ },
1429
+ "node_modules/minimist": {
1430
+ "version": "1.2.8",
1431
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1432
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1433
+ "license": "MIT",
1434
+ "funding": {
1435
+ "url": "https://github.com/sponsors/ljharb"
1436
+ }
1437
+ },
1438
+ "node_modules/ms": {
1439
+ "version": "2.1.3",
1440
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1441
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1442
+ "license": "MIT"
1443
+ },
1444
+ "node_modules/neo-async": {
1445
+ "version": "2.6.2",
1446
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
1447
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
1448
+ "license": "MIT"
1449
+ },
1450
+ "node_modules/node-domexception": {
1451
+ "version": "1.0.0",
1452
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
1453
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
1454
+ "deprecated": "Use your platform's native DOMException instead",
1455
+ "funding": [
1456
+ {
1457
+ "type": "github",
1458
+ "url": "https://github.com/sponsors/jimmywarting"
1459
+ },
1460
+ {
1461
+ "type": "github",
1462
+ "url": "https://paypal.me/jimmywarting"
1463
+ }
1464
+ ],
1465
+ "license": "MIT",
1466
+ "engines": {
1467
+ "node": ">=10.5.0"
1468
+ }
1469
+ },
1470
+ "node_modules/node-fetch": {
1471
+ "version": "3.3.2",
1472
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
1473
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
1474
+ "license": "MIT",
1475
+ "dependencies": {
1476
+ "data-uri-to-buffer": "^4.0.0",
1477
+ "fetch-blob": "^3.1.4",
1478
+ "formdata-polyfill": "^4.0.10"
1479
+ },
1480
+ "engines": {
1481
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1482
+ },
1483
+ "funding": {
1484
+ "type": "opencollective",
1485
+ "url": "https://opencollective.com/node-fetch"
1486
+ }
1487
+ },
1488
+ "node_modules/on-exit-leak-free": {
1489
+ "version": "2.1.2",
1490
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
1491
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
1492
+ "license": "MIT",
1493
+ "engines": {
1494
+ "node": ">=14.0.0"
1495
+ }
1496
+ },
1497
+ "node_modules/once": {
1498
+ "version": "1.4.0",
1499
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1500
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1501
+ "license": "ISC",
1502
+ "dependencies": {
1503
+ "wrappy": "1"
1504
+ }
1505
+ },
1506
+ "node_modules/p-limit": {
1507
+ "version": "7.3.1",
1508
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz",
1509
+ "integrity": "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==",
1510
+ "license": "MIT",
1511
+ "dependencies": {
1512
+ "yocto-queue": "^1.2.1"
1513
+ },
1514
+ "engines": {
1515
+ "node": ">=20"
1516
+ },
1517
+ "funding": {
1518
+ "url": "https://github.com/sponsors/sindresorhus"
1519
+ }
1520
+ },
1521
+ "node_modules/pino": {
1522
+ "version": "10.3.1",
1523
+ "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
1524
+ "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
1525
+ "license": "MIT",
1526
+ "dependencies": {
1527
+ "@pinojs/redact": "^0.4.0",
1528
+ "atomic-sleep": "^1.0.0",
1529
+ "on-exit-leak-free": "^2.1.0",
1530
+ "pino-abstract-transport": "^3.0.0",
1531
+ "pino-std-serializers": "^7.0.0",
1532
+ "process-warning": "^5.0.0",
1533
+ "quick-format-unescaped": "^4.0.3",
1534
+ "real-require": "^0.2.0",
1535
+ "safe-stable-stringify": "^2.3.1",
1536
+ "sonic-boom": "^4.0.1",
1537
+ "thread-stream": "^4.0.0"
1538
+ },
1539
+ "bin": {
1540
+ "pino": "bin.js"
1541
+ }
1542
+ },
1543
+ "node_modules/pino-abstract-transport": {
1544
+ "version": "3.0.0",
1545
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
1546
+ "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
1547
+ "license": "MIT",
1548
+ "dependencies": {
1549
+ "split2": "^4.0.0"
1550
+ }
1551
+ },
1552
+ "node_modules/pino-pretty": {
1553
+ "version": "13.1.3",
1554
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
1555
+ "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
1556
+ "license": "MIT",
1557
+ "dependencies": {
1558
+ "colorette": "^2.0.7",
1559
+ "dateformat": "^4.6.3",
1560
+ "fast-copy": "^4.0.0",
1561
+ "fast-safe-stringify": "^2.1.1",
1562
+ "help-me": "^5.0.0",
1563
+ "joycon": "^3.1.1",
1564
+ "minimist": "^1.2.6",
1565
+ "on-exit-leak-free": "^2.1.0",
1566
+ "pino-abstract-transport": "^3.0.0",
1567
+ "pump": "^3.0.0",
1568
+ "secure-json-parse": "^4.0.0",
1569
+ "sonic-boom": "^4.0.1",
1570
+ "strip-json-comments": "^5.0.2"
1571
+ },
1572
+ "bin": {
1573
+ "pino-pretty": "bin.js"
1574
+ }
1575
+ },
1576
+ "node_modules/pino-std-serializers": {
1577
+ "version": "7.0.0",
1578
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
1579
+ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
1580
+ "license": "MIT"
1581
+ },
1582
+ "node_modules/process-warning": {
1583
+ "version": "5.0.0",
1584
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
1585
+ "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
1586
+ "funding": [
1587
+ {
1588
+ "type": "github",
1589
+ "url": "https://github.com/sponsors/fastify"
1590
+ },
1591
+ {
1592
+ "type": "opencollective",
1593
+ "url": "https://opencollective.com/fastify"
1594
+ }
1595
+ ],
1596
+ "license": "MIT"
1597
+ },
1598
+ "node_modules/pump": {
1599
+ "version": "3.0.3",
1600
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
1601
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
1602
+ "license": "MIT",
1603
+ "dependencies": {
1604
+ "end-of-stream": "^1.1.0",
1605
+ "once": "^1.3.1"
1606
+ }
1607
+ },
1608
+ "node_modules/quick-format-unescaped": {
1609
+ "version": "4.0.4",
1610
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
1611
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
1612
+ "license": "MIT"
1613
+ },
1614
+ "node_modules/real-require": {
1615
+ "version": "0.2.0",
1616
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
1617
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
1618
+ "license": "MIT",
1619
+ "engines": {
1620
+ "node": ">= 12.13.0"
1621
+ }
1622
+ },
1623
+ "node_modules/safe-buffer": {
1624
+ "version": "5.2.1",
1625
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1626
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1627
+ "funding": [
1628
+ {
1629
+ "type": "github",
1630
+ "url": "https://github.com/sponsors/feross"
1631
+ },
1632
+ {
1633
+ "type": "patreon",
1634
+ "url": "https://www.patreon.com/feross"
1635
+ },
1636
+ {
1637
+ "type": "consulting",
1638
+ "url": "https://feross.org/support"
1639
+ }
1640
+ ],
1641
+ "license": "MIT"
1642
+ },
1643
+ "node_modules/safe-stable-stringify": {
1644
+ "version": "2.5.0",
1645
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
1646
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
1647
+ "license": "MIT",
1648
+ "engines": {
1649
+ "node": ">=10"
1650
+ }
1651
+ },
1652
+ "node_modules/secure-json-parse": {
1653
+ "version": "4.1.0",
1654
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
1655
+ "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
1656
+ "funding": [
1657
+ {
1658
+ "type": "github",
1659
+ "url": "https://github.com/sponsors/fastify"
1660
+ },
1661
+ {
1662
+ "type": "opencollective",
1663
+ "url": "https://opencollective.com/fastify"
1664
+ }
1665
+ ],
1666
+ "license": "BSD-3-Clause"
1667
+ },
1668
+ "node_modules/sonic-boom": {
1669
+ "version": "4.2.0",
1670
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
1671
+ "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
1672
+ "license": "MIT",
1673
+ "dependencies": {
1674
+ "atomic-sleep": "^1.0.0"
1675
+ }
1676
+ },
1677
+ "node_modules/source-map": {
1678
+ "version": "0.6.1",
1679
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1680
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1681
+ "license": "BSD-3-Clause",
1682
+ "engines": {
1683
+ "node": ">=0.10.0"
1684
+ }
1685
+ },
1686
+ "node_modules/split2": {
1687
+ "version": "4.2.0",
1688
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
1689
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
1690
+ "license": "ISC",
1691
+ "engines": {
1692
+ "node": ">= 10.x"
1693
+ }
1694
+ },
1695
+ "node_modules/string-width": {
1696
+ "version": "7.2.0",
1697
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
1698
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
1699
+ "license": "MIT",
1700
+ "dependencies": {
1701
+ "emoji-regex": "^10.3.0",
1702
+ "get-east-asian-width": "^1.0.0",
1703
+ "strip-ansi": "^7.1.0"
1704
+ },
1705
+ "engines": {
1706
+ "node": ">=18"
1707
+ },
1708
+ "funding": {
1709
+ "url": "https://github.com/sponsors/sindresorhus"
1710
+ }
1711
+ },
1712
+ "node_modules/strip-ansi": {
1713
+ "version": "7.2.0",
1714
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
1715
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
1716
+ "license": "MIT",
1717
+ "dependencies": {
1718
+ "ansi-regex": "^6.2.2"
1719
+ },
1720
+ "engines": {
1721
+ "node": ">=12"
1722
+ },
1723
+ "funding": {
1724
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
1725
+ }
1726
+ },
1727
+ "node_modules/strip-json-comments": {
1728
+ "version": "5.0.3",
1729
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
1730
+ "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
1731
+ "license": "MIT",
1732
+ "engines": {
1733
+ "node": ">=14.16"
1734
+ },
1735
+ "funding": {
1736
+ "url": "https://github.com/sponsors/sindresorhus"
1737
+ }
1738
+ },
1739
+ "node_modules/thread-stream": {
1740
+ "version": "4.0.0",
1741
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
1742
+ "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
1743
+ "license": "MIT",
1744
+ "dependencies": {
1745
+ "real-require": "^0.2.0"
1746
+ },
1747
+ "engines": {
1748
+ "node": ">=20"
1749
+ }
1750
+ },
1751
+ "node_modules/tmp": {
1752
+ "version": "0.2.7",
1753
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
1754
+ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
1755
+ "license": "MIT",
1756
+ "engines": {
1757
+ "node": ">=14.14"
1758
+ }
1759
+ },
1760
+ "node_modules/tsx": {
1761
+ "version": "4.23.11",
1762
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz",
1763
+ "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==",
1764
+ "license": "MIT",
1765
+ "dependencies": {
1766
+ "esbuild": "~0.28.0"
1767
+ },
1768
+ "bin": {
1769
+ "tsx": "dist/cli.mjs"
1770
+ },
1771
+ "engines": {
1772
+ "node": ">=18.0.0"
1773
+ },
1774
+ "optionalDependencies": {
1775
+ "fsevents": "~2.3.3"
1776
+ }
1777
+ },
1778
+ "node_modules/typescript": {
1779
+ "version": "7.0.2",
1780
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
1781
+ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
1782
+ "license": "Apache-2.0",
1783
+ "bin": {
1784
+ "tsc": "bin/tsc"
1785
+ },
1786
+ "engines": {
1787
+ "node": ">=16.20.0"
1788
+ },
1789
+ "optionalDependencies": {
1790
+ "@typescript/typescript-aix-ppc64": "7.0.2",
1791
+ "@typescript/typescript-darwin-arm64": "7.0.2",
1792
+ "@typescript/typescript-darwin-x64": "7.0.2",
1793
+ "@typescript/typescript-freebsd-arm64": "7.0.2",
1794
+ "@typescript/typescript-freebsd-x64": "7.0.2",
1795
+ "@typescript/typescript-linux-arm": "7.0.2",
1796
+ "@typescript/typescript-linux-arm64": "7.0.2",
1797
+ "@typescript/typescript-linux-loong64": "7.0.2",
1798
+ "@typescript/typescript-linux-mips64el": "7.0.2",
1799
+ "@typescript/typescript-linux-ppc64": "7.0.2",
1800
+ "@typescript/typescript-linux-riscv64": "7.0.2",
1801
+ "@typescript/typescript-linux-s390x": "7.0.2",
1802
+ "@typescript/typescript-linux-x64": "7.0.2",
1803
+ "@typescript/typescript-netbsd-arm64": "7.0.2",
1804
+ "@typescript/typescript-netbsd-x64": "7.0.2",
1805
+ "@typescript/typescript-openbsd-arm64": "7.0.2",
1806
+ "@typescript/typescript-openbsd-x64": "7.0.2",
1807
+ "@typescript/typescript-sunos-x64": "7.0.2",
1808
+ "@typescript/typescript-win32-arm64": "7.0.2",
1809
+ "@typescript/typescript-win32-x64": "7.0.2"
1810
+ }
1811
+ },
1812
+ "node_modules/uglify-js": {
1813
+ "version": "3.19.3",
1814
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
1815
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
1816
+ "license": "BSD-2-Clause",
1817
+ "optional": true,
1818
+ "bin": {
1819
+ "uglifyjs": "bin/uglifyjs"
1820
+ },
1821
+ "engines": {
1822
+ "node": ">=0.8.0"
1823
+ }
1824
+ },
1825
+ "node_modules/undici": {
1826
+ "version": "7.29.0",
1827
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
1828
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
1829
+ "license": "MIT",
1830
+ "engines": {
1831
+ "node": ">=20.18.1"
1832
+ }
1833
+ },
1834
+ "node_modules/undici-types": {
1835
+ "version": "7.18.2",
1836
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
1837
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
1838
+ "license": "MIT"
1839
+ },
1840
+ "node_modules/web-streams-polyfill": {
1841
+ "version": "3.3.3",
1842
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
1843
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
1844
+ "license": "MIT",
1845
+ "engines": {
1846
+ "node": ">= 8"
1847
+ }
1848
+ },
1849
+ "node_modules/wordwrap": {
1850
+ "version": "1.0.0",
1851
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
1852
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
1853
+ "license": "MIT"
1854
+ },
1855
+ "node_modules/wrap-ansi": {
1856
+ "version": "9.0.2",
1857
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
1858
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
1859
+ "license": "MIT",
1860
+ "dependencies": {
1861
+ "ansi-styles": "^6.2.1",
1862
+ "string-width": "^7.0.0",
1863
+ "strip-ansi": "^7.1.0"
1864
+ },
1865
+ "engines": {
1866
+ "node": ">=18"
1867
+ },
1868
+ "funding": {
1869
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1870
+ }
1871
+ },
1872
+ "node_modules/wrappy": {
1873
+ "version": "1.0.2",
1874
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1875
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1876
+ "license": "ISC"
1877
+ },
1878
+ "node_modules/y18n": {
1879
+ "version": "5.0.8",
1880
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
1881
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
1882
+ "license": "ISC",
1883
+ "engines": {
1884
+ "node": ">=10"
1885
+ }
1886
+ },
1887
+ "node_modules/yaml": {
1888
+ "version": "2.9.0",
1889
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
1890
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
1891
+ "license": "ISC",
1892
+ "bin": {
1893
+ "yaml": "bin.mjs"
1894
+ },
1895
+ "engines": {
1896
+ "node": ">= 14.6"
1897
+ },
1898
+ "funding": {
1899
+ "url": "https://github.com/sponsors/eemeli"
1900
+ }
1901
+ },
1902
+ "node_modules/yargs": {
1903
+ "version": "18.1.0",
1904
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
1905
+ "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
1906
+ "license": "MIT",
1907
+ "dependencies": {
1908
+ "cliui": "^9.0.1",
1909
+ "escalade": "^3.1.1",
1910
+ "get-caller-file": "^2.0.5",
1911
+ "string-width": "^8.2.1",
1912
+ "y18n": "^5.0.5",
1913
+ "yargs-parser": "^22.0.0"
1914
+ },
1915
+ "engines": {
1916
+ "node": "^20.19.0 || ^22.12.0 || >=23"
1917
+ }
1918
+ },
1919
+ "node_modules/yargs-parser": {
1920
+ "version": "22.0.0",
1921
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
1922
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
1923
+ "license": "ISC",
1924
+ "engines": {
1925
+ "node": "^20.19.0 || ^22.12.0 || >=23"
1926
+ }
1927
+ },
1928
+ "node_modules/yargs/node_modules/string-width": {
1929
+ "version": "8.2.2",
1930
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
1931
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
1932
+ "license": "MIT",
1933
+ "dependencies": {
1934
+ "get-east-asian-width": "^1.5.0",
1935
+ "strip-ansi": "^7.1.2"
1936
+ },
1937
+ "engines": {
1938
+ "node": ">=20"
1939
+ },
1940
+ "funding": {
1941
+ "url": "https://github.com/sponsors/sindresorhus"
1942
+ }
1943
+ },
1944
+ "node_modules/yocto-queue": {
1945
+ "version": "1.2.2",
1946
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
1947
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
1948
+ "license": "MIT",
1949
+ "engines": {
1950
+ "node": ">=12.20"
1951
+ },
1952
+ "funding": {
1953
+ "url": "https://github.com/sponsors/sindresorhus"
1954
+ }
1955
+ },
1956
+ "node_modules/zod": {
1957
+ "version": "4.4.3",
1958
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
1959
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
1960
+ "license": "MIT",
1961
+ "funding": {
1962
+ "url": "https://github.com/sponsors/colinhacks"
1963
+ }
1964
+ }
1965
+ }
1966
+ }
package.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "forge-code-evals",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "",
6
+ "license": "ISC",
7
+ "author": "Tushar Mathur <tusharmath@gmail.com>",
8
+ "type": "module",
9
+ "scripts": {
10
+ "eval": "tsx benchmarks/cli.ts",
11
+ "test:bounty": "tsx --test .github/scripts/bounty/tests/*.test.ts",
12
+ "bounty:sync-issue": "tsx .github/scripts/bounty/src/sync-issue.ts",
13
+ "bounty:sync-pr": "tsx .github/scripts/bounty/src/sync-pr.ts",
14
+ "bounty:sync-all-issues": "tsx .github/scripts/bounty/src/sync-all-issues.ts"
15
+ },
16
+ "dependencies": {
17
+ "@ai-sdk/google-vertex": "^5.0.0",
18
+ "@types/handlebars": "^4.0.40",
19
+ "@types/node": "^24.10.1",
20
+ "@types/tmp": "^0.2.6",
21
+ "@types/yargs": "^17.0.35",
22
+ "ai": "^7.0.0",
23
+ "chalk": "^6.0.0",
24
+ "csv-parse": "^7.0.0",
25
+ "handlebars": "^4.7.9",
26
+ "p-limit": "^7.2.0",
27
+ "pino": "^10.1.0",
28
+ "pino-pretty": "^13.1.2",
29
+ "strip-ansi": "^7.1.2",
30
+ "tmp": "^0.2.5",
31
+ "tsx": "^4.20.6",
32
+ "typescript": "^7.0.0",
33
+ "yaml": "^2.8.3",
34
+ "yargs": "^18.0.0",
35
+ "zod": "^4.0.0"
36
+ }
37
+ }
plans/2025-04-02-system-context-rendering-final.md ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dynamic System Context Rendering with Variables
2
+
3
+ ## Objective
4
+
5
+ Modify the system context rendering mechanism to:
6
+
7
+
8
+ 1. Re-render the system context on every conversation turn
9
+ 2. Pass variables to the system context renderer (similar to event rendering)
10
+
11
+ The current implementation renders the system context only once during agent initialization, which means time-sensitive information like `current_time` is not updated. Additionally, the system context cannot use dynamic variables from the conversation state.
12
+
13
+ ## Implementation Plan
14
+
15
+ ### 1. Update TemplateService Trait
16
+
17
+ Modify the `TemplateService` trait in `crates/forge_domain/src/services.rs` to include variables in the `render_system` method signature:
18
+
19
+ ```rust
20
+ async fn render_system(
21
+ &self,
22
+ agent: &Agent,
23
+ prompt: &Template<SystemContext>,
24
+ variables: &HashMap<String, Value>,
25
+ ) -> anyhow::Result<String>;
26
+ ```
27
+
28
+ ### 2. Modify ForgeTemplateService Implementation
29
+
30
+ Update the implementation in `crates/forge_services/src/template.rs` to handle variables in system context rendering:
31
+
32
+ ```rust
33
+ async fn render_system(
34
+ &self,
35
+ _agent: &Agent,
36
+ prompt: &Template<SystemContext>,
37
+ variables: &HashMap<String, Value>,
38
+ ) -> anyhow::Result<String> {
39
+ let env = self.infra.environment_service().get_environment();
40
+
41
+ // Build the walker, only setting max_depth if a value was provided
42
+ let mut walker = Walker::max_all();
43
+
44
+ // Only set max_depth if the value is provided
45
+ // Create maximum depth for file walker, defaulting to 1 if not specified
46
+ walker = walker.max_depth(_agent.max_walker_depth.unwrap_or(1));
47
+
48
+ let mut files = walker
49
+ .cwd(env.cwd.clone())
50
+ .get()
51
+ .await?
52
+ .iter()
53
+ .map(|f| f.path.to_string())
54
+ .collect::<Vec<_>>();
55
+
56
+ // Sort the files alphabetically to ensure consistent ordering
57
+ files.sort();
58
+
59
+ // Get current date and time with timezone
60
+ let current_time = Local::now().format("%Y-%m-%d %H:%M:%S %:z").to_string();
61
+
62
+ // Create the context with README content for all agents
63
+ let ctx = SystemContext {
64
+ current_time,
65
+ env: Some(env),
66
+ tool_information: Some(self.tool_service.usage_prompt()),
67
+ tool_supported: _agent.tool_supported.unwrap_or_default(),
68
+ files,
69
+ readme: README_CONTENT.to_string(),
70
+ custom_rules: _agent.custom_rules.as_ref().cloned().unwrap_or_default(),
71
+ variables: variables.clone(), // Add the variables
72
+ };
73
+
74
+ // Render the template with the context
75
+ let result = self.hb.render_template(prompt.template.as_str(), &ctx)?;
76
+ Ok(result)
77
+ }
78
+ ```
79
+
80
+ ### 3. Update SystemContext Struct
81
+
82
+ Modify the `SystemContext` struct in `crates/forge_domain/src/system_context.rs` to include variables:
83
+
84
+ ```rust
85
+ #[derive(Debug, Setters, Clone, Serialize, Deserialize)]
86
+ #[setters(strip_option)]
87
+ pub struct SystemContext {
88
+ // Current date and time at the time of context creation
89
+ pub current_time: String,
90
+
91
+ // Environment information to be included in the system context
92
+ #[serde(skip_serializing_if = "Option::is_none")]
93
+ pub env: Option<Environment>,
94
+
95
+ // Information about available tools that can be used by the agent
96
+ #[serde(skip_serializing_if = "Option::is_none")]
97
+ pub tool_information: Option<String>,
98
+
99
+ /// Indicates whether the agent supports tools.
100
+ /// This value is populated directly from the Agent configuration.
101
+ #[serde(default)]
102
+ pub tool_supported: bool,
103
+
104
+ // List of file paths that are relevant for the agent context
105
+ #[serde(skip_serializing_if = "Vec::is_empty")]
106
+ pub files: Vec<String>,
107
+
108
+ // README content to provide project context to the agent
109
+ pub readme: String,
110
+
111
+ #[serde(skip_serializing_if = "String::is_empty")]
112
+ pub custom_rules: String,
113
+
114
+ // Variables to pass to the system context
115
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
116
+ pub variables: HashMap<String, Value>,
117
+ }
118
+
119
+ // Update the Default implementation if it exists
120
+ impl Default for SystemContext {
121
+ fn default() -> Self {
122
+ Self {
123
+ current_time: String::new(),
124
+ env: None,
125
+ tool_information: None,
126
+ tool_supported: false,
127
+ files: Vec::new(),
128
+ readme: String::new(),
129
+ custom_rules: String::new(),
130
+ variables: HashMap::new(),
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### 4. Update Orchestrator to Re-render System Context
137
+
138
+ Modify the orchestrator's conversation loop in `crates/forge_domain/src/orch.rs` to re-render the system context on each iteration:
139
+
140
+ ```rust
141
+ async fn init_agent(&self, agent_id: &AgentId, event: &Event) -> anyhow::Result<()> {
142
+ let conversation = self.get_conversation().await?;
143
+ debug!(
144
+ conversation_id = %conversation.id,
145
+ agent = %agent_id,
146
+ event = ?event,
147
+ "Initializing agent"
148
+ );
149
+ let agent = conversation.workflow.get_agent(agent_id)?;
150
+
151
+ let mut context = if agent.ephemeral.unwrap_or_default() {
152
+ self.init_agent_context(agent).await?
153
+ } else {
154
+ match conversation.context(&agent.id) {
155
+ Some(context) => context.clone(),
156
+ None => self.init_agent_context(agent).await?,
157
+ }
158
+ };
159
+
160
+ if let Some(temperature) = agent.temperature {
161
+ context = context.temperature(temperature);
162
+ }
163
+
164
+ let content = if let Some(user_prompt) = &agent.user_prompt {
165
+ // Get conversation variables from the conversation
166
+ let variables = &conversation.variables;
167
+
168
+ // Use the consolidated render_event method which handles suggestions and
169
+ // variables
170
+ self.services
171
+ .template_service()
172
+ .render_event(agent, user_prompt, event, variables)
173
+ .await?
174
+ } else {
175
+ // Use the raw event value as content if no user_prompt is provided
176
+ event.value.to_string()
177
+ };
178
+
179
+ if !content.is_empty() {
180
+ context = context.add_message(ContextMessage::user(content));
181
+ }
182
+
183
+ // Process attachments
184
+ let attachments = self
185
+ .services
186
+ .attachment_service()
187
+ .attachments(&event.value.to_string())
188
+ .await?;
189
+
190
+ for attachment in attachments.into_iter() {
191
+ match attachment.content_type {
192
+ ContentType::Image => {
193
+ context = context.add_message(ContextMessage::Image(attachment.content));
194
+ }
195
+ ContentType::Text => {
196
+ let content = format!(
197
+ "<file_content path=\"{}\">{}</file_content>",
198
+ attachment.path, attachment.content
199
+ );
200
+ context = context.add_message(ContextMessage::user(content));
201
+ }
202
+ }
203
+ }
204
+
205
+ self.set_context(&agent.id, context.clone()).await?;
206
+
207
+ loop {
208
+ // Get the latest conversation variables
209
+ let variables = self.conversation.read().await.variables.clone();
210
+
211
+ // Re-render system prompt if present
212
+ if let Some(system_prompt) = &agent.system_prompt {
213
+ let system_message = self
214
+ .services
215
+ .template_service()
216
+ .render_system(agent, system_prompt, &variables)
217
+ .await?;
218
+
219
+ context = context.set_first_system_message(system_message);
220
+ }
221
+
222
+ // Set context for the current loop iteration
223
+ self.set_context(&agent.id, context.clone()).await?;
224
+ let response = self
225
+ .services
226
+ .provider_service()
227
+ .chat(
228
+ agent
229
+ .model
230
+ .as_ref()
231
+ .ok_or(Error::MissingModel(agent.id.clone()))?,
232
+ context.clone(),
233
+ )
234
+ .await?;
235
+ let ChatCompletionResult { tool_calls, content } =
236
+ self.collect_messages(agent, response).await?;
237
+
238
+ // Get all tool results using the helper function
239
+ let tool_results = self.get_all_tool_results(agent, &tool_calls).await?;
240
+
241
+ context = context
242
+ .add_message(ContextMessage::assistant(content, Some(tool_calls)))
243
+ .add_tool_results(tool_results.clone());
244
+
245
+ // Check if context requires compression
246
+ context = self.compactor.compact_context(agent, context).await?;
247
+
248
+ self.set_context(&agent.id, context.clone()).await?;
249
+ self.sync_conversation().await?;
250
+
251
+ if tool_results.is_empty() {
252
+ break;
253
+ }
254
+ }
255
+
256
+ self.complete_turn(&agent.id).await?;
257
+
258
+ self.sync_conversation().await?;
259
+
260
+ Ok(())
261
+ }
262
+ ```
263
+
264
+ ### 5. Update Tests
265
+
266
+ Add tests for the updated `render_system` method with variables:
267
+
268
+ ```rust
269
+ // In forge_services/src/template.rs or a test file
270
+ #[tokio::test]
271
+ async fn test_render_system_with_variables() {
272
+ // Create a test agent
273
+ let agent = Agent::new("test-agent")
274
+ .system_prompt(Template::from_string("{{current_time}} - {{variables.test_var}}"));
275
+
276
+ // Create test variables
277
+ let mut variables = HashMap::new();
278
+ variables.insert("test_var".to_string(), json!("test_value"));
279
+
280
+ // Render the system prompt with variables
281
+ let result = template_service
282
+ .render_system(&agent, agent.system_prompt.as_ref().unwrap(), &variables)
283
+ .await
284
+ .unwrap();
285
+
286
+ // Verify the result contains both the current time and the variable
287
+ assert!(result.contains("test_value"));
288
+ }
289
+ ```
290
+
291
+ ### 6. Update init_agent_context Method
292
+
293
+ Also need to update the `init_agent_context` method in `crates/forge_domain/src/orch.rs` to pass empty variables:
294
+
295
+ ```rust
296
+ async fn init_agent_context(&self, agent: &Agent) -> anyhow::Result<Context> {
297
+ let tool_defs = self.init_tool_definitions(agent);
298
+
299
+ // Use the agent's tool_supported flag directly instead of querying the provider
300
+ let tool_supported = agent.tool_supported.unwrap_or_default();
301
+
302
+ let mut context = Context::default();
303
+
304
+ if let Some(system_prompt) = &agent.system_prompt {
305
+ // Create empty variables for initial rendering
306
+ let empty_variables = HashMap::new();
307
+
308
+ let system_message = self
309
+ .services
310
+ .template_service()
311
+ .render_system(agent, system_prompt, &empty_variables)
312
+ .await?;
313
+
314
+ context = context.set_first_system_message(system_message);
315
+ }
316
+
317
+ Ok(context.extend_tools(if tool_supported {
318
+ tool_defs
319
+ } else {
320
+ Vec::new()
321
+ }))
322
+ }
323
+ ```
324
+
325
+ ## Verification Criteria
326
+
327
+ The implementation will be considered successful if:
328
+
329
+
330
+ 1. The system context is re-rendered on each conversation turn, ensuring up-to-date information
331
+ 2. Variables from the conversation state are correctly passed to the system context renderer
332
+ 3. System context templates can access and display these variables (templates have already been modified)
333
+ 4. All tests pass with the new implementation
334
+ 5. The code is clean, without unnecessary backward compatibility layers
335
+
336
+ ## Technical Design Notes
337
+
338
+
339
+ 1. **Clean Approach**: The implementation takes a clean approach without backward compatibility concerns, as specified.
340
+ 2. **Performance**: Re-rendering the system context on each turn should not cause performance issues.
341
+ 3. **Consistency**: The approach aligns with how event rendering already handles variables.
342
+ 4. **Error Handling**: Proper error handling is maintained throughout the implementation.
343
+
344
+
plans/2025-04-02-system-context-rendering-v1.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dynamic System Context Rendering with Variables
2
+
3
+ ## Objective
4
+
5
+ Modify the system context rendering mechanism to:
6
+
7
+
8
+ 1. Re-render the system context on every conversation turn
9
+ 2. Pass variables to the system context renderer (similar to event rendering)
10
+
11
+ The current implementation renders the system context only once during agent initialization, which means time-sensitive information like `current_time` is not updated. Additionally, the system context cannot use dynamic variables from the conversation state.
12
+
13
+ ## Implementation Plan
14
+
15
+ ### 1. Update TemplateService Trait
16
+
17
+ Modify the `TemplateService` trait in `crates/forge_domain/src/services.rs` to include variables in the `render_system` method signature:
18
+
19
+ ```rust
20
+ async fn render_system(
21
+ &self,
22
+ agent: &Agent,
23
+ prompt: &Template<SystemContext>,
24
+ variables: &HashMap<String, Value>,
25
+ ) -> anyhow::Result<String>;
26
+ ```
27
+
28
+ ### 2. Modify ForgeTemplateService Implementation
29
+
30
+ Update the implementation in `crates/forge_services/src/template.rs` to handle variables in system context rendering:
31
+
32
+ ```rust
33
+ async fn render_system(
34
+ &self,
35
+ _agent: &Agent,
36
+ prompt: &Template<SystemContext>,
37
+ variables: &HashMap<String, Value>,
38
+ ) -> anyhow::Result<String> {
39
+ // Existing code...
40
+
41
+ // Create the context with README content for all agents
42
+ let mut ctx = SystemContext {
43
+ current_time,
44
+ env: Some(env),
45
+ tool_information: Some(self.tool_service.usage_prompt()),
46
+ tool_supported: _agent.tool_supported.unwrap_or_default(),
47
+ files,
48
+ readme: README_CONTENT.to_string(),
49
+ custom_rules: _agent.custom_rules.as_ref().cloned().unwrap_or_default(),
50
+ variables: variables.clone(), // Add the variables
51
+ };
52
+
53
+ // Render the template with the context and variables
54
+ let result = self.hb.render_template(prompt.template.as_str(), &ctx)?;
55
+ Ok(result)
56
+ }
57
+ ```
58
+
59
+ ### 3. Update SystemContext Struct
60
+
61
+ Modify the `SystemContext` struct in `crates/forge_domain/src/system_context.rs` to include variables:
62
+
63
+ ```rust
64
+ #[derive(Debug, Setters, Clone, Serialize, Deserialize)]
65
+ #[setters(strip_option)]
66
+ pub struct SystemContext {
67
+ // Existing fields...
68
+
69
+ // Variables passed to the template
70
+ #[serde(skip_serializing_if = "HashMap::is_empty")]
71
+ pub variables: HashMap<String, Value>,
72
+ }
73
+ ```
74
+
75
+ ### 4. Update Orchestrator to Re-render System Context
76
+
77
+ Modify the orchestrator's conversation loop in `crates/forge_domain/src/orch.rs` to re-render the system context on each iteration:
78
+
79
+ ```rust
80
+ async fn init_agent(&self, agent_id: &AgentId, event: &Event) -> anyhow::Result<()> {
81
+ // Existing code...
82
+
83
+ loop {
84
+ // Get up-to-date variables
85
+ let conversation_vars = self.conversation.read().await.variables.clone();
86
+
87
+ // Re-render the system message if system_prompt is present
88
+ if let Some(system_prompt) = &agent.system_prompt {
89
+ let system_message = self
90
+ .services
91
+ .template_service()
92
+ .render_system(agent, system_prompt, &conversation_vars)
93
+ .await?;
94
+
95
+ // Update the system message in the context
96
+ context = context.set_first_system_message(system_message);
97
+ }
98
+
99
+ // Set context for the current loop iteration
100
+ self.set_context(&agent.id, context.clone()).await?;
101
+
102
+ // Existing loop code...
103
+
104
+ // If no tool results, break the loop as before
105
+ if tool_results.is_empty() {
106
+ break;
107
+ }
108
+ }
109
+
110
+ // Existing code...
111
+ }
112
+ ```
113
+
114
+ ### 5. Update System Context Templates
115
+
116
+ Ensure the templates can use variables from the context:
117
+
118
+ In `templates/forge-partial-system-info.hbs` and other system templates, add support for variables:
119
+
120
+ ```handlebars
121
+ <system_info>
122
+ <operating_system>{{env.os}}</operating_system>
123
+ <current_time>{{current_time}}</current_time>
124
+ <current_working_directory>{{env.cwd}}</current_working_directory>
125
+ <default_shell>{{env.shell}}</default_shell>
126
+ <home_directory>{{env.home}}</home_directory>
127
+ <file_list>
128
+ {{#each files}} - {{this}}
129
+ {{/each}}
130
+ </file_list>
131
+ </system_info>
132
+
133
+ {{!-- Add access to variables --}}
134
+ {{#if variables}}
135
+ <variables>
136
+ {{#each variables}}
137
+ <{{@key}}>{{this}}</{{@key}}>
138
+ {{/each}}
139
+ </variables>
140
+ {{/if}}
141
+ ```
142
+
143
+ ### 6. Update Tests
144
+
145
+ Update relevant tests to verify the changes:
146
+
147
+
148
+ 1. Add tests for the updated `render_system` method with variables
149
+ 2. Ensure the system context is properly updated in the orchestrator tests
150
+ 3. Verify that templates can correctly access variables in the system context
151
+
152
+ ## Verification Criteria
153
+
154
+ The implementation will be considered successful if:
155
+
156
+
157
+ 1. The system context is re-rendered on each conversation turn, with up-to-date `current_time`
158
+ 2. Variables from the conversation state are correctly passed to the system context renderer
159
+ 3. System context templates can access and display these variables
160
+ 4. All tests pass with the new implementation
161
+ 5. The overall API remains consistent with the existing paradigm
162
+
163
+ ## Technical Design Notes
164
+
165
+
166
+ 1. **Backward Compatibility**: The implementation should maintain backward compatibility with existing templates by making variables optional.
167
+ 2. **Performance**: Re-rendering the system context on each turn is not expected to cause performance issues, as confirmed.
168
+ 3. **Consistency**: The approach aligns with how event rendering already handles variables.
169
+ 4. **Error Handling**: Proper error handling should be maintained throughout the implementation.
170
+
171
+
plans/2025-04-02-system-context-rendering-v2.md ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dynamic System Context Rendering with Variables
2
+
3
+ ## Objective
4
+
5
+ Modify the system context rendering mechanism to:
6
+
7
+
8
+
9
+
10
+ 1. Re-render the system context on every conversation turn
11
+ 2. Pass variables to the system context renderer (similar to event rendering)
12
+
13
+ The current implementation renders the system context only once during agent initialization, which means time-sensitive information like `current_time` is not updated. Additionally, the system context cannot use dynamic variables from the conversation state.
14
+
15
+ ## Implementation Plan
16
+
17
+ ### 1. Update TemplateService Trait
18
+
19
+ Modify the `TemplateService` trait in `crates/forge_domain/src/services.rs` to include variables in the `render_system` method signature:
20
+
21
+ ```rust
22
+ async fn render_system(
23
+ &self,
24
+ agent: &Agent,
25
+ prompt: &Template<SystemContext>,
26
+ variables: &HashMap<String, Value>,
27
+ ) -> anyhow::Result<String>;
28
+ ```
29
+
30
+ ### 2. Modify ForgeTemplateService Implementation
31
+
32
+ Update the implementation in `crates/forge_services/src/template.rs` to handle variables in system context rendering:
33
+
34
+ ```rust
35
+ async fn render_system(
36
+ &self,
37
+ _agent: &Agent,
38
+ prompt: &Template<SystemContext>,
39
+ variables: &HashMap<String, Value>,
40
+ ) -> anyhow::Result<String> {
41
+ let env = self.infra.environment_service().get_environment();
42
+
43
+ // Build the walker, only setting max_depth if a value was provided
44
+ let mut walker = Walker::max_all();
45
+
46
+ // Only set max_depth if the value is provided
47
+ // Create maximum depth for file walker, defaulting to 1 if not specified
48
+ walker = walker.max_depth(_agent.max_walker_depth.unwrap_or(1));
49
+
50
+ let mut files = walker
51
+ .cwd(env.cwd.clone())
52
+ .get()
53
+ .await?
54
+ .iter()
55
+ .map(|f| f.path.to_string())
56
+ .collect::<Vec<_>>();
57
+
58
+ // Sort the files alphabetically to ensure consistent ordering
59
+ files.sort();
60
+
61
+ // Get current date and time with timezone
62
+ let current_time = Local::now().format("%Y-%m-%d %H:%M:%S %:z").to_string();
63
+
64
+ // Create the context with README content for all agents
65
+ let ctx = SystemContext {
66
+ current_time,
67
+ env: Some(env),
68
+ tool_information: Some(self.tool_service.usage_prompt()),
69
+ tool_supported: _agent.tool_supported.unwrap_or_default(),
70
+ files,
71
+ readme: README_CONTENT.to_string(),
72
+ custom_rules: _agent.custom_rules.as_ref().cloned().unwrap_or_default(),
73
+ variables: variables.clone(), // Add the variables
74
+ };
75
+
76
+ // Render the template with the context
77
+ let result = self.hb.render_template(prompt.template.as_str(), &ctx)?;
78
+ Ok(result)
79
+ }
80
+ ```
81
+
82
+ ### 3. Update SystemContext Struct
83
+
84
+ Modify the `SystemContext` struct in `crates/forge_domain/src/system_context.rs` to include variables:
85
+
86
+ ```rust
87
+ #[derive(Debug, Setters, Clone, Serialize, Deserialize)]
88
+ #[setters(strip_option)]
89
+ pub struct SystemContext {
90
+ // Current date and time at the time of context creation
91
+ pub current_time: String,
92
+
93
+ // Environment information to be included in the system context
94
+ #[serde(skip_serializing_if = "Option::is_none")]
95
+ pub env: Option<Environment>,
96
+
97
+ // Information about available tools that can be used by the agent
98
+ #[serde(skip_serializing_if = "Option::is_none")]
99
+ pub tool_information: Option<String>,
100
+
101
+ /// Indicates whether the agent supports tools.
102
+ /// This value is populated directly from the Agent configuration.
103
+ #[serde(default)]
104
+ pub tool_supported: bool,
105
+
106
+ // List of file paths that are relevant for the agent context
107
+ #[serde(skip_serializing_if = "Vec::is_empty")]
108
+ pub files: Vec<String>,
109
+
110
+ // README content to provide project context to the agent
111
+ pub readme: String,
112
+
113
+ #[serde(skip_serializing_if = "String::is_empty")]
114
+ pub custom_rules: String,
115
+
116
+ // Variables to pass to the system context
117
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
118
+ pub variables: HashMap<String, Value>,
119
+ }
120
+
121
+ // Update the Default implementation if it exists
122
+ impl Default for SystemContext {
123
+ fn default() -> Self {
124
+ Self {
125
+ current_time: String::new(),
126
+ env: None,
127
+ tool_information: None,
128
+ tool_supported: false,
129
+ files: Vec::new(),
130
+ readme: String::new(),
131
+ custom_rules: String::new(),
132
+ variables: HashMap::new(),
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### 4. Update Orchestrator to Re-render System Context
139
+
140
+ Modify the orchestrator's conversation loop in `crates/forge_domain/src/orch.rs` to re-render the system context on each iteration:
141
+
142
+ ```rust
143
+ async fn init_agent(&self, agent_id: &AgentId, event: &Event) -> anyhow::Result<()> {
144
+ let conversation = self.get_conversation().await?;
145
+ debug!(
146
+ conversation_id = %conversation.id,
147
+ agent = %agent_id,
148
+ event = ?event,
149
+ "Initializing agent"
150
+ );
151
+ let agent = conversation.workflow.get_agent(agent_id)?;
152
+
153
+ let mut context = if agent.ephemeral.unwrap_or_default() {
154
+ self.init_agent_context(agent).await?
155
+ } else {
156
+ match conversation.context(&agent.id) {
157
+ Some(context) => context.clone(),
158
+ None => self.init_agent_context(agent).await?,
159
+ }
160
+ };
161
+
162
+ if let Some(temperature) = agent.temperature {
163
+ context = context.temperature(temperature);
164
+ }
165
+
166
+ let content = if let Some(user_prompt) = &agent.user_prompt {
167
+ // Get conversation variables from the conversation
168
+ let variables = &conversation.variables;
169
+
170
+ // Use the consolidated render_event method which handles suggestions and
171
+ // variables
172
+ self.services
173
+ .template_service()
174
+ .render_event(agent, user_prompt, event, variables)
175
+ .await?
176
+ } else {
177
+ // Use the raw event value as content if no user_prompt is provided
178
+ event.value.to_string()
179
+ };
180
+
181
+ if !content.is_empty() {
182
+ context = context.add_message(ContextMessage::user(content));
183
+ }
184
+
185
+ // Process attachments
186
+ let attachments = self
187
+ .services
188
+ .attachment_service()
189
+ .attachments(&event.value.to_string())
190
+ .await?;
191
+
192
+ for attachment in attachments.into_iter() {
193
+ match attachment.content_type {
194
+ ContentType::Image => {
195
+ context = context.add_message(ContextMessage::Image(attachment.content));
196
+ }
197
+ ContentType::Text => {
198
+ let content = format!(
199
+ "<file_content path=\"{}\">{}</file_content>",
200
+ attachment.path, attachment.content
201
+ );
202
+ context = context.add_message(ContextMessage::user(content));
203
+ }
204
+ }
205
+ }
206
+
207
+ self.set_context(&agent.id, context.clone()).await?;
208
+
209
+ loop {
210
+ // Get the latest conversation variables
211
+ let variables = self.conversation.read().await.variables.clone();
212
+
213
+ // Re-render system prompt if present
214
+ if let Some(system_prompt) = &agent.system_prompt {
215
+ let system_message = self
216
+ .services
217
+ .template_service()
218
+ .render_system(agent, system_prompt, &variables)
219
+ .await?;
220
+
221
+ context = context.set_first_system_message(system_message);
222
+ }
223
+
224
+ // Set context for the current loop iteration
225
+ self.set_context(&agent.id, context.clone()).await?;
226
+ let response = self
227
+ .services
228
+ .provider_service()
229
+ .chat(
230
+ agent
231
+ .model
232
+ .as_ref()
233
+ .ok_or(Error::MissingModel(agent.id.clone()))?,
234
+ context.clone(),
235
+ )
236
+ .await?;
237
+ let ChatCompletionResult { tool_calls, content } =
238
+ self.collect_messages(agent, response).await?;
239
+
240
+ // Get all tool results using the helper function
241
+ let tool_results = self.get_all_tool_results(agent, &tool_calls).await?;
242
+
243
+ context = context
244
+ .add_message(ContextMessage::assistant(content, Some(tool_calls)))
245
+ .add_tool_results(tool_results.clone());
246
+
247
+ // Check if context requires compression
248
+ context = self.compactor.compact_context(agent, context).await?;
249
+
250
+ self.set_context(&agent.id, context.clone()).await?;
251
+ self.sync_conversation().await?;
252
+
253
+ if tool_results.is_empty() {
254
+ break;
255
+ }
256
+ }
257
+
258
+ self.complete_turn(&agent.id).await?;
259
+
260
+ self.sync_conversation().await?;
261
+
262
+ Ok(())
263
+ }
264
+ ```
265
+
266
+
267
+ ### 5. Update Tests
268
+
269
+ Add tests for the updated `render_system` method with variables:
270
+
271
+ ```rust
272
+ // In forge_services/src/template.rs or a test file
273
+ #[tokio::test]
274
+ async fn test_render_system_with_variables() {
275
+ // Create a test agent
276
+ let agent = Agent::new("test-agent")
277
+ .system_prompt(Template::from_string("{{current_time}} - {{variables.test_var}}"));
278
+
279
+ // Create test variables
280
+ let mut variables = HashMap::new();
281
+ variables.insert("test_var".to_string(), json!("test_value"));
282
+
283
+ // Render the system prompt with variables
284
+ let result = template_service
285
+ .render_system(&agent, agent.system_prompt.as_ref().unwrap(), &variables)
286
+ .await
287
+ .unwrap();
288
+
289
+ // Verify the result contains both the current time and the variable
290
+ assert!(result.contains("test_value"));
291
+ }
292
+ ```
293
+
294
+ ## Verification Criteria
295
+
296
+ The implementation will be considered successful if:
297
+
298
+
299
+
300
+ 1. The system context is re-rendered on each conversation turn, ensuring up-to-date information
301
+ 2. Variables from the conversation state are correctly passed to the system context renderer
302
+ 3. System context templates can access and display these variables
303
+ 4. All tests pass with the new implementation
304
+ 5. The code is clean, without unnecessary backward compatibility layers
305
+
306
+ ## Technical Design Notes
307
+
308
+
309
+
310
+ 1. **Clean Approach**: The implementation takes a clean approach without backward compatibility concerns, as specified.
311
+ 2. **Performance**: Re-rendering the system context on each turn should not cause performance issues.
312
+ 3. **Consistency**: The approach aligns with how event rendering already handles variables.
313
+ 4. **Error Handling**: Proper error handling is maintained throughout the implementation.
314
+
315
+
plans/2025-04-06-retry-config-migration.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Migrating RetryConfig from Workflow to Environment with Environment Variables
2
+
3
+ ## Objective
4
+ The objective is to move the retry configuration from the `Workflow` struct to the `Environment` struct and enable configuration via environment variables. This will provide flexibility and a centralized location for retry configuration that can be accessed by various components without relying on the workflow repository.
5
+
6
+ ## Implementation Plan
7
+
8
+ ### 1. Update the Environment struct
9
+ Modify the `Environment` struct in `crates/forge_domain/src/env.rs` to include a `RetryConfig` field.
10
+
11
+ ```rust
12
+ #[derive(Debug, Setters, Clone, Serialize, Deserialize)]
13
+ #[serde(rename_all = "camelCase")]
14
+ #[setters(strip_option)]
15
+ pub struct Environment {
16
+ // Existing fields...
17
+
18
+ /// Configuration for the retry mechanism
19
+ pub retry_config: RetryConfig,
20
+ }
21
+ ```
22
+
23
+ ### 2. Add Environment Variable Support for Retry Configuration
24
+ Enhance `ForgeEnvironmentService` in `crates/forge_infra/src/env.rs` to read retry configuration from environment variables:
25
+
26
+ - `FORGE_RETRY_INITIAL_BACKOFF_MS` - Initial backoff delay in milliseconds
27
+ - `FORGE_RETRY_BACKOFF_FACTOR` - Multiplication factor for each retry attempt
28
+ - `FORGE_RETRY_MAX_ATTEMPTS` - Maximum number of retry attempts
29
+ - `FORGE_RETRY_STATUS_CODES` - Comma-separated list of HTTP status codes that should trigger retries
30
+
31
+ The service should read these variables and use them to configure the `RetryConfig` instance, falling back to default values if the environment variables are not set.
32
+
33
+ ### 3. Remove RetryConfig from Workflow struct
34
+ Remove the `retry` field from the `Workflow` struct in `crates/forge_domain/src/workflow.rs`.
35
+
36
+ ### 4. Update ForgeProviderService
37
+ Modify the `ForgeProviderService::new` method in `crates/forge_services/src/provider.rs` to obtain the retry configuration from the environment rather than from the workflow:
38
+
39
+ ```rust
40
+ pub fn new<F: Infrastructure>(infra: Arc<F>) -> Self {
41
+ let infra = infra.clone();
42
+ let env = infra.environment_service().get_environment();
43
+ let provider = env.provider.clone();
44
+ let retry_config = env.retry_config;
45
+ Self {
46
+ client: Arc::new(Client::new(provider, retry_config).unwrap()),
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### 5. Update WorkflowRepository trait
52
+ Update the `WorkflowRepository` trait in `crates/forge_services/src/infra.rs` to remove any references to retry configuration.
53
+
54
+ ### 6. Update ForgeWorkflowRepository implementation
55
+ Modify the `ForgeWorkflowRepository` implementation in `crates/forge_infra/src/workflow.rs` to reflect the changes in the `Workflow` struct.
56
+
57
+ ### 7. Update tests
58
+ Update any tests that rely on the `retry` field in the `Workflow` struct to use the new field in `Environment` instead.
59
+
60
+ ### 8. Update Documentation
61
+ Update the documentation to explain how to configure retry settings using environment variables.
62
+
63
+ ## Verification Criteria
64
+
65
+ 1. The codebase should compile successfully after the changes.
66
+ 2. All tests should pass after the changes.
67
+ 3. The retry mechanism should work correctly with the configuration now sourced from the `Environment`.
68
+ 4. The retry configuration should be properly read from environment variables when they are set.
69
+ 5. Provider initialization should properly use the retry configuration from the environment.
70
+ 6. Default values should be used for retry configuration when environment variables are not set.
71
+
72
+ ## Potential Risks and Mitigations
73
+
74
+ ### Risk: Breaking existing code that accesses retry config from Workflow
75
+ - Mitigation: Identify all locations where retry configuration is accessed from Workflow and update them to use Environment instead.
76
+
77
+ ### Risk: Default values for retry configuration might differ
78
+ - Mitigation: Ensure that default values for the retry configuration are consistent between the old and new implementations.
79
+
80
+ ### Risk: Incomplete migration could lead to retry configuration being read from both Workflow and Environment
81
+ - Mitigation: Thoroughly test the application to ensure retry configuration is only read from Environment after the migration.
82
+
83
+ ### Risk: Backward compatibility issues with existing workflows
84
+ - Mitigation: Consider adding a compatibility layer or migration process for existing workflows that have retry configuration.
85
+
86
+ ### Risk: Environment variable parsing errors
87
+ - Mitigation: Add robust error handling and validation for environment variable parsing, with clear error messages.
88
+
89
+ ### Risk: Missing environment variables leading to unexpected behavior
90
+ - Mitigation: Ensure clear documentation and sensible defaults for all retry-related environment variables.
plans/2025-04-11-tool-call-context-implementation.md ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Tool Call Context Implementation
2
+
3
+ ## Objective
4
+
5
+ Add a required `ToolCallContext` parameter to the `ExecutableTool` trait's `call` method. This will allow for additional context to be passed to tool implementations at runtime, maintaining an extensible structure for future needs while initially keeping the context empty.
6
+
7
+ ## Implementation Plan
8
+
9
+
10
+ 1. Define the `ToolCallContext` struct in the domain layer
11
+ Priority: High
12
+ Complexity: Low
13
+ Dependencies: None
14
+
15
+ Create a new struct in `forge_domain` (likely in a new file `tool_call_context.rs`) to represent the context passed to tool calls. Initially, this will be an empty struct with the infrastructure in place for future extension.
16
+ 2. Create a helper function for test contexts
17
+ Priority: High
18
+ Complexity: Low
19
+ Dependencies: Step 1
20
+
21
+ Create a function to easily generate a default test context, such as `ToolCallContext::for_tests()`. This will make it simpler to update test code.
22
+ 3. Update the `ExecutableTool` trait definition
23
+ Priority: High
24
+ Complexity: Low
25
+ Dependencies: Step 1
26
+
27
+ Modify the trait definition in `crates/forge_domain/src/tool_definition.rs` to add the `ToolCallContext` parameter to the `call` method.
28
+ 4. Modify the `JsonTool` adapter in `forge_domain/src/tool.rs`
29
+ Priority: High
30
+ Complexity: Low
31
+ Dependencies: Step 3
32
+
33
+ Update the `JsonTool` adapter to pass the `ToolCallContext` to the wrapped tool's `call` method.
34
+ 5. Modify the `ToolService` implementation
35
+ Priority: High
36
+ Complexity: Medium
37
+ Dependencies: Step 3
38
+
39
+ Update `crates/forge_services/src/tool_service.rs` to create and pass a `ToolCallContext` when calling tools.
40
+ 6. Update all tool implementations
41
+ Priority: High
42
+ Complexity: High
43
+ Dependencies: Step 3
44
+
45
+ Update all implementations of `ExecutableTool` across the codebase to accept the new `ToolCallContext` parameter:
46
+ * Shell tool (`crates/forge_services/src/tools/shell/shell_tool.rs`)
47
+ * Show user tool (`crates/forge_services/src/tools/show_user.rs`)
48
+ * File system tools (in `crates/forge_services/src/tools/fs/`)
49
+ * Patch tools (in `crates/forge_services/src/tools/patch/`)
50
+ * Fetch tool (`crates/forge_services/src/tools/fetch.rs`)
51
+ * Other tools discovered during implementation
52
+ 7. Update test implementations with a systematic approach
53
+ Priority: High
54
+ Complexity: High
55
+ Dependencies: Steps 2, 6
56
+
57
+ Based on the analysis, there are numerous tests that implement `ExecutableTool`. Adopt the following systematic approach:
58
+
59
+ a. Create a test utility module with helper functions for creating test contexts
60
+ b. Update mock tool implementations in test modules:
61
+ * `SuccessTool` and `FailureTool` in `tool_service.rs`
62
+ * Test tools in filesystem modules
63
+ * Test tools in patch modules
64
+ * Any other mock implementations in test code
65
+ c. Update test invocations to pass the context parameter
66
+ d. Add context parameters to all test function calls
67
+ 8. Incremental compilation verification
68
+ Priority: Medium
69
+ Complexity: Medium
70
+ Dependencies: Steps 3-7
71
+
72
+ Periodically run cargo check after updating each major component or group of related files to catch compilation errors early.
73
+ 9. Final verification and testing
74
+ Priority: High
75
+ Complexity: Low
76
+ Dependencies: All previous steps
77
+
78
+ Run full test suite to verify that all code compiles and tests pass:
79
+
80
+ ```
81
+ cargo insta test --accept --unreferenced=delete
82
+ cargo +nightly fmt --all
83
+ cargo +nightly clippy --fix --allow-staged --allow-dirty --workspace
84
+ ```
85
+
86
+ ## Verification Criteria
87
+
88
+ * The `ExecutableTool` trait includes `ToolCallContext` as a parameter in its `call` method
89
+ * All implementations of `ExecutableTool` accept and handle the new parameter
90
+ * All tests are updated and pass successfully
91
+ * The codebase compiles with no warnings or errors
92
+ * Existing functionality continues to work as expected
93
+
94
+ ## Potential Risks and Mitigations
95
+
96
+ * Risk 1: Large number of implementations to update (13+ files identified)
97
+ Mitigation: Use systematic approach with clear test helper functions and incremental verification
98
+ * Risk 2: Hidden implementations in macros or nested structures
99
+ Mitigation: Careful grep searches and systematic compiler error resolution
100
+ * Risk 3: Breaking changes to API contract
101
+ Mitigation: Since this is a required parameter, ensure thorough testing after updates
102
+ * Risk 4: Missing test implementations leading to test failures
103
+ Mitigation: Develop helper functions for tests and use incremental compilation
104
+
105
+ ## Alternative Approaches
106
+
107
+
108
+ 1. Make `ToolCallContext` optional with a default: Could reduce the impact but wouldn't align with the requirement for it to be required.
109
+ 2. Use a trait object approach: Pass context as a trait object to allow for different context types, but this adds complexity and doesn't match the requirement for a specific struct.
110
+ 3. Thread-local context: Store context in a thread-local variable, but this approach is less explicit and could cause issues with async code.
111
+ 4. Implement in stages with feature flags: Could help manage the transition but adds complexity and doesn't align with the requirement for a cohesive implementation.
112
+
113
+
plans/2025-04-16-model-selection-command.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model Selection Command Implementation
2
+
3
+ ## Objective
4
+ Add a `/model` command to the Forge CLI that uses the `inquire` library to display a list of available models and update the project's `forge.yaml` file with the selected model.
5
+
6
+ ## Implementation Plan
7
+ - [x] Add `inquire` as a dependency to the workspace and forge_main crate
8
+ - [x] Implement a `handle_model_selection()` method in the UI module to:
9
+ - Fetch available models using the API
10
+ - Display a selection interface using inquire
11
+ - Update the forge.yaml file with the selected model
12
+ - [x] Update the Command::Model match case to call the new method
13
+
14
+ ## Implementation Details
15
+ 1. Added `inquire` dependency to the workspace and forge_main Cargo.toml files
16
+ 2. Implemented a new `handle_model_selection()` method in the UI module that:
17
+ - Fetches the list of available models
18
+ - Uses inquire to display a selection list
19
+ - Updates the standard_model anchor in the forge.yaml file
20
+ 3. Updated the Command::Model case in the UI::run method to call the new method
21
+
22
+ ## Verification Criteria
23
+ - The `/model` command should display a list of available models using inquire
24
+ - After selecting a model, the forge.yaml file should be updated with the selected model
25
+ - The standard_model anchor in the forge.yaml file should be created if it doesn't exist
26
+ - Appropriate error messages should be displayed if any step fails
27
+
28
+ ## Potential Risks and Mitigations
29
+ - Risk: Models API might return an empty list
30
+ Mitigation: Added error handling for empty model lists
31
+ - Risk: forge.yaml file might not exist in the current directory
32
+ Mitigation: Added error handling for missing files
33
+ - Risk: forge.yaml file might be in an unexpected format
34
+ Mitigation: Added error handling for parsing errors
35
+
36
+ ## Future Improvements
37
+ - Add support for updating advanced_model in addition to standard_model
38
+ - Add support for creating forge.yaml if it doesn't exist
39
+ - Add ability to filter models by provider
plans/2025-04-26-large-file-read-range-support-v3.md ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Large File Range Reading Support (v3)
2
+
3
+ ## Objective
4
+ Implement support for reading extremely large text files by adding range parameters (start_byte and end_byte) to the file read tool, allowing users to read specific portions of large files without loading the entire file into memory. Binary files should not be supported and UTF-8 character boundaries must always be respected.
5
+
6
+ ## Implementation Plan
7
+
8
+ 1. **Update `FsReadService` interface to support range reading**
9
+ - Dependencies: None
10
+ - Files:
11
+ - `crates/forge_services/src/infra.rs`
12
+ - Notes: Add a new method to the trait that accepts start and end positions
13
+ - Status: Not Started
14
+
15
+ 2. **Implement the range reading functionality in `ForgeFileReadService`**
16
+ - Dependencies: Task 1
17
+ - Files:
18
+ - `crates/forge_infra/src/fs_read.rs`
19
+ - Notes: Implement the new trait method using Tokio's file API for efficient reading
20
+ - Status: Not Started
21
+
22
+ 3. **Add binary file detection and validation**
23
+ - Dependencies: None
24
+ - Files:
25
+ - `crates/forge_fs/src/lib.rs`
26
+ - Notes: Implement a utility function to detect if a file is binary and return an appropriate error
27
+ - Status: Not Started
28
+
29
+ 4. **Update ForgeFS to support range reading with binary file validation**
30
+ - Dependencies: Tasks 2, 3
31
+ - Files:
32
+ - `crates/forge_fs/src/lib.rs`
33
+ - Notes: Add a new method for range-based file reading that rejects binary files
34
+ - Status: Not Started
35
+
36
+ 5. **Implement UTF-8 boundary detection and correction**
37
+ - Dependencies: Tasks 2, 4
38
+ - Files:
39
+ - `crates/forge_fs/src/lib.rs`
40
+ - Notes: Ensure that range reads always align with UTF-8 character boundaries by adjusting the actual read range
41
+ - Status: Not Started
42
+
43
+ 6. **Update the `FSReadInput` struct to include optional range parameters**
44
+ - Dependencies: None
45
+ - Files:
46
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
47
+ - Notes: Add optional start_byte and end_byte fields to the input struct
48
+ - Status: Not Started
49
+
50
+ 7. **Modify FSRead tool implementation to support range reading and reject binary files**
51
+ - Dependencies: Tasks 1, 2, 3, 4, 5, 6
52
+ - Files:
53
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
54
+ - Notes: Update the `call` method to use the range-based reading with UTF-8 boundary adjustment and ensure binary files are rejected
55
+ - Status: Not Started
56
+
57
+ 8. **Update the FSRead tool description**
58
+ - Dependencies: Task 6
59
+ - Files:
60
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
61
+ - Notes: Update docstring to include range parameters in the tool description and explicitly mention that binary files are not supported and UTF-8 boundaries are always respected
62
+ - Status: Not Started
63
+
64
+ 9. **Implement file size detection logic**
65
+ - Dependencies: None
66
+ - Files:
67
+ - `crates/forge_fs/src/lib.rs`
68
+ - Notes: Add functionality to efficiently determine file size without reading the entire file
69
+ - Status: Not Started
70
+
71
+ 10. **Add content length information to range read responses**
72
+ - Dependencies: Task 9
73
+ - Files:
74
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
75
+ - Notes: Include total file size and adjusted range information in the response to help users understand the context of the range
76
+ - Status: Not Started
77
+
78
+ 11. **Add unit tests for range-based file reading and binary file rejection**
79
+ - Dependencies: Tasks 1-10
80
+ - Files:
81
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
82
+ - `crates/forge_infra/src/fs_read.rs`
83
+ - `crates/forge_fs/src/lib.rs`
84
+ - Notes: Test different range scenarios, edge cases, binary file detection, UTF-8 boundary handling, and error conditions
85
+ - Status: Not Started
86
+
87
+ ## Verification Criteria
88
+ - The file read tool correctly returns only the requested range of bytes from large text files
89
+ - The tool properly identifies and rejects binary files with a clear error message
90
+ - The tool always adjusts range boundaries to respect UTF-8 character boundaries
91
+ - The tool handles edge cases properly:
92
+ - When start_byte is beyond the file size
93
+ - When end_byte is beyond the file size
94
+ - When start_byte is greater than end_byte
95
+ - When start_byte and end_byte are equal
96
+ - When start_byte is negative or otherwise invalid
97
+ - When reading from an empty file
98
+ - When the range spans across malformed UTF-8 sequences
99
+ - When the file is locked by another process
100
+ - When reading from special files (e.g., device files, named pipes)
101
+ - When hitting OS-specific file size limits
102
+ - The tool returns the entire file when no range is specified (backward compatibility)
103
+ - The tool provides helpful error messages for invalid range parameters
104
+ - The response includes information about the actual range read after UTF-8 boundary adjustment
105
+ - File size information is correctly included in the response
106
+ - Performance remains acceptable for both small and extremely large text files
107
+ - All unit tests pass
108
+ - Clippy runs with no errors or warnings
109
+
110
+ ## Potential Risks and Mitigations
111
+
112
+ 1. **Performance issues with extremely large text files**
113
+ Mitigation:
114
+ - Ensure that the implementation doesn't read the entire file when a range is specified
115
+ - Use Tokio's file operations that support seeking and partial reads
116
+ - Verify with benchmarks on files of various sizes (MB to GB)
117
+ - Consider implementing a buffered reading strategy for large ranges
118
+
119
+ 2. **UTF-8 boundary adjustment overhead**
120
+ Mitigation:
121
+ - Optimize the UTF-8 boundary detection algorithm for performance
122
+ - Implement caching for boundary positions when repeated reads are requested
123
+ - Use efficient byte scanning techniques that minimize CPU and memory usage
124
+ - Provide clear metadata about the boundary adjustments that were made
125
+
126
+ 3. **Breaking changes to the existing API**
127
+ Mitigation:
128
+ - Make the range parameters optional with default values that maintain backward compatibility
129
+ - Document the behavior changes thoroughly
130
+ - Ensure all existing tests continue to pass with the new implementation
131
+
132
+ 4. **Inaccurate binary file detection**
133
+ Mitigation:
134
+ - Implement a robust heuristic for detecting binary files (e.g., check for null bytes, analyze byte distribution)
135
+ - Consider implementing a configurable threshold for binary detection
136
+ - Add comprehensive tests with various file types to verify correct detection
137
+ - Provide clear error messages when a file is detected as binary
138
+
139
+ 5. **File locking and concurrent access issues**
140
+ Mitigation:
141
+ - Implement proper error handling for locked files
142
+ - Use non-exclusive file handles when possible
143
+ - Add retry logic with exponential backoff for temporary access issues
144
+
145
+ 6. **Memory consumption with large ranges**
146
+ Mitigation:
147
+ - Implement chunk-based reading for very large ranges
148
+ - Set reasonable defaults and maximum values for range sizes
149
+ - Add memory usage monitoring and provide warnings for potentially problematic operations
150
+
151
+ 7. **Platform-specific issues**
152
+ Mitigation:
153
+ - Test on all supported platforms (Windows, macOS, Linux)
154
+ - Handle platform-specific file path conventions
155
+ - Respect platform-specific file size limitations
156
+
157
+ 8. **Invalid UTF-8 sequences in text files**
158
+ Mitigation:
159
+ - Implement robust error handling for malformed UTF-8
160
+ - Provide clear error messages when invalid UTF-8 is encountered
161
+ - Consider options for replacement or reporting of invalid sequences
162
+
163
+ ## Alternative Approaches
164
+
165
+ 1. **Streaming API**: Implement a streaming interface for file reading instead of range-based reading. This would allow progressive loading of large files but would require more significant changes to the tool interface.
166
+
167
+ 2. **File Pagination Tool**: Create a separate tool specifically for paginated file reading, leaving the original file read tool unchanged. This would maintain perfect backward compatibility but introduce redundancy.
168
+
169
+ 3. **Content-Based Partitioning**: Implement intelligent partitioning based on content (e.g., by line, by paragraph, by JSON object) rather than raw bytes. This would be more semantic but more complex to implement.
170
+
171
+ 4. **Fixed-size chunking**: Instead of arbitrary byte ranges, implement a chunking system where files are divided into fixed-size chunks that can be requested by index. This would simplify the API but reduce flexibility.
172
+
173
+ 5. **Smart text-only file reading**: Implement a detection mechanism that automatically determines the optimal portion of a text file to return based on the context of the request, using language-aware boundaries like paragraphs or code blocks.
174
+
175
+ ## Implementation Details
176
+
177
+ ### Range Parameter Design
178
+
179
+ For the FSReadInput struct, add the following optional parameters:
180
+
181
+ ```rust
182
+ /// Optional start position in bytes (0-based)
183
+ pub start_byte: Option<u64>,
184
+
185
+ /// Optional end position in bytes (exclusive)
186
+ pub end_byte: Option<u64>,
187
+ ```
188
+
189
+ ### Binary File Detection
190
+
191
+ To detect binary files, we'll implement a function that:
192
+
193
+ 1. Reads a small sample of the file (e.g., first 8KB)
194
+ 2. Checks for null bytes or other binary indicators
195
+ 3. Analyzes the distribution of byte values
196
+ 4. Returns a boolean indicating whether the file is likely binary
197
+
198
+ When a file is detected as binary, we'll return an error message like:
199
+ "Binary files are not supported. Please use another tool or method to process this file."
200
+
201
+ ### UTF-8 Boundary Detection and Adjustment
202
+
203
+ To ensure range reads respect UTF-8 character boundaries:
204
+
205
+ 1. For the start position:
206
+ - If the byte at start_byte is a UTF-8 continuation byte (10xxxxxx), scan backward to find the leading byte
207
+ - Adjust start_byte to the position of the leading byte
208
+
209
+ 2. For the end position:
210
+ - If the byte at end_byte-1 is a leading byte of a multi-byte sequence, check if the complete character is included
211
+ - If not, scan forward to include the complete character or backward to exclude the partial character
212
+
213
+ 3. Report the adjusted positions in the response metadata
214
+
215
+ ### Response Format
216
+
217
+ The response will include:
218
+
219
+ - The requested file content (as text)
220
+ - Metadata about the read operation:
221
+ - Total file size
222
+ - Original requested range
223
+ - Actual range read after UTF-8 boundary adjustment
224
+ - Information about boundary adjustments made
225
+
226
+ ### Efficient Implementation Approach
227
+
228
+ To minimize memory usage and improve performance:
229
+
230
+ 1. Use `tokio::fs::File::open()` to get a file handle
231
+ 2. Perform binary file detection check
232
+ 3. Use `file.metadata()` to get the file size without reading content
233
+ 4. Validate range parameters against file size
234
+ 5. Use `file.seek()` to position near start_byte
235
+ 6. Perform UTF-8 boundary detection and adjust start position if needed
236
+ 7. Use `file.take(adjusted_end_byte - adjusted_start_byte)` to create a limited reader
237
+ 8. Read from the limited reader into a buffer
238
+ 9. Verify that the buffer contains valid UTF-8 and make any final adjustments
239
+ 10. Return the buffer content with detailed metadata
plans/2025-04-27-large-file-read-range-support-v4.md ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Large File Range Reading Support (v4)
2
+
3
+ ## Objective
4
+ Implement support for reading extremely large text files by adding range parameters (start_byte and end_byte) to the file read tool, allowing users to read specific portions of large files without loading the entire file into memory. Binary files should not be supported and UTF-8 character boundaries must always be respected.
5
+
6
+ ## Implementation Plan
7
+
8
+ 1. **Update `FsReadService` interface to support range reading**
9
+ - Dependencies: None
10
+ - Files:
11
+ - `crates/forge_services/src/infra.rs`
12
+ - Notes: Add a new method to the trait that accepts start and end positions
13
+ - Status: Not Started
14
+
15
+ 2. **Implement the range reading functionality in `ForgeFileReadService`**
16
+ - Dependencies: Task 1
17
+ - Files:
18
+ - `crates/forge_infra/src/fs_read.rs`
19
+ - Notes: Implement the new trait method using Tokio's file API for efficient reading
20
+ - Status: Not Started
21
+
22
+ 3. **Add binary file detection using the infer crate**
23
+ - Dependencies: None
24
+ - Files:
25
+ - `crates/forge_fs/src/lib.rs`
26
+ - `crates/forge_fs/Cargo.toml`
27
+ - Notes: Add the infer crate as a dependency and implement a utility function to detect if a file is binary and return an appropriate error
28
+ - Status: Not Started
29
+
30
+ 4. **Update ForgeFS to support range reading with binary file validation**
31
+ - Dependencies: Tasks 2, 3
32
+ - Files:
33
+ - `crates/forge_fs/src/lib.rs`
34
+ - Notes: Add a new method for range-based file reading that rejects binary files
35
+ - Status: Not Started
36
+
37
+ 5. **Implement UTF-8 boundary detection and correction**
38
+ - Dependencies: Tasks 2, 4
39
+ - Files:
40
+ - `crates/forge_fs/src/lib.rs`
41
+ - Notes: Ensure that range reads always align with UTF-8 character boundaries by adjusting the actual read range
42
+ - Status: Not Started
43
+
44
+ 6. **Update the `FSReadInput` struct to include optional range parameters**
45
+ - Dependencies: None
46
+ - Files:
47
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
48
+ - Notes: Add optional start_byte and end_byte fields to the input struct
49
+ - Status: Not Started
50
+
51
+ 7. **Modify FSRead tool implementation to support range reading and reject binary files**
52
+ - Dependencies: Tasks 1, 2, 3, 4, 5, 6
53
+ - Files:
54
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
55
+ - Notes: Update the `call` method to use the range-based reading with UTF-8 boundary adjustment and ensure binary files are rejected
56
+ - Status: Not Started
57
+
58
+ 8. **Update the FSRead tool description**
59
+ - Dependencies: Task 6
60
+ - Files:
61
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
62
+ - Notes: Update docstring to include range parameters in the tool description and explicitly mention that binary files are not supported and UTF-8 boundaries are always respected
63
+
64
+ Sample Response:
65
+ ```
66
+ ---
67
+ path: /a/b/c.txt
68
+ range: 100-200
69
+ total: 1024
70
+ ---
71
+ Hello! This is the contents of file c.txt
72
+ ```
73
+ - Status: Not Started
74
+
75
+ 9. **Implement file size detection logic**
76
+ - Dependencies: None
77
+ - Files:
78
+ - `crates/forge_fs/src/lib.rs`
79
+ - Notes: Add functionality to efficiently determine file size without reading the entire file
80
+ - Status: Not Started
81
+
82
+ 10. **Add content length information to range read responses**
83
+ - Dependencies: Task 9
84
+ - Files:
85
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
86
+ - Notes: Include total file size and adjusted range information in the response to help users understand the context of the range
87
+ - Status: Not Started
88
+
89
+ 11. **Add unit tests for range-based file reading and binary file rejection**
90
+ - Dependencies: Tasks 1-10
91
+ - Files:
92
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
93
+ - `crates/forge_infra/src/fs_read.rs`
94
+ - `crates/forge_fs/src/lib.rs`
95
+ - Notes: Test different range scenarios, edge cases, binary file detection with infer, UTF-8 boundary handling, and error conditions
96
+ - Status: Not Started
97
+
98
+ ## Verification Criteria
99
+ - The file read tool correctly returns only the requested range of bytes from large text files
100
+ - The tool properly identifies and rejects binary files with a clear error message using the infer crate
101
+ - The tool always adjusts range boundaries to respect UTF-8 character boundaries
102
+ - The tool handles edge cases properly:
103
+ - When start_byte is beyond the file size
104
+ - When end_byte is beyond the file size
105
+ - When start_byte is greater than end_byte
106
+ - When start_byte and end_byte are equal
107
+ - When start_byte is negative or otherwise invalid
108
+ - When reading from an empty file
109
+ - When the range spans across malformed UTF-8 sequences
110
+ - When the file is locked by another process
111
+ - When reading from special files (e.g., device files, named pipes)
112
+ - When hitting OS-specific file size limits
113
+ - The tool returns the entire file when no range is specified (backward compatibility)
114
+ - The tool provides helpful error messages for invalid range parameters
115
+ - The response includes information about the actual range read after UTF-8 boundary adjustment
116
+ - File size information is correctly included in the response
117
+ - Performance remains acceptable for both small and extremely large text files
118
+ - All unit tests pass
119
+ - Clippy runs with no errors or warnings
120
+
121
+ ## Potential Risks and Mitigations
122
+
123
+ 1. **Performance issues with extremely large text files**
124
+ Mitigation:
125
+ - Ensure that the implementation doesn't read the entire file when a range is specified
126
+ - Use Tokio's file operations that support seeking and partial reads
127
+ - Verify with benchmarks on files of various sizes (MB to GB)
128
+ - Consider implementing a buffered reading strategy for large ranges
129
+
130
+ 2. **UTF-8 boundary adjustment overhead**
131
+ Mitigation:
132
+ - Optimize the UTF-8 boundary detection algorithm for performance
133
+ - Implement caching for boundary positions when repeated reads are requested
134
+ - Use efficient byte scanning techniques that minimize CPU and memory usage
135
+ - Provide clear metadata about the boundary adjustments that were made
136
+
137
+ 3. **Breaking changes to the existing API**
138
+ Mitigation:
139
+ - Make the range parameters optional with default values that maintain backward compatibility
140
+ - Document the behavior changes thoroughly
141
+ - Ensure all existing tests continue to pass with the new implementation
142
+
143
+ 4. **Inaccurate binary file detection**
144
+ Mitigation:
145
+ - Use the infer crate which provides robust file type detection
146
+ - Still implement fallback checks for edge cases the infer crate might miss
147
+ - Add comprehensive tests with various file types to verify correct detection
148
+ - Provide clear error messages when a file is detected as binary
149
+
150
+ 5. **File locking and concurrent access issues**
151
+ Mitigation:
152
+ - Implement proper error handling for locked files
153
+ - Use non-exclusive file handles when possible
154
+ - Add retry logic with exponential backoff for temporary access issues
155
+
156
+ 6. **Memory consumption with large ranges**
157
+ Mitigation:
158
+ - Implement chunk-based reading for very large ranges
159
+ - Set reasonable defaults and maximum values for range sizes
160
+ - Add memory usage monitoring and provide warnings for potentially problematic operations
161
+
162
+ 7. **Platform-specific issues**
163
+ Mitigation:
164
+ - Test on all supported platforms (Windows, macOS, Linux)
165
+ - Handle platform-specific file path conventions
166
+ - Respect platform-specific file size limitations
167
+
168
+ 8. **Invalid UTF-8 sequences in text files**
169
+ Mitigation:
170
+ - Implement robust error handling for malformed UTF-8
171
+ - Provide clear error messages when invalid UTF-8 is encountered
172
+ - Consider options for replacement or reporting of invalid sequences
173
+
174
+ 9. **Dependency management issues with infer crate**
175
+ Mitigation:
176
+ - Pin to a specific version of the infer crate to avoid breaking changes
177
+ - Monitor for security updates and issues with the infer crate
178
+ - Have a fallback mechanism in case the infer crate fails
179
+
180
+ 10. **Confusion for users with the new metadata in responses**
181
+ Mitigation:
182
+ - Provide clear documentation on how to interpret the metadata
183
+ - Include examples of how to use the new range parameters
184
+ - Ensure backward compatibility so users not using ranges aren't affected
185
+
186
+ ## Alternative Approaches
187
+
188
+ 1. **Streaming API**: Implement a streaming interface for file reading instead of range-based reading. This would allow progressive loading of large files but would require more significant changes to the tool interface.
189
+
190
+ 2. **File Pagination Tool**: Create a separate tool specifically for paginated file reading, leaving the original file read tool unchanged. This would maintain perfect backward compatibility but introduce redundancy.
191
+
192
+ 3. **Content-Based Partitioning**: Implement intelligent partitioning based on content (e.g., by line, by paragraph, by JSON object) rather than raw bytes. This would be more semantic but more complex to implement.
193
+
194
+ 4. **Fixed-size chunking**: Instead of arbitrary byte ranges, implement a chunking system where files are divided into fixed-size chunks that can be requested by index. This would simplify the API but reduce flexibility.
195
+
196
+ 5. **Smart text-only file reading**: Implement a detection mechanism that automatically determines the optimal portion of a text file to return based on the context of the request, using language-aware boundaries like paragraphs or code blocks.
197
+
198
+ 6. **Custom binary detection instead of infer**: Implement our own binary detection logic instead of relying on an external crate. This would reduce dependencies but require more maintenance and could be less accurate.
199
+
200
+ ## Implementation Details
201
+
202
+ ### Range Parameter Design
203
+
204
+ For the FSReadInput struct, add the following optional parameters:
205
+
206
+ ```rust
207
+ /// Optional start position in bytes (0-based)
208
+ pub start_byte: Option<u64>,
209
+
210
+ /// Optional end position in bytes (exclusive)
211
+ pub end_byte: Option<u64>,
212
+ ```
213
+
214
+ ### Binary File Detection
215
+
216
+ To detect binary files, we'll use the `infer` crate:
217
+
218
+ 1. Add the infer crate to dependencies in Cargo.toml:
219
+ ```toml
220
+ [dependencies]
221
+ infer = "0.15.0" # Use the latest version
222
+ ```
223
+
224
+ 2. Implement a utility function that:
225
+ - Reads a small sample of the file (e.g., first 8KB)
226
+ - Uses `infer::is_image()`, `infer::is_video()`, `infer::is_audio()`, `infer::is_archive()`, etc. to detect binary formats
227
+ - For files not detected by infer, falls back to checking for null bytes or high concentration of non-printable characters
228
+ - Returns a boolean indicating whether the file is likely binary
229
+
230
+ 3. When a file is detected as binary, return an error message like:
231
+ "Binary files are not supported. File detected as [file type]. Please use another tool or method to process this file."
232
+
233
+ ### UTF-8 Boundary Detection and Adjustment
234
+
235
+ To ensure range reads respect UTF-8 character boundaries:
236
+
237
+ 1. For the start position:
238
+ - If the byte at start_byte is a UTF-8 continuation byte (10xxxxxx), scan backward to find the leading byte
239
+ - Adjust start_byte to the position of the leading byte
240
+
241
+ 2. For the end position:
242
+ - If the byte at end_byte-1 is a leading byte of a multi-byte sequence, check if the complete character is included
243
+ - If not, scan forward to include the complete character or backward to exclude the partial character
244
+
245
+ 3. Report the adjusted positions in the response metadata
246
+
247
+ ### Response Format
248
+
249
+ The response will include:
250
+
251
+ - The requested file content (as text)
252
+ - Metadata about the read operation:
253
+ - Total file size
254
+ - Original requested range
255
+ - Actual range read after UTF-8 boundary adjustment
256
+ - Information about boundary adjustments made
257
+
258
+ ### Sample Response
259
+
260
+ The fs_read tool will return JSON with the following structure:
261
+
262
+ ```json
263
+ {
264
+ "content": "This is the file content within the requested range...",
265
+ "metadata": {
266
+ "file_size": 1024000,
267
+ "requested_range": {
268
+ "start_byte": 500,
269
+ "end_byte": 1500
270
+ },
271
+ "actual_range": {
272
+ "start_byte": 498,
273
+ "end_byte": 1503
274
+ },
275
+ "boundary_adjustments": {
276
+ "start_adjusted": true,
277
+ "start_adjustment_reason": "UTF-8 character boundary alignment",
278
+ "end_adjusted": true,
279
+ "end_adjustment_reason": "UTF-8 character boundary alignment"
280
+ },
281
+ "is_partial": true,
282
+ "percent_of_file": 0.1
283
+ }
284
+ }
285
+ ```
286
+
287
+ For error cases:
288
+
289
+ ```json
290
+ {
291
+ "error": "Binary files are not supported. File detected as image/png. Please use another tool or method to process this file."
292
+ }
293
+ ```
294
+
295
+ Or for invalid ranges:
296
+
297
+ ```json
298
+ {
299
+ "error": "Invalid range specified: start_byte (5000) is greater than end_byte (4000)."
300
+ }
301
+ ```
302
+
303
+ ### Efficient Implementation Approach
304
+
305
+ To minimize memory usage and improve performance:
306
+
307
+ 1. Use `tokio::fs::File::open()` to get a file handle
308
+ 2. Read a small sample and perform binary file detection using the infer crate
309
+ 3. Use `file.metadata()` to get the file size without reading content
310
+ 4. Validate range parameters against file size
311
+ 5. Use `file.seek()` to position near start_byte
312
+ 6. Perform UTF-8 boundary detection and adjust start position if needed
313
+ 7. Use `file.take(adjusted_end_byte - adjusted_start_byte)` to create a limited reader
314
+ 8. Read from the limited reader into a buffer
315
+ 9. Verify that the buffer contains valid UTF-8 and make any final adjustments
316
+ 10. Return the buffer content with detailed metadata
plans/2025-06-07-tool-service-migration-v1.md ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Tool-to-Service Migration Plan
2
+
3
+ ## Objective
4
+
5
+ Migrate all tools from direct infrastructure dependencies to service-based architecture where each tool has a corresponding service and tools become thin wrappers that make single service calls. This will improve testability, maintainability, and follow clean architecture principles.
6
+
7
+ ## Implementation Plan
8
+
9
+ ### 1. **Analyze Current Tool Patterns and Dependencies**
10
+ - Dependencies: None
11
+ - Notes: Examine all existing tools to identify common patterns, infrastructure usage, and business logic that should be moved to services
12
+ - Files:
13
+ - `crates/forge_services/src/tools/fs/file_info.rs`
14
+ - `crates/forge_services/src/tools/fs/fs_find.rs`
15
+ - `crates/forge_services/src/tools/fs/fs_list.rs`
16
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
17
+ - `crates/forge_services/src/tools/fs/fs_remove.rs`
18
+ - `crates/forge_services/src/tools/fs/fs_undo.rs`
19
+ - `crates/forge_services/src/tools/fs/fs_write.rs`
20
+ - `crates/forge_services/src/tools/fetch.rs`
21
+ - `crates/forge_services/src/tools/followup.rs`
22
+ - `crates/forge_services/src/tools/patch.rs`
23
+ - `crates/forge_services/src/tools/shell.rs`
24
+ - `crates/forge_services/src/tools/registry.rs`
25
+ - `crates/forge_services/src/tools/mod.rs`
26
+ - Status: Not Started
27
+
28
+ ### 2. **Design Service Interface Standards**
29
+ - Dependencies: Task 1
30
+ - Notes: Create standardized patterns for tool services including error handling, input validation, and output formatting. Define naming conventions and service trait structure. **Important**: Services must NOT use ToolCallContext - this is UI-specific and stays in tools. Services should be pure business logic with simple input/output. **Critical**: Services must use Infrastructure traits (FsReadService, FsWriteService, etc.) instead of direct tokio::fs calls.
31
+ - Files:
32
+ - New service trait definitions in `crates/forge_services/src/`
33
+ - Service interface documentation
34
+ - Status: Not Started
35
+
36
+ ### 3. **Create Generic Service Trait Template**
37
+ - Dependencies: Task 2
38
+ - Notes: Define a generic service trait pattern that can be applied to any tool, including async methods, error handling with anyhow::Result, and integration with existing Infrastructure
39
+ - Files:
40
+ - `crates/forge_services/src/mod.rs` (new)
41
+ - Service trait template documentation
42
+ - Status: Not Started
43
+
44
+ ### 4. **Implement Service for Template Tool (FSRead as Example)**
45
+ - Dependencies: Task 3
46
+ - Notes: Create complete service implementation for FSRead tool as a **template example** that demonstrates the migration pattern. This is not the final implementation but a reference pattern to be applied to all tools. **Critical**: Service must NOT use ToolCallContext - extract only pure business logic. All UI concerns (titles, progress) remain in tool. **Important**: Service must use Infrastructure traits (FsReadService, etc.) instead of direct tokio::fs calls.
47
+ - Files:
48
+ - `crates/forge_services/src/fs_read_service.rs` (new - template example)
49
+ - Updated `crates/forge_services/src/mod.rs`
50
+ - Status: Not Started
51
+
52
+ ### 5. **Update Services Trait and ForgeServices (Template Pattern)**
53
+ - Dependencies: Task 4
54
+ - Notes: Add new FSReadService to main Services trait and implement it in ForgeServices struct as a **template pattern**. This demonstrates how any tool service should be integrated into the main service architecture.
55
+ - Files:
56
+ - `crates/forge_app/src/services.rs`
57
+ - `crates/forge_services/src/forge_services.rs`
58
+ - Status: Not Started
59
+
60
+ ### 6. **Refactor FSRead Tool Implementation (Template Pattern)**
61
+ - Dependencies: Task 5
62
+ - Notes: Convert FSRead tool to use FSReadService instead of direct infrastructure calls as a **template example**. This demonstrates how to make any tool a thin wrapper with single service call. The pattern shown here applies to all other tools. **Key**: Tool retains ToolCallContext for UI (titles, progress) but delegates all business logic to service.
63
+ - Files:
64
+ - `crates/forge_services/src/tools/fs/fs_read.rs`
65
+ - Status: Not Started
66
+
67
+ ### 7. **Update Tool Registry for Service Injection**
68
+ - Dependencies: Task 6
69
+ - Notes: Modify tool registration to inject service dependencies through the Services trait instead of raw Infrastructure
70
+ - Files:
71
+ - `crates/forge_services/src/tools/registry.rs`
72
+ - Status: Not Started
73
+
74
+ ### 8. **Create Migration Template Documentation**
75
+ - Dependencies: Task 7
76
+ - Notes: Document the complete **generic pattern** for migrating any tool to service-based architecture, using the FSRead example as a reference. Include code templates, step-by-step instructions, and patterns that can be applied to any tool (shell, fetch, patch, etc.).
77
+ - Files:
78
+ - `docs/tool-service-migration-template.md` (new)
79
+ - Migration checklist and examples showing how FSRead pattern applies to any tool
80
+ - Status: Not Started
81
+
82
+ ### 9. **Apply Migration Pattern to All Remaining Tools**
83
+ - Dependencies: Task 8
84
+ - Notes: Apply the **generic migration pattern** established with FSRead template to all remaining tools. Each tool follows the same pattern: create service, integrate into Services trait, refactor tool to use service.
85
+ - Files:
86
+ - `crates/forge_services/src/file_info_service.rs` (new - following FSRead pattern)
87
+ - `crates/forge_services/src/fs_find_service.rs` (new - following FSRead pattern)
88
+ - `crates/forge_services/src/fs_list_service.rs` (new - following FSRead pattern)
89
+ - `crates/forge_services/src/fs_remove_service.rs` (new - following FSRead pattern)
90
+ - `crates/forge_services/src/fs_undo_service.rs` (new - following FSRead pattern)
91
+ - `crates/forge_services/src/fs_write_service.rs` (new - following FSRead pattern)
92
+ - `crates/forge_services/src/fetch_service.rs` (new - following FSRead pattern)
93
+ - `crates/forge_services/src/followup_service.rs` (new - following FSRead pattern)
94
+ - `crates/forge_services/src/patch_service.rs` (new - following FSRead pattern)
95
+ - `crates/forge_services/src/shell_service.rs` (new - following FSRead pattern)
96
+ - Updated tool implementations for all tools (applying FSRead refactoring pattern)
97
+ - Updated Services trait and ForgeServices (following FSRead integration pattern)
98
+ - Status: Not Started
99
+
100
+ ### 10. **Validation and Testing**
101
+ - Dependencies: Task 9
102
+ - Notes: Ensure all migrated tools maintain functionality, test coverage is preserved, and new services have comprehensive tests following project testing standards. **Critical**: Migrate existing tests from tools to services - business logic tests move to service layer, UI/integration tests remain with tools.
103
+ - Files:
104
+ - Test files for all new services (migrated from tool tests)
105
+ - Updated tool tests (focused on UI and service integration)
106
+ - Integration tests
107
+ - Status: Not Started
108
+
109
+ ## Tool Migration Checklist
110
+
111
+ Track the progress of migrating each tool to service-based architecture:
112
+
113
+ ### File System Tools
114
+ - [ ] **file_info** - Get file metadata and information
115
+ - [ ] **fs_find** - Search for files and directories
116
+ - [ ] **fs_list** - List directory contents
117
+ - [ ] **fs_read** - Read file contents (**TEMPLATE EXAMPLE** - pattern to be applied to all other tools)
118
+ - [ ] **fs_remove** - Remove files and directories
119
+ - [ ] **fs_undo** - Undo file system operations
120
+ - [ ] **fs_write** - Write content to files
121
+
122
+ ### Network and External Tools
123
+ - [ ] **fetch** - Fetch content from URLs
124
+
125
+ ### Interactive and Workflow Tools
126
+ - [ ] **followup** - Handle follow-up actions and suggestions
127
+
128
+ ### Code and Content Processing Tools
129
+ - [ ] **patch** - Apply patches and modifications to files
130
+
131
+ ### System Tools
132
+ - [ ] **shell** - Execute shell commands
133
+
134
+ ### Registry and Meta Tools
135
+ - [ ] **registry** - Tool registration and management
136
+ - [ ] **mod** - Module and component operations
137
+
138
+ Each tool migration should follow the **FSRead template pattern** and include:
139
+ 1. Service interface design and implementation (following FSRead service pattern) - **Services in `crates/forge_services/src/`**
140
+ 2. Tool refactoring to use service (following FSRead tool refactoring pattern) - **Tool keeps ToolCallContext for UI, service gets pure business logic**
141
+ 3. Service integration into Services trait (following FSRead integration pattern)
142
+ 4. Test migration from tool to service (following FSRead testing pattern) - **Business logic tests move to service, UI tests stay with tool**
143
+ 5. Documentation updates
144
+
145
+ **Note**: FSRead serves as the template example demonstrating the generic migration pattern. All other tools should follow the exact same pattern established by the FSRead implementation.
146
+
147
+ **Critical Requirements**:
148
+ - Services are defined in `crates/forge_services/src/` directory
149
+ - Services must NOT use ToolCallContext - this is UI-specific and remains in tools
150
+ - Services contain only pure business logic with simple input/output
151
+ - Tools retain ToolCallContext for UI concerns (titles, progress, user interaction)
152
+ - Existing tests migrate from tools to services where they test business logic
153
+ - **IMPORTANT**: Services must use Infrastructure traits (FsReadService, FsWriteService, etc.) instead of direct tokio::fs calls. This ensures proper abstraction, testability, and consistency with the project's architecture.
154
+
155
+ ## Verification Criteria
156
+
157
+ - All tools are thin wrappers that make single calls to their corresponding services
158
+ - Each tool has a dedicated service implementing business logic in `crates/forge_services/src/`
159
+ - Services do NOT use ToolCallContext - they have pure input/output interfaces
160
+ - Services use Infrastructure traits (FsReadService, FsWriteService, etc.) instead of direct tokio::fs calls
161
+ - Tools retain ToolCallContext for UI concerns (titles, progress, user interaction)
162
+ - Services are properly integrated into the Services trait and ForgeServices implementation
163
+ - Tool registry uses Services trait instead of raw Infrastructure for tool instantiation
164
+ - All existing functionality is preserved with no breaking changes
165
+ - Test coverage is maintained or improved - business logic tests migrated to services, UI tests remain with tools
166
+ - Migration documentation is complete and can be followed for any remaining tools
167
+ - Code follows project standards including error handling with anyhow::Result and testing patterns
168
+
169
+ ## Potential Risks and Mitigations
170
+
171
+ ### 1. **Breaking Changes to Tool Interface**
172
+ **Risk**: Modifying tool constructors and registration could break existing code that depends on current tool instantiation patterns.
173
+ **Mitigation**: Maintain backward compatibility by keeping existing tool constructors while adding service-based alternatives. Use feature flags or gradual migration approach.
174
+
175
+ ### 2. **Service Dependency Complexity**
176
+ **Risk**: Adding service layer could introduce circular dependencies or complex dependency injection chains.
177
+ **Mitigation**: Design services to depend only on Infrastructure traits, not on other services. Keep service interfaces focused and minimal.
178
+
179
+ ### 3. **Performance Overhead**
180
+ **Risk**: Additional service layer could introduce performance overhead through extra abstraction.
181
+ **Mitigation**: Ensure services are lightweight wrappers around infrastructure calls. Profile critical paths to verify no significant performance impact.
182
+
183
+ ### 4. **Test Complexity**
184
+ **Risk**: Mocking services for tool tests could become more complex than current infrastructure mocking.
185
+ **Mitigation**: Create standardized service mocks and test utilities. Ensure service interfaces are designed for easy testing.
186
+
187
+ ### 5. **Inconsistent Migration**
188
+ **Risk**: Partial migration could result in inconsistent architecture with some tools using services and others using infrastructure directly.
189
+ **Mitigation**: Complete migration of all core tools before considering the migration complete. Document clear guidelines for future tool development.
190
+
191
+ ## Alternative Approaches
192
+
193
+ ### 1. **Gradual Infrastructure Extension**: Instead of creating separate services, extend the Infrastructure trait with higher-level methods that encapsulate business logic, allowing tools to call more semantic operations while maintaining current architecture.
194
+
195
+ ### 2. **Tool-Specific Service Injection**: Rather than adding all services to the main Services trait, inject specific services directly into tool constructors, reducing the size of the Services trait but requiring more complex tool instantiation.
196
+
197
+ ### 3. **Service Composition Pattern**: Create a single ToolService that composes multiple domain-specific services, providing a unified interface for all tool operations while maintaining service separation internally.
plans/2025-09-07-shell-env-variable-support-v1.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Shell Tool Environment Variable Support
2
+
3
+ ## Objective
4
+
5
+ Add environment variable support to the shell tool, allowing agents to specify environment variable names that should be set before executing commands. The infrastructure will read these environment variables from the system and apply them during command execution.
6
+
7
+ ## Implementation Plan
8
+
9
+ - [x] **Task 1. Update Shell Tool Domain Model**
10
+ Extend the `Shell` struct in `crates/forge_domain/src/tools.rs` to include an optional `env` field that accepts a vector of environment variable names. This field will specify which environment variables should be passed to the command execution environment.
11
+
12
+ - [x] **Task 2. Update CommandInfra Interface**
13
+ Modify the `CommandInfra` trait in `crates/forge_services/src/infra.rs` to accept environment variable names in the `execute_command` and `execute_command_raw` methods. This involves adding a new parameter `env_vars: Option<Vec<String>>` to both methods.
14
+
15
+ - [x] **Task 3. Update ShellService Interface**
16
+ Extend the `ShellService` trait in `crates/forge_app/src/services.rs` to accept environment variable names in the `execute` method. Add `env_vars: Option<Vec<String>>` parameter to maintain consistency with the updated CommandInfra interface.
17
+
18
+ - [x] **Task 4. Update Shell Service Implementation**
19
+ Modify the `ForgeShell` implementation in `crates/forge_services/src/tool_services/shell.rs` to pass environment variable names to the infrastructure layer. Update the `execute` method to forward the env_vars parameter to the command infrastructure.
20
+
21
+ - [x] **Task 5. Update Command Executor Implementation**
22
+ Enhance the `ForgeCommandExecutorService` in `crates/forge_infra/src/executor.rs` to read specified environment variables from the system and apply them to the command execution context. Modify the `prepare_command` method to set the requested environment variables on the Command instance.
23
+
24
+ - [x] **Task 6. Update Tool Operation Processing**
25
+ Modify the shell tool operation handling in `crates/forge_app/src/operation.rs` and `crates/forge_app/src/tool_executor.rs` to extract environment variable names from the Shell tool input and pass them through the service chain.
26
+
27
+ - [x] **Task 7. Update Infrastructure Implementations**
28
+ Update all CommandInfra implementations to support the new environment variable parameter, including the main implementation in `crates/forge_infra/src/forge_infra.rs` and any test implementations in `crates/forge_services/src/attachment.rs`.
29
+
30
+ - [x] **Task 8. Add Comprehensive Test Coverage**
31
+ Create tests to verify environment variable functionality works correctly, including tests for missing environment variables, empty env lists, and successful environment variable application during command execution.
32
+
33
+ ## Verification Criteria
34
+
35
+ - Environment variable names can be specified in shell tool calls through the new `env` field
36
+ - Specified environment variables are properly read from the system and applied to command execution
37
+ - Commands execute successfully with the requested environment variables available
38
+ - Missing environment variables are handled gracefully without causing command failures
39
+ - All existing shell tool functionality continues to work unchanged
40
+ - Test suite passes with comprehensive coverage of the new environment variable feature
41
+
42
+ ## Potential Risks and Mitigations
43
+
44
+ 1. **Breaking API Changes**
45
+ Mitigation: Use optional parameters and default values to maintain backward compatibility with existing shell tool usage
46
+
47
+ 2. **Environment Variable Security**
48
+ Mitigation: Only read specified environment variables by name rather than exposing the entire environment to prevent unintended information leakage
49
+
50
+ 3. **Missing Environment Variables**
51
+ Mitigation: Handle missing environment variables gracefully by either skipping them or providing clear error messages, depending on the desired behavior
52
+
53
+ 4. **Performance Impact**
54
+ Mitigation: Only read environment variables when explicitly requested, avoiding unnecessary system calls when the env field is not provided
55
+
56
+ ## Alternative Approaches
57
+
58
+ 1. **Direct Environment Variable Values**: Allow agents to specify environment variable values directly instead of just names
59
+ - Trade-offs: More flexible but potentially less secure, as it would allow agents to set arbitrary values rather than using system-defined values
60
+
61
+ 2. **Environment Variable Validation**: Implement allowlists or validation for which environment variables can be accessed
62
+ - Trade-offs: Enhanced security but increased complexity and potential limitations for legitimate use cases
63
+
64
+ 3. **Separate Environment Tool**: Create a dedicated tool for environment variable management instead of extending the shell tool
65
+ - Trade-offs: Cleaner separation of concerns but additional complexity for users who need both shell execution and environment variable access
plans/2025-09-08-agent-loader-cwd-extension-v1.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Loader CWD Extension Implementation Plan
2
+
3
+ ## Objective
4
+
5
+ Extend the `AgentLoaderService` to load agents from both the existing `{HOME}/forge/agents/` directory and an additional `CWD/.forge/agents/` directory, combining agents from both sources while maintaining the current architecture patterns and safety guarantees.
6
+
7
+ ## Implementation Plan
8
+
9
+ ### Phase 1: Domain Layer Extensions
10
+ - [x] Task 1. **Add CWD agent path method to Environment**
11
+ - Add `agent_cwd_path()` method to the `Environment` struct in `crates/forge_domain/src/env.rs`
12
+ - Method should return `PathBuf::from(".forge/agents")` to point to the current working directory
13
+ - Follow the same pattern as existing `agent_path()` method but use current directory as base
14
+
15
+ - [x] Task 2. **Update EnvironmentInfra trait usage documentation**
16
+ - Document that environments now support both global and project-local agent directories
17
+ - Update any relevant comments or documentation strings in the trait definitions
18
+
19
+ ### Phase 2: Service Layer Implementation
20
+ - [x] Task 3. **Extend AgentLoaderService init method**
21
+ - Modify `init()` method in `crates/forge_services/src/agent_loader.rs` to load from three sources instead of two
22
+ - Keep existing built-in agents loading (`init_default()`)
23
+ - Keep existing custom agents loading from global directory (`init_custom()`)
24
+ - Add new CWD agents loading (`init_cwd()`) method call
25
+
26
+ - [x] Task 4. **Implement init_cwd method**
27
+ - Add `init_cwd()` private method to `AgentLoaderService`
28
+ - Use `self.infra.get_environment().agent_cwd_path()` to get the CWD agent directory
29
+ - Check if directory exists using `self.infra.exists()` like existing custom agent logic
30
+ - Use `DirectoryReaderInfra::read_directory_files()` with `"*.md"` pattern
31
+ - Parse agents using existing `parse_agent_iter()` function
32
+ - Handle missing directory gracefully by returning empty vector
33
+
34
+ - [x] Task 5. **Combine agent sources in init method**
35
+ - Modify existing `init()` method to combine all three sources: built-in, custom (global), and CWD
36
+ - Use `Vec::extend()` pattern like existing implementation
37
+ - Maintain order: built-in agents first, then global custom agents, then CWD agents
38
+ - This order ensures built-in agents have precedence over custom ones
39
+
40
+ ### Phase 3: Error Handling and Safety
41
+ - [x] Task 6. **Add comprehensive error context**
42
+ - Add context information to distinguish between global and CWD agent loading failures
43
+ - Update error messages to specify which directory failed to load
44
+ - Ensure partial failures don't prevent other agent sources from loading
45
+
46
+ - [x] Task 7. **Handle agent ID conflicts**
47
+ - Implement conflict resolution strategy for duplicate agent IDs across directories
48
+ - Later-loaded agents (CWD) should take precedence over earlier-loaded agents (global)
49
+ - Document the precedence order: CWD Custom > Global Custom > Built-in
50
+
51
+ ### Phase 4: Testing Implementation
52
+ - [x] Task 8. **Add unit tests for agent_cwd_path**
53
+ - Test that `Environment::agent_cwd_path()` returns correct path structure
54
+ - Verify path resolution works independently from `agent_path()`
55
+ - Add test cases for the new method in existing environment tests
56
+
57
+ - [x] Task 9. **Add unit tests for CWD agent loading**
58
+ - Create test fixtures in `crates/forge_services/src/fixtures/` for CWD agent scenarios
59
+ - Test successful CWD agent loading with valid markdown files
60
+ - Test graceful handling of missing CWD `.forge/agents/` directory
61
+ - Test agent conflict resolution between global and CWD directories
62
+
63
+ - [x] Task 10. **Add integration tests**
64
+ - Test complete agent loading flow with all three sources active
65
+ - Verify agent precedence order works correctly
66
+ - Test error isolation - CWD loading failure doesn't break global loading
67
+
68
+ ### Phase 5: Documentation and Validation
69
+ - [x] Task 11. **Update service documentation**
70
+ - Update docstrings in `AgentLoaderService` to reflect multiple directory support
71
+ - Document the agent precedence order and directory resolution strategy
72
+ - Add usage examples showing how CWD agents complement global agents
73
+
74
+ - [x] Task 12. **Run comprehensive testing**
75
+ - Execute `cargo insta test --accept --unreferenced=delete` for service tests
76
+ - Run full test suite to ensure no regressions in existing functionality
77
+ - Validate that built-in agents still load correctly
78
+
79
+ ## Verification Criteria
80
+
81
+ - All existing built-in agents continue to load successfully from embedded sources
82
+ - Global custom agents continue to load from `{HOME}/forge/agents/` directory
83
+ - CWD agents load from `.forge/agents/` directory when it exists
84
+ - Missing `.forge/agents/` directory doesn't cause errors or prevent other agent loading
85
+ - Agent precedence follows documented order: Built-in > Global > CWD
86
+ - Agents with duplicate IDs are resolved correctly with later sources taking precedence
87
+ - All existing tests pass without modification
88
+ - New tests provide comprehensive coverage of the extended functionality
89
+
90
+ ## Potential Risks and Mitigations
91
+
92
+ 1. **Agent ID conflicts between directories**
93
+ Mitigation: Implement clear precedence rules with CWD agents overriding global agents
94
+
95
+ 2. **Performance impact from additional directory scanning**
96
+ Mitigation: Maintain existing caching strategy and parallel loading patterns
97
+
98
+ 3. **Breaking changes to existing agent loading behavior**
99
+ Mitigation: Extend functionality additively without modifying existing load paths
100
+
101
+ 4. **Directory permission or access issues for CWD**
102
+ Mitigation: Use same graceful error handling pattern as existing custom agent loading
103
+
104
+ 5. **Inconsistent agent quality between global and CWD sources**
105
+ Mitigation: Apply same parsing and validation logic to all agent sources
106
+
107
+ ## Alternative Approaches
108
+
109
+ 1. **Configuration-driven approach**: Add agent directory paths to forge.yaml configuration file, allowing users to specify custom agent directories explicitly
110
+
111
+ 2. **Environment variable approach**: Support `FORGE_AGENT_PATHS` environment variable for colon-separated list of agent directories
112
+
113
+ 3. **Recursive directory scanning**: Modify existing agent loading to recursively scan subdirectories within agent paths
114
+
115
+ 4. **Agent registry pattern**: Implement a more sophisticated agent discovery system with registration and dependency management
plans/2025-09-12-forge-dump-autoopen-env-var-v2.md ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FORGE_DUMP_AUTO_OPEN Environment Variable Implementation Plan v2
2
+
3
+ ## Objective
4
+
5
+ Implement environment variable control for the auto-open functionality of HTML dumps in the Forge CLI by integrating the `FORGE_DUMP_AUTO_OPEN` environment variable directly into the Environment service. The feature will be accessible via `self.api.environment().auto_open_dump` and should default to `false` (disabled) and only auto-open when explicitly set to `true` or `1`.
6
+
7
+ ## Background Analysis
8
+
9
+ Based on the GitHub issue (#1201) analysis and updated requirements:
10
+
11
+ - **Problem**: Users in WSL2 environments experience slow browser performance when `/dump html` automatically opens files in the Linux browser
12
+ - **Current Behavior**: HTML dumps always auto-open via `open::that()` call in `ui.rs:739`
13
+ - **User Need**: Option to disable auto-opening for better workflow in cross-platform environments
14
+ - **Updated Solution**: Store environment variable information directly in the Environment service and document in README.md
15
+
16
+ ## Architecture Assessment
17
+
18
+ **Current Implementation Location**: `crates/forge_main/src/ui.rs:721-740`
19
+
20
+ **Environment Service Architecture**: The codebase uses a layered service architecture:
21
+ - `Environment` struct in `crates/forge_domain/src/env.rs:18` contains configuration fields
22
+ - `EnvironmentInfra` trait in `crates/forge_infra/src/env.rs:107-108` handles environment variable access
23
+ - Environment variables are parsed during initialization in `crates/forge_infra/src/env.rs:39-71`
24
+ - API access pattern: `self.api.environment()` provides access to Environment struct
25
+
26
+ **Access Pattern**: The desired `self.api.environment().auto_open_dump` pattern requires adding the field to the Environment struct and parsing logic to the infrastructure layer.
27
+
28
+ ## Implementation Plan
29
+
30
+ - [x] **Task 1: Add auto_open_dump field to Environment struct**
31
+ - Location: `crates/forge_domain/src/env.rs:54` (add field after existing configuration fields)
32
+ - Add `pub auto_open_dump: bool` field to Environment struct
33
+ - Ensure field is included in builder pattern via existing `derive_setters::Setters`
34
+
35
+ - [x] **Task 2: Add environment variable parsing in infrastructure layer**
36
+ - Location: `crates/forge_infra/src/env.rs:39-71` (in the `get()` method)
37
+ - Parse `FORGE_DUMP_AUTO_OPEN` environment variable using existing `parse_env` helper
38
+ - Support boolean values: "true", "1", "yes" (case-insensitive) for enabled; everything else disabled
39
+ - Default to `false` when variable is not set (maintains backward compatibility for new users)
40
+ - Follow existing patterns similar to other boolean environment variables in the codebase
41
+
42
+ - [x] **Task 3: Update UI dump method to use Environment service**
43
+ - Location: `crates/forge_main/src/ui.rs:721-740`
44
+ - Replace direct `open::that()` call with conditional logic
45
+ - Use `self.api.environment().auto_open_dump` to determine if auto-open should occur
46
+ - Maintain existing behavior when auto-open is enabled
47
+
48
+ - [x] **Task 4: Add user feedback for disabled auto-open**
49
+ - When auto-open is disabled, provide clear user feedback about file location
50
+ - Enhance existing title message to indicate manual file opening may be needed
51
+ - Ensure user knows where the HTML file was saved without being intrusive
52
+
53
+ - [x] **Task 5: Document environment variable in README.md**
54
+ - Location: `README.md:376-382` (add to existing "Tool Configuration" section)
55
+ - Follow established documentation pattern with expandable details section
56
+ - Include environment variable name, default value, and clear description
57
+ - Document supported values and expected behavior
58
+
59
+ - [x] **Task 6: Create comprehensive test coverage**
60
+ - Location: `crates/forge_infra/src/env.rs:269-511` (following existing test patterns)
61
+ - Unit tests for environment variable parsing logic using `serial_test::serial`
62
+ - Test default behavior (disabled when not set)
63
+ - Test enabled behavior (when set to truthy values: "true", "1", "yes")
64
+ - Test case-insensitive parsing
65
+ - Integration tests for dump functionality with different environment variable states
66
+
67
+ - [x] **Task 7: Verify integration and behavior**
68
+ - Ensure Environment struct properly initializes with new field
69
+ - Verify API service correctly exposes the auto_open_dump property
70
+ - Test that UI component can access the configuration via API
71
+ - Confirm all existing functionality remains unchanged when environment variable enables auto-open
72
+
73
+ ## Verification Criteria
74
+
75
+ - Environment variable `FORGE_DUMP_AUTO_OPEN` is integrated into Environment service
76
+ - Accessible via `self.api.environment().auto_open_dump` pattern
77
+ - Default behavior (when unset) is to NOT auto-open (false)
78
+ - Setting to "true", "1", or "yes" (case-insensitive) enables auto-open
79
+ - Any other value or unset state disables auto-open
80
+ - HTML file is still created regardless of auto-open setting
81
+ - User receives appropriate feedback about file creation and location when auto-open is disabled
82
+ - All existing functionality remains unchanged when environment variable enables auto-open
83
+ - Environment variable is properly documented in README.md
84
+ - Tests cover all scenarios including edge cases and follow established patterns
85
+
86
+ ## Potential Risks and Mitigations
87
+
88
+ 1. **Breaking Change Risk for Existing Users**
89
+ - Risk: Current users expect auto-open behavior by default
90
+ - Mitigation: Default to false as specified, which is better for new users. Document the change and migration path clearly.
91
+
92
+ 2. **Environment Service Integration Complexity**
93
+ - Risk: Adding field to Environment struct might affect other components
94
+ - Mitigation: Follow established patterns in the codebase for environment variable integration; the Environment struct is designed for extension.
95
+
96
+ 3. **Boolean Parsing Inconsistencies**
97
+ - Risk: Different boolean representations might cause confusion
98
+ - Mitigation: Use existing `parse_env` helper and document supported values clearly in README.
99
+
100
+ 4. **API Access Pattern Changes**
101
+ - Risk: New access pattern might not integrate seamlessly with existing UI code
102
+ - Mitigation: The `self.api.environment()` pattern is already established and widely used in the UI layer.
103
+
104
+ ## Alternative Approaches
105
+
106
+ 1. **Direct Environment Variable Check in UI**
107
+ - Alternative: Check environment variable directly in UI layer using `get_env_var()`
108
+ - Trade-offs: Simpler implementation but doesn't follow the architectural pattern of centralizing configuration in Environment service
109
+
110
+ 2. **Service Method Approach**
111
+ - Alternative: Add `should_auto_open_dump()` method to environment service
112
+ - Trade-offs: More encapsulated but doesn't provide the direct property access pattern requested
113
+
114
+ 3. **Workflow-level Configuration**
115
+ - Alternative: Add configuration to forge.yaml workflow files
116
+ - Trade-offs: More discoverable but less flexible than environment variable approach
117
+
118
+ ## Technical Implementation Details
119
+
120
+ **Environment Struct Field Addition:**
121
+ ```rust
122
+ // In crates/forge_domain/src/env.rs
123
+ #[derive(Clone, Debug, Setters)]
124
+ pub struct Environment {
125
+ // existing fields...
126
+ pub auto_open_dump: bool,
127
+ }
128
+ ```
129
+
130
+ **Environment Variable Parsing:**
131
+ ```rust
132
+ // In crates/forge_infra/src/env.rs get() method
133
+ let auto_open_dump = parse_env::<bool>("FORGE_DUMP_AUTO_OPEN").unwrap_or(false);
134
+
135
+ Environment {
136
+ // existing field assignments...
137
+ auto_open_dump,
138
+ }
139
+ ```
140
+
141
+ **UI Integration:**
142
+ ```rust
143
+ // In crates/forge_main/src/ui.rs dump methods
144
+ if self.api.environment().auto_open_dump {
145
+ open::that(path.as_str()).ok();
146
+ } else {
147
+ // Provide user feedback about file location
148
+ }
149
+ ```
150
+
151
+ **README Documentation Pattern:**
152
+ ```markdown
153
+ <details>
154
+ <summary><strong>Tool Configuration</strong></summary>
155
+
156
+ Configuring the tool calls settings:
157
+
158
+ ```bash
159
+ # .env
160
+ FORGE_TOOL_TIMEOUT=300 # Maximum execution time in seconds for a tool (default: 300)
161
+ FORGE_DUMP_AUTO_OPEN=false # Automatically open dump files in browser (default: false)
162
+ ```
163
+
164
+ </details>
165
+ ```
166
+
167
+ This plan follows established architectural patterns in the Forge codebase while providing the requested `self.api.environment().auto_open_dump` access pattern and comprehensive documentation.
plans/2025-09-13-dynamic-agent-slash-commands-v3.md ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dynamic Agent-Specific Slash Commands Implementation Plan
2
+
3
+ ## Objective
4
+
5
+ Implement dynamic registration of agent-specific slash commands that automatically creates commands like `/agent-foo` and `/agent-bar` based on the agents returned by the API. This feature will provide direct shortcuts for agent switching without requiring the interactive selection interface of the existing `/agent` command. The implementation follows the existing command registration pattern used by workflow commands.
6
+
7
+ ## Implementation Plan
8
+
9
+ - [x] **Task 1. Add AgentSwitch Command Variant**
10
+
11
+ **Rationale**: Extend the existing Command enum with a new variant for direct agent switching, following the pattern of other command variants like Custom(PartialEvent).
12
+
13
+ - Add `AgentSwitch(String)` variant to `Command` enum in `crates/forge_main/src/model.rs:221`
14
+ - Add corresponding name() method case at `crates/forge_main/src/model.rs:325`
15
+ - Add usage description using the strum property pattern similar to existing commands
16
+ - Update command parsing logic to route agent-specific commands to this variant
17
+
18
+ - [x] **Task 2. Extend ForgeCommandManager with Agent Command Registration**
19
+
20
+ **Rationale**: Follow the existing pattern used by `register_all()` method for workflow commands, but create a separate method for agent-based dynamic commands to maintain separation of concerns.
21
+
22
+ - Add `register_agent_commands(&self, agents: Vec<Agent>)` method to `ForgeCommandManager` at `crates/forge_main/src/model.rs:63`
23
+ - Implement agent ID sanitization for valid command names (replace special chars, handle spaces)
24
+ - Generate `ForgeCommand` objects with pattern `/agent-{sanitized_id}` following workflow command format
25
+ - Add agent-specific description format like "🤖 Switch to {title} agent"
26
+ - Store agent ID in the `value` field for later retrieval during parsing
27
+
28
+ - [x] **Task 3. Update ForgeCommandManager Command Parsing**
29
+
30
+ **Rationale**: Extend the existing parsing logic in the `parse()` method to detect and handle agent-specific command patterns, similar to how workflow custom commands are handled.
31
+
32
+ - Extend the `parse()` method fallback logic at `crates/forge_main/src/model.rs:192` to check for agent commands
33
+ - Detect commands matching `/agent-*` pattern before falling back to custom commands
34
+ - Extract agent ID from command names by removing `/agent-` prefix
35
+ - Return `Command::AgentSwitch(agent_id)` for valid agent commands
36
+ - Maintain existing error handling for invalid commands
37
+
38
+ - [x] **Task 4. Integrate Agent Command Registration into UI Initialization**
39
+
40
+ **Rationale**: Agent commands must be registered during UI initialization right after workflow commands, ensuring they are available throughout the session.
41
+
42
+ - Modify `init_state()` method in `crates/forge_main/src/ui.rs:700` to register agent commands
43
+ - Load agents using existing `self.api.get_agents().await?` after line 720
44
+ - Call `self.command.register_agent_commands(agents)` after workflow registration
45
+ - Add error handling that logs warnings for agent loading failures without breaking initialization
46
+ - Ensure agent commands are refreshed on subsequent `init_state()` calls
47
+
48
+ - [x] **Task 5. Implement Agent Switch Command Handler**
49
+
50
+ **Rationale**: Create a streamlined handler for direct agent switching in the main UI command processing loop, reusing existing agent switching logic for consistency.
51
+
52
+ - Add handler for `Command::AgentSwitch(agent_id)` in the UI command processing loop (around `crates/forge_main/src/ui.rs`)
53
+ - Validate that the requested agent exists by checking against loaded agents
54
+ - Reuse existing `on_agent_change()` logic for the actual switch operation
55
+ - Provide user feedback for successful switches using existing notification patterns
56
+ - Handle error cases with clear messages when agent is not found or unavailable
57
+
58
+ - [x] **Task 6. Implement Agent ID Sanitization Logic**
59
+
60
+ **Rationale**: Ensure generated command names are valid shell command identifiers and don't conflict with existing system commands.
61
+
62
+ - Create helper function to sanitize agent IDs for command names
63
+ - Convert spaces and special characters to hyphens or underscores
64
+ - Handle edge cases like empty IDs, numeric-only IDs, or very long IDs
65
+ - Validate against existing built-in command names (`/agent`, `/forge`, `/muse`, etc.)
66
+ - Provide deterministic fallback naming for problematic agent IDs
67
+
68
+ - [x] **Task 7. Update register_all Method to Handle Combined Registration**
69
+
70
+ **Rationale**: Ensure the existing workflow command registration doesn't interfere with agent commands and that both can coexist in the command registry.
71
+
72
+ - Modify `register_all()` method at `crates/forge_main/src/model.rs:78` to preserve existing agent commands
73
+ - Ensure agent commands are not overwritten when workflow commands are re-registered
74
+ - Maintain proper sorting of all commands (workflow + agent) for consistent completion behavior
75
+ - Add logic to clear only workflow-related commands while preserving agent commands
76
+
77
+ - [x] **Task 8. Add Command Validation and Conflict Resolution**
78
+
79
+ **Rationale**: Prevent agent command names from conflicting with built-in commands or workflow commands, ensuring system stability.
80
+
81
+ - Add validation in `register_agent_commands()` to check for name conflicts
82
+ - Implement conflict resolution strategy (e.g., add numeric suffix for duplicates)
83
+ - Log warnings for agents that can't be registered due to naming conflicts
84
+ - Ensure built-in commands always take precedence over dynamic agent commands
85
+ - Provide clear error messages when agent commands can't be registered
86
+
87
+ ## Verification Criteria
88
+
89
+ - **Agent Command Registration**: All discovered agents generate corresponding `/agent-{id}` commands in the command registry
90
+ - **Command Parsing**: Agent-specific commands are correctly parsed and routed to `Command::AgentSwitch` variant
91
+ - **Agent Switching**: Direct agent switching via generated commands functions identically to the interactive `/agent` command
92
+ - **Command Completion**: Agent commands appear in autocompletion alongside workflow and built-in commands
93
+ - **Error Handling**: Invalid agent commands provide clear error messages without crashing the system
94
+ - **Name Validation**: Generated command names are valid and don't conflict with existing commands
95
+ - **Session Persistence**: Agent commands remain available throughout the session and are properly refreshed
96
+ - **Integration**: Agent command registration doesn't interfere with existing workflow command functionality
97
+
98
+ ## Potential Risks and Mitigations
99
+
100
+ ### **Risk: Command Name Collisions with Built-in Commands**
101
+ **Impact**: Agent IDs might conflict with existing built-in commands like `/agent`, `/forge`, `/muse`
102
+ **Mitigation**:
103
+ - Use consistent `/agent-{id}` prefixing to avoid conflicts with built-in commands
104
+ - Implement validation to check against all existing command names during registration
105
+ - Skip registration for agents whose sanitized IDs would conflict with built-in commands
106
+
107
+ ### **Risk: Agent Loading Performance Impact**
108
+ **Impact**: Loading agents during UI initialization could slow startup
109
+ **Mitigation**:
110
+ - Agent loading already happens during UI initialization for other purposes
111
+ - Agent command registration is a lightweight operation that just creates ForgeCommand objects
112
+ - Implement timeout and graceful degradation if agent loading takes too long
113
+
114
+ ### **Risk: Command Registry State Consistency**
115
+ **Impact**: Agent commands might become stale if agents are reloaded but commands aren't refreshed
116
+ **Mitigation**:
117
+ - Register agent commands on every `init_state()` call, which handles session refresh
118
+ - Clear existing agent commands before re-registering to prevent stale entries
119
+ - Use the existing agent loading cache to minimize performance impact
120
+
121
+ ### **Risk: Agent ID Edge Cases**
122
+ **Impact**: Unusual agent IDs (empty, special chars, very long) could break command generation
123
+ **Mitigation**:
124
+ - Implement robust agent ID sanitization with fallback strategies
125
+ - Skip agents with IDs that can't be sanitized to valid command names
126
+ - Log warnings for problematic agent IDs to aid debugging
127
+
128
+ ## Alternative Approaches
129
+
130
+ ### **1. Extend Existing /agent Command**: Add parameter support like `/agent foo` instead of creating separate commands
131
+ **Trade-offs**: Simpler implementation but doesn't provide individual completion entries and requires parameter parsing
132
+
133
+ ### **2. Use Different Prefix Pattern**: Use `/switch-{agent}` or `/to-{agent}` instead of `/agent-{agent}`
134
+ **Trade-offs**: Different naming might be clearer but `/agent-` prefix clearly indicates relationship to the `/agent` command
135
+
136
+ ### **3. Register Agent Commands as Custom Commands**: Treat agent commands as workflow-style custom commands
137
+ **Trade-offs**: Could reuse more existing logic but would blur the distinction between user-defined and system-generated commands
138
+
139
+ ## Future Enhancements
140
+
141
+ - **Agent Command Grouping**: Display agent commands in a separate section in help/completion
142
+ - **Agent Command Aliases**: Allow agents to define custom short aliases in their metadata
143
+ - **Dynamic Command Refresh**: Automatically refresh agent commands when agent list changes without full UI reinitialization
144
+ - **Agent Command History**: Track usage patterns and prioritize frequently used agents in completion
plans/2025-09-13-forge-history-file-env-var-v2.md ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Per-Project Custom History File Support via Environment Variable
2
+
3
+ ## Objective
4
+
5
+ Implement support for per-project custom history files through the `FORGE_HISTORY_FILE` environment variable, allowing users to maintain separate prompt histories for different projects while preserving the current default behavior. The implementation will use the existing global history file by default and only override when the environment variable is explicitly set, supporting both absolute and relative paths with proper Windows compatibility.
6
+
7
+ ## Current Analysis
8
+
9
+ ### Current State Assessment
10
+
11
+ Based on the codebase analysis, the current history implementation:
12
+
13
+ **History File Location**: Currently determined by `Environment::history_path()` method in `crates/forge_domain/src/env.rs:68-70`, which returns `self.base_path.join(".forge_history")` - a single global file.
14
+
15
+ **History Usage**: History is initialized in `crates/forge_main/src/editor.rs:68-71` using `FileBackedHistory::with_file()` with the path from `env.history_path()`.
16
+
17
+ **Environment Configuration**: Environment variables are parsed in `ForgeEnvironmentInfra::get()` method in `crates/forge_infra/src/env.rs:39-78`, which already supports parsing custom environment variables like `FORGE_DUMP_AUTO_OPEN`, `FORGE_TOOL_TIMEOUT`, etc.
18
+
19
+ **Default Behavior Priority**: The implementation prioritizes maintaining existing behavior, only overriding when users explicitly set the environment variable.
20
+
21
+ ## Implementation Plan
22
+
23
+ ### 1. Environment Variable Support
24
+ - [ ] **Task 1**: Add `FORGE_HISTORY_FILE` environment variable support to `ForgeEnvironmentInfra::get()` method
25
+ - **Location**: `crates/forge_infra/src/env.rs:39-78`
26
+ - **Rationale**: This is where all environment variable parsing occurs, following the established pattern of other FORGE_* environment variables
27
+ - **Implementation**: Use existing `parse_env::<String>()` function to retrieve the custom history file path, keeping it optional to preserve default behavior
28
+
29
+ ### 2. Environment Struct Extension
30
+ - [ ] **Task 2**: Add `custom_history_path` field to `Environment` struct to store the resolved history file path
31
+ - **Location**: `crates/forge_domain/src/env.rs:16-61`
32
+ - **Rationale**: Following the established pattern of storing environment-specific configurations as fields, enabling proper dependency injection and testing
33
+ - **Implementation**: Add `Option<PathBuf>` field with proper serialization attributes to maintain backward compatibility
34
+
35
+ ### 3. History Path Resolution Enhancement with Relative Path Support
36
+ - [ ] **Task 3**: Modify `Environment::history_path()` to support custom path resolution with relative path handling
37
+ - **Location**: `crates/forge_domain/src/env.rs:68-70`
38
+ - **Rationale**: This method is the single source of truth for history file location and needs to handle environment variable override while maintaining current default behavior
39
+ - **Implementation**:
40
+ - Check for custom_history_path first (from environment variable)
41
+ - If relative path, resolve against current working directory using `std::env::current_dir()`
42
+ - If absolute path, use as-is
43
+ - Fall back to current default behavior if no environment variable is set
44
+ - Handle path canonicalization for Windows compatibility
45
+
46
+ ### 4. Windows-Specific Path Handling
47
+ - [ ] **Task 4**: Implement Windows-specific path normalization and validation
48
+ - **Location**: History path resolution logic in Environment
49
+ - **Rationale**: Windows has different path conventions (backslashes, drive letters, UNC paths) that require special handling
50
+ - **Implementation**:
51
+ - Use `PathBuf::canonicalize()` for path normalization when possible
52
+ - Handle Windows drive letter paths (C:\, D:\, etc.)
53
+ - Support UNC paths (\\server\share) on Windows
54
+ - Ensure proper path separator handling across platforms
55
+ - Add Windows-specific error handling for invalid drive letters
56
+
57
+ ### 5. Path Creation and Validation
58
+ - [ ] **Task 5**: Add robust path creation and validation logic
59
+ - **Location**: History path resolution logic in Environment
60
+ - **Rationale**: Custom paths may point to non-existent directories or have permission issues
61
+ - **Implementation**:
62
+ - Create parent directories if they don't exist using `std::fs::create_dir_all()`
63
+ - Validate write permissions before attempting to use custom path
64
+ - Provide fallback to default path with user warning on failure
65
+ - Add specific error messages for common issues (permissions, invalid paths, disk space)
66
+
67
+ <task_status>
68
+ [x]: DONE - Task 1: Add FORGE_HISTORY_FILE environment variable support
69
+ [~]: IN_PROGRESS - Task 2: Add custom_history_path field to Environment struct
70
+ [ ]: PENDING - Task 3: Modify Environment::history_path() with relative path support
71
+ [ ]: PENDING - Task 4: Implement Windows-specific path handling
72
+ [ ]: PENDING - Task 5: Add path creation and validation logic
73
+ </task_status>
74
+
75
+ ## Verification Criteria
76
+
77
+ ### Default Behavior Preservation
78
+ - Existing installations continue to use global history file without any configuration changes
79
+ - No performance impact when environment variable is not set
80
+ - Zero breaking changes to existing APIs or user workflows
81
+
82
+ ### Environment Variable Functionality
83
+ - Environment variable `FORGE_HISTORY_FILE` successfully overrides default history location only when set
84
+ - Absolute paths specified in environment variable are used directly without modification
85
+ - Relative paths are resolved relative to current working directory where forge is invoked
86
+ - Missing or unset environment variable maintains exact current behavior
87
+
88
+ ### Path Resolution Support
89
+ - Relative paths like `./project-history` resolve correctly from current directory
90
+ - Relative paths like `../shared/history` work across different directory structures
91
+ - Absolute paths work on both Unix (`/home/user/history`) and Windows (`C:\Users\user\history`)
92
+ - Windows UNC paths (`\\server\share\history`) are supported correctly
93
+
94
+ ### Cross-Platform Compatibility
95
+ - Path handling works identically on Windows, macOS, and Linux
96
+ - Windows drive letter paths (C:\, D:\, etc.) are handled correctly
97
+ - Unix-style paths work on all Unix-like systems
98
+ - Path separators are normalized automatically by PathBuf
99
+
100
+ ### Error Handling and Recovery
101
+ - Non-existent parent directories are created automatically with appropriate permissions
102
+ - Invalid paths generate clear, actionable error messages
103
+ - Permission issues trigger graceful fallback to default location with user notification
104
+ - Disk space issues are detected and reported appropriately
105
+
106
+ ## Potential Risks and Mitigations
107
+
108
+ ### 1. **Backward Compatibility**
109
+ **Risk**: Changes might affect existing user workflows
110
+ **Mitigation**: Strict preservation of default behavior; environment variable is purely additive
111
+
112
+ ### 2. **Windows Path Complexity**
113
+ **Risk**: Windows path handling edge cases (UNC, long paths, reserved names)
114
+ **Mitigation**: Use Rust's built-in PathBuf and extensive Windows-specific testing; handle reserved names (CON, PRN, etc.)
115
+
116
+ ### 3. **Relative Path Confusion**
117
+ **Risk**: Users might not understand relative path resolution context
118
+ **Mitigation**: Clear documentation explaining resolution is relative to working directory, not forge binary location
119
+
120
+ ### 4. **Directory Creation Permissions**
121
+ **Risk**: Creating parent directories might fail due to permissions
122
+ **Mitigation**: Graceful fallback with informative error messages; suggest alternative paths
123
+
124
+ ### 5. **Path Traversal Concerns**
125
+ **Risk**: Users specifying paths outside intended areas
126
+ **Mitigation**: Document security considerations; users control their own environment so flexibility is acceptable
127
+
128
+ ## Alternative Approaches
129
+
130
+ ### 1. **Always Use Current Directory**: Default to `./.forge_history` instead of global file
131
+ **Trade-offs**: More intuitive per-project behavior but breaks existing user expectations
132
+
133
+ ### 2. **Auto-Detection**: Automatically look for `.forge_history` in current directory first
134
+ **Trade-offs**: Zero configuration but might create unexpected behavior changes
135
+
136
+ ### 3. **Forge.yaml Integration**: Add history_file field to project configuration
137
+ **Trade-offs**: More permanent per-project setting but requires configuration file management
138
+
139
+ ## Implementation Dependencies
140
+
141
+ ### Internal Dependencies
142
+ - Environment variable parsing infrastructure (already exists)
143
+ - FileBackedHistory initialization path (already exists)
144
+ - PathBuf handling utilities (built-in Rust functionality)
145
+
146
+ ### External Dependencies
147
+ - No new external crates required
148
+ - Leverages existing `std::fs`, `std::env`, and `std::path` functionality
149
+ - Uses current `dirs` crate for default path resolution
150
+
151
+ ### Testing Requirements
152
+ - Unit tests for environment variable parsing with None values
153
+ - Path resolution tests for absolute and relative paths
154
+ - Cross-platform integration tests (Windows, macOS, Linux)
155
+ - Edge case tests for Windows-specific path formats
156
+ - Permission and error handling tests
157
+ - Backward compatibility verification tests
158
+
159
+ ## Success Metrics
160
+
161
+ ### Functional Success
162
+ - Users can set `FORGE_HISTORY_FILE=./project-history` for relative paths
163
+ - Users can set `FORGE_HISTORY_FILE=/absolute/path/history` for absolute paths
164
+ - Windows users can use `FORGE_HISTORY_FILE=C:\Users\Name\history` successfully
165
+ - Unset environment variable maintains exact current behavior
166
+
167
+ ### User Experience Success
168
+ - Zero configuration change required for existing users
169
+ - Clear error messages guide users when path issues occur
170
+ - Seamless cross-platform experience with same environment variable
171
+
172
+ ### Developer Experience Success
173
+ - Implementation follows existing codebase patterns
174
+ - Windows-specific considerations are well-documented
175
+ - Error handling provides actionable feedback for debugging
176
+
177
+ ## Windows-Specific Considerations
178
+
179
+ ### Path Format Support
180
+ - **Drive Letters**: Support for `C:\path\to\history`, `D:\projects\history`
181
+ - **UNC Paths**: Support for `\\server\share\history` network paths
182
+ - **Long Paths**: Handle Windows long path limitations (>260 characters) appropriately
183
+ - **Reserved Names**: Detect and warn about reserved Windows filenames (CON, PRN, AUX, etc.)
184
+
185
+ ### Path Separator Handling
186
+ - Accept both forward slashes (`C:/path/to/history`) and backslashes (`C:\path\to\history`)
187
+ - Normalize path separators using Rust's PathBuf automatic conversion
188
+ - Ensure relative path resolution works with Windows-style separators
189
+
190
+ ### Permission and Security
191
+ - Handle Windows-specific permission models (ACLs vs Unix permissions)
192
+ - Detect when paths point to system directories that may be restricted
193
+ - Provide Windows-appropriate error messages for common permission issues
194
+
195
+ ### Environment Variable Examples
196
+ ```bash
197
+ # Unix/Linux/macOS examples
198
+ FORGE_HISTORY_FILE=./project-history
199
+ FORGE_HISTORY_FILE=../shared/team-history
200
+ FORGE_HISTORY_FILE=/home/user/forge-histories/project1
201
+
202
+ # Windows examples
203
+ FORGE_HISTORY_FILE=.\project-history
204
+ FORGE_HISTORY_FILE=..\shared\team-history
205
+ FORGE_HISTORY_FILE=C:\Users\Name\ForgeHistories\project1
206
+ FORGE_HISTORY_FILE=\\server\share\team-histories\project1
207
+ ```
plans/2025-09-14-explicit-conversation-id-generation-v1.md ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Explicit Conversation ID Generation Implementation Plan
2
+
3
+ ## Objective
4
+
5
+ Implement explicit conversation ID generation to enable multiple terminal sessions to maintain separate conversation contexts. This approach eliminates output parsing challenges by requiring users to explicitly generate and manage conversation IDs.
6
+
7
+ ## Research Summary
8
+
9
+ Based on codebase analysis, I found:
10
+
11
+ 1. **Current CLI Structure**: `--resume` flag exists in `crates/forge_main/src/cli.rs:64` and automatically loads the most recent conversation
12
+ 2. **Conversation ID Management**: `ConversationId` is a UUID-based type in `crates/forge_domain/src/conversation.rs:15` with `generate()` and `parse()` methods
13
+ 3. **Resume Logic**: Current `handle_resume()` in `crates/forge_main/src/ui.rs:265` uses `api.last_conversation()` to find the most recent conversation
14
+ 4. **Shell Plugin**: Current transformation in `shell-plugin/forge.plugin.zsh:18` uses `--resume` without parameters
15
+ 5. **Database**: Repository has `get_conversation()` method for retrieving specific conversations by ID
16
+ 6. **Terminal Command**: Existing `TopLevelCommand::Term` in `crates/forge_main/src/cli.rs:100` handles terminal-related operations
17
+
18
+ ## Implementation Plan
19
+
20
+ ### Phase 1: CLI Parameter Enhancement
21
+
22
+ - [x] **Task 1.1: Add conversation ID generation parameter**
23
+ - Add `--generate-conversation-id` flag to CLI structure
24
+ - Update argument parsing to handle conversation ID generation
25
+ - Add validation and error handling for generation mode
26
+ - Rationale: Provides explicit mechanism for users to create conversation IDs
27
+
28
+ - [x] **Task 1.2: Modify --resume to require conversation ID**
29
+ - Update `--resume` parameter to require `--conversation-id` argument
30
+ - Add clap validation to enforce this requirement
31
+ - Update help text and documentation for new behavior
32
+ - Rationale: Changes resume semantics from "find most recent" to "resume specific conversation"
33
+
34
+ - [x] **Task 1.3: Add --conversation-id parameter**
35
+ - Add `--conversation-id` parameter for specifying conversation ID
36
+ - Implement conversation ID validation using existing `ConversationId::parse()`
37
+ - Add error handling for invalid conversation ID formats
38
+ - Rationale: Allows users to specify which conversation to use for new operations
39
+
40
+ ### Phase 2: Core Logic Implementation
41
+
42
+ - [x] **Task 2.1: Implement conversation ID generation logic**
43
+ - Create `handle_generate_conversation_id()` method in UI layer
44
+ - Use existing `ConversationId::generate()` method for ID creation
45
+ - Output generated ID to stdout and exit cleanly
46
+ - Rationale: Provides the core functionality for creating new conversation IDs
47
+
48
+ - [x] **Task 2.2: Update resume logic for explicit conversation loading**
49
+ - Modify `handle_resume()` to use `api.get_conversation()` instead of `api.last_conversation()`
50
+ - Add conversation existence validation and error handling
51
+ - Update user messages to show specific conversation being resumed
52
+ - Rationale: Changes resume behavior to work with explicit conversation IDs
53
+
54
+ - [x] **Task 2.3: Update conversation creation to accept explicit IDs**
55
+ - Modify conversation initialization to use provided conversation ID when available
56
+ - Add validation to ensure conversation ID uniqueness when creating new conversations
57
+ - Implement proper error handling for duplicate conversation IDs
58
+ - Rationale: Allows users to create conversations with specific IDs
59
+
60
+ ### Phase 3: Shell Plugin Integration
61
+
62
+ - [x] **Task 3.1: Create forge-term command for conversation ID generation**
63
+ - Add `forge-term` function to shell plugin for ID generation
64
+ - Implement conversation ID storage in shell environment variables
65
+ - Add user feedback and error handling for generation process
66
+ - Rationale: Provides user-friendly interface for managing conversation IDs
67
+
68
+ - [x] **Task 3.2: Update command transformation for explicit conversation IDs**
69
+ - Modify `??` commands to use `--resume --conversation-id $FORGE_CONVERSATION_ID`
70
+ - Modify `?` commands to use `--conversation-id $FORGE_CONVERSATION_ID` when available
71
+ - Add fallback behavior when no conversation ID is set
72
+ - Rationale: Integrates new conversation ID management with existing shell workflow
73
+
74
+ - [x] **Task 3.3: Add conversation ID lifecycle management**
75
+ - Implement conversation ID validation before command execution
76
+ - Add error handling for missing or invalid conversation IDs
77
+ - Implement cleanup and reset functionality for conversation IDs
78
+ - Rationale: Ensures robust conversation ID management throughout terminal session
79
+
80
+ ### Phase 4: API and Service Layer Updates
81
+
82
+ - [x] **Task 4.1: Update API layer to support conversation ID parameters**
83
+ - Add methods to API trait for conversation ID validation and retrieval
84
+ - Implement conversation existence checking functionality
85
+ - Add proper error handling for conversation-related operations
86
+ - Rationale: Provides necessary API support for explicit conversation management
87
+
88
+ - [x] **Task 4.2: Update service layer for conversation ID operations**
89
+ - Add conversation validation methods to conversation service
90
+ - Implement conversation ID uniqueness checking
91
+ - Add error handling for conversation-related service operations
92
+ - Rationale: Ensures service layer supports new conversation ID requirements
93
+
94
+ ### Phase 5: Testing and Validation
95
+
96
+ - [x] **Task 5.1: Create unit tests for conversation ID generation**
97
+ - Test `ConversationId::generate()` produces valid unique IDs
98
+ - Test conversation ID parsing and validation
99
+ - Test error handling for invalid conversation ID formats
100
+ - Rationale: Ensures conversation ID generation works correctly
101
+
102
+ - [x] **Task 5.2: Create integration tests for resume functionality**
103
+ - Test resuming existing conversations with explicit IDs
104
+ - Test error handling for non-existent conversation IDs
105
+ - Test conversation loading and state restoration
106
+ - Rationale: Verifies that resume functionality works with explicit IDs
107
+
108
+ - [x] **Task 5.3: Create end-to-end tests for shell plugin integration**
109
+ - Test conversation ID generation and storage in shell environment
110
+ - Test command transformation with stored conversation IDs
111
+ - Test fallback behavior when no conversation ID is available
112
+ - Rationale: Ensures complete workflow works correctly from shell to application
113
+
114
+ ### Phase 6: Migration and Backward Compatibility
115
+
116
+ - [x] **Task 6.1: Implement deprecation warnings for old resume behavior**
117
+ - Add warnings when `--resume` is used without `--conversation-id`
118
+ - Provide clear migration instructions to users
119
+ - Implement graceful fallback to current behavior during transition
120
+ - Rationale: Helps users transition to new behavior without breaking existing workflows
121
+
122
+ - [x] **Task 6.2: Create migration documentation**
123
+ - Document breaking changes and migration steps
124
+ - Provide examples of new workflow patterns
125
+ - Create troubleshooting guide for common migration issues
126
+ - Rationale: Ensures users can successfully migrate to new approach
127
+
128
+ ## Verification Criteria
129
+
130
+ - [x] **Criterion 1: Conversation ID Generation** `forge --generate-conversation-id` produces valid, unique conversation IDs
131
+ - [x] **Criterion 2: Explicit Resume Functionality** `forge --resume --conversation-id <id>` successfully resumes the specified conversation
132
+ - [x] **Criterion 3: Shell Integration** `forge-term generate-conversation-id` properly stores ID in shell environment
133
+ - [x] **Criterion 4: Command Transformation** `?? text` transforms to `forge --resume --conversation-id <id> <<< text` when ID is available
134
+ - [x] **Criterion 5: Error Handling** Invalid conversation IDs produce clear error messages without crashing
135
+ - [x] **Criterion 6: Backward Compatibility** Existing `--resume` behavior works with deprecation warnings during transition
136
+ - [x] **Criterion 7: Multi-Terminal Isolation** Multiple terminals with different conversation IDs maintain separate conversation histories
137
+
138
+ ## Potential Risks and Mitigations
139
+
140
+ 1. **Breaking Changes to Existing Workflows**
141
+ Mitigation: Implement deprecation warnings and fallback behavior; provide clear migration documentation; maintain backward compatibility during transition period
142
+
143
+ 2. **Conversation ID Collisions**
144
+ Mitigation: Use UUID-based generation which has extremely low collision probability; implement uniqueness validation when creating conversations
145
+
146
+ 3. **Shell Environment Variable Conflicts**
147
+ Mitigation: Use unique variable names (`FORGE_CONVERSATION_ID`); implement proper variable scoping; add validation for stored IDs
148
+
149
+ 4. **User Confusion with New Workflow**
150
+ Mitigation: Provide clear documentation and examples; implement helpful error messages; add interactive guidance when possible
151
+
152
+ 5. **Database Performance Impact**
153
+ Mitigation: Leverage existing conversation retrieval methods; ensure proper indexing on conversation_id column; monitor query performance
154
+
155
+ 6. **Migration Complexity**
156
+ Mitigation: Implement gradual migration approach; provide automated migration tools; offer support during transition period
157
+
158
+ ## Alternative Approaches
159
+
160
+ 1. **Optional Session ID Parameter**: Keep existing `--resume` behavior and add optional `--session-id` parameter
161
+ Trade-offs: Less breaking changes, but more complex implementation with two parallel behaviors
162
+
163
+ 2. **Environment Variable Only**: Use only environment variables without CLI parameter changes
164
+ Trade-offs: Simpler CLI changes, but less explicit control and harder to debug
165
+
166
+ 3. **Configuration File Based**: Store conversation IDs in configuration files instead of environment variables
167
+ Trade-offs: More persistent across shell sessions, but adds file management complexity
168
+
169
+ 4. **Hybrid Approach**: Support both explicit conversation IDs and automatic session management
170
+ Trade-offs: More flexible, but significantly more complex implementation and maintenance
171
+
172
+ 5. **Separate Command Set**: Create new commands (`forge-resume`, `forge-new`) alongside existing ones
173
+ Trade-offs: No breaking changes, but confusing user experience with multiple ways to do the same thing
plans/2025-09-27-AppConfigRepository_Implementation-v1.md ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AppConfigRepository Implementation Plan
2
+
3
+ ## Objective
4
+
5
+ Create a new AppConfigRepository following the established repository pattern in the codebase, and update all direct AppConfig usage to use the new repository pattern instead of the current AppConfigService implementation.
6
+
7
+ ## Implementation Plan
8
+
9
+ ### Phase 1: Repository Pattern Implementation
10
+
11
+ - [x] **Create AppConfigRepository trait in forge_services/src/infra.rs**
12
+ - Define async methods for CRUD operations on AppConfig
13
+ - Follow the same pattern as ConversationRepository
14
+ - Include methods: get_app_config() -> Option<AppConfig>, set_app_config(config: &AppConfig) -> anyhow::Result<()>
15
+
16
+ - [x] **Create AppConfigRepositoryImpl in forge_infra crate**
17
+ - Create new file: crates/forge_infra/src/database/repository/app_config.rs
18
+ - Implement the AppConfigRepository trait
19
+ - Handle file-based storage operations similar to current ForgeConfigService
20
+ - Include comprehensive tests following the project's testing patterns
21
+
22
+ - [x] **Update forge_infra/src/database/mod.rs**
23
+ - Add module declaration for app_config repository
24
+ - Export the new AppConfigRepositoryImpl
25
+
26
+ - [x] **Update ForgeInfra struct**
27
+ - Add app_config_repository field to ForgeInfra in crates/forge_infra/src/forge_infra.rs
28
+ - Initialize the repository in the constructor
29
+ - Implement AppConfigRepository trait for ForgeInfra by delegating to the repository
30
+
31
+ ### Phase 2: Service Layer Updates
32
+
33
+ - [x] **Update ForgeConfigService implementation**
34
+ - Modify crates/forge_services/src/app_config.rs to use AppConfigRepository instead of direct file operations
35
+ - Replace direct file read/write with repository method calls
36
+ - Maintain the same AppConfigService interface for backward compatibility
37
+
38
+ - [x] **Update Services trait integration**
39
+ - Add AppConfigRepository associated type to the Services trait in crates/forge_app/src/services.rs
40
+ - Add app_config_repository() method to Services trait
41
+ - Update the trait implementation in forge_services to return the repository
42
+
43
+ ### Phase 3: Direct Usage Migration
44
+
45
+ - [x] **Update Authenticator class**
46
+ - Modify crates/forge_app/src/authenticator.rs to use AppConfigRepository through the services layer
47
+ - Replace direct AppConfigService calls with repository-based operations
48
+
49
+ - [x] **Update forge_api implementations**
50
+ - Modify crates/forge_api/src/forge_api.rs and crates/forge_api/src/api.rs
51
+ - Ensure API layer uses the updated service layer with repository pattern
52
+
53
+ - [x] **Update UI components**
54
+ - Modify crates/forge_main/src/ui.rs to use the updated service layer
55
+ - Ensure no direct AppConfig instantiation remains
56
+
57
+ - [x] **Update authentication service**
58
+ - Modify crates/forge_services/src/auth.rs to use repository pattern
59
+ - Ensure LoginInfo and InitAuth operations work with the new pattern
60
+
61
+ ### Phase 4: Testing and Verification
62
+
63
+ - [x] **Create comprehensive unit tests**
64
+ - Test AppConfigRepositoryImpl with various scenarios (file exists, doesn't exist, invalid JSON)
65
+ - Test error handling and edge cases
66
+ - Follow the project's testing pattern with fixtures, actual, expected structure
67
+
68
+ - [x] **Create integration tests**
69
+ - Test the complete flow from API to repository
70
+ - Verify backward compatibility of AppConfigService interface
71
+
72
+ - [x] **Update existing tests**
73
+ - Modify any tests that directly instantiate AppConfig to use proper fixtures
74
+ - Ensure all tests pass with the new repository pattern
75
+
76
+ ### Phase 5: Documentation and Cleanup
77
+
78
+ - [x] **Remove deprecated direct usage**
79
+ - Remove any remaining direct file operations in ForgeConfigService
80
+ - Ensure all AppConfig operations go through the repository
81
+
82
+ - [x] **Add documentation**
83
+ - Document the new repository pattern in code comments
84
+ - Update any architectural documentation if it exists
85
+
86
+ - [x] **Verify consistency**
87
+ - Ensure the new pattern follows the same style as ConversationRepository
88
+ - Verify all async trait implementations are consistent
89
+
90
+ ## Verification Criteria
91
+
92
+ - **Repository Pattern Compliance**: AppConfigRepository follows the same pattern as ConversationRepository with proper async traits and error handling
93
+ - **Backward Compatibility**: All existing AppConfigService functionality continues to work without breaking changes
94
+ - **Test Coverage**: New repository implementation has comprehensive unit tests with >90% coverage
95
+ - **Integration Success**: All existing functionality (login, authentication, config management) works seamlessly with the new repository
96
+ - **Code Quality**: New code passes all linting rules and follows project conventions
97
+ - **Performance**: No performance degradation compared to the current file-based implementation
98
+
99
+ ## Potential Risks and Mitigations
100
+
101
+ 1. **Breaking Existing Functionality**
102
+ Mitigation: Maintain the AppConfigService interface and gradually migrate internal implementations while preserving public APIs
103
+
104
+ 2. **File System Access Complexity**
105
+ Mitigation: Reuse existing file infrastructure patterns from ForgeConfigService and follow the same error handling approach
106
+
107
+ 3. **Testing Integration Points**
108
+ Mitigation: Create mock implementations for testing and follow the established testing patterns used for ConversationRepository
109
+
110
+ 4. **Async Trait Complexity**
111
+ Mitigation: Follow the exact same async trait patterns used in ConversationRepository and other existing repositories
112
+
113
+ ## Alternative Approaches
114
+
115
+ 1. **Direct Migration**: Completely replace AppConfigService with repository pattern in one step
116
+ Trade-offs: Higher risk but simpler final architecture
117
+
118
+ 2. **Adapter Pattern**: Create an adapter that wraps the current service
119
+ Trade-offs: Lower risk but adds an extra abstraction layer
120
+
121
+ 3. **Hybrid Approach**: Keep both patterns and gradually deprecate the service
122
+ Trade-offs: Maintains maximum backward compatibility but increases code complexity
plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v1.md ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fix Auto-Sync Workspace Registration Issue
2
+
3
+ **Issue:** [#2288](https://github.com/tailcallhq/forgecode/issues/2288)
4
+
5
+ **Problem:** The zsh plugin automatically syncs workspaces in the background on every directory change, causing unintended parent directories to be registered as workspaces. When users run `forge workspace list`, ancestor directories appear as "Current" instead of the actual working directory.
6
+
7
+ **Root Cause:** `_forge_start_background_sync()` in `shell-plugin/lib/helpers.zsh:114-131` unconditionally runs `forge workspace sync` without checking if the workspace or its ancestors are already registered.
8
+
9
+ ---
10
+
11
+ ## Objective
12
+
13
+ Prevent auto-sync from creating unintended workspace registrations while maintaining the convenience of automatic syncing for already-indexed workspaces.
14
+
15
+ ---
16
+
17
+ ## Implementation Plan
18
+
19
+ ### Phase 1: Add Workspace Detection Command
20
+
21
+ - [ ] Add new CLI subcommand `forge workspace is-indexed [PATH]` that checks if a path (or any ancestor) is already registered as a workspace
22
+ - [ ] Implement in `crates/forge_main/src/cli.rs` - add `IsIndexed` variant to `WorkspaceCommand` enum
23
+ - [ ] Add handler in `crates/forge_main/src/ui.rs` that calls existing `is_indexed()` service method
24
+ - [ ] The command should return exit code 0 if indexed, exit code 1 if not indexed
25
+ - [ ] Output should be silent by default (only exit code matters for scripting)
26
+ - [ ] Add optional `--verbose` flag to show which workspace was found (exact or ancestor)
27
+
28
+ ### Phase 2: Update Zsh Background Sync Logic
29
+
30
+ - [ ] Modify `_forge_start_background_sync()` in `shell-plugin/lib/helpers.zsh:114-131`
31
+ - [ ] Before running sync, call `$_FORGE_BIN workspace is-indexed "$workspace_path"`
32
+ - [ ] Check exit code: if non-zero (not indexed), skip the sync and return early
33
+ - [ ] Only proceed with background sync if exit code is 0 (workspace or ancestor is already indexed)
34
+ - [ ] Add debug logging (when `FORGE_DEBUG=true`) to indicate when sync is skipped
35
+
36
+ ### Phase 3: Handle Initial Workspace Registration
37
+
38
+ - [ ] Ensure manual `forge workspace sync` still works for first-time registration
39
+ - [ ] Update documentation to clarify the auto-sync behavior (only syncs already-registered workspaces)
40
+ - [ ] Consider adding a helper message when users first cd into a new directory that suggests running `forge workspace sync` to enable auto-sync
41
+
42
+ ### Phase 4: Update Workspace List Display
43
+
44
+ - [ ] Enhance `on_list_workspaces()` in `crates/forge_main/src/ui.rs:3266-3311` to distinguish between exact match and ancestor match
45
+ - [ ] When current workspace is an ancestor match, display as `[Current via ancestor]` or similar
46
+ - [ ] Show the actual current directory path in the output when it differs from the workspace path
47
+
48
+ ---
49
+
50
+ ## Verification Criteria
51
+
52
+ ### Before Fix
53
+ - [ ] Run `cd /Users/username` (parent directory)
54
+ - [ ] Run `forge workspace sync` once
55
+ - [ ] Run `cd /Users/username/Documents/Projects/forge` (subdirectory)
56
+ - [ ] Trigger zsh accept-line (press Enter on any command)
57
+ - [ ] Run `forge workspace list`
58
+ - [ ] Verify: `/Users/username/Documents/Projects/forge` is incorrectly registered as a workspace
59
+
60
+ ### After Fix
61
+ - [ ] Clean database and repeat above steps
62
+ - [ ] Run `cd /Users/username/Documents/Projects/forge`
63
+ - [ ] Trigger zsh accept-line (press Enter)
64
+ - [ ] Run `forge workspace list`
65
+ - [ ] Verify: Only `/Users/username` appears (no auto-sync of subdirectory)
66
+ - [ ] Verify: List shows `[Current via ancestor]` or similar indication
67
+ - [ ] Run explicit `forge workspace sync` in subdirectory
68
+ - [ ] Verify: Now subdirectory is registered and will auto-sync on future visits
69
+
70
+ ### Edge Cases
71
+ - [ ] Test behavior when no workspace exists at all (should not auto-sync)
72
+ - [ ] Test behavior in deeply nested directories (should find closest ancestor)
73
+ - [ ] Test behavior when `FORGE_SYNC_ENABLED=false` (should still respect flag)
74
+ - [ ] Test manual sync in new directory (should still work)
75
+
76
+ ---
77
+
78
+ ## Potential Risks and Mitigations
79
+
80
+ ### Risk 1: Breaking Existing User Workflows
81
+ **Mitigation:** Users who rely on automatic workspace creation will need to run `forge workspace sync` once per workspace. This is a one-time migration cost for better UX long-term.
82
+
83
+ ### Risk 2: Performance Impact
84
+ **Mitigation:** The `workspace is-indexed` check is a fast database query (already exists in `is_indexed()` method). It should add negligible overhead.
85
+
86
+ ### Risk 3: Race Conditions
87
+ **Mitigation:** The check and sync are not atomic, but this is acceptable - worst case, a sync happens when it shouldn't have. No data corruption risk.
88
+
89
+ ---
90
+
91
+ ## Alternative Approaches
92
+
93
+ ### Alternative 1: Opt-in Auto-Sync
94
+ Instead of checking if workspace exists, require users to explicitly enable auto-sync per workspace via a flag or configuration file.
95
+
96
+ **Pros:** More explicit control
97
+ **Cons:** More complex UX, requires additional configuration management
98
+
99
+ ### Alternative 2: Display-Only Fix
100
+ Just improve the workspace list display to show `[Current via ancestor]` without changing auto-sync behavior.
101
+
102
+ **Pros:** Simpler implementation, no behavior changes
103
+ **Cons:** Doesn't solve the root problem of unintended workspace registrations
104
+
105
+ ### Alternative 3: Auto-Sync with Depth Limit
106
+ Only auto-sync directories within N levels of an existing workspace.
107
+
108
+ **Pros:** Prevents deep directory pollution
109
+ **Cons:** Arbitrary limit, still creates unintended workspaces
110
+
111
+ ---
112
+
113
+ ## Dependencies
114
+
115
+ - No external dependencies
116
+ - Existing `is_indexed()` method in `WorkspaceService` can be reused
117
+ - Zsh plugin already has access to `$_FORGE_BIN` for CLI calls
118
+
119
+ ---
120
+
121
+ ## Testing Strategy
122
+
123
+ 1. **Unit Tests:** Add tests for `workspace is-indexed` command in CLI tests
124
+ 2. **Integration Tests:** Test zsh function behavior with mocked CLI commands
125
+ 3. **Manual Testing:** Follow verification criteria above
126
+ 4. **Regression Testing:** Ensure existing workspace sync functionality unchanged
127
+
128
+ ---
129
+
130
+ ## Documentation Updates
131
+
132
+ - [ ] Update `shell-plugin/README.md` to explain auto-sync behavior
133
+ - [ ] Add note about one-time `workspace sync` requirement for new workspaces
134
+ - [ ] Document `FORGE_SYNC_ENABLED` environment variable
135
+ - [ ] Update workspace command documentation with new `is-indexed` subcommand
plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v2.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fix Auto-Sync Workspace Registration Issue
2
+
3
+ **Issue:** [#2288](https://github.com/tailcallhq/forgecode/issues/2288)
4
+
5
+ **Problem:** The zsh plugin automatically syncs workspaces in the background on every directory change, causing unintended parent directories to be registered as workspaces. When users run `forge workspace list`, ancestor directories appear as "Current" instead of the actual working directory.
6
+
7
+ **Root Cause:** `_forge_start_background_sync()` in `shell-plugin/lib/helpers.zsh:114-131` unconditionally runs `forge workspace sync` without checking if the workspace or its ancestors are already registered.
8
+
9
+ ---
10
+
11
+ ## Objective
12
+
13
+ Prevent auto-sync from creating unintended workspace registrations while maintaining the convenience of automatic syncing for already-indexed workspaces.
14
+
15
+ ---
16
+
17
+ ## Implementation Plan
18
+
19
+ ### Phase 1: Add Porcelain Mode to Workspace Info Command
20
+
21
+ - [ ] Add `--porcelain` flag to `workspace info` command in `crates/forge_main/src/cli.rs:262-266`
22
+ - [ ] Update `on_workspace_info()` in `crates/forge_main/src/ui.rs:3328-3389` to handle porcelain mode
23
+ - [ ] In porcelain mode, return exit code 0 if workspace exists (Some), exit code 1 if None
24
+ - [ ] In porcelain mode, output should be silent (no text output, only exit code)
25
+ - [ ] Existing behavior (display mode) remains unchanged
26
+ - [ ] This leverages the existing `get_workspace_info()` call which already checks for workspace or ancestor
27
+
28
+ ### Phase 2: Update Zsh Background Sync Logic
29
+
30
+ - [ ] Modify `_forge_start_background_sync()` in `shell-plugin/lib/helpers.zsh:114-131`
31
+ - [ ] Before running sync, call `$_FORGE_BIN workspace info "$workspace_path" --porcelain 2>/dev/null`
32
+ - [ ] Check exit code: if non-zero (not indexed), skip the sync and return early
33
+ - [ ] Only proceed with background sync if exit code is 0 (workspace or ancestor is already indexed)
34
+ - [ ] Add debug logging (when `FORGE_DEBUG=true`) to indicate when sync is skipped
35
+ - [ ] Ensure the check runs silently (stderr redirect already in place)
36
+
37
+ ### Phase 3: Handle Initial Workspace Registration
38
+
39
+ - [ ] Ensure manual `forge workspace sync` still works for first-time registration
40
+ - [ ] Update shell-plugin documentation to clarify the auto-sync behavior (only syncs already-registered workspaces)
41
+ - [ ] Consider adding a one-time message when users navigate to unindexed directories suggesting `forge workspace sync`
42
+
43
+ ### Phase 4: Update Workspace List Display (Optional Enhancement)
44
+
45
+ - [ ] Enhance `on_list_workspaces()` in `crates/forge_main/src/ui.rs:3266-3311` to distinguish between exact match and ancestor match
46
+ - [ ] When current workspace is an ancestor match, display as `[Current via ancestor]` or similar
47
+ - [ ] Show the actual current directory path when it differs from the workspace path
48
+ - [ ] This provides clarity without changing core behavior
49
+
50
+ ---
51
+
52
+ ## Verification Criteria
53
+
54
+ ### Before Fix
55
+ - [ ] Run `cd /Users/username` (parent directory)
56
+ - [ ] Run `forge workspace sync` once
57
+ - [ ] Run `cd /Users/username/Documents/Projects/forge` (subdirectory)
58
+ - [ ] Trigger zsh accept-line (press Enter on any command)
59
+ - [ ] Run `forge workspace list`
60
+ - [ ] Verify: `/Users/username/Documents/Projects/forge` is incorrectly registered as a workspace
61
+
62
+ ### After Fix
63
+ - [ ] Clean database and repeat above steps
64
+ - [ ] Run `cd /Users/username/Documents/Projects/forge`
65
+ - [ ] Trigger zsh accept-line (press Enter)
66
+ - [ ] Run `forge workspace list`
67
+ - [ ] Verify: Only `/Users/username` appears (no auto-sync of subdirectory)
68
+ - [ ] Verify: List shows parent as Current (expected due to ancestor matching)
69
+ - [ ] Run explicit `forge workspace sync` in subdirectory
70
+ - [ ] Verify: Now subdirectory is registered and will auto-sync on future visits
71
+
72
+ ### Edge Cases
73
+ - [ ] Test behavior when no workspace exists at all (should not auto-sync)
74
+ - [ ] Test `forge workspace info` with `--porcelain` flag returns correct exit codes
75
+ - [ ] Test behavior in deeply nested directories (should find closest ancestor)
76
+ - [ ] Test behavior when `FORGE_SYNC_ENABLED=false` (should still respect flag)
77
+ - [ ] Test manual sync in new directory (should still work)
78
+
79
+ ---
80
+
81
+ ## Potential Risks and Mitigations
82
+
83
+ ### Risk 1: Breaking Existing User Workflows
84
+ **Mitigation:** Users who rely on automatic workspace creation will need to run `forge workspace sync` once per workspace. This is a one-time migration cost for better UX long-term.
85
+
86
+ ### Risk 2: Performance Impact
87
+ **Mitigation:** The `workspace info --porcelain` check reuses existing `get_workspace_info()` which is already fast. Negligible overhead.
88
+
89
+ ### Risk 3: Race Conditions
90
+ **Mitigation:** The check and sync are not atomic, but this is acceptable - worst case, a sync happens when it shouldn't have. No data corruption risk.
91
+
92
+ ---
93
+
94
+ ## Alternative Approaches
95
+
96
+ ### Alternative 1: Create New `is-indexed` Subcommand
97
+ Create a dedicated `forge workspace is-indexed` command instead of extending `info`.
98
+
99
+ **Pros:** Clearer intent, dedicated purpose
100
+ **Cons:** More API surface, duplicates existing functionality
101
+
102
+ ### Alternative 2: Display-Only Fix
103
+ Just improve the workspace list display to show `[Current via ancestor]` without changing auto-sync behavior.
104
+
105
+ **Pros:** Simpler implementation, no behavior changes
106
+ **Cons:** Doesn't solve the root problem of unintended workspace registrations
107
+
108
+ ### Alternative 3: Auto-Sync with Depth Limit
109
+ Only auto-sync directories within N levels of an existing workspace.
110
+
111
+ **Pros:** Prevents deep directory pollution
112
+ **Cons:** Arbitrary limit, still creates unintended workspaces
113
+
114
+ ---
115
+
116
+ ## Dependencies
117
+
118
+ - No external dependencies
119
+ - Reuses existing `get_workspace_info()` method which already handles ancestor matching
120
+ - Zsh plugin already has access to `$_FORGE_BIN` for CLI calls
121
+
122
+ ---
123
+
124
+ ## Testing Strategy
125
+
126
+ 1. **Unit Tests:** Add tests for `workspace info --porcelain` exit codes
127
+ 2. **Integration Tests:** Test zsh function behavior with mocked CLI commands
128
+ 3. **Manual Testing:** Follow verification criteria above
129
+ 4. **Regression Testing:** Ensure existing workspace info functionality unchanged
130
+
131
+ ---
132
+
133
+ ## Documentation Updates
134
+
135
+ - [ ] Update `shell-plugin/README.md` to explain auto-sync behavior
136
+ - [ ] Add note about one-time `workspace sync` requirement for new workspaces
137
+ - [ ] Document `FORGE_SYNC_ENABLED` environment variable
138
+ - [ ] Update workspace info command help text to mention `--porcelain` flag
plans/2026-03-31-slim-environment-and-add-get-config-v1.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Slim Environment & Add `get_config` to `EnvironmentInfra`
2
+
3
+ ## Objective
4
+
5
+ Reduce `Environment` to only the six fields that cannot be sourced from `ForgeConfig` — `os`, `pid`, `cwd`, `home`, `shell`, `base_path` — and expose `ForgeConfig` directly through a new `get_config` method on `EnvironmentInfra`. All service/app code that currently accesses configuration fields through `Environment` will instead call `infra.get_config()` and read the canonical `ForgeConfig` fields directly.
6
+
7
+ ---
8
+
9
+ ## Implementation Plan
10
+
11
+ ### Phase 1 — Slim `Environment` in `forge_domain`
12
+
13
+ - [ ] Task 1. **Remove all `ForgeConfig`-sourced fields from the `Environment` struct** (`crates/forge_domain/src/env.rs:54-198`).
14
+
15
+ The 37 fields below the `// --- Infrastructure-derived fields ---` comment block — including `retry_config`, `max_search_lines`, `fetch_truncation_limit`, `session`, `commit`, `suggest`, `is_restricted`, `tool_supported`, `temperature`, `top_p`, `top_k`, `max_tokens`, `max_tool_failure_per_turn`, `max_requests_per_turn`, `compact`, `updates`, and all remaining mapped fields — must be deleted. Only the six runtime-derived fields remain: `os`, `pid`, `cwd`, `home`, `shell`, `base_path`.
16
+
17
+ - [ ] Task 2. **Adjust `history_path()` to accept a `custom_path` parameter** (`crates/forge_domain/src/env.rs:267-271`).
18
+
19
+ `history_path()` currently reads `self.custom_history_path`, which originates from `ForgeConfig`. Change its signature to `pub fn history_path(&self, custom_path: Option<&PathBuf>) -> PathBuf` so callers pass the value sourced from `ForgeConfig` themselves. Update all call sites accordingly.
20
+
21
+ - [ ] Task 3. **Remove `apply_op()` from `Environment`** (`crates/forge_domain/src/env.rs:222-261`).
22
+
23
+ This method mutates `session`, `commit`, and `suggest` — all fields being removed. The mutation logic will move to the infra layer (see Phase 3). Delete `apply_op` and its `#[cfg(test)]` unit tests that exercise it via `fixture_env`.
24
+
25
+ - [ ] Task 4. **Remove the `SessionConfig` re-export from `Environment`'s imports if it becomes unused** (`crates/forge_domain/src/env.rs:10-13`).
26
+
27
+ After removing `session`, `commit`, `suggest` from `Environment`, check whether `SessionConfig`, `CommitConfig`, `SuggestConfig`, `RetryConfig`, `HttpConfig`, `MaxTokens`, `Temperature`, `TopK`, `TopP`, `Update`, and `Compact` are still needed in this file. Remove any that are now unreferenced.
28
+
29
+ - [ ] Task 5. **Remove `AutoDumpFormat` from `env.rs` if it is no longer referenced there**.
30
+
31
+ `AutoDumpFormat` was used by the `auto_dump` field being removed. If it has no other use in `env.rs`, move its definition or re-export to the appropriate module, or leave it in `forge_domain` as a standalone type — do not silently delete it if it is still needed elsewhere in the domain.
32
+
33
+ - [ ] Task 6. **Update the `fake::Dummy` derive on `Environment`**.
34
+
35
+ With only six fields, the `#[derive(fake::Dummy)]` annotation and any `#[dummy(...)]` attribute overrides on removed fields must be cleaned up. Verify the derive still compiles.
36
+
37
+ ---
38
+
39
+ ### Phase 2 — Add `type Config` and `get_config` to `EnvironmentInfra`
40
+
41
+ - [ ] Task 7. **Add the `Config` associated type to `EnvironmentInfra`** (`crates/forge_app/src/infra.rs:21-39`).
42
+
43
+ Add `type Config;` as an associated type on the trait. Every service bound written as `F: EnvironmentInfra` will need the concrete type resolved through the `type Config` mechanism, so consider whether a `where Self::Config: ...` bound is necessary at the trait level (e.g. `Clone` and `Send` if callers cache the value).
44
+
45
+ - [ ] Task 8. **Add `fn get_config(&self) -> Self::Config` to `EnvironmentInfra`**.
46
+
47
+ This method returns the full `ForgeConfig` to any consumer that holds a reference to an `EnvironmentInfra` implementor. Add it alongside the existing `get_environment`, `get_env_var`, and `get_env_vars` methods.
48
+
49
+ - [ ] Task 9. **Add `forge_config` as a direct dependency of `forge_app`** (`crates/forge_app/Cargo.toml`).
50
+
51
+ `forge_app` now references `ForgeConfig` in the trait definition. Add the path dependency if it does not already exist.
52
+
53
+ ---
54
+
55
+ ### Phase 3 — Update `ForgeEnvironmentInfra` in `forge_infra`
56
+
57
+ - [ ] Task 10. **Implement `type Config = ForgeConfig` and `get_config` on `ForgeEnvironmentInfra`** (`crates/forge_infra/src/env.rs:393-443`).
58
+
59
+ `get_config` reads from the existing `Arc<Mutex<Option<ForgeConfig>>>` cache, loading from disk on the first call — identical to how `get_environment` already works. Return the `ForgeConfig` value (cloned from cache).
60
+
61
+ - [ ] Task 11. **Simplify `to_environment()` to populate only the six runtime fields** (`crates/forge_infra/src/env.rs:120-179`).
62
+
63
+ Remove the entire `// --- ForgeConfig-mapped fields ---` section. `to_environment` should only construct `os`, `pid`, `cwd`, `home`, `shell`, and `base_path`.
64
+
65
+ - [ ] Task 12. **Delete `to_forge_config()` and all its `from_*` helper conversion functions** (`crates/forge_infra/src/env.rs:181-354`).
66
+
67
+ These were needed to round-trip `Environment ↔ ForgeConfig`. With `Environment` no longer carrying config data, the round-trip path is eliminated. Remove `to_forge_config`, `from_retry_config`, `from_http_config`, `from_tls_version`, `from_tls_backend`, `from_auto_dump_format`, `from_update_frequency`, `from_update`, and `from_compact`.
68
+
69
+ - [ ] Task 13. **Rewrite `update_environment()` to operate on `ForgeConfig` directly** (`crates/forge_infra/src/env.rs:417-442`).
70
+
71
+ The new flow:
72
+ 1. Load `ForgeConfig` from disk via `ConfigReader` (defaults + global only, as today).
73
+ 2. Apply each `ConfigOperation` directly to the `ForgeConfig` using a new free function `apply_config_op(fc: &mut ForgeConfig, op: ConfigOperation)` defined in this file.
74
+ 3. Call `fc.write()` to persist.
75
+ 4. Invalidate the cache.
76
+
77
+ The `apply_config_op` function replicates the `SetProvider`, `SetModel`, `SetCommitConfig`, and `SetSuggestConfig` mutation logic that was previously in `Environment::apply_op`, but targeting `ForgeConfig`'s `session`, `commit`, and `suggest` fields of type `Option<ModelConfig>`.
78
+
79
+ - [ ] Task 14. **Delete the round-trip identity test** (`crates/forge_infra/src/env.rs:483-509`).
80
+
81
+ The test `test_forge_config_environment_identity` verified that `fc → env → fc' → env'` preserved equality. Since `to_forge_config` is removed, this test is invalid. Remove it and add a simpler test that confirms `get_config` returns the same values as were written via `update_environment`.
82
+
83
+ - [ ] Task 15. **Implement `type Config = ForgeConfig` and `get_config` on `ForgeInfra`** (`crates/forge_infra/src/forge_infra.rs:94-113`).
84
+
85
+ Add the delegation: `fn get_config(&self) -> ForgeConfig { self.config_infra.get_config() }`. Also add `type Config = ForgeConfig;`.
86
+
87
+ - [ ] Task 16. **Update `ForgeInfra::new()` to read config through `get_config()` rather than `get_environment()`** (`crates/forge_infra/src/forge_infra.rs:58-91`).
88
+
89
+ `ForgeInfra::new()` currently calls `config_infra.get_environment()` to extract `env.parallel_file_reads` and `env.service_url` for constructing `ForgeDirectoryReaderService` and `ForgeGrpcClient`. Change these to read from `config_infra.get_config()` using the canonical `ForgeConfig` field names: `max_parallel_file_reads` and `services_url` (parsed to `Url`).
90
+
91
+ ---
92
+
93
+ ### Phase 4 — Update `MockInfra` in tests
94
+
95
+ - [ ] Task 17. **Add a `ForgeConfig` field to `MockInfra`** (`crates/forge_services/src/app_config.rs:148-278`).
96
+
97
+ `MockInfra` currently holds `Arc<Mutex<Environment>>`. Add `config: Arc<Mutex<ForgeConfig>>` alongside it, initialised from `ForgeConfig::default()` or a faked value in `MockInfra::new()`.
98
+
99
+ - [ ] Task 18. **Implement `type Config = ForgeConfig` and `get_config` on `MockInfra`**.
100
+
101
+ Return a clone of the locked `ForgeConfig` value.
102
+
103
+ - [ ] Task 19. **Rewrite `update_environment` on `MockInfra` to operate on `ForgeConfig`**.
104
+
105
+ Replace the inline `Environment`-mutation logic with the same `apply_config_op` semantics (on `ForgeConfig`), keeping `get_environment` returning a still-valid slim `Environment` built from the stored cwd/os/shell/etc.
106
+
107
+ - [ ] Task 20. **Update `MockInfra::get_environment()` to return a slim `Environment`**.
108
+
109
+ Since `Environment` now has only six fields, construct it from static test values (`os`, `pid`, `cwd`, `home`, `shell`, `base_path`) rather than from a full fake.
110
+
111
+ - [ ] Task 21. **Update test assertions in `app_config.rs` that inspect `env.session` and `env.suggest`**.
112
+
113
+ Tests currently call `fixture.get_environment()` and then inspect `env.session`. Change these assertions to call `fixture.get_config()` and inspect `config.session` on the returned `ForgeConfig`. Update the field access paths to match `ForgeConfig`'s field names (`session: Option<ModelConfig>`, `model_id: Option<String>`, etc.).
114
+
115
+ ---
116
+
117
+ ### Phase 5 — Update all service and app consumers
118
+
119
+ - [ ] Task 22. **Update `ForgeAppConfigService` to read session config via `get_config()`** (`crates/forge_services/src/app_config.rs:33-129`).
120
+
121
+ Every call to `self.infra.get_environment()` followed by `env.session`, `env.commit`, or `env.suggest` must be replaced with `self.infra.get_config()` accessing `config.session`, `config.commit`, `config.suggest` of type `Option<ModelConfig>`. Adapt the field access to `ModelConfig`'s `provider_id: Option<String>` and `model_id: Option<String>` fields.
122
+
123
+ - [ ] Task 23. **Update `forge_services/src/auth.rs` — `env.service_url` → `get_config().services_url`**.
124
+
125
+ Parse `config.services_url` to `Url` at the call site as needed.
126
+
127
+ - [ ] Task 24. **Update `forge_services/src/context_engine.rs` — `env.max_file_read_batch_size`** (two sites).
128
+
129
+ Replace with `infra.get_config().max_file_read_batch_size`.
130
+
131
+ - [ ] Task 25. **Update `forge_services/src/attachment.rs` — `env.max_search_result_bytes`**.
132
+
133
+ Replace with `infra.get_config().max_search_result_bytes`.
134
+
135
+ - [ ] Task 26. **Update `forge_app/src/tool_executor.rs` — `env.fetch_truncation_limit`**.
136
+
137
+ Replace with `infra.get_config().max_fetch_chars`.
138
+
139
+ - [ ] Task 27. **Update `forge_app/src/tool_registry.rs` — `env.tool_timeout` and `env.max_search_lines`**.
140
+
141
+ Replace with `infra.get_config().tool_timeout_secs` and `infra.get_config().max_search_lines`.
142
+
143
+ - [ ] Task 28. **Update `forge_app/src/changed_files.rs` — `env.parallel_file_reads`**.
144
+
145
+ Replace with `infra.get_config().max_parallel_file_reads`.
146
+
147
+ - [ ] Task 29. **Update `forge_app/src/app.rs` — `env.tool_supported`**.
148
+
149
+ Replace with `infra.get_config().tool_supported`.
150
+
151
+ - [ ] Task 30. **Audit all remaining `get_environment()` call sites that access now-removed fields**.
152
+
153
+ Run a codebase search for `get_environment()` followed by field access. For any that reference a field no longer on `Environment`, redirect to `get_config()`. Document any additional files not enumerated in Tasks 22–29.
154
+
155
+ - [ ] Task 31. **Add `forge_config` as a direct dependency to any crate that now imports `ForgeConfig` for field access** (e.g. `forge_services`, `forge_app`).
156
+
157
+ For each crate modified in Tasks 22–29, verify `forge_config` is listed in its `Cargo.toml` and add it if absent.
158
+
159
+ ---
160
+
161
+ ### Phase 6 — Update other infra consumers
162
+
163
+ - [ ] Task 32. **Update `ForgeHttpInfra::new()` which currently accepts `Environment`** (`crates/forge_infra/src/http.rs`).
164
+
165
+ If `ForgeHttpInfra` uses any `Environment` field that originated from `ForgeConfig` (e.g. `env.http`, `env.retry_config`), change it to accept `ForgeConfig` instead and read `config.http` and `config.retry` directly.
166
+
167
+ - [ ] Task 33. **Update `ForgeCommandExecutorService::new()` which currently accepts `Environment`** (`crates/forge_infra/src/executor.rs`).
168
+
169
+ If it reads fields like `env.tool_timeout`, `env.shell`, or `env.cwd`, split the arguments: pass the slim `Environment` for `shell`/`cwd`, and pass the relevant `ForgeConfig` fields for config-sourced values.
170
+
171
+ ---
172
+
173
+ ### Phase 7 — Remove dead code and verify
174
+
175
+ - [ ] Task 34. **Remove unused imports and dead code** across all modified files.
176
+
177
+ After removing fields from `Environment`, many imports in `forge_domain/src/env.rs`, `forge_infra/src/env.rs`, and consumer files will become unused. Clean them all up.
178
+
179
+ - [ ] Task 35. **Remove the `AutoDumpFormat`-related and other now-unused conversion helpers** from `forge_infra/src/env.rs`.
180
+
181
+ The `to_auto_dump_format`, `to_compact`, `to_update`, `to_update_frequency`, `to_session_config`, `to_tls_version`, `to_tls_backend`, `to_http_config`, and `to_retry_config` functions may all become dead code. Remove any that are no longer called.
182
+
183
+ - [ ] Task 36. **Run `cargo insta test --accept`** to verify all tests pass and update any snapshot tests affected by the structural changes.
184
+
185
+ ---
186
+
187
+ ## Verification Criteria
188
+
189
+ - `Environment` contains exactly six fields: `os`, `pid`, `cwd`, `home`, `shell`, `base_path`. All path helper methods continue to compile and behave correctly.
190
+ - `EnvironmentInfra` has `type Config;` and `fn get_config(&self) -> Self::Config` as part of its public interface.
191
+ - Every `impl EnvironmentInfra` in the codebase (production: `ForgeEnvironmentInfra`, `ForgeInfra`; test: `MockInfra`) declares `type Config = ForgeConfig`.
192
+ - No service or app code accesses a field on the return value of `get_environment()` that was formerly sourced from `ForgeConfig`; all such accesses use `get_config()` instead.
193
+ - The `to_forge_config()` function and all `from_*` conversion helpers are deleted from `forge_infra/src/env.rs`.
194
+ - `update_environment()` operates directly on `ForgeConfig` without involving `Environment` mutation.
195
+ - `apply_op()` no longer exists on `Environment`; mutation logic lives in a free function in `forge_infra/src/env.rs`.
196
+ - `cargo insta test --accept` completes without errors.
197
+ - `cargo check` produces no warnings for unused imports or dead code in any modified file.
198
+
199
+ ---
200
+
201
+ ## Potential Risks and Mitigations
202
+
203
+ 1. **`SessionConfig` vs `ModelConfig` type mismatch**
204
+ `Environment.session` was of type `Option<SessionConfig>` (domain type), while `ForgeConfig.session` is `Option<ModelConfig>` (config type). Service code that destructures `SessionConfig` will need updating to work with `ModelConfig`'s field names. Additionally, `SessionConfig` may become an orphaned domain type if nothing else uses it — audit its usages before deciding to retain or remove it.
205
+ Mitigation: Before removing `SessionConfig`, run a workspace-wide search for all usages and migrate or remove them as part of Tasks 22 and 21.
206
+
207
+ 2. **`cwd` used in `ForgeCommandExecutorService` and `ForgeHttpInfra`**
208
+ These types currently take a full `Environment` but may rely on a mix of infra fields (`cwd`, `shell`) and config fields (`tool_timeout`, `http`, `retry`). A naive split risks passing two objects where one worked before.
209
+ Mitigation: Tasks 32–33 explicitly address this. The slim `Environment` is passed for runtime fields; individual `ForgeConfig` fields are passed separately, preserving the single-responsibility boundary at each constructor.
210
+
211
+ 3. **`history_path()` signature change breaks all callers**
212
+ This is the only path helper that currently reads a `ForgeConfig` field. Changing its signature is a mechanical but wide-reaching refactor.
213
+ Mitigation: Make the change in Task 2 early, then use compiler errors to find all call sites. Each call site must source `custom_history_path` from a locally available `ForgeConfig` (obtained via `get_config()`).
214
+
215
+ 4. **Crate dependency graph expansion**
216
+ `forge_services` and `forge_app` currently have no direct dependency on `forge_config`. Adding one is architecturally significant — it means domain-level services now know about the infrastructure config format.
217
+ Mitigation: This is an explicit requirement of the task. Task 31 ensures all `Cargo.toml` files are updated consistently. If future work wants to reintroduce a domain abstraction layer, a `ForgeConfig`-to-domain mapping can be re-introduced separately.
218
+
219
+ 5. **`fake::Dummy` derive on slim `Environment` may fail**
220
+ The `fake` crate derives require all field types to implement `Dummy`. With six simple fields (`String`, `u32`, `PathBuf`, `Option<PathBuf>`), this should remain straightforward, but the `url::Url` `#[dummy(expr = ...)]` override that was on `service_url` is being removed.
221
+ Mitigation: Task 6 ensures the derive is re-validated after the structural change.
222
+
223
+ ---
224
+
225
+ ## Alternative Approaches
226
+
227
+ 1. **Keep `Environment` as-is but add `get_config()` alongside `get_environment()`**: This is the minimal additive change — no consumer code is disrupted, and new code can opt into `get_config()`. Trade-off: `Environment` remains a redundant mirror of `ForgeConfig`, the `to_environment`/`to_forge_config` round-trip complexity is preserved, and the synchronisation risk (duplicate fields drifting out of step) persists.
228
+
229
+ 2. **Embed `ForgeConfig` as a field inside `Environment` rather than replacing the fields**: `Environment` could hold `pub config: ForgeConfig` plus the six infra fields, allowing a gradual migration of callers. Trade-off: Transitional approach that still results in duplicated field access patterns and retains the `to_environment` mapping, just restructured. Cleaner than alternative 1 but more disruptive than the targeted plan above.
plans/2026-04-05-config-init-at-startup-v1.md ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Config Read at Application Init — Surface Errors, Remove Silent Defaults
2
+
3
+ ## Objective
4
+
5
+ Currently, `ForgeConfig` is read lazily from disk inside `ForgeEnvironmentInfra` and any parse/deserialization errors are silently swallowed — returning `ForgeConfig::default()` (all-zero values) with only a tracing log that the user never sees. This causes silent breakage of all tool limits and agent parameters when the user's config file is corrupt or invalid.
6
+
7
+ The goal is to:
8
+ 1. Read `ForgeConfig` **once at application startup** in `main.rs`, surfacing any parse error directly to the user before the app proceeds.
9
+ 2. Pass the pre-read config through the construction chain to every consumer.
10
+ 3. **Remove `get_config()` from the `EnvironmentInfra` trait** entirely — it is no longer needed since config is injected at construction time.
11
+ 4. Preserve the `update_environment` write path so that `forge config set` commands continue to work correctly.
12
+
13
+ ---
14
+
15
+ ## Architecture Overview
16
+
17
+ ### Current Flow (Lazy, Silent-Error)
18
+
19
+ ```
20
+ main.rs
21
+ └─ ForgeAPI::init(cwd)
22
+ └─ ForgeInfra::new(cwd)
23
+ └─ ForgeEnvironmentInfra::new(cwd) ← cache = None
24
+ └─ (first get_config() call)
25
+ └─ ForgeConfig::read() ← disk I/O
26
+ └─ Err(_) → Default::default() ← SILENT!
27
+ ```
28
+
29
+ ### Target Flow (Eager, Error-Surfaced)
30
+
31
+ ```
32
+ main.rs
33
+ └─ ForgeConfig::read()? ← fails loudly here
34
+ └─ ForgeAPI::init(cwd, config)
35
+ └─ ForgeInfra::new(cwd, config)
36
+ └─ ForgeEnvironmentInfra::new(cwd, config) ← cache = Some(config)
37
+ └─ ForgeHttpInfra::new(config, ...)
38
+ └─ ForgeDirectoryReaderService::new(config.max_parallel_file_reads)
39
+ └─ ForgeGrpcClient::new(config.services_url.parse()?)
40
+ ```
41
+
42
+ ### What `update_environment` Does (Preserved)
43
+
44
+ `ForgeEnvironmentInfra::update_environment` performs a **read-from-disk → mutate → write-to-disk → cache-invalidate** cycle whenever the user changes provider/model/reasoning settings. After invalidation, the next `get_config()` call re-reads from disk. Since `get_config()` is being removed from the trait, the internal cache mechanism in `ForgeEnvironmentInfra` must be adapted so the fresh post-write value is propagated back to all consumers that hold a stored config.
45
+
46
+ The cleanest resolution: `update_environment` returns the updated `ForgeConfig` in its result, and callers store the new value. However, since the trait is used in many places, the simplest compatible approach is to keep the cache internal to `ForgeEnvironmentInfra` but make `update_environment` callable from the services layer to update its stored config — or to have the application re-read config after an update via a narrower dedicated trait method (not `get_config()` on the general infra trait).
47
+
48
+ ---
49
+
50
+ ## Key Invariants to Preserve
51
+
52
+ 1. **`update_environment` must still work** — `forge config set model ...` must update the TOML and the in-memory state visible to subsequent calls.
53
+ 2. **`/new` conversation re-creation** — `on_new()` calls `(self.new_api)()`, rebuilding `ForgeAPI`. The captured `ForgeConfig` in the closure must be the **latest** config (post any `update_environment` calls), not the startup snapshot.
54
+ 3. **`get_config()` removal scope** — removing it from `EnvironmentInfra` does not mean removing it from every consumer; it means the consumers receive the value as a constructor argument or method parameter rather than calling `infra.get_config()` at runtime.
55
+ 4. **`services_url` panic elimination** — with config surfaced in `main.rs`, the `.expect()` on `services_url.parse()` can be converted to `?`-propagation, making `ForgeInfra::new()` fallible.
56
+
57
+ ---
58
+
59
+ ## Implementation Plan
60
+
61
+ ### Phase 1 — Surface Config Errors in `ForgeConfig::read()`
62
+
63
+ - [~] Task 1.1. **Fix silent error in `ConfigReader::read_global()`** (`crates/forge_config/src/reader.rs`): The `.required(false)` flag on `config::File::from(path)` silently swallows parse errors for malformed TOML files. Change this so that if the file *exists* but is invalid (e.g., malformed TOML, wrong types), an error is returned. Only missing files should be silently skipped. This may require checking file existence before adding the `config` source, or using a custom file reader that returns `Err` on parse failure but `Ok` on file-not-found.
64
+
65
+ - [ ] Task 1.2. **Fix silent skip in `ConfigReader::read_legacy()`** (`crates/forge_config/src/reader.rs`): Currently uses `if let Ok(content) = content { ... } else { self }` — silently ignores errors. Change to at minimum emit a `warn!` log message, or propagate the error. Since legacy JSON is a migration concern, a `warn!` is appropriate rather than a hard error.
66
+
67
+ - [ ] Task 1.3. **Verify `ForgeConfig::read()` return type** — it already returns `anyhow::Result<ForgeConfig>`. No signature change needed here. The fix is upstream in the reader chain ensuring errors actually reach the `Result::Err` variant.
68
+
69
+ ### Phase 2 — Read Config Once in `main.rs`
70
+
71
+ - [ ] Task 2.1. **Call `ForgeConfig::read()` at the top of `main()` in `crates/forge_main/src/main.rs`** before the `UI::init` call. Use `?` propagation so any error is printed to stderr and exits with a non-zero code. The error message from `anyhow` will include the cause (e.g., "invalid TOML at line 12: expected string, found integer"), which is exactly what the user needs to see.
72
+
73
+ - [ ] Task 2.2. **Thread the `config: ForgeConfig` into the `UI::init` factory closure** in `main.rs`. The closure currently captures `cwd: PathBuf`; it must now also capture `config: ForgeConfig`. Since `ForgeConfig` derives `Clone`, the closure can clone it on each invocation (once at startup, once per `/new` command).
74
+
75
+ > **Note on `/new` and config freshness**: The closure will capture the **startup config**. After a `forge config set` command, `update_environment` writes to disk and invalidates the `ForgeEnvironmentInfra` internal cache. However, since `get_config()` is being removed from the trait, the stale startup config captured in the closure would be used on the next `/new`. This must be addressed in Phase 4.
76
+
77
+ ### Phase 3 — Propagate Config Through the Construction Chain
78
+
79
+ - [ ] Task 3.1. **Change `ForgeAPI::init` signature** (`crates/forge_api/src/forge_api.rs`): Add `config: ForgeConfig` as a parameter. Forward it to `ForgeInfra::new(cwd, config)`. This is the only `impl ForgeAPI<...>` concrete method that constructs the infra stack.
80
+
81
+ - [ ] Task 3.2. **Change `ForgeInfra::new` signature** (`crates/forge_infra/src/forge_infra.rs`): Add `config: ForgeConfig` as a parameter. Remove the `config_infra.get_config()` call that currently triggers the lazy disk read. Pass `config` directly to:
82
+ - `ForgeEnvironmentInfra::new(cwd, config)` — seeds the internal cache
83
+ - `ForgeHttpInfra::new(config.clone(), ...)` — already accepts `ForgeConfig`
84
+ - `ForgeDirectoryReaderService::new(config.max_parallel_file_reads)` — already accepts the field
85
+ - `ForgeGrpcClient::new(...)` — use `?` propagation instead of `.expect()` (see Task 3.3)
86
+
87
+ Make `ForgeInfra::new` return `anyhow::Result<Self>` to allow `?`-propagation from within.
88
+
89
+ - [ ] Task 3.3. **Replace `.expect()` with `?` for `services_url` parsing** (`crates/forge_infra/src/forge_infra.rs:73-78`): Now that `ForgeInfra::new` is fallible, convert `config.services_url.parse().expect(...)` to `config.services_url.parse().context("services_url must be a valid URL")?`. This turns a panic into a clean error message at startup.
90
+
91
+ - [ ] Task 3.4. **Change `ForgeEnvironmentInfra::new` signature** (`crates/forge_infra/src/env.rs`): Add `config: ForgeConfig` parameter. Initialize `cache` as `Arc::new(Mutex::new(Some(config)))` instead of `None`. The `cached_config()` method (`env.rs:125-134`) already handles the `Some` case by returning the cached value — it will simply never need to perform a disk read for the initial config. The read-from-disk path in `read_from_disk()` becomes dead code after this change and can be removed.
92
+
93
+ - [ ] Task 3.5. **Update `ForgeAPI::init` call site in `main.rs`** to handle the new `Result<Self>` return from `ForgeInfra::new` (if propagated up through `ForgeAPI::init`). The closure passed to `UI::init` currently produces `A` (not `Result<A>`); if `ForgeAPI::init` becomes fallible, either the closure's return type changes to `Result<A>` and `on_new` handles the error, or the URL validation is done eagerly before the closure is constructed in `main.rs`.
94
+
95
+ > **Recommended resolution**: Validate `services_url` in `main.rs` by parsing it there using `config.services_url.parse::<Url>().context(...)?` before creating the closure. Pass the parsed `Url` into `ForgeInfra::new` instead of the raw string. This keeps the factory closure infallible (consistent with current `UI<A, F>` design where `F: Fn() -> A`).
96
+
97
+ ### Phase 4 — Handle Config Freshness After `update_environment`
98
+
99
+ The `/new` closure captures `ForgeConfig` at startup. After `update_environment` writes a new config and invalidates the `ForgeEnvironmentInfra` cache, the next `/new` would reconstruct `ForgeAPI` with the stale startup config. This must be addressed.
100
+
101
+ - [ ] Task 4.1. **Expose a config-accessor on the existing `API` trait** that the `UI` can use to retrieve the latest config when constructing a new API instance on `/new`. Since `ForgeEnvironmentInfra` still holds the authoritative in-memory cache (updated after `update_environment`), calling `api.get_config()` after an `update_environment` will return the fresh value. The `UI` can call `self.api.get_config()` inside `on_new` to get the latest config, then pass it to the new `ForgeAPI` factory.
102
+
103
+ Concretely: Change the factory closure stored in `new_api` from `Fn() -> A` to `Fn(ForgeConfig) -> A`. The `UI::on_new` method calls `(self.new_api)(self.api.get_config())` to forward the live config into the new API instance.
104
+
105
+ Adjust `UI<A, F>` struct and `UI::init` accordingly:
106
+ - Change the `F` bound from `Fn() -> A` to `Fn(ForgeConfig) -> A`
107
+ - Update `main.rs` closure from `move || ForgeAPI::init(cwd.clone())` to `move |config| ForgeAPI::init(cwd.clone(), config)`
108
+ - Update `on_new` to call `(self.new_api)(self.api.get_config())`
109
+
110
+ > This preserves `get_config()` on the `API` trait (not `EnvironmentInfra`) for this specific use case. The `API` trait's `get_config()` can delegate to the stored infra's cache (same as today), but the `EnvironmentInfra` trait no longer exposes it.
111
+
112
+ ### Phase 5 — Remove `get_config()` from `EnvironmentInfra` Trait
113
+
114
+ - [ ] Task 5.1. **Remove `get_config()` from the `EnvironmentInfra` trait** in `crates/forge_app/src/infra.rs`. This is the core structural change. All code that calls `infra.get_config()` through the trait must be updated to receive `ForgeConfig` through another mechanism (constructor parameter, method parameter).
115
+
116
+ - [ ] Task 5.2. **Remove `get_config()` from `ForgeInfra`** (`crates/forge_infra/src/forge_infra.rs`): The delegation to `config_infra.get_config()` is no longer needed.
117
+
118
+ - [ ] Task 5.3. **Remove `get_config()` from `ForgeRepo`** (`crates/forge_repo/src/forge_repo.rs`): Same delegation removal.
119
+
120
+ - [ ] Task 5.4. **Remove `get_config()` from `ForgeServices`** (`crates/forge_services/src/forge_services.rs`): Same delegation removal. The `AppConfigService` methods currently call `self.infra.get_config()` to read existing config fields before mutating — these should instead use the config passed through method parameters or read from the updated state returned by `update_environment`.
121
+
122
+ - [ ] Task 5.5. **Audit all call sites of `get_config()` through the `EnvironmentInfra` trait** — specifically inside `crates/forge_app/src/app.rs`, `crates/forge_app/src/agent.rs`, `crates/forge_app/src/tool_executor.rs`, `crates/forge_app/src/operation.rs`, and `crates/forge_services/src/app_config.rs`. For each call site:
123
+ - If the caller is a service method, consider passing `ForgeConfig` as a method parameter from the call site one level up.
124
+ - If the caller is a long-lived struct that currently reads config on every operation, consider storing `ForgeConfig` as a field injected at construction time.
125
+
126
+ - [ ] Task 5.6. **Update `ForgeAppConfigService::get_default_provider`, `get_provider_model`, etc.** (`crates/forge_services/src/app_config.rs`): These currently call `self.infra.get_config()` to read the current provider/model. After removing `get_config()` from `EnvironmentInfra`, the service needs another way to get the current config. Options:
127
+ - Store `ForgeConfig` in `ForgeAppConfigService` at construction time (simplest, but may go stale after `update_environment`).
128
+ - Have `update_environment` return the updated `ForgeConfig` value, letting callers store the fresh value.
129
+ - Keep a **separate, narrow trait** like `ConfigReader` with only `get_config()` on it, distinct from `EnvironmentInfra`, used only where runtime config re-reads are genuinely needed (i.e., after `update_environment` writes).
130
+
131
+ > **Recommended**: The cleanest approach given the existing architecture is to have `update_environment` return `ForgeConfig` (the new config after applying ops). Services that call `update_environment` can then update their stored config reference. This requires changing `update_environment`'s return type from `anyhow::Result<()>` to `anyhow::Result<ForgeConfig>` in the `EnvironmentInfra` trait.
132
+
133
+ ### Phase 6 — Remove `read_from_disk()` Dead Code
134
+
135
+ - [ ] Task 6.1. **Remove `read_from_disk()` from `ForgeEnvironmentInfra`** (`crates/forge_infra/src/env.rs`): With the cache always pre-seeded from the constructor, the `read_from_disk()` method and its `error!("Failed to read config file. Using default config.")` fallback are dead code. Remove both the method and the `// NOTE: This should never-happen` comment that has been lying about the real risk.
136
+
137
+ - [ ] Task 6.2. **Simplify `cached_config()`** (`crates/forge_infra/src/env.rs`): The `Mutex<Option<ForgeConfig>>` can be simplified. If the cache is always initialized via the constructor and only set to `None` by `update_environment` (which then repopulates from disk), the `Option` wrapper is still needed for the update cycle. Retain the structure but remove the `read_from_disk()` call in the `None` branch — instead call `ConfigReader::default().read_defaults().read_global().build()?` directly and propagate the error rather than swallowing it.
138
+
139
+ > **Note**: `cached_config()` currently returns `ForgeConfig` (not `Result<ForgeConfig>`). If `update_environment` invalidates the cache and the subsequent re-read can fail, `cached_config()` must return `Result<ForgeConfig>`. Propagate this change upward through `get_config()` on `ForgeEnvironmentInfra` (which may remain internal/non-trait after trait removal).
140
+
141
+ ### Phase 7 — Update Tests and Snapshots
142
+
143
+ - [ ] Task 7.1. **Update mock implementations of `EnvironmentInfra`** in test code: Any `#[cfg(test)]` or mock structs that implement `EnvironmentInfra` will need `get_config()` removed from their impl blocks after Phase 5. Search for all `impl EnvironmentInfra` in the codebase and remove the `get_config()` method bodies.
144
+
145
+ - [ ] Task 7.2. **Update tests that exercise `ForgeInfra::new` or `ForgeAPI::init`**: These now require a `ForgeConfig` argument. Construct test configs using `ForgeConfig::default()` (which provides Rust-defaulted zeroes) or `ForgeConfig::read()` where a realistic config is needed.
146
+
147
+ - [ ] Task 7.3. **Run `cargo insta test --accept`** to update any snapshot tests affected by structural changes.
148
+
149
+ - [ ] Task 7.4. **Run `cargo check`** across the workspace to surface any remaining compilation errors from the trait removal cascade.
150
+
151
+ ---
152
+
153
+ ## Verification Criteria
154
+
155
+ - Starting forge with a corrupt `~/.forge.toml` file (e.g., `forge = {invalid`) must print a clear, human-readable error message to stderr and exit with a non-zero code — not silently start with default config.
156
+ - Starting forge with a `FORGE_SERVICES_URL=not-a-url` environment variable must print a clear error and exit rather than panicking.
157
+ - `forge config set model anthropic claude-3-opus` must still work correctly — the model is updated in `~/.forge.toml` and subsequent operations use the new model.
158
+ - Starting a new conversation with `/new` after `forge config set ...` must reflect the updated config (not the startup snapshot).
159
+ - `forge env` must still display the current config.
160
+ - All existing tests pass under `cargo insta test --accept`.
161
+ - `cargo check` produces no errors across the workspace.
162
+
163
+ ---
164
+
165
+ ## Potential Risks and Mitigations
166
+
167
+ 1. **`/new` gets stale config after `update_environment`**
168
+ Mitigation: Change the factory closure type from `Fn() -> A` to `Fn(ForgeConfig) -> A` (Task 4.1). `on_new` reads the latest config from the live API before constructing the new one.
169
+
170
+ 2. **`EnvironmentInfra` implementors in external or test code break on trait change**
171
+ Mitigation: Search all `impl EnvironmentInfra` blocks across the workspace (Task 7.1) and update them. Since the trait is internal (not `pub` across crate boundaries to user code), the scope is bounded to this repository.
172
+
173
+ 3. **`update_environment` re-read from disk can fail post-startup**
174
+ Mitigation: Change `update_environment` return type to `anyhow::Result<ForgeConfig>` and propagate the error up through the service layer to the UI, which displays it as a user-visible error message rather than silently ignoring it.
175
+
176
+ 4. **`ConfigReader::read_global()` change breaks behavior for missing config file**
177
+ Mitigation: The fix in Task 1.1 must distinguish between "file does not exist" (skip silently, as today) and "file exists but is malformed" (return error). Use `path.exists()` check before deciding whether to add the source as required or not.
178
+
179
+ 5. **`ForgeInfra::new` becoming `Result`-returning cascades through `ForgeAPI::init` and the closure**
180
+ Mitigation: Pre-validate `services_url` in `main.rs` before the closure (Task 3.5, recommended approach). This keeps the factory closure return type as `A` (not `Result<A>`), preserving the `UI<A, F>` generic constraint without requiring a `UI` redesign.
181
+
182
+ 6. **`AppConfigService::get_default_provider` and similar read-config methods need config access**
183
+ Mitigation: Implement the `update_environment` → returns `ForgeConfig` approach (Task 5.6). Alternatively, store `ForgeConfig` at service construction time and update it in-place after each `update_environment` call returns. The former is cleaner.
184
+
185
+ ---
186
+
187
+ ## Alternative Approaches
188
+
189
+ 1. **Keep `get_config()` on `EnvironmentInfra` but add startup validation**: Instead of full removal, keep the lazy read but call `ForgeConfig::read()?` in `main.rs` purely for validation (discard the result), then let the infra re-read it lazily. Simpler change, but duplicates disk reads and doesn't achieve the "config piped through everywhere" goal. Does not eliminate the dead error path in `read_from_disk()`.
190
+
191
+ 2. **Wrap `ForgeConfig` in `Arc<RwLock<ForgeConfig>>`**: Rather than threading config through constructors, store a shared `Arc<RwLock<ForgeConfig>>` that all consumers read from. `update_environment` acquires the write lock and updates in place. This is a valid reactive pattern but adds lock complexity and requires write-lock acquisition on every config read, even in hot paths like `tool_executor`.
192
+
193
+ 3. **Narrow the `EnvironmentInfra` trait instead of removing `get_config()`**: Split into `EnvironmentInfra` (env vars, environment) and a separate `ConfigInfra` trait (just `get_config()` and `update_environment()`). Services that need only config use `ConfigInfra`; services that need only env use `EnvironmentInfra`. This reduces the trait surface each service depends on and is a valid design improvement, but is a larger refactor scope than strictly required.
plans/agent-context-compaction-2025-03-24-concise.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Context Compaction Implementation Plan
2
+
3
+ ## Objective
4
+ Add automatic context compaction to the `Agent` struct based on configurable triggers (token count, turn count, message count) with a specified maximum token limit.
5
+
6
+ ## Implementation Plan
7
+
8
+ ### 1. Define New Compaction Configuration Structure
9
+ Create a `Compaction` struct in `crates/forge_domain/src/agent.rs` with:
10
+ - `token_threshold`: Optional maximum token count trigger
11
+ - `turn_threshold`: Optional maximum turns trigger
12
+ - `message_threshold`: Optional maximum messages trigger
13
+ - `max_tokens`: Maximum allowed tokens after compaction
14
+ - `should_compact()` method to determine when compaction is needed
15
+
16
+ ### 2. Update Agent Struct
17
+ Add an optional `compact` field to the `Agent` struct that follows existing patterns and merging strategies.
18
+
19
+ ### 3. Implement Context Compaction Logic
20
+ Modify the `Orchestrator` implementation to:
21
+ - Add `compact_context()` method to handle the compaction process
22
+ - Update `init_agent_with_event()` to check compaction conditions before processing
23
+ - Use existing `Summarize` mechanism for the initial implementation
24
+
25
+ ### 4. Enhance Token Counting
26
+ Implement a more accurate token counting function to support compaction decisions.
27
+
28
+ ### 5. Handle Transform Deprecation
29
+ - Add deprecation notice to the `Transform::Assistant` variant
30
+ - Add warning logs when the deprecated functionality is used
31
+ - Document migration path to the new approach
32
+
33
+ ### 6. Testing Strategy
34
+ Add tests to verify:
35
+ - Compaction configuration works correctly
36
+ - Compaction triggers function as expected
37
+ - Merging behavior for the new field
38
+ - Context reduction achieves the token target
39
+
40
+ ### 7. Documentation Updates
41
+ Update documentation with examples of the new configuration:
42
+
43
+ ```yaml
44
+ # Example in forge.yaml
45
+ agents:
46
+ myAgent:
47
+ id: myAgent
48
+ model: gpt-4-turbo
49
+ system_prompt: "You are a helpful assistant."
50
+ compact:
51
+ max_tokens: 4000
52
+ token_threshold: 6000
53
+ turn_threshold: 10
54
+ message_threshold: 30
55
+ ```
56
+
57
+ ## Verification Criteria
58
+ 1. Proper implementation of `Compaction` struct with needed fields and methods
59
+ 2. `Agent` struct correctly handles the new `compact` field
60
+ 3. Automatic compaction triggers when configured conditions are met
61
+ 4. Appropriate deprecation of the transform-based approach
62
+ 5. All tests pass, verifying compaction logic works as expected
63
+ 6. Clear documentation explains how to use the new feature
64
+
65
+ ## Implementation Sequence
66
+ 1. Add `Compaction` struct to `agent.rs`
67
+ 2. Add `compact` field to `Agent` struct
68
+ 3. Implement orchestrator compaction methods
69
+ 4. Add token counting enhancement
70
+ 5. Deprecate the transform-based approach
71
+ 6. Add tests for the new functionality
72
+ 7. Update documentation and examples
plans/agent-context-compaction-2025-03-24.md ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Context Compaction Implementation Plan
2
+
3
+ ## Objective
4
+
5
+ Add a new field to the `Agent` struct that enables automatic context compaction based on configurable triggers (token count, turn count, message count) and a specified maximum token limit.
6
+
7
+ ## Implementation Plan
8
+
9
+ ### 1. Define a New Context Compaction Configuration Structure
10
+
11
+ Create a new struct `Compaction` in `crates/forge_domain/src/agent.rs` that will hold the configuration options for context compaction:
12
+
13
+ ```rust
14
+ /// Configuration for automatic context compaction
15
+ #[derive(Debug, Clone, Serialize, Deserialize, Setters)]
16
+ #[setters(strip_option, into)]
17
+ pub struct Compaction {
18
+ /// Maximum token count before compaction is triggered
19
+ /// When the context exceeds this token count, compaction will be applied
20
+ #[serde(skip_serializing_if = "Option::is_none")]
21
+ pub token_threshold: Option<usize>,
22
+
23
+ /// Maximum number of turns before compaction is triggered
24
+ /// After this many conversation turns, compaction will be applied
25
+ #[serde(skip_serializing_if = "Option::is_none")]
26
+ pub turn_threshold: Option<usize>,
27
+
28
+ /// Maximum number of messages before compaction is triggered
29
+ /// After this many messages in the context, compaction will be applied
30
+ #[serde(skip_serializing_if = "Option::is_none")]
31
+ pub message_threshold: Option<usize>,
32
+
33
+ /// Maximum allowed token count after compaction
34
+ /// The compaction process will reduce the context to approximately this token count
35
+ pub max_tokens: usize,
36
+ }
37
+
38
+ impl Compaction {
39
+ /// Creates a new context compaction configuration with the specified maximum token limit
40
+ pub fn new(max_tokens: usize) -> Self {
41
+ Self {
42
+ token_threshold: None,
43
+ turn_threshold: None,
44
+ message_threshold: None,
45
+ max_tokens,
46
+ }
47
+ }
48
+
49
+ /// Determines if compaction should be triggered based on the current context state
50
+ pub fn should_compact(
51
+ &self,
52
+ context: &Context,
53
+ turn_count: u64,
54
+ message_count: usize
55
+ ) -> bool {
56
+ // Check token threshold
57
+ if let Some(token_threshold) = self.token_threshold {
58
+ let current_tokens = token_count(&context.to_text());
59
+ if current_tokens > token_threshold {
60
+ return true;
61
+ }
62
+ }
63
+
64
+ // Check turn threshold
65
+ if let Some(turn_threshold) = self.turn_threshold {
66
+ if turn_count >= turn_threshold as u64 {
67
+ return true;
68
+ }
69
+ }
70
+
71
+ // Check message threshold
72
+ if let Some(message_threshold) = self.message_threshold {
73
+ if message_count > message_threshold {
74
+ return true;
75
+ }
76
+ }
77
+
78
+ false
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### 2. Update the Agent Struct
84
+
85
+ Add the new `compact` field to the `Agent` struct in `crates/forge_domain/src/agent.rs`:
86
+
87
+ ```rust
88
+ #[derive(Debug, Clone, Serialize, Deserialize, Merge, Setters)]
89
+ #[setters(strip_option, into)]
90
+ pub struct Agent {
91
+ // Existing fields...
92
+
93
+ /// Configuration for automatic context compaction
94
+ /// When provided, enables automatic context management based on specified triggers
95
+ #[serde(skip_serializing_if = "Option::is_none")]
96
+ #[merge(strategy = crate::merge::option)]
97
+ pub compact: Option<Compaction>,
98
+
99
+ // Other existing fields...
100
+ }
101
+ ```
102
+
103
+ ### 3. Implement Context Compaction Logic
104
+
105
+ Modify the `Orchestrator` implementation in `crates/forge_domain/src/orch.rs` to apply context compaction automatically:
106
+
107
+ ```rust
108
+ impl<A: App> Orchestrator<A> {
109
+ // Add a new method to compact context
110
+ async fn compact_context(
111
+ &self,
112
+ agent: &Agent,
113
+ context: &mut Context
114
+ ) -> anyhow::Result<()> {
115
+ if let Some(config) = &agent.compact {
116
+ let max_tokens = config.max_tokens;
117
+
118
+ // Use the existing Summarize mechanism
119
+ let mut summarize = Summarize::new(context, max_tokens);
120
+ while let Some(mut summary) = summarize.summarize() {
121
+ // Get the content to summarize
122
+ let content_to_summarize = summary.get();
123
+
124
+ // TODO: In a future enhancement, we could use an AI model to generate better summaries
125
+ // For now, use a simple placeholder that the existing code will handle
126
+ summary.set("Summary of previous conversation");
127
+ }
128
+ }
129
+ Ok(())
130
+ }
131
+
132
+ // Modify the init_agent_with_event method to check for compaction
133
+ async fn init_agent_with_event(&self, agent_id: &AgentId, event: &Event) -> anyhow::Result<()> {
134
+ // Existing code...
135
+
136
+ // Before sending the context to the provider, check if compaction is needed
137
+ if let Some(config) = &agent.compact {
138
+ let turn_count = self.conversation.read().await.turn_count(&agent.id).unwrap_or(0);
139
+ let message_count = context.messages.len();
140
+
141
+ if config.should_compact(&context, turn_count, message_count) {
142
+ self.compact_context(agent, &mut context).await?;
143
+ self.set_context(&agent.id, context.clone()).await?;
144
+ }
145
+ }
146
+
147
+ // Continue with existing code...
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### 4. Update the Summarize Implementation
153
+
154
+ Enhance the `Summarize` implementation in `crates/forge_domain/src/summarize.rs` to better handle context compaction:
155
+
156
+ ```rust
157
+ // Add support for better summarization metrics
158
+ fn token_count(text: &str) -> usize {
159
+ // This is a placeholder for a more accurate token counting function
160
+ // In a production implementation, this should use a proper tokenizer
161
+ text.split_whitespace().count() * 75 / 100
162
+ }
163
+ ```
164
+
165
+ ### 5. Update or Deprecate the Transform::Assistant Variant
166
+
167
+ Since we're dropping the transform-based approach, we need to handle the transition:
168
+
169
+ 1. Add a deprecation comment to the `Transform::Assistant` variant:
170
+ ```rust
171
+ #[derive(Debug, Clone, Serialize, Deserialize)]
172
+ #[serde(tag = "type", rename_all = "snake_case")]
173
+ pub enum Transform {
174
+ /// Compresses multiple assistant messages into a single message
175
+ ///
176
+ /// DEPRECATED: Use the new `compact` field on the Agent struct instead.
177
+ #[deprecated(
178
+ since = "next_version",
179
+ note = "Use the compact field on Agent instead"
180
+ )]
181
+ Assistant {
182
+ // existing fields...
183
+ },
184
+
185
+ // Other variants...
186
+ }
187
+ ```
188
+
189
+ 2. Add logging in the `execute_transform` method to warn about deprecated usage:
190
+ ```rust
191
+ async fn execute_transform(
192
+ &self,
193
+ transforms: &[Transform],
194
+ mut context: Context,
195
+ ) -> anyhow::Result<Context> {
196
+ for transform in transforms.iter() {
197
+ match transform {
198
+ Transform::Assistant { .. } => {
199
+ tracing::warn!(
200
+ "Transform::Assistant is deprecated. Use the compact field on Agent instead."
201
+ );
202
+ // Existing implementation...
203
+ },
204
+ // Other variants...
205
+ }
206
+ }
207
+ // Rest of the method...
208
+ }
209
+ ```
210
+
211
+ ### 6. Add Tests
212
+
213
+ Add new tests in `crates/forge_domain/src/agent.rs`:
214
+
215
+ ```rust
216
+ #[cfg(test)]
217
+ mod compact_tests {
218
+ use pretty_assertions::assert_eq;
219
+ use super::*;
220
+
221
+ #[test]
222
+ fn compact_config() {
223
+ let config = Compaction::new(1000)
224
+ .token_threshold(2000)
225
+ .turn_threshold(5)
226
+ .message_threshold(20);
227
+
228
+ assert_eq!(config.max_tokens, 1000);
229
+ assert_eq!(config.token_threshold, Some(2000));
230
+ assert_eq!(config.turn_threshold, Some(5));
231
+ assert_eq!(config.message_threshold, Some(20));
232
+ }
233
+
234
+ #[test]
235
+ fn test_should_compact() {
236
+ let config = Compaction::new(1000)
237
+ .token_threshold(2000)
238
+ .turn_threshold(5);
239
+
240
+ // Mock context with token count > threshold
241
+ let mut context = Context::default();
242
+ for _ in 0..100 {
243
+ context = context.add_message(ContextMessage::user("Long message that would exceed the token threshold"));
244
+ }
245
+
246
+ // Should compact due to token threshold
247
+ assert!(config.should_compact(&context, 1, 100));
248
+
249
+ // Should compact due to turn threshold
250
+ assert!(config.should_compact(&context, 5, 100));
251
+
252
+ // Should not compact for low values
253
+ let config = Compaction::new(1000)
254
+ .token_threshold(100000) // Very high threshold
255
+ .turn_threshold(100); // Very high threshold
256
+
257
+ assert!(!config.should_compact(&context, 1, 100));
258
+ }
259
+
260
+ #[test]
261
+ fn compact() {
262
+ let config1 = Compaction::new(1000).token_threshold(2000);
263
+ let config2 = Compaction::new(1500).turn_threshold(3);
264
+
265
+ // Base has no value, should take other's value
266
+ let mut base = Agent::new("Base"); // No compact set
267
+ let other = Agent::new("Other").compact(config2.clone());
268
+ base.merge(other);
269
+ assert_eq!(base.compact.as_ref().unwrap().max_tokens, 1500);
270
+ assert_eq!(base.compact.as_ref().unwrap().turn_threshold, Some(3));
271
+
272
+ // Base has a value, should be overwritten
273
+ let mut base = Agent::new("Base").compact(config1.clone());
274
+ let other = Agent::new("Other").compact(config2.clone());
275
+ base.merge(other);
276
+ assert_eq!(base.compact.as_ref().unwrap().max_tokens, 1500);
277
+ assert_eq!(base.compact.as_ref().unwrap().turn_threshold, Some(3));
278
+ assert_eq!(base.compact.as_ref().unwrap().token_threshold, None);
279
+
280
+ // Other has no value, should keep base's value
281
+ let mut base = Agent::new("Base").compact(config1.clone());
282
+ let other = Agent::new("Other"); // No compact set
283
+ base.merge(other);
284
+ assert_eq!(base.compact.as_ref().unwrap().max_tokens, 1000);
285
+ assert_eq!(base.compact.as_ref().unwrap().token_threshold, Some(2000));
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### 7. Update Documentation and Examples
291
+
292
+ Add examples in documentation and configuration files:
293
+
294
+ ```yaml
295
+ # Example in forge.yaml
296
+ agents:
297
+ myAgent:
298
+ id: myAgent
299
+ model: gpt-4-turbo
300
+ system_prompt: "You are a helpful assistant."
301
+ compact:
302
+ max_tokens: 4000
303
+ token_threshold: 6000
304
+ turn_threshold: 10
305
+ message_threshold: 30
306
+ ```
307
+
308
+ ## Verification Criteria
309
+
310
+ 1. The `Compaction` struct should be correctly defined with all required fields and methods
311
+ 2. The `Agent` struct should have a new `compact` field that follows the existing patterns
312
+ 3. The orchestrator should automatically apply context compaction when the configured conditions are met
313
+ 4. The transform-based approach should be properly deprecated with warnings
314
+ 5. All tests should pass, demonstrating that the compaction logic works as expected
315
+ 6. Documentation and examples should clearly explain how to use the new feature
316
+
317
+ ## Implementation Steps
318
+
319
+ 1. Add the `Compaction` struct to `agent.rs`
320
+ 2. Add the `compact` field to the `Agent` struct
321
+ 3. Implement the `compact_context` method in the Orchestrator
322
+ 4. Update the `init_agent_with_event` method to check for compaction
323
+ 5. Enhance the token counting mechanism
324
+ 6. Deprecate the `Transform::Assistant` variant
325
+ 7. Add tests for the new functionality
326
+ 8. Update documentation and examples
327
+
328
+ This implementation provides a flexible and automatic way to manage context compaction based on various triggers, replacing the more complex transform-based approach.
plans/agent-context-compaction-2025-03-25-concise.md ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Context Compaction Logic Update Plan (Revised)
2
+
3
+ ## Objective
4
+
5
+ Modify the context compaction logic in `crates/forge_domain/src/compaction.rs` to identify and compress only one continuous sequence of assistant messages at a time, rather than processing all sequences at once or compressing the entire context.
6
+
7
+ ## Current Implementation Analysis
8
+
9
+ The current implementation in `compaction.rs` performs compaction on the entire context as a single unit:
10
+
11
+ 1. It checks if compaction is needed via `should_perform_compaction`
12
+ 2. If needed, it generates a summary of the entire context
13
+ 3. It then builds a new compacted context with just the summary
14
+
15
+ This approach doesn't distinguish between different message roles or sequences, treating all messages equally in the compaction process.
16
+
17
+ ## Revised Implementation Plan
18
+
19
+ ### 1. Identify Single Compressible Sequence
20
+
21
+ Add functionality to identify only the first sequence of assistant messages that qualify for compression:
22
+
23
+ ```rust
24
+ /// Identifies the first sequence of assistant messages that can be compressed (2+ consecutive messages)
25
+ fn identify_first_compressible_sequence(&self, context: &Context) -> Option<(usize, usize)> {
26
+ let messages = context.messages();
27
+ let mut current_sequence_start: Option<usize> = None;
28
+
29
+ for (i, message) in messages.iter().enumerate() {
30
+ if message.is_assistant() {
31
+ // Start a new sequence or continue current one
32
+ if current_sequence_start.is_none() {
33
+ current_sequence_start = Some(i);
34
+ }
35
+ } else {
36
+ // End of a potential sequence
37
+ if let Some(start) = current_sequence_start {
38
+ // Only compress sequences with more than 1 assistant message
39
+ if i - start > 1 {
40
+ return Some((start, i - 1));
41
+ }
42
+ current_sequence_start = None;
43
+ }
44
+ }
45
+ }
46
+
47
+ // Check for a sequence at the end
48
+ if let Some(start) = current_sequence_start {
49
+ let end = messages.len() - 1;
50
+ if end - start > 0 { // More than 1 message
51
+ return Some((start, end));
52
+ }
53
+ }
54
+
55
+ None // No compressible sequence found
56
+ }
57
+ ```
58
+
59
+ ### 2. Update Main Compaction Method
60
+
61
+ Modify the main compaction method to identify and compress just one sequence:
62
+
63
+ ```rust
64
+ pub async fn compact_context(&self, agent: &Agent, context: Context) -> Result<Context> {
65
+ if !self.should_perform_compaction(agent, &context) {
66
+ return Ok(context);
67
+ }
68
+
69
+ debug!(
70
+ agent_id = %agent.id,
71
+ "Context compaction triggered"
72
+ );
73
+
74
+ // Identify the first compressible sequence
75
+ if let Some(sequence) = self.identify_first_compressible_sequence(&context) {
76
+ debug!(
77
+ agent_id = %agent.id,
78
+ sequence_start = sequence.0,
79
+ sequence_end = sequence.1,
80
+ "Compressing assistant message sequence"
81
+ );
82
+
83
+ // Compress just this sequence
84
+ self.compress_single_sequence(agent, context, sequence).await
85
+ } else {
86
+ debug!(agent_id = %agent.id, "No compressible sequences found");
87
+ Ok(context)
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 3. Implement Single Sequence Compression
93
+
94
+ Create a method to handle the compression of a single identified sequence:
95
+
96
+ ```rust
97
+ async fn compress_single_sequence(
98
+ &self,
99
+ agent: &Agent,
100
+ original_context: Context,
101
+ sequence: (usize, usize)
102
+ ) -> Result<Context> {
103
+ let messages = original_context.messages();
104
+ let (start, end) = sequence;
105
+
106
+ // Extract the sequence to summarize
107
+ let sequence_messages = &messages[start..=end];
108
+
109
+ // Generate summary for this sequence
110
+ let summary = self.generate_summary_for_sequence(agent, sequence_messages).await?;
111
+
112
+ // Build a new context with the sequence replaced by the summary
113
+ let mut compacted_messages = Vec::new();
114
+
115
+ // Add messages before the sequence
116
+ compacted_messages.extend(messages[0..start].to_vec());
117
+
118
+ // Add the summary as a single assistant message
119
+ compacted_messages.push(ContextMessage::assistant(summary, None));
120
+
121
+ // Add messages after the sequence
122
+ if end + 1 < messages.len() {
123
+ compacted_messages.extend(messages[end+1..].to_vec());
124
+ }
125
+
126
+ // Build the new context
127
+ let mut compacted_context = Context::default();
128
+
129
+ // Add system message if present in original context
130
+ if let Some(system_msg) = original_context.system_message() {
131
+ compacted_context = compacted_context.set_first_system_message(system_msg.clone());
132
+ }
133
+
134
+ // Add all the processed messages
135
+ for msg in compacted_messages {
136
+ compacted_context = compacted_context.add_message(msg);
137
+ }
138
+
139
+ Ok(compacted_context)
140
+ }
141
+ ```
142
+
143
+ ### 4. Implement Sequence-Based Summary Generation
144
+
145
+ Create a method to generate summaries for a specific sequence:
146
+
147
+ ```rust
148
+ async fn generate_summary_for_sequence(
149
+ &self,
150
+ agent: &Agent,
151
+ messages: &[ContextMessage]
152
+ ) -> Result<String> {
153
+ let compact = agent.compact.as_ref().unwrap();
154
+
155
+ // Create a temporary context with just the sequence for summarization
156
+ let mut sequence_context = Context::default();
157
+ for msg in messages {
158
+ sequence_context = sequence_context.add_message(msg.clone());
159
+ }
160
+
161
+ // Render the summarization prompt
162
+ let prompt = self
163
+ .services
164
+ .template_service()
165
+ .render_summarization(agent, &sequence_context)
166
+ .await?;
167
+
168
+ let message = ContextMessage::user(prompt);
169
+ let summary_context = Context::default().add_message(message);
170
+
171
+ // Get summary from the provider
172
+ let response = self
173
+ .services
174
+ .provider_service()
175
+ .chat(&compact.model, summary_context)
176
+ .await?;
177
+
178
+ self.collect_completion_stream_content(response).await
179
+ }
180
+ ```
181
+
182
+ ### 5. Update Helper Methods
183
+
184
+ The `should_perform_compaction` method remains unchanged since it's still applicable. The `collect_completion_stream_content` also remains as is.
185
+
186
+ ### 6. Remove Unused Methods
187
+
188
+ The original `generate_summary` and `build_compacted_context` methods will be replaced by our new sequence-based methods, so they can be removed or repurposed.
189
+
190
+ ## Implementation Considerations
191
+
192
+ 1. **Processing Only One Sequence**: This approach only compresses one sequence at a time, which means that if there are multiple compressible sequences, only the first one will be compressed in a single call to `compact_context`.
193
+
194
+ 2. **Repeated Compaction**: If desired, the caller can repeatedly call `compact_context` to compress additional sequences over multiple iterations.
195
+
196
+ 3. **Processing Order**: Sequences are identified from the beginning of the context, so the first eligible sequence found will be compressed first.
197
+
198
+ 4. **Message Order Preservation**: This approach preserves the order of all non-compressed messages, maintaining the conversation flow.
199
+
200
+ ## Verification Criteria
201
+
202
+ 1. **Correctness**: The compacted context should:
203
+ - Preserve all user messages in their original positions
204
+ - Replace only the first sequence of 2+ assistant messages with a summary
205
+ - Keep single assistant messages unchanged
206
+ - Keep other sequences of assistant messages unchanged (they are not processed in this call)
207
+
208
+ 2. **Functionality**:
209
+ - The compaction should trigger under the same conditions as before
210
+ - Summary generation should work properly for the identified sequence
211
+ - The context structure should be preserved
212
+
213
+ 3. **Edge Cases**:
214
+ - Empty contexts should be handled properly
215
+ - Contexts with no compressible sequences should be returned unchanged
216
+ - Boundary conditions (sequence at start/end of context) should work correctly
217
+
218
+ 4. **Performance**:
219
+ - This approach should be more efficient as it only processes one sequence per call
220
+ - Only one API call for summarization is made per call to `compact_context`
plans/agent-context-compaction-2025-03-25.md ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Context Compaction Logic Update Plan
2
+
3
+ ## Objective
4
+
5
+ Modify the context compaction logic in `crates/forge_domain/src/compaction.rs` to identify and compress only continuous sequences of assistant messages, rather than compressing the entire context. This will provide more targeted and effective context compression while preserving the structure of conversations.
6
+
7
+ ## Current Implementation Analysis
8
+
9
+ The current implementation in `compaction.rs` performs compaction on the entire context as a single unit:
10
+
11
+ 1. It checks if compaction is needed via `should_perform_compaction`
12
+ 2. If needed, it generates a summary of the entire context
13
+ 3. It then builds a new compacted context with just the summary
14
+
15
+ This approach doesn't distinguish between different message roles or sequences, treating all messages equally in the compaction process.
16
+
17
+ ## Implementation Plan
18
+
19
+ ### 1. Identify Compressible Sequences
20
+
21
+ Add functionality to identify sequences of assistant messages that qualify for compression:
22
+
23
+ ```rust
24
+ /// Identifies sequences of assistant messages that can be compressed (2+ consecutive messages)
25
+ fn identify_compressible_sequences(&self, context: &Context) -> Vec<(usize, usize)> {
26
+ let messages = context.messages();
27
+ let mut sequences = Vec::new();
28
+ let mut current_sequence_start: Option<usize> = None;
29
+
30
+ for (i, message) in messages.iter().enumerate() {
31
+ if message.is_assistant() {
32
+ // Start a new sequence or continue current one
33
+ if current_sequence_start.is_none() {
34
+ current_sequence_start = Some(i);
35
+ }
36
+ } else {
37
+ // End of a potential sequence
38
+ if let Some(start) = current_sequence_start {
39
+ // Only compress sequences with more than 1 assistant message
40
+ if i - start > 1 {
41
+ sequences.push((start, i - 1));
42
+ }
43
+ current_sequence_start = None;
44
+ }
45
+ }
46
+ }
47
+
48
+ // Check for a sequence at the end
49
+ if let Some(start) = current_sequence_start {
50
+ let end = messages.len() - 1;
51
+ if end - start > 0 { // More than 1 message
52
+ sequences.push((start, end));
53
+ }
54
+ }
55
+
56
+ sequences
57
+ }
58
+ ```
59
+
60
+ ### 2. Update Main Compaction Method
61
+
62
+ Modify the main compaction method to use the sequence identification:
63
+
64
+ ```rust
65
+ pub async fn compact_context(&self, agent: &Agent, context: Context) -> Result<Context> {
66
+ if !self.should_perform_compaction(agent, &context) {
67
+ return Ok(context);
68
+ }
69
+
70
+ debug!(
71
+ agent_id = %agent.id,
72
+ "Context compaction triggered"
73
+ );
74
+
75
+ // Identify compressible sequences
76
+ let sequences = self.identify_compressible_sequences(&context);
77
+
78
+ if sequences.is_empty() {
79
+ debug!(agent_id = %agent.id, "No compressible sequences found");
80
+ return Ok(context);
81
+ }
82
+
83
+ // Process the compressible sequences and build new context
84
+ let compacted_context = self.compress_context_sequences(agent, context, sequences).await?;
85
+
86
+ Ok(compacted_context)
87
+ }
88
+ ```
89
+
90
+ ### 3. Implement Sequence Compression
91
+
92
+ Create a method to handle the compression of identified sequences:
93
+
94
+ ```rust
95
+ async fn compress_context_sequences(
96
+ &self,
97
+ agent: &Agent,
98
+ original_context: Context,
99
+ sequences: Vec<(usize, usize)>
100
+ ) -> Result<Context> {
101
+ let messages = original_context.messages();
102
+ let mut compacted_messages = Vec::new();
103
+
104
+ let mut next_index = 0;
105
+
106
+ // Process each sequence
107
+ for (start, end) in sequences {
108
+ // Add any messages before this sequence
109
+ compacted_messages.extend(messages[next_index..start].to_vec());
110
+
111
+ // Extract the sequence to summarize
112
+ let sequence = &messages[start..=end];
113
+
114
+ // Only process if we have multiple assistant messages (safety check)
115
+ if sequence.len() > 1 && sequence.iter().all(|m| m.is_assistant()) {
116
+ // Generate summary for this sequence
117
+ let summary = self.generate_summary_for_sequence(agent, sequence).await?;
118
+
119
+ // Add the summary as a single assistant message
120
+ compacted_messages.push(ContextMessage::assistant(summary, None));
121
+ } else {
122
+ // If not eligible for compression, keep original messages
123
+ compacted_messages.extend(sequence.to_vec());
124
+ }
125
+
126
+ next_index = end + 1;
127
+ }
128
+
129
+ // Add any remaining messages
130
+ if next_index < messages.len() {
131
+ compacted_messages.extend(messages[next_index..].to_vec());
132
+ }
133
+
134
+ // Build the new context
135
+ let mut compacted_context = Context::default();
136
+
137
+ // Add system message if present in original context
138
+ if let Some(system_msg) = original_context.system_message() {
139
+ compacted_context = compacted_context.set_first_system_message(system_msg.clone());
140
+ }
141
+
142
+ // Add all the processed messages
143
+ for msg in compacted_messages {
144
+ compacted_context = compacted_context.add_message(msg);
145
+ }
146
+
147
+ Ok(compacted_context)
148
+ }
149
+ ```
150
+
151
+ ### 4. Update Summary Generation
152
+
153
+ Create a method to generate summaries for specific sequences:
154
+
155
+ ```rust
156
+ async fn generate_summary_for_sequence(
157
+ &self,
158
+ agent: &Agent,
159
+ messages: &[ContextMessage]
160
+ ) -> Result<String> {
161
+ let compact = agent.compact.as_ref().unwrap();
162
+
163
+ // Create a temporary context with just the sequence for summarization
164
+ let mut sequence_context = Context::default();
165
+ for msg in messages {
166
+ sequence_context = sequence_context.add_message(msg.clone());
167
+ }
168
+
169
+ // Render the summarization prompt
170
+ let prompt = self
171
+ .services
172
+ .template_service()
173
+ .render_summarization(agent, &sequence_context)
174
+ .await?;
175
+
176
+ let message = ContextMessage::user(prompt);
177
+ let summary_context = Context::default().add_message(message);
178
+
179
+ // Get summary from the provider
180
+ let response = self
181
+ .services
182
+ .provider_service()
183
+ .chat(&compact.model, summary_context)
184
+ .await?;
185
+
186
+ self.collect_completion_stream_content(response).await
187
+ }
188
+ ```
189
+
190
+ ### 5. Update Helper Methods
191
+
192
+ The `should_perform_compaction` method remains unchanged since it's still applicable.
193
+
194
+ ### 6. Remove Unused Methods
195
+
196
+ The original `generate_summary` and `build_compacted_context` methods will be replaced by our new sequence-based methods, so they can be removed or repurposed.
197
+
198
+ ## Verification Criteria
199
+
200
+ 1. **Correctness**: The compacted context should:
201
+ - Preserve all user messages in their original positions
202
+ - Replace only sequences of 2+ assistant messages with summaries
203
+ - Keep single assistant messages unchanged
204
+
205
+ 2. **Functionality**:
206
+ - The compaction should trigger under the same conditions as before
207
+ - Summary generation should work properly for each sequence
208
+ - The context structure should be preserved
209
+
210
+ 3. **Edge Cases**:
211
+ - Empty contexts should be handled properly
212
+ - Contexts with no compressible sequences should be returned unchanged
213
+ - Boundary conditions (sequences at start/end of context) should work correctly
214
+
215
+ 4. **Performance**:
216
+ - Any performance impact should be minimal, especially for large contexts
217
+ - The number of API calls for summarization should be proportional to the number of compressible sequences
renovate.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ],
6
+ "automerge": true,
7
+ "platformAutomerge": true
8
+ }
rust-analyzer.toml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [workspace]
2
+ # Enable proc-macro support
3
+ procMacro.enable = true
4
+ procMacro.ignored = {}
5
+
6
+ # Improve macro expansion
7
+ cargo.buildScripts.enable = true
8
+ cargo.buildScripts.invocationStrategy = "per_workspace"
9
+ cargo.buildScripts.invocationLocation = "workspace"
10
+
11
+ # Better error handling for macros
12
+ diagnostics.disabled = []
13
+ diagnostics.warningsAsHint = []
14
+
15
+ # Improve performance with large workspaces
16
+ cargo.allTargets = false
17
+ cargo.target = null
18
+
19
+ # Enable all features for better analysis
20
+ cargo.allFeatures = true
21
+
22
+ # Improve macro expansion limits
23
+ expand.limit = 512
rust-toolchain.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [toolchain]
2
+ channel = "1.97"
3
+ profile = "default"
shell-plugin/forge.theme.zsh ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env zsh
2
+
3
+ # Enable prompt substitution for RPROMPT
4
+ setopt PROMPT_SUBST
5
+
6
+ # Model and agent info with token count
7
+ # Fully formatted output directly from Rust
8
+ # Returns ZSH-formatted string ready for use in RPROMPT
9
+ function _forge_prompt_info() {
10
+ local forge_bin="${_FORGE_BIN:-${FORGE_BIN:-forge}}"
11
+
12
+ # Get fully formatted prompt from forge (single command).
13
+ # Pass session model/provider as CLI flags when set so the rprompt
14
+ # reflects the active session override rather than global config.
15
+ local -a forge_cmd
16
+ forge_cmd=("$forge_bin")
17
+ forge_cmd+=(zsh rprompt)
18
+ [[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL"
19
+ [[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER"
20
+ [[ -n "$_FORGE_SESSION_REASONING_EFFORT" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING_EFFORT"
21
+ _FORGE_CONVERSATION_ID=$_FORGE_CONVERSATION_ID _FORGE_ACTIVE_AGENT=$_FORGE_ACTIVE_AGENT COLUMNS=$COLUMNS "${forge_cmd[@]}" 2>/dev/null
22
+ }
23
+
24
+ # Right prompt: agent and model with token count (uses single forge prompt command)
25
+ # Set RPROMPT if empty, otherwise append to existing value
26
+ if [[ -z "$_FORGE_THEME_LOADED" ]]; then
27
+ RPROMPT='$(_forge_prompt_info)'"${RPROMPT:+ ${RPROMPT}}"
28
+ fi
vertex.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "google/gemini-3.5-flash",
4
+ "name": "Gemini 3.5 Flash",
5
+ "description": "",
6
+ "context_length": 1048576,
7
+ "tools_supported": true,
8
+ "supports_parallel_tool_calls": true,
9
+ "supports_reasoning": true,
10
+ "input_modalities": ["text", "image"]
11
+ },
12
+ {
13
+ "id": "mistral/mistral-large-2411",
14
+ "name": "Mistral Large 2411",
15
+ "description": "Mistral's powerful large language model",
16
+ "context_length": 128000,
17
+ "tools_supported": true,
18
+ "supports_parallel_tool_calls": true,
19
+ "supports_reasoning": false,
20
+ "input_modalities": ["text"]
21
+ },
22
+ {
23
+ "id": "mistral/mistral-small-2503",
24
+ "name": "Mistral Small 2503",
25
+ "description": "Mistral's efficient small language model",
26
+ "context_length": 128000,
27
+ "tools_supported": true,
28
+ "supports_parallel_tool_calls": true,
29
+ "supports_reasoning": false,
30
+ "input_modalities": ["text"]
31
+ },
32
+ {
33
+ "id": "mistral/codestral-2501",
34
+ "name": "Codestral 2501",
35
+ "description": "Mistral's code-specialized model",
36
+ "context_length": 256000,
37
+ "tools_supported": true,
38
+ "supports_parallel_tool_calls": true,
39
+ "supports_reasoning": false,
40
+ "input_modalities": ["text"]
41
+ },
42
+ {
43
+ "id": "meta/llama-4-maverick-17b-128e-instruct-maas",
44
+ "name": "Llama 4 Maverick 17B",
45
+ "description": "Meta's large instruction-tuned model with extended context",
46
+ "context_length": 1048576,
47
+ "tools_supported": true,
48
+ "supports_parallel_tool_calls": true,
49
+ "supports_reasoning": false,
50
+ "input_modalities": ["text"]
51
+ },
52
+ {
53
+ "id": "meta/llama-4-scout-17b-16e-instruct-maas",
54
+ "name": "Llama 4 Scout 17B",
55
+ "description": "Meta's efficient instruction-tuned model with massive context",
56
+ "context_length": 10485760,
57
+ "tools_supported": true,
58
+ "supports_parallel_tool_calls": true,
59
+ "supports_reasoning": false,
60
+ "input_modalities": ["text"]
61
+ },
62
+ {
63
+ "id": "google/gemini-2.0-flash-001",
64
+ "name": "Gemini 2.0 Flash 001",
65
+ "description": "Google's fast and capable multimodal model",
66
+ "context_length": 1048576,
67
+ "tools_supported": true,
68
+ "supports_parallel_tool_calls": true,
69
+ "supports_reasoning": true,
70
+ "input_modalities": ["text", "image"]
71
+ },
72
+ {
73
+ "id": "google/gemini-2.5-pro",
74
+ "name": "Gemini 2.5 Pro",
75
+ "description": "Google's most capable multimodal model",
76
+ "context_length": 1048576,
77
+ "tools_supported": true,
78
+ "supports_parallel_tool_calls": true,
79
+ "supports_reasoning": true,
80
+ "input_modalities": ["text", "image"]
81
+ },
82
+ {
83
+ "id": "google/gemini-2.5-flash",
84
+ "name": "Gemini 2.5 Flash",
85
+ "description": "Google's fast and capable model with thinking support",
86
+ "context_length": 1048576,
87
+ "tools_supported": true,
88
+ "supports_parallel_tool_calls": true,
89
+ "supports_reasoning": true,
90
+ "input_modalities": ["text", "image"]
91
+ },
92
+ {
93
+ "id": "google/gemini-3-pro-preview",
94
+ "name": "Gemini 3 Pro Preview",
95
+ "description": "Google's fast and capable model with thinking support",
96
+ "context_length": 1048576,
97
+ "tools_supported": true,
98
+ "supports_parallel_tool_calls": true,
99
+ "supports_reasoning": true,
100
+ "input_modalities": ["text", "image"]
101
+ },
102
+ {
103
+ "id": "google/gemini-3-flash-preview",
104
+ "name": "Gemini 3 Flash Preview",
105
+ "description": "Google's fast and capable model with thinking support",
106
+ "context_length": 1048576,
107
+ "tools_supported": true,
108
+ "supports_parallel_tool_calls": true,
109
+ "supports_reasoning": true,
110
+ "input_modalities": ["text", "image"]
111
+ }
112
+ ]