Skip to main content

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}