Skip to main content

projectatlas_core/
language.rs

1//! Purpose: Own deterministic language capability and detection truth.
2
3use blake3::Hasher;
4use serde::{Deserialize, Serialize};
5use std::collections::{BTreeMap, BTreeSet};
6use std::error::Error;
7use std::fmt;
8use std::fmt::Write as _;
9use std::path::Path;
10use std::str::FromStr;
11use std::sync::OnceLock;
12
13/// Version of the generated language registry contract.
14pub const LANGUAGE_CAPABILITY_REGISTRY_VERSION: u32 = 5;
15
16/// Version of the direct and embedded semantic-provider projection.
17pub const SEMANTIC_PROVIDER_CONTRACT_VERSION: u32 = 1;
18
19/// Version of the accepted language capability floor.
20pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION: u32 = 15;
21
22/// Version of exact detector precedence and content-matching semantics.
23pub const LANGUAGE_DETECTION_POLICY_VERSION: u32 = 1;
24
25/// Historical acceptance seal for capability-set version 1.
26///
27/// Any membership, detector, owner, minimum, fixture, provenance, or platform
28/// change must bump the accepted-set version instead of rewriting this seal.
29pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST: &str =
30    "58f2e1e6755464d573df998c1c1cecb2d076c829c0dbe04a463ce14a5a239861";
31
32/// Historical acceptance seal for capability-set version 2.
33///
34/// Version 2 preserves version 1 membership and capability strength while
35/// binding ProjectAtlas-owned parser provenance to the 0.4.0 runtime.
36pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST: &str =
37    "8397f73a7593b849d0e83b3892e4721874ac4a4fd93f62b42dac9ffe166a2a7c";
38
39/// Historical acceptance seal for capability-set version 3.
40///
41/// Version 3 adds the independently owned Rust, ECMAScript, Python, and Cargo
42/// semantic provider floor plus typed HTML-like/component/template embedding.
43pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST: &str =
44    "a4b69ce4aed2ebf8d28f7b237ead76a53e5363e34c8c97ea5980776ea4217ef4";
45
46/// Historical acceptance seal for capability-set version 4.
47///
48/// Version 4 preserves version 3 membership and capability strength while
49/// binding ProjectAtlas-owned parser provenance to the 0.4.1 runtime.
50pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST: &str =
51    "e9a952d0b3bc2d2c5db832130d85b7cdfd656aaa07ebbafab1505da6b87d9084";
52
53/// Historical acceptance seal for capability-set version 5.
54///
55/// Version 5 preserves version 4 membership and capability strength while
56/// binding ProjectAtlas-owned parser provenance to the 0.4.2 runtime.
57pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST: &str =
58    "07a3d2c45a4736bc764e44016a6ba9b7f9ea1b769b0100604702160528679bc7";
59
60/// Historical acceptance seal for capability-set version 6.
61///
62/// Version 6 preserves version 5 membership and capability strength while
63/// binding ProjectAtlas-owned parser provenance to the 0.4.3 runtime.
64pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V6_DIGEST: &str =
65    "e9342f2b06b083a72ecc58af0afe4ba12f0ec33321225199bb0c9be4f4375c7a";
66
67/// Historical acceptance seal for capability-set version 7.
68///
69/// Version 7 preserves version 6 membership and capability strength while
70/// binding ProjectAtlas-owned parser provenance to the 0.4.4 runtime.
71pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V7_DIGEST: &str =
72    "50fcac887dffecc27f1b7d365ff5da991f2a86dc15e0474ef5bcc339c58bfd60";
73
74/// Historical acceptance seal for capability-set version 8.
75///
76/// Version 8 adds registry-owned content roles and bounded Markdown heading
77/// symbols without weakening any version 7 capability.
78pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V8_DIGEST: &str =
79    "2b26ae43b74475ea0dcb78d5b182329d500d76c33b2d87470d303400886ead1b";
80
81/// Historical acceptance seal for capability-set version 9.
82///
83/// Version 9 preserves version 8 membership and capability strength while
84/// binding ProjectAtlas-owned parser provenance to the 0.4.5-rc1 runtime.
85pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V9_DIGEST: &str =
86    "5fa0073094df29fba7160cecf85afa3ba5a9bcfa7ee4b5a53cde9e371b0077d2";
87
88/// Historical acceptance seal for capability-set version 10.
89///
90/// Version 10 preserves version 9 membership and capability strength while
91/// binding ProjectAtlas-owned parser provenance to the 0.4.5-rc2 runtime.
92pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V10_DIGEST: &str =
93    "cbede576a7b2ab4309798075210b59dbece0cf99cc7874a9659fd17f3c2d961f";
94
95/// Historical acceptance seal for capability-set version 11.
96///
97/// Version 11 preserves version 10 membership and capability strength while
98/// binding ProjectAtlas-owned parser provenance to the 0.4.5-rc3 runtime.
99pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V11_DIGEST: &str =
100    "3776f19c62b3debfcae13715e3bdc3ec3029978a4f7ba1428b7a06d433524915";
101
102/// Historical acceptance seal for capability-set version 12.
103///
104/// Version 12 preserves version 11 membership and capability strength while
105/// binding ProjectAtlas-owned parser provenance to the 0.4.5 runtime.
106pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V12_DIGEST: &str =
107    "bae01db588d8e6c8666bb1afd66ffcbffb3022c23c68df52f9822c291f9d895c";
108
109/// Acceptance seal for the built-in PHP grammar capability set.
110pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V13_DIGEST: &str =
111    "63ccc321601fcc207a7540163abb9cff9547d41e43b8b696073e22707db4a3d1";
112
113/// Acceptance seal for PDF and DOCX alongside the built-in PHP grammar.
114pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V14_DIGEST: &str =
115    "323321adb18b8f7c9ddc045949fc097bd0e6933422228c2c25a8ee5b07daeac9";
116
117/// Historical acceptance seal for capability-set version 15.
118///
119/// Version 15 preserves version 14 membership and capability strength while
120/// binding ProjectAtlas-owned parser provenance to the 0.5.0-rc1 runtime.
121pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V15_DIGEST: &str =
122    "f560cb41478d81ac46b3ac5d79ad9e94a27a235f1b4209091c4e0220298d2b1a";
123
124/// Maximum content prefix inspected by the bounded content/dialect detector.
125pub const LANGUAGE_CONTENT_DETECTION_MAX_BYTES: usize = 512;
126
127/// Pinned metadata catalog used to admit broad detection and fallback rows.
128pub const OPTIONAL_GRAMMAR_CATALOG: &str = "tree-sitter-language-pack";
129
130/// Exact catalog version used by the accepted broad-language registry.
131pub const OPTIONAL_GRAMMAR_CATALOG_VERSION: &str = "1.13.2";
132
133/// Exact upstream release-tag revision used by the accepted broad-language registry.
134pub const OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION: &str =
135    "6258abac30304283763a0d2dc8a48cb87fbcf438";
136
137/// Minimum additional non-built-in grammars required for the broad-pack claim.
138pub const OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS: usize = 150;
139
140/// Stable owner identity for the one logical optional broad parser pack.
141pub const BROAD_PARSER_PACK_ID: &str = "broad-parser";
142
143/// Maximum JSON bytes for the compact language settings projection.
144pub const LANGUAGE_REGISTRY_REPORT_MAX_BYTES: usize = 32_000;
145
146/// Parser coverage level retained for compatibility with the 0.3.26 API.
147#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
148#[serde(rename_all = "kebab-case")]
149pub enum LanguageParserSupport {
150    /// A native Tree-sitter adapter backs symbol extraction.
151    Native,
152    /// A manifest-specific parser backs package/dependency extraction.
153    Manifest,
154    /// A deterministic structural summarizer backs agent-facing summaries.
155    Structural,
156    /// A conservative fallback parser is the current coverage boundary.
157    Fallback,
158}
159
160/// Independently achieved support for one language capability axis.
161#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
162#[serde(rename_all = "kebab-case")]
163pub enum CapabilitySupportLevel {
164    /// The capability has not been accepted or proved.
165    Unavailable,
166    /// Conservative heuristic behavior exists and is reported as fallback.
167    Fallback,
168    /// The capability is supported by its declared owner and fixtures.
169    Supported,
170}
171
172impl CapabilitySupportLevel {
173    /// Return the stable settings and documentation label.
174    #[must_use]
175    pub const fn as_str(self) -> &'static str {
176        match self {
177            Self::Unavailable => "unavailable",
178            Self::Fallback => "fallback",
179            Self::Supported => "supported",
180        }
181    }
182}
183
184/// Independent language capability axes.
185#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
186pub struct LanguageCapabilitySupport {
187    /// The language can be selected deterministically.
188    pub detected: CapabilitySupportLevel,
189    /// Source can be parsed at the declared quality level.
190    pub parsed: CapabilitySupportLevel,
191    /// Definitions and relationships can be extracted at the declared quality level.
192    pub symbols: CapabilitySupportLevel,
193    /// Project-wide semantic resolution has been independently validated.
194    pub semantic: CapabilitySupportLevel,
195    /// Representative benchmark coverage exists.
196    pub benchmarked: CapabilitySupportLevel,
197}
198
199impl LanguageCapabilitySupport {
200    /// Build one independent capability profile.
201    #[must_use]
202    pub const fn new(
203        detected: CapabilitySupportLevel,
204        parsed: CapabilitySupportLevel,
205        symbols: CapabilitySupportLevel,
206        semantic: CapabilitySupportLevel,
207        benchmarked: CapabilitySupportLevel,
208    ) -> Self {
209        Self {
210            detected,
211            parsed,
212            symbols,
213            semantic,
214            benchmarked,
215        }
216    }
217
218    /// Return whether every achieved axis meets the accepted minimum.
219    #[must_use]
220    pub const fn meets(self, minimum: Self) -> bool {
221        self.detected as u8 >= minimum.detected as u8
222            && self.parsed as u8 >= minimum.parsed as u8
223            && self.symbols as u8 >= minimum.symbols as u8
224            && self.semantic as u8 >= minimum.semantic as u8
225            && self.benchmarked as u8 >= minimum.benchmarked as u8
226    }
227}
228
229/// Grammar-backed or purpose-built parsing and symbol support.
230const SUPPORTED_NATIVE: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
231    CapabilitySupportLevel::Supported,
232    CapabilitySupportLevel::Supported,
233    CapabilitySupportLevel::Supported,
234    CapabilitySupportLevel::Unavailable,
235    CapabilitySupportLevel::Unavailable,
236);
237
238/// Grammar-backed or purpose-built support with project-wide semantic resolution.
239const SUPPORTED_SEMANTIC: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
240    CapabilitySupportLevel::Supported,
241    CapabilitySupportLevel::Supported,
242    CapabilitySupportLevel::Supported,
243    CapabilitySupportLevel::Supported,
244    CapabilitySupportLevel::Unavailable,
245);
246
247/// Deterministic structural parsing without a symbol extractor.
248const SUPPORTED_STRUCTURAL: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
249    CapabilitySupportLevel::Supported,
250    CapabilitySupportLevel::Supported,
251    CapabilitySupportLevel::Unavailable,
252    CapabilitySupportLevel::Unavailable,
253    CapabilitySupportLevel::Unavailable,
254);
255
256/// Deterministic detection with conservative parsing and symbol fallback.
257const SUPPORTED_FALLBACK: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
258    CapabilitySupportLevel::Supported,
259    CapabilitySupportLevel::Fallback,
260    CapabilitySupportLevel::Fallback,
261    CapabilitySupportLevel::Unavailable,
262    CapabilitySupportLevel::Unavailable,
263);
264
265/// Closed built-in Tree-sitter grammar identity.
266#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
267#[serde(rename_all = "kebab-case")]
268pub enum TreeSitterGrammar {
269    /// Rust grammar.
270    Rust,
271    /// Python grammar.
272    Python,
273    /// JavaScript grammar.
274    JavaScript,
275    /// TypeScript grammar.
276    TypeScript,
277    /// TSX grammar.
278    Tsx,
279    /// Java grammar.
280    Java,
281    /// Kotlin grammar.
282    Kotlin,
283    /// C# grammar.
284    CSharp,
285    /// Go grammar.
286    Go,
287    /// Objective-C grammar.
288    ObjectiveC,
289    /// Zig grammar.
290    Zig,
291    /// C grammar.
292    C,
293    /// C++ grammar.
294    Cpp,
295    /// PHP grammar.
296    Php,
297}
298
299impl TreeSitterGrammar {
300    /// Every closed built-in grammar identity.
301    pub const ALL: &'static [Self] = &[
302        Self::Rust,
303        Self::Python,
304        Self::JavaScript,
305        Self::TypeScript,
306        Self::Tsx,
307        Self::Java,
308        Self::Kotlin,
309        Self::CSharp,
310        Self::Go,
311        Self::ObjectiveC,
312        Self::Zig,
313        Self::C,
314        Self::Cpp,
315        Self::Php,
316    ];
317
318    /// Return the exact Cargo package that provides this built-in grammar.
319    #[must_use]
320    pub const fn package(self) -> &'static str {
321        match self {
322            Self::Rust => "tree-sitter-rust",
323            Self::Python => "tree-sitter-python",
324            Self::JavaScript => "tree-sitter-javascript",
325            Self::TypeScript | Self::Tsx => "tree-sitter-typescript",
326            Self::Java => "tree-sitter-java",
327            Self::Kotlin => "tree-sitter-kotlin-ng",
328            Self::CSharp => "tree-sitter-c-sharp",
329            Self::Go => "tree-sitter-go",
330            Self::ObjectiveC => "tree-sitter-objc",
331            Self::Zig => "tree-sitter-zig",
332            Self::C => "tree-sitter-c",
333            Self::Cpp => "tree-sitter-cpp",
334            Self::Php => "tree-sitter-php",
335        }
336    }
337
338    /// Return the exact workspace-pinned Cargo version of this grammar.
339    #[must_use]
340    pub const fn version(self) -> &'static str {
341        match self {
342            Self::Rust | Self::C | Self::Php => "0.24.2",
343            Self::Python | Self::JavaScript | Self::Go => "0.25.0",
344            Self::TypeScript | Self::Tsx => "0.23.2",
345            Self::Java | Self::CSharp => "0.23.5",
346            Self::Kotlin => "1.1.0",
347            Self::ObjectiveC => "3.0.2",
348            Self::Zig => "1.1.2",
349            Self::Cpp => "0.23.4",
350        }
351    }
352}
353
354/// Closed owner of built-in symbol extraction.
355#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
356#[serde(rename_all = "kebab-case")]
357pub enum SymbolParserOwner {
358    /// A declared Tree-sitter grammar.
359    TreeSitter(TreeSitterGrammar),
360    /// `ProjectAtlas` Cargo manifest/lock extraction.
361    CargoManifest,
362    /// `ProjectAtlas` Vue component extraction.
363    Vue,
364    /// `ProjectAtlas` `PowerShell` extraction.
365    PowerShell,
366    /// `ProjectAtlas` Markdown heading and explicit-reference extraction.
367    Markdown,
368    /// Bounded PDF and DOCX text and evidence extraction.
369    Document,
370    /// Conservative `ProjectAtlas` fallback extraction.
371    Fallback,
372    /// No definition or relationship extractor is available.
373    Unavailable,
374}
375
376/// Closed owner of project-wide semantic resolution.
377#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
378#[serde(rename_all = "kebab-case")]
379pub enum SemanticProviderOwner {
380    /// Rust modules, imports, exports, and references.
381    Rust,
382    /// JavaScript, TypeScript, and TSX modules, imports, exports, and references.
383    EcmaScript,
384    /// Python modules, imports, exports, and references.
385    Python,
386    /// Cargo package and dependency identities.
387    Cargo,
388    /// No independently validated project-wide semantic provider is available.
389    Unavailable,
390}
391
392impl SemanticProviderOwner {
393    /// Return the stable settings and documentation label.
394    #[must_use]
395    pub const fn as_str(self) -> &'static str {
396        match self {
397            Self::Rust => "rust",
398            Self::EcmaScript => "ecma-script",
399            Self::Python => "python",
400            Self::Cargo => "cargo",
401            Self::Unavailable => "unavailable",
402        }
403    }
404
405    /// Return the canonical cross-language resolution family owned by this provider.
406    ///
407    /// The provider label identifies the implementation owner while this family
408    /// keeps compatible source dialects, such as JavaScript, TypeScript, and
409    /// TSX, in one resolution namespace.
410    #[must_use]
411    pub const fn resolution_family(self) -> Option<&'static str> {
412        match self {
413            Self::Rust => Some("rust"),
414            Self::EcmaScript => Some("ecmascript"),
415            Self::Python => Some("python"),
416            Self::Cargo => Some("cargo"),
417            Self::Unavailable => None,
418        }
419    }
420}
421
422/// Closed host class for bounded embedded-source projection.
423#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
424#[serde(rename_all = "kebab-case")]
425pub enum EmbeddedHostKind {
426    /// HTML-like host source.
427    HtmlLike,
428    /// Component host source.
429    Component,
430    /// Template host source.
431    Template,
432}
433
434impl EmbeddedHostKind {
435    /// Return the stable settings and documentation label.
436    #[must_use]
437    pub const fn as_str(self) -> &'static str {
438        match self {
439            Self::HtmlLike => "html-like",
440            Self::Component => "component",
441            Self::Template => "template",
442        }
443    }
444}
445
446/// One accepted host-to-embedded semantic provider pairing.
447#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
448pub struct EmbeddedLanguageCapability {
449    /// Host extraction class used for bounded source reconciliation.
450    pub host_kind: EmbeddedHostKind,
451    /// Provider that interprets accepted embedded source regions.
452    pub semantic_provider: SemanticProviderOwner,
453}
454
455/// Closed owner of deterministic structural summaries.
456#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
457#[serde(rename_all = "kebab-case")]
458pub enum StructuralSummaryOwner {
459    /// Markdown/CommonMark summaries.
460    Markdown,
461    /// JSON and JSONC summaries.
462    Json,
463    /// YAML summaries.
464    Yaml,
465    /// TOML and Cargo manifest summaries.
466    Toml,
467    /// XML summaries.
468    Xml,
469    /// CSS-family summaries.
470    Css,
471    /// HTML summaries.
472    Html,
473    /// TOON summaries.
474    Toon,
475    /// `PowerShell` summaries.
476    PowerShell,
477    /// Configuration and plain-text summaries.
478    ConfigText,
479}
480
481/// Provenance class for an accepted language row.
482#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
483#[serde(rename_all = "kebab-case")]
484pub enum CapabilityProvenance {
485    /// ProjectAtlas-owned deterministic behavior under the workspace license.
486    ProjectAtlas,
487    /// A pinned Cargo Tree-sitter grammar dependency.
488    TreeSitter(TreeSitterGrammar),
489    /// ProjectAtlas-accepted detection metadata from the pinned optional catalog.
490    ///
491    /// This does not claim that the corresponding grammar binary, its subtree
492    /// license, or its required-platform behavior has passed optional-pack gates.
493    PinnedOptionalCatalog,
494}
495
496impl CapabilityProvenance {
497    /// Return the declared license input for settings and generated documentation.
498    #[must_use]
499    pub const fn license(self) -> &'static str {
500        match self {
501            Self::ProjectAtlas | Self::TreeSitter(_) | Self::PinnedOptionalCatalog => "MIT",
502        }
503    }
504
505    /// Return the exact owning package or catalog identity.
506    #[must_use]
507    pub const fn source(self) -> &'static str {
508        match self {
509            Self::ProjectAtlas => "projectatlas",
510            Self::TreeSitter(grammar) => grammar.package(),
511            Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG,
512        }
513    }
514
515    /// Return the exact owning package or catalog version.
516    #[must_use]
517    pub const fn version(self) -> &'static str {
518        match self {
519            Self::ProjectAtlas => env!("CARGO_PKG_VERSION"),
520            Self::TreeSitter(grammar) => grammar.version(),
521            Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG_VERSION,
522        }
523    }
524}
525
526/// Required platform applicability for one accepted capability.
527#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
528#[serde(rename_all = "kebab-case")]
529pub enum RequiredPlatformSet {
530    /// Every platform supported by the `ProjectAtlas` release matrix.
531    AllSupported,
532}
533
534/// Natural positive and negative detector fixtures for an accepted row.
535#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
536pub struct LanguageCapabilityFixtures {
537    /// Natural path that must select this language.
538    pub positive_path: &'static str,
539    /// Similar path that must not select this language.
540    pub negative_path: &'static str,
541}
542
543/// Derived role of one admitted repository file.
544#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
545#[serde(rename_all = "snake_case")]
546pub enum ContentClassification {
547    /// Program, template, query, build, or style source.
548    Source,
549    /// Human-facing documentation or specification content.
550    Documentation,
551    /// Structured configuration or data content.
552    ConfigurationData,
553    /// Eligible UTF-8 text without a more specific registry role.
554    OtherText,
555    /// Admitted metadata whose content is binary or invalid UTF-8.
556    Opaque,
557}
558
559impl ContentClassification {
560    /// Complete stable persisted classification set.
561    pub const ALL: [Self; 5] = [
562        Self::Source,
563        Self::Documentation,
564        Self::ConfigurationData,
565        Self::OtherText,
566        Self::Opaque,
567    ];
568
569    /// Return the stable database and payload spelling.
570    #[must_use]
571    pub const fn as_str(self) -> &'static str {
572        match self {
573            Self::Source => "source",
574            Self::Documentation => "documentation",
575            Self::ConfigurationData => "configuration_data",
576            Self::OtherText => "other_text",
577            Self::Opaque => "opaque",
578        }
579    }
580
581    /// Parse one stable persisted classification.
582    #[must_use]
583    pub fn from_db(value: &str) -> Option<Self> {
584        match value {
585            "source" => Some(Self::Source),
586            "documentation" => Some(Self::Documentation),
587            "configuration_data" => Some(Self::ConfigurationData),
588            "other_text" => Some(Self::OtherText),
589            "opaque" => Some(Self::Opaque),
590            _ => None,
591        }
592    }
593}
594
595impl fmt::Display for ContentClassification {
596    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
597        formatter.write_str(self.as_str())
598    }
599}
600
601/// Caller selection applied to classified repository content.
602#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
603#[serde(rename_all = "snake_case")]
604pub enum ContentSelection {
605    /// The request omitted selection and must retain the legacy candidate universe.
606    #[default]
607    #[serde(skip)]
608    UnspecifiedLegacy,
609    /// Select only source files for ordinary candidates and graph frontiers.
610    Source,
611    /// Select only documentation for ordinary candidates and graph frontiers.
612    Documentation,
613    /// Select source and documentation while excluding other classifications.
614    Both,
615}
616
617impl ContentSelection {
618    /// Return the explicit wire spelling, or `None` for an omitted legacy request.
619    #[must_use]
620    pub const fn explicit_value(self) -> Option<&'static str> {
621        match self {
622            Self::UnspecifiedLegacy => None,
623            Self::Source => Some("source"),
624            Self::Documentation => Some("documentation"),
625            Self::Both => Some("both"),
626        }
627    }
628
629    /// Return whether the classification belongs to this selection.
630    #[must_use]
631    pub const fn includes(self, classification: ContentClassification) -> bool {
632        match self {
633            Self::UnspecifiedLegacy => true,
634            Self::Source => matches!(classification, ContentClassification::Source),
635            Self::Documentation => {
636                matches!(classification, ContentClassification::Documentation)
637            }
638            Self::Both => matches!(
639                classification,
640                ContentClassification::Source | ContentClassification::Documentation
641            ),
642        }
643    }
644}
645
646/// Invalid explicit content selection.
647#[derive(Clone, Debug, Eq, PartialEq)]
648pub struct ContentSelectionParseError {
649    /// Rejected caller value retained for adapter diagnostics.
650    requested: String,
651}
652
653impl ContentSelectionParseError {
654    /// Borrow the rejected caller input.
655    #[must_use]
656    pub fn requested(&self) -> &str {
657        &self.requested
658    }
659}
660
661impl fmt::Display for ContentSelectionParseError {
662    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
663        write!(
664            formatter,
665            "unsupported content selection {:?}; expected source, documentation, or both",
666            self.requested
667        )
668    }
669}
670
671impl Error for ContentSelectionParseError {}
672
673impl FromStr for ContentSelection {
674    type Err = ContentSelectionParseError;
675
676    fn from_str(value: &str) -> Result<Self, Self::Err> {
677        match value {
678            "source" => Ok(Self::Source),
679            "documentation" => Ok(Self::Documentation),
680            "both" => Ok(Self::Both),
681            _ => Err(ContentSelectionParseError {
682                requested: value.to_owned(),
683            }),
684        }
685    }
686}
687
688/// One accepted canonical language capability row.
689#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
690pub struct LanguageCapability {
691    /// Stable canonical language or file-family identifier.
692    pub id: &'static str,
693    /// Compatibility aliases accepted only for explicit selection.
694    pub aliases: &'static [&'static str],
695    /// Registry-owned content role.
696    pub classification: ContentClassification,
697    /// Compatibility parser tier.
698    pub parser_support: LanguageParserSupport,
699    /// Closed symbol parser owner.
700    pub symbol_parser: SymbolParserOwner,
701    /// Closed project-wide semantic provider owner.
702    pub semantic_provider: SemanticProviderOwner,
703    /// Optional bounded host-to-embedded provider pairing.
704    pub embedded_language: Option<EmbeddedLanguageCapability>,
705    /// Optional structural summary owner.
706    pub structural_summary: Option<StructuralSummaryOwner>,
707    /// Optional broad-parser pack owner; absent for the 0.3.26 core floor.
708    pub optional_pack: Option<&'static str>,
709    /// Currently achieved independent support.
710    pub support: LanguageCapabilitySupport,
711    /// Accepted minimum that cannot shrink silently in this set version.
712    pub accepted_minimum: LanguageCapabilitySupport,
713    /// Natural detector fixtures.
714    pub fixtures: LanguageCapabilityFixtures,
715    /// Parser or `ProjectAtlas` provenance input.
716    pub provenance: CapabilityProvenance,
717    /// Required release platforms.
718    pub required_platforms: RequiredPlatformSet,
719}
720
721impl LanguageCapability {
722    /// Return the accepted semantic provider for direct or embedded source.
723    ///
724    /// Direct providers take precedence. Embedded hosts expose their provider
725    /// only for admitted embedded facts; callers remain responsible for not
726    /// treating the host itself as an ordinary module without such facts.
727    #[must_use]
728    pub const fn effective_semantic_provider(self) -> Option<SemanticProviderOwner> {
729        match self.semantic_provider {
730            SemanticProviderOwner::Unavailable => match self.embedded_language {
731                Some(embedded)
732                    if !matches!(
733                        embedded.semantic_provider,
734                        SemanticProviderOwner::Unavailable
735                    ) =>
736                {
737                    Some(embedded.semantic_provider)
738                }
739                _ => None,
740            },
741            provider => Some(provider),
742        }
743    }
744}
745
746/// Static compatibility parser metadata for one detected language family.
747#[derive(Clone, Copy, Debug, Eq, PartialEq)]
748pub struct LanguageSpec {
749    /// Detected language or file-family identifier.
750    pub language: &'static str,
751    /// Parser coverage level.
752    pub parser_support: LanguageParserSupport,
753}
754
755/// Detection rule class and precedence reason.
756#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
757#[serde(rename_all = "kebab-case")]
758pub enum LanguageDetectionReason {
759    /// A validated explicit override selected the language.
760    ExplicitOverride,
761    /// A case-sensitive exact filename selected the language.
762    ExactFilename,
763    /// The longest declared compound extension selected the language.
764    CompoundExtension,
765    /// A case-insensitive ordinary extension selected the language.
766    Extension,
767    /// A bounded declared content/dialect rule selected the language.
768    ContentDialect,
769}
770
771/// Typed language detection result.
772#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
773pub struct LanguageDetection {
774    /// Canonical registry identifier.
775    pub language: &'static str,
776    /// Rule class that selected the identifier.
777    pub reason: LanguageDetectionReason,
778}
779
780/// Typed language detection request.
781#[derive(Clone, Copy, Debug, Default)]
782pub struct LanguageDetectionRequest<'a> {
783    /// Repository or native path.
784    pub path: &'a str,
785    /// Pre-normalized extension supplied by a scanner, when available.
786    pub extension: Option<&'a str>,
787    /// Explicit canonical ID or alias, when configured.
788    pub explicit_override: Option<&'a str>,
789    /// Bounded content prefix captured by the owning source read.
790    pub content_prefix: Option<&'a [u8]>,
791}
792
793impl<'a> LanguageDetectionRequest<'a> {
794    /// Build a request compatible with the 0.3.26 path/extension API.
795    #[must_use]
796    pub const fn new(path: &'a str, extension: Option<&'a str>) -> Self {
797        Self {
798            path,
799            extension,
800            explicit_override: None,
801            content_prefix: None,
802        }
803    }
804}
805
806/// Invalid explicit language selection.
807#[derive(Clone, Debug, Eq, PartialEq)]
808pub struct LanguageDetectionError {
809    /// Invalid canonical ID or alias supplied by the caller.
810    requested: String,
811}
812
813impl fmt::Display for LanguageDetectionError {
814    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
815        write!(
816            formatter,
817            "unknown explicit language override {:?}",
818            self.requested
819        )
820    }
821}
822
823impl Error for LanguageDetectionError {}
824
825/// Registry validation failure with both conflicting owners where applicable.
826#[derive(Clone, Debug, Eq, PartialEq)]
827pub struct LanguageRegistryError {
828    /// Deterministic validation diagnostic.
829    message: String,
830}
831
832impl LanguageRegistryError {
833    /// Build one deterministic registry diagnostic.
834    fn new(message: impl Into<String>) -> Self {
835        Self {
836            message: message.into(),
837        }
838    }
839}
840
841impl fmt::Display for LanguageRegistryError {
842    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
843        formatter.write_str(&self.message)
844    }
845}
846
847impl Error for LanguageRegistryError {}
848
849/// One static exact, compound, or extension detector rule.
850#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
851pub struct LanguageDetectionRule {
852    /// Match value.
853    pub value: &'static str,
854    /// Canonical owner language.
855    pub language: &'static str,
856}
857
858/// One registry-owned shebang interpreter rule.
859#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
860pub struct LanguageContentRule {
861    /// Lowercase interpreter basename matched after optional `env` or `busybox`.
862    pub interpreter: &'static str,
863    /// Whether an ASCII numeric version suffix such as `3.12` is accepted.
864    pub allow_version_suffix: bool,
865    /// Canonical owner language.
866    pub language: &'static str,
867}
868
869/// Default an omitted semantic provider to an honest unavailable claim.
870macro_rules! semantic_provider_or_unavailable {
871    () => {
872        SemanticProviderOwner::Unavailable
873    };
874    ($provider:expr) => {
875        $provider
876    };
877}
878
879/// Default an omitted embedded-language contract to no host projection.
880macro_rules! embedded_language_or_none {
881    () => {
882        None
883    };
884    ($capability:expr) => {
885        Some($capability)
886    };
887}
888
889/// Default a registry row to source unless it declares a non-source role.
890macro_rules! content_classification_or_source {
891    () => {
892        ContentClassification::Source
893    };
894    ($classification:expr) => {
895        $classification
896    };
897}
898
899/// Generate typed capability and detection projections from one local manifest.
900macro_rules! define_language_registry {
901    (
902        capabilities {
903            $(
904                $id:literal => {
905                    aliases: [$($alias:literal),* $(,)?],
906                    $(classification: $classification:expr,)?
907                    parser_support: $parser_support:ident,
908                    symbol_parser: $symbol_parser:expr,
909                    structural_summary: $structural_summary:expr,
910                    support: $support:expr,
911                    $(semantic_provider: $semantic_provider:expr,)?
912                    $(embedded_language: $embedded_language:expr,)?
913                    positive: $positive:literal,
914                    negative: $negative:literal,
915                    provenance: $provenance:expr
916                }
917            ),* $(,)?
918        }
919        optional_capabilities {
920            $(
921                $optional_id:literal => {
922                    aliases: [$($optional_alias:literal),* $(,)?],
923                    $(classification: $optional_classification:expr,)?
924                    extension: $optional_extension:literal
925                }
926            ),* $(,)?
927        }
928        exact_filenames { $($exact:literal => $exact_language:literal),* $(,)? }
929        compound_extensions { $($compound:literal => $compound_language:literal),* $(,)? }
930        broad_extensions { $($extension:literal => $extension_language:literal),* $(,)? }
931        additional_extensions { $($additional:literal => $additional_language:literal),* $(,)? }
932        content_interpreters {
933            $(
934                $interpreter:literal => {
935                    language: $content_language:literal,
936                    version_suffix: $version_suffix:literal
937                }
938            ),* $(,)?
939        }
940    ) => {
941        /// Accepted canonical language capabilities.
942        pub const LANGUAGE_CAPABILITIES: &[LanguageCapability] = &[
943            $(LanguageCapability {
944                id: $id,
945                aliases: &[$($alias),*],
946                classification: content_classification_or_source!($($classification)?),
947                parser_support: LanguageParserSupport::$parser_support,
948                symbol_parser: $symbol_parser,
949                semantic_provider: semantic_provider_or_unavailable!($($semantic_provider)?),
950                embedded_language: embedded_language_or_none!($($embedded_language)?),
951                structural_summary: $structural_summary,
952                optional_pack: None,
953                support: $support,
954                accepted_minimum: $support,
955                fixtures: LanguageCapabilityFixtures {
956                    positive_path: $positive,
957                    negative_path: $negative,
958                },
959                provenance: $provenance,
960                required_platforms: RequiredPlatformSet::AllSupported,
961            }),*,
962            $(LanguageCapability {
963                id: $optional_id,
964                aliases: &[$($optional_alias),*],
965                classification: content_classification_or_source!($($optional_classification)?),
966                parser_support: LanguageParserSupport::Fallback,
967                symbol_parser: SymbolParserOwner::Fallback,
968                semantic_provider: SemanticProviderOwner::Unavailable,
969                embedded_language: None,
970                structural_summary: None,
971                optional_pack: Some(BROAD_PARSER_PACK_ID),
972                support: SUPPORTED_FALLBACK,
973                accepted_minimum: SUPPORTED_FALLBACK,
974                fixtures: LanguageCapabilityFixtures {
975                    positive_path: concat!("fixture", $optional_extension),
976                    negative_path: concat!("fixture", $optional_extension, ".bak"),
977                },
978                provenance: CapabilityProvenance::PinnedOptionalCatalog,
979                required_platforms: RequiredPlatformSet::AllSupported,
980            }),*
981        ];
982
983        /// Compatibility parser metadata generated from the accepted manifest.
984        pub const LANGUAGE_SPECS: &[LanguageSpec] = &[
985            $(LanguageSpec {
986                language: $id,
987                parser_support: LanguageParserSupport::$parser_support,
988            }),*,
989            $(LanguageSpec {
990                language: $optional_id,
991                parser_support: LanguageParserSupport::Fallback,
992            }),*
993        ];
994
995        /// Broad source extensions supported by the 0.3.26 scanner contract.
996        pub const BROAD_SOURCE_EXTENSIONS: &[&str] = &[$($extension),*];
997
998        /// Complete registry-known extension projection, including recognition-only pack rows.
999        pub const DETECTED_SOURCE_EXTENSIONS: &[&str] = &[
1000            $($extension),*,
1001            $($optional_extension),*
1002        ];
1003
1004        /// Case-sensitive exact filename rules.
1005        pub const EXACT_FILENAME_RULES: &[LanguageDetectionRule] = &[
1006            $(LanguageDetectionRule { value: $exact, language: $exact_language }),*
1007        ];
1008
1009        /// Longest-first compound extension rules.
1010        pub const COMPOUND_EXTENSION_RULES: &[LanguageDetectionRule] = &[
1011            $(LanguageDetectionRule { value: $compound, language: $compound_language }),*
1012        ];
1013
1014        /// Ordinary extension rules, including compatibility-only hidden extensions.
1015        pub const EXTENSION_RULES: &[LanguageDetectionRule] = &[
1016            $(LanguageDetectionRule { value: $extension, language: $extension_language }),*,
1017            $(LanguageDetectionRule { value: $optional_extension, language: $optional_id }),*,
1018            $(LanguageDetectionRule { value: $additional, language: $additional_language }),*
1019        ];
1020
1021        /// Bounded shebang interpreter rules owned by the accepted registry.
1022        pub const CONTENT_DIALECT_RULES: &[LanguageContentRule] = &[
1023            $(LanguageContentRule {
1024                interpreter: $interpreter,
1025                allow_version_suffix: $version_suffix,
1026                language: $content_language,
1027            }),*
1028        ];
1029
1030        fn exact_filename_language(file_name: &str) -> Option<&'static str> {
1031            match file_name {
1032                $($exact => Some($exact_language),)*
1033                _ => None,
1034            }
1035        }
1036
1037        fn extension_language(extension: &str) -> Option<&'static str> {
1038            match extension {
1039                $($extension => Some($extension_language),)*
1040                $($optional_extension => Some($optional_id),)*
1041                $($additional => Some($additional_language),)*
1042                _ => None,
1043            }
1044        }
1045
1046        /// Resolve a canonical language ID or compatibility alias.
1047        #[must_use]
1048        pub fn canonical_language_id(value: &str) -> Option<&'static str> {
1049            let trimmed = value.trim();
1050            if let Some(canonical) = canonical_language_id_exact(trimmed) {
1051                return Some(canonical);
1052            }
1053            let normalized = trimmed.to_ascii_lowercase();
1054            canonical_language_id_exact(&normalized)
1055        }
1056
1057        /// Resolve an already-normalized canonical language ID or alias.
1058        fn canonical_language_id_exact(value: &str) -> Option<&'static str> {
1059            match value {
1060                $($id $(| $alias)* => Some($id),)*
1061                $($optional_id $(| $optional_alias)* => Some($optional_id),)*
1062                _ => None,
1063            }
1064        }
1065    };
1066}
1067
1068define_language_registry! {
1069    capabilities {
1070        "rust" => { aliases: ["rs"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Rust), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Rust, positive: "fixture.rs", negative: "fixture.rs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Rust) },
1071        "rust-build-script" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Rust), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Rust, positive: "build.rs", negative: "build.rs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Rust) },
1072        "python" => { aliases: ["py"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Python), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Python, positive: "fixture.py", negative: "fixture.py.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Python) },
1073        "javascript" => { aliases: ["js"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::JavaScript), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.js", negative: "fixture.js.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::JavaScript) },
1074        "typescript" => { aliases: ["ts"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::TypeScript), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.ts", negative: "fixture.ts.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::TypeScript) },
1075        "tsx" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Tsx), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.tsx", negative: "fixture.tsx.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Tsx) },
1076        "java" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Java), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.java", negative: "Fixture.java.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Java) },
1077        "kotlin" => { aliases: ["kt"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Kotlin), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.kt", negative: "Fixture.kt.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Kotlin) },
1078        "csharp" => { aliases: ["c#", "cs"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::CSharp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.cs", negative: "Fixture.cs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::CSharp) },
1079        "go" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Go), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.go", negative: "fixture.go.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Go) },
1080        "objective-c" => { aliases: ["objc"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::ObjectiveC), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.m", negative: "fixture.m.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::ObjectiveC) },
1081        "zig" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Zig), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.zig", negative: "fixture.zig.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Zig) },
1082        "c" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::C), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.c", negative: "fixture.c.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::C) },
1083        "cpp" => { aliases: ["c++"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Cpp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.cpp", negative: "fixture.cpp.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Cpp) },
1084        "h" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::C), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.h", negative: "fixture.h.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::C) },
1085        "hpp" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Cpp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.hpp", negative: "fixture.hpp.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Cpp) },
1086        "cargo-manifest" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Manifest, symbol_parser: SymbolParserOwner::CargoManifest, structural_summary: Some(StructuralSummaryOwner::Toml), support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Cargo, positive: "Cargo.toml", negative: "Cargo.toml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1087        "cargo-lock" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Manifest, symbol_parser: SymbolParserOwner::CargoManifest, structural_summary: None, support: SUPPORTED_NATIVE, positive: "Cargo.lock", negative: "Cargo.lock.bak", provenance: CapabilityProvenance::ProjectAtlas },
1088        "vue" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Vue, structural_summary: None, support: SUPPORTED_NATIVE, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::Component, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "Fixture.vue", negative: "Fixture.vue.bak", provenance: CapabilityProvenance::ProjectAtlas },
1089        "markdown" => { aliases: ["md"], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Markdown, structural_summary: Some(StructuralSummaryOwner::Markdown), support: SUPPORTED_NATIVE, positive: "fixture.md", negative: "fixture.md.bak", provenance: CapabilityProvenance::ProjectAtlas },
1090        "pdf" => { aliases: [], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Document, structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.pdf", negative: "fixture.pdf.bak", provenance: CapabilityProvenance::ProjectAtlas },
1091        "docx" => { aliases: [], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Document, structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.docx", negative: "fixture.docx.bak", provenance: CapabilityProvenance::ProjectAtlas },
1092        "json" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Json), support: SUPPORTED_STRUCTURAL, positive: "fixture.json", negative: "fixture.json.bak", provenance: CapabilityProvenance::ProjectAtlas },
1093        "yaml" => { aliases: ["yml"], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Yaml), support: SUPPORTED_STRUCTURAL, positive: "fixture.yml", negative: "fixture.yml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1094        "css" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Css), support: SUPPORTED_STRUCTURAL, positive: "fixture.css", negative: "fixture.css.bak", provenance: CapabilityProvenance::ProjectAtlas },
1095        "html" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Html), support: SUPPORTED_STRUCTURAL, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::HtmlLike, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "fixture.html", negative: "fixture.html.bak", provenance: CapabilityProvenance::ProjectAtlas },
1096        "toon" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Toon), support: SUPPORTED_STRUCTURAL, positive: "fixture.toon", negative: "fixture.toon.bak", provenance: CapabilityProvenance::ProjectAtlas },
1097        "dockerfile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Dockerfile", negative: "Dockerfile.bak", provenance: CapabilityProvenance::ProjectAtlas },
1098        "makefile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Makefile", negative: "Makefile.bak", provenance: CapabilityProvenance::ProjectAtlas },
1099        "text" => { aliases: ["txt"], classification: ContentClassification::OtherText, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::ConfigText), support: SUPPORTED_STRUCTURAL, positive: "fixture.txt", negative: "fixture.txt.bak", provenance: CapabilityProvenance::ProjectAtlas },
1100        "toml" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Toml), support: SUPPORTED_STRUCTURAL, positive: "fixture.toml", negative: "fixture.toml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1101        "xml" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Xml), support: SUPPORTED_STRUCTURAL, positive: "fixture.xml", negative: "fixture.xml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1102        "svelte" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::Template, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "Fixture.svelte", negative: "Fixture.svelte.bak", provenance: CapabilityProvenance::ProjectAtlas },
1103        "astro" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Fixture.astro", negative: "Fixture.astro.bak", provenance: CapabilityProvenance::ProjectAtlas },
1104        "jsp" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.jsp", negative: "fixture.jsp.bak", provenance: CapabilityProvenance::ProjectAtlas },
1105        "jsp-tag" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.tag", negative: "fixture.tag.bak", provenance: CapabilityProvenance::ProjectAtlas },
1106        "gsp" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.gsp", negative: "fixture.gsp.bak", provenance: CapabilityProvenance::ProjectAtlas },
1107        "groovy" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.groovy", negative: "fixture.groovy.bak", provenance: CapabilityProvenance::ProjectAtlas },
1108        "protobuf" => { aliases: ["proto"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.proto", negative: "fixture.proto.bak", provenance: CapabilityProvenance::ProjectAtlas },
1109        "handlebars" => { aliases: ["hbs"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.hbs", negative: "fixture.hbs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1110        "ejs" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ejs", negative: "fixture.ejs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1111        "pug" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.pug", negative: "fixture.pug.bak", provenance: CapabilityProvenance::ProjectAtlas },
1112        "freemarker" => { aliases: ["ftl"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ftl", negative: "fixture.ftl.bak", provenance: CapabilityProvenance::ProjectAtlas },
1113        "mustache" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.mustache", negative: "fixture.mustache.bak", provenance: CapabilityProvenance::ProjectAtlas },
1114        "liquid" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.liquid", negative: "fixture.liquid.bak", provenance: CapabilityProvenance::ProjectAtlas },
1115        "erb" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.erb", negative: "fixture.erb.bak", provenance: CapabilityProvenance::ProjectAtlas },
1116        "sql" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.sql", negative: "fixture.sql.bak", provenance: CapabilityProvenance::ProjectAtlas },
1117        "graphql" => { aliases: ["gql"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.gql", negative: "fixture.gql.bak", provenance: CapabilityProvenance::ProjectAtlas },
1118        "config" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::ConfigText), support: SUPPORTED_STRUCTURAL, positive: "fixture.ini", negative: "fixture.ini.bak", provenance: CapabilityProvenance::ProjectAtlas },
1119        "ruby" => { aliases: ["rb"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.rb", negative: "fixture.rb.bak", provenance: CapabilityProvenance::ProjectAtlas },
1120        "php" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Php), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.php", negative: "fixture.php.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Php) },
1121        "swift" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.swift", negative: "fixture.swift.bak", provenance: CapabilityProvenance::ProjectAtlas },
1122        "scala" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.scala", negative: "fixture.scala.bak", provenance: CapabilityProvenance::ProjectAtlas },
1123        "shell" => { aliases: ["sh"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.sh", negative: "fixture.sh.bak", provenance: CapabilityProvenance::ProjectAtlas },
1124        "powershell" => { aliases: ["pwsh"], parser_support: Fallback, symbol_parser: SymbolParserOwner::PowerShell, structural_summary: Some(StructuralSummaryOwner::PowerShell), support: SUPPORTED_NATIVE, positive: "fixture.ps1", negative: "fixture.ps1.bak", provenance: CapabilityProvenance::ProjectAtlas },
1125        "batch" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.bat", negative: "fixture.bat.bak", provenance: CapabilityProvenance::ProjectAtlas },
1126        "r" => { aliases: ["rscript"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.r", negative: "fixture.r.bak", provenance: CapabilityProvenance::ProjectAtlas },
1127        "perl" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.pl", negative: "fixture.pl.bak", provenance: CapabilityProvenance::ProjectAtlas },
1128        "lua" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.lua", negative: "fixture.lua.bak", provenance: CapabilityProvenance::ProjectAtlas },
1129        "dart" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.dart", negative: "fixture.dart.bak", provenance: CapabilityProvenance::ProjectAtlas },
1130        "haskell" => { aliases: ["hs"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.hs", negative: "fixture.hs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1131        "ocaml" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ml", negative: "fixture.ml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1132        "fsharp" => { aliases: ["f#"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.fs", negative: "fixture.fs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1133        "clojure" => { aliases: ["clj"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.clj", negative: "fixture.clj.bak", provenance: CapabilityProvenance::ProjectAtlas },
1134        "vim" => { aliases: ["vimscript"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.vim", negative: "fixture.vim.bak", provenance: CapabilityProvenance::ProjectAtlas }
1135    }
1136    optional_capabilities {
1137        "abl" => { aliases: [], extension: ".p" },
1138        "actionscript" => { aliases: [], extension: ".as" },
1139        "ada" => { aliases: [], extension: ".ada" },
1140        "agda" => { aliases: [], extension: ".agda" },
1141        "al" => { aliases: [], extension: ".al" },
1142        "arduino" => { aliases: [], extension: ".ino" },
1143        "asciidoc" => { aliases: [], classification: ContentClassification::Documentation, extension: ".adoc" },
1144        "asm" => { aliases: [], extension: ".s" },
1145        "awk" => { aliases: [], extension: ".awk" },
1146        "beancount" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".beancount" },
1147        "bibtex" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".bib" },
1148        "bicep" => { aliases: [], extension: ".bicep" },
1149        "bitbake" => { aliases: [], extension: ".bb" },
1150        "blade" => { aliases: [], extension: ".blade" },
1151        "brightscript" => { aliases: [], extension: ".brs" },
1152        "bsl" => { aliases: [], extension: ".bsl" },
1153        "c3" => { aliases: [], extension: ".c3" },
1154        "caddy" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".caddyfile" },
1155        "cairo" => { aliases: [], extension: ".cairo" },
1156        "capnp" => { aliases: [], extension: ".capnp" },
1157        "cedar" => { aliases: [], extension: ".cedar" },
1158        "cedarschema" => { aliases: [], extension: ".cedarschema" },
1159        "cel" => { aliases: [], extension: ".cel" },
1160        "cfml" => { aliases: [], extension: ".cfc" },
1161        "chatito" => { aliases: [], extension: ".chatito" },
1162        "chuck" => { aliases: [], extension: ".ck" },
1163        "circom" => { aliases: [], extension: ".circom" },
1164        "clarity" => { aliases: [], extension: ".clar" },
1165        "cmake" => { aliases: [], extension: ".cmake" },
1166        "cobol" => { aliases: [], extension: ".cobol" },
1167        "commonlisp" => { aliases: [], extension: ".lisp" },
1168        "cooklang" => { aliases: [], extension: ".cook" },
1169        "corn" => { aliases: [], extension: ".corn" },
1170        "cpon" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".cpon" },
1171        "crystal" => { aliases: [], extension: ".cr" },
1172        "cst" => { aliases: [], extension: ".cst" },
1173        "csv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".csv" },
1174        "cuda" => { aliases: [], extension: ".cu" },
1175        "cue" => { aliases: [], extension: ".cue" },
1176        "cylc" => { aliases: [], extension: ".cylc" },
1177        "d" => { aliases: [], extension: ".d" },
1178        "desktop" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".desktop" },
1179        "devicetree" => { aliases: [], extension: ".dts" },
1180        "dhall" => { aliases: [], extension: ".dhall" },
1181        "diff" => { aliases: [], classification: ContentClassification::OtherText, extension: ".diff" },
1182        "djot" => { aliases: [], classification: ContentClassification::Documentation, extension: ".dj" },
1183        "dot" => { aliases: [], extension: ".dot" },
1184        "dtd" => { aliases: [], extension: ".dtd" },
1185        "ebnf" => { aliases: [], extension: ".ebnf" },
1186        "eds" => { aliases: [], extension: ".eds" },
1187        "eex" => { aliases: [], extension: ".eex" },
1188        "elisp" => { aliases: [], extension: ".el" },
1189        "elixir" => { aliases: [], extension: ".ex" },
1190        "elm" => { aliases: [], extension: ".elm" },
1191        "elsa" => { aliases: [], extension: ".lc" },
1192        "elvish" => { aliases: [], extension: ".elv" },
1193        "enforce" => { aliases: [], extension: ".enforce" },
1194        "erlang" => { aliases: [], extension: ".erl" },
1195        "facility" => { aliases: [], extension: ".fsd" },
1196        "faust" => { aliases: [], extension: ".dsp" },
1197        "fennel" => { aliases: [], extension: ".fnl" },
1198        "fidl" => { aliases: [], extension: ".fidl" },
1199        "firrtl" => { aliases: [], extension: ".fir" },
1200        "fish" => { aliases: [], extension: ".fish" },
1201        "forth" => { aliases: [], extension: ".fth" },
1202        "fortran" => { aliases: [], extension: ".f90" },
1203        "fsharp_signature" => { aliases: [], extension: ".fsi" },
1204        "func" => { aliases: [], extension: ".fc" },
1205        "gap" => { aliases: [], extension: ".g" },
1206        "gdscript" => { aliases: [], extension: ".gd" },
1207        "gdshader" => { aliases: [], extension: ".gdshader" },
1208        "gherkin" => { aliases: [], extension: ".feature" },
1209        "gitattributes" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".gitattributes" },
1210        "gleam" => { aliases: [], extension: ".gleam" },
1211        "glsl" => { aliases: [], extension: ".glsl" },
1212        "gn" => { aliases: [], extension: ".gn" },
1213        "gnuplot" => { aliases: [], extension: ".gp" },
1214        "godot_resource" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tres" },
1215        "gomod" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".mod" },
1216        "gotmpl" => { aliases: [], extension: ".gotmpl" },
1217        "gren" => { aliases: [], extension: ".gren" },
1218        "hack" => { aliases: [], extension: ".hack" },
1219        "hare" => { aliases: [], extension: ".hare" },
1220        "haxe" => { aliases: [], extension: ".hx" },
1221        "hcl" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hcl" },
1222        "heex" => { aliases: [], extension: ".heex" },
1223        "hjson" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hjson" },
1224        "hlsl" => { aliases: [], extension: ".hlsl" },
1225        "hocon" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hocon" },
1226        "hoon" => { aliases: [], extension: ".hoon" },
1227        "http" => { aliases: [], extension: ".http" },
1228        "hurl" => { aliases: [], extension: ".hurl" },
1229        "idris" => { aliases: [], extension: ".idr" },
1230        "ispc" => { aliases: [], extension: ".ispc" },
1231        "jai" => { aliases: [], extension: ".jai" },
1232        "janet" => { aliases: [], extension: ".janet" },
1233        "jinja2" => { aliases: [], extension: ".j2" },
1234        "jq" => { aliases: [], extension: ".jq" },
1235        "json5" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".json5" },
1236        "jsonnet" => { aliases: [], extension: ".jsonnet" },
1237        "julia" => { aliases: [], extension: ".jl" },
1238        "just" => { aliases: [], extension: ".just" },
1239        "kcl" => { aliases: [], extension: ".k" },
1240        "kdl" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".kdl" },
1241        "latex" => { aliases: [], classification: ContentClassification::Documentation, extension: ".tex" },
1242        "lean" => { aliases: [], extension: ".lean" },
1243        "ledger" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ldg" },
1244        "linkerscript" => { aliases: [], extension: ".lds" },
1245        "llvm" => { aliases: [], extension: ".ll" },
1246        "luau" => { aliases: [], extension: ".luau" },
1247        "magik" => { aliases: [], extension: ".magik" },
1248        "make" => { aliases: [], extension: ".mk" },
1249        "matlab" => { aliases: [], extension: ".matlab" },
1250        "mermaid" => { aliases: [], extension: ".mmd" },
1251        "meson" => { aliases: [], extension: ".meson" },
1252        "mlir" => { aliases: [], extension: ".mlir" },
1253        "mojo" => { aliases: [], extension: ".mojo" },
1254        "move" => { aliases: [], extension: ".move" },
1255        "nasm" => { aliases: [], extension: ".nasm" },
1256        "netlinx" => { aliases: [], extension: ".axs" },
1257        "nginx" => { aliases: [], extension: ".nginx" },
1258        "nickel" => { aliases: [], extension: ".ncl" },
1259        "nim" => { aliases: [], extension: ".nim" },
1260        "ninja" => { aliases: [], extension: ".ninja" },
1261        "nix" => { aliases: [], extension: ".nix" },
1262        "norg" => { aliases: [], classification: ContentClassification::Documentation, extension: ".norg" },
1263        "nqc" => { aliases: [], extension: ".nqc" },
1264        "nushell" => { aliases: [], extension: ".nu" },
1265        "ocamllex" => { aliases: [], extension: ".mll" },
1266        "odin" => { aliases: [], extension: ".odin" },
1267        "openscad" => { aliases: [], extension: ".scad" },
1268        "org" => { aliases: [], classification: ContentClassification::Documentation, extension: ".org" },
1269        "pascal" => { aliases: [], extension: ".pas" },
1270        "pem" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".pem" },
1271        "pgn" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".pgn" },
1272        "pkl" => { aliases: [], extension: ".pkl" },
1273        "po" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".po" },
1274        "poe_filter" => { aliases: [], extension: ".filter" },
1275        "pony" => { aliases: [], extension: ".pony" },
1276        "postscript" => { aliases: [], extension: ".ps" },
1277        "prisma" => { aliases: [], extension: ".prisma" },
1278        "prolog" => { aliases: [], extension: ".pro" },
1279        "promql" => { aliases: [], extension: ".promql" },
1280        "prql" => { aliases: [], extension: ".prql" },
1281        "psv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".psv" },
1282        "puppet" => { aliases: [], extension: ".pp" },
1283        "purescript" => { aliases: [], extension: ".purs" },
1284        "ql" => { aliases: [], extension: ".ql" },
1285        "qmljs" => { aliases: [], extension: ".qml" },
1286        "racket" => { aliases: [], extension: ".rkt" },
1287        "rasi" => { aliases: [], extension: ".rasi" },
1288        "razor" => { aliases: [], extension: ".razor" },
1289        "rbs" => { aliases: [], extension: ".rbs" },
1290        "re2c" => { aliases: [], extension: ".re" },
1291        "rego" => { aliases: [], extension: ".rego" },
1292        "rescript" => { aliases: [], extension: ".res" },
1293        "robot" => { aliases: [], extension: ".robot" },
1294        "roc" => { aliases: [], extension: ".roc" },
1295        "ron" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ron" },
1296        "rst" => { aliases: [], classification: ContentClassification::Documentation, extension: ".rst" },
1297        "rtf" => { aliases: [], classification: ContentClassification::Documentation, extension: ".rtf" },
1298        "scheme" => { aliases: [], extension: ".scm" },
1299        "slang" => { aliases: [], extension: ".slang" },
1300        "smali" => { aliases: [], extension: ".smali" },
1301        "smalltalk" => { aliases: [], extension: ".st" },
1302        "smithy" => { aliases: [], extension: ".smithy" },
1303        "sml" => { aliases: [], extension: ".sml" },
1304        "snakemake" => { aliases: [], extension: ".smk" },
1305        "solidity" => { aliases: [], extension: ".sol" },
1306        "souffle" => { aliases: [], extension: ".dl" },
1307        "sourcepawn" => { aliases: [], extension: ".sp" },
1308        "sql_bigquery" => { aliases: [], extension: ".bq" },
1309        "squirrel" => { aliases: [], extension: ".squirrel" },
1310        "stan" => { aliases: [], extension: ".stan" },
1311        "starlark" => { aliases: [], extension: ".star" },
1312        "superhtml" => { aliases: [], extension: ".shtml" },
1313        "sway" => { aliases: [], extension: ".sw" },
1314        "systemverilog" => { aliases: [], extension: ".sv" },
1315        "tablegen" => { aliases: [], extension: ".td" },
1316        "tact" => { aliases: [], extension: ".tact" },
1317        "tcl" => { aliases: [], extension: ".tcl" },
1318        "teal" => { aliases: [], extension: ".tl" },
1319        "templ" => { aliases: [], extension: ".templ" },
1320        "tera" => { aliases: [], extension: ".tera" },
1321        "terraform" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tf" },
1322        "textproto" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".textproto" },
1323        "thrift" => { aliases: [], extension: ".thrift" },
1324        "tlaplus" => { aliases: [], extension: ".tla" },
1325        "todotxt" => { aliases: [], classification: ContentClassification::OtherText, extension: ".todotxt" },
1326        "tsv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tsv" },
1327        "turtle" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ttl" },
1328        "twig" => { aliases: [], extension: ".twig" },
1329        "typespec" => { aliases: [], extension: ".tsp" },
1330        "typoscript" => { aliases: [], extension: ".typoscript" },
1331        "typst" => { aliases: [], classification: ContentClassification::Documentation, extension: ".typst" },
1332        "uxntal" => { aliases: [], extension: ".tal" },
1333        "v" => { aliases: [], extension: ".v" },
1334        "vb" => { aliases: [], extension: ".vb" },
1335        "verilog" => { aliases: [], extension: ".verilog" },
1336        "vhdl" => { aliases: [], extension: ".vhdl" },
1337        "vhs" => { aliases: [], extension: ".tape" },
1338        "vrl" => { aliases: [], extension: ".vrl" },
1339        "wast" => { aliases: [], extension: ".wast" },
1340        "wat" => { aliases: [], extension: ".wat" },
1341        "wgsl" => { aliases: [], extension: ".wgsl" },
1342        "wit" => { aliases: [], extension: ".wit" },
1343        "yuck" => { aliases: [], extension: ".yuck" },
1344        "ziggy" => { aliases: [], extension: ".ziggy" },
1345    }
1346    exact_filenames {
1347        "Cargo.toml" => "cargo-manifest",
1348        "Cargo.lock" => "cargo-lock",
1349        "build.rs" => "rust-build-script",
1350        "Dockerfile" => "dockerfile",
1351        "Makefile" => "makefile"
1352    }
1353    compound_extensions { ".d.ts" => "typescript" }
1354    broad_extensions {
1355        ".py" => "python", ".pyw" => "python", ".js" => "javascript", ".jsx" => "javascript", ".ts" => "typescript", ".tsx" => "tsx", ".mjs" => "javascript", ".cjs" => "javascript", ".d.ts" => "typescript", ".java" => "java", ".c" => "c", ".cpp" => "cpp", ".h" => "h", ".hpp" => "hpp", ".cxx" => "cpp", ".cc" => "cpp", ".hxx" => "hpp", ".hh" => "hpp", ".cs" => "csharp", ".go" => "go", ".m" => "objective-c", ".mm" => "objective-c", ".rb" => "ruby", ".php" => "php", ".swift" => "swift", ".kt" => "kotlin", ".kts" => "kotlin", ".rs" => "rust", ".scala" => "scala", ".sh" => "shell", ".bash" => "shell", ".zsh" => "shell", ".ps1" => "powershell", ".psm1" => "powershell", ".psd1" => "powershell", ".bat" => "batch", ".cmd" => "batch", ".r" => "r", ".R" => "r", ".pl" => "perl", ".pm" => "perl", ".lua" => "lua", ".dart" => "dart", ".hs" => "haskell", ".ml" => "ocaml", ".mli" => "ocaml", ".fs" => "fsharp", ".fsx" => "fsharp", ".clj" => "clojure", ".cljs" => "clojure", ".vim" => "vim", ".zig" => "zig", ".zon" => "zig", ".html" => "html", ".htm" => "html", ".css" => "css", ".scss" => "css", ".sass" => "css", ".less" => "css", ".stylus" => "css", ".styl" => "css", ".md" => "markdown", ".mdx" => "markdown", ".json" => "json", ".jsonc" => "json", ".xml" => "xml", ".yml" => "yaml", ".yaml" => "yaml", ".toml" => "toml", ".toon" => "toon", ".txt" => "text", ".ini" => "config", ".cfg" => "config", ".conf" => "config", ".vue" => "vue", ".svelte" => "svelte", ".astro" => "astro", ".jsp" => "jsp", ".jspx" => "jsp", ".jspf" => "jsp", ".tag" => "jsp-tag", ".tagx" => "jsp-tag", ".gsp" => "gsp", ".properties" => "config", ".gradle" => "groovy", ".groovy" => "groovy", ".proto" => "protobuf", ".hbs" => "handlebars", ".handlebars" => "handlebars", ".ejs" => "ejs", ".pug" => "pug", ".ftl" => "freemarker", ".mustache" => "mustache", ".liquid" => "liquid", ".erb" => "erb", ".sql" => "sql", ".ddl" => "sql", ".dml" => "sql", ".mysql" => "sql", ".postgresql" => "sql", ".psql" => "sql", ".sqlite" => "sql", ".mssql" => "sql", ".oracle" => "sql", ".ora" => "sql", ".db2" => "sql", ".proc" => "sql", ".procedure" => "sql", ".func" => "sql", ".function" => "sql", ".view" => "sql", ".trigger" => "sql", ".index" => "sql", ".migration" => "sql", ".seed" => "sql", ".fixture" => "sql", ".schema" => "sql", ".cql" => "sql", ".cypher" => "sql", ".sparql" => "sql", ".gql" => "graphql", ".liquibase" => "sql", ".flyway" => "sql"
1356    }
1357    additional_extensions {
1358        ".env" => "config", ".gitignore" => "config", ".dockerignore" => "config", ".editorconfig" => "config", ".pdf" => "pdf", ".docx" => "docx"
1359    }
1360    content_interpreters {
1361        "python" => { language: "python", version_suffix: true },
1362        "pythonw" => { language: "python", version_suffix: true },
1363        "node" => { language: "javascript", version_suffix: false },
1364        "deno" => { language: "javascript", version_suffix: false },
1365        "powershell" => { language: "powershell", version_suffix: false },
1366        "pwsh" => { language: "powershell", version_suffix: false },
1367        "ruby" => { language: "ruby", version_suffix: true },
1368        "perl" => { language: "perl", version_suffix: true },
1369        "lua" => { language: "lua", version_suffix: true },
1370        "rscript" => { language: "r", version_suffix: false },
1371        "sh" => { language: "shell", version_suffix: false },
1372        "bash" => { language: "shell", version_suffix: false },
1373        "dash" => { language: "shell", version_suffix: false },
1374        "ash" => { language: "shell", version_suffix: false },
1375        "zsh" => { language: "shell", version_suffix: false },
1376        "ksh" => { language: "shell", version_suffix: false },
1377        "mksh" => { language: "shell", version_suffix: false },
1378        "fish" => { language: "shell", version_suffix: false }
1379    }
1380}
1381
1382/// Return parser coverage metadata for a detected language family.
1383#[must_use]
1384pub fn language_spec(language: &str) -> Option<&'static LanguageSpec> {
1385    let canonical = canonical_language_id(language)?;
1386    LANGUAGE_SPECS
1387        .iter()
1388        .find(|spec| spec.language == canonical)
1389}
1390
1391/// Return the accepted capability row for a canonical ID or alias.
1392#[must_use]
1393pub fn language_capability(language: &str) -> Option<&'static LanguageCapability> {
1394    static BY_ID: OnceLock<BTreeMap<&'static str, &'static LanguageCapability>> = OnceLock::new();
1395    let canonical = canonical_language_id(language)?;
1396    BY_ID
1397        .get_or_init(|| {
1398            LANGUAGE_CAPABILITIES
1399                .iter()
1400                .map(|capability| (capability.id, capability))
1401                .collect()
1402        })
1403        .get(canonical)
1404        .copied()
1405}
1406
1407/// Classify one already-admitted file from registry truth and UTF-8 validity.
1408///
1409/// Admission and ignore policy remain owned by the scanner. Registry truth wins
1410/// for known rows; only otherwise unknown content falls back to UTF-8 evidence.
1411#[must_use]
1412pub fn content_classification(language: Option<&str>, valid_utf8: bool) -> ContentClassification {
1413    if let Some(classification) = language
1414        .and_then(language_capability)
1415        .map(|capability| capability.classification)
1416    {
1417        return classification;
1418    }
1419    if valid_utf8 {
1420        ContentClassification::OtherText
1421    } else {
1422        ContentClassification::Opaque
1423    }
1424}
1425
1426/// Return all accepted language rows as generated documentation inputs.
1427#[must_use]
1428pub const fn language_documentation_rows() -> &'static [LanguageCapability] {
1429    LANGUAGE_CAPABILITIES
1430}
1431
1432/// Return the built-in Tree-sitter grammar owner for a language.
1433#[must_use]
1434pub fn tree_sitter_grammar(language: &str) -> Option<TreeSitterGrammar> {
1435    match language_capability(language)?.symbol_parser {
1436        SymbolParserOwner::TreeSitter(grammar) => Some(grammar),
1437        SymbolParserOwner::CargoManifest
1438        | SymbolParserOwner::Vue
1439        | SymbolParserOwner::PowerShell
1440        | SymbolParserOwner::Markdown
1441        | SymbolParserOwner::Document
1442        | SymbolParserOwner::Fallback
1443        | SymbolParserOwner::Unavailable => None,
1444    }
1445}
1446
1447/// Return all canonical language IDs backed by built-in Tree-sitter grammars.
1448#[must_use]
1449pub fn builtin_tree_sitter_language_ids() -> &'static [&'static str] {
1450    static IDS: OnceLock<Box<[&'static str]>> = OnceLock::new();
1451    IDS.get_or_init(|| {
1452        LANGUAGE_CAPABILITIES
1453            .iter()
1454            .filter_map(|capability| {
1455                matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
1456                    .then_some(capability.id)
1457            })
1458            .collect::<Vec<_>>()
1459            .into_boxed_slice()
1460    })
1461}
1462
1463/// Return whether a path ends in a declared compound language extension.
1464#[must_use]
1465pub fn compound_language_extension(path: &str) -> Option<&'static str> {
1466    let file_name = path.rsplit(['/', '\\']).next().unwrap_or(path);
1467    let lower = file_name.to_ascii_lowercase();
1468    COMPOUND_EXTENSION_RULES
1469        .iter()
1470        .find(|rule| lower.ends_with(rule.value))
1471        .map(|rule| rule.value)
1472}
1473
1474/// Return the normalized extension used by the scanner and detector.
1475#[must_use]
1476pub fn normalized_language_extension(path: &Path) -> Option<String> {
1477    let file_name = path.file_name()?.to_string_lossy();
1478    if let Some(compound) = compound_language_extension(&file_name) {
1479        return Some(compound.to_string());
1480    }
1481    path.extension()
1482        .map(|extension| format!(".{}", extension.to_string_lossy().to_lowercase()))
1483}
1484
1485/// Detect one language through the typed precedence contract.
1486///
1487/// # Errors
1488///
1489/// Returns an error when an explicit override is not a known canonical ID or alias.
1490pub fn detect_language_request(
1491    request: LanguageDetectionRequest<'_>,
1492) -> Result<Option<LanguageDetection>, LanguageDetectionError> {
1493    if let Some(explicit) = request.explicit_override {
1494        let Some(language) = canonical_language_id(explicit) else {
1495            return Err(LanguageDetectionError {
1496                requested: explicit.to_string(),
1497            });
1498        };
1499        return Ok(Some(LanguageDetection {
1500            language,
1501            reason: LanguageDetectionReason::ExplicitOverride,
1502        }));
1503    }
1504
1505    let file_name = request
1506        .path
1507        .rsplit(['/', '\\'])
1508        .next()
1509        .unwrap_or(request.path);
1510    if let Some(language) = exact_filename_language(file_name) {
1511        return Ok(Some(LanguageDetection {
1512            language,
1513            reason: LanguageDetectionReason::ExactFilename,
1514        }));
1515    }
1516
1517    let lower_file_name = file_name.to_ascii_lowercase();
1518    if let Some(rule) = COMPOUND_EXTENSION_RULES
1519        .iter()
1520        .find(|rule| lower_file_name.ends_with(rule.value))
1521    {
1522        return Ok(Some(LanguageDetection {
1523            language: rule.language,
1524            reason: LanguageDetectionReason::CompoundExtension,
1525        }));
1526    }
1527
1528    let normalized_extension = request.extension.map(str::to_ascii_lowercase).or_else(|| {
1529        file_name
1530            .rsplit_once('.')
1531            .map(|(_, extension)| format!(".{}", extension.to_ascii_lowercase()))
1532    });
1533    if let Some(extension) = normalized_extension
1534        && let Some(language) = extension_language(&extension)
1535    {
1536        return Ok(Some(LanguageDetection {
1537            language,
1538            reason: LanguageDetectionReason::Extension,
1539        }));
1540    }
1541
1542    Ok(
1543        content_dialect_language(request.content_prefix).map(|language| LanguageDetection {
1544            language,
1545            reason: LanguageDetectionReason::ContentDialect,
1546        }),
1547    )
1548}
1549
1550/// Detect a language or file family from an extension.
1551#[must_use]
1552pub fn detect_language(extension: Option<&str>) -> Option<String> {
1553    detect_language_request(LanguageDetectionRequest::new("", extension))
1554        .ok()
1555        .flatten()
1556        .map(|detected| detected.language.to_string())
1557}
1558
1559/// Detect a language or file family from a path plus extension.
1560#[must_use]
1561pub fn detect_language_for_path(path: &str, extension: Option<&str>) -> Option<String> {
1562    detect_language_request(LanguageDetectionRequest::new(path, extension))
1563        .ok()
1564        .flatten()
1565        .map(|detected| detected.language.to_string())
1566}
1567
1568/// Apply bounded registry-owned shebang rules after filename/extension rules miss.
1569fn content_dialect_language(content_prefix: Option<&[u8]>) -> Option<&'static str> {
1570    let prefix = content_prefix?;
1571    let bounded = &prefix[..prefix.len().min(LANGUAGE_CONTENT_DETECTION_MAX_BYTES)];
1572    let line_end = bounded
1573        .iter()
1574        .position(|byte| *byte == b'\n')
1575        .unwrap_or(bounded.len());
1576    let first_line = std::str::from_utf8(&bounded[..line_end])
1577        .ok()?
1578        .trim_end_matches('\r');
1579    let shebang = first_line.strip_prefix("#!")?.trim();
1580    let interpreter = shebang_interpreter(shebang)?;
1581    CONTENT_DIALECT_RULES
1582        .iter()
1583        .find(|rule| content_interpreter_matches(&interpreter, rule))
1584        .map(|rule| rule.language)
1585}
1586
1587/// Return the lowercase interpreter basename from a bounded shebang command.
1588fn shebang_interpreter(shebang: &str) -> Option<String> {
1589    let mut tokens = shebang.split_ascii_whitespace();
1590    let first = tokens.next()?;
1591    let first_basename = command_basename(first);
1592    let selected = if first_basename.eq_ignore_ascii_case("env") {
1593        tokens.find(|token| !token.starts_with('-') && !token.contains('='))?
1594    } else if first_basename.eq_ignore_ascii_case("busybox") {
1595        tokens.next()?
1596    } else {
1597        first
1598    };
1599    let normalized = command_basename(selected).to_ascii_lowercase();
1600    Some(
1601        normalized
1602            .strip_suffix(".exe")
1603            .unwrap_or(&normalized)
1604            .to_string(),
1605    )
1606}
1607
1608/// Return a command basename for Unix and Windows-style shebang paths.
1609fn command_basename(command: &str) -> &str {
1610    command.rsplit(['/', '\\']).next().unwrap_or(command)
1611}
1612
1613/// Match one normalized interpreter against its declared exact/versioned rule.
1614fn content_interpreter_matches(interpreter: &str, rule: &LanguageContentRule) -> bool {
1615    if interpreter == rule.interpreter {
1616        return true;
1617    }
1618    rule.allow_version_suffix
1619        && interpreter
1620            .strip_prefix(rule.interpreter)
1621            .is_some_and(|suffix| {
1622                !suffix.is_empty()
1623                    && suffix.split('.').all(|segment| {
1624                        !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())
1625                    })
1626            })
1627}
1628
1629/// Count levels for one capability axis.
1630#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1631pub struct CapabilityLevelCounts {
1632    /// Rows without this capability.
1633    pub unavailable: usize,
1634    /// Rows with conservative fallback behavior.
1635    pub fallback: usize,
1636    /// Rows with supported behavior.
1637    pub supported: usize,
1638}
1639
1640/// Derived accepted language capability counts.
1641#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1642pub struct LanguageCapabilityCounts {
1643    /// Canonical accepted rows; aliases and extensions do not add to this count.
1644    pub accepted: usize,
1645    /// Rows owned entirely by the default-core runtime.
1646    pub built_in: usize,
1647    /// Broad rows eligible for the separately verified optional pack.
1648    pub optional_candidates: usize,
1649    /// Detection tier counts.
1650    pub detected: CapabilityLevelCounts,
1651    /// Parsing tier counts.
1652    pub parsed: CapabilityLevelCounts,
1653    /// Symbol tier counts.
1654    pub symbols: CapabilityLevelCounts,
1655    /// Semantic tier counts.
1656    pub semantic: CapabilityLevelCounts,
1657    /// Benchmark tier counts.
1658    pub benchmarked: CapabilityLevelCounts,
1659}
1660
1661/// One content-free settings and generated-documentation capability row.
1662#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1663pub struct LanguageCapabilityReportRow {
1664    /// Stable canonical language or file-family identifier.
1665    pub id: &'static str,
1666    /// Compatibility aliases; these never add to capability counts.
1667    pub aliases: &'static [&'static str],
1668    /// Registry-owned content role.
1669    pub classification: ContentClassification,
1670    /// Exact filename detector rules owned by this row.
1671    pub exact_filenames: Vec<&'static str>,
1672    /// Compound extension detector rules owned by this row.
1673    pub compound_extensions: Vec<&'static str>,
1674    /// Ordinary extension detector rules owned by this row.
1675    pub extensions: Vec<&'static str>,
1676    /// Shebang interpreter basenames owned by this row.
1677    pub content_interpreters: Vec<&'static str>,
1678    /// Compatibility parser tier.
1679    pub parser_support: LanguageParserSupport,
1680    /// Closed symbol parser owner.
1681    pub symbol_parser: SymbolParserOwner,
1682    /// Closed project-wide semantic provider owner.
1683    pub semantic_provider: SemanticProviderOwner,
1684    /// Optional bounded host-to-embedded provider pairing.
1685    pub embedded_language: Option<EmbeddedLanguageCapability>,
1686    /// Optional structural summary owner.
1687    pub structural_summary: Option<StructuralSummaryOwner>,
1688    /// Optional broad-parser pack owner.
1689    pub optional_pack: Option<&'static str>,
1690    /// Currently achieved independent support.
1691    pub support: LanguageCapabilitySupport,
1692    /// Accepted minimum support.
1693    pub accepted_minimum: LanguageCapabilitySupport,
1694    /// Natural positive and negative detector fixtures.
1695    pub fixtures: LanguageCapabilityFixtures,
1696    /// Exact provenance package or catalog identity.
1697    pub provenance_source: &'static str,
1698    /// Exact provenance package or catalog version.
1699    pub provenance_version: &'static str,
1700    /// License applying to the declared implementation or catalog metadata.
1701    pub provenance_license: &'static str,
1702    /// Required release platforms.
1703    pub required_platforms: RequiredPlatformSet,
1704}
1705
1706/// Pinned broad-language catalog identity used for detection candidates.
1707#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
1708pub struct LanguageCatalogReport {
1709    /// Catalog package identity.
1710    pub name: &'static str,
1711    /// Exact catalog release.
1712    pub version: &'static str,
1713    /// Exact catalog source revision.
1714    pub revision: &'static str,
1715    /// License applying to the catalog metadata, not every grammar subtree.
1716    pub metadata_license: &'static str,
1717    /// Minimum additional accepted grammars required for the broad-pack claim.
1718    pub minimum_additional_grammars: usize,
1719}
1720
1721/// Content-free language registry settings projection.
1722#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1723pub struct LanguageRegistryReport {
1724    /// Registry schema version.
1725    pub registry_version: u32,
1726    /// Accepted capability-set version.
1727    pub accepted_set_version: u32,
1728    /// Detector precedence and content-matching policy version.
1729    pub detection_policy_version: u32,
1730    /// Digest of runtime rows and detection rules.
1731    pub registry_digest: String,
1732    /// Digest of accepted minimums.
1733    pub accepted_set_digest: String,
1734    /// Digest of direct/embedded provider ownership and resolution-family truth.
1735    pub semantic_provider_digest: String,
1736    /// Derived independent support counts.
1737    pub counts: LanguageCapabilityCounts,
1738    /// Pinned catalog identity for optional capability candidates.
1739    pub optional_catalog: LanguageCatalogReport,
1740}
1741
1742/// Build a content-free settings/documentation report from the registry authority.
1743#[must_use]
1744pub fn language_registry_report() -> LanguageRegistryReport {
1745    LanguageRegistryReport {
1746        registry_version: LANGUAGE_CAPABILITY_REGISTRY_VERSION,
1747        accepted_set_version: ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION,
1748        detection_policy_version: LANGUAGE_DETECTION_POLICY_VERSION,
1749        registry_digest: language_registry_digest(),
1750        accepted_set_digest: accepted_language_capability_digest(),
1751        semantic_provider_digest: semantic_provider_digest(),
1752        counts: language_capability_counts(),
1753        optional_catalog: LanguageCatalogReport {
1754            name: OPTIONAL_GRAMMAR_CATALOG,
1755            version: OPTIONAL_GRAMMAR_CATALOG_VERSION,
1756            revision: OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
1757            metadata_license: "MIT",
1758            minimum_additional_grammars: OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
1759        },
1760    }
1761}
1762
1763/// Build the complete content-free capability matrix from registry authority.
1764#[must_use]
1765pub fn language_capability_report_rows() -> Vec<LanguageCapabilityReportRow> {
1766    LANGUAGE_CAPABILITIES
1767        .iter()
1768        .map(|capability| LanguageCapabilityReportRow {
1769            id: capability.id,
1770            aliases: capability.aliases,
1771            classification: capability.classification,
1772            exact_filenames: rules_for_language(EXACT_FILENAME_RULES, capability.id),
1773            compound_extensions: rules_for_language(COMPOUND_EXTENSION_RULES, capability.id),
1774            extensions: rules_for_language(EXTENSION_RULES, capability.id),
1775            content_interpreters: CONTENT_DIALECT_RULES
1776                .iter()
1777                .filter_map(|rule| (rule.language == capability.id).then_some(rule.interpreter))
1778                .collect(),
1779            parser_support: capability.parser_support,
1780            symbol_parser: capability.symbol_parser,
1781            semantic_provider: capability.semantic_provider,
1782            embedded_language: capability.embedded_language,
1783            structural_summary: capability.structural_summary,
1784            optional_pack: capability.optional_pack,
1785            support: capability.support,
1786            accepted_minimum: capability.accepted_minimum,
1787            fixtures: capability.fixtures,
1788            provenance_source: capability.provenance.source(),
1789            provenance_version: capability.provenance.version(),
1790            provenance_license: capability.provenance.license(),
1791            required_platforms: capability.required_platforms,
1792        })
1793        .collect()
1794}
1795
1796/// Render the checked-in public language support matrix from registry authority.
1797///
1798/// # Errors
1799///
1800/// Returns a formatting error if writing to the owned `String` fails.
1801pub fn render_language_support_markdown() -> Result<String, fmt::Error> {
1802    let report = language_registry_report();
1803    let capability_rows = language_capability_report_rows();
1804    let mut output = String::new();
1805    output.push_str("# ProjectAtlas Language Support\n\n");
1806    output.push_str(
1807        "This document is generated from the versioned Rust language capability registry. \
1808Do not edit the capability table or totals by hand. Canonical rows count once; aliases and \
1809extensions never increase a capability total.\n\n",
1810    );
1811    write!(
1812        &mut output,
1813        "Registry version: `{}`. Accepted capability-set version: `{}`. Detection policy \
1814version: `{}`. \
1815Registry digest: `{}`. Accepted-set digest: `{}`. Semantic-provider digest: `{}`.\n\n",
1816        report.registry_version,
1817        report.accepted_set_version,
1818        report.detection_policy_version,
1819        report.registry_digest,
1820        report.accepted_set_digest,
1821        report.semantic_provider_digest
1822    )?;
1823    write!(
1824        &mut output,
1825        "Optional catalog input: `{}@{}` revision `{}` under `{}` metadata license. \
1826This catalog identity is not a grammar-license or runtime-support claim.\n\n",
1827        report.optional_catalog.name,
1828        report.optional_catalog.version,
1829        report.optional_catalog.revision,
1830        report.optional_catalog.metadata_license
1831    )?;
1832    write!(
1833        &mut output,
1834        "The registry contains **{}** canonical rows: **{}** default-core rows and **{}** \
1835optional-pack candidates. Detection is supported for {} rows. Parsing is supported for {}, \
1836fallback for {}, and unavailable for {}. Symbols are supported for {}, fallback for {}, and \
1837unavailable for {}. Semantic resolution and benchmark coverage are reported independently.\n\n",
1838        report.counts.accepted,
1839        report.counts.built_in,
1840        report.counts.optional_candidates,
1841        report.counts.detected.supported,
1842        report.counts.parsed.supported,
1843        report.counts.parsed.fallback,
1844        report.counts.parsed.unavailable,
1845        report.counts.symbols.supported,
1846        report.counts.symbols.fallback,
1847        report.counts.symbols.unavailable
1848    )?;
1849    output.push_str(
1850        "Rows marked `broad-parser` are detected and, when explicitly admitted to the scan \
1851policy, remain usable through the conservative default-core fallback while the optional pack is \
1852absent. Catalog recognition alone does not add these extensions to the default scan surface. The \
1853pinned catalog is provenance for detection metadata only. A row becomes grammar-backed parsed \
1854support only after its exact \
1855grammar binary, subtree license, ABI/export, fixtures, and every accepted optional-pack target \
1856pass the separate acceptance gates. The v0.4 optional-pack targets are Linux x86-64 and Windows \
1857x86-64; macOS keeps the full built-in surface and reports `unsupported_containment` for optional-\
1858pack activation. Built-in owners always retain precedence.\n\n",
1859    );
1860    output.push_str(
1861        "Broad candidate rows are admitted only when the pinned catalog supplies a stable \
1862canonical grammar identity and at least one ordinary extension that does not conflict with an \
1863already accepted detector owner. Extensionless, ambiguous, duplicate, pseudo, or conflicting \
1864catalog entries remain unadvertised until a separate deterministic rule and evidence exist.\n\n",
1865    );
1866    output.push_str(
1867        "| Language | Classification | Aliases | Detection rules | Parser owner | Parsed | Symbols | Semantic | Embedded source | Benchmarked | Optional pack | Provenance | License |\n",
1868    );
1869    output.push_str(
1870        "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n",
1871    );
1872    for row in &capability_rows {
1873        let rules = detector_rule_summary(row);
1874        let aliases = list_or_dash(row.aliases.iter().copied());
1875        let optional_pack = row.optional_pack.unwrap_or("—");
1876        let provenance = format!("{}@{}", row.provenance_source, row.provenance_version);
1877        writeln!(
1878            &mut output,
1879            "| `{}` | `{}` | {} | {} | {} | {} | {} | {} | {} | {} | {} | `{}` | `{}` |",
1880            row.id,
1881            row.classification,
1882            aliases,
1883            rules,
1884            symbol_parser_label(row.symbol_parser),
1885            row.support.parsed.as_str(),
1886            row.support.symbols.as_str(),
1887            semantic_support_label(row.support.semantic, row.semantic_provider),
1888            embedded_language_label(row.embedded_language),
1889            row.support.benchmarked.as_str(),
1890            optional_pack,
1891            provenance,
1892            row.provenance_license
1893        )?;
1894    }
1895    crate::support_catalog::append_support_catalog_markdown(&mut output)?;
1896    Ok(output)
1897}
1898
1899/// Render all detector rules for one documentation row.
1900fn detector_rule_summary(row: &LanguageCapabilityReportRow) -> String {
1901    let mut rules = Vec::new();
1902    rules.extend(
1903        row.exact_filenames
1904            .iter()
1905            .map(|value| format!("exact `{value}`")),
1906    );
1907    rules.extend(
1908        row.compound_extensions
1909            .iter()
1910            .map(|value| format!("compound `{value}`")),
1911    );
1912    rules.extend(row.extensions.iter().map(|value| format!("`{value}`")));
1913    rules.extend(
1914        row.content_interpreters
1915            .iter()
1916            .map(|value| format!("shebang `{value}`")),
1917    );
1918    if rules.is_empty() {
1919        "—".to_string()
1920    } else {
1921        rules.join(", ")
1922    }
1923}
1924
1925/// Render one closed symbol-parser owner without duplicating selection policy.
1926fn symbol_parser_label(owner: SymbolParserOwner) -> String {
1927    match owner {
1928        SymbolParserOwner::TreeSitter(grammar) => {
1929            format!("{}@{}", grammar.package(), grammar.version())
1930        }
1931        SymbolParserOwner::CargoManifest => "projectatlas:cargo-manifest".to_string(),
1932        SymbolParserOwner::Vue => "projectatlas:vue".to_string(),
1933        SymbolParserOwner::PowerShell => "projectatlas:powershell".to_string(),
1934        SymbolParserOwner::Markdown => "projectatlas:markdown".to_string(),
1935        SymbolParserOwner::Document => "projectatlas:documents".to_string(),
1936        SymbolParserOwner::Fallback => "projectatlas:fallback".to_string(),
1937        SymbolParserOwner::Unavailable => "unavailable".to_string(),
1938    }
1939}
1940
1941/// Render semantic support with its closed owner when available.
1942fn semantic_support_label(support: CapabilitySupportLevel, owner: SemanticProviderOwner) -> String {
1943    match owner {
1944        SemanticProviderOwner::Unavailable => support.as_str().to_string(),
1945        _ => format!("{} ({})", support.as_str(), owner.as_str()),
1946    }
1947}
1948
1949/// Render one accepted host-to-embedded provider pairing.
1950fn embedded_language_label(capability: Option<EmbeddedLanguageCapability>) -> String {
1951    capability.map_or_else(
1952        || "—".to_string(),
1953        |capability| {
1954            format!(
1955                "{} → {}",
1956                capability.host_kind.as_str(),
1957                capability.semantic_provider.as_str()
1958            )
1959        },
1960    )
1961}
1962
1963/// Join documentation values or render an explicit empty marker.
1964fn list_or_dash<'a>(values: impl Iterator<Item = &'a str>) -> String {
1965    let values = values.collect::<Vec<_>>();
1966    if values.is_empty() {
1967        "—".to_string()
1968    } else {
1969        values
1970            .into_iter()
1971            .map(|value| format!("`{value}`"))
1972            .collect::<Vec<_>>()
1973            .join(", ")
1974    }
1975}
1976
1977/// Collect rule values owned by one canonical language row.
1978fn rules_for_language(rules: &[LanguageDetectionRule], language: &str) -> Vec<&'static str> {
1979    rules
1980        .iter()
1981        .filter_map(|rule| (rule.language == language).then_some(rule.value))
1982        .collect()
1983}
1984
1985/// Derive independent support counts without mutable literals.
1986#[must_use]
1987pub fn language_capability_counts() -> LanguageCapabilityCounts {
1988    let mut counts = LanguageCapabilityCounts {
1989        accepted: LANGUAGE_CAPABILITIES.len(),
1990        ..LanguageCapabilityCounts::default()
1991    };
1992    for capability in LANGUAGE_CAPABILITIES {
1993        if capability.optional_pack.is_some() {
1994            counts.optional_candidates += 1;
1995        } else {
1996            counts.built_in += 1;
1997        }
1998        increment_level(&mut counts.detected, capability.support.detected);
1999        increment_level(&mut counts.parsed, capability.support.parsed);
2000        increment_level(&mut counts.symbols, capability.support.symbols);
2001        increment_level(&mut counts.semantic, capability.support.semantic);
2002        increment_level(&mut counts.benchmarked, capability.support.benchmarked);
2003    }
2004    counts
2005}
2006
2007/// Increment one derived support-level bucket.
2008fn increment_level(counts: &mut CapabilityLevelCounts, level: CapabilitySupportLevel) {
2009    match level {
2010        CapabilitySupportLevel::Unavailable => counts.unavailable += 1,
2011        CapabilitySupportLevel::Fallback => counts.fallback += 1,
2012        CapabilitySupportLevel::Supported => counts.supported += 1,
2013    }
2014}
2015
2016/// Return the deterministic digest of achieved registry truth and detection rules.
2017#[must_use]
2018pub fn language_registry_digest() -> String {
2019    hash_language_registry(false)
2020}
2021
2022/// Return the deterministic digest of accepted minimum capability truth.
2023#[must_use]
2024pub fn accepted_language_capability_digest() -> String {
2025    hash_language_registry(true)
2026}
2027
2028/// Return the deterministic digest of semantic-provider ownership.
2029///
2030/// The projection is deliberately narrower than the complete language registry:
2031/// it binds only provider ownership, canonical resolution families, embedded-host
2032/// admission, and achieved semantic strength. Relation behavior remains owned by
2033/// `projectatlas-symbols` until the accepted relation-family inventory lands.
2034#[must_use]
2035pub fn semantic_provider_digest() -> String {
2036    let mut hasher = Hasher::new();
2037    hasher.update(&SEMANTIC_PROVIDER_CONTRACT_VERSION.to_le_bytes());
2038    for capability in LANGUAGE_CAPABILITIES {
2039        let effective_provider = capability.effective_semantic_provider();
2040        if effective_provider.is_none() && capability.embedded_language.is_none() {
2041            continue;
2042        }
2043        hash_value(&mut hasher, capability.id);
2044        hash_value(&mut hasher, capability.semantic_provider.as_str());
2045        hash_value(
2046            &mut hasher,
2047            capability
2048                .semantic_provider
2049                .resolution_family()
2050                .unwrap_or("unavailable"),
2051        );
2052        hash_value(
2053            &mut hasher,
2054            effective_provider
2055                .and_then(SemanticProviderOwner::resolution_family)
2056                .unwrap_or("unavailable"),
2057        );
2058        if let Some(embedded) = capability.embedded_language {
2059            hash_value(&mut hasher, embedded.host_kind.as_str());
2060            hash_value(&mut hasher, embedded.semantic_provider.as_str());
2061            hash_value(
2062                &mut hasher,
2063                embedded
2064                    .semantic_provider
2065                    .resolution_family()
2066                    .unwrap_or("unavailable"),
2067            );
2068        } else {
2069            hash_value(&mut hasher, "no-embedded-provider");
2070        }
2071        hash_value(&mut hasher, capability.support.semantic.as_str());
2072    }
2073    hasher.finalize().to_hex().to_string()
2074}
2075
2076/// Hash either achieved runtime truth or accepted minimums deterministically.
2077fn hash_language_registry(accepted_only: bool) -> String {
2078    hash_language_registry_with_content_rules(accepted_only, CONTENT_DIALECT_RULES)
2079}
2080
2081/// Hash registry truth with an explicit content-rule projection for validation tests.
2082fn hash_language_registry_with_content_rules(
2083    accepted_only: bool,
2084    content_rules: &[LanguageContentRule],
2085) -> String {
2086    let mut hasher = Hasher::new();
2087    hasher.update(&LANGUAGE_CAPABILITY_REGISTRY_VERSION.to_le_bytes());
2088    hasher.update(&ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION.to_le_bytes());
2089    hasher.update(&LANGUAGE_DETECTION_POLICY_VERSION.to_le_bytes());
2090    hasher.update(&(LANGUAGE_CONTENT_DETECTION_MAX_BYTES as u64).to_le_bytes());
2091    hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG);
2092    hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_VERSION);
2093    hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION);
2094    hasher.update(&(OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS as u64).to_le_bytes());
2095    for capability in LANGUAGE_CAPABILITIES {
2096        hash_value(&mut hasher, capability.id);
2097        for alias in capability.aliases {
2098            hash_value(&mut hasher, alias);
2099        }
2100        hash_value(&mut hasher, capability.classification.as_str());
2101        let support = if accepted_only {
2102            capability.accepted_minimum
2103        } else {
2104            capability.support
2105        };
2106        hash_support(&mut hasher, support);
2107        hash_value(&mut hasher, &format!("{:?}", capability.parser_support));
2108        hash_value(&mut hasher, &format!("{:?}", capability.symbol_parser));
2109        hash_value(&mut hasher, &format!("{:?}", capability.semantic_provider));
2110        hash_value(&mut hasher, &format!("{:?}", capability.embedded_language));
2111        hash_value(&mut hasher, &format!("{:?}", capability.structural_summary));
2112        hash_value(
2113            &mut hasher,
2114            capability.optional_pack.unwrap_or("default-core"),
2115        );
2116        hash_value(&mut hasher, capability.fixtures.positive_path);
2117        hash_value(&mut hasher, capability.fixtures.negative_path);
2118        hash_value(&mut hasher, capability.provenance.source());
2119        hash_value(&mut hasher, capability.provenance.version());
2120        hash_value(&mut hasher, capability.provenance.license());
2121        hash_value(&mut hasher, &format!("{:?}", capability.required_platforms));
2122    }
2123    for (kind, rules) in [
2124        ("exact", EXACT_FILENAME_RULES),
2125        ("compound", COMPOUND_EXTENSION_RULES),
2126        ("extension", EXTENSION_RULES),
2127    ] {
2128        for rule in rules {
2129            hash_value(&mut hasher, kind);
2130            hash_value(&mut hasher, rule.value);
2131            hash_value(&mut hasher, rule.language);
2132        }
2133    }
2134    for rule in content_rules {
2135        hash_value(&mut hasher, "content-interpreter");
2136        hash_value(&mut hasher, rule.interpreter);
2137        hasher.update(&[u8::from(rule.allow_version_suffix)]);
2138        hash_value(&mut hasher, rule.language);
2139    }
2140    hasher.finalize().to_hex().to_string()
2141}
2142
2143/// Hash the five independent support axes.
2144fn hash_support(hasher: &mut Hasher, support: LanguageCapabilitySupport) {
2145    hasher.update(&[
2146        support.detected as u8,
2147        support.parsed as u8,
2148        support.symbols as u8,
2149        support.semantic as u8,
2150        support.benchmarked as u8,
2151    ]);
2152}
2153
2154/// Hash one length-delimited registry string.
2155fn hash_value(hasher: &mut Hasher, value: &str) {
2156    hasher.update(&(value.len() as u64).to_le_bytes());
2157    hasher.update(value.as_bytes());
2158}
2159
2160/// Validate the accepted registry and all generated projections.
2161///
2162/// # Errors
2163///
2164/// Returns the first deterministic conflict or incomplete accepted row.
2165pub fn validate_language_registry() -> Result<(), LanguageRegistryError> {
2166    let mut canonical = BTreeSet::new();
2167    let mut names = BTreeMap::new();
2168    for capability in LANGUAGE_CAPABILITIES {
2169        if capability.id.is_empty() {
2170            return Err(LanguageRegistryError::new("empty canonical language ID"));
2171        }
2172        if !canonical.insert(capability.id) {
2173            return Err(LanguageRegistryError::new(format!(
2174                "duplicate canonical language ID {:?}",
2175                capability.id
2176            )));
2177        }
2178        register_owner(&mut names, capability.id, capability.id, "language name")?;
2179        for alias in capability.aliases {
2180            register_owner(&mut names, alias, capability.id, "language alias")?;
2181        }
2182        if !capability.support.meets(capability.accepted_minimum) {
2183            return Err(LanguageRegistryError::new(format!(
2184                "language {:?} is weaker than accepted capability set version {}",
2185                capability.id, ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION
2186            )));
2187        }
2188        if capability.fixtures.positive_path.is_empty()
2189            || capability.fixtures.negative_path.is_empty()
2190            || capability.fixtures.positive_path == capability.fixtures.negative_path
2191        {
2192            return Err(LanguageRegistryError::new(format!(
2193                "language {:?} lacks distinct natural positive and negative fixtures",
2194                capability.id
2195            )));
2196        }
2197        if capability.provenance.license().is_empty() {
2198            return Err(LanguageRegistryError::new(format!(
2199                "language {:?} lacks a provenance license input",
2200                capability.id
2201            )));
2202        }
2203        if capability
2204            .optional_pack
2205            .is_some_and(|owner| owner != BROAD_PARSER_PACK_ID)
2206        {
2207            return Err(LanguageRegistryError::new(format!(
2208                "optional language {:?} is assigned to unknown pack owner {:?}",
2209                capability.id, capability.optional_pack
2210            )));
2211        }
2212        if capability.optional_pack.is_some()
2213            && capability.provenance != CapabilityProvenance::PinnedOptionalCatalog
2214        {
2215            return Err(LanguageRegistryError::new(format!(
2216                "optional language {:?} lacks pinned catalog provenance",
2217                capability.id
2218            )));
2219        }
2220        if capability.optional_pack.is_none()
2221            && capability.provenance == CapabilityProvenance::PinnedOptionalCatalog
2222        {
2223            return Err(LanguageRegistryError::new(format!(
2224                "default-core language {:?} incorrectly claims optional catalog provenance",
2225                capability.id
2226            )));
2227        }
2228        match (capability.support.symbols, capability.symbol_parser) {
2229            (CapabilitySupportLevel::Unavailable, SymbolParserOwner::Unavailable)
2230            | (CapabilitySupportLevel::Fallback, SymbolParserOwner::Fallback)
2231            | (
2232                CapabilitySupportLevel::Supported,
2233                SymbolParserOwner::TreeSitter(_)
2234                | SymbolParserOwner::CargoManifest
2235                | SymbolParserOwner::Vue
2236                | SymbolParserOwner::PowerShell
2237                | SymbolParserOwner::Markdown
2238                | SymbolParserOwner::Document,
2239            ) => {}
2240            (support, owner) => {
2241                return Err(LanguageRegistryError::new(format!(
2242                    "language {:?} advertises symbol support {support:?} with incompatible owner {owner:?}",
2243                    capability.id
2244                )));
2245            }
2246        }
2247        match (capability.support.semantic, capability.semantic_provider) {
2248            (CapabilitySupportLevel::Unavailable, SemanticProviderOwner::Unavailable)
2249            | (
2250                CapabilitySupportLevel::Supported,
2251                SemanticProviderOwner::Rust
2252                | SemanticProviderOwner::EcmaScript
2253                | SemanticProviderOwner::Python
2254                | SemanticProviderOwner::Cargo,
2255            ) => {}
2256            (support, owner) => {
2257                return Err(LanguageRegistryError::new(format!(
2258                    "language {:?} advertises semantic support {support:?} with incompatible owner {owner:?}",
2259                    capability.id
2260                )));
2261            }
2262        }
2263        if let Some(embedded) = capability.embedded_language {
2264            if embedded.semantic_provider == SemanticProviderOwner::Unavailable {
2265                return Err(LanguageRegistryError::new(format!(
2266                    "embedded language host {:?} lacks a semantic provider owner",
2267                    capability.id
2268                )));
2269            }
2270            if capability.support.semantic != CapabilitySupportLevel::Unavailable
2271                || capability.semantic_provider != SemanticProviderOwner::Unavailable
2272            {
2273                return Err(LanguageRegistryError::new(format!(
2274                    "embedded language host {:?} conflates host and embedded semantic support",
2275                    capability.id
2276                )));
2277            }
2278        }
2279        if capability.optional_pack.is_some()
2280            && (capability.semantic_provider != SemanticProviderOwner::Unavailable
2281                || capability.embedded_language.is_some())
2282        {
2283            return Err(LanguageRegistryError::new(format!(
2284                "optional language {:?} advertises unvalidated semantic capability",
2285                capability.id
2286            )));
2287        }
2288    }
2289
2290    validate_rules("exact filename", EXACT_FILENAME_RULES, &canonical, false)?;
2291    validate_rules(
2292        "compound extension",
2293        COMPOUND_EXTENSION_RULES,
2294        &canonical,
2295        true,
2296    )?;
2297    validate_rules("extension", EXTENSION_RULES, &canonical, true)?;
2298    validate_content_rules(&canonical)?;
2299
2300    let mut detected: BTreeSet<_> = EXACT_FILENAME_RULES
2301        .iter()
2302        .chain(COMPOUND_EXTENSION_RULES)
2303        .chain(EXTENSION_RULES)
2304        .map(|rule| rule.language)
2305        .collect();
2306    detected.extend(CONTENT_DIALECT_RULES.iter().map(|rule| rule.language));
2307    for capability in LANGUAGE_CAPABILITIES {
2308        if capability.support.detected == CapabilitySupportLevel::Supported
2309            && !detected.contains(capability.id)
2310        {
2311            return Err(LanguageRegistryError::new(format!(
2312                "accepted language {:?} is a ghost row with no detector rule",
2313                capability.id
2314            )));
2315        }
2316        let detected_fixture = detect_language_request(LanguageDetectionRequest::new(
2317            capability.fixtures.positive_path,
2318            None,
2319        ))
2320        .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2321        if detected_fixture.map(|result| result.language) != Some(capability.id) {
2322            return Err(LanguageRegistryError::new(format!(
2323                "positive fixture {:?} does not select owning language {:?}",
2324                capability.fixtures.positive_path, capability.id
2325            )));
2326        }
2327        let negative_fixture = detect_language_request(LanguageDetectionRequest::new(
2328            capability.fixtures.negative_path,
2329            None,
2330        ))
2331        .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2332        if negative_fixture.map(|result| result.language) == Some(capability.id) {
2333            return Err(LanguageRegistryError::new(format!(
2334                "negative fixture {:?} still selects owning language {:?}",
2335                capability.fixtures.negative_path, capability.id
2336            )));
2337        }
2338    }
2339    let optional_candidates = LANGUAGE_CAPABILITIES
2340        .iter()
2341        .filter(|capability| capability.optional_pack.is_some())
2342        .count();
2343    if optional_candidates < OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS {
2344        return Err(LanguageRegistryError::new(format!(
2345            "optional catalog exposes only {optional_candidates} distinct candidate rows; at least {OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS} are required"
2346        )));
2347    }
2348    let accepted_digest = accepted_language_capability_digest();
2349    let expected_accepted_digest = match ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION {
2350        1 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST,
2351        2 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST,
2352        3 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST,
2353        4 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST,
2354        5 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST,
2355        6 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V6_DIGEST,
2356        7 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V7_DIGEST,
2357        8 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V8_DIGEST,
2358        9 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V9_DIGEST,
2359        10 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V10_DIGEST,
2360        11 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V11_DIGEST,
2361        12 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V12_DIGEST,
2362        13 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V13_DIGEST,
2363        14 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V14_DIGEST,
2364        15 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V15_DIGEST,
2365        version => {
2366            return Err(LanguageRegistryError::new(format!(
2367                "accepted language capability-set version {version} lacks a historical digest seal"
2368            )));
2369        }
2370    };
2371    if accepted_digest != expected_accepted_digest {
2372        return Err(LanguageRegistryError::new(format!(
2373            "accepted language capability-set version {ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION} changed from {expected_accepted_digest} to {accepted_digest}; bump the set version for an explicit compatibility decision"
2374        )));
2375    }
2376    Ok(())
2377}
2378
2379/// Register one normalized ID or alias and report both conflicting owners.
2380fn register_owner<'a>(
2381    owners: &mut BTreeMap<String, &'a str>,
2382    value: &str,
2383    owner: &'a str,
2384    kind: &str,
2385) -> Result<(), LanguageRegistryError> {
2386    let normalized = value.to_ascii_lowercase();
2387    if let Some(previous) = owners.insert(normalized.clone(), owner) {
2388        return Err(LanguageRegistryError::new(format!(
2389            "conflicting {kind} {normalized:?} is owned by both {previous:?} and {owner:?}"
2390        )));
2391    }
2392    Ok(())
2393}
2394
2395/// Validate one precedence-class rule table and its canonical targets.
2396fn validate_rules(
2397    kind: &str,
2398    rules: &[LanguageDetectionRule],
2399    canonical: &BTreeSet<&str>,
2400    normalize_case: bool,
2401) -> Result<(), LanguageRegistryError> {
2402    let mut owners = BTreeMap::new();
2403    let mut previous_compound_length = usize::MAX;
2404    for rule in rules {
2405        if !canonical.contains(rule.language) {
2406            return Err(LanguageRegistryError::new(format!(
2407                "{kind} rule {:?} targets missing language {:?}",
2408                rule.value, rule.language
2409            )));
2410        }
2411        let key = if normalize_case {
2412            rule.value.to_ascii_lowercase()
2413        } else {
2414            rule.value.to_string()
2415        };
2416        if let Some(previous) = owners.insert(key.clone(), rule.language)
2417            && previous != rule.language
2418        {
2419            return Err(LanguageRegistryError::new(format!(
2420                "conflicting {kind} rule {key:?} is owned by both {previous:?} and {:?}",
2421                rule.language
2422            )));
2423        }
2424        if kind == "compound extension" {
2425            if rule.value.len() > previous_compound_length {
2426                return Err(LanguageRegistryError::new(format!(
2427                    "compound extension rules are not longest-first at {:?}",
2428                    rule.value
2429                )));
2430            }
2431            previous_compound_length = rule.value.len();
2432        }
2433    }
2434    Ok(())
2435}
2436
2437/// Validate registry-owned content rules and their canonical targets.
2438fn validate_content_rules(canonical: &BTreeSet<&str>) -> Result<(), LanguageRegistryError> {
2439    let mut owners = BTreeMap::new();
2440    for rule in CONTENT_DIALECT_RULES {
2441        if !canonical.contains(rule.language) {
2442            return Err(LanguageRegistryError::new(format!(
2443                "content interpreter {:?} targets missing language {:?}",
2444                rule.interpreter, rule.language
2445            )));
2446        }
2447        if rule.interpreter.is_empty()
2448            || !rule
2449                .interpreter
2450                .bytes()
2451                .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
2452        {
2453            return Err(LanguageRegistryError::new(format!(
2454                "content interpreter {:?} is not a normalized basename",
2455                rule.interpreter
2456            )));
2457        }
2458        if let Some(previous) = owners.insert(rule.interpreter, rule.language) {
2459            return Err(LanguageRegistryError::new(format!(
2460                "conflicting content interpreter {:?} is owned by both {previous:?} and {:?}",
2461                rule.interpreter, rule.language
2462            )));
2463        }
2464    }
2465    Ok(())
2466}
2467
2468#[cfg(test)]
2469mod tests {
2470    use super::*;
2471    use serde::Deserialize;
2472    use std::io;
2473
2474    /// Independent frozen detector corpus from the 0.3.26 release.
2475    #[derive(Debug, Deserialize)]
2476    struct DetectionCompatibilityFixture {
2477        /// Ordered broad extensions and their canonical results.
2478        extensions: Vec<String>,
2479        /// Exact filename rules and their canonical results.
2480        exact_filenames: Vec<String>,
2481    }
2482
2483    /// Independently accepted projection of the pinned optional catalog subset.
2484    #[derive(Debug, Deserialize)]
2485    struct OptionalDetectionCatalogFixture {
2486        /// Catalog package identity.
2487        catalog: String,
2488        /// Exact catalog release.
2489        version: String,
2490        /// Exact source revision.
2491        revision: String,
2492        /// Ordered canonical ID to accepted extension mappings.
2493        rows: Vec<String>,
2494    }
2495
2496    fn require_test(condition: bool, message: impl Into<String>) -> Result<(), Box<dyn Error>> {
2497        if condition {
2498            Ok(())
2499        } else {
2500            Err(io::Error::other(message.into()).into())
2501        }
2502    }
2503
2504    #[test]
2505    fn content_classification_is_registry_owned_and_utf8_safe() -> Result<(), Box<dyn Error>> {
2506        require_test(
2507            content_classification(Some("rust"), true) == ContentClassification::Source,
2508            "Rust did not retain its source classification",
2509        )?;
2510        require_test(
2511            content_classification(Some("markdown"), true) == ContentClassification::Documentation,
2512            "Markdown did not retain its documentation classification",
2513        )?;
2514        require_test(
2515            content_classification(Some("json"), true) == ContentClassification::ConfigurationData,
2516            "JSON did not retain its configuration/data classification",
2517        )?;
2518        require_test(
2519            content_classification(None, true) == ContentClassification::OtherText,
2520            "unknown UTF-8 content was not classified as other text",
2521        )?;
2522        require_test(
2523            content_classification(Some("rust"), false) == ContentClassification::Source,
2524            "known language classification did not take precedence over UTF-8 fallback",
2525        )?;
2526        require_test(
2527            content_classification(None, false) == ContentClassification::Opaque,
2528            "unknown invalid UTF-8 content was not opaque",
2529        )?;
2530        require_test(
2531            language_capability("rst").map(|row| row.classification)
2532                == Some(ContentClassification::Documentation),
2533            "optional documentation rows lost registry-owned classification",
2534        )?;
2535        require_test(
2536            language_capability("csv").map(|row| row.classification)
2537                == Some(ContentClassification::ConfigurationData),
2538            "optional structured-data rows lost registry-owned classification",
2539        )?;
2540
2541        let rendered = render_language_support_markdown()?;
2542        require_test(
2543            rendered.contains("| Language | Classification | Aliases |"),
2544            "generated capability matrix omitted the classification column",
2545        )?;
2546        require_test(
2547            rendered.contains("| `markdown` | `documentation` |"),
2548            "generated capability matrix omitted Markdown's documentation role",
2549        )
2550    }
2551
2552    #[test]
2553    fn content_classification_storage_and_selection_contracts_are_closed()
2554    -> Result<(), Box<dyn Error>> {
2555        for classification in ContentClassification::ALL {
2556            require_test(
2557                ContentClassification::from_db(classification.as_str()) == Some(classification),
2558                format!("classification {classification} did not round-trip"),
2559            )?;
2560            require_test(
2561                serde_json::from_str::<ContentClassification>(&serde_json::to_string(
2562                    &classification,
2563                )?)? == classification,
2564                format!("classification {classification} wire spelling did not round-trip"),
2565            )?;
2566        }
2567        require_test(
2568            ContentClassification::from_db("generated").is_none(),
2569            "unsupported generated classification was accepted",
2570        )?;
2571
2572        require_test(
2573            ContentSelection::default() == ContentSelection::UnspecifiedLegacy
2574                && ContentSelection::default().explicit_value().is_none(),
2575            "omitted selection no longer preserves the distinct legacy state",
2576        )?;
2577        require_test(
2578            ContentSelection::UnspecifiedLegacy.includes(ContentClassification::ConfigurationData)
2579                && ContentSelection::Source.includes(ContentClassification::Source)
2580                && !ContentSelection::Source.includes(ContentClassification::Documentation)
2581                && ContentSelection::Documentation.includes(ContentClassification::Documentation)
2582                && ContentSelection::Both.includes(ContentClassification::Source)
2583                && ContentSelection::Both.includes(ContentClassification::Documentation)
2584                && !ContentSelection::Both.includes(ContentClassification::OtherText),
2585            "content selection admitted the wrong classification",
2586        )?;
2587        require_test(
2588            "source".parse::<ContentSelection>()? == ContentSelection::Source
2589                && "documentation".parse::<ContentSelection>()? == ContentSelection::Documentation
2590                && "both".parse::<ContentSelection>()? == ContentSelection::Both,
2591            "an explicit selection failed to parse",
2592        )?;
2593        let invalid = "".parse::<ContentSelection>().err().ok_or_else(|| {
2594            std::io::Error::other("empty explicit content selection was accepted")
2595        })?;
2596        require_test(
2597            invalid.requested().is_empty()
2598                && invalid
2599                    .to_string()
2600                    .contains("source, documentation, or both"),
2601            "invalid selection did not retain the typed allowed-value diagnostic",
2602        )?;
2603        require_test(
2604            serde_json::to_string(&ContentSelection::Source)? == "\"source\""
2605                && serde_json::to_string(&ContentSelection::UnspecifiedLegacy).is_err()
2606                && serde_json::from_str::<ContentSelection>("\"unspecified_legacy\"").is_err(),
2607            "the internal legacy state leaked into the caller-visible wire contract",
2608        )
2609    }
2610
2611    #[test]
2612    fn accepted_registry_and_generated_projections_validate() -> Result<(), Box<dyn Error>> {
2613        validate_language_registry()?;
2614        require_test(
2615            LANGUAGE_CAPABILITIES.len() == LANGUAGE_SPECS.len(),
2616            "language capability and parser metadata projections differ",
2617        )?;
2618        let report = language_registry_report();
2619        require_test(
2620            report.counts.accepted == LANGUAGE_CAPABILITIES.len(),
2621            "accepted language count is not registry-derived",
2622        )?;
2623        let canonical_ids = LANGUAGE_CAPABILITIES
2624            .iter()
2625            .map(|capability| capability.id)
2626            .collect::<BTreeSet<_>>();
2627        require_test(
2628            report.counts.accepted == canonical_ids.len(),
2629            "aliases or extensions inflated the canonical capability count",
2630        )?;
2631        require_test(
2632            LANGUAGE_CAPABILITIES
2633                .iter()
2634                .any(|capability| !capability.aliases.is_empty())
2635                && !EXTENSION_RULES.is_empty(),
2636            "alias/extension non-inflation check became vacuous",
2637        )?;
2638        require_test(
2639            report.counts.built_in + report.counts.optional_candidates == report.counts.accepted,
2640            "built-in and optional language counts do not cover the accepted set",
2641        )?;
2642        require_test(
2643            language_capability_report_rows().len() == report.counts.accepted,
2644            "documentation projection does not cover the accepted set",
2645        )?;
2646        require_test(
2647            report.counts.optional_candidates >= OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
2648            "optional catalog projection is below the accepted breadth floor",
2649        )?;
2650        require_test(
2651            !report.registry_digest.is_empty(),
2652            "language registry digest is empty",
2653        )?;
2654        require_test(
2655            !report.accepted_set_digest.is_empty(),
2656            "accepted language-set digest is empty",
2657        )?;
2658        require_test(
2659            report.semantic_provider_digest.len() == 64
2660                && report
2661                    .semantic_provider_digest
2662                    .bytes()
2663                    .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)),
2664            "semantic provider digest is not a bounded lowercase hexadecimal identity",
2665        )?;
2666        let encoded = serde_json::to_vec(&report)?;
2667        require_test(
2668            encoded.len() <= LANGUAGE_REGISTRY_REPORT_MAX_BYTES,
2669            "language registry settings projection exceeded its content-free output budget",
2670        )?;
2671        Ok(())
2672    }
2673
2674    #[test]
2675    fn semantic_provider_and_embedded_host_claims_are_honest() -> Result<(), Box<dyn Error>> {
2676        let report = language_registry_report();
2677        let semantic_rows = LANGUAGE_CAPABILITIES
2678            .iter()
2679            .filter(|capability| capability.support.semantic == CapabilitySupportLevel::Supported)
2680            .count();
2681        require_test(
2682            report.counts.semantic.supported == semantic_rows,
2683            "semantic provider rows did not determine the semantic capability count",
2684        )?;
2685
2686        for capability in LANGUAGE_CAPABILITIES
2687            .iter()
2688            .filter(|capability| capability.optional_pack.is_some())
2689        {
2690            require_test(
2691                capability.semantic_provider == SemanticProviderOwner::Unavailable
2692                    && capability.embedded_language.is_none()
2693                    && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2694                format!(
2695                    "optional candidate {:?} advertised semantic capability",
2696                    capability.id
2697                ),
2698            )?;
2699        }
2700
2701        for (language, expected) in [
2702            (
2703                "html",
2704                EmbeddedLanguageCapability {
2705                    host_kind: EmbeddedHostKind::HtmlLike,
2706                    semantic_provider: SemanticProviderOwner::EcmaScript,
2707                },
2708            ),
2709            (
2710                "vue",
2711                EmbeddedLanguageCapability {
2712                    host_kind: EmbeddedHostKind::Component,
2713                    semantic_provider: SemanticProviderOwner::EcmaScript,
2714                },
2715            ),
2716            (
2717                "svelte",
2718                EmbeddedLanguageCapability {
2719                    host_kind: EmbeddedHostKind::Template,
2720                    semantic_provider: SemanticProviderOwner::EcmaScript,
2721                },
2722            ),
2723        ] {
2724            let capability = language_capability(language)
2725                .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2726            require_test(
2727                capability.embedded_language == Some(expected)
2728                    && capability.semantic_provider == SemanticProviderOwner::Unavailable
2729                    && capability.effective_semantic_provider()
2730                        == Some(SemanticProviderOwner::EcmaScript)
2731                    && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2732                format!("{language} host/embedded capability drifted"),
2733            )?;
2734        }
2735
2736        for language in ["cargo-lock", "java", "c", "go"] {
2737            let capability = language_capability(language)
2738                .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2739            require_test(
2740                capability.semantic_provider == SemanticProviderOwner::Unavailable
2741                    && capability.effective_semantic_provider().is_none()
2742                    && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2743                format!("unsupported semantic language {language:?} was promoted"),
2744            )?;
2745        }
2746        require_test(
2747            SemanticProviderOwner::EcmaScript.resolution_family() == Some("ecmascript")
2748                && SemanticProviderOwner::Unavailable
2749                    .resolution_family()
2750                    .is_none(),
2751            "semantic provider resolution-family identity drifted",
2752        )?;
2753        Ok(())
2754    }
2755
2756    #[test]
2757    fn detects_every_broad_source_extension() {
2758        for extension in BROAD_SOURCE_EXTENSIONS {
2759            assert!(
2760                detect_language(Some(extension)).is_some(),
2761                "missing broad source extension support for {extension}"
2762            );
2763        }
2764    }
2765
2766    #[test]
2767    fn detects_every_accepted_registry_source_extension() {
2768        for extension in DETECTED_SOURCE_EXTENSIONS {
2769            assert!(
2770                detect_language(Some(extension)).is_some(),
2771                "missing accepted source extension support for {extension}"
2772            );
2773        }
2774    }
2775
2776    #[test]
2777    fn generated_language_support_document_is_current() -> Result<(), Box<dyn Error>> {
2778        let rendered = render_language_support_markdown()?;
2779        require_test(
2780            include_str!("../../../docs/language-support.md") == rendered,
2781            "checked-in language support document is stale",
2782        )?;
2783        require_test(
2784            include_str!(
2785                "../../../plugins/projectatlas/skills/projectatlas/references/language-support.md"
2786            ) == rendered,
2787            "bundled skill language support reference is stale",
2788        )
2789    }
2790
2791    #[test]
2792    fn built_in_grammar_provenance_matches_workspace_pins() {
2793        let workspace = include_str!("../../../Cargo.toml");
2794        let mut checked = BTreeSet::new();
2795        for grammar in TreeSitterGrammar::ALL {
2796            let package = grammar.package();
2797            if checked.insert(package) {
2798                let pin = format!("{package} = \"={}\"", grammar.version());
2799                assert!(
2800                    workspace.lines().any(|line| line.trim() == pin),
2801                    "workspace dependency pin drifted from registry provenance: {pin}"
2802                );
2803            }
2804        }
2805    }
2806
2807    #[test]
2808    fn frozen_v0326_detection_corpus_remains_exact() -> Result<(), Box<dyn Error>> {
2809        let fixture: DetectionCompatibilityFixture = serde_json::from_str(include_str!(
2810            "../../../fixtures/languages/v0.3.26-detection.json"
2811        ))?;
2812        let ordered_extensions = fixture
2813            .extensions
2814            .iter()
2815            .map(|row| row.split_once('=').map(|(extension, _)| extension))
2816            .collect::<Option<Vec<_>>>()
2817            .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2818        if ordered_extensions != BROAD_SOURCE_EXTENSIONS {
2819            return Err(io::Error::other(
2820                "ordered 0.3.26 broad extension compatibility corpus changed",
2821            )
2822            .into());
2823        }
2824        for row in fixture.extensions {
2825            let (extension, expected) = row
2826                .split_once('=')
2827                .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2828            if detect_language(Some(extension)).as_deref() != Some(expected) {
2829                return Err(io::Error::other(format!(
2830                    "0.3.26 extension {extension:?} no longer selects {expected:?}"
2831                ))
2832                .into());
2833            }
2834        }
2835        for row in fixture.exact_filenames {
2836            let (path, expected) = row
2837                .split_once('=')
2838                .ok_or_else(|| io::Error::other("invalid exact filename compatibility row"))?;
2839            if detect_language_for_path(path, None).as_deref() != Some(expected)
2840                || detect_language_for_path(&format!("folder\\{path}"), None).as_deref()
2841                    != Some(expected)
2842            {
2843                return Err(io::Error::other(format!(
2844                    "0.3.26 exact filename {path:?} no longer selects {expected:?}"
2845                ))
2846                .into());
2847            }
2848        }
2849        Ok(())
2850    }
2851
2852    #[test]
2853    fn accepted_optional_detection_subset_matches_independent_catalog_projection()
2854    -> Result<(), Box<dyn Error>> {
2855        let fixture: OptionalDetectionCatalogFixture = serde_json::from_str(include_str!(
2856            "../../../fixtures/languages/accepted-optional-detection-catalog.json"
2857        ))?;
2858        require_test(
2859            fixture.catalog == OPTIONAL_GRAMMAR_CATALOG,
2860            "optional catalog identity drifted",
2861        )?;
2862        require_test(
2863            fixture.version == OPTIONAL_GRAMMAR_CATALOG_VERSION,
2864            "optional catalog version drifted",
2865        )?;
2866        require_test(
2867            fixture.revision == OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
2868            "optional catalog revision drifted",
2869        )?;
2870        let projected = LANGUAGE_CAPABILITIES
2871            .iter()
2872            .filter(|capability| capability.optional_pack.is_some())
2873            .map(|capability| {
2874                let extensions = rules_for_language(EXTENSION_RULES, capability.id);
2875                match extensions.as_slice() {
2876                    [extension] => Ok(format!("{}={extension}", capability.id)),
2877                    _ => Err(io::Error::other(format!(
2878                        "optional detection row {:?} must own exactly one accepted extension",
2879                        capability.id
2880                    ))),
2881                }
2882            })
2883            .collect::<Result<Vec<_>, _>>()?;
2884        require_test(
2885            fixture.rows == projected,
2886            "accepted optional catalog projection drifted",
2887        )
2888    }
2889
2890    #[test]
2891    fn preserves_representative_broad_source_extensions() {
2892        assert_eq!(
2893            detect_language(Some(".d.ts")).as_deref(),
2894            Some("typescript")
2895        );
2896        assert_eq!(detect_language(Some(".pyw")).as_deref(), Some("python"));
2897        assert_eq!(detect_language(Some(".kts")).as_deref(), Some("kotlin"));
2898        assert_eq!(
2899            detect_language(Some(".psm1")).as_deref(),
2900            Some("powershell")
2901        );
2902        assert_eq!(detect_language(Some(".zon")).as_deref(), Some("zig"));
2903        assert_eq!(detect_language(Some(".proto")).as_deref(), Some("protobuf"));
2904        assert_eq!(detect_language(Some(".R")).as_deref(), Some("r"));
2905        assert_eq!(detect_language(Some(".ini")).as_deref(), Some("config"));
2906        assert_eq!(detect_language(Some(".liquibase")).as_deref(), Some("sql"));
2907        assert_eq!(detect_language(Some(".toon")).as_deref(), Some("toon"));
2908    }
2909
2910    #[test]
2911    fn preserves_exact_filename_case_and_compound_precedence() {
2912        let exact =
2913            detect_language_request(LanguageDetectionRequest::new("Cargo.toml", Some(".toml")));
2914        assert!(matches!(
2915            exact,
2916            Ok(Some(LanguageDetection {
2917                language: "cargo-manifest",
2918                reason: LanguageDetectionReason::ExactFilename,
2919            }))
2920        ));
2921        assert_eq!(
2922            detect_language_for_path("cargo.toml", Some(".toml")).as_deref(),
2923            Some("toml")
2924        );
2925        let compound = detect_language_request(LanguageDetectionRequest::new(
2926            "types/index.d.ts",
2927            Some(".d.ts"),
2928        ));
2929        assert!(matches!(
2930            compound,
2931            Ok(Some(LanguageDetection {
2932                language: "typescript",
2933                reason: LanguageDetectionReason::CompoundExtension,
2934            }))
2935        ));
2936    }
2937
2938    #[test]
2939    fn typed_precedence_prefers_override_and_bounds_content() -> Result<(), Box<dyn Error>> {
2940        let override_result = detect_language_request(LanguageDetectionRequest {
2941            path: "Cargo.toml",
2942            extension: Some(".toml"),
2943            explicit_override: Some("py"),
2944            content_prefix: Some(b"#!/usr/bin/env node\n"),
2945        })?
2946        .ok_or_else(|| io::Error::other("override detection missing"))?;
2947        require_test(
2948            override_result.language == "python",
2949            "explicit alias override did not select Python",
2950        )?;
2951        require_test(
2952            override_result.reason == LanguageDetectionReason::ExplicitOverride,
2953            "explicit override did not retain its typed reason",
2954        )?;
2955
2956        let content_result = detect_language_request(LanguageDetectionRequest {
2957            path: "tool",
2958            extension: None,
2959            explicit_override: None,
2960            content_prefix: Some(b"#!/usr/bin/env python\nprint('ok')\n"),
2961        })?
2962        .ok_or_else(|| io::Error::other("content detection missing"))?;
2963        require_test(
2964            content_result.language == "python",
2965            "bounded shebang did not select Python",
2966        )?;
2967        require_test(
2968            content_result.reason == LanguageDetectionReason::ContentDialect,
2969            "bounded shebang did not retain its typed reason",
2970        )?;
2971        let extension_result =
2972            detect_language_request(LanguageDetectionRequest::new("module.py", Some(".py")))?
2973                .ok_or_else(|| io::Error::other("extension detection missing"))?;
2974        require_test(
2975            extension_result.language == "python",
2976            "extension did not select Python",
2977        )?;
2978        require_test(
2979            extension_result.reason == LanguageDetectionReason::Extension,
2980            "extension did not retain its typed reason",
2981        )?;
2982        require_test(
2983            detect_language_request(LanguageDetectionRequest {
2984                path: "tool",
2985                extension: None,
2986                explicit_override: Some("missing-language"),
2987                content_prefix: None,
2988            })
2989            .is_err(),
2990            "unknown explicit override did not fail closed",
2991        )?;
2992        Ok(())
2993    }
2994
2995    #[test]
2996    fn every_detected_language_has_compatible_parser_metadata() -> Result<(), Box<dyn Error>> {
2997        for rule in EXACT_FILENAME_RULES
2998            .iter()
2999            .chain(COMPOUND_EXTENSION_RULES)
3000            .chain(EXTENSION_RULES)
3001        {
3002            let spec = language_spec(rule.language).ok_or_else(|| {
3003                io::Error::other(format!("missing parser coverage for {}", rule.language))
3004            })?;
3005            require_test(
3006                spec.language == rule.language,
3007                format!("parser coverage ownership drifted for {}", rule.language),
3008            )?;
3009        }
3010        for rule in CONTENT_DIALECT_RULES {
3011            let spec = language_spec(rule.language).ok_or_else(|| {
3012                io::Error::other(format!("missing parser coverage for {}", rule.language))
3013            })?;
3014            require_test(
3015                spec.language == rule.language,
3016                format!("content detector ownership drifted for {}", rule.language),
3017            )?;
3018        }
3019        Ok(())
3020    }
3021
3022    #[test]
3023    fn structural_summary_rows_do_not_advertise_symbols() {
3024        for capability in LANGUAGE_CAPABILITIES
3025            .iter()
3026            .filter(|capability| capability.parser_support == LanguageParserSupport::Structural)
3027            .filter(|capability| capability.symbol_parser == SymbolParserOwner::Unavailable)
3028        {
3029            assert_eq!(
3030                capability.support.symbols,
3031                CapabilitySupportLevel::Unavailable,
3032                "structural summary row {:?} advertised symbols without an owner",
3033                capability.id
3034            );
3035        }
3036    }
3037
3038    #[test]
3039    fn shebang_rules_are_exact_bounded_and_utf8_boundary_safe() -> Result<(), Box<dyn Error>> {
3040        for rule in CONTENT_DIALECT_RULES {
3041            let source = format!("#!/usr/bin/env {}\n", rule.interpreter);
3042            let detected = detect_language_request(LanguageDetectionRequest {
3043                path: "tool",
3044                extension: None,
3045                explicit_override: None,
3046                content_prefix: Some(source.as_bytes()),
3047            })?
3048            .ok_or_else(|| io::Error::other("declared shebang was not detected"))?;
3049            require_test(
3050                detected.language == rule.language,
3051                format!("shebang owner drifted for {}", rule.interpreter),
3052            )?;
3053            require_test(
3054                detected.reason == LanguageDetectionReason::ContentDialect,
3055                format!("shebang reason drifted for {}", rule.interpreter),
3056            )?;
3057        }
3058
3059        for near_miss in ["wish", "mesh-agent", "python-helper", "nodejs"] {
3060            let source = format!("#!/usr/bin/env {near_miss}\n");
3061            let detected = detect_language_request(LanguageDetectionRequest {
3062                path: "tool",
3063                extension: None,
3064                explicit_override: None,
3065                content_prefix: Some(source.as_bytes()),
3066            })?;
3067            require_test(
3068                detected.is_none(),
3069                format!("near-miss interpreter {near_miss:?} was classified"),
3070            )?;
3071        }
3072
3073        for (interpreter, expected) in [
3074            ("python3", "python"),
3075            ("python3.12", "python"),
3076            ("ruby3.3", "ruby"),
3077            ("lua5.4", "lua"),
3078        ] {
3079            let source = format!("#!/usr/bin/env {interpreter}\n");
3080            let detected = detect_language_request(LanguageDetectionRequest {
3081                path: "tool",
3082                extension: None,
3083                explicit_override: None,
3084                content_prefix: Some(source.as_bytes()),
3085            })?;
3086            require_test(
3087                detected.map(|result| result.language) == Some(expected),
3088                format!("valid versioned interpreter {interpreter:?} was not classified"),
3089            )?;
3090        }
3091
3092        for near_miss in ["python.", "python..3", "ruby...", "lua."] {
3093            let source = format!("#!/usr/bin/env {near_miss}\n");
3094            let detected = detect_language_request(LanguageDetectionRequest {
3095                path: "tool",
3096                extension: None,
3097                explicit_override: None,
3098                content_prefix: Some(source.as_bytes()),
3099            })?;
3100            require_test(
3101                detected.is_none(),
3102                format!("invalid versioned interpreter {near_miss:?} was classified"),
3103            )?;
3104        }
3105
3106        let mut split_utf8_after_shebang = b"#!/usr/bin/env python\n".to_vec();
3107        split_utf8_after_shebang.resize(LANGUAGE_CONTENT_DETECTION_MAX_BYTES, b'x');
3108        split_utf8_after_shebang[LANGUAGE_CONTENT_DETECTION_MAX_BYTES - 1] = 0xc3;
3109        let detected = detect_language_request(LanguageDetectionRequest {
3110            path: "tool",
3111            extension: None,
3112            explicit_override: None,
3113            content_prefix: Some(&split_utf8_after_shebang),
3114        })?;
3115        require_test(
3116            detected.map(|result| result.language) == Some("python"),
3117            "valid shebang was lost at a later UTF-8 split boundary",
3118        )?;
3119
3120        let mut after_bound = vec![b' '; LANGUAGE_CONTENT_DETECTION_MAX_BYTES];
3121        after_bound.extend_from_slice(b"#!/usr/bin/env python\n");
3122        let detected = detect_language_request(LanguageDetectionRequest {
3123            path: "tool",
3124            extension: None,
3125            explicit_override: None,
3126            content_prefix: Some(&after_bound),
3127        })?;
3128        require_test(
3129            detected.is_none(),
3130            "content detection inspected bytes beyond its accepted bound",
3131        )
3132    }
3133
3134    #[test]
3135    fn content_rule_changes_advance_registry_digest() {
3136        let baseline = hash_language_registry_with_content_rules(false, CONTENT_DIALECT_RULES);
3137        let mut changed = CONTENT_DIALECT_RULES.to_vec();
3138        changed[0].language = "ruby";
3139        assert_ne!(
3140            baseline,
3141            hash_language_registry_with_content_rules(false, &changed)
3142        );
3143    }
3144
3145    #[test]
3146    fn built_in_tree_sitter_projection_is_registry_derived() {
3147        let projected = builtin_tree_sitter_language_ids();
3148        let expected = LANGUAGE_CAPABILITIES
3149            .iter()
3150            .filter(|capability| {
3151                matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
3152            })
3153            .count();
3154        assert_eq!(projected.len(), expected);
3155        assert!(projected.contains(&"rust"));
3156        assert!(!projected.contains(&"markdown"));
3157    }
3158
3159    #[test]
3160    fn conflicting_detection_rules_report_both_owners() -> Result<(), Box<dyn Error>> {
3161        let canonical = BTreeSet::from(["python", "rust"]);
3162        let rules = [
3163            LanguageDetectionRule {
3164                value: ".mixed",
3165                language: "rust",
3166            },
3167            LanguageDetectionRule {
3168                value: ".MIXED",
3169                language: "python",
3170            },
3171        ];
3172        let error = validate_rules("extension", &rules, &canonical, true)
3173            .err()
3174            .ok_or_else(|| io::Error::other("case-normalized conflicting owners must fail"))?;
3175        let message = error.to_string();
3176        require_test(
3177            message.contains("rust") && message.contains("python"),
3178            "conflict diagnostic did not name both owners",
3179        )
3180    }
3181}