Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions commands/create_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from prompt import collect_var_value


async def handle_create_spec(spec_template: str, output_file: str | None = None):
async def handle_create_spec(spec_template: str, output_file: str | None = None, var_overrides: list[str] | None = None):
"""Handle the create-spec command"""

# Resolve relative paths against current working directory
Expand All @@ -24,13 +24,27 @@ async def handle_create_spec(spec_template: str, output_file: str | None = None)
# Get variables from template
variables = template_engine.get_variables()

# Parse var_overrides into a dictionary
provided_vars = {}
if var_overrides:
for var_override in var_overrides:
if '=' not in var_override:
print(f"Error: Invalid variable format '{var_override}'. Use KEY=VALUE format.", file=sys.stderr)
sys.exit(1)
key, value = var_override.split('=', 1)
provided_vars[key] = value

# Collect values for each variable
collected_vars = {}
if variables:
print("Collecting values for template variables:")
for var in sorted(variables):
raw_value = await collect_var_value(var)
print(f" {var}: {raw_value}")
if var in provided_vars:
raw_value = provided_vars[var]
print(f" {var}: {raw_value}")
else:
raw_value = await collect_var_value(var)
print(f" {var}: {raw_value}")
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable assignment is missing after displaying the provided variable value. The raw_value is displayed but never assigned to collected_vars[var], causing the variable to be undefined during template rendering.

Copilot uses AI. Check for mistakes.

# Try to parse as JSON if it looks like JSON
if raw_value and (raw_value.strip().startswith('{') or raw_value.strip().startswith('[')):
Expand Down
3 changes: 2 additions & 1 deletion cxk.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async def main():
create_spec_parser = subparsers.add_parser("create-spec", help="Create spec from template")
create_spec_parser.add_argument("spec_template", help="Path to the spec template file")
create_spec_parser.add_argument("--output", help="Output file path (defaults to stdout if not specified)")
create_spec_parser.add_argument("--var", action="append", help="Set template variable value (format: key=value)")

# cxk mcp
mcp_parser = subparsers.add_parser("mcp", help="Manage MCP servers")
Expand Down Expand Up @@ -59,7 +60,7 @@ async def main():
await handle_init(state)

elif args.command == "create-spec":
await handle_create_spec(args.spec_template, args.output)
await handle_create_spec(args.spec_template, args.output, args.var)

elif args.command == "mcp":
if not args.mcp_command:
Expand Down
14 changes: 14 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,18 @@
## Running Tests
```
uv run pytest
```

## Manual Testing

```
uv run cxk.py create-spec tests/templates/spec1.md
```

```
uv run cxk.py create-spec tests/templates/spec1.md --output result.md
```

```
uv run cxk.py create-spec tests/templates/spec1.md --var additional_context=aa --var ticket='{"id":1}'
```
143 changes: 141 additions & 2 deletions tests/e2e/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ def test_create_spec_with_variables(self, temp_non_git_dir):
assert 'weather: {"condition": "sunny", "temp": "75F"}' in result.stdout

# Verify rendered template output
assert "Rendered template:" in result.stdout
assert "Hello John!" in result.stdout
assert "Your age is 25 and you live in New York." in result.stdout
assert "Today's weather is sunny with temperature 75F." in result.stdout
Expand Down Expand Up @@ -342,7 +341,6 @@ def test_create_spec_relative_path(self, temp_non_git_dir):
assert "username: testuser" in result.stdout

# Verify rendered template output
assert "Rendered template:" in result.stdout
assert "Hello testuser!" in result.stdout

def test_create_spec_file_not_found(self, temp_non_git_dir):
Expand Down Expand Up @@ -442,3 +440,144 @@ def test_create_spec_stdout_vs_file_output(self, temp_non_git_dir):
file_content = output_file.read_text().strip()
assert stdout_rendered == file_content
assert "Hello John! You are 25 years old." in file_content

def test_create_spec_with_var_override_single(self, temp_non_git_dir):
"""Test create-spec with single --var override."""
# Create a test template with variables
template_content = "Hello {{ name }}! You are {{ age }} years old."
template_file = temp_non_git_dir / "var_override_template.j2"
template_file.write_text(template_content)

# Run create-spec command with --var override
result = self.run_cli(
["create-spec", str(template_file), "--var", "name=Alice"],
use_test_runner=True
)

assert result.returncode == 0

# Verify rendered template output
assert "Hello Alice! You are 25 years old." in result.stdout

def test_create_spec_with_var_override_multiple(self, temp_non_git_dir):
"""Test create-spec with multiple --var overrides."""
# Create a test template with variables
template_content = "Hello {{ name }}! You are {{ age }} years old and live in {{ city }}."
template_file = temp_non_git_dir / "multiple_var_override_template.j2"
template_file.write_text(template_content)

# Run create-spec command with multiple --var overrides
result = self.run_cli(
["create-spec", str(template_file), "--var", "name=Bob", "--var", "city=Boston"],
use_test_runner=True
)

assert result.returncode == 0

# Verify rendered template output
assert "Hello Bob! You are 25 years old and live in Boston." in result.stdout

def test_create_spec_with_var_override_all_variables(self, temp_non_git_dir):
"""Test create-spec with all variables provided via --var (no interactive prompts)."""
# Create a test template with variables
template_content = "{{ greeting }} {{ name }}! Your score is {{ score }}."
template_file = temp_non_git_dir / "all_var_override_template.j2"
template_file.write_text(template_content)

# Run create-spec command with all variables provided
result = self.run_cli(
[
"create-spec", str(template_file),
"--var", "greeting=Hi",
"--var", "name=Charlie",
"--var", "score=100"
],
use_test_runner=True
)

assert result.returncode == 0

# Verify rendered template output
assert "Hi Charlie! Your score is 100." in result.stdout

def test_create_spec_with_var_override_json_value(self, temp_non_git_dir):
"""Test create-spec with --var containing JSON value."""
# Create a test template with JSON variable
template_content = "User: {{ user.name }}, Email: {{ user.email }}"
template_file = temp_non_git_dir / "json_var_override_template.j2"
template_file.write_text(template_content)

# Run create-spec command with JSON --var
json_value = '{"name": "Dave", "email": "dave@example.com"}'
result = self.run_cli(
["create-spec", str(template_file), "--var", f"user={json_value}"],
use_test_runner=True
)

assert result.returncode == 0

# Verify rendered template output
assert "User: Dave, Email: dave@example.com" in result.stdout

def test_create_spec_with_var_invalid_format(self, temp_non_git_dir):
"""Test create-spec with invalid --var format."""
# Create a test template
template_content = "Hello {{ name }}!"
template_file = temp_non_git_dir / "invalid_var_format_template.j2"
template_file.write_text(template_content)

# Run create-spec command with invalid --var format (missing =)
result = self.run_cli(
["create-spec", str(template_file), "--var", "invalid_format"],
use_test_runner=True
)

assert result.returncode != 0
assert "Error: Invalid variable format 'invalid_format'. Use KEY=VALUE format." in result.stderr

def test_create_spec_with_var_equals_in_value(self, temp_non_git_dir):
"""Test create-spec with --var value containing equals sign."""
# Create a test template
template_content = "Equation: {{ equation }}"
template_file = temp_non_git_dir / "equals_in_value_template.j2"
template_file.write_text(template_content)

# Run create-spec command with --var value containing equals
result = self.run_cli(
["create-spec", str(template_file), "--var", "equation=x=y+z"],
use_test_runner=True
)

assert result.returncode == 0

# Verify rendered template output
assert "Equation: x=y+z" in result.stdout

def test_create_spec_with_var_and_output_file(self, temp_non_git_dir):
"""Test create-spec with --var and --output together."""
# Create a test template
template_content = "Project: {{ project }}, Version: {{ version }}"
template_file = temp_non_git_dir / "var_and_output_template.j2"
template_file.write_text(template_content)

# Define output file
output_file = temp_non_git_dir / "var_output.md"

# Run create-spec command with both --var and --output
result = self.run_cli(
[
"create-spec", str(template_file),
"--var", "project=MyApp",
"--var", "version=1.0.0",
"--output", str(output_file)
],
use_test_runner=True
)

assert result.returncode == 0
assert f"Rendered template saved to: {output_file}" in result.stdout

# Verify file was created and contains expected content
assert output_file.exists()
content = output_file.read_text()
assert "Project: MyApp, Version: 1.0.0" in content