pub struct GenerationState {
pub last_commit_hash: Option<String>,
pub file_fingerprints: HashMap<String, String>,
pub generated_at: String,
pub doc_fingerprints: HashMap<String, String>,
pub doc_modules: HashMap<String, String>,
pub protected_docs: Vec<String>,
pub tool_version: Option<String>,
pub failed_modules: Vec<String>,
}Expand description
生成状态
记录上一次生成时的 commit hash 和文件指纹, 用于增量更新时检测变更。
Fields§
§last_commit_hash: Option<String>上一次生成时的 commit hash
file_fingerprints: HashMap<String, String>文件路径 → SHA256 指纹
generated_at: String生成时间(ISO 8601)
doc_fingerprints: HashMap<String, String>已生成文档路径 → SHA256 指纹(用于检测人工修改)
doc_modules: HashMap<String, String>已生成文档路径 → 所属模块名(人工修改反向同步的精确归属依据; 全局文档 api/overview/toc 无模块归属,不记录。模块名压平为 “::” 连接(module_path.join(“::”)),与卡片 module_name 同规则, 精确匹配,杜绝 stem 匹配的下划线歧义)
protected_docs: Vec<String>人工修改过的文档路径集合(保护集:下次自动更新不覆盖,直到 –force 清空)
tool_version: Option<String>生成时工具版本(v19 t01 版本自检依据)
from_insights 写入 env!(“CARGO_PKG_VERSION”);doctor 读取并对比 当前二进制版本,捕获「PATH 里的旧版二进制生成产物后又被新版 调用」的静默漂移(旧版缺 doctor/dry-run,实测报 unrecognized subcommand exit 2,用户无从知道产物是旧格式)。旧状态文件无此 字段(serde default None)→ doctor 提示无法判断,不误报。
failed_modules: Vec<String>上次生成失败被隔离的模块名(卡片或页面生成失败,v22 修复)
失败隔离(generate_wiki_pages/generate_all_cards 的 record_failure) 只跳过失败模块、不中断整体生成——但失败模块若源码不再变更将永远 无法补生成(增量以 git diff 触发,失败模块不在 diff 中)。 本轮生成结束时把失败模块写入状态,下次 update 时并入变更集重试; no-op 快速判定(should_skip_noop)也因非空跳过。清空时机: 成功重试(该模块生成成功)或全量生成(自然覆盖)。
Implementations§
Source§impl GenerationState
impl GenerationState
Sourcepub fn save(&self, state_dir: &Path) -> Result<()>
pub fn save(&self, state_dir: &Path) -> Result<()>
保存生成状态到目录(原子写:fs::write_file_atomic 临时文件 + rename 覆盖,防止崩溃留下半截 JSON——半截状态会被 load 判为 损坏,进而触发调用方的 fail-loud 路径,见 lib.rs load_protection)
确定性序列化:file_fingerprints/doc_fingerprints/doc_modules 是 HashMap,迭代序随 RandomState 每次进程而异——直序列化会让状态 文件字节级漂移(同样的内容两次写入字节不同,git diff 噪音且 破坏“同输入同输出“的确定性契约)。写入前把三个 map 按键排序 组装成 serde_json::Map,保证同状态同字节;load 反序列化对 JSON 对象键序不敏感,旧文件完全兼容。
Sourcepub fn preserve_protection(&mut self, old: &GenerationState)
pub fn preserve_protection(&mut self, old: &GenerationState)
生成前中途存盘的保护字段保留(票 03)
from_insights 构造的新状态只含 file_fingerprints/commit hash—— 若在 LLM 生成(流水线 Phase 3,最常见失败点)之前直接落盘, doc_fingerprints/protected_docs/doc_modules 全空;一旦生成失败, 磁盘状态即“无保护“版本,下次运行的人工修改保护失效。 本方法把旧状态中的保护字段合并进新状态,使中途存盘只推进 代码侧状态(commit hash/文件指纹),产物侧保护信息不因中途 失败而丢失。新状态保护字段非空时保留新值(正常全量完成后 Phase 6 的最终保存不受影响)。
Sourcepub fn from_insights(
root: &ProjectRoot,
insights: &[FileInsight],
commit_hash: &str,
) -> Result<Self>
pub fn from_insights( root: &ProjectRoot, insights: &[FileInsight], commit_hash: &str, ) -> Result<Self>
从文件解析结果和 commit hash 构建新的生成状态
root 注入:insight.path 是相对项目根的路径,指纹计算必须先与 root 拼接——直接相对 cwd 打开会随进程 cwd 漂移而错读(watch 常驻进程的 cwd 漂移不再影响指纹基准)。
Sourcepub fn compute_file_fingerprint(path: &Path) -> Result<String>
pub fn compute_file_fingerprint(path: &Path) -> Result<String>
计算文件的 SHA256 指纹
非 UTF-8 文件返回错误。
Sourcepub fn record_doc_fingerprints(
docs: &[WikiDocument],
cards: &[KnowledgeCard],
output_dir: &Path,
languages: &[String],
) -> Result<(HashMap<String, String>, HashMap<String, String>)>
pub fn record_doc_fingerprints( docs: &[WikiDocument], cards: &[KnowledgeCard], output_dir: &Path, languages: &[String], ) -> Result<(HashMap<String, String>, HashMap<String, String>)>
记录所有已生成文档的 SHA256 指纹
路径与 render_all 实际写盘路径一致:{output_dir}/wiki/{lang}/{file}.md,
文件名复用 output::wiki_page_path(ArchitectureOverview 特判写
architecture.md),languages 为主语言 + 扩展语言(与 output::wiki_languages 保持一致)。
另补记三个全局文档(api.md / overview.md / toc.md)的指纹,
路径复用 output::api_doc_path / overview_doc_path / toc_doc_path,
与 render_all 的保护判定路径同一规则产出。
卡片同样计入指纹(路径 cards/{lang}/{module.replace(“::”,“”)}.md,与
render_all 写盘路径一致),使人工编辑的卡片与 wiki 页一样被
detect_manually_modified 识别并纳入保护集,全量 generate 不再静默覆盖。
返回值:(文档指纹表, 文档模块归属表)。模块归属 = 产物路径 → 模块名 (wiki 页取 module_path.join(“::”),卡片取 module_name;api/overview/toc 全局文档无模块归属不记录),供人工修改反向同步的精确归属—— 精确匹配杜绝了 stem 匹配在模块名含下划线时的串卡片歧义。
Sourcepub fn detect_manually_modified(&self) -> Vec<String>
pub fn detect_manually_modified(&self) -> Vec<String>
比对磁盘文档与生成时指纹,返回人工修改的文档路径集合
磁盘文件不存在时不视为人工修改(跳过保护);指纹读取失败时 保守计入修改集(保护优先)——宁可多保护一次也不让人工编辑 内容在下次生成中被静默覆盖(读取失败通常伴随权限/IO 异常, 此时无法确认磁盘内容是否仍是上次生成的原样)。
Sourcepub fn is_file_changed(&self, root: &ProjectRoot, path: &Path) -> Result<bool>
pub fn is_file_changed(&self, root: &ProjectRoot, path: &Path) -> Result<bool>
检查文件是否已变更
如果文件不在指纹表中(新增文件)或指纹不匹配,返回 true。 root 注入:path 是相对项目根的路径,与 root 拼接后计算 (同 from_insights,不依赖进程 cwd)。
Trait Implementations§
Source§impl Clone for GenerationState
impl Clone for GenerationState
Source§fn clone(&self) -> GenerationState
fn clone(&self) -> GenerationState
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for GenerationState
impl Debug for GenerationState
Source§impl<'de> Deserialize<'de> for GenerationState
impl<'de> Deserialize<'de> for GenerationState
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for GenerationState
impl RefUnwindSafe for GenerationState
impl Send for GenerationState
impl Sync for GenerationState
impl Unpin for GenerationState
impl UnsafeUnpin for GenerationState
impl UnwindSafe for GenerationState
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more