Skip to content

Commit 1ff87aa

Browse files
committed
feat: implement zerv flow cli core structure
1 parent 430f9a8 commit 1ff87aa

33 files changed

+1453
-218
lines changed

.claude/plan/34-zerv-flow-implementation-steps.md

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
11
# Zerv Flow Implementation Plan
22

3-
**Status**: Planned
3+
**Status**: In Progress
44
**Priority**: High
55
**Context**: Step-by-step implementation plan for the `zerv flow` command based on CLI design in document #33.
66

7+
## Current Progress Summary (as of Oct 29, 2025)
8+
9+
### **Completed Work:**
10+
11+
- **Phase 1**: Core CLI Structure - 100% Complete
12+
- CLI module structure implemented (`src/cli/flow/`)
13+
- Flow command registered in parser and dispatcher
14+
- Help system working (`zerv flow --help`)
15+
- Shared args integration completed
16+
- Validation system implemented
17+
- All tests passing (6/6 flow tests)
18+
19+
- **Shared Args Refactoring**: Completed
20+
- Created `src/cli/common/args/` with InputConfig, OutputConfig, Validation
21+
- Both version and flow commands now use shared args
22+
- Eliminated code duplication between commands
23+
- Comprehensive test coverage (35 tests) for shared components
24+
25+
- **Code Quality Improvements**: Completed
26+
- Added BoolResolution utility for opposing boolean flags
27+
- Cleaned up verbose documentation comments
28+
- Fixed inconsistent module structures
29+
- Added comprehensive test coverage to common args
30+
31+
### 🔄 **Next Steps:**
32+
33+
- **Phase 2**: Implement branch pattern system and flow-to-version translation logic
34+
- **Phase 3**: Complete pipeline assembly with actual version command integration
35+
- **Phase 4**: Comprehensive testing and documentation
36+
737
## Goals
838

939
1. Implement `zerv flow` command that mirrors `zerv version` structure
@@ -14,41 +44,55 @@
1444

1545
## Implementation Plan
1646

17-
### Phase 1: Core CLI Structure
47+
### **Phase 1: Core CLI Structure - COMPLETED**
48+
49+
**All Phase 1 tasks have been successfully completed:**
50+
51+
- ✅ CLI structure implemented and working
52+
- ✅ Flow command registered and dispatching correctly
53+
- ✅ Help system functioning (`zerv flow --help` works)
54+
- ✅ Basic validation and error handling in place
55+
- ✅ Shared input/output args integration complete
56+
- ✅ Module structure properly organized (args/mod.rs pattern)
1857

19-
#### Step 1: Create Flow Command Module Structure
58+
### Phase 2: Flow Logic as Translation Layer
59+
60+
#### Step 1: Create Flow Command Module Structure ✅
2061

2162
- **Files**:
2263
- `src/cli/flow/mod.rs` (new file - module exports)
23-
- `src/cli/flow/args.rs` (new file - argument structs)
64+
- `src/cli/flow/args/mod.rs` (new file - argument structs)
2465
- `src/cli/flow/pipeline.rs` (new file - main handler)
2566
- **Tasks**:
26-
- Create module structure following existing version command pattern
27-
- Define `FlowArgs` struct with `clap::Parser` derive macro
28-
- Organize arguments into logical groups (main, overrides, flow-specific)
29-
- Set up proper module exports and dependencies
30-
- **Validation**: Module compiles and imports work correctly
67+
- ✅ Create module structure following existing version command pattern
68+
- ✅ Define `FlowArgs` struct with `clap::Parser` derive macro
69+
- ✅ Organize arguments into logical groups (input, output, flow-specific, overrides)
70+
- ✅ Set up proper module exports and dependencies
71+
- ✅ Use shared input/output args from `src/cli/common/args/`
72+
- ✅ Implement consistent module structure with version command
73+
- **Validation**: ✅ Module compiles and imports work correctly
3174

32-
#### Step 2: Implement Flow Command Handler
75+
#### Step 2: Implement Flow Command Handler
3376

3477
- **File**: `src/cli/flow/pipeline.rs`
3578
- **Tasks**:
36-
- Create `run_flow_pipeline(args: FlowArgs) -> Result<String, ZervError>` function
37-
- Follow existing pattern: validation → processing → formatting → return
38-
- Set up basic argument validation using `args.validate()` method
39-
- Add error handling with `ZervError` and detailed context
40-
- Use constants from `crate::utils::constants::*` instead of bare strings
41-
- **Validation**: Function compiles and returns proper Result type
79+
- ✅ Create `run_flow_pipeline(args: FlowArgs) -> Result<String, ZervError>` function
80+
- ✅ Follow existing pattern: validation → processing → formatting → return
81+
- ✅ Set up basic argument validation using `args.validate()` method
82+
- ✅ Add error handling with `ZervError` and detailed context
83+
- ✅ Use constants from `crate::utils::constants::*` instead of bare strings
84+
- ✅ Add placeholder implementation that returns `NotImplemented` error
85+
- **Validation**: ✅ Function compiles and returns proper Result type
4286

43-
#### Step 3: Register Flow Command in CLI Parser
87+
#### Step 3: Register Flow Command in CLI Parser
4488

4589
- **Files**: `src/cli/parser.rs`, `src/cli/app.rs`
4690
- **Tasks**:
47-
- Add `Flow(FlowArgs)` to `Commands` enum in `parser.rs`
48-
- Add command dispatch handling in `app.rs` following existing pattern
49-
- Ensure output is written to provided `writer`
50-
- Add proper error propagation in dispatch
51-
- **Validation**: `zerv flow --help` shows all arguments correctly
91+
- Add `Flow(FlowArgs)` to `Commands` enum in `parser.rs`
92+
- Add command dispatch handling in `app.rs` following existing pattern
93+
- Ensure output is written to provided `writer`
94+
- Add proper error propagation in dispatch
95+
- **Validation**: `zerv flow --help` shows all arguments correctly
5296

5397
### Phase 2: Flow Logic as Translation Layer
5498

@@ -103,17 +147,18 @@
103147
- Add verbose output showing translation results for debugging
104148
- **Validation**: Complete flow works end-to-end using version command
105149

106-
#### Step 8: Add Argument Validation
150+
#### Step 8: Add Argument Validation
107151

108-
- **File**: `src/cli/flow/args.rs` (continued)
152+
- **File**: `src/cli/flow/args/validation.rs`
109153
- **Tasks**:
110-
- Implement `validate()` method for `FlowArgs` struct
111-
- Add validation for conflicting argument combinations
112-
- Validate RON string format for branch rules
113-
- Validate post mode values and hash length ranges
114-
- Use `ZervError` with detailed messages for validation failures
115-
- Follow existing validation patterns from version command
116-
- **Validation**: All validation scenarios work correctly
154+
- ✅ Implement `validate()` method for `FlowArgs` struct
155+
- ✅ Add validation for conflicting argument combinations
156+
- ✅ Validate RON string format for branch rules
157+
- ✅ Validate post mode values and hash length ranges
158+
- ✅ Use `ZervError` with detailed messages for validation failures
159+
- ✅ Follow existing validation patterns from version command
160+
- ✅ Use shared validation for input/output conflicts
161+
- **Validation**: ✅ All validation scenarios work correctly
117162

118163
### Phase 4: Testing and Documentation
119164

src/cli/app.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::io::Write;
33
use clap::Parser;
44

55
use crate::cli::check::run_check_command;
6+
use crate::cli::flow::run_flow_pipeline;
67
use crate::cli::llm_help::display_llm_help;
78
use crate::cli::parser::{
89
Cli,
@@ -31,6 +32,10 @@ pub fn run_with_args<W: Write>(
3132
let output = run_version_pipeline(*version_args)?;
3233
writeln!(writer, "{output}")?;
3334
}
35+
Some(Commands::Flow(flow_args)) => {
36+
let output = run_flow_pipeline(*flow_args)?;
37+
writeln!(writer, "{output}")?;
38+
}
3439
Some(Commands::Check(check_args)) => {
3540
run_check_command(check_args)?;
3641
}

src/cli/check.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use clap::Parser;
66
use crate::error::ZervError;
77
use crate::utils::constants::{
88
SUPPORTED_FORMAT_NAMES,
9-
SUPPORTED_FORMATS,
109
format_names,
1110
formats,
1211
};
@@ -79,7 +78,10 @@ pub fn run_check_command(args: CheckArgs) -> Result<(), ZervError> {
7978
}
8079
Some(format) => {
8180
eprintln!("✗ Unknown format: {format}");
82-
eprintln!("Supported formats: {}", SUPPORTED_FORMATS.join(", "));
81+
eprintln!(
82+
"Supported formats: {}",
83+
formats::SUPPORTED_FORMATS.join(", ")
84+
);
8385
return Err(ZervError::UnknownFormat(format.to_string()));
8486
}
8587
}

src/cli/common/args/input.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use clap::Parser;
2+
3+
use crate::utils::constants::{
4+
formats,
5+
sources,
6+
};
7+
8+
/// Reusable input configuration for version data
9+
#[derive(Parser, Debug, Clone)]
10+
pub struct InputConfig {
11+
// ============================================================================
12+
// INPUT OPTIONS
13+
// ============================================================================
14+
/// Input source for version data
15+
#[arg(short = 's', long = "source", default_value = sources::GIT, value_parser = [sources::GIT, sources::STDIN],
16+
help = "Input source: 'git' (extract from repository) or 'stdin' (read Zerv RON format)")]
17+
pub source: String,
18+
19+
/// Input format for version string parsing
20+
#[arg(short = 'f', long = "input-format", default_value = formats::AUTO, value_parser = [formats::AUTO, formats::SEMVER, formats::PEP440],
21+
help = "Input format: 'auto' (detect), 'semver', or 'pep440'")]
22+
pub input_format: String,
23+
24+
/// Working directory (default: current directory)
25+
#[arg(short = 'C', long = "directory", value_name = "DIR")]
26+
pub directory: Option<String>,
27+
}
28+
29+
impl Default for InputConfig {
30+
fn default() -> Self {
31+
Self {
32+
source: sources::GIT.to_string(),
33+
input_format: formats::AUTO.to_string(),
34+
directory: None,
35+
}
36+
}
37+
}
38+
39+
#[cfg(test)]
40+
mod tests {
41+
use super::*;
42+
43+
#[test]
44+
fn test_input_config_defaults() {
45+
let config = InputConfig::default();
46+
assert_eq!(config.source, sources::GIT);
47+
assert_eq!(config.input_format, formats::AUTO);
48+
assert!(config.directory.is_none());
49+
}
50+
51+
#[test]
52+
fn test_input_config_construction() {
53+
let config = InputConfig {
54+
source: sources::STDIN.to_string(),
55+
input_format: formats::SEMVER.to_string(),
56+
directory: Some("/path/to/repo".to_string()),
57+
};
58+
assert_eq!(config.source, sources::STDIN);
59+
assert_eq!(config.input_format, formats::SEMVER);
60+
assert_eq!(config.directory, Some("/path/to/repo".to_string()));
61+
}
62+
63+
#[test]
64+
fn test_input_config_various_sources() {
65+
let sources_to_test = [
66+
(sources::GIT, sources::GIT),
67+
(sources::STDIN, sources::STDIN),
68+
];
69+
70+
for (source_value, expected_source) in sources_to_test {
71+
let config = InputConfig {
72+
source: source_value.to_string(),
73+
input_format: formats::AUTO.to_string(),
74+
directory: None,
75+
};
76+
assert_eq!(config.source, expected_source);
77+
}
78+
}
79+
80+
#[test]
81+
fn test_input_config_various_formats() {
82+
let formats_to_test = [
83+
(formats::AUTO, formats::AUTO),
84+
(formats::SEMVER, formats::SEMVER),
85+
(formats::PEP440, formats::PEP440),
86+
];
87+
88+
for (format_value, expected_format) in formats_to_test {
89+
let config = InputConfig {
90+
source: sources::GIT.to_string(),
91+
input_format: format_value.to_string(),
92+
directory: None,
93+
};
94+
assert_eq!(config.input_format, expected_format);
95+
}
96+
}
97+
98+
#[test]
99+
fn test_input_config_debug_format() {
100+
let config = InputConfig {
101+
source: "stdin".to_string(),
102+
input_format: "semver".to_string(),
103+
directory: Some("/test".to_string()),
104+
};
105+
let debug_str = format!("{:?}", config);
106+
assert!(debug_str.contains("stdin"));
107+
assert!(debug_str.contains("semver"));
108+
assert!(debug_str.contains("/test"));
109+
}
110+
111+
#[test]
112+
fn test_input_config_clone() {
113+
let config = InputConfig {
114+
source: "stdin".to_string(),
115+
input_format: "semver".to_string(),
116+
directory: Some("/test".to_string()),
117+
};
118+
let cloned = config.clone();
119+
assert_eq!(config.source, cloned.source);
120+
assert_eq!(config.input_format, cloned.input_format);
121+
assert_eq!(config.directory, cloned.directory);
122+
}
123+
124+
#[test]
125+
fn test_input_config_empty_directory() {
126+
let config = InputConfig {
127+
source: sources::GIT.to_string(),
128+
input_format: formats::AUTO.to_string(),
129+
directory: Some("".to_string()),
130+
};
131+
assert_eq!(config.directory, Some("".to_string()));
132+
}
133+
134+
#[test]
135+
fn test_input_config_complex_directory() {
136+
let complex_path = "/workspace/user/project/subdir";
137+
let config = InputConfig {
138+
source: sources::GIT.to_string(),
139+
input_format: formats::SEMVER.to_string(),
140+
directory: Some(complex_path.to_string()),
141+
};
142+
assert_eq!(config.directory, Some(complex_path.to_string()));
143+
}
144+
}

src/cli/common/args/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod input;
2+
pub mod output;
3+
pub mod validation;
4+
5+
pub use input::InputConfig;
6+
pub use output::OutputConfig;
7+
pub use validation::Validation;

0 commit comments

Comments
 (0)