use crate::cli::GraphExportFormat;
use crate::entity_type::EntityType;
use serde::Serialize;
use std::path::PathBuf;
#[derive(clap::Subcommand)]
pub enum GraphSubcommand {
Traverse(GraphTraverseArgs),
Stats(GraphStatsArgs),
Entities(GraphEntitiesArgs),
RecomputeDegree(GraphRecomputeDegreeArgs),
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphTraverseFormat {
Json,
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphStatsFormat {
Json,
Text,
}
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# Export full entity snapshot as JSON (default)\n \
sqlite-graphrag graph\n\n \
# Traverse relationships from a starting entity\n \
sqlite-graphrag graph traverse --from acme-corp --depth 2\n\n \
# Show graph statistics as structured JSON\n \
sqlite-graphrag graph stats --format json\n\n \
# List entities filtered by type\n \
sqlite-graphrag graph entities --entity-type person\n\n \
# Export full snapshot in DOT format for Graphviz\n \
sqlite-graphrag graph --format dot --output graph.dot\n\n \
NOTES:\n \
Without a subcommand, exports the full entity+edge snapshot.\n \
Use `traverse`, `stats`, or `entities` for targeted queries.")]
pub struct GraphArgs {
#[command(subcommand)]
pub subcommand: Option<GraphSubcommand>,
#[arg(long)]
pub namespace: Option<String>,
#[arg(long, value_enum, default_value = "json")]
pub format: GraphExportFormat,
#[arg(long)]
pub output: Option<PathBuf>,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
#[arg(long)]
pub db: Option<String>,
}
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# Traverse relationships from an entity with default depth (2)\n \
sqlite-graphrag graph traverse --from acme-corp\n\n \
# Increase traversal depth to 3 hops\n \
sqlite-graphrag graph traverse --from acme-corp --depth 3\n\n \
# Traverse within a specific namespace\n \
sqlite-graphrag graph traverse --from acme-corp --namespace project-x\n\n \
NOTES:\n \
Output is always JSON. The `hops` array contains each reachable entity\n \
with its relation, direction (inbound/outbound), weight, and depth level.\n \
Short nicknames (e.g. `danilo` vs `danilo-aguiar-teixeira`) do not exact-match;\n \
without `--fuzzy` the error includes ranked suggestions (v1.1.05). With\n \
`--fuzzy`, a clear single winner is auto-resolved and warned on stderr.")]
pub struct GraphTraverseArgs {
#[arg(long)]
pub from: String,
#[arg(long, default_value_t = 2u32)]
pub depth: u32,
#[arg(long, default_value_t = false)]
pub fuzzy: bool,
#[arg(long)]
pub namespace: Option<String>,
#[arg(long, value_enum, default_value = "json")]
pub format: GraphTraverseFormat,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
#[arg(long)]
pub db: Option<String>,
}
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# Show stats for all namespaces (human-readable text)\n \
sqlite-graphrag graph stats --format text\n\n \
# Show stats as structured JSON\n \
sqlite-graphrag graph stats --format json\n\n \
# Show stats for a specific namespace\n \
sqlite-graphrag graph stats --namespace project-x --format text\n\n \
NOTES:\n \
Reports node_count, edge_count, avg_degree, and max_degree.\n \
Default format is JSON. Use `--format text` for a compact single-line summary.")]
pub struct GraphStatsArgs {
#[arg(long)]
pub namespace: Option<String>,
#[arg(long, value_enum, default_value = "json")]
pub format: GraphStatsFormat,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
#[arg(long)]
pub db: Option<String>,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum EntitySortField {
Name,
Degree,
CreatedAt,
}
#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
pub enum SortOrder {
#[default]
Asc,
Desc,
}
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# List all entities (default limit applies)\n \
sqlite-graphrag graph entities\n\n \
# Filter by entity type\n \
sqlite-graphrag graph entities --entity-type person\n\n \
# Filter by namespace and type\n \
sqlite-graphrag graph entities --namespace project-x --entity-type concept\n\n \
# Paginate results (skip first 20, return next 10)\n \
sqlite-graphrag graph entities --offset 20 --limit 10\n\n \
# Sort by degree descending (most connected first)\n \
sqlite-graphrag graph entities --sort-by degree --order desc\n\n \
# Sort by creation date ascending\n \
sqlite-graphrag graph entities --sort-by created-at --order asc\n\n \
NOTES:\n \
Output is always JSON with `entities`, `total_count`, `limit`, and `offset` fields.\n \
Entity types are canonical strings (e.g. `person`, `organization`, `location`).")]
pub struct GraphEntitiesArgs {
#[arg(long)]
pub namespace: Option<String>,
#[arg(long, value_enum)]
pub entity_type: Option<EntityType>,
#[arg(long, default_value_t = crate::constants::K_GRAPH_ENTITIES_DEFAULT_LIMIT)]
pub limit: usize,
#[arg(long, default_value_t = 0usize)]
pub offset: usize,
#[arg(long, value_enum, help = "Sort entities by field")]
pub sort_by: Option<EntitySortField>,
#[arg(long, value_enum, default_value_t = SortOrder::Asc, help = "Sort order")]
pub order: SortOrder,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
#[arg(long)]
pub db: Option<String>,
}
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# Preview divergences without writing (recommended first run)\n \
sqlite-graphrag graph recompute-degree --dry-run\n\n \
# Reconcile every namespace\n \
sqlite-graphrag graph recompute-degree\n\n \
# Reconcile a single namespace\n \
sqlite-graphrag graph recompute-degree --namespace project-x\n\n\
NOTES:\n \
`entities.degree` is a derived cache (incremented on link, recalculated\n \
on merge/delete) that drifts when edges are written by paths that skip\n \
the recalculation. This command recomputes every entity's degree from\n \
the real `relationships` rows (same semantics as the canonical\n \
`recalculate_degree` helper: COUNT(*) WHERE source_id = id OR\n \
target_id = id) inside one transaction. Entities left with zero edges\n \
are zeroed. Envelope: {total, updated, zeroed, unchanged}.")]
pub struct GraphRecomputeDegreeArgs {
#[arg(long)]
pub namespace: Option<String>,
#[arg(long)]
pub dry_run: bool,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
#[arg(long)]
pub db: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct TraverseHop {
pub(crate) entity: String,
pub(crate) relation: String,
pub(crate) direction: String,
pub(crate) weight: f64,
pub(crate) depth: u32,
}
#[derive(Serialize)]
pub(crate) struct GraphTraverseResponse {
pub(crate) from: String,
pub(crate) namespace: String,
pub(crate) depth: u32,
pub(crate) hops: Vec<TraverseHop>,
pub(crate) elapsed_ms: u64,
}
#[derive(Serialize)]
pub(crate) struct GraphStatsResponse {
pub(crate) namespace: Option<String>,
pub(crate) node_count: i64,
pub(crate) edge_count: i64,
pub(crate) avg_degree: f64,
pub(crate) max_degree: i64,
pub(crate) elapsed_ms: u64,
}
#[derive(Serialize)]
pub(crate) struct EntityItem {
pub(crate) id: i64,
pub(crate) name: String,
pub(crate) entity_type: String,
pub(crate) namespace: String,
pub(crate) created_at: String,
pub(crate) degree: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) description: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct GraphEntitiesResponse {
pub(crate) entities: Vec<EntityItem>,
pub(crate) total_count: i64,
pub(crate) limit: usize,
pub(crate) offset: usize,
pub(crate) namespace: Option<String>,
pub(crate) elapsed_ms: u64,
}