moai-adk 0.8.0__py3-none-any.whl → 0.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of moai-adk might be problematic. Click here for more details.

Files changed (207) hide show
  1. moai_adk/cli/commands/init.py +14 -2
  2. moai_adk/cli/commands/update.py +229 -60
  3. moai_adk/core/config/migration.py +1 -1
  4. moai_adk/core/issue_creator.py +313 -0
  5. moai_adk/core/project/detector.py +201 -12
  6. moai_adk/core/project/initializer.py +62 -1
  7. moai_adk/core/project/phase_executor.py +48 -6
  8. moai_adk/core/tags/__init__.py +86 -0
  9. moai_adk/core/tags/ci_validator.py +463 -0
  10. moai_adk/core/tags/cli.py +283 -0
  11. moai_adk/core/tags/generator.py +109 -0
  12. moai_adk/core/tags/inserter.py +99 -0
  13. moai_adk/core/tags/mapper.py +126 -0
  14. moai_adk/core/tags/parser.py +76 -0
  15. moai_adk/core/tags/pre_commit_validator.py +393 -0
  16. moai_adk/core/tags/reporter.py +956 -0
  17. moai_adk/core/tags/tags.py +149 -0
  18. moai_adk/core/tags/validator.py +897 -0
  19. moai_adk/core/template_engine.py +268 -0
  20. moai_adk/templates/.claude/agents/alfred/backend-expert.md +319 -0
  21. moai_adk/templates/.claude/agents/alfred/cc-manager.md +25 -2
  22. moai_adk/templates/.claude/agents/alfred/debug-helper.md +24 -12
  23. moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
  24. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +20 -13
  25. moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
  26. moai_adk/templates/.claude/agents/alfred/git-manager.md +47 -16
  27. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +95 -15
  28. moai_adk/templates/.claude/agents/alfred/project-manager.md +78 -12
  29. moai_adk/templates/.claude/agents/alfred/quality-gate.md +28 -5
  30. moai_adk/templates/.claude/agents/alfred/skill-factory.md +30 -2
  31. moai_adk/templates/.claude/agents/alfred/spec-builder.md +133 -13
  32. moai_adk/templates/.claude/agents/alfred/tag-agent.md +104 -8
  33. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +133 -16
  34. moai_adk/templates/.claude/agents/alfred/trust-checker.md +27 -4
  35. moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +571 -0
  36. moai_adk/templates/.claude/commands/alfred/0-project.md +466 -125
  37. moai_adk/templates/.claude/commands/alfred/1-plan.md +208 -71
  38. moai_adk/templates/.claude/commands/alfred/2-run.md +276 -55
  39. moai_adk/templates/.claude/commands/alfred/3-sync.md +439 -53
  40. moai_adk/templates/.claude/commands/alfred/9-feedback.md +149 -0
  41. moai_adk/templates/.claude/hooks/alfred/core/project.py +361 -29
  42. moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
  43. moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
  44. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
  45. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +14 -6
  46. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +94 -0
  47. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +100 -0
  48. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +94 -0
  49. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +94 -0
  50. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/__init__.py +2 -2
  51. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +3 -3
  52. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +5 -5
  53. moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
  54. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/tags.py +55 -23
  55. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
  56. moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
  57. moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
  58. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/session.py +28 -15
  59. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/tool.py +3 -6
  60. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/user.py +19 -0
  61. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +112 -0
  62. moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
  63. moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
  64. moai_adk/templates/.claude/settings.json +5 -5
  65. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
  66. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
  67. moai_adk/templates/{.moai/memory/CLAUDE-AGENTS-GUIDE.md → .claude/skills/moai-alfred-agent-guide/reference.md} +34 -0
  68. moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +56 -0
  69. moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
  70. moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
  71. moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
  72. moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
  73. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
  74. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
  75. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
  76. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
  77. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
  78. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
  79. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/SKILL.md +74 -0
  80. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/examples.md +4 -0
  81. moai_adk/templates/.claude/skills/moai-alfred-gitflow-policy/reference.md +269 -0
  82. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +19 -0
  83. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
  84. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/reference.md +150 -0
  85. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/SKILL.md +198 -0
  86. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/examples.md +431 -0
  87. moai_adk/templates/.claude/skills/moai-alfred-persona-roles/reference.md +141 -0
  88. moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
  89. moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
  90. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
  91. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
  92. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
  93. moai_adk/templates/.claude/skills/moai-alfred-reporting/SKILL.md +273 -0
  94. moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
  95. moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
  96. moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +19 -0
  97. moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
  98. moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
  99. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/README.md +137 -0
  100. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/SKILL.md +219 -0
  101. moai_adk/templates/.claude/skills/{moai-spec-authoring → moai-alfred-spec-authoring}/examples/validate-spec.sh +3 -3
  102. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples.md +541 -0
  103. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/reference.md +622 -0
  104. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/SKILL.md +115 -0
  105. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/examples.md +4 -0
  106. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-extended/reference.md +348 -0
  107. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
  108. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
  109. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
  110. moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
  111. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/SKILL.md +19 -0
  112. moai_adk/templates/.claude/skills/moai-cc-skill-descriptions/examples.md +4 -0
  113. moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL.md +3 -3
  114. moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
  115. moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
  116. moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
  117. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +17 -13
  118. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +9 -6
  119. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +15 -12
  120. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +14 -12
  121. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +14 -11
  122. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +10 -8
  123. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +15 -12
  124. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +13 -11
  125. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +16 -10
  126. moai_adk/templates/.claude/skills/moai-project-documentation.md +622 -0
  127. moai_adk/templates/.git-hooks/pre-push +143 -0
  128. moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
  129. moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
  130. moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
  131. moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
  132. moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
  133. moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
  134. moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
  135. moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
  136. moai_adk/templates/.github/workflows/moai-gitflow.yml +166 -3
  137. moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
  138. moai_adk/templates/.github/workflows/moai-release-pipeline.yml +188 -0
  139. moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
  140. moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
  141. moai_adk/templates/.github/workflows/release.yml +118 -0
  142. moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
  143. moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
  144. moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
  145. moai_adk/templates/.github/workflows/spec-issue-sync.yml +206 -35
  146. moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
  147. moai_adk/templates/.github/workflows/tag-report.yml +269 -0
  148. moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
  149. moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
  150. moai_adk/templates/.moai/config.json +21 -2
  151. moai_adk/templates/CLAUDE.md +972 -78
  152. moai_adk/templates/workflows/go-tag-validation.yml +30 -0
  153. moai_adk/templates/workflows/javascript-tag-validation.yml +41 -0
  154. moai_adk/templates/workflows/python-tag-validation.yml +42 -0
  155. moai_adk/templates/workflows/typescript-tag-validation.yml +31 -0
  156. moai_adk/utils/banner.py +5 -5
  157. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/METADATA +1518 -161
  158. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/RECORD +183 -100
  159. moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +0 -313
  160. moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
  161. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -174
  162. moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
  163. moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +0 -175
  164. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -640
  165. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -696
  166. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -474
  167. moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +0 -137
  168. moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +0 -218
  169. moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +0 -541
  170. moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +0 -622
  171. moai_adk/templates/.github/ISSUE_TEMPLATE/spec.yml +0 -176
  172. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
  173. moai_adk/templates/.moai/memory/DEVELOPMENT-GUIDE.md +0 -344
  174. moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +0 -220
  175. moai_adk/templates/.moai/memory/SPEC-METADATA.md +0 -356
  176. moai_adk/templates/.moai/memory/config-schema.md +0 -444
  177. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
  178. moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
  179. moai_adk/templates/.moai/project/product.md +0 -161
  180. moai_adk/templates/.moai/project/structure.md +0 -156
  181. moai_adk/templates/.moai/project/tech.md +0 -227
  182. moai_adk/templates/__init__.py +0 -2
  183. /moai_adk/templates/{.moai/memory/CONFIG-SCHEMA.md → .claude/skills/moai-alfred-config-schema/reference.md} +0 -0
  184. /moai_adk/templates/{.moai/memory/CLAUDE-PRACTICES.md → .claude/skills/moai-alfred-practices/reference.md} +0 -0
  185. /moai_adk/templates/{.moai/memory/CLAUDE-RULES.md → .claude/skills/moai-alfred-rules/reference.md} +0 -0
  186. /moai_adk/templates/{.moai/memory/SKILLS-DESCRIPTION-POLICY.md → .claude/skills/moai-cc-skill-descriptions/reference.md} +0 -0
  187. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/CHECKLIST.md +0 -0
  188. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/EXAMPLES.md +0 -0
  189. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/INTERACTIVE-DISCOVERY.md +0 -0
  190. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/METADATA.md +0 -0
  191. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PARALLEL-ANALYSIS-REPORT.md +0 -0
  192. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/PYTHON-VERSION-MATRIX.md +0 -0
  193. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-FACTORY-WORKFLOW.md +0 -0
  194. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/SKILL-UPDATE-ADVISOR.md +0 -0
  195. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STEP-BY-STEP-GUIDE.md +0 -0
  196. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/STRUCTURE.md +0 -0
  197. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/WEB-RESEARCH.md +0 -0
  198. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/reference.md +0 -0
  199. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/generate-structure.sh +0 -0
  200. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/scripts/validate-skill.sh +0 -0
  201. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/SKILL_TEMPLATE.md +0 -0
  202. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/examples-template.md +0 -0
  203. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/reference-template.md +0 -0
  204. /moai_adk/templates/.claude/skills/{moai-skill-factory → moai-cc-skill-factory}/templates/scripts-template.sh +0 -0
  205. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/WHEEL +0 -0
  206. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/entry_points.txt +0 -0
  207. {moai_adk-0.8.0.dist-info → moai_adk-0.15.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env python3
2
+ # @CODE:DOC-TAG-004 | Component 1: Pre-commit TAG validator
3
+ """Pre-commit TAG validation module
4
+
5
+ This module provides validation functionality for TAG annotations:
6
+ - Format validation (@DOC:DOMAIN-TYPE-NNN)
7
+ - Duplicate TAG detection across files
8
+ - Orphan TAG detection (CODE without TEST, etc.)
9
+ - Git staged file scanning
10
+
11
+ Used by pre-commit hooks to ensure TAG quality.
12
+ """
13
+
14
+ import re
15
+ import subprocess
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Tuple
19
+
20
+
21
+ @dataclass
22
+ class ValidationError:
23
+ """Validation error with file location information"""
24
+ message: str
25
+ tag: str
26
+ locations: List[Tuple[str, int]] = field(default_factory=list)
27
+
28
+ def __str__(self) -> str:
29
+ loc_str = ", ".join([f"{f}:{line}" for f, line in self.locations])
30
+ return f"{self.message}: {self.tag} at {loc_str}"
31
+
32
+
33
+ @dataclass
34
+ class ValidationWarning:
35
+ """Validation warning with file location"""
36
+ message: str
37
+ tag: str
38
+ location: Tuple[str, int]
39
+
40
+ def __str__(self) -> str:
41
+ return f"{self.message}: {self.tag} at {self.location[0]}:{self.location[1]}"
42
+
43
+
44
+ @dataclass
45
+ class ValidationResult:
46
+ """Complete validation result"""
47
+ is_valid: bool
48
+ errors: List[ValidationError] = field(default_factory=list)
49
+ warnings: List[ValidationWarning] = field(default_factory=list)
50
+
51
+ def format(self) -> str:
52
+ """Format result for display"""
53
+ lines = []
54
+
55
+ if self.errors:
56
+ lines.append("Errors:")
57
+ for error in self.errors:
58
+ lines.append(f" - {error}")
59
+
60
+ if self.warnings:
61
+ lines.append("\nWarnings:")
62
+ for warning in self.warnings:
63
+ lines.append(f" - {warning}")
64
+
65
+ if not self.errors and not self.warnings:
66
+ lines.append("No issues found.")
67
+
68
+ return "\n".join(lines)
69
+
70
+
71
+ class PreCommitValidator:
72
+ """Pre-commit TAG validator
73
+
74
+ Validates TAG annotations in files:
75
+ - Format: @DOC:DOMAIN-TYPE-NNN
76
+ - No duplicates
77
+ - No orphans (CODE without TEST)
78
+
79
+ Args:
80
+ strict_mode: Treat warnings as errors
81
+ check_orphans: Enable orphan TAG detection
82
+ tag_pattern: Custom TAG regex pattern
83
+ """
84
+
85
+ # Default TAG pattern: @(SPEC|CODE|TEST|DOC):DOMAIN-NNN or DOMAIN-TYPE-NNN
86
+ # Matches formats like:
87
+ # - @CODE:AUTH-API-001 (domain-type-number)
88
+ # - @CODE:SPEC-001 (domain-number)
89
+ # - @TEST:USER-REG-001 (domain-type-number)
90
+ DEFAULT_TAG_PATTERN = r"@(SPEC|CODE|TEST|DOC):([A-Z]+(?:-[A-Z]+)*-\d{3})"
91
+
92
+ def __init__(
93
+ self,
94
+ strict_mode: bool = False,
95
+ check_orphans: bool = True,
96
+ tag_pattern: Optional[str] = None
97
+ ):
98
+ self.strict_mode = strict_mode
99
+ self.check_orphans = check_orphans
100
+ self.tag_pattern = re.compile(tag_pattern or self.DEFAULT_TAG_PATTERN)
101
+ # Document files to exclude from TAG validation
102
+ self.excluded_file_patterns = [
103
+ r"\.md$", # Markdown files
104
+ r"README", # README files
105
+ r"CHANGELOG", # CHANGELOG files
106
+ r"CONTRIBUTING", # CONTRIBUTING files
107
+ r"LICENSE", # LICENSE files
108
+ r"\.txt$", # Text files
109
+ r"\.rst$", # ReStructuredText files
110
+ r"test_.*\.py$", # Test files (test_*.py)
111
+ r".*_test\.py$", # Test files (*_test.py)
112
+ r"tests/", # Files in tests/ directory
113
+ r"validator\.py$", # Validator files (contain example TAGs in docstrings)
114
+ ]
115
+
116
+ def should_validate_file(self, filepath: str) -> bool:
117
+ """Check if file should be validated for TAGs
118
+
119
+ Document files (*.md, README, CONTRIBUTING, etc.) are excluded
120
+ because they often contain example TAGs that are not actual code.
121
+
122
+ Args:
123
+ filepath: File path to check
124
+
125
+ Returns:
126
+ True if file should be validated, False if excluded
127
+ """
128
+ for pattern in self.excluded_file_patterns:
129
+ if re.search(pattern, filepath):
130
+ return False
131
+ return True
132
+
133
+ def validate_format(self, tag: str) -> bool:
134
+ """Validate TAG format
135
+
136
+ Args:
137
+ tag: TAG string (e.g., "@CODE:AUTH-API-001")
138
+
139
+ Returns:
140
+ True if format is valid
141
+ """
142
+ return bool(self.tag_pattern.match(tag))
143
+
144
+ def extract_tags(self, content: str) -> List[str]:
145
+ """Extract all TAGs from content
146
+
147
+ Args:
148
+ content: File content
149
+
150
+ Returns:
151
+ List of TAG strings
152
+ """
153
+ matches = self.tag_pattern.findall(content)
154
+ # Convert tuples to full TAG strings
155
+ tags = [f"@{prefix}:{domain}" for prefix, domain in matches]
156
+ return tags
157
+
158
+ def validate_duplicates(self, files: List[str]) -> List[ValidationError]:
159
+ """Detect duplicate TAGs
160
+
161
+ Args:
162
+ files: List of file paths to scan
163
+
164
+ Returns:
165
+ List of validation errors for duplicates
166
+ """
167
+ errors: List[ValidationError] = []
168
+ tag_locations: Dict[str, List[Tuple[str, int]]] = {}
169
+
170
+ for filepath in files:
171
+ # Skip document files (*.md, README, CONTRIBUTING, etc.)
172
+ if not self.should_validate_file(filepath):
173
+ continue
174
+
175
+ try:
176
+ path = Path(filepath)
177
+ if not path.exists() or not path.is_file():
178
+ continue
179
+
180
+ content = path.read_text(encoding="utf-8", errors="ignore")
181
+ lines = content.splitlines()
182
+
183
+ for line_num, line in enumerate(lines, start=1):
184
+ tags = self.extract_tags(line)
185
+ for tag in tags:
186
+ if tag not in tag_locations:
187
+ tag_locations[tag] = []
188
+ tag_locations[tag].append((filepath, line_num))
189
+
190
+ except Exception:
191
+ # Skip files that can't be read
192
+ continue
193
+
194
+ # Find duplicates
195
+ for tag, locations in tag_locations.items():
196
+ if len(locations) > 1:
197
+ errors.append(ValidationError(
198
+ message="Duplicate TAG found",
199
+ tag=tag,
200
+ locations=locations
201
+ ))
202
+
203
+ return errors
204
+
205
+ def validate_orphans(self, files: List[str]) -> List[ValidationWarning]:
206
+ """Detect orphan TAGs
207
+
208
+ Orphan TAGs are:
209
+ - @CODE without corresponding @TEST
210
+ - @TEST without corresponding @CODE
211
+ - @SPEC without implementation
212
+
213
+ Args:
214
+ files: List of file paths to scan
215
+
216
+ Returns:
217
+ List of validation warnings
218
+ """
219
+ if not self.check_orphans:
220
+ return []
221
+
222
+ warnings: List[ValidationWarning] = []
223
+
224
+ # Collect all TAGs by type and domain
225
+ tags_by_type: Dict[str, Dict[str, List[Tuple[str, int]]]] = {
226
+ "SPEC": {},
227
+ "CODE": {},
228
+ "TEST": {},
229
+ "DOC": {}
230
+ }
231
+
232
+ for filepath in files:
233
+ # Skip document files (*.md, README, CONTRIBUTING, etc.)
234
+ if not self.should_validate_file(filepath):
235
+ continue
236
+
237
+ try:
238
+ path = Path(filepath)
239
+ if not path.exists() or not path.is_file():
240
+ continue
241
+
242
+ content = path.read_text(encoding="utf-8", errors="ignore")
243
+ lines = content.splitlines()
244
+
245
+ for line_num, line in enumerate(lines, start=1):
246
+ matches = self.tag_pattern.findall(line)
247
+ for prefix, domain in matches:
248
+ if domain not in tags_by_type[prefix]:
249
+ tags_by_type[prefix][domain] = []
250
+ tags_by_type[prefix][domain].append((filepath, line_num))
251
+
252
+ except Exception:
253
+ continue
254
+
255
+ # Check for orphans
256
+ # CODE without TEST
257
+ for domain, locations in tags_by_type["CODE"].items():
258
+ if domain not in tags_by_type["TEST"]:
259
+ for filepath, line_num in locations:
260
+ warnings.append(ValidationWarning(
261
+ message="CODE TAG without corresponding TEST",
262
+ tag=f"@CODE:{domain}",
263
+ location=(filepath, line_num)
264
+ ))
265
+
266
+ # TEST without CODE
267
+ for domain, locations in tags_by_type["TEST"].items():
268
+ if domain not in tags_by_type["CODE"]:
269
+ for filepath, line_num in locations:
270
+ warnings.append(ValidationWarning(
271
+ message="TEST TAG without corresponding CODE",
272
+ tag=f"@TEST:{domain}",
273
+ location=(filepath, line_num)
274
+ ))
275
+
276
+ return warnings
277
+
278
+ def get_staged_files(self, repo_path: str = ".") -> List[str]:
279
+ """Get list of staged files from git
280
+
281
+ Args:
282
+ repo_path: Git repository path
283
+
284
+ Returns:
285
+ List of staged file paths
286
+ """
287
+ try:
288
+ result = subprocess.run(
289
+ ["git", "diff", "--name-only", "--cached"],
290
+ cwd=repo_path,
291
+ capture_output=True,
292
+ text=True,
293
+ timeout=5,
294
+ check=True
295
+ )
296
+ files = [
297
+ line.strip()
298
+ for line in result.stdout.splitlines()
299
+ if line.strip()
300
+ ]
301
+ return files
302
+ except Exception:
303
+ return []
304
+
305
+ def validate_files(self, files: List[str]) -> ValidationResult:
306
+ """Validate list of files
307
+
308
+ Main validation method that runs all checks:
309
+ - Format validation
310
+ - Duplicate detection
311
+ - Orphan detection
312
+
313
+ Args:
314
+ files: List of file paths to validate
315
+
316
+ Returns:
317
+ ValidationResult with errors and warnings
318
+ """
319
+ if not files:
320
+ return ValidationResult(is_valid=True)
321
+
322
+ # Check for duplicates
323
+ errors = self.validate_duplicates(files)
324
+
325
+ # Check for orphans
326
+ warnings = self.validate_orphans(files)
327
+
328
+ # In strict mode, warnings become errors
329
+ if self.strict_mode and warnings:
330
+ is_valid = False
331
+ else:
332
+ is_valid = len(errors) == 0
333
+
334
+ return ValidationResult(
335
+ is_valid=is_valid,
336
+ errors=errors,
337
+ warnings=warnings
338
+ )
339
+
340
+
341
+ def main():
342
+ """CLI entry point for pre-commit hook"""
343
+ import argparse
344
+ import sys
345
+
346
+ parser = argparse.ArgumentParser(
347
+ description="Validate TAG annotations in git staged files"
348
+ )
349
+ parser.add_argument(
350
+ "--files",
351
+ nargs="*",
352
+ help="Files to validate (default: git staged files)"
353
+ )
354
+ parser.add_argument(
355
+ "--strict",
356
+ action="store_true",
357
+ help="Treat warnings as errors"
358
+ )
359
+ parser.add_argument(
360
+ "--no-orphan-check",
361
+ action="store_true",
362
+ help="Disable orphan TAG checking"
363
+ )
364
+
365
+ args = parser.parse_args()
366
+
367
+ validator = PreCommitValidator(
368
+ strict_mode=args.strict,
369
+ check_orphans=not args.no_orphan_check
370
+ )
371
+
372
+ # Get files to validate
373
+ if args.files:
374
+ files = args.files
375
+ else:
376
+ files = validator.get_staged_files()
377
+
378
+ if not files:
379
+ print("No files to validate.")
380
+ sys.exit(0)
381
+
382
+ # Run validation
383
+ result = validator.validate_files(files)
384
+
385
+ # Print results
386
+ print(result.format())
387
+
388
+ # Exit with error code if validation failed
389
+ sys.exit(0 if result.is_valid else 1)
390
+
391
+
392
+ if __name__ == "__main__":
393
+ main()