Keen CLI — Phase 1 Progress Evaluation¶
Build & Test Status¶
| Check | Status |
|---|---|
go build ./... |
✅ Passes |
go test ./... |
✅ Passes (config, filesystem) |
Task-by-Task Assessment¶
Task 1: Project Structure & Go Module ✅ Complete¶
| Planned | Actual | Status |
|---|---|---|
cmd/agent/main.go |
✅ Present | Done |
internal/config/ |
✅ Present (config.go, loader.go) | Done |
internal/filesystem/ |
✅ Present (guard.go, gitawareness.go) | Done |
internal/cli/ |
✅ Present (root.go, repl.go, setup.go) | Done |
configs/system_prompts/ |
⚠️ Directory exists, empty | Minimal |
go.mod with deps |
✅ cobra, yaml.v3, go-git, huh | Done |
Actual structure matches the plan closely. The addition of configs/providers/ (registry) is a welcome bonus not in the original phase 1 plan but specified in the later CLI design doc.
Task 2: Config System ✅ Complete¶
Design doc: output-4_config-design.md
| Requirement | Status | Notes |
|---|---|---|
GlobalConfig struct (JSON) |
✅ | active_provider, active_model, providers map |
ProviderConfig struct |
✅ | models []string, api_key string |
SessionConfig struct |
✅ | In-memory only, no persistence |
ResolvedConfig struct |
✅ | Final merged config |
Resolve() function |
✅ | Session > Global > Default resolution |
Loader (Load/Save/Exists) |
✅ | JSON persistence to ~/.keen/configs.json |
GetProviderConfig() |
✅ | |
SetProviderConfig() |
✅ | |
AddModel() |
✅ | Dedup logic included |
GetFirstModel() |
✅ | |
ConfigPath() / ConfigDir() |
✅ | |
| 0600 file permissions | ✅ | In loader.go Save() |
| Unit tests | ✅ | 13 tests across config_test.go and loader_test.go |
[!TIP] The config system is one of the most complete parts of the codebase — fully matching the design doc with good test coverage.
Minor deviation: The design doc has SetProviderConfig returning an error, but the implementation is void (no error return). The current implementation silently initializes the map if nil, which is fine.
Task 3: FileGuard ✅ Complete¶
| Requirement | Status | Notes |
|---|---|---|
Permission type (Denied/Granted/Pending) |
✅ | Enum with iota |
Guard struct |
✅ | workingDir, blockedPaths, gitignore |
NewGuard() constructor |
✅ | Injects working dir + GitAwareness |
CheckPath() permission matrix |
✅ | read+inDir=Granted, write=Pending, outside=Pending |
IsBlocked() |
✅ | Checks gitignore + sensitive paths + dotfiles |
ResolvePath() |
✅ | Handles relative and absolute paths |
IsInWorkingDir() |
✅ | |
| Blocked paths list | ✅ | /etc, /usr, /bin, etc. |
| Dotfile protection | ✅ | Blocks ~/.<anything> |
| Unit tests | ✅ | 8 test functions |
Deviation from plan: The plan specified blocking path traversal (../) as PermissionDenied, but the implementation treats it as PermissionPending (asks user). The test explicitly documents this choice. This is arguably more flexible — the user can approve access to sibling directories.
[!NOTE] The plan listed
~/.ssh,~/.awsas explicitly blocked sensitive paths. The implementation takes a broader approach: any path starting with~/.<something>is blocked. This is stricter than the plan and a reasonable security choice.
Task 4: GitAwareness ✅ Complete¶
| Requirement | Status | Notes |
|---|---|---|
GitAwareness struct |
✅ | Uses go-git gitignore package |
LoadGitignore() |
✅ | Parses single .gitignore file |
LoadGitignoreRecursive() |
✅ | Walks directories for nested .gitignore |
IsIgnored() |
✅ | Checks all loaded patterns |
FilterPaths() |
✅ | Batch filter operation |
| Comments & blank lines | ✅ | Handled in parser |
| Glob patterns | ✅ | Via go-git library |
| Unit tests | ✅ | 5 test functions including recursive loading |
What's missing from the plan:
- ❌ Global gitignore (~/.gitignore_global) — not implemented
- ❌ Caching of IsIgnored results — no cache layer
- ❌ Negation patterns (!important.log) — no test for this (the library may support it, but it's untested)
- ❌ Interface extraction — the plan specifies GitAwareness as an interface, but the implementation is a concrete struct. Guard depends on *GitAwareness directly, not an interface.
Task 5: Basic CLI ✅ Complete¶
Design doc: output-5_basic-cli.md
| Requirement | Status | Notes |
|---|---|---|
keen starts REPL |
✅ | Root command runs REPL |
keen --version |
✅ | Via cobra's built-in version |
| Interactive setup (first run) | ✅ | Provider → API Key → Model flow |
| Provider registry (embedded YAML) | ✅ | configs/providers/registry.yaml |
huh for interactive prompts |
✅ | Select + password input |
| Config saved after setup | ✅ | To ~/.keen/configs.json |
| REPL with styled output | ✅ | lipgloss styling, welcome banner |
| Signal handling (Ctrl+C) | ✅ | Graceful shutdown |
/exit command |
✅ | |
| No CLI flags (all via prompts) | ✅ |
The REPL stub:
The REPL currently echoes input back — it has no command routing, no /help, /plan, /work, /model, /clear, or /add commands. This is expected for a phase 1 stub.
No tests for CLI/setup/providers:
? github.com/user/keen-cli/cmd/agent [no test files]
? github.com/user/keen-cli/configs/providers [no test files]
? github.com/user/keen-cli/internal/cli [no test files]
Task 6: Structured Logging ✅ Complete¶
| Requirement | Status | Notes |
|---|---|---|
log/slog usage |
✅ | Standard library structured logging |
| Log level config | ✅ | Via KEEN_LOG_LEVEL env var |
| Logging in components | ✅ | Used in config loader, guard, config resolution |
Deviation: The plan mentioned a dedicated internal/logger/ package. Instead, logging is configured directly in main.go using slog.SetDefault(). This is simpler and perfectly fine for the current stage.
Overall Scorecard¶
| Task | Plan Status | Quality | Tests |
|---|---|---|---|
| 1. Project Structure | ✅ Complete | Good | N/A |
| 2. Config System | ✅ Complete | Strong | 13 tests ✅ |
| 3. FileGuard | ✅ Complete | Strong | 8 tests ✅ |
| 4. GitAwareness | ⚠️ Mostly Complete | Good (missing interface, cache, global gitignore) | 5 tests ✅ |
| 5. Basic CLI | ✅ Complete | Good | ❌ No tests |
| 6. Logging | ✅ Complete | Simple but effective | N/A |
Summary¶
Phase 1 is ~90% complete. All six tasks have working implementations. The project builds, tests pass, and the binary runs. The remaining gaps are:
- GitAwareness interface — not extracted (breaks RFC design)
- No caching in GitAwareness (plan specified it)
- No global gitignore support
- No tests for
internal/cli/,configs/providers/, orcmd/agent/ prompt.godeletion — mentioned in the basic CLI plan as something to remove, but it doesn't exist (already done or never created)- REPL is a stub — only echoes input, no command routing (expected for phase 1)
The code quality is clean, idiomatic Go. No comments clutter the code. Dependencies are minimal and well-chosen. The security model (FileGuard + GitAwareness integration) is solid.