diff --git a/source/compiler/qsc_doc_gen/src/generate_docs.rs b/source/compiler/qsc_doc_gen/src/generate_docs.rs index 6658c6a8e9..4765b8bd54 100644 --- a/source/compiler/qsc_doc_gen/src/generate_docs.rs +++ b/source/compiler/qsc_doc_gen/src/generate_docs.rs @@ -4,8 +4,7 @@ #[cfg(test)] mod tests; -use crate::display::{CodeDisplay, Lookup}; -use crate::display::{increase_header_level, parse_doc_for_summary}; +use crate::display::{CodeDisplay, Lookup, increase_header_level, parse_doc_for_summary}; use crate::table_of_contents::table_of_contents; use qsc_ast::ast; use qsc_data_structures::language_features::LanguageFeatures; @@ -15,6 +14,8 @@ use qsc_frontend::resolve; use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Res, Visibility}; use qsc_hir::{hir, ty}; use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use std::fmt::Write; use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::sync::Arc; @@ -276,46 +277,46 @@ impl Lookup for Compilation { } } -/// Generates and returns documentation files for the standard library -/// and additional sources (if specified.) -#[must_use] -pub fn generate_docs( - additional_sources: Option<(PackageStore, &Dependencies, SourceMap)>, - capabilities: Option, - language_features: Option, -) -> Files { - // Capabilities should default to all capabilities for documentation generation. - let capabilities = Some(capabilities.unwrap_or(TargetCapabilityFlags::all())); - let compilation = Compilation::new(additional_sources, capabilities, language_features); - let mut files: FilesWithMetadata = vec![]; +/// Determines the package kind for a given package in the compilation context +fn determine_package_kind(package_id: PackageId, compilation: &Compilation) -> Option { + let is_current_package = compilation.current_package_id == Some(package_id); + + if package_id == PackageId::CORE { + // Core package is always included in the compilation. + Some(PackageKind::Core) + } else if package_id == 1.into() { + // Standard package is currently always included, but this isn't enforced by the compiler. + Some(PackageKind::StandardLibrary) + } else if is_current_package { + // This package could be user code if current package is specified. + Some(PackageKind::UserCode) + } else { + // This is a either a direct dependency of the user code or + // is not a package user can access (an indirect dependency). + compilation + .dependencies + .get(&package_id) + .map(|alias| PackageKind::AliasedPackage(alias.to_string())) + } +} - let display = &CodeDisplay { - compilation: &compilation, - }; +/// Processes all packages in a compilation and builds a table-of-contents structure +fn build_toc_from_compilation( + compilation: &Compilation, + mut files: Option<&mut FilesWithMetadata>, +) -> ToC { + let display = &CodeDisplay { compilation }; let mut toc: ToC = FxHashMap::default(); for (package_id, unit) in &compilation.package_store { - let is_current_package = compilation.current_package_id == Some(package_id); - let package_kind; - if package_id == PackageId::CORE { - // Core package is always included in the compilation. - package_kind = PackageKind::Core; - } else if package_id == 1.into() { - // Standard package is currently always included, but this isn't enforced by the compiler. - package_kind = PackageKind::StandardLibrary; - } else if is_current_package { - // This package could be user code if current package is specified. - package_kind = PackageKind::UserCode; - } else if let Some(alias) = compilation.dependencies.get(&package_id) { - // This is a direct dependency of the user code. - package_kind = PackageKind::AliasedPackage(alias.to_string()); - } else { - // This is not a package user can access (an indirect dependency). + let Some(package_kind) = determine_package_kind(package_id, compilation) else { continue; - } + }; + let is_current_package = compilation.current_package_id == Some(package_id); let package = &unit.package; + for (_, item) in &package.items { if let Some((ns, metadata)) = generate_doc_for_item( package_id, @@ -324,13 +325,31 @@ pub fn generate_docs( is_current_package, item, display, - &mut files, + files.as_deref_mut().unwrap_or(&mut vec![]), ) { toc.entry(ns).or_default().push(metadata); } } } + toc +} + +/// Generates and returns documentation files for the standard library +/// and additional sources (if specified.) +#[must_use] +pub fn generate_docs( + additional_sources: Option<(PackageStore, &Dependencies, SourceMap)>, + capabilities: Option, + language_features: Option, +) -> Files { + // Capabilities should default to all capabilities for documentation generation. + let capabilities = Some(capabilities.unwrap_or(TargetCapabilityFlags::all())); + let compilation = Compilation::new(additional_sources, capabilities, language_features); + let mut files: FilesWithMetadata = vec![]; + + let mut toc = build_toc_from_compilation(&compilation, Some(&mut files)); + // Generate Overview files for each namespace for (ns, items) in &mut toc { generate_index_file(&mut files, ns, items); @@ -709,3 +728,74 @@ fn get_metadata( signature, }) } + +/// Generates summary documentation organized by namespace. +/// Returns a map of namespace -> metadata items for easier testing and manipulation. +fn generate_summaries_map() -> BTreeMap>> { + let compilation = Compilation::new(None, None, None); + + // Use the shared logic to build ToC structure + let toc = build_toc_from_compilation(&compilation, None); + + // Convert ToC to BTreeMap, filtering out table of contents entries + let mut result = BTreeMap::new(); + + for (ns, items) in toc { + let mut summaries = Vec::new(); + + for item in items { + // Skip table of contents entries + if item.kind == MetadataKind::TableOfContents { + continue; + } + + summaries.push(item); + } + + if !summaries.is_empty() { + // Sort items within namespace + summaries.sort_by_key(|item| item.name.clone()); + result.insert(ns.to_string(), summaries); + } + } + + result +} +/// Converts a Metadata item to its markdown representation +fn metadata_to_markdown(item: &Metadata) -> String { + let mut result = format!("## {}\n\n", item.name); + let _ = write!(result, "```qsharp\n{}\n```\n\n", item.signature); + if !item.summary.is_empty() { + let _ = write!(result, "{}\n\n", item.summary); + } + result +} + +/// Generates markdown summary for a single namespace +fn generate_namespace_summary(namespace: &str, items: &[Rc]) -> String { + let mut result = format!("# {namespace}\n\n"); + + for item in items { + result.push_str(&metadata_to_markdown(item)); + } + + result +} + +/// Generates summary documentation organized by namespace. +/// Returns a single markdown string with namespace headers and minimal item documentation +/// containing just function signatures and summaries for efficient consumption by language models. +#[must_use] +pub fn generate_summaries() -> String { + let summaries_map = generate_summaries_map(); + + // Generate markdown output organized by namespace + let mut result = String::new(); + + // Sort namespaces for consistent output (BTreeMap already sorts keys) + for (ns, items) in &summaries_map { + result.push_str(&generate_namespace_summary(ns, items)); + } + + result +} diff --git a/source/compiler/qsc_doc_gen/src/generate_docs/tests.rs b/source/compiler/qsc_doc_gen/src/generate_docs/tests.rs index 6ee8082fa1..66407605eb 100644 --- a/source/compiler/qsc_doc_gen/src/generate_docs/tests.rs +++ b/source/compiler/qsc_doc_gen/src/generate_docs/tests.rs @@ -1,8 +1,31 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use super::generate_docs; +use crate::generate_docs::{generate_docs, generate_summaries_map, metadata_to_markdown}; use expect_test::expect; +use std::collections::BTreeMap; + +/// Testing helper function that returns summaries as a structured map +/// for easier test validation. Returns a map where each namespace maps to +/// a vector of markdown strings, one per item. +pub fn generate_summaries_for_testing() -> BTreeMap> { + let summaries_map = generate_summaries_map(); + + let mut result = BTreeMap::new(); + + for (ns, items) in summaries_map { + let mut item_markdowns = Vec::new(); + + for item in items { + let markdown = metadata_to_markdown(&item); + item_markdowns.push(markdown); + } + + result.insert(ns, item_markdowns); + } + + result +} #[test] fn generates_standard_item() { @@ -207,3 +230,84 @@ fn top_index_file_generation() { "#]] .assert_eq(full_contents.as_str()); } + +#[test] +fn generates_standard_item_summary() { + let summaries = generate_summaries_for_testing(); + // Find a summary for a known item, e.g., Std.Core.Length + let core_summaries = summaries + .get("Std.Core") + .expect("Could not find Std.Core namespace"); + let length_summary = core_summaries + .iter() + .find(|item| item.contains("## Length")) + .expect("Could not find summary for Length"); + + expect![[r#" + ## Length + + ```qsharp + function Length<'T>(a : 'T[]) : Int + ``` + + Returns the number of elements in the input array `a`. + + "#]] + .assert_eq(length_summary); +} + +#[test] +fn generates_std_core_summary() { + let summaries = generate_summaries_for_testing(); + let core_summaries = summaries + .get("Std.Core") + .expect("Could not find Std.Core namespace"); + + // Combine all summaries for the namespace + let combined_summary = core_summaries.join("\n\n"); + + expect![[r#" + ## Length + + ```qsharp + function Length<'T>(a : 'T[]) : Int + ``` + + Returns the number of elements in the input array `a`. + + + + ## Repeated + + ```qsharp + function Repeated<'T>(value : 'T, length : Int) : 'T[] + ``` + + Creates an array of given `length` with all elements equal to given `value`. `length` must be a non-negative integer. + + "#]] + .assert_eq(&combined_summary); +} + +#[test] +fn generates_summary_for_reexport() { + let summaries = generate_summaries_for_testing(); + let length_summary = summaries + .get("Microsoft.Quantum.Core") + .expect("Could not find Microsoft.Quantum.Core namespace") + .iter() + .find(|item| item.contains("## Length")) + .expect("Could not find summary for Length"); + + expect![[r#" + ## Length + + ```qsharp + + ``` + + This is an exported item. The actual definition is found here: [Std.Core.Length](xref:Qdk.Std.Core.Length) + + "#]] + .assert_eq(length_summary); +} diff --git a/source/npm/qsharp/src/compiler/compiler.ts b/source/npm/qsharp/src/compiler/compiler.ts index 9ff7e23540..be5cb176ad 100644 --- a/source/npm/qsharp/src/compiler/compiler.ts +++ b/source/npm/qsharp/src/compiler/compiler.ts @@ -76,6 +76,8 @@ export interface ICompiler { getDocumentation(additionalProgram?: ProgramConfig): Promise; + getLibrarySummaries(): Promise; + checkExerciseSolution( userCode: string, exerciseSources: string[], @@ -249,6 +251,10 @@ export class Compiler implements ICompiler { ); } + async getLibrarySummaries(): Promise { + return this.wasm.get_library_summaries(); + } + async checkExerciseSolution( userCode: string, exerciseSources: string[], @@ -348,6 +354,7 @@ export const compilerProtocol: ServiceProtocol = { getEstimates: "request", getCircuit: "request", getDocumentation: "request", + getLibrarySummaries: "request", run: "requestWithProgress", runWithNoise: "requestWithProgress", checkExerciseSolution: "requestWithProgress", diff --git a/source/npm/qsharp/test/basics.js b/source/npm/qsharp/test/basics.js index 9d40417cf3..872e623bd6 100644 --- a/source/npm/qsharp/test/basics.js +++ b/source/npm/qsharp/test/basics.js @@ -84,6 +84,32 @@ test("autogenerated documentation", async () => { ); }); +test("library summaries slim docs", async () => { + const compiler = getCompiler(); + const summaries = await compiler.getLibrarySummaries(); + assert(typeof summaries === "string", "Summaries should be a string"); + assert(summaries.length > 0, "Summaries should not be empty"); + + // Check that it contains namespace headers (markdown format) + assert( + summaries.includes("# Microsoft.Quantum"), + "Should contain standard library namespaces", + ); + + // Check that it contains function signatures in code blocks + assert(summaries.includes("```qsharp"), "Should contain Q# code blocks"); + assert(summaries.includes("## "), "Should contain function headers"); + + // Check that it's organized by namespace + const lines = summaries.split("\n"); + const namespaceHeaders = lines.filter((line) => line.startsWith("# ")); + assert(namespaceHeaders.length > 0, "Should have namespace headers"); + + console.log( + `Generated ${summaries.length} characters of summaries with ${namespaceHeaders.length} namespaces`, + ); +}); + test("basic eval", async () => { let code = `namespace Test { function Answer() : Int { diff --git a/source/vscode/package.json b/source/vscode/package.json index 9e19fd69c1..cbe3a0e71f 100644 --- a/source/vscode/package.json +++ b/source/vscode/package.json @@ -934,6 +934,25 @@ ], "additionalProperties": false } + }, + { + "name": "qsharp-get-library-descriptions", + "tags": [ + "azure-quantum", + "qsharp", + "qdk" + ], + "toolReferenceName": "qsharpGetLibraryDescriptions", + "displayName": "Get Q# Library API Descriptions", + "modelDescription": "Returns a Markdown string summarizing all Q# standard library items, grouped by namespace. Each entry includes its signature and a short description extracted from doc comments when available. Use this tool to answer any questions about Q# library functions or to ensure generated Q# code uses library functions correctly.", + "canBeReferencedInPrompt": true, + "icon": "./resources/file-icon-light.svg", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } } ] }, diff --git a/source/vscode/resources/chat-instructions/qsharp.instructions.md b/source/vscode/resources/chat-instructions/qsharp.instructions.md index 81b0e3ff08..eff35f35a4 100644 --- a/source/vscode/resources/chat-instructions/qsharp.instructions.md +++ b/source/vscode/resources/chat-instructions/qsharp.instructions.md @@ -127,6 +127,8 @@ and `azure-quantum` packages. To execute Q# code, use the provided tools. +Whenever the user asks about Q# standard libraries, their contents, or any Q# library function, you **must** call the `qsharpGetLibraryDescriptions` tool to retrieve the authoritative list of available Q# library items. When generating Q# code, always use the `qsharpGetLibraryDescriptions` tool to determine which library functions are available and to ensure you use them correctly in your code suggestions. Do not attempt to answer questions about Q# library APIs, functions, or operations without first consulting this tool. + ## Response formatting Avoid using LaTeX in your responses to the user. diff --git a/source/vscode/src/gh-copilot/qsharpTools.ts b/source/vscode/src/gh-copilot/qsharpTools.ts index 23f082fb74..360f2d54bd 100644 --- a/source/vscode/src/gh-copilot/qsharpTools.ts +++ b/source/vscode/src/gh-copilot/qsharpTools.ts @@ -222,6 +222,23 @@ export class QSharpTools { } } + /** + * Copilot tool: Returns a Markdown string summarizing all Q# standard library items, + * organized by namespace. Each entry includes its signature and a short description extracted + * from doc comments when available. + */ + async qsharpGetLibraryDescriptions(): Promise { + const compilerRunTimeoutMs = 1000 * 5; // 5 seconds + const compilerTimeout = setTimeout(() => { + worker.terminate(); + }, compilerRunTimeoutMs); + const worker = loadCompilerWorker(this.extensionUri!); + const summaries = await worker.getLibrarySummaries(); + clearTimeout(compilerTimeout); + worker.terminate(); + return summaries; + } + async getProgram( filePath: string, options: { targetProfileFallback?: TargetProfile } = {}, diff --git a/source/vscode/src/gh-copilot/tools.ts b/source/vscode/src/gh-copilot/tools.ts index acdbe19908..3a58a7ae73 100644 --- a/source/vscode/src/gh-copilot/tools.ts +++ b/source/vscode/src/gh-copilot/tools.ts @@ -97,6 +97,10 @@ const toolDefinitions: { name: "qdk-run-resource-estimator", tool: async (input) => await qsharpTools!.runResourceEstimator(input), }, + { + name: "qsharp-get-library-descriptions", + tool: async () => await qsharpTools!.qsharpGetLibraryDescriptions(), + }, ]; export function registerLanguageModelTools(context: vscode.ExtensionContext) { diff --git a/source/wasm/src/lib.rs b/source/wasm/src/lib.rs index d0dc54c432..8739d9dd00 100644 --- a/source/wasm/src/lib.rs +++ b/source/wasm/src/lib.rs @@ -670,6 +670,12 @@ pub fn generate_docs(additional_program: Option) -> Vec result } +#[wasm_bindgen] +#[must_use] +pub fn get_library_summaries() -> String { + qsc_doc_gen::generate_docs::generate_summaries() +} + fn get_debugger_from_openqasm( sources: &[(Arc, Arc)], capabilities: TargetCapabilityFlags,