@@ -63,6 +63,68 @@ impl DocType {
6363 "AILOG" , "AIDEC" , "ADR" , "ETH" , "REQ" , "TES" , "INC" , "TDE" ,
6464 "SEC" , "MCARD" , "SBOM" , "DPIA" ,
6565 ] ;
66+
67+ /// All DocType variants in display order
68+ pub const ALL : & ' static [ DocType ] = & [
69+ DocType :: Ailog , DocType :: Aidec , DocType :: Adr , DocType :: Eth ,
70+ DocType :: Req , DocType :: Tes , DocType :: Inc , DocType :: Tde ,
71+ DocType :: Sec , DocType :: Mcard , DocType :: Sbom , DocType :: Dpia ,
72+ ] ;
73+
74+ /// Human-readable display name
75+ pub fn display_name ( & self ) -> & ' static str {
76+ match self {
77+ DocType :: Ailog => "AI Action Log" ,
78+ DocType :: Aidec => "AI Decision" ,
79+ DocType :: Adr => "Architecture Decision Record" ,
80+ DocType :: Eth => "Ethical Review" ,
81+ DocType :: Req => "Requirement" ,
82+ DocType :: Tes => "Test Plan" ,
83+ DocType :: Inc => "Incident Post-mortem" ,
84+ DocType :: Tde => "Technical Debt" ,
85+ DocType :: Sec => "Security Assessment" ,
86+ DocType :: Mcard => "Model/System Card" ,
87+ DocType :: Sbom => "Software Bill of Materials" ,
88+ DocType :: Dpia => "Data Protection Impact Assessment" ,
89+ }
90+ }
91+
92+ /// Subdirectory under .devtrail/ where this document type lives
93+ pub fn directory ( & self ) -> & ' static str {
94+ match self {
95+ DocType :: Ailog => "07-ai-audit/agent-logs" ,
96+ DocType :: Aidec => "07-ai-audit/decisions" ,
97+ DocType :: Eth => "07-ai-audit/ethical-reviews" ,
98+ DocType :: Adr => "02-design/decisions" ,
99+ DocType :: Req => "01-requirements" ,
100+ DocType :: Tes => "04-testing" ,
101+ DocType :: Inc => "05-operations/incidents" ,
102+ DocType :: Tde => "06-evolution/technical-debt" ,
103+ DocType :: Sec => "08-security" ,
104+ DocType :: Mcard => "09-ai-models" ,
105+ DocType :: Sbom => "07-ai-audit" ,
106+ DocType :: Dpia => "07-ai-audit/ethical-reviews" ,
107+ }
108+ }
109+
110+ /// Parse a DocType from a user-provided string (case-insensitive)
111+ pub fn from_str_loose ( s : & str ) -> Option < DocType > {
112+ match s. to_lowercase ( ) . as_str ( ) {
113+ "ailog" => Some ( DocType :: Ailog ) ,
114+ "aidec" => Some ( DocType :: Aidec ) ,
115+ "adr" => Some ( DocType :: Adr ) ,
116+ "eth" => Some ( DocType :: Eth ) ,
117+ "req" => Some ( DocType :: Req ) ,
118+ "tes" => Some ( DocType :: Tes ) ,
119+ "inc" => Some ( DocType :: Inc ) ,
120+ "tde" => Some ( DocType :: Tde ) ,
121+ "sec" => Some ( DocType :: Sec ) ,
122+ "mcard" => Some ( DocType :: Mcard ) ,
123+ "sbom" => Some ( DocType :: Sbom ) ,
124+ "dpia" => Some ( DocType :: Dpia ) ,
125+ _ => None ,
126+ }
127+ }
66128}
67129
68130impl fmt:: Display for DocType {
@@ -154,7 +216,7 @@ pub fn parse_document(path: &Path) -> Result<DevTrailDocument> {
154216}
155217
156218/// Detect document type from filename prefix
157- fn detect_doc_type ( filename : & str ) -> Option < DocType > {
219+ pub fn detect_doc_type ( filename : & str ) -> Option < DocType > {
158220 for prefix in DocType :: ALL_PREFIXES {
159221 if filename. starts_with ( & format ! ( "{}-" , prefix) ) {
160222 return DocType :: from_prefix ( prefix) ;
@@ -276,4 +338,37 @@ mod tests {
276338 assert_eq ! ( fm. title. as_deref( ) , Some ( "Test" ) ) ;
277339 assert ! ( body. contains( "# Body" ) ) ;
278340 }
341+
342+ #[ test]
343+ fn test_doc_type_all_has_12_entries ( ) {
344+ assert_eq ! ( DocType :: ALL . len( ) , 12 ) ;
345+ assert_eq ! ( DocType :: ALL_PREFIXES . len( ) , 12 ) ;
346+ }
347+
348+ #[ test]
349+ fn test_doc_type_directory_mapping ( ) {
350+ for dt in DocType :: ALL {
351+ let dir = dt. directory ( ) ;
352+ assert ! ( !dir. is_empty( ) , "{} has empty directory" , dt. prefix( ) ) ;
353+ assert ! ( !dir. starts_with( '/' ) , "{} directory should be relative" , dt. prefix( ) ) ;
354+ }
355+ }
356+
357+ #[ test]
358+ fn test_doc_type_display_names ( ) {
359+ for dt in DocType :: ALL {
360+ let name = dt. display_name ( ) ;
361+ assert ! ( !name. is_empty( ) , "{} has empty display_name" , dt. prefix( ) ) ;
362+ }
363+ }
364+
365+ #[ test]
366+ fn test_doc_type_from_str_loose ( ) {
367+ assert_eq ! ( DocType :: from_str_loose( "ailog" ) , Some ( DocType :: Ailog ) ) ;
368+ assert_eq ! ( DocType :: from_str_loose( "AILOG" ) , Some ( DocType :: Ailog ) ) ;
369+ assert_eq ! ( DocType :: from_str_loose( "AiLog" ) , Some ( DocType :: Ailog ) ) ;
370+ assert_eq ! ( DocType :: from_str_loose( "sec" ) , Some ( DocType :: Sec ) ) ;
371+ assert_eq ! ( DocType :: from_str_loose( "mcard" ) , Some ( DocType :: Mcard ) ) ;
372+ assert_eq ! ( DocType :: from_str_loose( "invalid" ) , None ) ;
373+ }
279374}
0 commit comments