Add files using upload-large-folder tool
Browse files- .config/nextest.toml +10 -0
- .devcontainer/devcontainer.json +39 -0
- .gitattributes +1 -35
- .gitignore +48 -0
- .ignore +2 -0
- .rustfmt.toml +6 -0
- AGENTS.md +246 -0
- Cargo.lock +0 -0
- Cargo.toml +166 -0
- Cross.toml +75 -0
- LICENSE +201 -0
- README.md +1171 -0
- _config.yml +3 -0
- clippy.toml +3 -0
- diesel.toml +5 -0
- flake.lock +27 -0
- flake.nix +159 -0
- forge.schema.json +1037 -0
- insta.yaml +4 -0
- package-lock.json +1966 -0
- package.json +37 -0
- plans/2025-04-02-system-context-rendering-final.md +344 -0
- plans/2025-04-02-system-context-rendering-v1.md +171 -0
- plans/2025-04-02-system-context-rendering-v2.md +315 -0
- plans/2025-04-06-retry-config-migration.md +90 -0
- plans/2025-04-11-tool-call-context-implementation.md +113 -0
- plans/2025-04-16-model-selection-command.md +39 -0
- plans/2025-04-26-large-file-read-range-support-v3.md +239 -0
- plans/2025-04-27-large-file-read-range-support-v4.md +316 -0
- plans/2025-06-07-tool-service-migration-v1.md +197 -0
- plans/2025-09-07-shell-env-variable-support-v1.md +65 -0
- plans/2025-09-08-agent-loader-cwd-extension-v1.md +115 -0
- plans/2025-09-12-forge-dump-autoopen-env-var-v2.md +167 -0
- plans/2025-09-13-dynamic-agent-slash-commands-v3.md +144 -0
- plans/2025-09-13-forge-history-file-env-var-v2.md +207 -0
- plans/2025-09-14-explicit-conversation-id-generation-v1.md +173 -0
- plans/2025-09-27-AppConfigRepository_Implementation-v1.md +122 -0
- plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v1.md +135 -0
- plans/2026-01-22-Fix Auto-Sync Workspace Registration Issue-v2.md +138 -0
- plans/2026-03-31-slim-environment-and-add-get-config-v1.md +229 -0
- plans/2026-04-05-config-init-at-startup-v1.md +193 -0
- plans/agent-context-compaction-2025-03-24-concise.md +72 -0
- plans/agent-context-compaction-2025-03-24.md +328 -0
- plans/agent-context-compaction-2025-03-25-concise.md +220 -0
- plans/agent-context-compaction-2025-03-25.md +217 -0
- renovate.json +8 -0
- rust-analyzer.toml +23 -0
- rust-toolchain.toml +3 -0
- shell-plugin/forge.theme.zsh +28 -0
- 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 |
-
*.
|
| 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 |
+
[](https://github.com/tailcallhq/forgecode/actions)
|
| 19 |
+
[](https://github.com/tailcallhq/forgecode/releases)
|
| 20 |
+
[](https://discord.gg/kRZBPpkgwq)
|
| 21 |
+
[](https://cla-assistant.io/tailcallhq/forgecode)
|
| 22 |
+
|
| 23 |
+

|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
<details>
|
| 28 |
+
<summary><strong>Table of 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 |
+
[](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 |
+
]
|