diff --git a/artifacts/v031-phase2-features.yaml b/artifacts/v031-phase2-features.yaml new file mode 100644 index 0000000..650dae1 --- /dev/null +++ b/artifacts/v031-phase2-features.yaml @@ -0,0 +1,157 @@ +# Features shipped in v0.3.1 second batch (2026-04-02) +artifacts: + # ── Rowan parser phases ────────────────────────────────────────────── + + - id: FEAT-093 + type: feature + title: Rowan YAML CST parser (Phase 1) + status: approved + description: > + Lossless, span-preserving YAML parser using rowan. 28 SyntaxKind + variants, hand-written lexer and recursive-descent parser with + indent tracking. Round-trip guarantee for all project YAML files. + links: + - type: satisfies + target: REQ-002 + + - id: FEAT-094 + type: feature + title: HIR extraction from rowan CST (Phase 2) + status: approved + description: > + Walks rowan CST to extract Vec with byte-accurate + spans for every field. Cross-validated against serde-based parser. + links: + - type: satisfies + target: REQ-002 + + - id: FEAT-095 + type: feature + title: Schema-driven YAML extraction (Phase 3) + status: approved + description: > + Uses yaml-section and shorthand-links schema metadata to parse + section-based YAML formats. One function replaces the 861-line + hardcoded STPA adapter. Handles generic and section formats. + links: + - type: satisfies + target: REQ-002 + - type: satisfies + target: REQ-010 + + # ── Domain schemas ───────────────────────────────────────────────── + + - id: FEAT-096 + type: feature + title: DO-178C airborne software schema + status: approved + description: > + 14 artifact types covering PSAC through SAS with DAL-based + traceability rules for aviation software certification. + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-097 + type: feature + title: EN 50128 railway software safety schema + status: approved + description: > + 14 artifact types with SIL-dependent rules, tool qualification, + independent assessment for railway software. + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-098 + type: feature + title: IEC 61508 functional safety schema + status: approved + description: > + 15 artifact types covering the full IEC 61508 V-model with + SIL-based traceability and independent assessment rules. + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-099 + type: feature + title: IEC 62304 medical device software schema + status: approved + description: > + 13 artifact types with class-conditional verification rules + for medical device software (Class A/B/C). + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-100 + type: feature + title: ISO/PAS 8800 AI safety lifecycle schema + status: approved + description: > + 12 artifact types for AI element safety in road vehicles. + Bridges to STPA-AI for ML controller traceability. + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-101 + type: feature + title: ISO 21448 SOTIF schema + status: approved + description: > + 8 artifact types covering functional insufficiency hazards, + triggering conditions, and known/unknown unsafe scenarios. + links: + - type: satisfies + target: REQ-010 + + # ── Other features ───────────────────────────────────────────────── + + - id: FEAT-102 + type: feature + title: MCP server expanded to 9 tools + status: approved + description: > + Added rivet_get, rivet_coverage, rivet_schema, rivet_embed, + rivet_snapshot_capture, rivet_add to the MCP server with + proper JSON Schema inputSchema definitions. + links: + - type: satisfies + target: REQ-001 + + - id: FEAT-103 + type: feature + title: rivet schema validate command + status: approved + description: > + Validates loaded schemas are well-formed: checks link types + exist, target types exist, traceability rule references valid. + Found 3 real errors in AADL cross-schema references. + links: + - type: satisfies + target: REQ-010 + + - id: FEAT-104 + type: feature + title: Documentation refresh with 7 new topics + status: approved + description: > + New docs: embed-syntax, schemas-overview, schema/eu-ai-act, + schema/safety-case, schema/stpa-ai, schema/stpa-sec, + schema/research. Updated CLI and YAML references. + links: + - type: satisfies + target: REQ-001 + + - id: FEAT-105 + type: feature + title: Pre-commit hook for formatting and clippy + status: approved + description: > + scripts/pre-commit runs cargo fmt and clippy before every commit. + Prevents CI format failures. + links: + - type: satisfies + target: REQ-001 diff --git a/rivet-cli/src/main.rs b/rivet-cli/src/main.rs index 3086676..8d449ea 100644 --- a/rivet-cli/src/main.rs +++ b/rivet-cli/src/main.rs @@ -6540,7 +6540,7 @@ fn cmd_lsp(cli: &Cli) -> Result { }; // Build supplementary state for rendering - let store = db.store(source_set); + let store = db.store(source_set, schema_set); let render_schema = db.schema(schema_set); let mut render_graph = rivet_core::links::LinkGraph::build(&store, &render_schema); @@ -6617,7 +6617,7 @@ fn cmd_lsp(cli: &Cli) -> Result { match method { "textDocument/hover" => { let params: HoverParams = serde_json::from_value(req.params.clone())?; - let store = db.store(source_set); + let store = db.store(source_set, schema_set); let result = lsp_hover(¶ms, &store); connection.sender.send(Message::Response(Response { id: req.id, @@ -6628,7 +6628,7 @@ fn cmd_lsp(cli: &Cli) -> Result { "textDocument/definition" => { let params: GotoDefinitionParams = serde_json::from_value(req.params.clone())?; - let store = db.store(source_set); + let store = db.store(source_set, schema_set); let result = lsp_goto_definition(¶ms, &store); connection.sender.send(Message::Response(Response { id: req.id, @@ -6638,7 +6638,7 @@ fn cmd_lsp(cli: &Cli) -> Result { } "textDocument/completion" => { let params: CompletionParams = serde_json::from_value(req.params.clone())?; - let store = db.store(source_set); + let store = db.store(source_set, schema_set); let schema = db.schema(schema_set); let result = lsp_completion(¶ms, &store, &schema); connection.sender.send(Message::Response(Response { @@ -6923,7 +6923,7 @@ fn cmd_lsp(cli: &Cli) -> Result { // and append document [[ID]] reference validation so // broken wiki-links in markdown files are reported. let mut new_diagnostics = db.diagnostics(source_set, schema_set); - let new_store = db.store(source_set); + let new_store = db.store(source_set, schema_set); new_diagnostics .extend(validate::validate_documents(&doc_store, &new_store)); lsp_publish_salsa_diagnostics( @@ -6939,7 +6939,7 @@ fn cmd_lsp(cli: &Cli) -> Result { ); // Rebuild render state - render_store = db.store(source_set); + render_store = db.store(source_set, schema_set); let render_schema = db.schema(schema_set); render_graph = rivet_core::links::LinkGraph::build( &render_store, @@ -6986,7 +6986,7 @@ fn cmd_lsp(cli: &Cli) -> Result { // including document [[ID]] reference validation. let mut diagnostics = db.diagnostics(source_set, schema_set); - let store = db.store(source_set); + let store = db.store(source_set, schema_set); diagnostics.extend(validate::validate_documents( &doc_store, &store, )); diff --git a/rivet-core/Cargo.toml b/rivet-core/Cargo.toml index f42899d..001cea2 100644 --- a/rivet-core/Cargo.toml +++ b/rivet-core/Cargo.toml @@ -9,7 +9,8 @@ rust-version.workspace = true [features] -default = ["aadl"] +default = ["aadl", "rowan-yaml"] +rowan-yaml = [] oslc = ["dep:reqwest", "dep:urlencoding"] wasm = ["dep:wasmtime", "dep:wasmtime-wasi"] aadl = ["dep:spar-hir", "dep:spar-analysis"] diff --git a/rivet-core/src/db.rs b/rivet-core/src/db.rs index f5b33b7..adcbc67 100644 --- a/rivet-core/src/db.rs +++ b/rivet-core/src/db.rs @@ -21,6 +21,8 @@ use crate::model::Artifact; use crate::schema::{Schema, SchemaFile}; use crate::store::Store; use crate::validate::Diagnostic; +#[cfg(feature = "rowan-yaml")] +use crate::yaml_hir; // ── Salsa inputs ──────────────────────────────────────────────────────── @@ -109,6 +111,30 @@ pub fn parse_artifacts(db: &dyn salsa::Database, source: SourceFile) -> Vec Vec { + let content = source.content(db); + let path = source.path(db); + let source_path = std::path::Path::new(&path); + + let schema = build_schema(db, schema_set); + let parsed = yaml_hir::extract_schema_driven(&content, &schema, Some(source_path)); + + parsed.artifacts.into_iter().map(|sa| sa.artifact).collect() +} + /// Collect parse errors from all source files as diagnostics. /// /// Each file that fails to parse produces a `yaml-parse-error` diagnostic @@ -234,9 +260,9 @@ pub fn validate_all( /// /// Both `validate_all` and this function call `build_pipeline`, which is a /// plain (non-tracked) helper. The tracked functions that `build_pipeline` -/// delegates to (`parse_artifacts`) are individually cached by salsa, so -/// the repeated calls do NOT re-parse source files — only the lightweight -/// store/schema assembly runs twice. +/// delegates to (`parse_artifacts` / `parse_artifacts_v2`) are individually +/// cached by salsa, so the repeated calls do NOT re-parse source files — +/// only the lightweight store/schema assembly runs twice. #[salsa::tracked] pub fn evaluate_conditional_rules( db: &dyn salsa::Database, @@ -280,18 +306,53 @@ fn build_pipeline( source_set: SourceFileSet, schema_set: SchemaInputSet, ) -> (Store, Schema, LinkGraph) { - let store = build_store(db, source_set); + let store = build_store(db, source_set, schema_set); let schema = build_schema(db, schema_set); let graph = LinkGraph::build(&store, &schema); (store, schema, graph) } /// Build an artifact `Store` from all source file inputs. -fn build_store(db: &dyn salsa::Database, source_set: SourceFileSet) -> Store { +/// +/// When the `rowan-yaml` feature is enabled, uses the schema-driven rowan +/// parser (`parse_artifacts_v2`) which reads `yaml-section` metadata from +/// the schema. In debug builds, both parsers run and their output is +/// compared as a cross-check. +fn build_store( + db: &dyn salsa::Database, + source_set: SourceFileSet, + schema_set: SchemaInputSet, +) -> Store { + #[cfg(not(feature = "rowan-yaml"))] + let _ = schema_set; + let sources = source_set.files(db); let mut store = Store::new(); for source in sources { - for artifact in parse_artifacts(db, source) { + #[cfg(feature = "rowan-yaml")] + let artifacts = { + let new_arts = parse_artifacts_v2(db, source, schema_set); + + #[cfg(debug_assertions)] + { + let old_arts = parse_artifacts(db, source); + let new_ids: Vec<&str> = new_arts.iter().map(|a| a.id.as_str()).collect(); + let old_ids: Vec<&str> = old_arts.iter().map(|a| a.id.as_str()).collect(); + if old_ids != new_ids { + log::warn!( + "parser mismatch for {}: old={old_ids:?} new={new_ids:?}", + source.path(db), + ); + } + } + + new_arts + }; + + #[cfg(not(feature = "rowan-yaml"))] + let artifacts = parse_artifacts(db, source); + + for artifact in artifacts { // Use upsert to avoid panics on duplicate IDs across files. store.upsert(artifact); } @@ -378,9 +439,9 @@ impl RivetDatabase { false } - /// Get the current store (computed from source inputs). - pub fn store(&self, source_set: SourceFileSet) -> Store { - build_store(self, source_set) + /// Get the current store (computed from source and schema inputs). + pub fn store(&self, source_set: SourceFileSet, schema_set: SchemaInputSet) -> Store { + build_store(self, source_set, schema_set) } /// Get the current merged schema (computed from schema inputs). @@ -618,9 +679,10 @@ artifacts: fn adding_artifact_appears_in_store() { let mut db = RivetDatabase::new(); let sources = db.load_sources(&[("reqs.yaml", SOURCE_REQ)]); + let schemas = db.load_schemas(&[("test", TEST_SCHEMA)]); // Initially: 1 artifact (REQ-001). - let store = db.store(sources); + let store = db.store(sources, schemas); assert_eq!(store.len(), 1); assert!(store.contains("REQ-001")); @@ -636,7 +698,7 @@ artifacts: "#; db.update_source(sources, "reqs.yaml", combined.to_string()); - let store = db.store(sources); + let store = db.store(sources, schemas); assert_eq!(store.len(), 2); assert!(store.contains("REQ-001")); assert!(store.contains("REQ-002"));