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