44from dataclasses import dataclass
55from pathlib import Path
66from typing import Dict , List , Optional , Tuple
7+ from issues import ValidationIssue
78from paths import CONTENT_ROOT , should_skip
89
910IDENTIFIER_PATTERN = re .compile (r"^`([^`]+)`\s*$" )
@@ -21,9 +22,9 @@ class CommitmentIdentity:
2122 expected_base : str
2223
2324
24- def derive_identity (path : Path ) -> Tuple [Optional [CommitmentIdentity ], List [str ]]:
25+ def derive_identity (path : Path ) -> Tuple [Optional [CommitmentIdentity ], List [ValidationIssue ]]:
2526 """Infer roadmap identity metadata from the file path."""
26- issues : List [str ] = []
27+ issues : List [ValidationIssue ] = []
2728 try :
2829 relative = path .resolve ().relative_to (CONTENT_ROOT )
2930 except ValueError :
@@ -35,7 +36,11 @@ def derive_identity(path: Path) -> Tuple[Optional[CommitmentIdentity], List[str]
3536 match = FILENAME_PATTERN .match (path .stem )
3637 if not match :
3738 issues .append (
38- f"{ path } : filename should follow `<year>q<quarter>-<slug>.md` (found `{ path .name } `)"
39+ ValidationIssue (
40+ path = path ,
41+ line = None ,
42+ message = f"filename should follow `<year>q<quarter>-<slug>.md` (found `{ path .name } `)" ,
43+ )
3944 )
4045 return None , issues
4146
@@ -44,18 +49,16 @@ def derive_identity(path: Path) -> Tuple[Optional[CommitmentIdentity], List[str]
4449 expected_base = f"vac:{ unit } :{ area } :{ quarter } -{ slug } "
4550 expected_tags = [quarter , unit , area ]
4651 expected_identifier = expected_base
47- return (
48- CommitmentIdentity (
52+ identity = CommitmentIdentity (
4953 unit = unit ,
5054 quarter = quarter ,
5155 area = area ,
5256 slug = slug ,
5357 expected_tags = expected_tags ,
5458 expected_identifier = expected_identifier ,
5559 expected_base = expected_base ,
56- ),
57- issues ,
5860 )
61+ return identity , issues
5962
6063
6164def validate_identity (
@@ -64,20 +67,28 @@ def validate_identity(
6467 lines : List [str ],
6568 body_start : int ,
6669 identity : CommitmentIdentity ,
67- ) -> List [str ]:
70+ ) -> List [ValidationIssue ]:
6871 """Validate tags and inline identifier for a commitment."""
69- issues : List [str ] = []
72+ issues : List [ValidationIssue ] = []
7073
7174 tags = front_matter .get ("tags" )
7275 if isinstance (tags , (list , tuple )):
7376 tag_values = [str (tag ) for tag in tags ]
7477 if tag_values != identity .expected_tags :
7578 issues .append (
76- f"{ path } : tags should be { identity .expected_tags !r} (found { tag_values !r} )"
79+ ValidationIssue (
80+ path = path ,
81+ line = 1 ,
82+ message = f"tags should be { identity .expected_tags !r} (found { tag_values !r} )" ,
83+ )
7784 )
7885 else :
7986 issues .append (
80- f"{ path } : tags must be a list matching { identity .expected_tags !r} "
87+ ValidationIssue (
88+ path = path ,
89+ line = 1 ,
90+ message = f"tags must be a list matching { identity .expected_tags !r} " ,
91+ )
8192 )
8293
8394 identifier_value : Optional [str ] = None
@@ -92,13 +103,21 @@ def validate_identity(
92103
93104 if identifier_value is None :
94105 issues .append (
95- f"{ path } :{ body_start + 1 } : missing commitment identifier `"
96- f"{ identity .expected_identifier } `"
106+ ValidationIssue (
107+ path = path ,
108+ line = body_start + 1 ,
109+ message = f"missing commitment identifier `{ identity .expected_identifier } `" ,
110+ )
97111 )
98112 elif identifier_value != identity .expected_identifier :
99113 issues .append (
100- f"{ path } :{ identifier_line } : identifier should be `"
101- f"{ identity .expected_identifier } ` (found `{ identifier_value } `)"
114+ ValidationIssue (
115+ path = path ,
116+ line = identifier_line ,
117+ message = (
118+ f"identifier should be `{ identity .expected_identifier } ` (found `{ identifier_value } `)"
119+ ),
120+ )
102121 )
103122
104123 return issues
0 commit comments