rules

package
v0.7.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

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

View Source
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

func DefaultProjectRulesDir(projectDir string) string

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
const (
	SeverityWarning Severity = "warning"
	SeverityError   Severity = "error"
)

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

func BuildSnapshot(cands []Candidate) Snapshot

BuildSnapshot 把按优先级(低→高)排好的候选确定性合并成快照。

合并规则(全部 Go 侧确定性,不交给 LLM):

  • structured:按字段覆盖,高优先级来源覆盖低优先级;fatigue_words 按词叠加
  • preferences:不覆盖,按来源顺序拼接(高优先级在后),带来源标题
  • 空值/零值视为字段缺失,不覆盖已有值(sanitizeStructured)
  • 任一来源 Degraded → 快照 status=degraded

func OverlaySnapshot added in v0.6.0

func OverlaySnapshot(base Snapshot, cand Candidate) Snapshot

OverlaySnapshot 把一个高优先级候选叠加到已有快照上(候选胜出)。

用于运行中 Arbiter rules 动作:不重新归一化所有来源,只把新规则覆盖进当前快照—— structured 按字段覆盖、preferences 追加一段、sources/uncertain 累加、降级传播。

func (Snapshot) Payload added in v0.6.0

func (s Snapshot) Payload() map[string]any

Payload 返回注入 working_memory.user_rules 的形态:只暴露 structured + preferences。 即便都为空也返回稳定结构,避免 LLM 看到 user_rules=null 走异常分支。

type SourceKind

type SourceKind int

SourceKind 标记规则文件来源,仅用于生成来源标签(如 global:my-style.md)。

const (
	// SourceGlobal — 用户全局偏好(~/.ainovel/rules/ 目录下所有 .md,按文件名字典序合并),跨书复用。
	SourceGlobal SourceKind = iota
	// SourceProject — 本书规则(./.ainovel/rules/ 目录下所有 .md,按文件名字典序合并),优先级最高。
	SourceProject
)

func (SourceKind) String

func (k SourceKind) String() string

String 返回来源的可读名称,用于来源标签前缀。

type Status added in v0.6.0

type Status string

Status 标记快照归一化是否完整成功。

const (
	// StatusReady 所有来源都成功归一化。
	StatusReady Status = "ready"
	// StatusDegraded 至少一个来源归一化失败,已降级为 raw preferences(详见 Uncertain / 日志)。
	StatusDegraded Status = "degraded"
)

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。

func Lint added in v0.5.0

func Lint(text string) []Violation

Lint 内置产品底线检查:扫描正文中的机制残留,与用户规则无关,commit 时始终执行。 与 Check 同契约——仅返事实(铁律一),不阻断流程,由评审/用户裁定。

当前三类(全部来自真实长跑产物的实证缺陷):

  • markdown_residue:正文残留 ** 加粗、首行之外的 # 标题行(导出 txt 会裸露符号)
  • non_cjk_fragments:连续拉丁字母片段(模型语言混杂,如中文正文裸混 "pattern")

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL