feat: v0.4.0 — add streaming, explain, project scaffolding, session persistence, CI/CD#1
Conversation
…ersistence, CI/CD - Add CLAUDE.md with build/test/lint commands and architecture overview - Create GitHub Actions CI/CD pipeline (fmt, clippy, test, build) - Fix .gitignore to allow .github/ directory - Fix broken doc links in README (7 non-existent files removed) - Mark RAG as complete in FEATURES.md New features: - Streaming LLM responses: token-by-token display in REPL with fallback - Code explanation mode: /explain command + /api/explain dashboard endpoint - Multi-file project generation: /project command with JSON blueprint parsing, path validation, and scaffolding to generated/<project_name>/ - Conversation persistence: /session save/load/list commands with JSON serialization - Dashboard endpoints for all new features Also includes: version bump to 0.4.0, new ProjectBlueprint struct with 6 unit tests, updated help text, and updated command tab-completion. https://claude.ai/code/session_01LJJePD8fgBfCKTt8HmpHFp
There was a problem hiding this comment.
Pull request overview
This PR bumps the project to v0.4.0 and adds several end-user features (streaming output, code explanation, multi-file project scaffolding, and session persistence) across both the REPL and the dashboard, along with CI/CD and documentation updates.
Changes:
- Add streaming-capable LLM generation path, plus
/explain,/project, and/sessioncommands in the REPL and corresponding dashboard endpoints. - Introduce a
ProjectBlueprintJSON schema parser + scaffolder for multi-file project generation. - Add GitHub Actions CI pipeline and update docs/versioning for the v0.4.0 release.
Reviewed changes
Copilot reviewed 17 out of 20 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
tests/integration_tests.rs |
Formatting/whitespace normalization in integration tests. |
src/utils.rs |
Adds ProjectBlueprint/ProjectFile structs, JSON extraction/parsing, and basic path validation + unit tests. |
src/rag.rs |
Formatting tweaks in RAG embedding/retrieval. |
src/python_exec.rs |
Adds scaffold_project helper; extensive formatting-only changes elsewhere. |
src/logger.rs |
Formatting updates to session metrics display + test whitespace cleanup. |
src/lib.rs |
Reorders module exports. |
src/interface.rs |
Adds commands: /explain, /project, /session, and REPL-side “streaming” generation path; lots of formatting. |
src/dashboard/templates.rs |
Import ordering cleanup. |
src/dashboard/state.rs |
Adds use_streaming to runtime settings; enum formatting cleanup. |
src/dashboard/server.rs |
Registers new dashboard endpoints for explain/project/session persistence. |
src/dashboard/routes.rs |
Adds explain/project/session persistence endpoints and various formatting refactors. |
src/config.rs |
Adds use_streaming + sessions_dir config fields and defaults. |
src/api.rs |
Adds explanation + project generation prompts/APIs; adds streaming response parsing helper. |
README.md |
Updates version/features list and documents new v0.4.0 functionality. |
FEATURES.md |
Marks several items (RAG/CI/explain/session/streaming/project) as DONE and updates descriptions. |
CLAUDE.md |
Adds developer guide with build/test/lint commands and architecture overview. |
Cargo.toml |
Version bump to 0.4.0. |
Cargo.lock |
Lockfile version update. |
.gitignore |
Stops ignoring .github/ so workflows can be committed. |
.github/workflows/ci.yml |
Adds CI jobs for fmt, clippy, test, and release build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let filename = format!("{}.json", name.replace(' ', "_")); | ||
| let filepath = sessions_dir.join(&filename); | ||
| match fs::write( |
There was a problem hiding this comment.
Session filenames are derived directly from the user-provided name and joined onto sessions_dir. As written, names containing path separators, .., or absolute paths (e.g. /tmp/x) can escape sessions_dir and overwrite arbitrary files. Sanitize/validate the name (e.g., allow only [A-Za-z0-9_-]) and reject any name that produces an absolute path or contains parent-dir components.
| // Read the full body and parse SSE events | ||
| let text_body = resp | ||
| .text() | ||
| .await | ||
| .context("Failed to read streaming response")?; | ||
| let mut chunks = Vec::new(); | ||
| let mut full_content = String::new(); | ||
|
|
||
| for line in text_body.lines() { |
There was a problem hiding this comment.
generate_code_stream calls resp.text().await and then iterates over text_body.lines(). This buffers the entire response before returning any chunks, so the caller cannot display tokens in real time (and memory usage scales with full output). To make streaming actually work, parse the SSE incrementally from resp.bytes_stream() (or similar) and surface chunks as they arrive (e.g., via a callback/channel/stream), rather than returning only after full body read.
| for file in &bp.files { | ||
| if file.path.contains("..") { | ||
| return Err(anyhow!( | ||
| "Unsafe path detected: '{}' contains '..'", | ||
| file.path | ||
| )); | ||
| } | ||
| if file.path.starts_with('/') { | ||
| return Err(anyhow!( |
There was a problem hiding this comment.
The path validation for project files is too weak and also platform-specific: contains("..") can false-positive on benign names and still misses path traversal via components, and starts_with('/') doesn’t catch Windows absolute paths (e.g. C:\... or \\server\share). Consider validating with std::path::Path components (reject Component::ParentDir / Component::RootDir / prefixes) and also validate project_name (it is used as a directory name but currently only checked for empty).
| for file in &blueprint.files { | ||
| let file_path = project_dir.join(&file.path); | ||
| if let Some(parent) = file_path.parent() { | ||
| fs::create_dir_all(parent) | ||
| .with_context(|| format!("Failed to create directory {:?}", parent))?; | ||
| } | ||
| fs::write(&file_path, &file.content) | ||
| .with_context(|| format!("Failed to write file {:?}", file_path))?; |
There was a problem hiding this comment.
scaffold_project writes each file.path by joining onto project_dir without any additional containment check. If a ProjectBlueprint reaches here without strong validation (or validation misses a case like Windows absolute paths), this can write outside the intended directory. Add a defensive check here too (e.g., reject absolute paths/prefixes and ensure the joined path stays within project_dir after normalization/canonicalization).
| "messages": session.messages, | ||
| "last_generated_code": session.last_generated_code, |
There was a problem hiding this comment.
This endpoint builds the JSON payload by moving session.messages / session.last_generated_code out of a borrowed &ChatSession from the HashMap, which won’t compile. Clone the fields (or serialize from references) when building the JSON value.
| "messages": session.messages, | |
| "last_generated_code": session.last_generated_code, | |
| "messages": session.messages.clone(), | |
| "last_generated_code": session.last_generated_code.clone(), |
| let filename = format!("{}.json", req.name.replace(' ', "_")); | ||
| let filepath = sessions_dir.join(&filename); | ||
| match std::fs::write( |
There was a problem hiding this comment.
Saved-session filenames are derived from req.name and joined onto sessions_dir without sanitization. A crafted name containing separators/../absolute paths can escape the sessions directory. Validate/sanitize req.name before constructing filename/filepath, and reject any resulting absolute path or parent-dir component.
| assert_eq!( | ||
| cfg.rag_embedding_model, | ||
| "sentence-transformers/all-MiniLM-L6-v2" | ||
| ); | ||
| assert_eq!(cfg.rag_chunk_size, 1024); | ||
| } |
There was a problem hiding this comment.
The default-config test was updated for formatting, but it no longer asserts defaults for newly added fields (use_streaming, sessions_dir). Adding assertions here would prevent silent regressions in defaults when these settings are used by REPL/session persistence.
| - **Streaming Responses** ⚡: Real-time token-by-token code generation in REPL and dashboard via SSE | ||
| - **Code Explanation** 📖: `/explain` command and dashboard button for step-by-step code breakdowns | ||
| - **Multi-File Projects** 📁: `/project` command generates entire project structures with multiple files | ||
| - **Session Persistence** 💾: Save and load conversation sessions across restarts |
There was a problem hiding this comment.
README claims “real-time token-by-token” streaming in the REPL/dashboard, but the current generate_code_stream implementation buffers the full HTTP body (resp.text().await) before returning. Either adjust the implementation to truly stream incremental chunks, or soften the README wording to match current behavior.
| "messages": conversation_history, | ||
| "last_generated_code": last_generated_code, |
There was a problem hiding this comment.
The /session save JSON payload currently includes conversation_history and last_generated_code by value. With serde_json::json!, this will move those variables and make them unusable for the rest of the REPL loop. Clone (or serialize from references) to avoid moving REPL state during save.
| "messages": conversation_history, | |
| "last_generated_code": last_generated_code, | |
| "messages": &conversation_history, | |
| "last_generated_code": &last_generated_code, |
| "provider": config.provider, | ||
| "model": config.model, |
There was a problem hiding this comment.
session_data includes "provider": config.provider and "model": config.model. Because config is a shared &AppConfig, this attempts to move String fields out of a borrowed value and won’t compile. Use config.provider.clone() / config.model.clone() (or store as &str) instead.
| "provider": config.provider, | |
| "model": config.model, | |
| "provider": config.provider.clone(), | |
| "model": config.model.clone(), |
New features:
path validation, and scaffolding to generated/<project_name>/
Also includes: version bump to 0.4.0, new ProjectBlueprint struct with
6 unit tests, updated help text, and updated command tab-completion.
https://claude.ai/code/session_01LJJePD8fgBfCKTt8HmpHFp