Skip to content

Commit be7a94c

Browse files
committed
feat: update plan
1 parent c8ec4e8 commit be7a94c

File tree

29 files changed

+803
-72
lines changed

29 files changed

+803
-72
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
"""Reusable file comparison tool for template validation."""
3+
4+
import difflib
5+
from pathlib import Path
6+
from typing import Literal
7+
8+
import typer
9+
10+
11+
def compare_files(file1: Path, file2: Path, label1: str, label2: str) -> bool:
12+
"""Compare two files and show differences. Returns True if identical."""
13+
print(f"\n{'='*60}")
14+
print(f"COMPARING: {file1.name}")
15+
print(f"{label1}: {file1}")
16+
print(f"{label2}: {file2}")
17+
print(f"{'='*60}")
18+
19+
if not file1.exists():
20+
print(f"❌ MISSING: {file1} does not exist")
21+
return False
22+
23+
if not file2.exists():
24+
print(f"❌ MISSING: {file2} does not exist")
25+
return False
26+
27+
content1 = file1.read_text().splitlines(keepends=True)
28+
content2 = file2.read_text().splitlines(keepends=True)
29+
30+
diff = list(
31+
difflib.unified_diff(
32+
content1,
33+
content2,
34+
fromfile=f"{label1}/{file1.name}",
35+
tofile=f"{label2}/{file2.name}",
36+
lineterm="",
37+
)
38+
)
39+
40+
if not diff:
41+
print("✅ FILES IDENTICAL")
42+
return True
43+
else:
44+
print("❌ DIFFERENCES FOUND:")
45+
for line in diff:
46+
print(line)
47+
return False
48+
49+
50+
def main(
51+
mode: Literal["template", "generated"] = typer.Argument(
52+
help="Compare template files or generated files"
53+
),
54+
question: str = typer.Option("invert_binary_tree", help="Question name for comparison"),
55+
):
56+
"""Compare files for template validation."""
57+
base_dir = Path(__file__).parent.parent.parent
58+
59+
files_to_compare = ["solution.py", "tests.py", "README.md", "playground.ipynb", "__init__.py"]
60+
61+
if mode == "template":
62+
# Compare reference vs template source
63+
dir1 = base_dir / "leetcode" / ".example" / question
64+
dir2 = base_dir / ".templates" / "leetcode" / ".example" / "{{cookiecutter.question_name}}"
65+
label1, label2 = "Reference", "Template"
66+
print("TEMPLATE SOURCE ANALYSIS")
67+
68+
elif mode == "generated":
69+
# Compare reference vs currently generated
70+
dir1 = base_dir / "leetcode" / ".example" / question
71+
dir2 = base_dir / "leetcode" / question
72+
label1, label2 = "Reference", "Generated"
73+
print("GENERATED FILES VALIDATION")
74+
75+
if not dir2.exists():
76+
print(f"\n❌ ERROR: Generated directory does not exist: {dir2}")
77+
print(f"Run: make q-gen QUESTION={question}")
78+
return
79+
80+
print(f"{label1}: {dir1}")
81+
print(f"{label2}: {dir2}")
82+
83+
identical_count = 0
84+
for filename in files_to_compare:
85+
file1 = dir1 / filename
86+
file2 = dir2 / filename
87+
if compare_files(file1, file2, label1, label2):
88+
identical_count += 1
89+
90+
print(f"\n{'='*60}")
91+
print(f"SUMMARY: {identical_count}/{len(files_to_compare)} files identical")
92+
print("- ✅ = Files identical")
93+
print("- ❌ = Differences found or missing files")
94+
95+
96+
if __name__ == "__main__":
97+
typer.run(main)
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Cookiecutter Template Modernization Plan
2+
3+
## Analysis Summary
4+
5+
**Target Structure**: `leetcode/.example/` contains the reference implementation
6+
**Key Differences Found:**
7+
8+
- `leetcode/.example/` has `__init__.py` files (missing in old template)
9+
- `leetcode/.example/` uses modern Python syntax (`TreeNode | None` vs `Optional[TreeNode]`)
10+
- `leetcode/.example/` follows project's coding standards more closely
11+
- Template must generate files identical to `leetcode/.example/` structure
12+
13+
## Implementation Plan
14+
15+
### 0. Explicit File Content Analysis
16+
17+
- **Tool**: `.amazonq/plan/compare_template_files.py` (reusable comparison script)
18+
- **Usage**:
19+
- `python .amazonq/plan/compare_template_files.py template` - Compare reference vs template source
20+
- `python .amazonq/plan/compare_template_files.py generated` - Compare reference vs generated files
21+
- **Analysis**: Line-by-line diff of all file types
22+
- **Document**: Exact differences and required changes
23+
- **Verify**: Template variables handle all variations
24+
25+
### 1. Incremental Template Updates (File-by-File Approach)
26+
27+
#### Phase 1: Add `__init__.py`
28+
29+
- **Add**: Empty `__init__.py` file to template
30+
- **Validate**: `make q-gen``make q-validate``make lint`
31+
32+
#### Phase 2: Fix `solution.py`
33+
34+
- **Update**: Modern syntax (`TreeNode | None`), clean template logic
35+
- **Validate**: `make q-gen``make q-validate``make lint`
36+
37+
#### Phase 3: Fix `tests.py`
38+
39+
- **Update**: Relative imports (`from .solution`), clean structure
40+
- **Validate**: `make q-gen``make q-validate``make lint`
41+
42+
#### Phase 4: Fix `README.md`
43+
44+
- **Update**: Clean formatting, proper markdown
45+
- **Validate**: `make q-gen``make q-validate``make lint`
46+
47+
#### Phase 5: Fix `playground.ipynb`
48+
49+
- **Update**: Clean cells without execution state
50+
- **Validate**: `make q-gen``make q-validate``make lint`
51+
52+
**Benefits**: Isolated debugging, safer progression, easier rollback
53+
54+
### 2. Multi-Problem Type Testing
55+
56+
- **Test Cases**: Ensure template handles all problem types
57+
- Basic array: `two_sum` (return `list[int]`)
58+
- Tree: `invert_binary_tree` (return `TreeNode | None`)
59+
- String: problems returning `str`
60+
- Boolean: problems returning `bool`
61+
- No imports vs TreeNode/ListNode imports
62+
- **Validation**: Each type generates correctly
63+
64+
### 3. Modernize cookiecutter.json Schema
65+
66+
- **Base on**: Existing `.templates/leetcode/.example/examples/` JSON5 files
67+
- **Ensure**: All template variables are properly defined
68+
- **Add**: Missing fields found in real examples
69+
- **Note**: Variable mapping handled by `gen.py` `convert_arrays_to_nested()`
70+
71+
### 4. Update Template Files
72+
73+
#### solution.py
74+
75+
- Use modern type hints (`TreeNode | None` not `Optional[TreeNode]`)
76+
- Match exact import patterns from real examples
77+
- Ensure proper TODO placeholder format
78+
79+
#### tests.py
80+
81+
- Follow `@logged_test` decorator pattern
82+
- Use parametrized pytest structure
83+
- Match logging format from real examples
84+
85+
#### README.md
86+
87+
- Follow exact format from real examples
88+
- Include proper problem description formatting
89+
90+
#### playground.ipynb
91+
92+
- Ensure notebook structure matches real examples
93+
94+
### 5. Template Generation Logic
95+
96+
- **File**: `.templates/leetcode/gen.py` (already handles variable mapping)
97+
- **Integration**: Works with `make q-gen QUESTION=name` (verified in Makefile)
98+
- **Update**: Handle new `__init__.py` file
99+
- **Process**: JSON → `gen.py` → cookiecutter → `leetcode/$(QUESTION)/`
100+
101+
### 6. Automated Validation System
102+
103+
- **Tool**: Reusable `.amazonq/plan/compare_template_files.py`
104+
- **Usage**:
105+
```bash
106+
# Validate current template generates correct files
107+
python .amazonq/plan/compare_template_files.py generated --question=invert_binary_tree
108+
```
109+
- **Makefile**: `make q-validate QUESTION=name` (implemented)
110+
- **Test**: Template regression testing
111+
- **Ensure**: `make q-gen` + `make lint` + `make q-test` all pass
112+
113+
### 7. Testing & Validation
114+
115+
- **Test**: Template generation with existing JSON files
116+
- **Verify**: Generated files match `leetcode/.example/` structure exactly
117+
- **Compare**: Automated diff against reference files
118+
- **Ensure**: `make q-gen` works seamlessly
119+
- **Test**: Recreation process from `.prompt/` files
120+
- **Validate**: Multi-problem type generation
121+
122+
## Key Template Variables to Ensure
123+
124+
```json
125+
{
126+
"question_name": "snake_case_name",
127+
"class_name": "PascalCaseName",
128+
"method_name": "snake_case_method",
129+
"problem_number": "226",
130+
"problem_title": "Display Title",
131+
"difficulty": "Easy|Medium|Hard",
132+
"topics": "Comma, Separated, Topics",
133+
"tags": ["grind-75", "blind-75"],
134+
"problem_description": "Full description",
135+
"examples": [{"input": "...", "output": "..."}],
136+
"constraints": "Formatted constraints",
137+
"parameters": "typed_params: list[int]",
138+
"return_type": "TreeNode | None",
139+
"imports": "from leetcode_py.tree_node import TreeNode",
140+
"test_cases": [{"args": [...], "expected": ...}]
141+
}
142+
```
143+
144+
## Success Criteria
145+
146+
### Phase-by-Phase Validation (File-by-File)
147+
148+
1. ✅ **Phase 1**: `__init__.py` files generated correctly
149+
2. ✅ **Phase 2**: `solution.py` with modern syntax (`TreeNode | None`)
150+
3. ✅ **Phase 3**: `tests.py` with relative imports and clean structure
151+
4. ✅ **Phase 4**: `README.md` with clean formatting
152+
5. ✅ **Phase 5**: `playground.ipynb` with clean cells
153+
154+
### Multi-Problem Type Validation
155+
156+
5. ✅ Basic problems (array/string) generate correctly
157+
6. ✅ Tree problems generate correctly
158+
7. ✅ Different return types handled (`bool`, `int`, `str`, `list`, etc.)
159+
160+
### Automated Validation
161+
162+
8. ✅ Automated diff shows no differences vs `leetcode/.example/`
163+
9. ✅ `make q-validate` passes for all problem types
164+
10. ✅ Recreation from `.prompt/` works flawlessly
165+
11. ✅ All linting passes (`make lint`)
166+
12. ✅ Tests run successfully (`make q-test`)
167+
168+
## Files to Modify
169+
170+
### Template Files
171+
172+
1. `.templates/leetcode/{{cookiecutter.question_name}}/`
173+
- **Add**: `__init__.py` (empty file)
174+
- **Update**: `solution.py` (modern syntax, imports)
175+
- **Update**: `tests.py` (match `leetcode/.example/` format)
176+
- **Update**: `README.md` (match `leetcode/.example/` format)
177+
- **Update**: `playground.ipynb` (match structure)
178+
179+
### Configuration
180+
181+
2. `.templates/leetcode/cookiecutter.json`
182+
- Align with JSON5 examples
183+
- Add missing variables
184+
185+
### Generation Logic
186+
187+
3. `.templates/leetcode/gen.py`
188+
- Handle `__init__.py` generation
189+
- Maintain existing variable mapping
190+
191+
### Validation Tools
192+
193+
4. **Reusable**: `.amazonq/plan/compare_template_files.py` (handles both template and generated comparisons)
194+
5. **New**: Makefile target `make q-validate`
195+
196+
## Risk Mitigation
197+
198+
- **Incremental phases** prevent all-or-nothing failures
199+
- **Automated validation** catches regressions immediately
200+
- **Multi-problem testing** ensures template generalization
201+
- **Explicit file comparison** documents exact requirements
202+
203+
This plan ensures the template generates files that exactly match `leetcode/.example/` while maintaining the robust generation process described in the rules.

.amazonq/rules/development-rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
## Testing
1616

17-
- Test specific: `make test-question QUESTION=<name>`
17+
- Test specific: `make q-test QUESTION=<name>`
1818
- Test all: `make test`
1919
- Beautiful logging with loguru
2020

.templates/leetcode/cookiecutter.json renamed to .templates/leetcode/.example/cookiecutter.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
]
3030
},
3131
"param_names": "nums, target, expected",
32+
"param_names_with_types": "nums: list[int], target: int, expected: list[int]",
3233
"input_description": "nums={nums}, target={target}",
3334
"input_params": "nums, target",
3435
"expected_param": "expected",

.templates/leetcode/examples/basic.json5 renamed to .templates/leetcode/.example/examples/basic.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
// Test template variables (auto-generated, can be customized)
4444
param_names: "nums, target, expected",
45+
param_names_with_types: "nums: list[int], target: int, expected: list[int]",
4546
input_description: "nums={nums}, target={target}",
4647
input_params: "nums, target",
4748
expected_param: "expected",

.templates/leetcode/examples/tree.json5 renamed to .templates/leetcode/.example/examples/tree.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
// Tree-specific test setup
4242
param_names: "root, expected",
43+
param_names_with_types: "root: list[int], expected: list[int]",
4344
input_description: "root={root}",
4445
input_params: "root",
4546
expected_param: "expected",

.templates/leetcode/json/invert_binary_tree.json renamed to .templates/leetcode/.example/json/invert_binary_tree.json

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,26 @@
99
"tags": ["grind-75"],
1010
"problem_description": "Given the root of a binary tree, invert the tree, and return its root.",
1111
"examples": [
12-
{
13-
"input": "root = [4,2,7,1,3,6,9]",
14-
"output": "[4,7,2,9,6,3,1]"
15-
},
16-
{
17-
"input": "root = [2,1,3]",
18-
"output": "[2,3,1]"
19-
},
20-
{
21-
"input": "root = []",
22-
"output": "[]"
23-
}
12+
{ "input": "root = [4,2,7,1,3,6,9]", "output": "[4,7,2,9,6,3,1]" },
13+
{ "input": "root = [2,1,3]", "output": "[2,3,1]" },
14+
{ "input": "root = []", "output": "[]" }
2415
],
2516
"constraints": "- The number of nodes in the tree is in the range [0, 100].\n- -100 <= Node.val <= 100",
2617
"parameters": "root: TreeNode | None",
2718
"return_type": "TreeNode | None",
2819
"imports": "from leetcode_py.tree_node import TreeNode",
2920
"test_cases": [
30-
{
31-
"args": [[4, 2, 7, 1, 3, 6, 9]],
32-
"expected": [4, 7, 2, 9, 6, 3, 1]
33-
},
34-
{
35-
"args": [[2, 1, 3]],
36-
"expected": [2, 3, 1]
37-
},
38-
{
39-
"args": [[]],
40-
"expected": []
41-
}
21+
{ "args": [[4, 2, 7, 1, 3, 6, 9]], "expected": [4, 7, 2, 9, 6, 3, 1] },
22+
{ "args": [[2, 1, 3]], "expected": [2, 3, 1] },
23+
{ "args": [[]], "expected": [] }
4224
],
43-
"param_names": "root, expected",
44-
"input_description": "root={root}",
25+
"param_names": "root_list, expected",
26+
"param_names_with_types": "root_list: list[int | None], expected: list[int | None]",
27+
"input_description": "root_list={root_list}",
4528
"input_params": "root",
4629
"expected_param": "expected",
4730
"method_args": "root",
48-
"test_setup": "root = TreeNode.from_list(root)",
31+
"test_setup": "root = TreeNode.from_list(root_list)",
4932
"test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")",
5033
"assertion_code": "assert result == expected",
5134
"test_input_setup": "# Example test case\\nroot = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])",

0 commit comments

Comments
 (0)