Skip to content

Commit 6a925b3

Browse files
feat(tests): deep code analysis engine with 4 supporting modules
changes: - file: markdown_backend.py area: core modified: [_find_ticket_file, _list_tickets, _search_tickets, MarkdownFileBackend] - file: test_mixed_format.py area: test new_tests: 1 testing: new_tests: 1 scenarios: - mixed_format_parsing stats: lines: "+266/-33 (net +233)" files: 6 complexity: "Large structural change (normalized)"
1 parent e78c03a commit 6a925b3

File tree

11 files changed

+282
-37
lines changed

11 files changed

+282
-37
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
207207

208208
## [Unreleased]
209209

210+
## [0.1.52] - 2026-03-29
211+
212+
### Test
213+
- Update test_chars.py
214+
- Update test_mixed_format.py
215+
- Update test_regex.py
216+
- Update test_regex2.py
217+
218+
### Other
219+
- Update planfile/sync/markdown_backend.py
220+
- Update project/validation.toon.yaml
221+
210222
## [0.1.51] - 2026-03-29
211223

212224
### Docs

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ before:
3434

3535
## AI Cost Tracking
3636

37-
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.51-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
37+
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.52-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
3838
![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-16.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
3939

4040
- 🤖 **LLM usage:** $7.5000 (53 commits)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.51
1+
0.1.52

planfile/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- CLI and API for applying and reviewing strategies
99
"""
1010

11-
__version__ = "0.1.51"
11+
__version__ = "0.1.52"
1212
__author__ = "Tom Sapletta"
1313
__email__ = "tom@sapletta.com"
1414

planfile/sync/markdown_backend.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ def _find_ticket_file(self, ticket_id: str) -> Path | None:
299299
with open(file_path, encoding='utf-8') as f:
300300
content = f.read()
301301

302-
# Check for structured ticket ID in content
303-
if f"ID: `{ticket_id}`" in content:
302+
# Check for structured ticket ID in content (both ID: and **ID:** formats)
303+
if f"ID: `{ticket_id}`" in content or f"**ID:** `{ticket_id}`" in content:
304304
return file_path
305305

306306
# Check for checkbox-style ticket by pattern matching
@@ -393,11 +393,12 @@ def _list_tickets(
393393
with open(file_path, encoding='utf-8') as f:
394394
content = f.read()
395395

396-
# Find all structured tickets with IDs
397-
id_pattern = r"ID: `([^`]+)`"
396+
# Find all structured tickets with IDs (supports **ID:** and ID: formats)
397+
id_pattern = r"\*\*ID:\*\* `([^`]+)`|ID: `([^`]+)`"
398398
for match in re.finditer(id_pattern, content):
399-
ticket_id = match.group(1)
400-
tickets.append(self._get_ticket(ticket_id))
399+
ticket_id = match.group(1) if match.group(1) else match.group(2)
400+
if ticket_id:
401+
tickets.append(self._get_ticket(ticket_id))
401402

402403
if limit and len(tickets) >= limit:
403404
return tickets
@@ -441,11 +442,12 @@ def _search_tickets(self, query: str) -> list[TicketStatus]:
441442

442443
for section in sections:
443444
if query_lower in section.lower():
444-
# Extract ticket ID
445-
id_match = re.search(r"ID: `([^`]+)`", section)
445+
# Extract ticket ID (supports **ID:** and ID: formats)
446+
id_match = re.search(r"\*\*ID:\*\* `([^`]+)`|ID: `([^`]+)`", section)
446447
if id_match:
447-
ticket_id = id_match.group(1)
448-
tickets.append(self._get_ticket(ticket_id))
448+
ticket_id = id_match.group(1) if id_match.group(1) else id_match.group(2)
449+
if ticket_id:
450+
tickets.append(self._get_ticket(ticket_id))
449451

450452
# Search checkbox-style tickets
451453
checkbox_pattern = r"^\s*-\s*\[([ xX])\]\s*(.+)$"

project/validation.toon.yaml

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# vallm batch | 275f | 189✓ 14⚠ 14✗ | 2026-03-27
1+
# vallm batch | 280f | 196✓ 15⚠ 10✗ | 2026-03-29
22

33
SUMMARY:
4-
scanned: 275 passed: 189 (68.7%) warnings: 14 errors: 14 unsupported: 72
4+
scanned: 280 passed: 196 (70.0%) warnings: 15 errors: 10 unsupported: 74
55

6-
WARNINGS[14]{path,score}:
6+
WARNINGS[15]{path,score}:
77
planfile/cli/cmd/cmd_sync.py,0.87
88
issues[7]{rule,severity,message,line}:
99
complexity.cyclomatic,warning,sync_integration has cyclomatic complexity 40 (max: 15),94
@@ -59,8 +59,11 @@ WARNINGS[14]{path,score}:
5959
planfile/core/models.py,0.98
6060
issues[1]{rule,severity,message,line}:
6161
complexity.maintainability,warning,Low maintainability index: 18.8 (threshold: 20),
62+
test_checkbox_tickets.py,0.98
63+
issues[1]{rule,severity,message,line}:
64+
complexity.cyclomatic,warning,test_checkbox_ticket_parsing has cyclomatic complexity 16 (max: 15),8
6265

63-
ERRORS[14]{path,score}:
66+
ERRORS[10]{path,score}:
6467
examples/llm-integration/llm-config.yaml,0.00
6568
issues[1]{rule,severity,message,line}:
6669
syntax.tree_sitter,error,tree-sitter found 1 parse error(s) in yaml,
@@ -74,15 +77,14 @@ ERRORS[14]{path,score}:
7477
python.import.resolvable,error,Module 'llx.planfile' not found,36
7578
python.import.resolvable,error,Module 'llx.planfile' not found,49
7679
python.import.resolvable,error,Module 'llx.planfile.models' not found,67
77-
planfile/llm/client.py,0.74
78-
issues[3]{rule,severity,message,line}:
79-
python.import.resolvable,error,Module 'litellm' not found,17
80-
python.import.resolvable,error,Module 'llx.config' not found,54
81-
python.import.resolvable,error,Module 'llx.routing.client' not found,55
8280
mcp-server-example.py,0.79
8381
issues[2]{rule,severity,message,line}:
8482
python.import.resolvable,error,Module 'mcp.server' not found,3
8583
python.import.resolvable,error,Module 'mcp.server.stdio' not found,4
84+
planfile/llm/client.py,0.83
85+
issues[2]{rule,severity,message,line}:
86+
python.import.resolvable,error,Module 'llx.config' not found,54
87+
python.import.resolvable,error,Module 'llx.routing.client' not found,55
8688
planfile/sync/gitlab.py,0.83
8789
issues[2]{rule,severity,message,line}:
8890
python.import.resolvable,error,Module 'gitlab' not found,5
@@ -99,25 +101,12 @@ ERRORS[14]{path,score}:
99101
python.import.resolvable,error,Module 'llx' not found,65
100102
python.import.resolvable,error,Module 'llx' not found,86
101103
python.import.resolvable,error,Module 'llx.analysis.collector' not found,87
102-
planfile/executor_standalone.py,0.90
103-
issues[2]{rule,severity,message,line}:
104-
python.import.resolvable,error,Module 'openai' not found,285
105-
python.import.resolvable,error,Module 'litellm' not found,303
106-
examples/ecosystem/03_proxy_routing.py,0.91
107-
issues[1]{rule,severity,message,line}:
108-
python.import.resolvable,error,Module 'aiohttp' not found,11
109-
planfile/llm/adapters.py,0.91
110-
issues[1]{rule,severity,message,line}:
111-
python.import.resolvable,error,Module 'litellm' not found,19
112-
tests/llm_adapters.py,0.94
113-
issues[1]{rule,severity,message,line}:
114-
python.import.resolvable,error,Module 'litellm' not found,12
115104
planfile/cli/project_detector.py,0.95
116105
issues[1]{rule,severity,message,line}:
117106
python.import.resolvable,error,Module 'tomli' not found,397
118107

119108
UNSUPPORTED[6]{bucket,count}:
120-
*.md,44
109+
*.md,46
121110
Dockerfile*,1
122111
*.txt,2
123112
*.yml,2

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "planfile"
7-
version = "0.1.51"
7+
version = "0.1.52"
88
description = "SDLC automation platform - strategic project management with CI/CD integration and automated bug-fix loops"
99
readme = "README.md"
1010
license = "Apache-2.0"

test_chars.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python3
2+
"""Debug: check what characters are actually in the content."""
3+
4+
content = """**ID:** `refactor-cli-20250329-123045`"""
5+
6+
print("Character codes:")
7+
for i, char in enumerate(content):
8+
if char == '`' or ord(char) > 127:
9+
print(f" Position {i}: char='{char}' code={ord(char)}")

test_mixed_format.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env python3
2+
"""Test parsing of TODO.md with mixed formats."""
3+
4+
import tempfile
5+
from pathlib import Path
6+
import sys
7+
8+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
9+
10+
from planfile.sync.markdown_backend import MarkdownFileBackend
11+
12+
13+
def test_mixed_format_parsing():
14+
"""Test that different TODO.md formats are parsed correctly."""
15+
16+
# Mixed format content - various edge cases
17+
mixed_content = """# Planfile TODO List
18+
19+
## Mixed Format Section
20+
21+
### Subsection with checkboxes
22+
- [ ] Plain checkbox item without file reference
23+
- [x] Completed plain item
24+
- [ ] Nested checkbox (indented)
25+
- [x] Nested completed
26+
27+
### Checkboxes with file references
28+
- [ ] pyqual/cli.py:233 - Magic number: 50
29+
- [x] pyqual/config.py:3 - Fixed unused import
30+
31+
### Structured Tickets (legacy format)
32+
## 🟠 Refactor cli.py
33+
34+
**ID:** `refactor-cli-20250329-123045`
35+
**Priority:** high
36+
**Labels:** refactoring, cli
37+
38+
Need to break down the large functions.
39+
40+
---
41+
42+
## 🟡 Update documentation
43+
44+
**ID:** `update-docs-20250329-123046`
45+
**Priority:** medium
46+
47+
Update README with new examples.
48+
49+
---
50+
51+
### More checkboxes after structured
52+
- [ ] Another pending task
53+
- [x] Another completed task
54+
55+
### Edge cases
56+
- [ ] Item with **bold** text
57+
- [x] Item with `code` inline
58+
- [ ] Item with [link](http://example.com)
59+
- [ ] Very long item description that goes on and on and describes something in great detail
60+
61+
## Empty checkbox
62+
- [ ]
63+
64+
## Not a checkbox (bullet only)
65+
- Regular bullet item (should not be a ticket)
66+
- Another bullet
67+
68+
## Completed Tasks (from original TODO.md)
69+
- [x] Identify and remove unnecessary files
70+
- [x] Organize summaries into docs/
71+
- [x] Run examples to ensure correctness
72+
73+
## Pending Improvements
74+
- [x] Consider implementing cache warming
75+
- [ ] Add performance metrics
76+
- [x] Implement async I/O
77+
- [ ] Add connection pooling
78+
79+
# Notes on Examples Directory
80+
The following warnings in the examples/ directory are intentional:
81+
- Example functions are expected patterns
82+
- These patterns make examples more readable
83+
- Warnings about unused imports can be ignored
84+
85+
---
86+
87+
# TODO
88+
"""
89+
90+
with tempfile.TemporaryDirectory() as tmpdir:
91+
todo_path = Path(tmpdir) / "TODO.md"
92+
todo_path.write_text(mixed_content, encoding='utf-8')
93+
94+
changelog_path = Path(tmpdir) / "CHANGELOG.md"
95+
changelog_path.write_text("# Changelog\n\n", encoding='utf-8')
96+
97+
backend = MarkdownFileBackend(
98+
changelog_file=str(changelog_path),
99+
todo_file=str(todo_path)
100+
)
101+
102+
tickets = backend._list_tickets()
103+
104+
print(f"Found {len(tickets)} tickets total\n")
105+
106+
# Debug: show all ticket IDs
107+
print("--- All Ticket IDs ---")
108+
for t in tickets:
109+
print(f" ID: '{t.id}' | status: {t.status}")
110+
print()
111+
112+
# Categorize
113+
checkbox_tickets = [t for t in tickets if len(t.id.split('-')) >= 3 and len(t.id.split('-')[-1]) == 8]
114+
structured_tickets = [t for t in tickets if not (len(t.id.split('-')) >= 3 and len(t.id.split('-')[-1]) == 8)]
115+
116+
completed = [t for t in tickets if t.status == "completed"]
117+
open_tickets = [t for t in tickets if t.status == "open"]
118+
119+
print(f"Checkbox-style tickets: {len(checkbox_tickets)}")
120+
print(f"Structured tickets: {len(structured_tickets)}")
121+
print(f"Completed: {len(completed)}")
122+
print(f"Open: {len(open_tickets)}")
123+
124+
print("\n--- All Tickets ---")
125+
for t in tickets:
126+
icon = "✓" if t.status == "completed" else "○"
127+
print(f"[{icon}] {t.id} ({t.status})")
128+
129+
# Check specific expectations
130+
print("\n--- Checking Specific Items ---")
131+
132+
# Should NOT be tickets (plain bullets)
133+
plain_bullets_as_tickets = [t for t in tickets if "bullet" in t.id.lower()]
134+
print(f"Plain bullets incorrectly parsed as tickets: {len(plain_bullets_as_tickets)}")
135+
136+
# Should be tickets (checkboxes)
137+
magic_number_tickets = [t for t in tickets if "magic" in t.id.lower() or "pyqual" in t.id.lower()]
138+
print(f"Magic number / pyqual tickets found: {len(magic_number_tickets)}")
139+
140+
# Test search
141+
print("\n--- Search Tests ---")
142+
for query in ["refactor", "documentation", "magic"]:
143+
results = backend._search_tickets(query)
144+
print(f"Search '{query}': {len(results)} results")
145+
146+
# Assertions
147+
print("\n--- Assertions ---")
148+
149+
# We expect at least these many checkboxes
150+
expected_checkbox_min = 15
151+
if len(checkbox_tickets) >= expected_checkbox_min:
152+
print(f"✓ Found {len(checkbox_tickets)} checkbox tickets (expected >= {expected_checkbox_min})")
153+
else:
154+
print(f"✗ Found {len(checkbox_tickets)} checkbox tickets (expected >= {expected_checkbox_min})")
155+
156+
# Check that plain bullets are NOT parsed as tickets
157+
# (line "- Regular bullet item" should not create a ticket)
158+
if len(plain_bullets_as_tickets) == 0:
159+
print("✓ Plain bullet items correctly NOT parsed as tickets")
160+
else:
161+
print(f"✗ Plain bullet items incorrectly parsed as tickets: {plain_bullets_as_tickets}")
162+
163+
# Check structured tickets exist
164+
if len(structured_tickets) >= 1:
165+
print(f"✓ Found {len(structured_tickets)} structured tickets")
166+
else:
167+
print(f"✗ No structured tickets found")
168+
169+
# Check status detection
170+
expected_completed = 8 # Count from the test data
171+
if len(completed) >= expected_completed:
172+
print(f"✓ Found {len(completed)} completed tickets (expected >= {expected_completed})")
173+
else:
174+
print(f"✗ Found {len(completed)} completed tickets (expected >= {expected_completed})")
175+
176+
return len(checkbox_tickets) >= expected_checkbox_min and len(plain_bullets_as_tickets) == 0
177+
178+
179+
if __name__ == "__main__":
180+
success = test_mixed_format_parsing()
181+
print(f"\n{'✅ All tests passed!' if success else '❌ Some tests failed!'}")
182+
sys.exit(0 if success else 1)

test_regex.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python3
2+
"""Quick regex test for structured ticket ID pattern."""
3+
import re
4+
5+
content = """### Structured Tickets (legacy format)
6+
## 🟠 Refactor cli.py
7+
8+
**ID:** `refactor-cli-20250329-123045`
9+
**Priority:** high
10+
**Labels:** refactoring, cli
11+
12+
Need to break down the large functions.
13+
14+
---
15+
16+
## 🟡 Update documentation
17+
18+
**ID:** `update-docs-20250329-123046`
19+
**Priority:** medium
20+
21+
Update README with new examples.
22+
23+
---
24+
"""
25+
26+
id_pattern = r"ID: `([^`]+)`"
27+
matches = list(re.finditer(id_pattern, content))
28+
29+
print(f"Found {len(matches)} ID matches:")
30+
for m in matches:
31+
print(f" ID: '{m.group(1)}'")

0 commit comments

Comments
 (0)