cgx_engine/docs/mod.rs
1//! Layered, Obsidian-ready documentation vault generator.
2//!
3//! The docs generator turns the cgx graph into a markdown vault organised by the
4//! Diátaxis-ish layout described in `cgx-docs`:
5//!
6//! - `00-Overview/` — Architecture.md, Glossary.md
7//! - `10-PublicAPI/` — one note per community's exported surface
8//! - `20-Architecture/` — Communities, CrossClusterDeps, EntryPoints
9//! - `30-Modules/<community>/<file>.md` — per-file notes with prompt packets
10//! - `40-Risk/` — Hotspots, ComplexityHigh, DeadCode, Duplicates
11//! - `50-Ownership/` — Owners, BlameGraph
12//!
13//! cgx never calls an LLM. Each module note ends with a `<!-- cgx-prompt -->` block
14//! that contains all context the user's AI agent needs to produce prose without
15//! re-exploring the repo. The agent runs *outside* cgx.
16
17pub mod incremental;
18pub mod label;
19pub mod layered;
20pub mod obsidian_detect;
21pub mod project;
22pub mod prompt_packet;
23pub mod prompts_index;
24pub mod role;
25pub mod wiki_link;
26
27use std::path::{Path, PathBuf};
28
29use crate::graph::GraphDb;
30
31/// User-selected output target for `cgx docs`.
32#[derive(Debug, Clone)]
33pub enum DocsTarget {
34 /// Write to a path inside the working repo (e.g. `./cgx-docs/`).
35 Local(PathBuf),
36 /// Write to an Obsidian vault. If the inner path is empty, auto-detect.
37 Vault(Option<PathBuf>),
38}
39
40/// Mode of generation.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum DocsMode {
43 /// Re-write every note from scratch.
44 Full,
45 /// Only re-write module notes whose graph slice changed since the last run.
46 /// Top-level index notes (Architecture, Hotspots, ...) are always re-written.
47 Incremental,
48}
49
50/// Options that flow from CLI flags / `DocsConfig` into the generator.
51#[derive(Debug, Clone)]
52pub struct DocsOptions {
53 pub prompt_packets: bool,
54 pub frontmatter: bool,
55 pub wiki_links_style: WikiLinkStyle,
56 pub include_dead_code: bool,
57 pub include_duplicates: bool,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum WikiLinkStyle {
62 /// `[[path/to/note|alias]]` (Obsidian).
63 Obsidian,
64 /// `[alias](path/to/note.md)` (plain markdown).
65 Markdown,
66}
67
68impl Default for DocsOptions {
69 fn default() -> Self {
70 Self {
71 prompt_packets: true,
72 frontmatter: true,
73 wiki_links_style: WikiLinkStyle::Obsidian,
74 include_dead_code: true,
75 include_duplicates: true,
76 }
77 }
78}
79
80/// Stats returned by [`generate_vault`].
81#[derive(Debug, Clone, Default)]
82pub struct DocsReport {
83 pub output_dir: PathBuf,
84 pub module_notes_written: usize,
85 pub module_notes_skipped: usize,
86 pub index_notes_written: usize,
87 pub mode: &'static str,
88}
89
90/// Main entry point — generate (or refresh) a docs vault for the repo indexed in `db`.
91pub fn generate_vault(
92 repo_path: &Path,
93 db: &GraphDb,
94 target: DocsTarget,
95 mode: DocsMode,
96 opts: &DocsOptions,
97) -> anyhow::Result<DocsReport> {
98 let output_dir = match target {
99 DocsTarget::Local(p) => p,
100 DocsTarget::Vault(Some(p)) => p.join("cgx-docs"),
101 DocsTarget::Vault(None) => obsidian_detect::detect_default_vault()
102 .ok_or_else(|| {
103 anyhow::anyhow!(
104 "No Obsidian vault detected. Pass a path: `cgx docs --vault <path>`."
105 )
106 })?
107 .join("cgx-docs"),
108 };
109
110 layered::write_vault(repo_path, db, &output_dir, mode, opts)
111}