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
10 changes: 5 additions & 5 deletions template/scripts/ai_tools/template_loader.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def substitute_variables(
"""Substitute template variables with actual values.

Args:
template: Template content with {{variable}} placeholders
template: Template content with {% raw %}{{variable}}{% endraw %} placeholders
session_id: Session ID for this task
task_name: Name of the task
task_type: Type of task
Expand All @@ -63,10 +63,10 @@ def substitute_variables(
timestamp = format_timestamp()

# Perform substitutions
result = template.replace("{{session_id}}", session_id)
result = result.replace("{{task_name}}", task_name)
result = result.replace("{{task_type}}", task_type)
result = result.replace("{{timestamp}}", timestamp)
result = template.replace({% raw %}"{{session_id}}"{% endraw %}, session_id)
result = result.replace({% raw %}"{{task_name}}"{% endraw %}, task_name)
result = result.replace({% raw %}"{{task_type}}"{% endraw %}, task_type)
result = result.replace({% raw %}"{{timestamp}}"{% endraw %}, timestamp)

return result

Expand Down
114 changes: 114 additions & 0 deletions template/tests/ai_tools/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Shared fixtures for AI tools tests."""

from __future__ import annotations

from pathlib import Path

import pytest


@pytest.fixture
def temp_context_dir(tmp_path: Path) -> Path:
"""Create temporary .ai-context directory.

Args:
tmp_path: Pytest temporary path fixture

Returns:
Path to temporary .ai-context directory
"""
context_dir = tmp_path / ".ai-context"
context_dir.mkdir()
(context_dir / "sessions").mkdir()
(context_dir / "sessions" / "archive").mkdir()

# Create basic context files
(context_dir / "LAST_SESSION_SUMMARY.md").write_text(
"# Last Session Summary\n\nNo sessions yet.\n"
)
(context_dir / "ACTIVE_TASKS.md").write_text(
"# Active Tasks\n\n## In Progress\n\n## Blocked\n\n## Completed\n\n"
)
(context_dir / "RECENT_DECISIONS.md").write_text("# Recent Decisions\n\n")
(context_dir / "CONVENTIONS.md").write_text("# Conventions\n\n")

return context_dir


@pytest.fixture
def sample_session_files(temp_context_dir: Path) -> dict[str, Path]:
"""Create sample session files for testing.

Args:
temp_context_dir: Temporary context directory

Returns:
Dictionary with keys 'plan', 'summary', 'execution' and Path values
"""
session_id = "20251102150000"
slug = "test-task"

plan_file = temp_context_dir / "sessions" / f"{session_id}-PLAN-{slug}.md"
summary_file = temp_context_dir / "sessions" / f"{session_id}-SUMMARY-{slug}.md"
execution_file = temp_context_dir / "sessions" / f"{session_id}-EXECUTION-{slug}.md"

# Write sample content
plan_file.write_text(
"""# Task Plan: Test Task

**Session ID**: 20251102150000
**Created**: 2025-11-02 15:00:00
**Task Type**: feature
**Status**: 🚧 In Progress

---

## Implementation Steps

### Phase 1: Write Tests (TDD)
- [ ] Identify test cases
- [ ] Write test file(s)
- [ ] Run tests to confirm they fail

### Phase 2: Implementation
- [ ] Implement functionality
- [ ] Run tests to confirm they pass
"""
)

summary_file.write_text(
"""# Task Summary: Test Task

**Session ID**: 20251102150000
**Created**: 2025-11-02 15:00:00
**Status**: 🚧 In Progress

---

## What Was Done

[To be filled at end of session]
"""
)

execution_file.write_text(
"""# Execution Log: Test Task

**Session ID**: 20251102150000
**Started**: 2025-11-02 15:00:00

---

## Log

[2025-11-02 15:00:00] 🎯 Task started: Test Task
[2025-11-02 15:00:00] 📚 Context loaded successfully
[2025-11-02 15:00:00] ✅ Session files created
"""
)

return {
"plan": plan_file,
"summary": summary_file,
"execution": execution_file,
}
12 changes: 6 additions & 6 deletions template/tests/ai_tools/test_template_loader.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,31 @@ class TestSubstituteVariables:

def test_substitute_session_id(self) -> None:
"""Test substituting session ID variable."""
template = "Session: {{session_id}}"
template = {% raw %}"Session: {{session_id}}"{% endraw %}
result = substitute_variables(
template, session_id="20251103120000", task_name="Test", task_type="feature"
)
assert result == "Session: 20251103120000"

def test_substitute_task_name(self) -> None:
"""Test substituting task name variable."""
template = "Task: {{task_name}}"
template = {% raw %}"Task: {{task_name}}"{% endraw %}
result = substitute_variables(
template, session_id="123", task_name="Add validation", task_type="feature"
)
assert result == "Task: Add validation"

def test_substitute_task_type(self) -> None:
"""Test substituting task type variable."""
template = "Type: {{task_type}}"
template = {% raw %}"Type: {{task_type}}"{% endraw %}
result = substitute_variables(
template, session_id="123", task_name="Test", task_type="bugfix"
)
assert result == "Type: bugfix"

def test_substitute_timestamp(self) -> None:
"""Test substituting timestamp variable."""
template = "Created: {{timestamp}}"
template = {% raw %}"Created: {{timestamp}}"{% endraw %}
result = substitute_variables(
template, session_id="123", task_name="Test", task_type="feature"
)
Expand All @@ -88,7 +88,7 @@ class TestSubstituteVariables:

def test_substitute_multiple_variables(self) -> None:
"""Test substituting multiple variables at once."""
template = "{{session_id}}: {{task_name}} ({{task_type}})"
template = {% raw %}"{{session_id}}: {{task_name}} ({{task_type}})"{% endraw %}
result = substitute_variables(
template,
session_id="20251103120000",
Expand All @@ -99,7 +99,7 @@ class TestSubstituteVariables:

def test_substitute_same_variable_multiple_times(self) -> None:
"""Test substituting same variable appearing multiple times."""
template = "Start: {{task_name}}, End: {{task_name}}"
template = {% raw %}"Start: {{task_name}}, End: {{task_name}}"{% endraw %}
result = substitute_variables(
template, session_id="123", task_name="Build", task_type="feature"
)
Expand Down
25 changes: 16 additions & 9 deletions template/tests/test_dependencies.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@ def test_dev_dependency_versions_are_realistic() -> None:
dev_deps: list[str] = data["dependency-groups"]["dev"]

expected = [
"black>=24.8.0",
"isort>=5.13.2",
"mypy>=1.11.2",
"pre-commit>=3.8.0",
"pylint>=3.2.7",
"pytest>=8.3.3",
"pytest-cov>=4.1.0",
"pytest-mock>=3.14.0",
"ruff>=0.6.5",
"black>=25.9.0",
"isort>=7.0.0",
"mypy>=1.18.2",
{%- if include_pre_commit %}
"pre-commit>=4.3.0",
{%- endif %}
"pylint>=4.0.2",
"pytest>=8.4.2",
"pytest-cov>=7.0.0",
"pytest-mock>=3.15.1",
"ruff>=0.14.3",
{%- if include_docs %}
"mkdocs>=1.6.0",
"mkdocs-material>=9.5.0",
"mkdocstrings[python]>=0.26.0",
{%- endif %}
]

assert dev_deps == expected
21 changes: 12 additions & 9 deletions template/tests/test_docs.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent
@pytest.mark.parametrize(
("relative_path", "required_phrases", "forbidden_phrases"),
[
{%- if include_ai_tools %}
(
"AI_DOCS/project-context.md",
["Modern Python Project Template"],
Expand All @@ -37,16 +38,15 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent
["{{ package_name }}"],
["leadership_blog_generator"],
),
(
"template/AGENTS.md.jinja",
["{{ package_name }}"],
["leadership_blog_generator"],
),
(
"template/AI_DOCS/project-context.md.jinja",
["{{ package_name }}"],
{%- else %}
# No AI docs to check when AI tools are disabled
pytest.param(
"README.md",
["{{ project_name }}"],
["leadership_blog_generator"],
marks=pytest.mark.skip(reason="No AI docs when AI tools disabled"),
),
{%- endif %}
],
)
def test_documentation_is_template_specific(
Expand All @@ -73,13 +73,16 @@ def test_no_legacy_identifier_left() -> None:
banned = "leadership_blog_generator"
allowed: set[Path] = {PROJECT_ROOT / "tests" / "test_docs.py"}
search_roots = [
{%- if include_ai_tools %}
PROJECT_ROOT / "AI_DOCS",
PROJECT_ROOT / "template",
PROJECT_ROOT / ".ai-context",
{%- endif %}
PROJECT_ROOT / ".github",
PROJECT_ROOT / "src",
PROJECT_ROOT / "tests",
{%- if include_quality_scripts or include_ai_tools %}
PROJECT_ROOT / "scripts",
{%- endif %}
PROJECT_ROOT,
]

Expand Down
4 changes: 2 additions & 2 deletions template/tests/test_makefile.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def test_run_target_invokes_template_entry_point() -> None:
content = (PROJECT_ROOT / "Makefile").read_text(encoding="utf-8")

missing_entry_point = (
"Makefile run target should call {{ project_name }} script"
"Makefile run target should call {{ package_name }} script"
)
assert "uv run {{ project_name }}" in content, missing_entry_point
assert "uv run {{ package_name }}" in content, missing_entry_point

legacy_entry_point = "Legacy run target still references leadership-blog-generator"
assert "uv run leadership-blog-generator" not in content, legacy_entry_point
Loading