1use crate::cli::GraphExportFormat;
4use serde::Serialize;
5use std::path::PathBuf;
6
7#[derive(clap::Subcommand)]
10pub enum GraphSubcommand {
11 Traverse(GraphTraverseArgs),
13 Stats(GraphStatsArgs),
15 Entities(GraphEntitiesArgs),
17 EntityTypes(GraphEntityTypesArgs),
19 RecomputeDegree(GraphRecomputeDegreeArgs),
21}
22
23#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
25pub enum GraphTraverseFormat {
26 Json,
28}
29
30#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
32pub enum GraphStatsFormat {
33 Json,
35 Text,
37}
38
39#[derive(clap::Args)]
40#[command(after_long_help = "EXAMPLES:\n \
41 # Export full entity snapshot as JSON (default)\n \
42 sqlite-graphrag graph\n\n \
43 # Traverse relationships from a starting entity\n \
44 sqlite-graphrag graph traverse --from acme-corp --depth 2\n\n \
45 # Show graph statistics as structured JSON\n \
46 sqlite-graphrag graph stats --format json\n\n \
47 # List entities filtered by type\n \
48 sqlite-graphrag graph entities --entity-type person\n\n \
49 # Export full snapshot in DOT format for Graphviz\n \
50 sqlite-graphrag graph --format dot --output graph.dot\n\n \
51NOTES:\n \
52 Without a subcommand, exports the full entity+edge snapshot.\n \
53 Use `traverse`, `stats`, or `entities` for targeted queries.")]
54pub struct GraphArgs {
56 #[command(subcommand)]
58 pub subcommand: Option<GraphSubcommand>,
59 #[arg(long)]
61 pub namespace: Option<String>,
62 #[arg(long, value_enum, default_value = "json")]
64 pub format: GraphExportFormat,
65 #[arg(long)]
67 pub output: Option<PathBuf>,
68 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
70 pub json: bool,
71 #[arg(long)]
73 pub db: Option<String>,
74}
75
76#[derive(clap::Args)]
77#[command(after_long_help = "EXAMPLES:\n \
78 # Traverse relationships from an entity with default depth (2)\n \
79 sqlite-graphrag graph traverse --from acme-corp\n\n \
80 # Increase traversal depth to 3 hops\n \
81 sqlite-graphrag graph traverse --from acme-corp --depth 3\n\n \
82 # Traverse within a specific namespace\n \
83 sqlite-graphrag graph traverse --from acme-corp --namespace project-x\n\n \
84NOTES:\n \
85 Output is always JSON. The `hops` array contains each reachable entity\n \
86 with its relation, direction (inbound/outbound), weight, and depth level.\n \
87 Short nicknames (e.g. `alice` vs `alice-martins-souza`) do not exact-match;\n \
88 without `--fuzzy` the error includes ranked suggestions (v1.1.05). With\n \
89 `--fuzzy`, a clear single winner is auto-resolved and warned on stderr.")]
90pub struct GraphTraverseArgs {
92 #[arg(long)]
94 pub from: String,
95 #[arg(long, default_value_t = 2u32, value_parser = crate::parsers::parse_hops_range_u32)]
97 pub depth: u32,
98 #[arg(long, default_value_t = false)]
102 pub fuzzy: bool,
103 #[arg(long)]
105 pub namespace: Option<String>,
106 #[arg(long, value_enum, default_value = "json")]
108 pub format: GraphTraverseFormat,
109 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
111 pub json: bool,
112 #[arg(long)]
114 pub db: Option<String>,
115}
116
117#[derive(clap::Args)]
118#[command(after_long_help = "EXAMPLES:\n \
119 # Show stats for all namespaces (human-readable text)\n \
120 sqlite-graphrag graph stats --format text\n\n \
121 # Show stats as structured JSON\n \
122 sqlite-graphrag graph stats --format json\n\n \
123 # Show stats for a specific namespace\n \
124 sqlite-graphrag graph stats --namespace project-x --format text\n\n \
125NOTES:\n \
126 Reports node_count, edge_count, avg_degree, and max_degree.\n \
127 Default format is JSON. Use `--format text` for a compact single-line summary.")]
128pub struct GraphStatsArgs {
130 #[arg(long)]
132 pub namespace: Option<String>,
133 #[arg(long, value_enum, default_value = "json")]
135 pub format: GraphStatsFormat,
136 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
138 pub json: bool,
139 #[arg(long)]
141 pub db: Option<String>,
142}
143
144#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
146pub enum GraphEntityTypesFormat {
147 Json,
149 Text,
151}
152
153#[derive(clap::Args)]
154#[command(after_long_help = "EXAMPLES:\n \
155 # List every entity type present, most frequent first\n \
156 sqlite-graphrag graph entity-types\n\n \
157 # Restrict the audit to one namespace\n \
158 sqlite-graphrag graph entity-types --namespace project-x\n\n \
159 # Compact human-readable summary\n \
160 sqlite-graphrag graph entity-types --format text\n\n \
161 # Only the labels outside the canonical set\n \
162 sqlite-graphrag graph entity-types --filter canonical=false\n\n\
163NOTES:\n \
164 v1.2.8 opened the entity-type vocabulary, so the set of stored labels is\n \
165 no longer knowable from the source. This reports what the database\n \
166 actually holds: each `type` with its `count` and whether it belongs to\n \
167 the canonical thirteen. `--entity-type` on `graph entities` can only\n \
168 filter by a label you already guessed; this is where you find the label.")]
169pub struct GraphEntityTypesArgs {
171 #[arg(long)]
173 pub namespace: Option<String>,
174 #[arg(long, value_enum, default_value = "json")]
176 pub format: GraphEntityTypesFormat,
177 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
179 pub json: bool,
180 #[arg(long)]
182 pub db: Option<String>,
183}
184
185#[derive(Serialize)]
187pub(crate) struct EntityTypeCount {
188 #[serde(rename = "type")]
190 pub(crate) entity_type: String,
191 pub(crate) count: i64,
193 pub(crate) canonical: bool,
196}
197
198#[derive(Serialize)]
199pub(crate) struct GraphEntityTypesResponse {
200 pub(crate) types: Vec<EntityTypeCount>,
201 pub(crate) total_types: usize,
204 pub(crate) total_entities: i64,
206 pub(crate) namespace: Option<String>,
207 pub(crate) elapsed_ms: u64,
208}
209
210#[derive(Debug, Clone, Copy, clap::ValueEnum)]
212pub enum EntitySortField {
213 Name,
215 Degree,
217 CreatedAt,
219}
220
221#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
223pub enum SortOrder {
224 #[default]
226 Asc,
227 Desc,
229}
230
231#[derive(clap::Args)]
232#[command(after_long_help = "EXAMPLES:\n \
233 # List all entities (default limit applies)\n \
234 sqlite-graphrag graph entities\n\n \
235 # Filter by entity type\n \
236 sqlite-graphrag graph entities --entity-type person\n\n \
237 # Filter by namespace and type\n \
238 sqlite-graphrag graph entities --namespace project-x --entity-type concept\n\n \
239 # Paginate results (skip first 20, return next 10)\n \
240 sqlite-graphrag graph entities --offset 20 --limit 10\n\n \
241 # Sort by degree descending (most connected first)\n \
242 sqlite-graphrag graph entities --sort-by degree --order desc\n\n \
243 # Sort by creation date ascending\n \
244 sqlite-graphrag graph entities --sort-by created-at --order asc\n\n \
245NOTES:\n \
246 Output is always JSON with `entities`, `total_count`, `limit`, and `offset` fields.\n \
247 Entity types are free-form strings; the canonical ones (e.g. `person`,\n \
248 `organization`, `location`) are recommended rather than exhaustive.")]
249pub struct GraphEntitiesArgs {
251 #[arg(long)]
253 pub namespace: Option<String>,
254 #[arg(long, value_name = "TYPE")]
259 pub entity_type: Option<String>,
260 #[arg(long, default_value_t = crate::constants::K_GRAPH_ENTITIES_DEFAULT_LIMIT, value_parser = crate::parsers::parse_k_range)]
262 pub limit: usize,
263 #[arg(long, default_value_t = 0usize)]
265 pub offset: usize,
266 #[arg(long, value_enum, help = "Sort entities by field")]
268 pub sort_by: Option<EntitySortField>,
269 #[arg(long, value_enum, default_value_t = SortOrder::Asc, help = "Sort order")]
271 pub order: SortOrder,
272 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
274 pub json: bool,
275 #[arg(long)]
277 pub db: Option<String>,
278}
279
280#[derive(clap::Args)]
281#[command(after_long_help = "EXAMPLES:\n \
282 # Preview divergences without writing (recommended first run)\n \
283 sqlite-graphrag graph recompute-degree --dry-run\n\n \
284 # Reconcile every namespace\n \
285 sqlite-graphrag graph recompute-degree\n\n \
286 # Reconcile a single namespace\n \
287 sqlite-graphrag graph recompute-degree --namespace project-x\n\n\
288NOTES:\n \
289 `entities.degree` is a derived cache (incremented on link, recalculated\n \
290 on merge/delete) that drifts when edges are written by paths that skip\n \
291 the recalculation. This command recomputes every entity's degree from\n \
292 the real `relationships` rows (same semantics as the canonical\n \
293 `recalculate_degree` helper: COUNT(*) WHERE source_id = id OR\n \
294 target_id = id) inside one transaction. Entities left with zero edges\n \
295 are zeroed. Envelope: {total, updated, zeroed, unchanged}.")]
296pub struct GraphRecomputeDegreeArgs {
298 #[arg(long)]
300 pub namespace: Option<String>,
301 #[arg(long)]
303 pub dry_run: bool,
304 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
306 pub json: bool,
307 #[arg(long)]
309 pub db: Option<String>,
310}
311
312#[derive(Serialize)]
313pub(crate) struct TraverseHop {
314 pub(crate) entity: String,
315 pub(crate) relation: String,
316 pub(crate) direction: String,
317 pub(crate) weight: f64,
318 pub(crate) depth: u32,
319}
320
321#[derive(Serialize)]
322pub(crate) struct GraphTraverseResponse {
323 pub(crate) from: String,
324 pub(crate) namespace: String,
325 pub(crate) depth: u32,
326 pub(crate) hops: Vec<TraverseHop>,
327 pub(crate) elapsed_ms: u64,
328}
329
330#[derive(Serialize)]
331pub(crate) struct GraphStatsResponse {
332 pub(crate) namespace: Option<String>,
333 pub(crate) node_count: i64,
334 pub(crate) edge_count: i64,
335 pub(crate) avg_degree: f64,
336 pub(crate) max_degree: i64,
337 pub(crate) elapsed_ms: u64,
338}
339
340#[derive(Serialize)]
341pub(crate) struct EntityItem {
342 pub(crate) id: i64,
343 pub(crate) name: String,
344 pub(crate) entity_type: String,
345 pub(crate) namespace: String,
346 pub(crate) created_at: String,
347 pub(crate) degree: u32,
349 #[serde(skip_serializing_if = "Option::is_none")]
350 pub(crate) description: Option<String>,
351}
352
353#[derive(Serialize)]
354pub(crate) struct GraphEntitiesResponse {
355 pub(crate) entities: Vec<EntityItem>,
356 pub(crate) total_count: i64,
357 pub(crate) limit: usize,
358 pub(crate) offset: usize,
359 pub(crate) namespace: Option<String>,
360 pub(crate) elapsed_ms: u64,
361}