Documentation
¶
Overview ¶
Package rules 实现用户偏好的输入层(Policy):把各来源的写作规则归一化、合并成 本书快照(见 snapshot.go),运行时由 novel_context 注入、commit_chapter 机械检查。
Rule 是第四类事实,跟 Progress / Checkpoint / Artifact 并列,但性质相反: 前三类是系统输出,Rule 是用户意图的持久化输入。
设计约束(不可妥协):
- 工具只返事实,不返指令(Violation 是事实,由 editor 决定是否触发重写)
- 不引入新的 verdict 路径(复用 PendingRewrites)
- 不引入严格度字段(severity 由规则类型固定映射,editor 自主语义裁定)
- 不动 Flow Router(rule 不参与路由)
Index ¶
Constants ¶
const SnapshotVersion = 2
SnapshotVersion 是当前快照 schema 版本,便于未来迁移。 v2:chapter_words 退出 structured(字数是语义软约束,走 preferences)。 v1 快照直接加载兼容:未知字段被反序列化忽略,下次叠加保存时自然收敛为 v2; 刻意不做"版本不符即重建"——那会丢掉 AddRuntimeRule 运行中追加的不可再生规则。
Variables ¶
This section is empty.
Functions ¶
func DefaultHomeRulesDir ¶ added in v0.4.3
func DefaultHomeRulesDir() string
DefaultHomeRulesDir 拼出 ~/.ainovel/rules/ 目录的绝对路径。 home 解析失败返回空串(调用方据此跳过该来源)。
func DefaultProjectRulesDir ¶ added in v0.5.2
DefaultProjectRulesDir 拼出 ./.ainovel/rules/ 的绝对路径(基于给定项目目录)。 调用方传入项目根,避免在 loader 内部依赖 cwd;镜像 DefaultHomeRulesDir。
func EnsureHomeRulesDir ¶ added in v0.4.3
func EnsureHomeRulesDir()
EnsureHomeRulesDir 尽力创建 ~/.ainovel/rules/ 目录并写入 README.txt 引导, 让用户发现这个全局偏好扩展点、知道怎么写。 nice-to-have,非关键路径:home 解析失败或写入出错都静默吞掉,绝不阻断启动。
Types ¶
type Candidate ¶ added in v0.6.0
type Candidate struct {
Source string // 可读来源标签,进入 Snapshot.Sources(如 system_defaults / startup_prompt / global:my.md)
Structured Structured // 该来源候选结构化字段
Preferences string // 该来源的自然语言偏好正文
Uncertain []string // 该来源故意未提升到 structured 的项 + 原因(诊断)
Degraded bool // 该来源归一化失败、已降级为 raw preferences
}
Candidate 是单个来源归一化后的候选结果。
来源按优先级低→高排列后交给 BuildSnapshot 确定性合并。LLM 只负责把单一来源的 自然语言变成候选 Structured/Preferences;优先级与字段覆盖由 BuildSnapshot(Go)裁定。
func SystemDefaults ¶ added in v0.6.0
func SystemDefaults() Candidate
SystemDefaults 是代码内置的机械基线(最低优先级来源),不走 LLM 归一化。
数值迁自旧 assets/rules/default.md 的 front matter。阈值依据一并保留: 后段疲劳词(像一/沉默了/没有说话/X息)来自 196 章长跑产物实证——传统 AI 套话被前段 表灭绝后,模型转而把这些"节拍词"用到章均 5-7 次,阈值放宽以容忍正常使用。
type LoadOptions ¶
type LoadOptions struct {
// HomeRulesDir 是 ~/.ainovel/rules/ 目录;扫描其下所有顶层 .md(文件名字典序合并)。空表示跳过。
HomeRulesDir string
// ProjectRulesDir 是 ./.ainovel/rules/ 目录(镜像全局,同样扫描其下所有顶层 .md)。空表示跳过。
ProjectRulesDir string
}
LoadOptions 枚举 rules 文件来源目录,供 RawFileSources 扫描归一化。
目录不存在不算错误,扫描时静默跳过。
func DefaultOptions ¶
func DefaultOptions() LoadOptions
DefaultOptions 根据当前工作目录构造常用 LoadOptions。
适合 Host 启动时调用一次,让用户规则服务复用同一份来源配置。 解析 cwd 失败时 ProjectRulesDir 留空(扫描会跳过该来源)。
路径语义:ProjectRulesDir 绑定 **当前工作目录(cwd)** 而非 outputDir。 用户 cd 到不同目录启动写不同的书,./.ainovel/rules/ 自然跟着 cwd 走;如需跨书共享, 放 ~/.ainovel/rules/ 全局目录即可(其下所有 .md 都会被加载)。
type RawSource ¶ added in v0.6.0
type RawSource struct {
Label string // 来源标签,进入 Snapshot.Sources(如 global:my-style.md)
Kind SourceKind // 优先级层级
Text string // 文件原始内容
}
RawSource 是一个待归一化的原始来源(rules 文件的整段文本)。
砍 YAML 后,rules 文件就是普通自然语言提示词;归一化只需要原文,不再做 front matter 解析。
func RawFileSources ¶ added in v0.6.0
func RawFileSources(opts LoadOptions) []RawSource
RawFileSources 按 Global → Project 顺序枚举 rules 目录下的 .md 文件并返回原始文本。
与 readDirFromDisk 同样的扫描约定(顶层 .md、字典序、跳过隐藏文件),但不解析 YAML, 整段文本原样交给归一化器。System defaults / 启动 prompt / 运行中要求由 service 另行提供。
type Severity ¶
type Severity string
Severity 标记 Violation 的严重等级。 固定映射(用户不可配置):
forbidden_chars 出现 -> Error forbidden_phrases 出现 -> Error fatigue_words 超阈值 -> Warning
type Snapshot ¶ added in v0.6.0
type Snapshot struct {
Version int `json:"version"`
Status Status `json:"status"`
Structured Structured `json:"structured"`
Preferences string `json:"preferences"`
Sources []string `json:"sources"`
Uncertain []string `json:"uncertain"`
}
Snapshot 是本书归一化后的用户规则快照(meta/user_rules.json)。
它是运行时唯一事实源:开书/导入/刷新时由各来源归一化合并而成,之后 novel_context 注入与 commit_chapter 检查都只读这一份,不再反复读 rules 文件(避免漂移与双读者发散)。
注入给模型的只有 Structured + Preferences(见 Payload);Version / Status / Sources / Uncertain 是运维与诊断元数据,不进 working_memory.user_rules。
func BuildSnapshot ¶ added in v0.6.0
BuildSnapshot 把按优先级(低→高)排好的候选确定性合并成快照。
合并规则(全部 Go 侧确定性,不交给 LLM):
- structured:按字段覆盖,高优先级来源覆盖低优先级;fatigue_words 按词叠加
- preferences:不覆盖,按来源顺序拼接(高优先级在后),带来源标题
- 空值/零值视为字段缺失,不覆盖已有值(sanitizeStructured)
- 任一来源 Degraded → 快照 status=degraded
func OverlaySnapshot ¶ added in v0.6.0
OverlaySnapshot 把一个高优先级候选叠加到已有快照上(候选胜出)。
用于运行中 Arbiter rules 动作:不重新归一化所有来源,只把新规则覆盖进当前快照—— structured 按字段覆盖、preferences 追加一段、sources/uncertain 累加、降级传播。
type SourceKind ¶
type SourceKind int
SourceKind 标记规则文件来源,仅用于生成来源标签(如 global:my-style.md)。
const ( // SourceGlobal — 用户全局偏好(~/.ainovel/rules/ 目录下所有 .md,按文件名字典序合并),跨书复用。 SourceGlobal SourceKind = iota // SourceProject — 本书规则(./.ainovel/rules/ 目录下所有 .md,按文件名字典序合并),优先级最高。 SourceProject )
type Structured ¶
type Structured struct {
Genre string `json:"genre,omitempty"`
ForbiddenChars []string `json:"forbidden_chars,omitempty"`
ForbiddenPhrases []string `json:"forbidden_phrases,omitempty"`
FatigueWords map[string]int `json:"fatigue_words,omitempty"`
}
Structured 装载机械可检的结构化规则字段(归一化各来源后的候选/合并结果)。 章节字数刻意不在此列:多长算一章是叙事完整性问题,属语义裁量(writer/editor), 数字化成机械硬线会诱导模型为跨线注水——字数意愿走 preferences 自然语言通道。
func (Structured) IsEmpty ¶
func (s Structured) IsEmpty() bool
IsEmpty 用于判定是否完全没有结构化规则;checker 可据此跳过。
type Violation ¶
type Violation struct {
Rule string `json:"rule"` // forbidden_chars / forbidden_phrases / fatigue_words
Target string `json:"target,omitempty"` // 具体违规对象(哪个词/字符)
Limit any `json:"limit,omitempty"` // 阈值;fatigue_words=int / forbidden_*=空
Actual any `json:"actual"` // 实际值:出现次数
Severity Severity `json:"severity"` // error / warning
}
Violation 是 checker 的输出:本章违反了某条机械规则的事实陈述。
注意:commit_chapter 把 violations 透传到返回 JSON,不阻断 commit; editor 在审阅时把这些事实映射到现有七维(aesthetic/pacing/character/consistency), 由 LLM 自主决定是否升级 verdict 触发 polish/rewrite。
func Check ¶
func Check(text string, s Structured) []Violation
Check 对章节正文按结构化规则进行机械检查,返回违规事实列表。
设计契约:
- 仅返事实,不下指令(铁律一)
- 不阻断任何调用方流程
- severity 按规则类型固定映射(参见 types.go 注释表)
参数:
- text:章节正文(终稿或草稿都可)
- s:合并后的结构化规则;IsEmpty 时直接返回 nil。