Skip to content

Commit 3384618

Browse files
author
Marvin Zhang
committed
Refactor test files for improved organization and readability
- Adjusted imports in integration tests to ensure correct module loading. - Cleaned up whitespace in various test files for consistency. - Reorganized the order of module imports in protocol tests for clarity. - Enhanced readability in tools tests by formatting multi-line structures. - Ensured consistent error message assertions in search and create tests. - Streamlined test assertions for better clarity and maintainability.
1 parent 1b6e4e2 commit 3384618

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+3121
-2139
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,50 @@ on:
77
branches: [main]
88

99
jobs:
10+
# Node.js/TypeScript build and test
11+
node:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '20'
21+
22+
- name: Setup pnpm
23+
uses: pnpm/action-setup@v4
24+
with:
25+
version: '10.26.0'
26+
run_install: false
27+
28+
- name: Get pnpm store directory
29+
id: pnpm-cache
30+
shell: bash
31+
run: |
32+
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
33+
34+
- name: Setup pnpm cache
35+
uses: actions/cache@v4
36+
with:
37+
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
38+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
39+
restore-keys: |
40+
${{ runner.os }}-pnpm-store-
41+
42+
- name: Install dependencies
43+
run: pnpm install --frozen-lockfile
44+
45+
- name: Build all packages
46+
run: pnpm build
47+
48+
- name: Run typecheck
49+
run: pnpm typecheck
50+
51+
- name: Run tests
52+
run: pnpm test
53+
1054
# Rust build and test
1155
rust:
1256
runs-on: ubuntu-latest

.github/workflows/copilot-setup-steps.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ jobs:
3131
with:
3232
node-version: "20"
3333

34+
- name: Set up pnpm
35+
uses: pnpm/action-setup@v4
36+
with:
37+
version: "10.26.0"
38+
run_install: false
39+
40+
- name: Install JavaScript dependencies
41+
run: pnpm install --frozen-lockfile
42+
43+
- name: Build lean-spec CLI (TypeScript)
44+
run: pnpm --filter lean-spec run build
45+
3446
- name: Set up Rust toolchain
3547
uses: actions-rust-lang/setup-rust-toolchain@v1
3648

rust/leanspec-cli/src/commands/agent.rs

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
//! Dispatch specs to AI coding agents for automated implementation.
44
55
use colored::Colorize;
6+
use leanspec_core::SpecLoader;
67
use std::error::Error;
7-
use std::process::{Command, Stdio};
88
use std::fs;
9-
use leanspec_core::SpecLoader;
9+
use std::process::{Command, Stdio};
1010

1111
/// Supported AI agents
1212
const SUPPORTED_AGENTS: &[&str] = &["claude", "copilot", "aider", "gemini", "cursor", "continue"];
@@ -36,7 +36,12 @@ pub fn run(
3636

3737
fn show_help() {
3838
println!();
39-
println!("{}", "LeanSpec Agent - Dispatch specs to AI coding agents".cyan().bold());
39+
println!(
40+
"{}",
41+
"LeanSpec Agent - Dispatch specs to AI coding agents"
42+
.cyan()
43+
.bold()
44+
);
4045
println!();
4146
println!("{}", "Usage:".bold());
4247
println!(" lean-spec agent run <spec> [--agent <type>] Dispatch spec to AI agent");
@@ -68,61 +73,66 @@ fn run_agent(
6873
dry_run: bool,
6974
) -> Result<(), Box<dyn Error>> {
7075
let spec_ids = specs.ok_or("At least one spec is required for 'run' action")?;
71-
76+
7277
if spec_ids.is_empty() {
7378
return Err("At least one spec is required".into());
7479
}
75-
80+
7681
let agent_name = agent.unwrap_or_else(|| "claude".to_string());
77-
82+
7883
// Validate agent
7984
if !SUPPORTED_AGENTS.contains(&agent_name.as_str()) {
8085
return Err(format!(
8186
"Unknown agent: {}. Supported: {}",
8287
agent_name,
8388
SUPPORTED_AGENTS.join(", ")
84-
).into());
89+
)
90+
.into());
8591
}
86-
92+
8793
// Check if agent is available
8894
let agent_command = get_agent_command(&agent_name);
8995
if !is_command_available(&agent_command) && !dry_run {
9096
return Err(format!(
9197
"Agent not found: {}. Make sure {} is installed and in your PATH.",
9298
agent_name, agent_command
93-
).into());
99+
)
100+
.into());
94101
}
95-
102+
96103
println!();
97-
println!("{}", format!("🤖 Dispatching to {} agent", agent_name.cyan()).green());
104+
println!(
105+
"{}",
106+
format!("🤖 Dispatching to {} agent", agent_name.cyan()).green()
107+
);
98108
println!();
99-
109+
100110
// Load specs
101111
let loader = SpecLoader::new(specs_dir);
102112
let all_specs = loader.load_all()?;
103-
113+
104114
// Find matching specs
105115
let mut found_specs = Vec::new();
106116
for spec_id in &spec_ids {
107117
let matching: Vec<_> = all_specs
108118
.iter()
109119
.filter(|s| s.path.contains(spec_id) || s.name().contains(spec_id))
110120
.collect();
111-
121+
112122
if matching.is_empty() {
113123
return Err(format!("Spec not found: {}", spec_id).into());
114124
}
115-
125+
116126
found_specs.extend(matching);
117127
}
118-
128+
119129
println!("{}", "Specs to process:".bold());
120130
for spec in &found_specs {
121131
let status_icon = spec.frontmatter.status_emoji();
122132
println!(" • {} {}", spec.name(), status_icon);
123133
}
124134
println!();
125-
135+
126136
if dry_run {
127137
println!("{}", "Dry run mode - no actions will be taken".yellow());
128138
println!();
@@ -139,41 +149,44 @@ fn run_agent(
139149
}
140150
return Ok(());
141151
}
142-
152+
143153
// Process each spec
144154
for spec in &found_specs {
145155
println!("{}", format!("Processing: {}", spec.name()).bold());
146-
156+
147157
// Update status to in-progress (if not disabled)
148158
if !no_status_update {
149159
println!(" {} Updated status to in-progress", "✓".green());
150160
}
151-
161+
152162
// Create worktree for parallel development
153163
if parallel {
154-
println!(" {} Creating worktree (not implemented in Rust CLI yet)", "⚠".yellow());
164+
println!(
165+
" {} Creating worktree (not implemented in Rust CLI yet)",
166+
"⚠".yellow()
167+
);
155168
}
156-
169+
157170
// Load spec content
158171
let readme_path = spec.file_path.clone();
159172
let content = fs::read_to_string(&readme_path).unwrap_or_default();
160-
173+
161174
// Launch agent
162175
println!(" {} Launching {}...", "→".cyan(), agent_name);
163-
176+
164177
let result = launch_agent(&agent_name, spec.name(), &content);
165-
178+
166179
match result {
167180
Ok(_) => println!(" {} Agent session started", "✓".green()),
168181
Err(e) => println!(" {} Failed to launch agent: {}", "✗".red(), e),
169182
}
170-
183+
171184
println!();
172185
}
173-
186+
174187
println!("{}", "✨ Agent dispatch complete".green());
175188
println!("Use {} to check progress", "lean-spec agent status".cyan());
176-
189+
177190
Ok(())
178191
}
179192

@@ -185,7 +198,7 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
185198
available: bool,
186199
agent_type: String,
187200
}
188-
201+
189202
let agents: Vec<AgentInfo> = SUPPORTED_AGENTS
190203
.iter()
191204
.map(|&name| {
@@ -198,17 +211,17 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
198211
}
199212
})
200213
.collect();
201-
214+
202215
if output_format == "json" {
203216
println!("{}", serde_json::to_string_pretty(&agents)?);
204217
return Ok(());
205218
}
206-
219+
207220
println!();
208221
println!("{}", "=== Available AI Agents ===".green().bold());
209222
println!();
210223
println!("{}", "CLI-based (local):".bold());
211-
224+
212225
for agent in &agents {
213226
let status = if agent.available {
214227
"✓".green()
@@ -217,52 +230,63 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
217230
};
218231
println!(" {} {} ({})", status, agent.name, agent.command.dimmed());
219232
}
220-
233+
221234
println!();
222235
println!("Set default: {}", "lean-spec agent config <agent>".cyan());
223-
println!("Run agent: {}", "lean-spec agent run <spec> --agent <agent>".cyan());
236+
println!(
237+
"Run agent: {}",
238+
"lean-spec agent run <spec> --agent <agent>".cyan()
239+
);
224240
println!();
225-
241+
226242
Ok(())
227243
}
228244

229245
fn show_status(specs: Option<Vec<String>>, output_format: &str) -> Result<(), Box<dyn Error>> {
230246
// In a real implementation, this would track active sessions
231247
// For now, just report that there are no active sessions
232-
248+
233249
if output_format == "json" {
234250
println!("{{}}");
235251
return Ok(());
236252
}
237-
253+
238254
println!();
239-
255+
240256
if let Some(spec_ids) = specs {
241257
for spec_id in spec_ids {
242-
println!("{}", format!("No active session for spec: {}", spec_id).yellow());
258+
println!(
259+
"{}",
260+
format!("No active session for spec: {}", spec_id).yellow()
261+
);
243262
}
244263
} else {
245264
println!("{}", "No active agent sessions".dimmed());
246265
}
247-
266+
248267
println!();
249-
268+
250269
Ok(())
251270
}
252271

253272
fn configure_default_agent(agent: Option<String>) -> Result<(), Box<dyn Error>> {
254273
let agent_name = agent.ok_or("Agent name required for 'config' action")?;
255-
274+
256275
if !SUPPORTED_AGENTS.contains(&agent_name.as_str()) {
257276
return Err(format!(
258277
"Unknown agent: {}. Supported: {}",
259278
agent_name,
260279
SUPPORTED_AGENTS.join(", ")
261-
).into());
280+
)
281+
.into());
262282
}
263-
264-
println!("{} Default agent set to: {}", "✓".green(), agent_name.cyan());
265-
283+
284+
println!(
285+
"{} Default agent set to: {}",
286+
"✓".green(),
287+
agent_name.cyan()
288+
);
289+
266290
Ok(())
267291
}
268292

@@ -290,7 +314,7 @@ fn is_command_available(command: &str) -> bool {
290314
.map(|s| s.success())
291315
.unwrap_or(false)
292316
}
293-
317+
294318
#[cfg(not(target_os = "windows"))]
295319
{
296320
Command::new("which")
@@ -305,20 +329,23 @@ fn is_command_available(command: &str) -> bool {
305329

306330
fn launch_agent(agent: &str, _spec_name: &str, content: &str) -> Result<(), Box<dyn Error>> {
307331
let command = get_agent_command(agent);
308-
332+
309333
// Build context template
310334
let context = format!(
311335
"Implement the following LeanSpec specification:\n\n---\n{}\n---\n\n\
312336
Please follow the spec's design, plan, and test sections. \
313337
Update the spec status to 'complete' when done.",
314338
content.chars().take(2000).collect::<String>()
315339
);
316-
340+
317341
// Launch agent based on type
318342
match agent {
319343
"claude" => {
320344
println!(" {} Context prepared for Claude", "✓".green());
321-
println!(" {} Copy the spec content and paste into Claude", "ℹ".cyan());
345+
println!(
346+
" {} Copy the spec content and paste into Claude",
347+
"ℹ".cyan()
348+
);
322349
}
323350
"aider" => {
324351
// Aider takes --message flag
@@ -333,9 +360,13 @@ fn launch_agent(agent: &str, _spec_name: &str, content: &str) -> Result<(), Box<
333360
.spawn()?;
334361
}
335362
_ => {
336-
println!(" {} Launch {} manually with the spec content", "ℹ".cyan(), agent);
363+
println!(
364+
" {} Launch {} manually with the spec content",
365+
"ℹ".cyan(),
366+
agent
367+
);
337368
}
338369
}
339-
370+
340371
Ok(())
341372
}

0 commit comments

Comments
 (0)