diff --git a/.ai-context/LAST_SESSION_SUMMARY.md b/.ai-context/LAST_SESSION_SUMMARY.md index 7ef628d..3783bfe 100644 --- a/.ai-context/LAST_SESSION_SUMMARY.md +++ b/.ai-context/LAST_SESSION_SUMMARY.md @@ -31,7 +31,7 @@ Reviewed all template/docs files for relevance. All files are relevant. Converte ## Files Changed -- No files tracked +- `validate_ai_docs_sync.py` --- diff --git a/AGENTS.md b/AGENTS.md index 82aaf4c..42ec01a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -251,7 +251,7 @@ Before committing code, ensure: 7. ✅ **Type hints everywhere**: mypy strict mode 8. ✅ **No security issues**: CI runs Bandit + Safety 9. ✅ **AI documentation synchronized** (MANDATORY) - - Run: `uv run python src/python_modern_template/validate_ai_docs_sync.py` + - Run: `uv run ai-validate-docs` - Update manual sync files if AI_DOCS changed - Update .claude/skills or .claude/agents if relevant - Update template files if changes apply to new projects diff --git a/AI_DOCS/documentation-sync-rules.md b/AI_DOCS/documentation-sync-rules.md index 594f991..7e2b64d 100644 --- a/AI_DOCS/documentation-sync-rules.md +++ b/AI_DOCS/documentation-sync-rules.md @@ -65,7 +65,7 @@ For agents that **support references** (Claude, Cursor, Aider, Gemini/AGENTS.md) - [ ] Ensure references point to correct files - [ ] Validate no broken references -**Script:** `uv run python src/python_modern_template/validate_ai_docs_sync.py` +**Script:** `uv run ai-validate-docs` ### Step 3: Update Non-Supporting Agents @@ -114,7 +114,7 @@ For agents that **don't support references** (Gemini/styleguide, Copilot): **Run validation script:** ```bash -uv run python src/python_modern_template/validate_ai_docs_sync.py +uv run ai-validate-docs ``` **Should report:** @@ -191,7 +191,7 @@ uv run python src/python_modern_template/validate_ai_docs_sync.py ## Validation Script -**Location:** `src/python_modern_template/validate_ai_docs_sync.py` +**Location:** `scripts/ai_tools/validate_ai_docs_sync.py` **What it checks:** 1. All `@AI_DOCS/` references point to existing files @@ -203,7 +203,7 @@ uv run python src/python_modern_template/validate_ai_docs_sync.py **Usage:** ```bash # Run validation -uv run python src/python_modern_template/validate_ai_docs_sync.py +uv run ai-validate-docs # Expected output (all passing): ✅ All AI_DOCS files found @@ -232,8 +232,7 @@ uv run python src/python_modern_template/validate_ai_docs_sync.py - Quality tests (5 files): `tests/quality/*.py` → `template/tests/quality/*.py.jinja` - Project tests (4 files): `tests/test_*.py` → `template/tests/test_*.py.jinja` - Quality scripts (6 files): `scripts/quality/*.py` → `template/scripts/quality/*.py.jinja` -- AI tools scripts (11 files): `scripts/ai_tools/*.py` → `template/scripts/ai_tools/*.py.jinja` -- Source modules (1 file): `src/{{ package_name }}/validate_ai_docs_sync.py.jinja` +- AI tools scripts (12 files): `scripts/ai_tools/*.py` → `template/scripts/ai_tools/*.py.jinja` **Note:** AI tools tests are NOT synced to the template. They remain in the template repository only, as they test template infrastructure that users typically won't modify. @@ -253,8 +252,7 @@ python sync_template.py **When to run:** 1. After modifying any test files in `tests/` 2. After modifying any scripts in `scripts/quality/` or `scripts/ai_tools/` -3. After modifying `src/{{ package_name }}/validate_ai_docs_sync.py` -4. Before releasing a new template version +3. Before releasing a new template version **What it doesn't sync (intentionally):** - AI_DOCS/*.md files (already synced via separate workflow) @@ -306,7 +304,7 @@ In `AI_DOCS/code-conventions.md` and all agent configs, add: - [ ] `make lint` passes - [ ] `make check` passes - [ ] **AI documentation synchronized** ⭐ NEW - - [ ] Validated with `validate_ai_docs_sync.py` + - [ ] Validated with `uv run ai-validate-docs` - [ ] Updated manual sync files if needed - [ ] Updated .claude/skills or .claude/agents if needed - [ ] Updated template files if needed @@ -314,7 +312,7 @@ In `AI_DOCS/code-conventions.md` and all agent configs, add: ### Integration with ai-finish-task Before `ai-finish-task` completes, it should: -1. Run `validate_ai_docs_sync.py` +1. Run `uv run ai-validate-docs` 2. If validation fails, prompt to fix or continue with `--yes` 3. Log validation results to EXECUTION file @@ -363,8 +361,8 @@ project/ │ ├── project-context.md.jinja │ └── documentation-sync-rules.md.jinja │ -└── src/python_modern_template/ - └── validate_ai_docs_sync.py # Validation script +└── scripts/ai_tools/ + └── validate_ai_docs_sync.py # Validation script (CLI: ai-validate-docs) ``` --- @@ -411,7 +409,7 @@ project/ 1. **Use the validation script** ```bash # After any doc change - uv run python src/python_modern_template/validate_ai_docs_sync.py + uv run ai-validate-docs ``` 2. **Reference, don't duplicate** @@ -437,7 +435,7 @@ project/ # Correct: Update both vim AI_DOCS/code-conventions.md vim template/AI_DOCS/code-conventions.md.jinja - uv run python src/python_modern_template/validate_ai_docs_sync.py + uv run ai-validate-docs ``` --- @@ -448,7 +446,7 @@ project/ **Quick Checklist:** 1. ✅ Update the changed file -2. ✅ Run `validate_ai_docs_sync.py` +2. ✅ Run `uv run ai-validate-docs` 3. ⚠️ Update manual sync files if needed (Gemini/Copilot) 4. ✅ Update .claude/skills or .claude/agents if relevant 5. ✅ Update template files diff --git a/CLAUDE.md b/CLAUDE.md index 2dccc0a..7e7cab2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -302,7 +302,7 @@ Before completing any task: - [ ] Formatter harmony check (Black vs Ruff) — adjust code (e.g., use message variables) if tools disagree - [ ] **AI documentation synchronized** (MANDATORY) - - [ ] Run `uv run python src/python_modern_template/validate_ai_docs_sync.py` + - [ ] Run `uv run ai-validate-docs` - [ ] Update `.gemini/styleguide.md` if AI_DOCS changed (add sync date) - [ ] Update `.github/copilot-instructions.md` if AI_DOCS changed (add sync date) - [ ] Update `.claude/skills/` or `.claude/agents/` if relevant diff --git a/pyproject.toml b/pyproject.toml index 4c0bf34..3fa1d26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ ai-context-summary = "scripts.ai_tools.context_summary:main" ai-check-conflicts = "scripts.ai_tools.check_conflicts:main" ai-add-decision = "scripts.ai_tools.add_decision:main" ai-add-convention = "scripts.ai_tools.add_convention:main" +ai-validate-docs = "scripts.ai_tools.validate_ai_docs_sync:main" # Quality Tools - Single source of truth for quality checks quality-format = "scripts.quality.format:main" diff --git a/src/python_modern_template/validate_ai_docs_sync.py b/scripts/ai_tools/validate_ai_docs_sync.py similarity index 99% rename from src/python_modern_template/validate_ai_docs_sync.py rename to scripts/ai_tools/validate_ai_docs_sync.py index 7829056..bf23a89 100644 --- a/src/python_modern_template/validate_ai_docs_sync.py +++ b/scripts/ai_tools/validate_ai_docs_sync.py @@ -271,7 +271,7 @@ def generate_sync_report(issues: list[dict[str, str]]) -> str: 3. **Verify fixes:** ```bash - python -m python_modern_template.validate_ai_docs_sync + uv run ai-validate-docs ``` """ diff --git a/sync_template.py b/sync_template.py index eb3c0d7..c4d73c5 100644 --- a/sync_template.py +++ b/sync_template.py @@ -171,6 +171,7 @@ def main() -> None: "template_loader.py", "update_plan.py", "utils.py", + "validate_ai_docs_sync.py", ], PROJECT_ROOT / "scripts" / "ai_tools", TEMPLATE_ROOT / "scripts" / "ai_tools", @@ -185,14 +186,6 @@ def main() -> None: sync_file(scripts_init_src, scripts_init_dst, add_jinja_ext=False) total += 1 - # Sync src module files - total += sync_file_list( - ["validate_ai_docs_sync.py"], - PROJECT_ROOT / "src" / "python_modern_template", - TEMPLATE_ROOT / "src" / "{{ package_name }}", - "📦 Syncing src module files...", - ) - print("\n" + "=" * 70) print(f"✅ SYNC COMPLETE: {total} files synced") print("=" * 70) diff --git a/template/AI_DOCS/documentation-sync-rules.md.jinja b/template/AI_DOCS/documentation-sync-rules.md.jinja index 724c07f..b75e84e 100644 --- a/template/AI_DOCS/documentation-sync-rules.md.jinja +++ b/template/AI_DOCS/documentation-sync-rules.md.jinja @@ -65,7 +65,7 @@ For agents that **support references** (Claude, Cursor, Aider, Gemini/AGENTS.md) - [ ] Ensure references point to correct files - [ ] Validate no broken references -**Script:** `uv run python src/{{ package_name }}/validate_ai_docs_sync.py` +**Script:** `uv run ai-validate-docs` ### Step 3: Update Non-Supporting Agents @@ -114,7 +114,7 @@ For agents that **don't support references** (Gemini/styleguide, Copilot): **Run validation script:** ```bash -uv run python src/{{ package_name }}/validate_ai_docs_sync.py +uv run ai-validate-docs ``` **Should report:** @@ -191,7 +191,7 @@ uv run python src/{{ package_name }}/validate_ai_docs_sync.py ## Validation Script -**Location:** `src/{{ package_name }}/validate_ai_docs_sync.py` +**Location:** `scripts/ai_tools/validate_ai_docs_sync.py` **What it checks:** 1. All `@AI_DOCS/` references point to existing files @@ -203,7 +203,7 @@ uv run python src/{{ package_name }}/validate_ai_docs_sync.py **Usage:** ```bash # Run validation -uv run python src/{{ package_name }}/validate_ai_docs_sync.py +uv run ai-validate-docs # Expected output (all passing): ✅ All AI_DOCS files found @@ -232,8 +232,7 @@ uv run python src/{{ package_name }}/validate_ai_docs_sync.py - Quality tests (5 files): `tests/quality/*.py` → `template/tests/quality/*.py.jinja` - Project tests (4 files): `tests/test_*.py` → `template/tests/test_*.py.jinja` - Quality scripts (6 files): `scripts/quality/*.py` → `template/scripts/quality/*.py.jinja` -- AI tools scripts (11 files): `scripts/ai_tools/*.py` → `template/scripts/ai_tools/*.py.jinja` -- Source modules (1 file): `src/{{ package_name }}/validate_ai_docs_sync.py.jinja` +- AI tools scripts (12 files): `scripts/ai_tools/*.py` → `template/scripts/ai_tools/*.py.jinja` **Note:** AI tools tests are NOT synced to the template. They remain in the template repository only, as they test template infrastructure that users typically won't modify. @@ -253,7 +252,7 @@ python sync_template.py **When to run:** 1. After modifying any test files in `tests/` 2. After modifying any scripts in `scripts/quality/` or `scripts/ai_tools/` -3. After modifying `src/{{ package_name }}/validate_ai_docs_sync.py` +3. After modifying `scripts/ai_tools/validate_ai_docs_sync.py` 4. Before releasing a new template version **What it doesn't sync (intentionally):** @@ -306,7 +305,7 @@ In `AI_DOCS/code-conventions.md` and all agent configs, add: - [ ] `make lint` passes - [ ] `make check` passes - [ ] **AI documentation synchronized** ⭐ NEW - - [ ] Validated with `validate_ai_docs_sync.py` + - [ ] Validated with `uv run ai-validate-docs` - [ ] Updated manual sync files if needed - [ ] Updated .claude/skills or .claude/agents if needed - [ ] Updated template files if needed @@ -314,7 +313,7 @@ In `AI_DOCS/code-conventions.md` and all agent configs, add: ### Integration with ai-finish-task Before `ai-finish-task` completes, it should: -1. Run `validate_ai_docs_sync.py` +1. Run `uv run ai-validate-docs` 2. If validation fails, prompt to fix or continue with `--yes` 3. Log validation results to EXECUTION file @@ -363,8 +362,8 @@ project/ │ ├── project-context.md.jinja │ └── documentation-sync-rules.md.jinja │ -└── src/{{ package_name }}/ - └── validate_ai_docs_sync.py # Validation script +└── scripts/ai_tools/ + └── validate_ai_docs_sync.py # Validation script (CLI: ai-validate-docs) ``` --- @@ -411,7 +410,7 @@ project/ 1. **Use the validation script** ```bash # After any doc change - uv run python src/{{ package_name }}/validate_ai_docs_sync.py + uv run ai-validate-docs ``` 2. **Reference, don't duplicate** @@ -437,7 +436,7 @@ project/ # Correct: Update both vim AI_DOCS/code-conventions.md vim template/AI_DOCS/code-conventions.md.jinja - uv run python src/{{ package_name }}/validate_ai_docs_sync.py + uv run ai-validate-docs ``` --- @@ -448,7 +447,7 @@ project/ **Quick Checklist:** 1. ✅ Update the changed file -2. ✅ Run `validate_ai_docs_sync.py` +2. ✅ Run `uv run ai-validate-docs` 3. ⚠️ Update manual sync files if needed (Gemini/Copilot) 4. ✅ Update .claude/skills or .claude/agents if relevant 5. ✅ Update template files diff --git a/template/src/{{ package_name }}/validate_ai_docs_sync.py.jinja b/template/scripts/ai_tools/validate_ai_docs_sync.py.jinja similarity index 99% rename from template/src/{{ package_name }}/validate_ai_docs_sync.py.jinja rename to template/scripts/ai_tools/validate_ai_docs_sync.py.jinja index 37e39d5..bf23a89 100644 --- a/template/src/{{ package_name }}/validate_ai_docs_sync.py.jinja +++ b/template/scripts/ai_tools/validate_ai_docs_sync.py.jinja @@ -271,7 +271,7 @@ Everything is synchronized! 🎉 3. **Verify fixes:** ```bash - python -m {{ package_name }}.validate_ai_docs_sync + uv run ai-validate-docs ``` """ diff --git a/template/tests/test_validate_ai_docs_sync.py.jinja b/template/tests/test_validate_ai_docs_sync.py.jinja deleted file mode 100644 index 0aec975..0000000 --- a/template/tests/test_validate_ai_docs_sync.py.jinja +++ /dev/null @@ -1,229 +0,0 @@ -"""Tests for AI_DOCS and template sync validation.""" - -from __future__ import annotations - -from pathlib import Path - -from {{ package_name }}.validate_ai_docs_sync import ( - check_file_exists, - compare_files, - find_ai_doc_files, - find_template_files, - generate_sync_report, - validate_sync, -) - - -class TestFindAIDocFiles: - """Test cases for finding AI_DOCS files.""" - - def test_find_ai_doc_files_in_real_directory(self) -> None: - """Test finding actual AI_DOCS files.""" - # Act - files = find_ai_doc_files() - - # Assert - assert len(files) > 0 - assert any("ai-tools.md" in str(f) for f in files) - assert any("tdd-workflow.md" in str(f) for f in files) - assert any("code-conventions.md" in str(f) for f in files) - - def test_find_ai_doc_files_returns_path_objects(self) -> None: - """Test that function returns Path objects.""" - # Act - files = find_ai_doc_files() - - # Assert - assert all(isinstance(f, Path) for f in files) - - -class TestFindTemplateFiles: - """Test cases for finding template files.""" - - def test_find_template_files_in_real_directory(self) -> None: - """Test finding actual template files.""" - # Act - files = find_template_files() - - # Assert - assert len(files) > 0 - # Should find template versions - assert any("CLAUDE.md.jinja" in str(f) for f in files) - - def test_find_template_files_returns_path_objects(self) -> None: - """Test that function returns Path objects.""" - # Act - files = find_template_files() - - # Assert - assert all(isinstance(f, Path) for f in files) - - -class TestCheckFileExists: - """Test cases for file existence checking.""" - - def test_check_file_exists_with_existing_file(self, tmp_path: Path) -> None: - """Test checking an existing file.""" - # Arrange - test_file = tmp_path / "test.txt" - test_file.write_text("content") - - # Act - result = check_file_exists(test_file) - - # Assert - assert result is True - - def test_check_file_exists_with_missing_file(self, tmp_path: Path) -> None: - """Test checking a missing file.""" - # Arrange - test_file = tmp_path / "nonexistent.txt" - - # Act - result = check_file_exists(test_file) - - # Assert - assert result is False - - -class TestCompareFiles: - """Test cases for file comparison.""" - - def test_compare_files_identical_content(self, tmp_path: Path) -> None: - """Test comparing files with identical content.""" - # Arrange - file1 = tmp_path / "file1.txt" - file2 = tmp_path / "file2.txt" - file1.write_text("same content\n") - file2.write_text("same content\n") - - # Act - are_same, diff = compare_files(file1, file2) - - # Assert - assert are_same is True - assert diff == "" - - def test_compare_files_different_content(self, tmp_path: Path) -> None: - """Test comparing files with different content.""" - # Arrange - file1 = tmp_path / "file1.txt" - file2 = tmp_path / "file2.txt" - file1.write_text("content A\n") - file2.write_text("content B\n") - - # Act - are_same, diff = compare_files(file1, file2) - - # Assert - assert are_same is False - assert diff != "" - assert "content A" in diff or "content B" in diff - - def test_compare_files_with_jinja_template(self, tmp_path: Path) -> None: - """Test comparing with Jinja template (should handle template syntax).""" - # Arrange - doc_file = tmp_path / "doc.md" - template_file = tmp_path / "doc.md.jinja" - doc_file.write_text("# Title\nContent here\n") - template_file.write_text("# Title\nContent here\n") - - # Act - are_same, diff = compare_files(doc_file, template_file, is_template=True) - - # Assert - assert are_same is True - - -class TestValidateSync: - """Test cases for sync validation.""" - - def test_validate_sync_with_real_files(self) -> None: - """Test validation with actual project files.""" - # Act - issues = validate_sync() - - # Assert - # Issues list should be returned (empty or with items) - assert isinstance(issues, list) - # Each issue should be a dict with required keys - for issue in issues: - assert "type" in issue - assert "file" in issue - assert "message" in issue - - def test_validate_sync_returns_list(self) -> None: - """Test that validate_sync returns a list.""" - # Act - result = validate_sync() - - # Assert - assert isinstance(result, list) - - -class TestGenerateSyncReport: - """Test cases for report generation.""" - - def test_generate_sync_report_with_no_issues(self) -> None: - """Test report generation with no issues.""" - # Arrange - issues: list[dict[str, str]] = [] - - # Act - report = generate_sync_report(issues) - - # Assert - assert "✅" in report or "PASSED" in report - assert "Issues Found:** 0" in report - - def test_generate_sync_report_with_issues(self) -> None: - """Test report generation with issues.""" - # Arrange - issues = [ - { - "type": "missing", - "file": "AI_DOCS/test.md", - "message": "Missing template file", - }, - { - "type": "different", - "file": "AI_DOCS/test2.md", - "message": "Content differs", - }, - ] - - # Act - report = generate_sync_report(issues) - - # Assert - assert "❌" in report or "FAILED" in report - assert "Total Issues:** 2" in report - assert "missing" in report.lower() - assert "differences" in report.lower() - assert "AI_DOCS/test.md" in report - assert "AI_DOCS/test2.md" in report - - def test_generate_sync_report_returns_string(self) -> None: - """Test that report generation returns a string.""" - # Arrange - issues: list[dict[str, str]] = [] - - # Act - result = generate_sync_report(issues) - - # Assert - assert isinstance(result, str) - assert len(result) > 0 - - def test_generate_sync_report_includes_file_types_checked(self) -> None: - """Test report includes list of file types checked.""" - # Arrange - issues: list[dict[str, str]] = [] - - # Act - result = generate_sync_report(issues) - - # Assert - assert "AI_DOCS" in result - assert "CLAUDE.md" in result - assert "AGENTS.md" in result diff --git a/tests/test_validate_ai_docs_sync.py b/tests/test_validate_ai_docs_sync.py index be8c570..1c4acdf 100644 --- a/tests/test_validate_ai_docs_sync.py +++ b/tests/test_validate_ai_docs_sync.py @@ -2,9 +2,10 @@ from __future__ import annotations +import subprocess from pathlib import Path -from python_modern_template.validate_ai_docs_sync import ( +from scripts.ai_tools.validate_ai_docs_sync import ( check_file_exists, compare_files, find_ai_doc_files, @@ -227,3 +228,42 @@ def test_generate_sync_report_includes_file_types_checked(self) -> None: assert "AI_DOCS" in result assert "CLAUDE.md" in result assert "AGENTS.md" in result + + +class TestCLIInterface: + """Test cases for CLI interface.""" + + def test_cli_command_runs_successfully(self) -> None: + """Test that ai-validate-docs CLI command runs.""" + # Act + result = subprocess.run( + ["uv", "run", "ai-validate-docs"], + capture_output=True, + text=True, + check=False, + ) + + # Assert + # Command should complete (exit code 0 if all valid, 1 if issues found) + assert result.returncode in [0, 1] + # Output should contain report + assert "AI_DOCS Sync Validation Report" in result.stdout + assert "Status:" in result.stdout + + def test_cli_output_format(self) -> None: + """Test that CLI output has expected format.""" + # Act + result = subprocess.run( + ["uv", "run", "ai-validate-docs"], + capture_output=True, + text=True, + check=False, + ) + + # Assert + output = result.stdout + # Should have markdown-style report + assert "#" in output # Markdown headers + assert "**" in output # Markdown bold + # Should mention file types checked + assert "AI_DOCS" in output or "Files Checked" in output