mcpField
M
memory-literary-analysis

memory-literary-analysis

github.com/basicmachines-co生产力4.4k1.2k 次安装今天更新
代码仓库

关于 memory-literary-analysis

此技能将完整文学作品整理为 Basic Memory 中相互关联的结构化知识图谱,涵盖逐章分析、实体补充、验证与综合,适用于学习、教学和研究。

主要功能

  1. 01逐章创建笔记
  2. 02定义文学实体模式
  3. 03关联人物与主题笔记
  4. 04验证图谱并撰写综合分析
  5. 05按范围读取原文

适用场景

  1. 01为小说构建学习资料
  2. 02追踪戏剧中的主题
  3. 03整理细读研究

MCP 相关

通过 Basic Memory MCP 的 bm_read、bm_find、bm_search 和 bm_list 读取笔记与元数据;使用 bm_write 和 bm_edit 写入内容。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/basicmachines-co/basic-memory --skill memory-literary-analysis

安装后在 Claude Code 中输入/memory-literary-analysis确认已加载。

详细介绍

文学作品记忆分析

将一部完整的文学作品转化为结构化知识图谱。角色、主题、章节、地点、象征和文学手法会成为相互关联的笔记,可供搜索、验证和遍历。

适用场景

  • 从头到尾分析小说、戏剧、诗歌或非虚构类图书
  • 为文学文本创建教学或学习资源
  • 创建读书会配套知识库
  • 开展需要结构化细读的研究项目
  • 大规模压力测试 Basic Memory(约 200+ 条笔记、1000+ 个关系)

流程概览

Phase 0: Setup         → project, schemas, directory structure
Phase 1: Seed          → stub notes for known major entities
Phase 2: Process       → chapter-by-chapter notes in batches
Phase 3: Cross-ref     → enrich arcs, add parallels, write analysis
Phase 4: Validate      → schema checks, drift detection, consistency
Phase 5: Explore       → traverse the graph, write synthesis notes

工具

写入通过 bm_write 和 bm_edit 完成。MCP 读取使用 bm_read、bm_find、 bm_search 和 bm_list。CLI 保留其 POSIX 动词;下表和 shell 示例使用的是 CLI 命令(每个动词前都要加 bm):

需求使用不要使用
长笔记中的某个部分cat <note> --section Observations读取整篇笔记
源文本中的某个行范围cat <source>.txt --lines 4200-4890将整本书载入上下文
匹配 frontmatter 的笔记find --meta status=active读取笔记以检查字段
多篇笔记中的字段find --meta ... --fields pov,setting每篇笔记读取一次
查找某内容的位置ls、tree、find --name '*.md'列出所有内容

贯穿 100+ 章分析过程的两条重要规则:

  • 绝不要为检查字段而读取笔记。 应使用 --meta 谓词和 --fields 投影——一次调用就能得到结果,无须逐篇读取。
  • 绝不要为了查看文件的一部分而将整个文件载入上下文。 章节和行范围会裁剪输出:系统仍会先获取整篇笔记,然后再裁剪后返回。它们节省的是上下文,而非 I/O——长章节或完整源文本只会消耗相关部分所需的 token,而不是整个文件的 token。

查询前要了解两处容易踩坑的地方。第一处会悄无声息地失败——回答很短,退出码为 0,也没有警告——最好现在就了解,而不是事后才发现自己以为已经审计过的图谱并不完整:

  • find 会分页,默认每页为 10 条。 如果查询目标是“所有 N 个章节”,需要使用 --page-size 200(最大值)——参见覆盖检查。
  • --name 不能与 --meta 组合使用。 元数据搜索不支持文件名 glob。要限定 --meta 查询范围,请改用位置参数路径:find /characters --meta 'note_type=character'。该路径会按照目录边界匹配笔记所编入索引的文件路径——也就是笔记实际所在的位置,而非其永久链接;笔记在 frontmatter 中设置 permalink: 或被移动后,永久链接就不再与文件路径对应。因此,/characters 会匹配 characters/ 下的所有内容(包括 characters/major/),但不会匹配 characters-cut/。

对于 MCP,可使用 bm_read(identifier="<note>", section="Observations") 或 start_line/end_line 获取切片,使用 bm_find(filters={"type": "chapter"}, fields=["chapter_number", "pov"]) 查询元数据,并使用 bm_search(query="doubloon", lines=2) 查找匹配行。bm_list 会列出一个文件夹层级;bm_find 会递归搜索。 MCP 的 list/find/search 每页最多返回 100 条;请通过 has_more 遍历所有页面。 以下 CLI 页大小和标志与 MCP 的参数形式相互独立。

阶段 0:设置

创建项目
bm_project(command="create", name="<work-name>", path="~/basic-memory/<work-name>")

使用作品标题的 kebab-case 短名称(例如 great-gatsby、hamlet、beloved)。

定义 Schema

在 schema/ 中编写 6 条 schema 笔记。每条 schema 都定义实体类型的字段、观察类别和关系类型。根据作品调整字段——以下 schema 是起点,而非固定模板。

角色 Schema
bm_write(
  title="Character",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "Character",
    "version": 1,
    "schema": {
      "role(enum)": "[protagonist, antagonist, supporting, minor], character's narrative role",
      "description": "string, brief character description",
      "first_appearance?": "string, chapter or scene of first appearance",
      "status?(enum)": "[alive, dead, unknown, transformed], character status at end of work"
    },
    "settings": {"validation": "warn"}
  },
  content="""# Character

Schema for character entity notes.

## Observations
- [convention] Major characters in characters/major/, minor in characters/minor/
- [convention] Observation categories: trait, motivation, arc, quote, appearance, relationship, symbolism, fate
- [convention] Relations: appears_in, contrasts_with, allied_with, commands, symbolizes, associated_with"""
)

根据需要添加作品特定字段,例如军事题材小说中的 rank、家族史诗中的 house、奇幻作品中的 species。

主题 Schema
bm_write(
  title="Theme",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "Theme",
    "version": 1,
    "schema": {
      "description": "string, what this theme explores",
      "prevalence(enum)": "[major, minor], how central to the work",
      "first_introduced?": "string, where theme first appears"
    },
    "settings": {"validation": "warn"}
  },
  content="""# Theme

Schema for thematic analysis notes.

## Observations
- [convention] Observation categories: definition, manifestation, evolution, counterpoint, quote, interpretation
- [convention] Relations: embodied_by, contrasts_with, reinforced_by, explored_in, expressed_through"""
)
章节 Schema
bm_write(
  title="Chapter",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "Chapter",
    "version": 1,
    "schema": {
      "chapter_number": "integer, sequential chapter number",
      "pov?": "string, point-of-view character or narrator mode",
      "setting?": "string, primary location",
      "narrative_mode?(enum)": "[dramatic, expository, reflective, epistolary, mixed], chapter's primary mode"
    },
    "settings": {"validation": "warn"}
  },
  content="""# Chapter

Schema for chapter-level analysis notes.

## Observations
- [convention] Chapters stored in chapters/ directory
- [convention] Observation categories: summary, event, tone, technique, quote, significance, foreshadowing
- [convention] Relations: features, set_in, explores, contains, employs, follows, precedes, parallels"""
)
地点 Schema
bm_write(
  title="Location",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "Location",
    "version": 1,
    "schema": {
      "description": "string, what this place is",
      "location_type(enum)": "[city, building, landscape, body_of_water, region, fictional, vehicle], type of place",
      "real_or_fictional(enum)": "[real, fictional, both], whether the place exists"
    },
    "settings": {"validation": "warn"}
  },
  content="""# Location

Schema for location and setting notes.

## Observations
- [convention] Observation categories: description, atmosphere, symbolism, significance, geography
- [convention] Relations: setting_for, associated_with, symbolizes, contains, part_of"""
)
象征 Schema
bm_write(
  title="Symbol",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "Symbol",
    "version": 1,
    "schema": {
      "description": "string, what the symbol is literally",
      "symbol_type(enum)": "[object, animal, color, action, natural_phenomenon, body_part], category of symbol",
      "primary_meaning": "string, most common interpretation"
    },
    "settings": {"validation": "warn"}
  },
  content="""# Symbol

Schema for symbolic element notes.

## Observations
- [convention] Observation categories: meaning, appearance, ambiguity, interpretation, quote, evolution
- [convention] Relations: represents, associated_with, appears_in, contrasts_with, located_at"""
)
文学手法 Schema
bm_write(
  title="LiteraryDevice",
  directory="schema",
  note_type="schema",
  metadata={
    "entity": "LiteraryDevice",
    "version": 1,
    "schema": {
      "description": "string, what the device is",
      "device_type(enum)": "[rhetorical, structural, figurative, narrative, dramatic], category",
      "frequency(enum)": "[pervasive, frequent, occasional, rare], how often used"
    },
    "settings": {"validation": "warn"}
  },
  content="""# LiteraryDevice

Schema for literary technique and device notes.

## Observations
- [convention] Observation categories: definition, usage, effect, example, significance
- [convention] Relations: used_in, characterizes, expresses, related_to"""
)
目录结构
<project>/
  <work>.txt         # the source text, verbatim (see Phase 2)
  schema/            # 6 schema definitions
  chapters/          # one note per chapter/section + prologue/epilogue
  characters/
    major/           # protagonist, antagonist, key supporting
    minor/           # named characters with limited roles
  themes/            # thematic analysis notes
  locations/         # settings and places
  symbols/           # symbolic elements
  literary-devices/  # techniques and devices
  analysis/          # cross-cutting synthesis
  tasks/             # processing tracker

阶段 1:种子实体

在处理章节之前,先为主要实体创建存根笔记,以便从一开始就能解析 [[wiki-links]]。

主要角色

为每个主要角色创建存根,并填写已知元数据:

bm_write(
  title="<Character Name>",
  directory="characters/major",
  note_type="Character",
  tags=["character", "major", "<role>"],
  metadata={"role": "<role>", "description": "<brief description>"},
  content="""# <Character Name>

## Observations
- [role] <Character's role in the work>
- [appearance] <Key physical description>

## Relations
- associated_with [[<Related Character>]]
- appears_in [[<Key Location>]]"""
)
种子清单

开始阅读前,先确定作品中的主要实体。以下清单可作为良好的起点:

类型典型数量包含内容
角色(主要)8-20主角、反派、关键配角
主题5-12作品探讨的核心议题
地点4-10主要场景、具有象征意义的地点
象征物4-10反复出现、含义丰富的物件、意象或主题元素

存根无需完整——它们可提供 [[wiki-link]] 目标,并会在处理章节时逐步完善。

阶段 2:章节处理

准备源文本

获取全文并确定章节或小节的边界。对于公有领域作品,Project Gutenberg 是一个不错的来源。对于受版权保护的作品,请使用实体副本或授权的数字副本。

将源文本放在项目目录中,保存为 .txt,并为其建立一次索引。 bm cat 解析的是笔记标识符,而不是文件系统路径——它只能访问项目索引已收录的文件。一次性索引会为原始文本创建实体行,之后即可对其使用行范围切片:

cp ~/Downloads/moby-dick.txt ~/basic-memory/moby-dick/moby-dick.txt
bm reindex --search -p moby-dick          # one pass; the .txt becomes readable

有两项限制使这种做法最合适,务必遵守:

  • 保留 .txt 格式,不要转换为 .md。 Basic Memory 会向 markdown 笔记中注入 frontmatter,导致每一行的行号都按该区块的高度偏移——根据原始文件构建的偏移映射会因此悄然出错。.txt 会原样存储,因此其行号与磁盘上的文件保持 1:1 对应。
  • 将文件留在项目内。 磁盘其他位置的源文本不是实体,bm cat 会返回 Error: Entity not found。如果必须将文件放在项目外,请对源文本停用 BM 动词,改用普通 shell 命令(sed -n '4200,4890p' <path>)——笔记仍使用 BM 动词,只有原始源文本改用 shell 读取。

然后,在开始处理前一次性构建章节偏移映射。 扫描文本中的章节标题并记录每章的行范围,然后按范围读取章节,而不要把整本书反复读入上下文:

grep -n '^CHAPTER ' ~/basic-memory/moby-dick/moby-dick.txt   # heading -> line number
bm cat moby-dick/moby-dick.txt --lines 4200-4890 --plain   # one chapter, not the whole text

这里的 grep -n 是 shell 对文件系统路径执行的 grep(这是构建映射的步骤,需要文件)。接着,bm cat 接收笔记标识符,并返回恰好对应的切片,以及 lines 4200-4890 of N 页脚。请使用带项目限定的标识符(<work>/<work>.txt),以便将请求路由到正确的项目。bm head moby-dick/moby-dick.txt -n 40 是查看标题格式的简便方法,可在编写 grep 模式前使用。

将映射存储在项目中(作为笔记或小型 JSON 文件),这样后续批次以及上下文压缩后恢复的运行就无需重新查找。对于长篇作品,这是整个流程中节省上下文最多的一项做法。

分批策略

每批处理约 10 章,以兼顾深度和进度。按叙事弧线或主题重点分组:

批次典型内容
1开篇:背景设定、角色介绍、世界构建
2-3情节发展:冲突确立、关系演变
4-6中段:情节复杂化、转折点、主题深化
7-8接近高潮:局势升级、揭示真相、危机
最终批次高潮、结局、尾声

根据章节长度和内容密度调整批次大小。篇幅较短、动作场面较多的章节可以合并为更大的批次;篇幅较长、哲理性较强的章节可能需要拆成更小的批次。

每章的工作流

对于每一章:

1. 仔细阅读章节。 根据偏移映射读取章节对应的行范围 (bm cat <source>.txt --lines <start>-<end>),不要读取整个文件。阅读原文—— 绝不要凭记忆或摘要开展工作;文本证据才是全部要点。

2. 创建章节笔记:

bm_write(
  title="Chapter <N> - <Title>",
  directory="chapters",
  note_type="Chapter",
  tags=["chapter", "<arc-phase>"],
  metadata={
    "chapter_number": <N>,
    "pov": "<narrator or POV character>",
    "setting": "<primary location>",
    "narrative_mode": "<mode>"
  },
  content="""# Chapter <N> - <Title>

## Observations
- [summary] <1-2 sentence synopsis>
- [event] <Key plot events>
- [tone] <Emotional and stylistic atmosphere>
- [technique] <Notable narrative techniques>
- [quote] "<Significant passage>"
- [significance] <Why this chapter matters to the whole>
- [foreshadowing] <Hints at future events>

## Relations
- features [[<Character>]]
- set_in [[<Location>]]
- explores [[<Theme>]]
- contains [[<Symbol>]]
- employs [[<Literary Device>]]
- follows [[Chapter <N-1> - <Previous Title>]]
- precedes [[Chapter <N+1> - <Next Title>]]"""
)

3. 补充相关实体信息:

bm_edit(
  identifier="characters/major/<character-slug>",
  command="insert_after_section",
  section="Observations",
  content="""- [arc] Ch.<N>: <What happens to this character>
- [quote] "<Attributed quote>" (Ch.<N>)"""
)

append 和 prepend 不会指定某个章节——它们会分别写入笔记末尾和正文开头。使用 insert_after_section / insert_before_section,将内容写到标题下方;如果找不到标题,它们会明确报错。

4. 跟踪进度:使用 memory-tasks 技能创建处理任务,以便在上下文压缩后继续执行。

每章需要记录的内容
类别查找内容
[summary]用 1-2 句话概述章节
[event]关键情节事件(行动、揭示、到来)
[tone]情感与风格氛围
[technique]叙事创新(视角转换、结构实验、类型融合)
[quote]难忘或具有重要主题意义的段落
[significance]本章对整部作品的重要性
[foreshadowing]对未来事件的暗示
每章的实体补充

处理每一章时,向相关实体添加观察:

  • 角色:[arc] 相关时刻、新揭示的 [trait]、[quote] 引文归属
  • 主题:本章中的 [manifestation]、[evolution] 变化
  • 象征:附带背景的 [appearance]、新的 [interpretation] 角度
  • 地点:描述中的 [atmosphere]、场景中的 [significance]
  • 文学手法:本章中的 [example]
添加文字与阐释

完成结构化观察后,可考虑为重要实体笔记添加阐释性文字。使用 bm_edit(command="insert_before_section", section="Observations", content=...),在 Observations 部分之前插入 2-4 段文学评论。这些文字应:

  • 对角色、主题或象征提出一种解读,而不只是进行描述
  • 将实体与作品更广泛的关切及文学传统联系起来
  • 明确标注主观观点(“在我看来……”“我认为……”)
  • 以文本证据支撑论点,并注明章节编号

这些文字能增添仅靠结构化观察无法呈现的阐释层次。

第 3 阶段:交叉引用

处理完所有章节后:

找出需要补充的内容

不要为了判断哪些笔记内容单薄而重新阅读每一条笔记。使用查询来查找:

bm find --meta 'note_type=chapter' --fields chapter_number,pov,setting --page-size 200
bm find --meta 'note_type=character' --fields role,status --page-size 200   # who is still a stub
bm find --meta 'chapter_number>100' --fields pov --page-size 200           # late-book POV drift

笔记中从未设置过的字段会以空单元格的形式返回(在 --json 下为 null),因此含有空值的行就是待处理队列。这样一来,“审查知识图谱”就不再需要逐一阅读所有笔记,而是每个问题调用一次查询。

bm_write 会将 note_type 转换为蛇形命名法(Chapter 会存储为 chapter),--meta 的类型谓词也会进行同样的标准化,因此两种写法都能匹配。--page-size 200 并非可有可无:不设置时,这些查询只会返回前 10 行,待处理队列看起来就只有十项。

角色弧线

为每个主要角色撰写一条完整的 [arc] 总结观察,概述其在整部作品中的发展轨迹。

主题演变

为每个主题添加 [evolution] 观察,追踪其从引入到解决的发展过程。

章节对照

为结构相似的章节添加 parallels 和 contrasts_with 关系(例如,镜像场景、重复出现的场景、主题回响)。

分析笔记

在 analysis/ 中创建综合分析笔记:

bm_write(
  title="Narrative Structure",
  directory="analysis",
  note_type="note",
  tags=["analysis", "structure"],
  content="""# Narrative Structure

Analysis of the work's narrative architecture.

## Observations
- [structure] <Overall arc description>
- [technique] <Key narrative strategies>
...

## Relations
- analyzes [[<Protagonist>]]
- analyzes [[<Key Character>]]
- explores [[<Central Theme>]]
..."""
)

建议创建的分析笔记:

  • 叙事结构——整体架构与节奏
  • 作品概览——对完整作品的综合分析(摘要、论点、影响)
  • 评论界反响——历史与当代的解读
发现新实体

在处理章节期间,会出现新的次要人物、地点和象征。对于出现在 3 个或更多章节中,或具有主题意义的实体,请创建笔记。

第 4 阶段:验证

架构验证
# Validate each entity type
bm_schema(command="validate", note_type="Character")
bm_schema(command="validate", note_type="Theme")
bm_schema(command="validate", note_type="Chapter")
bm_schema(command="validate", note_type="Location")
bm_schema(command="validate", note_type="Symbol")
bm_schema(command="validate", note_type="LiteraryDevice")
漂移检测
bm_schema(command="diff", note_type="Character")
# ... for each type

修复发现的问题——常见修复方法:

  • 缺少必需的观察类别 → 通过 bm_edit 添加
  • 枚举值超出允许范围 → 更正元数据
  • 笔记中的字段未包含在架构中 → 如果合理,将其作为可选字段添加到架构中
覆盖率检查

架构验证可以证明笔记符合其结构。以下检查可以证明图谱是完整的:

bm find --meta 'note_type=chapter' --fields chapter_number --page-size 200   # every chapter present?
bm find --meta 'note_type=chapter' --fields pov,setting --page-size 200      # missing context?
bm find /characters --meta 'note_type=character' --fields role --page-size 200  # inventory vs. seed list

仅检查分页结果的覆盖率检查不算覆盖率检查。 bm find 默认使用 --page-size 10,因此第一个查询不指定页面大小时,会「证明」一部有 138 章的作品只有 10 章。200 是最大页面大小;超过此数时,请使用 --page 2、--page 3……逐页检查。 使用位置路径(/characters)限定 --meta 查询范围,切勿使用 --name——这两个选项互斥,因为元数据搜索不支持文件名 glob。位置路径按笔记索引所在的文件路径限定范围,并在目录边界处匹配:/characters 可以匹配 characters/major/ahab.md,但不会匹配 characters-cut/。它不是永久链接匹配,因此,即使笔记自行指定了 permalink:,仍会根据其文件所在位置找到它。

请从页脚读取总数,而不是从可见行数推断。在终端中,每条 find 结果都会显示 page 1 • total 138;如果页面截断了结果,还会附加 • more available (--page)——无论可见行数如何,只要出现此后缀,检查就未通过。**页脚仅在 TTY 中显示。**未使用 --plain 的管道输出为 JSON,其中包含 total 和 has_more;使用 --plain 时,行会输出到 stdout,是否有更多结果的提示会输出到 stderr。对于脚本,请从 --json 中读取 has_more。

要检查仅凭总数无法发现的序号缺口,请从 --json 中获取数值;其中包含 total、total_is_exact 和 has_more:

expected=138; page=1; rows='[]'
while :; do
  resp=$(bm find --meta 'note_type=chapter' --fields chapter_number \
           --page-size 200 --page $page --project <work> --json)
  rows=$(jq -n --argjson acc "$rows" --argjson r "$resp" \
           '$acc + [$r.results[] | {title, n: .fields.chapter_number}]')
  [ "$(jq -r '.has_more' <<<"$resp")" = "true" ] || break
  page=$((page + 1))
done
jq -n --argjson rows "$rows" --argjson expected "$expected" '
  ([$rows[] | select(.n != null and (.n | tostring | test("^[0-9]+$"))) | .n | tonumber]) as $n
  | { total:        ($rows | length),
      unnumbered:   [$rows[] | select(.n == null or (.n | tostring | test("^[0-9]+$") | not))
                             | .title],
      missing:      ([range(1; $expected + 1)] - $n),
      duplicates:   ($n | group_by(.) | map(select(length > 1) | .[0])),
      out_of_range: ($n | map(select(. < 1 or . > $expected)) | unique) }'

循环检查并非形式主义。--page-size 上限为 200,因此单次调用无法盘点超过 200 章的作品;而使用 --page 2 重新运行会替换数字,而不是累积数字,于是即使语料完整,也会报告第 1 至 200 章缺失。请逐页遍历,直到 has_more 为 false,并检查所有页面的并集。

--fields 会返回 YAML 原生类型,因此 chapter_number: 63 会以 63 的形式返回。tostring | test(...) | tonumber 这一保护措施仍能捕获手动输入的值,例如 "63" 或 "12a"。

请将作品的实际章节数作为 $expected 传入——根据找到的最大编号推导范围,会让不完整的图谱蒙混过关。若有 138 行,编号为 1..137,另有一个重复编号,基于最大编号的检查会报告 missing: [],但实际上仍有章节缺失:重复项使总数正确,而缺失的末尾章节改变了目标范围。只有在合并后的所有页面中,unnumbered: []、missing: []、duplicates: [] 和 out_of_range: [] 同时成立,检查才通过。

unnumbered 也不是装饰项。如果 chapter 笔记从未设置 chapter_number,返回值会是 null;直接将其传给 tonumber 会因 null cannot be parsed as a number 而中止整个流程——也就是说,检查恰恰会在它要查找的错误清单上崩溃。先进行分类,就能将其转换成一条具名记录。

out_of_range 并非假设情况:如果把序言或后记记为 chapter,编号就会是 0 或 $expected + 1;如果没有这个键,报告看起来会一切正常——所有预期编号都存在,且没有重复——但清单中却有一条编号未涵盖的笔记。请将卷首和卷末内容标记为独立的笔记类型,或有意扩大 $expected。

批次中间出现缺口是最常见的处理失败,也最容易用肉眼漏掉;第二常见的是章节编号重复,而它会掩盖前一种问题。

关系一致性

抽查双向关系:如果 Chapter X features [[Character]],Character 是否有引用 Chapter X 的 observations?补上缺失的关系。

孤立项是这项检查的另一半——没有入向或出向关系的笔记,要么确实彼此孤立,要么从未链接回图中:

bm orphans          # entities with no relations in the graph

orphans 会查找没有关系的笔记。它不会查找无法解析到任何目标的 [[target]]——这可能是拼错或重命名的实体名称。使用 bm tool build-context memory://chapters/<slug> --depth 1 --project <work> --json 检查章节链接,并查找 to_entity_id 为 null 的关系;修正拼写或添加别名,然后重新运行该章节。

图的质量取决于关系密度,而不是笔记数量。只增加笔记、却留下孤立项的操作会让图变得更差。

第 5 阶段:探索图

图构建完成后,遍历它,寻找逐章处理无法发现的内容:

bm grep "features [[" --page-size 200 --project <work> --json   # every chapter's cast, one call
bm find --meta 'note_type=theme' --fields prevalence --page-size 200  # thematic weight
bm grep "doubloon" --page-size 100 --project <work>                # every mention of a symbol

“哪些角色出现在最多章节中”可以通过第一行数据,以及在本地解析每一行 content 中的 features [[...]] 得出——一次调用,无需逐章读取。这里不要使用 bm tool build-context 'memory://characters/major/*':通配符上下文在所有主要条目中最多返回 100 条相关记录,并且每个索引 observation 都会返回一条主要记录,因此既不会枚举角色阵容,也不会遍历关系网。对于下面的另一个问题,build-context 才是处理单条笔记的合适工具。build-context 将 URL 作为位置参数,不支持 --url 选项。

grep 会匹配关键词并返回一页 10 条结果,因此“它出现在哪里?”会在不易察觉的情况下被截断——某个符号出现在 40 个章节中,结果只会返回 10 条。追踪符号时,请调高 --page-size;你要查找的含义变化通常出现在后面的匹配项中,默认页数会将它们丢掉。--semantic 则会按含义排序,因此回答的是“这讲的是什么?”,而不是“这个词出现在哪里?”。

关于 grep 返回的行,还有两点需要注意。匹配不区分大小写,且以笔记为单位:命中结果是一条笔记,而不是一行;也没有 -n 或上下文。每行的 content 最多包含 4000 个字符;content_truncated: true 表示内容被截断,因此长笔记的尾部(例如最后一章的 follows/precedes 关系)可能会在由 grep 驱动的扫描中缺失。若解析依赖笔记末尾的内容,请用 cat 查看该笔记。

--page-size 只会提高上限,并不会取消上限。长篇作品中的某个符号可能超过 100 条结果,因此请检查最后一页是否已满,并依次查看 --page 2、--page 3,直到结果页不再满为止。被截断的符号搜索会像未分页的 find 一样悄无声息地失败:返回看似合理的答案,退出码为 0,却没有任何迹象表明尾部内容缺失。

此外,grep 搜索的是你的笔记,而非源文本。<work>.txt 会被索引为一个实体,但其正文不在可搜索文本中,因此你从未记入笔记的内容无法检索到:仅出现在源文本中的词会返回 total: 0。所以,它回答的是“我在哪里写过 doubloon”,而不是“书中哪里出现了 doubloon”。要回答后者,请直接搜索文件,并使用章节偏移映射将命中位置转换为章节。

通过遍历可以回答更深一层的问题——哪些角色出现在最多章节中,哪些主题在最后一幕汇合,某个符号的含义在哪里发生变化。将发现记录为 analysis/ 笔记;这些综合分析正是构建图谱的价值所在。

适配其他体裁

这条处理流水线适用于任何文学文本。根据体裁调整架构:

体裁架构调整
小说基础架构可直接使用;按需添加体裁专属的 Character 字段
戏剧添加 Act 和 Scene 架构;为 Character 添加 speaking_lines 字段
诗集将 Chapter 替换为 Poem;添加 form、meter、rhyme_scheme 字段
非虚构作品将 Chapter 替换为 Section;添加 Argument、Evidence 架构
短篇小说集添加 Story 架构,并包含 narrator、setting、word_count 字段
史诗/神话添加 Deity、Prophecy 架构;为 Location 添加 mythological_significance 字段
回忆录为 Character 架构添加 relationship_to_narrator 字段;添加 Memory 架构
扩展指南
作品长度批次大小预计笔记数
中篇小说(约 40K 词)5-10 章约 50-80 条
长篇小说(约 80K 词)8-12 章约 100-150 条
超长篇小说(约 200K 词以上)10-15 章约 200-300 条
系列作品(多卷)每次处理 1 卷每卷约 200 条以上

相关技能

  • memory-schema — 创建、验证架构并检测架构漂移
  • memory-tasks — 在上下文压缩期间跟踪章节处理进度
  • memory-notes — 笔记写作模式、观察类别和 wiki-links
  • memory-ingest — 将外部输入处理为结构化实体
  • memory-metadata-search — 按 frontmatter 字段查询笔记
  • memory-lifecycle — 归档已完成的分析阶段

指南

  • 处理前先建立种子实体。 先创建实体占位笔记,让 wiki-links 在处理章节时立即解析。
  • 分批处理,确保进度可控。 每次处理约 10 章,兼顾深度和进度。使用 Task 笔记跟踪进度。
  • 阅读源文本。 不要依赖记忆或摘要。创建笔记前,先阅读(或重读)每批对应的实际文本。文本证据至关重要。
  • 按需阅读。 将源文本以 .txt 格式保存在项目中,只索引一次,并一次性建立章节偏移映射;之后按行范围阅读章节,按节阅读笔记。处理长篇作品时,整份文件进入上下文是这条流水线中最容易避免的最大开销。
  • 用查询代替扫读。 需要了解哪些笔记包含某个字段时,使用 --meta 谓词和 --fields 投影进行查询。逐篇阅读笔记以检查 frontmatter,是这条流水线在大规模使用时容易犯的错误。有一种情况会让查询悄无声息地返回不完整结果:除非传入 --page-size,否则 find 只返回 10 行。
  • 观察记录就是索引。 知识图谱的价值来自分类整理的观察记录。分类应尽量充分,内容应具体明确。
  • 关系构成你的网络。 每章都应关联人物、主题、地点和文学手法。每个实体都应反向关联到其出现的章节。
  • 逐步丰富内容。 随着每章的处理,实体笔记会不断充实。不要一开始就试图写出完美的人物笔记;随着进度逐步补充即可。
  • 添加散文式内容,深化分析。 结构化数据就位后,为重要笔记添加阐释性文章。散文能捕捉观察记录无法表达的内容:论证、细微差别、观点和声音。
  • 定期验证。 每批处理完成后运行 bm_schema(command="validate"),不要等到最后才验证。尽早发现架构漂移。
  • 充分引用。 文学分析离不开文本证据。将重要引文作为带章节归属的 [quote] 观察记录纳入笔记。
  • 复核并修订。 完成所有章节后,从外部视角复核完整图谱。查找内容单薄的笔记、缺失的关联和覆盖空白。第一轮从来不是最后一轮。
  • 最后再做分析。 应在处理完所有章节、掌握全貌之后,再编写 analysis/ 中的综合分析笔记。

评论

支持 Markdown