use serde::Serialize;
use crate::{
api::{ApiMetadata, RequestContext},
clock::system_now_millis_or_zero,
domain::{
DirectoryLoadHint, DirectoryUpdateRule, KnowledgeMap, KnowledgeMapHistoryEntry,
KnowledgeMapRoute, KnowledgeMapSource, KnowledgeMapSourceKind, KnowledgeMapTopic,
RepositoryMapDirectory, RepositoryMapType,
},
};
use super::artifact::ARTIFACT_SCHEMA_VERSION;
pub(super) struct MutableKnowledgeMap {
pub(super) map_type: RepositoryMapType,
pub(super) directories: Vec<RepositoryMapDirectory>,
pub(super) map: KnowledgeMap,
pub(super) omitted_through: u64,
pub(super) requires_publish: bool,
pub(super) legacy_glossary_uri_normalized: bool,
}
impl MutableKnowledgeMap {
pub(super) fn initial(map_type: RepositoryMapType, updated_at: String) -> Self {
Self {
map: match map_type {
RepositoryMapType::Knowledge => KnowledgeMap::initial(updated_at),
RepositoryMapType::Codespec => KnowledgeMap::empty(updated_at),
},
map_type,
directories: baseline_directories(map_type),
omitted_through: 0,
requires_publish: false,
legacy_glossary_uri_normalized: false,
}
}
pub(super) fn record_required_publication(
&mut self,
source_schema_version: u16,
updated_at: String,
) -> String {
let glossary_only =
source_schema_version == ARTIFACT_SCHEMA_VERSION && self.legacy_glossary_uri_normalized;
let (action, history_summary, response_summary) = if glossary_only {
(
"source.migrate",
"Migrated the reserved business glossary source URI to the canonical artifact.",
"migrated Knowledge Map legacy glossary URI to the canonical artifact",
)
} else {
(
"history.compact",
"Migrated repository map to bounded recent-only history storage.",
"migrated repository map to schema v4 recent-only history",
)
};
self.map
.record_change(action, history_summary.to_owned(), updated_at);
response_summary.to_owned()
}
}
pub(super) fn baseline_directories(map_type: RepositoryMapType) -> Vec<RepositoryMapDirectory> {
map_type
.required_directories()
.iter()
.map(|directory| RepositoryMapDirectory {
directory: (*directory).to_owned(),
purpose: baseline_purpose(map_type, directory).to_owned(),
content_scope: vec![format!("{}/{directory}/**", map_type.as_str())],
key_files: vec![format!("{}/{directory}/README.md", map_type.as_str())],
load_hint: DirectoryLoadHint::OnDemand,
relations: Vec::new(),
update_rule: DirectoryUpdateRule::Reviewed,
})
.collect()
}
fn baseline_purpose(map_type: RepositoryMapType, directory: &str) -> &'static str {
match (map_type, directory) {
(RepositoryMapType::Codespec, "requirements") => {
"Product requirements and acceptance criteria."
}
(RepositoryMapType::Codespec, "design") => {
"Architecture and implementation design records."
}
(RepositoryMapType::Codespec, "api") => "Public interface and schema contracts.",
(RepositoryMapType::Codespec, "test") => "Verification strategy, fixtures, and evidence.",
(RepositoryMapType::Codespec, "decisions") => "Durable architecture and product decisions.",
(RepositoryMapType::Knowledge, "domain") => "Domain concepts, models, and business rules.",
(RepositoryMapType::Knowledge, "guides") => "Task-oriented repository knowledge guides.",
(RepositoryMapType::Knowledge, "ops") => "Operational procedures and diagnostics.",
(RepositoryMapType::Knowledge, "glossary") => {
"Business terminology, aliases, and technical mappings."
}
(RepositoryMapType::Knowledge, "best-practices") => {
"Reviewed engineering and knowledge-management practices."
}
_ => "Repository navigation knowledge.",
}
}
pub(super) fn metadata(context: &RequestContext) -> ApiMetadata {
ApiMetadata::graph_only(context, crate::domain::GraphVersion::ZERO)
}
pub(super) fn now_stamp() -> String {
let seconds = system_now_millis_or_zero() / 1_000;
format!("unix:{seconds}")
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KnowledgeMapSourceAddRequest {
pub id: String,
pub topic: String,
pub kind: KnowledgeMapSourceKind,
pub uri: String,
pub source_scope: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapMutationResponse {
pub metadata: ApiMetadata,
pub path: String,
pub map_type: RepositoryMapType,
pub map_version: u64,
pub summary: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapShowResponse {
pub metadata: ApiMetadata,
pub path: String,
pub map_type: RepositoryMapType,
pub map: KnowledgeMapView,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapView {
pub artifact_schema_version: u16,
pub map_version: u64,
pub updated_at: String,
pub directories: Vec<RepositoryMapDirectory>,
pub topics: Vec<KnowledgeMapTopic>,
pub sources: Vec<KnowledgeMapSource>,
pub routes: Vec<KnowledgeMapRoute>,
pub history: KnowledgeMapHistoryWindow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapHistoryWindow {
pub omitted_through: u64,
pub complete: bool,
pub recent: Vec<KnowledgeMapHistoryEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapHistoryResponse {
pub metadata: ApiMetadata,
pub path: String,
pub map_type: RepositoryMapType,
pub map_version: u64,
pub omitted_through: u64,
pub earliest_available_version: u64,
pub from_version: u64,
pub through_version: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_from_version: Option<u64>,
pub entries: Vec<KnowledgeMapHistoryEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapRouteResponse {
pub metadata: ApiMetadata,
pub path: String,
pub map_type: RepositoryMapType,
pub topic: String,
pub route: Option<KnowledgeMapRoute>,
pub sources: Vec<KnowledgeMapSource>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapValidationResponse {
pub metadata: ApiMetadata,
pub path: String,
pub map_type: RepositoryMapType,
pub valid: bool,
pub diagnostics: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KnowledgeMapAgentSnippetResponse {
pub metadata: ApiMetadata,
pub snippet: String,
}