Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ tree-sitter-cpp = "0.23.4"
tree-sitter-css = "0.25.0"
tree-sitter-fortran = "0.6.0"
tree-sitter-go = "0.25.0"
tree-sitter-javascript = "0.25.0"
tree-sitter-python = "0.25.0"
tree-sitter-rust = "0.24.2"
walkdir = "2"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# codebaseGraph

`codebaseGraph` turns a local source repository into a searchable code graph for
AI coding agents. It indexes Python, Rust, Go, C, C++, Fortran, CSS, Markdown,
and MDX, then exposes compact context, schema information, query helpers, and
bounded read-only graph queries through a native CLI and MCP server.
AI coding agents. It indexes Python, Rust, Go, C, C++, Fortran, CSS, JavaScript,
JSX, Markdown, and MDX, then exposes compact context, schema information, query
helpers, and bounded read-only graph queries through a native CLI and MCP server.

This workspace also ships `k-wiki`, an optional subsystem for curated repository
knowledge. The graph and wiki have separate source and generated state.
Expand Down
28 changes: 28 additions & 0 deletions knowledge/architecture/language-javascript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
description: Built-in JavaScript and JSX discovery, grammar, catalog, and semantic-normalization contract.
resource: repository-architecture
tags:
- architecture
- javascript
- jsx
- language-support
- parser
- tree-sitter
timestamp: 2026-08-25
title: JavaScript and JSX Parsing Contract
type: architecture
---
# JavaScript and JSX Parsing Contract

The Graph Runtime exposes JavaScript as one built-in syntax language backed by the Tree-sitter JavaScript grammar, which includes JSX syntax.

## Public contract

- `javascript` recognizes `.js`, `.jsx`, `.mjs`, and `.cjs` using `tree_sitter_javascript@0.25.0`.
- The key is advertised by the CLI syntax catalog and MCP `graph_syntax` schema.
- Classes, functions, generator functions, methods, imports, and calls map to established ontology nodes.
- JSX elements remain available through ordered raw syntax captures; they are not promoted to framework-specific component nodes.

Framework inference and embedded-language extraction are outside this contract.

Related: [Graph Runtime](./graph-runtime.md) and [Repository Ownership Map](./repository-map.md).
2 changes: 1 addition & 1 deletion src/adapters/cli/format/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub(in crate::adapters::cli) fn graph_schema_help() -> &'static str {
}

pub(in crate::adapters::cli) fn graph_syntax_help() -> &'static str {
"codebase-graph syntax\n\nUSAGE:\n codebase-graph syntax <language> [--format json|block] [--json] [--pretty]\n\nOPTIONS:\n <language> One of c, cpp, css, fortran, go, markdown, python, or rust\n --format <format> block or json; defaults to block\n --json Emit compact JSON output\n --pretty Pretty-print JSON output"
"codebase-graph syntax\n\nUSAGE:\n codebase-graph syntax <language> [--format json|block] [--json] [--pretty]\n\nOPTIONS:\n <language> One of c, cpp, css, fortran, go, javascript, markdown, python, or rust\n --format <format> block or json; defaults to block\n --json Emit compact JSON output\n --pretty Pretty-print JSON output"
}

pub(in crate::adapters::cli) fn graph_query_helpers_help() -> &'static str {
Expand Down
22 changes: 19 additions & 3 deletions src/adapters/cli/tests/dispatch_materialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,26 @@ fn materialize_css_source_root_without_profiles() {
".card { color: red; width: calc(100% - 1rem); }\n",
)
.unwrap();
assert_materializes_language(&root, "styles.css", "css");
let _ = fs::remove_dir_all(root);
}

#[test]
fn materialize_javascript_source_root_without_profiles() {
let root = unique_temp_dir("codebase-graph-js-source-root");
fs::create_dir_all(&root).unwrap();
fs::write(
root.join("component.jsx"),
"export function Card() { return <section>Hello</section>; }\n",
)
.unwrap();
assert_materializes_language(&root, "component.jsx", "javascript");
let _ = fs::remove_dir_all(root);
}

fn assert_materializes_language(root: &Path, path: &str, language: &str) {
let db_path = root.join(".codebaseGraph").join("graph.ldb");
let manifest_path = root.join(".codebaseGraph").join("manifest.json");

let mut output = Vec::new();
run(
[
Expand All @@ -303,8 +320,7 @@ fn materialize_css_source_root_without_profiles() {
assert_eq!(value["database_written"], true);
let manifest: serde_json::Value =
serde_json::from_str(&fs::read_to_string(&manifest_path).unwrap()).unwrap();
assert_eq!(manifest["files"]["styles.css"]["language"], "css");
let _ = fs::remove_dir_all(root);
assert_eq!(manifest["files"][path]["language"], language);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions src/adapters/cli/tests/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ fn graph_syntax_outputs_language_catalog_and_validates_language() {
.as_array()
.is_some_and(|nodes| nodes.iter().any(|node| node["type"] == "rule_set")));

let mut javascript_output = Vec::new();
run(["syntax", "javascript", "--json"], &mut javascript_output).unwrap();
let javascript: serde_json::Value = serde_json::from_slice(&javascript_output).unwrap();
assert_eq!(javascript["language"], "javascript");
assert!(javascript["node_types"]
.as_array()
.is_some_and(|nodes| nodes.iter().any(|node| node["type"] == "jsx_element")));

let error = run(["syntax", "custom"], &mut Vec::new()).unwrap_err();
assert!(error.contains("Unknown syntax language"));
assert!(run(["syntax"], &mut Vec::new())
Expand Down
12 changes: 11 additions & 1 deletion src/adapters/mcp/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,17 @@ mod tests {
assert_eq!(syntax["inputSchema"]["required"], json!(["language"]));
assert_eq!(
syntax["inputSchema"]["properties"]["language"]["enum"],
json!(["c", "cpp", "css", "fortran", "go", "markdown", "python", "rust"])
json!([
"c",
"cpp",
"css",
"fortran",
"go",
"javascript",
"markdown",
"python",
"rust"
])
);
}
}
20 changes: 19 additions & 1 deletion src/api/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ mod tests {
.is_some_and(Vec::is_empty));
}

#[test]
fn syntax_catalog_describes_javascript_and_jsx_nodes() {
let mut payload = load_catalog("syntax").expect("syntax catalog index should load");
filter_catalog("syntax", &mut payload, Some("javascript"))
.expect("JavaScript syntax catalog should load");

assert_eq!(payload["grammar_version"], "tree_sitter_javascript@0.25.0");
let nodes = payload["node_types"]
.as_array()
.expect("node types should be an array");
assert!(nodes
.iter()
.any(|node| node["type"] == "function_declaration"));
assert!(nodes.iter().any(|node| node["type"] == "jsx_element"));
}

#[test]
fn syntax_catalog_rejects_missing_and_unsupported_languages() {
let mut payload = load_catalog("syntax").expect("syntax catalog index should load");
Expand All @@ -205,6 +221,8 @@ mod tests {
);
assert!(filter_catalog("syntax", &mut payload, Some("custom"))
.unwrap_err()
.contains("Valid languages: c, cpp, css, fortran, go, markdown, python, rust"));
.contains(
"Valid languages: c, cpp, css, fortran, go, javascript, markdown, python, rust"
));
}
}
1 change: 1 addition & 0 deletions src/api/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ fn language_for_path(path: &Path) -> Option<&'static str> {
Some("rs") => Some("rust"),
Some("go") => Some("go"),
Some("css") => Some("css"),
Some("js") | Some("jsx") | Some("mjs") | Some("cjs") => Some("javascript"),
Some("c") | Some("h") => Some("c"),
Some("cc") | Some("cpp") | Some("cxx") | Some("hpp") | Some("hh") => Some("cpp"),
Some("f") | Some("f90") | Some("f95") | Some("for") => Some("fortran"),
Expand Down
21 changes: 21 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,27 @@ mod tests {
assert!(marked_captures(&output.root).is_empty());
}

#[test]
fn javascript_tree_sitter_parser_marks_profile_captures_and_jsx() {
let output = parse_source(
"import { value } from './dep.js';\nclass Worker { run() { value(); } }\nfunction Card() { return <section>Hello</section>; }\n",
&profile("javascript"),
)
.expect("JavaScript parsing should succeed");

let captures = marked_captures(&output.root);
assert!(captures
.iter()
.any(|(capture, _)| capture == "reference.import"));
assert!(captures.contains(&("definition.class".to_string(), "Worker".to_string())));
assert!(captures.contains(&("definition.method".to_string(), "run".to_string())));
assert!(captures.contains(&("definition.function".to_string(), "Card".to_string())));
assert!(captures.contains(&("reference.call".to_string(), "value".to_string())));
assert_eq!(output.root.node_type, "program");
assert!(output.diagnostics.is_empty());
assert!(contains_node_type(&output.root, "jsx_element"));
}

#[test]
fn c_tree_sitter_parser_marks_profile_captures() {
let output = parse_source(
Expand Down
6 changes: 6 additions & 0 deletions src/parser/tree_sitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ fn grammar_language(profile: &LanguageProfile) -> Option<Language> {
("tree_sitter_css", _) | (_, "css") => Some(tree_sitter_css::LANGUAGE.into()),
("tree_sitter_fortran", _) | (_, "fortran") => Some(tree_sitter_fortran::LANGUAGE.into()),
("tree_sitter_go", _) | (_, "go") => Some(tree_sitter_go::LANGUAGE.into()),
("tree_sitter_javascript", _) | (_, "javascript") => {
Some(tree_sitter_javascript::LANGUAGE.into())
}
("tree_sitter_python", _) | (_, "python") => Some(tree_sitter_python::LANGUAGE.into()),
("tree_sitter_rust", _) | (_, "rust") => Some(tree_sitter_rust::LANGUAGE.into()),
_ => None,
Expand All @@ -63,6 +66,9 @@ pub(super) fn grammar_node_types(profile: &LanguageProfile) -> Option<&'static s
("tree_sitter_css", _) | (_, "css") => Some(tree_sitter_css::NODE_TYPES),
("tree_sitter_fortran", _) | (_, "fortran") => Some(tree_sitter_fortran::NODE_TYPES),
("tree_sitter_go", _) | (_, "go") => Some(tree_sitter_go::NODE_TYPES),
("tree_sitter_javascript", _) | (_, "javascript") => {
Some(tree_sitter_javascript::NODE_TYPES)
}
("tree_sitter_python", _) | (_, "python") => Some(tree_sitter_python::NODE_TYPES),
("tree_sitter_rust", _) | (_, "rust") => Some(tree_sitter_rust::NODE_TYPES),
_ => None,
Expand Down
35 changes: 35 additions & 0 deletions src/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ pub(crate) fn built_in_profiles() -> Vec<LanguageProfile> {
root_node_types: vec!["stylesheet".to_string()],
capture_mappings: Vec::new(),
},
LanguageProfile {
language: "javascript".to_string(),
suffixes: vec![
".js".to_string(),
".jsx".to_string(),
".mjs".to_string(),
".cjs".to_string(),
],
grammar_package: "tree_sitter_javascript".to_string(),
grammar_version: "tree_sitter_javascript@0.25.0".to_string(),
root_node_types: vec!["program".to_string()],
capture_mappings: javascript_mappings(),
},
LanguageProfile {
language: "rust".to_string(),
suffixes: vec![".rs".to_string()],
Expand Down Expand Up @@ -177,6 +190,24 @@ pub(crate) fn built_in_profiles() -> Vec<LanguageProfile> {
]
}

fn javascript_mappings() -> Vec<CaptureMapping> {
vec![
mapping("definition.class", &["class_declaration"], "Class"),
mapping(
"definition.function",
&["function_declaration", "generator_function_declaration"],
"Function",
),
mapping("definition.method", &["method_definition"], "Method"),
mapping(
"reference.import",
&["import_statement"],
"ImportDeclaration",
),
mapping("reference.call", &["call_expression"], "CallExpression"),
]
}

fn c_family_mappings() -> Vec<CaptureMapping> {
vec![
mapping("definition.function", &["function_definition"], "Function"),
Expand Down Expand Up @@ -239,6 +270,10 @@ mod tests {
("README.md", "markdown"),
("README.mdx", "markdown"),
("styles.css", "css"),
("service.js", "javascript"),
("component.jsx", "javascript"),
("module.mjs", "javascript"),
("config.cjs", "javascript"),
("src/lib.rs", "rust"),
("main.go", "go"),
("service.c", "c"),
Expand Down