mcpField
E
epistemic-mapper

epistemic-mapper

github.com/gohypergiant开发2576 次安装2 周前更新
代码仓库

关于 epistemic-mapper

epistemic-mapper 是一项检查项目文档与代码、并将证据和不确定性记录到规范文件 `EPISTEMIC-MAP.md` 的技能。它面向项目交接,通过合并脚本维护一份完整的认知状态图。

主要功能

  1. 01按确定性象限分类发现
  2. 02扫描文档和代码模块
  3. 03要求发现项提供证据
  4. 04使用合并脚本关联发现
  5. 05跟踪未解决事项与置信度

适用场景

  1. 01在交接时记录未写明的原型知识
  2. 02找出需要验证的假设
  3. 03审查项目文档与代码中的不确定性

安装

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

$npx skills add https://github.com/gohypergiant/agent-skills --skill epistemic-mapper

安装后在 Claude Code 中输入/epistemic-mapper确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CHANGELOG.md
README.md
evals/
evals.json
references/
quadrant-test.md
schema.md
template.md
scripts/
merge_epistemic_map.py

详细介绍

Epistemic Mapper

从项目的文档和代码中提取项目的知识状态,然后将每一条观察结果归入四个象限之一,并将结果写入规范的 EPISTEMIC-MAP.md。这是一份交接产物:它之所以存在,是因为原型构建者掌握着大量从未被记录下来的隐性知识,而一旦合同签署、团队接手,这些知识要么现在就被捕获,要么就得在生产环境中艰难地重新发现。

                    HIGH CERTAINTY (validated)
                              |
          [ ASSUMPTIONS ]    |    [ FACTS ]
          Unknown Known      |    Known Known
          nobody wrote it    |    proven, cited
          down, but the      |    evidence
          code/docs rely     |
          on it              |
    ------------------------ + ------------------------
                              |
          [ RISKS ]           |    [ QUESTIONS ]
          Unknown Unknown     |    Known Unknown
          blind spots, found  |    explicit open
          only by looking     |    gaps, already
          across everything   |    flagged somewhere
                              |
                    LOW CERTAINTY (unvalidated)

与其他活文档的关系

CONSTRAINTS.md 记录外部施加的硬性限制。JARGON.md 记录术语定义。ARCHITECTURE.md 记录结构性决策。AGENTS.md 记录 Agent 行为与编排。EPISTEMIC-MAP.md 与这四者性质不同:它不是关于什么是真实的账本,而是一张关于我们对什么是真实的有多确定的地图。在开始提取之前,先阅读其他活文档(如果存在),原因有二:

  1. 不要重复。 如果某个 Known Known 已在 CONSTRAINTS.md 中陈述,或在 JARGON.md 中定义,请通过文件和章节引用它,而不是将其作为新的 Fact 重新陈述。EPISTEMIC-MAP.md 应指向其他活文档,而不是与它们竞争。
  2. 为置信度提供依据。 有 CONSTRAINTS.md 中条目支撑的断言,其验证程度已近乎极致——默认将其视为具有 CONFIRMED 置信度的 Known Known,而不是需要重新争论的东西。

与其他不断累积的活文档不同,这份文档理应缩减。 每一个被验证的 Assumption 都会变成 Fact。每一个被回答的 Question 都会被标记为已解决。每一个被调查的 Risk 要么被修复(关闭),要么被正式接受(转入 Known Unknown,从此被追踪而非隐藏)。如果重新运行时未解决项数量持平或增长,那就表明交接实际上并未推进——请在执行摘要中直言这一点,而不是把庞大的文件当作周全细致的标志。

构建认知地图时的绝对禁忌

  • 绝对不要发明新的类别 slug。 根据后果落在哪里,从五个 规范 slug 中选择最接近的一个,把具体的 模式写进陈述/推理文本中 —— merge_epistemic_map.py 拒绝其他任何写法,并在错误信息中解释回退规则。 完整示例:references/schema.md。
  • 在编写子 Agent 提示词时,绝对不要凭记忆改写或重建 findings schema。 逐字引用 references/schema.md 中的字段名 和象限值。重命名的字段(用 observation 代替 statement)或缩写的象限(用 fact 代替 known-known)会导致 该子 Agent 产出的每一条记录都验证失败 —— 更糟的是, 如果错配发生在顶层键而非内部字段,还会被静默丢弃。
  • 绝对不要创建单独的状态或缓存文件(.epistemic-map-state.json 或类似文件)。所有状态都存在于 EPISTEMIC-MAP.md 自身的渲染文本中, 由 merge_epistemic_map.py 在每次运行时解析回来 —— 如果某次运行 似乎需要别处来持久化状态,请参阅 references/schema.md。
  • 绝对不要在 EPISTEMIC-MAP.md 中截断、概括或用占位符 替代完整内容 —— 不要写 "N total, see X for the complete list",不要写 "showing top K of N",不要指向 consolidated.json、backlog.md 或任何不是 EPISTEMIC-MAP.md 本身的文件。该文件是自包含的;合并脚本 产出的每一条记录都属于它,无论这会让文件多长。 条目数量庞大绝不是概括的理由 —— 参见上文 "关联与去重", 了解为什么规模并不要求 Agent 亲手持有或撰写条目。
  • 绝对不要创建中间的 "consolidated" 或 "merged" findings 文件。 通过多个 --findings 参数,把每个子 Agent 的草稿文件直接 传给单次 merge_epistemic_map.py 调用。 脚本负责关联;没有任何东西需要预先合并。
  • 绝对不要手工起草或重构 EPISTEMIC-MAP.md。 每次写入都要 经过 merge_epistemic_map.py —— 手工编辑不仅看起来 不一致,还会在下次运行时解析失败。
  • 绝对不要向 EPISTEMIC-MAP.md 本身添加 "Related Documentation" 小节。 链接是单向的:AGENTS.md/CLAUDE.md 指向 此文件,而非反向。
  • 绝对不要用相对路径,或在被扫描的仓库内部,写入草稿或 findings JSON。 只能使用绝对 /tmp/... 路径 —— merge_epistemic_map.py 直接拒绝相对路径。
  • 绝对不要发明没有证据支持的条目(Facts、Questions、 Assumptions)或推理链(Risks)。将其标记为 INFERRED 而非 断言其确定性,并且绝不要为了某个发现看起来更可靠而 静默提升置信度。
  • 绝对不要自动爬取 openspec/changes/archive/ —— 只有在用户 明确点名时才读取特定的归档文件。
  • 在有子 Agent 可用时,绝对不要串行运行子 Agent 提取 —— 同时为每个来源派发一个子 Agent,每波最多 5 个。

象限测试

每个候选观察项都按顺序经过以下分类。在第一个 YES 处停止。

CANDIDATE OBSERVATION
        |
        v
Q1: Backed by evidence (data, contract, test result,
    executed code path, explicit recorded decision)?
        |
   YES -+----------------------------> KNOWN KNOWN (Fact)
        | NO
        v
Q2: Is the gap explicitly raised somewhere (a TODO,
    an open question in a doc, a ticket, an unresolved
    thread)?
        |
   YES -+----------------------------> KNOWN UNKNOWN (Question)
        | NO
        v
Q3: Does the code or docs silently rely on this without
    anyone stating it outright?
        |
   YES -+----------------------------> UNKNOWN KNOWN (Assumption)
        | NO
        v
             (handled separately, see Synthesizing Risks below)
                                    -> UNKNOWN UNKNOWN (Risk)

references/quadrant-test.md 包含完整版本,其中有每个象限的 完整示例,以及一张重定向表,用于处理那些看似认知、 实则不然的内容(团队偏好、已确定且已存在于 CONSTRAINTS.md 中的事实、 术语表词条)。在首轮提取波次之前先阅读它。

注意这个测试在结构上意味着什么:Facts、Questions 和 Assumptions 都可以由子 Agent 仔细阅读单个来源后发现。Risks 则不行 —— 如果 单个来源就陈述了该风险,它早已是 Question 或 Assumption。Risks 只有同时审视全局图景时才会显现, 这就是为什么它们在下文有自己单独的一轮,而不是从 同一提取波次中产出。

发现来源

两种来源类型,均在范围内:

  • 文档。 自动扫描常规文件夹 —— docs/、documents/、 README.md 及类似文件 —— 与 constraints-extractor 的发现行为相同。 接受用户添加的显式路径。默认排除 openspec/changes/archive/;它是历史记录,而非当前知识。本次扫描还要排除 CONSTRAINTS.md、JARGON.md、 ARCHITECTURE.md、AGENTS.md/CLAUDE.md 以及 EPISTEMIC-MAP.md 本身 —— 它们会在下文单独读取以进行交叉引用, 而不是作为来源被重新提取。一个宽泛的文档 glob 若把这些也一并扫进来, 会产生循环或重复条目(重新“发现”一个已经是经过验证的 CONSTRAINTS.md 条目的事实),并浪费一个 subagent 的处理轮次 在已知内容上。
  • 代码库。 原型在代码中承载的隐性知识比在文字中更多 —— 一个硬编码的区域、一个 // TODO: handle retries、一个静默吞掉错误的 try/catch、一个没人解释的配置值。识别 主要源代码目录(src/、lib/、app/,或仓库实际使用的任何目录), 并将每个顶层模块或包视为其自身的 来源以进行提取。

按上文所述,预先读取任何现有的 CONSTRAINTS.md、JARGON.md、 ARCHITECTURE.md 和 AGENTS.md —— 它们为已知的已知提供种子,并告诉你不要重述什么。这次读取与上文的来源发现 glob 彼此独立 —— 在同一轮中将它们作为并行/批量工具 调用发出,而不是一个接一个。 二者都不依赖 对方的结果,而将两个廉价、独立的操作串行化只会增加 延迟,毫无益处。

可选的访谈环节。 传入 --interview 以在关联之后(见下文) 启用一个简短、有针对性的问题阶段,适用于原始构建者 仍可联系的情况。默认关闭 —— 不要要求必须有一次访谈会话才能 产出有用的地图,因为当此流程运行时构建者可能已经不在了。

提取事实、问题和假设

在编写任何 subagent 提示词之前,完整阅读 references/schema.md。 每个 subagent 的任务说明必须逐字引用该文件中的确切 JSON 结构 —— entries 顶层键、字段 名(statement、evidence、impact、confidence —— 绝不是 observation 或另一个同义词),以及象限值必须完全写作带连字符的 known-known / known-unknown / unknown-known 字符串,绝不用像 fact 或 question 这样的简写形式。不要凭记忆或 凭本文件对它的概述来重建该 schema —— 在这里改述正是 字段被改名或象限在 subagent 输出中被缩短的原因,而 merge_epistemic_map.py 严格依据所写 schema 进行校验, 而不是对照一个接近的近似版本。

每个来源生成一个 subagent —— 每个文档一个,每个顶层代码模块一个 —— 每波最多 5 个并发,与 本技能家族其余部分保持一致。 每个 subagent 对其来源中的所有内容应用 Q1/Q2/Q3 测试,并 仅回报 Q1 到 Q3 象限中的候选者扁平列表。 风险不在本波范围内;一个只查看一个文件的 subagent 无法 分辨真正的系统性风险与孤立的异常。

每个事实、问题和假设都需要至少一个证据指针 —— 一个文件(从项目根目录起的相对路径,而非绝对路径)、一个行 范围或章节,以及对其内容的一段简短引用或改述。没有 证据,就没有条目。这与 constraints-extractor 一致:歧义没问题, 无支撑的主张不行。

Subagents 仅将草稿发现写入绝对路径(例如 /tmp/epistemic-mapper/<source-id>.json)—— 绝不用相对路径,因为相对路径会 相对于仓库的工作目录解析,并在根目录留下零散的 JSON 文件。 如果即将使用相对路径,则拒绝并重新请求。

关联与去重

使用 scripts/merge_epistemic_map.py 合并本轮的发现结果,它会处理机械性的部分:ID 分配、依据 references/schema.md 中别名表进行类别 slug 规范化、跨来源的重复项折叠,以及排序。这不是判断性工作——不要手工完成,也不要预先合并。

一次调用,直接传入所有暂存文件。 每个子 Agent 的暂存文件(来自每一轮)加上风险综合文件,都要作为多个 --findings 参数传入脚本的单次运行:

python3 scripts/merge_epistemic_map.py \
  --findings /tmp/epistemic-mapper/doc-1.json /tmp/epistemic-mapper/doc-2.json ... /tmp/epistemic-mapper/risks.json \
  --output-dir /absolute/path/to/repo/root

没有手动预合并步骤,没有中间“汇总”文件,也没有理由需要它——脚本直接从磁盘读取每个发现结果文件,并自行完成去重。这意味着条目数量从不是编排 Agent 需要放在自身上下文中或手工编写的东西:13 个源文档产生 106 个总候选,就是脚本读取 14 个小文件并写出一个输出文件,与 2 个来源产生 4 个候选的编排工作量相同。如果一次运行感觉要写出的内容“太多”,那就是脚本没有被直接用于原始暂存文件的迹象——停下来,改为直接这样调用,而不是做总结。

合并时,为每个条目分配一个置信度层级:

  • CONFIRMED —— 两个或更多独立来源一致。
  • INFERRED —— 单一来源,且解读合理。
  • CONFLICTING —— 来源之间存在分歧。不要把它们平均成一个虚假的中间值;保留两种解读并标记出来。Facts 象限中的一个 CONFLICTING 条目本身就是值得升级处理的矛盾——两份文档对同一 API 给出不同的速率限制,这本身就是一项发现。

综合风险

在关联之后,对完整的合并图景运行一次专门的检查——不是按来源,而是一次性覆盖所有内容。寻找只有从整体上才会显现的模式:

  • 三个独立模块都假定某种依赖以特定方式工作,而代码库或文档中从未确认过这一点。
  • 单一供应商、区域或凭据在任何地方都没有提到备用方案,尤其是在多个 Assumptions 已经围绕它聚集的情况下。
  • 失败交接中常见的结构性模式——外部调用没有错误处理、没有记录在案的回滚路径、没有负责人或到期时间的“临时”决策——而这个特定项目也表现出这些模式,无论这里是否有人这样说过。

因为没有来源直接陈述 Risk,证据引用不适用。相反,要写一条推理链:哪些观察合在一起指向该风险,以及为什么这种组合比其中任何单独一项更重要。这是唯一一个负担在于综合与判断而非引用的象限——要明确说明这一点,而不是编造一个并不存在的引用。

Risks 仍需要五个规范类别 slug 之一——绝不能使用新 slug。 综合出的风险本质上是跨领域的(这正是它成为 Risk 而不是归入某一来源下的 Fact 的原因),所以它常常会让人觉得应该得到一个比 references/schema.md 中五个类别更精确的标签。要抵制这种冲动。category 字段回答的是“后果落在哪里”,而不是“这是哪种风险模式”——跨三个交付物的级联进度依赖不是新的 schedule-cascade-risk 类别;如果未兑现的承诺是面向外部的,它就是 business-scope;如果是内部瓶颈,它就是 team-process。根据影响实际落在哪里,选择最接近的现有 slug,并把具体机制放在 statement 和 reasoning 文本中,精确性应属于那里。merge_epistemic_map.py 会拒绝猜测一个臆造的类别——关于这种情况的确切示例,请参见 references/schema.md 中的示例。

组装待办清单

merge_epistemic_map.py 会自动生成认知待办清单——3-5 个行动项,按象限和严重程度排序(风险和假设通常优先于问题,因为未审视的盲点对新团队来说比已知的开放问题更危险)。它始终作为 ## Epistemic Backlog 部分落在 EPISTEMIC-MAP.md 本身内部。

不存在单独的 backlog.md 文件,也永远不应该有。 不要创建一个,不要引用一个,不要写“参见 backlog.md”来代替实际的检查清单。如果待办清单需要变成真正的工单而不是检查清单,可以在有票证创建器技能可用时,提议将顶部项目交给它——但那产生的是工单,不是 markdown 文件,而且这只是一个提议,从不自动执行。即使提议被拒绝,EPISTEMIC-MAP.md 内的检查清单也始终独立成立。

预览与写入 EPISTEMIC-MAP.md

EPISTEMIC-MAP.md 是一个自包含文件,而且是纯 markdown —— 没有 frontmatter、没有隐藏块、没有配套的状态文件。合并所需往返的每个字段 (id、category、confidence、evidence、impact、history、severity、first-seen date)都直接折入 每个条目自身渲染出的行中 —— 每个条目的确切格式见 references/schema.md。 merge_epistemic_map.py 在下一次运行时把同一段文本解析回来作为状态, 这正是 constraints-extractor 已经在用的做法。两个文件(或者一个可见块加一个隐藏块)正是 交接产物被复制、移动或审阅时只留下一半,或者 毫无理由地重复携带同一份内容的方式 —— 本技能早期的草稿用一个 YAML frontmatter 块做的恰恰就是 这件事,让文件体积大约翻了一倍,却没有增加任何信息。不要再把它加回来。

merge_epistemic_map.py 是这个文件被写入或修改的唯一正当方式。 绝不要 直接手写或重构 EPISTEMIC-MAP.md —— 一个标题都不行,条目内的单独一行也不行。按 references/schema.md 把发现写入 scratch JSON,然后运行脚本。 该文件在每次运行时都会根据脚本解析回来的内容完整重新生成;手改不仅无法在重跑后保留, 还会让下一次运行完全无法解析该文件,这是有意为之 —— 见上文的“NEVER Do”。 该文件的结构正是脚本所产出的内容 —— Facts、Questions、Assumptions、Risks、Epistemic Backlog,仅此而已。 如果某项任务看起来需要不同的章节(交叉链接说明、摘要文件, 任何不在该列表中的内容),那是一个信号:应该在对话中说出来, 而不是把它加进文件里。

写入前始终先预览,包括对已有文件的小幅更新 —— 与本技能家族其余部分相同的约定。 在重跑时,脚本直接从既有文件自身渲染出的条目中读取先前状态 (没有其他地方可读取),并标记:

  • Promotions —— 一个现在有了证据的 Assumption 或 Question 会移动 象限,并且该条目会新增一行 History (DATE): was -> became — reason。 一个条目的所有 history 行都会保留,而不仅仅是最新的那行。
  • Resolutions —— 一个已被回答的 Question 或一个已被修复的 Risk 会被标记为 resolved 并附一条简短说明,而不是被悄悄删除。 关于曾经不确定之事的机构记忆是有价值的。
  • Dismissals —— 一个经调查后发现并不适用的 Risk 会被标记为 dismissed 并保留推理过程,原因相同。

绝不静默覆盖状态。如果一次运行发现零个新增或变更的条目, 请在执行摘要中直白说明,而不是为了显得有产出而给文件注水。如果 EPISTEMIC-MAP.md 存在, 但与 merge_epistemic_map.py 自身会渲染出的内容不一致 —— 手改、缺少某个字段的标题、 本技能旧版本留下的基于 frontmatter 的旧文件 —— 脚本会拒绝运行,而不是去猜测 先前状态并静默丢失 promotion 历史。要把这个错误暴露给用户, 而不是绕过它。

交叉链接到 AGENTS.md / CLAUDE.md

这是"脚本拥有该文件"的唯一例外——对另一个文件进行单独的、一次性的散文式编辑,在 EPISTEMIC-MAP.md 已经写入并预览之后进行。它从不触及 EPISTEMIC-MAP.md 自身的生成过程。

如果目标位置存在 AGENTS.md 或 CLAUDE.md,检查其中是否已有引用 EPISTEMIC-MAP.md 的"相关文档"章节(或等效内容)。如果没有,提议按该文件已有的风格追加一行引用——与 jargon-extractor 链接 JARGON.md、constraints-extractor 链接 CONSTRAINTS.md 的方式保持一致。这是纯粹的散文式编辑,不是配套脚本:只需一次存在性检查,而非大批量排序或去重,因此这里没有持续性的机械工作来证明需要一个脚本。

AGENTS.md/CLAUDE.md 在每次 Agent 交互时都会被读取,因此放在那里的指针不是被动的脚注——无论 Agent 之后是否打开 EPISTEMIC-MAP.md 本身,它都会在文件每次加载时浮现。措辞上要推动"真正被查阅",而不只是可被发现:

- **EPISTEMIC-MAP.md**: What's proven, what's assumed, and what's still unknown about this project; most relevant during handoff.
  _(Check this before making changes in an area flagged as an open Question or Assumption — treat an entry there as something to verify, not something to rely on)_

链接是单向的——参见上面的 NEVER 列表规则。AGENTS.md/CLAUDE.md 通过此步骤指向 EPISTEMIC-MAP.md,绝不反向。

参考文件

  • references/quadrant-test.md — 每个象限的示例,以及针对近似情况的重定向表(分诊测试本身就是上面的流程图)。提取前阅读。
  • references/template.md — 用于创建新文件的完整 EPISTEMIC-MAP.md 骨架。
  • references/schema.md — 子 Agent 写入临时文件的 JSON 结构、类别别名表,以及 merge_epistemic_map.py 期望的结构。分派子 Agent 前必读——将其确切的字段名和象限值引用到子 Agent 提示词中,而不是转述;参见上面的"提取事实、问题和假设"。

评论

支持 Markdown