@@ -976,26 +976,215 @@ func symbolMatches(name, qualifiedName, want string) bool {
976976 strings .HasSuffix (qualifiedName , "::" + want )
977977}
978978
979- // @intent render a Wiki tree node as fallback Markdown when no generated file exists.
979+ // @intent render a Wiki tree node as generated-doc-shaped fallback Markdown when no generated file exists.
980980func nodeMarkdown (node * ragindex.TreeNode ) string {
981- parts := []string {fmt .Sprintf ("# %s" , node .Label )}
982- if strings .TrimSpace (node .Summary ) != "" {
983- parts = append (parts , strings .TrimSpace (node .Summary ))
981+ parts := []string {
982+ "<!-- generated-by: code-context-graph docs -->" ,
983+ fmt .Sprintf ("# %s" , node .Label ),
984+ }
985+ if summary := cleanMarkdownText (node .Summary ); summary != "" {
986+ parts = append (parts , "> " + summary )
984987 }
985988 if len (node .Children ) > 0 {
986- var children [] string
987- for _ , child := range node . Children {
988- line := "- " + child . Label
989- if strings . TrimSpace ( child . Summary ) != "" {
990- line += ": " + strings . TrimSpace ( child . Summary )
989+ sections := nodeMarkdownSections ( node . Children )
990+ for _ , section := range [] string { "Files" , "Packages" , "Functions" , "Classes" , "Types" , "Tests" , "Symbols" , " Children" } {
991+ children := sections [ section ]
992+ if len ( children ) == 0 {
993+ continue
991994 }
992- children = append (children , line )
995+ lines := []string {"## " + section }
996+ for _ , child := range children {
997+ lines = append (lines , nodeMarkdownChild (child )... )
998+ }
999+ parts = append (parts , strings .Join (lines , "\n " ))
9931000 }
994- parts = append (parts , strings .Join (children , "\n " ))
9951001 }
9961002 return strings .Join (parts , "\n \n " )
9971003}
9981004
1005+ // @intent group fallback child nodes into stable sections that the Wiki visual renderer can cardify.
1006+ func nodeMarkdownSections (children []* ragindex.TreeNode ) map [string ][]* ragindex.TreeNode {
1007+ sections := map [string ][]* ragindex.TreeNode {}
1008+ for _ , child := range children {
1009+ section := nodeMarkdownSection (child )
1010+ sections [section ] = append (sections [section ], child )
1011+ }
1012+ return sections
1013+ }
1014+
1015+ // @intent map graph node kinds to generated docs section names for DB-backed Wiki fallback.
1016+ func nodeMarkdownSection (node * ragindex.TreeNode ) string {
1017+ switch node .Kind {
1018+ case "file" :
1019+ return "Files"
1020+ case "package" :
1021+ return "Packages"
1022+ case string (model .NodeKindFunction ):
1023+ return "Functions"
1024+ case string (model .NodeKindClass ):
1025+ return "Classes"
1026+ case string (model .NodeKindType ):
1027+ return "Types"
1028+ case string (model .NodeKindTest ):
1029+ return "Tests"
1030+ case "symbol" :
1031+ return "Symbols"
1032+ default :
1033+ return "Children"
1034+ }
1035+ }
1036+
1037+ // @intent render one fallback tree child in the same symbol-card Markdown shape as generated docs.
1038+ func nodeMarkdownChild (child * ragindex.TreeNode ) []string {
1039+ lines := []string {"" , "### " + child .Label , "" }
1040+ description := cleanMarkdownText (child .Summary )
1041+ if child .Details != nil && child .Details .Annotation != nil {
1042+ if summary := cleanMarkdownText (child .Details .Annotation .Summary ); summary != "" {
1043+ description = summary
1044+ }
1045+ }
1046+ if description != "" {
1047+ lines = append (lines , description , "" )
1048+ }
1049+ if child .Details != nil {
1050+ if lineRange := markdownLineRange (child .Details .StartLine , child .Details .EndLine ); lineRange != "" {
1051+ lines = append (lines , "- **Lines:** " + lineRange )
1052+ }
1053+ if filePath := cleanMarkdownText (child .Details .FilePath ); filePath != "" {
1054+ lines = append (lines , "- **File:** " + filePath )
1055+ }
1056+ if language := cleanMarkdownText (child .Details .Language ); language != "" {
1057+ lines = append (lines , "- **Language:** " + language )
1058+ }
1059+ if child .Details .Annotation != nil {
1060+ lines = append (lines , annotationMarkdownBlocks (child .Details .Annotation )... )
1061+ }
1062+ return lines
1063+ }
1064+ if child .Kind != "" {
1065+ lines = append (lines , "- **Kind:** " + child .Kind )
1066+ }
1067+ if child .DocPath != "" {
1068+ lines = append (lines , "- **Path:** " + child .DocPath )
1069+ }
1070+ return lines
1071+ }
1072+
1073+ // @intent format annotation tags into labels already understood by the Wiki generated-doc renderer.
1074+ func annotationMarkdownBlocks (annotation * ragindex.AnnotationDetail ) []string {
1075+ blocks := []annotationMarkdownBlock {}
1076+ index := map [string ]int {}
1077+ add := func (label , value string ) {
1078+ value = cleanMarkdownText (value )
1079+ if value == "" {
1080+ return
1081+ }
1082+ pos , ok := index [label ]
1083+ if ! ok {
1084+ pos = len (blocks )
1085+ index [label ] = pos
1086+ blocks = append (blocks , annotationMarkdownBlock {label : label })
1087+ }
1088+ blocks [pos ].items = append (blocks [pos ].items , value )
1089+ }
1090+ for _ , tag := range annotation .Tags {
1091+ value := annotationTagMarkdownValue (tag )
1092+ switch tag .Kind {
1093+ case model .TagIntent :
1094+ add ("Intent" , value )
1095+ case model .TagDomainRule :
1096+ add ("Domain Rules" , value )
1097+ case model .TagSideEffect :
1098+ add ("Side Effects" , value )
1099+ case model .TagMutates :
1100+ add ("Mutates" , value )
1101+ case model .TagRequires :
1102+ add ("Requires" , value )
1103+ case model .TagEnsures :
1104+ add ("Ensures" , value )
1105+ case model .TagParam :
1106+ add ("Params" , formatParamMarkdownTag (tag ))
1107+ case model .TagReturn :
1108+ add ("Returns" , value )
1109+ case model .TagSee :
1110+ add ("See" , value )
1111+ case model .TagThrows :
1112+ add ("Throws" , value )
1113+ default :
1114+ add (string (tag .Kind ), value )
1115+ }
1116+ }
1117+ if context := cleanMarkdownText (annotation .Context ); context != "" {
1118+ add ("Context" , context )
1119+ }
1120+
1121+ var lines []string
1122+ for _ , block := range blocks {
1123+ if len (block .items ) == 1 && (block .label == "Intent" || block .label == "Returns" || block .label == "See" ) {
1124+ lines = append (lines , fmt .Sprintf ("- **%s:** %s" , block .label , block .items [0 ]))
1125+ continue
1126+ }
1127+ lines = append (lines , fmt .Sprintf ("- **%s:**" , block .label ))
1128+ for _ , item := range block .items {
1129+ lines = append (lines , " - " + item )
1130+ }
1131+ }
1132+ return lines
1133+ }
1134+
1135+ // annotationMarkdownBlock groups same-label annotation fallback lines before Markdown rendering.
1136+ // @intent keep annotation Markdown output stable while preserving original tag ordering by first label occurrence.
1137+ type annotationMarkdownBlock struct {
1138+ label string
1139+ items []string
1140+ }
1141+
1142+ // @intent preserve annotation tag name/type context in fallback Markdown without exposing raw JSON.
1143+ func annotationTagMarkdownValue (tag ragindex.DocTagDetail ) string {
1144+ parts := []string {}
1145+ if strings .TrimSpace (tag .Name ) != "" {
1146+ parts = append (parts , tag .Name )
1147+ }
1148+ if strings .TrimSpace (tag .Value ) != "" {
1149+ parts = append (parts , tag .Value )
1150+ }
1151+ return strings .Join (parts , " — " )
1152+ }
1153+
1154+ // @intent format @param tags consistently with browser-side generated doc fallback.
1155+ func formatParamMarkdownTag (tag ragindex.DocTagDetail ) string {
1156+ name := strings .TrimSpace (tag .Name )
1157+ if name == "" {
1158+ name = "param"
1159+ }
1160+ if typ := strings .TrimSpace (tag .Type ); typ != "" {
1161+ name = fmt .Sprintf ("%s [%s]" , name , typ )
1162+ }
1163+ if value := cleanMarkdownText (tag .Value ); value != "" {
1164+ return name + " — " + value
1165+ }
1166+ return name
1167+ }
1168+
1169+ // @intent keep fallback Markdown attributes single-line so the visual parser can read them predictably.
1170+ func cleanMarkdownText (value string ) string {
1171+ return strings .Join (strings .Fields (value ), " " )
1172+ }
1173+
1174+ // @intent format graph node source ranges for generated-doc-compatible fallback Markdown.
1175+ func markdownLineRange (start , end int ) string {
1176+ if start > 0 && end > 0 {
1177+ return fmt .Sprintf ("%d-%d" , start , end )
1178+ }
1179+ if start > 0 {
1180+ return strconv .Itoa (start )
1181+ }
1182+ if end > 0 {
1183+ return strconv .Itoa (end )
1184+ }
1185+ return ""
1186+ }
1187+
9991188// @intent normalize and validate the namespace query parameter shared by Wiki API endpoints.
10001189func namespaceParam (w http.ResponseWriter , r * http.Request ) (string , bool ) {
10011190 ns := ctxns .Normalize (strings .TrimSpace (r .URL .Query ().Get ("namespace" )))
0 commit comments