microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.22.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc_doc_gen/src/generate_docs.rs

801lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::display::{CodeDisplay, Lookup, increase_header_level, parse_doc_for_summary};
8use crate::table_of_contents::table_of_contents;
9use qsc_ast::ast;
10use qsc_data_structures::language_features::LanguageFeatures;
11use qsc_data_structures::target::TargetCapabilityFlags;
12use qsc_frontend::compile::{self, Dependencies, PackageStore, SourceMap, compile};
13use qsc_frontend::resolve;
14use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Res, Visibility};
15use qsc_hir::{hir, ty};
16use rustc_hash::FxHashMap;
17use std::collections::BTreeMap;
18use std::fmt::Write;
19use std::fmt::{Display, Formatter, Result};
20use std::rc::Rc;
21use std::sync::Arc;
22
23// Name, Metadata, Content
24type Files = Vec<(Rc<str>, Rc<str>, Rc<str>)>;
25type FilesWithMetadata = Vec<(Rc<str>, Rc<Metadata>, Rc<str>)>;
26
27// Namespace -> metadata for items
28type ToC = FxHashMap<Rc<str>, Vec<Rc<Metadata>>>;
29
30struct Metadata {
31 uid: String,
32 title: String,
33 kind: MetadataKind,
34 package: PackageKind,
35 namespace: Rc<str>,
36 name: Rc<str>,
37 summary: String,
38 signature: String,
39}
40
41impl Metadata {
42 fn fully_qualified_name(&self) -> String {
43 let mut buf = if let PackageKind::AliasedPackage(ref package_alias) = self.package {
44 vec![format!("{package_alias}")]
45 } else {
46 vec![]
47 };
48
49 buf.push(self.namespace.to_string());
50 buf.push(self.name.to_string());
51 buf.join(".")
52 }
53
54 fn display_for_toc(&self) -> String {
55 format!(
56 "---
57uid: {}
58title: {}
59description: {}
60ms.date: {{TIMESTAMP}}
61ms.topic: landing-page
62---",
63 self.uid, self.title, self.summary,
64 )
65 }
66
67 fn display_for_item(&self) -> String {
68 let kind = match &self.kind {
69 MetadataKind::Function => "function",
70 MetadataKind::Operation => "operation",
71 MetadataKind::Udt => "udt",
72 MetadataKind::Export => "export",
73 MetadataKind::TableOfContents => "table of contents",
74 };
75 format!(
76 "---
77uid: {}
78title: {}
79description: \"Q# {}: {}\"
80ms.date: {{TIMESTAMP}}
81qsharp.kind: {}
82qsharp.package: {}
83qsharp.namespace: {}
84qsharp.name: {}
85qsharp.summary: \"{}\"
86---",
87 self.uid,
88 self.title,
89 self.title,
90 self.summary,
91 kind,
92 self.package,
93 self.namespace,
94 self.name,
95 self.summary
96 )
97 }
98}
99
100impl Display for Metadata {
101 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
102 if self.kind == MetadataKind::TableOfContents {
103 write!(f, "{}", self.display_for_toc())
104 } else {
105 write!(f, "{}", self.display_for_item())
106 }
107 }
108}
109
110#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
111enum MetadataKind {
112 Function,
113 Operation,
114 Udt,
115 Export,
116 TableOfContents,
117}
118
119impl Display for MetadataKind {
120 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
121 let s = match &self {
122 MetadataKind::Function => "function",
123 MetadataKind::Operation => "operation",
124 MetadataKind::Udt => "user defined type",
125 MetadataKind::Export => "exported item",
126 MetadataKind::TableOfContents => "table of contents",
127 };
128 write!(f, "{s}")
129 }
130}
131
132#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
133enum PackageKind {
134 UserCode,
135 AliasedPackage(String),
136 StandardLibrary,
137 Core,
138}
139
140impl Display for PackageKind {
141 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
142 let s = match &self {
143 PackageKind::UserCode => "__Main__",
144 PackageKind::AliasedPackage(alias) => alias,
145 PackageKind::StandardLibrary => "__Std__",
146 PackageKind::Core => "__Core__",
147 };
148 write!(f, "{s}")
149 }
150}
151
152/// Represents an immutable compilation state.
153#[derive(Debug)]
154struct Compilation {
155 /// Package store, containing the current package and all its dependencies.
156 package_store: PackageStore,
157 /// Current package id when provided.
158 current_package_id: Option<PackageId>,
159 /// Aliases for packages.
160 dependencies: FxHashMap<PackageId, Arc<str>>,
161}
162
163impl Compilation {
164 /// Creates a new `Compilation` by compiling standard library
165 /// and additional sources.
166 pub(crate) fn new(
167 additional_program: Option<(PackageStore, &Dependencies, SourceMap)>,
168 capabilities: Option<TargetCapabilityFlags>,
169 language_features: Option<LanguageFeatures>,
170 ) -> Self {
171 let actual_capabilities = capabilities.unwrap_or_default();
172 let actual_language_features = language_features.unwrap_or_default();
173
174 let mut current_package_id: Option<PackageId> = None;
175 let mut package_aliases: FxHashMap<PackageId, Arc<str>> = FxHashMap::default();
176
177 let package_store =
178 if let Some((mut package_store, dependencies, sources)) = additional_program {
179 let unit = compile(
180 &package_store,
181 dependencies,
182 sources,
183 actual_capabilities,
184 actual_language_features,
185 );
186 // We ignore errors here (unit.errors vector) and use whatever
187 // documentation we can produce. In future we may consider
188 // displaying the fact of error presence on documentation page.
189
190 for (package_id, package_alias) in dependencies {
191 if let Some(package_alias) = package_alias {
192 package_aliases.insert(*package_id, package_alias.clone());
193 }
194 }
195
196 current_package_id = Some(package_store.insert(unit));
197 package_store
198 } else {
199 let mut package_store = PackageStore::new(compile::core());
200 let std_unit = compile::std(&package_store, actual_capabilities);
201 package_store.insert(std_unit);
202 package_store
203 };
204
205 Self {
206 package_store,
207 current_package_id,
208 dependencies: package_aliases,
209 }
210 }
211}
212
213impl Lookup for Compilation {
214 fn get_ty(&self, _: ast::NodeId) -> Option<&ty::Ty> {
215 unimplemented!("Not needed for docs generation")
216 }
217
218 fn get_res(&self, _: ast::NodeId) -> Option<&resolve::Res> {
219 unimplemented!("Not needed for docs generation")
220 }
221
222 fn resolve_item_relative_to_user_package(
223 &self,
224 _: &hir::ItemId,
225 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
226 unimplemented!("Not needed for docs generation")
227 }
228
229 /// Returns the hir `Item` node referred to by `res`.
230 /// `Res`s can resolve to external packages, and the references
231 /// are relative, so here we also need the
232 /// local `PackageId` that the `res` itself came from.
233 fn resolve_item_res(
234 &self,
235 local_package_id: PackageId,
236 res: &hir::Res,
237 ) -> (&hir::Item, hir::ItemId) {
238 match res {
239 hir::Res::Item(item_id) => {
240 let (item, _, resolved_item_id) = self.resolve_item(local_package_id, item_id);
241 (item, resolved_item_id)
242 }
243 _ => panic!("expected to find item"),
244 }
245 }
246
247 /// Returns the hir `Item` node referred to by `item_id`.
248 /// `ItemId`s can refer to external packages, and the references
249 /// are relative, so here we also need the local `PackageId`
250 /// that the `ItemId` originates from.
251 fn resolve_item(
252 &self,
253 local_package_id: PackageId,
254 item_id: &hir::ItemId,
255 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
256 // If the `ItemId` contains a package id, use that.
257 // Lack of a package id means the item is in the
258 // same package as the one this `ItemId` reference
259 // came from. So use the local package id passed in.
260 let package_id = item_id.package.unwrap_or(local_package_id);
261 let package = &self
262 .package_store
263 .get(package_id)
264 .expect("package should exist in store")
265 .package;
266 (
267 package
268 .items
269 .get(item_id.item)
270 .expect("item id should exist"),
271 package,
272 hir::ItemId {
273 package: Some(package_id),
274 item: item_id.item,
275 },
276 )
277 }
278}
279
280/// Determines the package kind for a given package in the compilation context
281fn determine_package_kind(package_id: PackageId, compilation: &Compilation) -> Option<PackageKind> {
282 let is_current_package = compilation.current_package_id == Some(package_id);
283
284 if package_id == PackageId::CORE {
285 // Core package is always included in the compilation.
286 Some(PackageKind::Core)
287 } else if package_id == 1.into() {
288 // Standard package is currently always included, but this isn't enforced by the compiler.
289 Some(PackageKind::StandardLibrary)
290 } else if is_current_package {
291 // This package could be user code if current package is specified.
292 Some(PackageKind::UserCode)
293 } else {
294 // This is a either a direct dependency of the user code or
295 // is not a package user can access (an indirect dependency).
296 compilation
297 .dependencies
298 .get(&package_id)
299 .map(|alias| PackageKind::AliasedPackage(alias.to_string()))
300 }
301}
302
303/// Processes all packages in a compilation and builds a table-of-contents structure
304fn build_toc_from_compilation(
305 compilation: &Compilation,
306 mut files: Option<&mut FilesWithMetadata>,
307) -> ToC {
308 let display = &CodeDisplay { compilation };
309
310 let mut toc: ToC = FxHashMap::default();
311
312 for (package_id, unit) in &compilation.package_store {
313 let Some(package_kind) = determine_package_kind(package_id, compilation) else {
314 continue;
315 };
316
317 let is_current_package = compilation.current_package_id == Some(package_id);
318 let package = &unit.package;
319
320 for (_, item) in &package.items {
321 if let Some((ns, metadata)) = generate_doc_for_item(
322 package_id,
323 package,
324 package_kind.clone(),
325 is_current_package,
326 item,
327 display,
328 files.as_deref_mut().unwrap_or(&mut vec![]),
329 ) {
330 toc.entry(ns).or_default().push(metadata);
331 }
332 }
333 }
334
335 toc
336}
337
338/// Generates and returns documentation files for the standard library
339/// and additional sources (if specified.)
340#[must_use]
341pub fn generate_docs(
342 additional_sources: Option<(PackageStore, &Dependencies, SourceMap)>,
343 capabilities: Option<TargetCapabilityFlags>,
344 language_features: Option<LanguageFeatures>,
345) -> Files {
346 // Capabilities should default to all capabilities for documentation generation.
347 let capabilities = Some(capabilities.unwrap_or(TargetCapabilityFlags::all()));
348 let compilation = Compilation::new(additional_sources, capabilities, language_features);
349 let mut files: FilesWithMetadata = vec![];
350
351 let mut toc = build_toc_from_compilation(&compilation, Some(&mut files));
352
353 // Generate Overview files for each namespace
354 for (ns, items) in &mut toc {
355 generate_index_file(&mut files, ns, items);
356 }
357
358 generate_top_index(&mut files, &mut toc);
359
360 // We want to sort documentation files in a meaningful way.
361 // First, we want to put files for the current project, if it exists.
362 // Then we want to put explicit dependencies of the current project, if they exist.
363 // Then we want to add built-in std package. And finally built-in core package.
364 // Namespaces within packages should be sorted alphabetically and
365 // items with a namespace should be also sorted alphabetically with the index file appearing first.
366 // Also, items without any metadata (table of content) should come last.
367 files.sort_by_key(|file| {
368 let prefix = if file.0.ends_with("index.md") {
369 "0"
370 } else {
371 "1"
372 };
373 let name_key = format!("{}{}", prefix, file.1.name);
374 (file.1.package.clone(), file.1.namespace.clone(), name_key)
375 });
376
377 let mut result: Files = files
378 .into_iter()
379 .map(|(name, metadata, content)| (name, Rc::from(metadata.to_string().as_str()), content))
380 .collect();
381
382 generate_toc(&mut toc, &mut result);
383
384 result
385}
386
387fn generate_doc_for_item<'a>(
388 default_package_id: PackageId,
389 package: &'a Package,
390 package_kind: PackageKind,
391 include_internals: bool,
392 item: &'a Item,
393 display: &'a CodeDisplay,
394 files: &mut FilesWithMetadata,
395) -> Option<(Rc<str>, Rc<Metadata>)> {
396 let (true_package, true_item) = resolve_export(
397 default_package_id,
398 package,
399 include_internals,
400 item,
401 display,
402 )?;
403
404 // Get namespace for item
405 let ns = get_namespace(package, item)?;
406
407 // Add file
408 let (metadata, content) = if matches!(item.kind, ItemKind::Export(_, _)) {
409 let true_ns = get_namespace(true_package, true_item)?;
410 generate_exported_file_content(
411 package_kind.clone(),
412 &ns,
413 item,
414 display,
415 &true_ns,
416 true_item,
417 )?
418 } else {
419 generate_file_content(package_kind, &ns, item, display)?
420 };
421 let file_name = Rc::from(format!("{ns}/{}.md", metadata.name).as_str());
422 let file_content = Rc::from(content.as_str());
423 let met = Rc::from(metadata);
424 files.push((file_name, met.clone(), file_content));
425
426 Some((ns.clone(), met))
427}
428
429fn generate_file_content(
430 package_kind: PackageKind,
431 ns: &Rc<str>,
432 item: &Item,
433 display: &CodeDisplay,
434) -> Option<(Metadata, String)> {
435 let metadata = get_metadata(package_kind, ns.clone(), item, display)?;
436
437 let doc = increase_header_level(&item.doc);
438 let title = &metadata.title;
439 let fqn = &metadata.fully_qualified_name();
440 let sig = &metadata.signature;
441
442 let content = format!(
443 "# {title}
444
445Fully qualified name: {fqn}
446
447```qsharp
448{sig}
449```
450"
451 );
452
453 let content = if doc.is_empty() {
454 content
455 } else {
456 format!("{content}\n{doc}\n")
457 };
458
459 Some((metadata, content))
460}
461
462#[allow(clippy::assigning_clones)]
463fn generate_exported_file_content(
464 package_kind: PackageKind,
465 ns: &Rc<str>,
466 item: &Item,
467 display: &CodeDisplay,
468 true_ns: &Rc<str>,
469 true_item: &Item,
470) -> Option<(Metadata, String)> {
471 let mut metadata = get_metadata(package_kind.clone(), ns.clone(), item, display)?;
472
473 let doc = increase_header_level(&item.doc);
474 let title = &metadata.title;
475 let fqn = &metadata.fully_qualified_name();
476
477 // Note: we are assuming the package kind does not change
478 let true_metadata = get_metadata(package_kind, true_ns.clone(), true_item, display)?;
479 let true_fqn = true_metadata.fully_qualified_name();
480
481 let summary = format!(
482 "This is an exported item. The actual definition is found here: [{true_fqn}](xref:Qdk.{true_fqn})"
483 );
484
485 metadata.summary = summary.clone();
486
487 let content = format!(
488 "# {title}
489
490Fully qualified name: {fqn}
491
492{summary}
493"
494 );
495
496 let content = if doc.is_empty() {
497 content
498 } else {
499 format!("{content}\n{doc}\n")
500 };
501
502 Some((metadata, content))
503}
504
505fn generate_index_file(files: &mut FilesWithMetadata, ns: &Rc<str>, items: &mut Vec<Rc<Metadata>>) {
506 if items.is_empty() {
507 return;
508 }
509
510 let short_name = if ns.starts_with("Microsoft.Quantum") {
511 ns.as_ref()
512 } else {
513 ns.split('.')
514 .next_back()
515 .expect("Namespaces should have at least one part.")
516 };
517
518 let package_kind = items[0].package.clone();
519 let metadata = Metadata {
520 uid: format!("Qdk.{ns}-toc"),
521 title: format!("{ns} namespace"),
522 kind: MetadataKind::TableOfContents,
523 package: package_kind,
524 namespace: ns.clone(),
525 name: "Overview".into(),
526 summary: format!("Table of contents for the Q# {short_name} namespace"),
527 signature: String::new(),
528 };
529
530 items.sort_by_key(|item| item.name.clone());
531 let content = items
532 .iter()
533 .map(|item| {
534 format!(
535 "| [{}](xref:Qdk.{}) | {} |",
536 item.name,
537 item.fully_qualified_name(),
538 item.summary.replace('|', "\\|")
539 )
540 })
541 .collect::<Vec<_>>()
542 .join("\n");
543
544 let content = format!(
545 "# {ns}
546
547The {ns} namespace contains the following items:
548
549| Name | Description |
550|------|-------------|
551{content}
552",
553 );
554
555 let rc_met = Rc::from(metadata);
556 items.insert(0, rc_met.clone());
557
558 let file_name = Rc::from(format!("{ns}/index.md").as_str());
559 let file_content = Rc::from(content.as_str());
560 files.push((file_name, rc_met, file_content));
561}
562
563fn generate_top_index(files: &mut FilesWithMetadata, toc: &mut ToC) {
564 let empty_ns: Rc<str> = Rc::from(String::new().as_str());
565 let metadata = Metadata {
566 uid: "Microsoft.Quantum.apiref-toc".to_string(),
567 title: "Q# standard libraries for the Azure Quantum Development Kit".to_string(),
568 kind: MetadataKind::TableOfContents,
569 package: PackageKind::StandardLibrary,
570 namespace: empty_ns.clone(),
571 name: "Overview".into(),
572 summary:
573 "Table of contents for the Q# standard libraries for Azure Quantum Development Kit"
574 .to_string(),
575 signature: String::new(),
576 };
577
578 let contents = table_of_contents();
579
580 files.push((Rc::from("index.md"), Rc::from(metadata), Rc::from(contents)));
581
582 toc.insert(empty_ns, vec![]);
583}
584
585/// Generates the Table of Contents file, toc.yml
586fn generate_toc(map: &mut ToC, files: &mut Files) {
587 let header = "
588# This file is automatically generated.
589# Please do not modify this file manually, or your changes will be lost when
590# documentation is rebuilt.";
591 let mut table = map
592 .iter_mut()
593 .map(|(namespace, items)| {
594 if namespace.is_empty() {
595 let content = "- items:
596 name: Overview
597 uid: Microsoft.Quantum.apiref-toc";
598 (namespace, content.to_string())
599 } else {
600 let items_str = items
601 .iter()
602 .map(|item| format!(" - {{name: {}, uid: {}}}", item.name, item.uid))
603 .collect::<Vec<String>>()
604 .join("\n");
605 let content =
606 format!("- items:\n{items_str}\n name: {namespace}\n uid: Qdk.{namespace}");
607 (namespace, content)
608 }
609 })
610 .collect::<Vec<_>>();
611
612 table.sort_unstable_by_key(|(n, _)| {
613 // Ensures that the Microsoft.Quantum.Unstable namespaces are listed last.
614 if n.starts_with("Microsoft.Quantum.Unstable") {
615 format!("1{n}")
616 } else {
617 format!("0{n}")
618 }
619 });
620 let table = table
621 .into_iter()
622 .map(|(_, c)| c)
623 .collect::<Vec<_>>()
624 .join("\n");
625 let content = format!("{header}\n{table}");
626 let content = content.as_str();
627
628 let file_name = Rc::from("toc.yml");
629 let file_metadata = Rc::from("");
630 let file_content = Rc::from(content);
631 files.push((file_name, file_metadata, file_content));
632}
633
634fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
635 let local_id = item.parent?;
636 let parent = package
637 .items
638 .get(local_id)
639 .expect("Could not resolve parent item id");
640 let ItemKind::Namespace(name, _) = &parent.kind else {
641 return None;
642 };
643 if name.starts_with("QIR") {
644 None // We ignore "QIR" namespaces
645 } else {
646 let name = name.name();
647 if name.to_lowercase().starts_with("std.openqasm") {
648 None // We ignore openqasm namespaces
649 } else {
650 Some(name)
651 }
652 }
653}
654
655// Recursively resolves export items until it can find the root definition.
656// Returns the package and item of the root definition for an item.
657// If given the root definition, it will return the same item.
658fn resolve_export<'a>(
659 default_package_id: PackageId,
660 package: &'a Package,
661 include_internals: bool,
662 item: &'a Item,
663 display: &'a CodeDisplay,
664) -> Option<(&'a Package, &'a Item)> {
665 // Filter out items that are not visible or are namespaces
666 if !include_internals && (item.visibility == Visibility::Internal) {
667 return None;
668 }
669 if matches!(item.kind, ItemKind::Namespace(_, _)) {
670 return None;
671 }
672 if let ItemKind::Export(_, Res::Item(id)) = item.kind {
673 let (exported_item, exported_package, _) =
674 display.compilation.resolve_item(default_package_id, &id);
675 return resolve_export(
676 default_package_id,
677 exported_package,
678 include_internals,
679 exported_item,
680 display,
681 );
682 }
683
684 Some((package, item))
685}
686
687fn get_metadata(
688 package_kind: PackageKind,
689 ns: Rc<str>,
690 item: &Item,
691 display: &CodeDisplay,
692) -> Option<Metadata> {
693 let (name, signature, kind) = match &item.kind {
694 ItemKind::Callable(decl) => Some((
695 decl.name.name.clone(),
696 display.hir_callable_decl(decl).to_string(),
697 match &decl.kind {
698 CallableKind::Function => MetadataKind::Function,
699 CallableKind::Operation => MetadataKind::Operation,
700 },
701 )),
702 ItemKind::Ty(ident, udt) => Some((
703 ident.name.clone(),
704 display.hir_udt(udt).to_string(),
705 MetadataKind::Udt,
706 )),
707 ItemKind::Namespace(_, _) => None,
708 ItemKind::Export(name, _) => Some((
709 name.name.clone(),
710 // If we want to show docs for exports, we could do that here.
711 String::new(),
712 MetadataKind::Export,
713 )),
714 }?;
715
716 let summary = parse_doc_for_summary(&item.doc)
717 .replace("\r\n", " ")
718 .replace('\n', " ");
719
720 Some(Metadata {
721 uid: format!("Qdk.{ns}.{name}"),
722 title: format!("{name} {kind}"),
723 kind,
724 package: package_kind,
725 namespace: ns,
726 name,
727 summary,
728 signature,
729 })
730}
731
732/// Generates summary documentation organized by namespace.
733/// Returns a map of namespace -> metadata items for easier testing and manipulation.
734fn generate_summaries_map() -> BTreeMap<String, Vec<Rc<Metadata>>> {
735 let compilation = Compilation::new(None, None, None);
736
737 // Use the shared logic to build ToC structure
738 let toc = build_toc_from_compilation(&compilation, None);
739
740 // Convert ToC to BTreeMap, filtering out table of contents entries
741 let mut result = BTreeMap::new();
742
743 for (ns, items) in toc {
744 let mut summaries = Vec::new();
745
746 for item in items {
747 // Skip table of contents entries
748 if item.kind == MetadataKind::TableOfContents {
749 continue;
750 }
751
752 summaries.push(item);
753 }
754
755 if !summaries.is_empty() {
756 // Sort items within namespace
757 summaries.sort_by_key(|item| item.name.clone());
758 result.insert(ns.to_string(), summaries);
759 }
760 }
761
762 result
763}
764/// Converts a Metadata item to its markdown representation
765fn metadata_to_markdown(item: &Metadata) -> String {
766 let mut result = format!("## {}\n\n", item.name);
767 let _ = write!(result, "```qsharp\n{}\n```\n\n", item.signature);
768 if !item.summary.is_empty() {
769 let _ = write!(result, "{}\n\n", item.summary);
770 }
771 result
772}
773
774/// Generates markdown summary for a single namespace
775fn generate_namespace_summary(namespace: &str, items: &[Rc<Metadata>]) -> String {
776 let mut result = format!("# {namespace}\n\n");
777
778 for item in items {
779 result.push_str(&metadata_to_markdown(item));
780 }
781
782 result
783}
784
785/// Generates summary documentation organized by namespace.
786/// Returns a single markdown string with namespace headers and minimal item documentation
787/// containing just function signatures and summaries for efficient consumption by language models.
788#[must_use]
789pub fn generate_summaries() -> String {
790 let summaries_map = generate_summaries_map();
791
792 // Generate markdown output organized by namespace
793 let mut result = String::new();
794
795 // Sort namespaces for consistent output (BTreeMap already sorts keys)
796 for (ns, items) in &summaries_map {
797 result.push_str(&generate_namespace_summary(ns, items));
798 }
799
800 result
801}
802