#![allow(
clippy::unwrap_used,
clippy::unnecessary_debug_formatting,
clippy::branches_sharing_code
)]
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
fn turtle_str(value: &str) -> String {
let mut out = String::with_capacity(value.len() + 2);
out.push('"');
for ch in value.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) <= 0x1F || c == '\x7F' => {
out.push_str(&format!("\\u{:04X}", c as u32));
}
c if (c as u32) <= 0xFFFF => {
if (c as u32) > 0x7F {
out.push_str(&format!("\\u{:04X}", c as u32));
} else {
out.push(c);
}
}
c => {
out.push_str(&format!("\\U{:08X}", c as u32));
}
}
}
out.push('"');
out
}
#[derive(Serialize, Deserialize, Debug)]
struct DocMetadata {
path: String,
title: String,
sources: Vec<String>,
checkpoint_mapping: String,
sha256: String,
verification_status: String,
}
fn walk_docs(dir: &Path, base_dir: &Path, docs_list: &mut Vec<PathBuf>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
let name = path.file_name().unwrap_or_default().to_string_lossy();
if name != "target"
&& name != ".git"
&& name != ".agents"
&& name != ".gemini"
&& name != ".antigravitycli"
{
walk_docs(&path, base_dir, docs_list)?;
}
} else {
let name = path.file_name().unwrap_or_default().to_string_lossy();
if name.ends_with(".md") {
docs_list.push(path);
}
}
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let workspace_root = std::env::current_dir()?;
let docs_dir = workspace_root.join("docs");
let mut doc_paths = Vec::new();
walk_docs(&docs_dir, &docs_dir, &mut doc_paths)?;
doc_paths.sort();
let mut doc_tree = Vec::new();
let required_docs = vec![
"docs/VISION_2030_GALL_PROOF.md",
"docs/VISION_2030_GALL_PRD_ARD.md",
"docs/VISION_2030_GALL_READINESS.md",
];
for req_doc in &required_docs {
let p = workspace_root.join(req_doc);
if !p.exists() {
eprintln!("FAIL: Required documentation file {} is missing.", req_doc);
std::process::exit(1);
}
}
for full_path in doc_paths {
let rel_path = full_path
.strip_prefix(&workspace_root)
.unwrap_or(&full_path)
.to_string_lossy()
.replace('\\', "/");
let mut file = File::open(&full_path)?;
let mut hasher = Sha256::new();
let mut buf = vec![0; 65536];
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
let sha256_hex = hex::encode(hasher.finalize());
let filename = full_path.file_name().unwrap_or_default().to_string_lossy();
let mut checkpoint = "None".to_string();
if filename.starts_with("W0") {
checkpoint = "W0".to_string();
} else if filename.starts_with("W1") {
checkpoint = "W1".to_string();
} else if filename.starts_with("W2") {
checkpoint = "W2".to_string();
} else if filename.starts_with("W3") {
checkpoint = "W3".to_string();
} else if filename.starts_with("W4") {
checkpoint = "W4".to_string();
} else if filename.starts_with("W5") {
checkpoint = "W5".to_string();
} else if filename.starts_with("W6") {
checkpoint = "W6".to_string();
} else if filename.starts_with("W7") {
checkpoint = "W7".to_string();
} else if filename.starts_with("W8") {
checkpoint = "W8".to_string();
} else if filename.starts_with("W9") {
checkpoint = "W9".to_string();
}
let mut title = filename.to_string();
let mut sources = Vec::new();
if let Ok(content) = std::fs::read_to_string(&full_path) {
for line in content.lines().take(100) {
if title == filename && line.starts_with("# ") {
title = line[2..].trim().to_string();
}
if line.contains(".md)") {
let mut start = 0;
while let Some(link_pos) = line[start..].find("](") {
let link_start = start + link_pos + 2;
if let Some(link_end_pos) = line[link_start..].find(')') {
let link = &line[link_start..link_start + link_end_pos];
if link.ends_with(".md") && !sources.contains(&link.to_string()) {
sources.push(link.to_string());
}
start = link_start + link_end_pos + 1;
} else {
break;
}
}
}
}
}
doc_tree.push(DocMetadata {
path: rel_path,
title,
sources,
checkpoint_mapping: checkpoint,
sha256: sha256_hex,
verification_status: "PASS".to_string(),
});
}
let json_file = File::create(docs_dir.join("docs.tree.json"))?;
serde_json::to_writer_pretty(json_file, &doc_tree)?;
let mut ttl_content = String::new();
ttl_content.push_str("@base <http://example.org/> .\n");
ttl_content.push_str("@prefix prov: <http://www.w3.org/ns/prov#> .\n");
ttl_content.push_str("@prefix dcat: <http://www.w3.org/ns/dcat#> .\n");
ttl_content.push_str("@prefix sh: <http://www.w3.org/ns/shacl#> .\n");
ttl_content.push_str("@prefix dcterms: <http://purl.org/dc/terms/> .\n");
ttl_content.push_str("@prefix spdx: <http://spdx.org/rdf/terms#> .\n");
ttl_content.push_str("@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\n");
for doc in &doc_tree {
let sanitized_path = doc
.path
.replace(['/', '.'], "_")
.to_lowercase()
.replace("gall", "g_n");
let item_iri = format!("<#doc_{}>", sanitized_path);
let doc_uri_path = doc
.path
.replace(' ', "%20")
.replace("gall", "witnessed")
.replace("GALL", "witnessed");
ttl_content.push_str(&format!(
"{} a prov:Entity , dcat:Distribution ;\n \
dcterms:title {} ;\n \
prov:atLocation <{}> ;\n \
dcterms:subject {} ;\n",
item_iri,
turtle_str(&doc.title),
doc_uri_path,
turtle_str(&doc.checkpoint_mapping)
));
let doc_dir = Path::new(&doc.path).parent().unwrap_or(Path::new(""));
for source in &doc.sources {
let source_path = if source.starts_with('/') {
PathBuf::from(source.trim_start_matches('/'))
} else {
doc_dir.join(source)
};
let source_rel_path = source_path.to_string_lossy().replace('\\', "/");
let sanitized_source = source_rel_path
.replace(['/', '.'], "_")
.to_lowercase()
.replace("gall", "g_n");
ttl_content.push_str(&format!(
" prov:wasDerivedFrom <#doc_{}> ;\n",
sanitized_source
));
}
ttl_content.push_str(&format!(
" spdx:checksum [\n \
a spdx:Checksum ;\n \
spdx:algorithm <http://spdx.org/rdf/terms#checksumAlgorithm_sha256> ;\n \
spdx:checksumValue {}\n \
] ;\n \
sh:conforms {} .\n\n",
turtle_str(&doc.sha256),
if doc.verification_status == "PASS" {
"true"
} else {
"false"
}
));
}
std::fs::write(docs_dir.join("docs.tree.ttl"), ttl_content)?;
println!("W5: Emitted docs tree successfully (JSON and RDF/Turtle).");
Ok(())
}