关于 mii-ig-migration
将 MII KDS FHIR 实施指南迁移到模块模板的技能,提供部分自动化并要求人工审核关卡。它盘点源内容与身份信息,支持 SUSHI 项目和原始 FHIR 资源仓库,且不会发布更改。
主要功能
- 01支持 SUSHI 与原始资源路径
- 02盘点源工件与叙述内容
- 03恢复并保留模块身份信息
- 04固定版本的 SUSHI 与 goFSH 工具链
- 05强制人工审核关卡
适用场景
- 01将 Simplifier IG 迁移到模块模板
- 02把原始 FHIR 资源转换为 FSH
- 03检查已有的模板迁移状态
- 04在迁移完成前审查内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forschungsgruppe-digital-health/ agent-skills --skill mii-ig-migration安装后在 Claude Code 中输入/mii-ig-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/codes.mdexpected-steps.tsvfql-crosswalk.mdfql-rules.tsvmigration-report-template.mdmigration-spec.mdprovenance.mdrouting-table.tsvtemplate-artifacts.tsvtemplate-pages.tsvtriggers.mdreferences/licenses/CC-BY-4.0.txtCC0-1.0.txtscripts/autofix-fix.pyautofix-loop.shcomparison-table.pyderived-scan.pyfql-scan.shgen-page-title-po.pygofsh-results.shguide-harvest.shguide-page-to-md.pylicense-align.pymigration-log.shpackage-identity.shpage-structure-advice.pyparent-snapshots.shpostprocess-gofsh.pyprepost-delta.pyqa-checklist.py详细介绍
将 MII KDS 模块 IG 迁移到模块模板
此技能支持并部分自动执行迁移。它绝不会发布内容,且必须经过 4 个人工审核关卡。完整流程及每个步骤的验收标准见迁移规范;本文是操作摘要。
前置条件
了解上下文:不要假设任何信息,也不要创建缺失的内容。
-
源指南。 有两项输入只能由人工提供,无法推导:已渲染 Simplifier IG 的 URL,以及其源 GitHub 仓库的 URL。若缺少任一项,请询问并停止。有关模块身份的其他所有信息都应通过读取获取,而不是询问(步骤 2)。
-
模块仓库——判定源结构。 有两种适用的结构,采用不同路径到达同一目标;第三种则不适用。先作出判定并记录(规范 §5.1b)。
- 结构 A — SUSHI / IG-Publisher 项目。 仓库根目录下有
sushi-config.yaml或ig.ini,且有input/fsh/—— 这正是让此技能具备可移植性的 FHIR IG 生态系统约定。FSH 已存在;步骤 4 会迁移它。如果input/fsh/存在但为空 → 报告此情况;没有可迁移产物的迁移属于配置错误,而不是空操作。 - 结构 B — 原始 FHIR 资源仓库。 没有脚手架——检查深度为 3(规范 §5.1b):如果 SUSHI 项目嵌套在根目录下(strukturdaten),则属于结构 A(嵌套),需通过
decision行重新指定根目录——用 goFSH 处理会连同比例尺一起丢弃作者的 FSH。 这是在 Forge 中编写并发布到 Simplifier 的模块的常见状态,完全适用——这是此技能所要处理的最典型情况。步骤 2b 使用 goFSH 推导 FSH;从步骤 3 起,两条路径完全相同。按内容而非文件夹名称检测(规范 §5.1b.1):文件可解析且包含resourceType,才是 FHIR 资源;文件夹名称由人工随意选择,且经常是德语,因此按常规名称编写的 glob 无法找到它们。 已渲染指南的叙述内容位于 Simplifier 上,而非仓库中——仓库没有implementation-guides/**树,因此步骤 1 会从已渲染的 IG 获取页面结构,而fql-scan.sh在迁移前因目标集为空而正确地以代码 2 退出。不在 Git 中不代表无法获取:步骤 2c 会采集这些内容,跳过此步骤就会以模块名称发布模板的起始页面。也不能据此认定“仓库中没有叙述内容”:参考模块包含一个 43 行的德语README.md和一个 126 行的 CodeSystem 镜像。请盘点所有包含叙述内容的文本文件,并逐一确定其处理方式。 - 两者皆非——既没有脚手架,也没有 FHIR 资源:这不是 FHIR IG 项目。请说明这一点并停止。**不要为其搭建脚手架。**路径 B 并无例外:它不会创建任何产物,只会从已有资源推导 FSH,且每项 FSH 都能追溯到源文件(护栏 3)。
- 结构 A — SUSHI / IG-Publisher 项目。 仓库根目录下有
-
目标模板。 本步骤仅确定模块处于哪种状态,脚手架将在流程的第 3 步引入。无论处于哪种状态,都应读取目标版本下的
medizininformatik-initiative/mii-kds-module-template,不要依赖本技能对它的描述。- 已使用模块模板——
ig.ini中的template条目指向 MII 模板(仓库 URL、已发布的软件包 ID,或旧版#ig-template);自带的ig-template/文件夹表示这是 2026-08-28 之前的脚手架。对于重新迁移:先报告现有内容,再进行任何更改。 - 纯 Simplifier 项目——只有 Simplifier 文件(
.simplifier/、project.yaml、implementation-guides/),没有 IG-Publisher 脚手架。这是通常的起始状态。 - 混合状态,或使用其他模板——Simplifier 文件旁还有 IG-Publisher 文件:
ig.ini指向其他模板、_genonce.sh等、已提交的fsh-generated/或渲染输出(可能位于 GitHub Pages 上)。这仍属于迁移。清点这些内容并记录哪些文件会被模板替换(ig.ini、_gen*/_update*),哪些内容需要转移(input/、fsh-generated/),哪些只能在 Gate D 之后弃用。还要列出任何无法识别的顶层条目(例如validator/),并提出保留或弃用方案——只列出,不要删除。
- 已使用模块模板——
-
未替换的占位符。 只有替换所有
{{...}}占位符后,模板才能构建;未替换的占位符会生成错误的构建产物,而不会明确报错。迁移前后都要对整个目录树执行 grep,查找{{,并说明每处命中的处理情况——排除.github/**(Actions${{ … }}会匹配此模式),叙述性源文件中的 Simplifier 指令视为已说明(属于第 5 步材料)。按排除项清查(grep -rIl '{{' . --exclude-dir=.git),不要使用--include列表: 后者会漏掉ig.ini,其 slug 曾导致一次发布器运行失败(PROs try-run)。 -
工具链——仅通过带版本固定的
npx调用 SUSHI 和 goFSH。 通常不会预装这两个工具(参考机器上运行which gofsh无结果),因此,裸写sushi/gofsh无法运行,也不应出现在本技能中:应写作npx --yes [email protected]和npx --yes [email protected]——SUSHI 的 npm 软件包名是fsh-sushi,不是sushi。所谓“不要获取工具链”规则保护的是精确且有记录的版本,版本固定可以提供这一保障,未固定版本的npx则不行;将版本固定作为记录,写入日志的cmd=词条中。(allowed-tools授予Bash(npx:*);Bash(gofsh:*)永远无法匹配npx命令行。)对于形态 B,必须使用 goFSH;对于形态 A,仅当源文件包含 JSON/XML 时才需要。第 7 步起需要 IG Publisher。缺少 node/npx 时,说明缺少哪一项,然后在第 2 步后停止。没有快照的上级软件包还需要 java 和版本固定的validator_cli.jar——仅在确实检测到这种情况时才获取(规范 §5.1b.5),不得手动替换。
流程
本文以英语撰写;所处理的产物是德语 KDS 文档,专业术语保留德语。输出语言遵循目标模板:英语为默认语言,德语为翻译语言。 这与旧约定相反——请参阅下文的 Language,并依据目标模板的 sushi-config.yaml 核实,不要仅凭本句判断。
先解析脚本路径。 下方命令中工具的路径均相对于本技能自身的目录,而非你的工作目录——工作目录是正在迁移的项目。设置
SKILL_DIR=<包含此 SKILL.md 的目录>(例如.claude/skills/mii-ig-migration)和ML="$SKILL_DIR/scripts/migration-log.sh",并在每次调用中使用它们。从项目根目录运行裸路径scripts/...不只是会运行失败:如果项目自身的scripts/中存在同名文件,它会静默运行该文件。
-
清点源内容 — 首先执行 Gate 0(规范 §9c):对尚未迁移的源内容运行同级工具
fhir-ig-analysis(bash "$ML" run 1 preflight-analysis …→migration-log/preflight-analysis.json);其产物计数决定经测量得出的 M9 决策和 §9b 中 CS 缺失的处理路径,special_url_prediction会预先生成 special-url 列表,而qa_baseline: None表示:现在获取源内容的 QA 证明。然后,从渲染后的 IG 和源代码库中提取每一项产物 (profiles、extensions、value sets、code systems、capability statements、examples)以及 叙述结构,并为每项记录其源路径 →migration-log/source-inventory.json。当implementation-guides/中包含多个指南目录树(版本 × 语言 + 共享资源 — 一个真实模块包含六个)时,按规范 §5.1a 执行:选定权威目录树,将并行语言目录树标记为采集种子,其余予以保留。 没有渲染后的 IG URL?请发现它 — 规范 §5.1c。bash "$SKILL_DIR/scripts/simplifier-discover.sh" --org ORG --module SLUG会依次遍历组织项目列表 → 软件包 → 项目 →/<project>/filterprojectguides(没有波浪号;带~的变体会返回 200,但不返回任何内容)→/published-guide/<key>/versions→ 服务端渲染的指南根页面,并在任何一步未返回内容时发出 WARN。固定一个已发布的只读版本,并像记录源代码提交 SHA 一样记录它 — 绝不能使用?version=current,因为那是实时可编辑项目。键和页面 slug 必须通过发现获取,绝不自行构造(自行构造的键会返回 404;渲染器会移除 slug 中的标点)。项目页面由客户端渲染,无法返回任何内容;指南页面则能返回所有内容 — 曾经将这一项测量结果泛化为整个平台的规律,正是该工具曾因此缺失此流程的原因。一个实例不能代表整个类别,反之亦然(规范 §5.1c.2a):对于声称某项产物具有的特性,必须在该确切产物上测量其是否缺失;在将某种结构编码为规范要求之前,必须在多个实例上测量,并选择最可能有所不同的实例 — 同一天读取的data-url="/guide/<key>",取自一个所有键都是裸键的模块,却会静默丢弃另外两个模块中的键(consent 为 3 个中的 3 个,mikrobiologie 为 3 个中的 2 个,person 为 3 个中的 0 个,三者退出码均为 0)。 -
读取模块的身份信息——不要询问,也不要编造。 从源代码的
sushi-config.yaml和package.json(若没有sushi-config.yaml,则从package.json和ImplementationGuide资源)读取title、packageId、canonical、status、releaseLabel、license、dependencies和publisher,并原样保留。若两者不一致,以sushi-config.yaml为准——构建读取的就是它;记录该值。两份文件中都没有的字段取自生成的ImplementationGuide;若所有来源均缺失,则在 Gate A 使用模板默认值。按规范 §2.1 解析浮动版本 (1.5.x),并记录选定版本及其依据。B 型结构通常没有这三个文件中的任何一个——仓库没有携带身份信息,并不意味着模块没有身份。 此时按以下顺序从多个来源中恢复身份信息(每个字段都记录其来源)(规范 §2.1;脚本会自行写入记录):- P — 已发布的包。
bash "$SKILL_DIR/scripts/package-identity.sh" --package ID --version V会记录packageId、version、description、fhirVersions、jurisdiction和依赖版本锁定值——这些是源证据,优先级高于任何dist-tags.latest——以及根据打包资源自身的 url 通过共同前缀推导出的canonical,必须一致,否则发出 WARN,绝不采用多数表决。任何清单都不包含title、license或publisher;author是注册表账号,不是发布者。 - R — 源代码仓库。
bash "$SKILL_DIR/scripts/repo-identity.sh" --dir DIR --repo OWNER/NAME --rendered URL会读取 LICENSE 文本中的 SPDX id——真实的许可证证据,该字段绝不能使用默认值(§2.2)——README 的首个标题(作为title候选值)、仓库描述,以及 release 标签;标签与 P 中版本的匹配可将该 release 与提交关联起来。无法识别的许可证文本不会产生任何值(license-text-unrecognized:);GitHub 所有者不是publisher。 - H — Simplifier。两个 URL 空间,两种答案——不可将其中一个的结果用于另一个(规范 §2.1.3)。项目页面(
simplifier.net/<Project>/)经测量为客户端渲染(HTTP 200,约 56 KB,52 个脚本标记,DOM 中没有身份元数据):它是 Gate A 的人工参考,用于查找机器来源中没有的信息,不是抓取目标;脚本会测量该页面,但不会提取任何内容。指南页面(simplifier.net/guide/<key>/<Root>[/<Page>]?version=<v>)属于不同的 URL 空间,且由服务器渲染——对 Consent 2026.0.0 的测量结果:根页面为 24509 字节,包含整个页面树(18 个页面链接);叶页面为 20481 字节,包含<h1 id="page-title">和真实的德语叙述。它们提供的是叙述内容,不是身份信息:按步骤 2c 抓取。
任何层级都无法提供的值都留给 Gate A——在参考模块中测得,只有
publisher是如此。 每个值都必须附上证据(bash "$ML" claim 2.1 ACTION FIELD VALUE TIER SOURCE), 若第二个来源提供了不同的值,就会触发identity-contradiction:——只报告, 绝不判定(实测示例:goFSH 的version: 1.0.8与包的2026.0.0不一致;源代码中的版本锁定值2.0.2与dist-tags.latest中的2.0.3不一致)。bash "$ML" claims --markdown会生成报告中的身份信息表。绝不根据恢复出的值修改现有元数据,即使恢复结果表明其存在不一致也不例外:恢复仅为 Gate A 提供证据。规范 §2.1.2–§2.1.4。 记录读取的每个值,以及每处不一致(级别为 WARN)。目标版本是唯一由人工决定的标识值:MII CalVerYYYY.n.n,而非 SemVer,默认与源版本相同。源文件与模板不一致时,以源文件为准——模板中的canonical和packageId模式适用于新建模块;更改已发布的 canonical 会破坏所有使用方。报告每处不一致,由人工决定;切勿静默规范化。这涵盖模板预填为字面值的每个值,尤其是上文的license:模板提供的是CC-BY-4.0,不会触发占位符检查,而 MII 模块通常声明CC0-1.0。重新授权必须由人工决定,绝不能作为默认操作。规范 §2.2。 - P — 已发布的包。
2b. 仅适用于源结构 B——从原始资源生成 FSH。 在骨架步骤之前运行,因为步骤 3 会将骨架合并到必须已存在的 FSH 中。在模块仓库之外的临时目录中操作;结构 A 跳过。此处以规范 §5.1b 为准(使用 goFSH 2.6.1 和 SUSHI 3.20.0 对 medizininformatik-initiative/kerndatensatzmodul-consent 进行测量)。
mkdir -p migration-log
ML="$SKILL_DIR/scripts/migration-log.sh" # run-log helper — see *Run log* below
SRC=<source-repo-root>; OUT=<scratch-dir>; GLOG=migration-log/gofsh.log
SUSHI="npx --yes [email protected]"
E() { grep -oE '[0-9]+ Errors' "$1" | tail -1 | cut -d' ' -f1 | grep . || echo n/a; }
bash "$ML" begin "step 2b — Path B on $SRC" # run boundary: re-runs stay separable
rm -rf "$OUT" # goFSH refuses a non-empty -o dir; $OUT is derived, so clearing is safe
N_IN=$(find "$SRC" -type f \( -name '*.json' -o -name '*.xml' \) \
-exec grep -lE '"resourceType"[[:space:]]*:|xmlns="http://hl7\.org/fhir"' {} + \
| wc -l | tr -d ' ') # inputs, BY CONTENT
bash "$ML" info 5.1b.2 gofsh-input "inputs=$N_IN src=$SRC"
bash "$ML" run 5.1b.2 gofsh-convert --raw-log "$GLOG" -- \
npx --yes [email protected] "$SRC" -o "$OUT" -s file-per-definition -t json-and-xml \
-d <parent-ig-package>@<version> -d [email protected]
GOFSH_EXIT=$?
bash "$SKILL_DIR/scripts/gofsh-results.sh" --log "$GLOG" --inputs "$N_IN" \
--exit $GOFSH_EXIT # <- the mandatory WARN fires here
# A failed conversion is a stop: everything below measures $OUT (spec §5.1b.2).
[ "$GOFSH_EXIT" -eq 0 ] || { bash "$ML" error 5.1b.2 gofsh-convert \
"conversion failed — not measuring \$OUT; fix the cause and re-run"; exit 1; }
bash "$ML" run 5.1b.3 sushi-before --raw-log migration-log/sushi-before.log -- \
bash -c "cd '$OUT' && $SUSHI ." # the 41 of "41 -> 5"
B=$(E migration-log/sushi-before.log); bash "$ML" info 5.1b.3 sushi-before "errors=$B"
bash "$ML" run 5.1b.3 postprocess-gofsh --emits-runlog -- \
python3 "$SKILL_DIR/scripts/postprocess-gofsh.py" "$OUT/input/fsh" --gofsh-log "$GLOG"
bash "$ML" run 5.1b.3 sushi-after --raw-log migration-log/sushi-after.log \
--expected-nonzero 'shape B: unresolvable parents are a Gate-A escalation (§5.1b.4)' -- \
bash -c "cd '$OUT' && $SUSHI ." # the 5
A=$(E migration-log/sushi-after.log) # n/a when a run printed no count (crash/kill)
case "$B$A" in *n/a*) R="resolved=not-measured";; *) R="resolved=$(( B - A ))";; esac
bash "$ML" info 5.1b.3 sushi-after "errors=$A $R before=$B"
按原样依次从同一目录运行:后处理器会读取 $GLOG,而 E 辅助函数会从 SUSHI 原始日志中取出每个错误计数,并写入 INFO 行——这组命令就是 41 → 5 的证据,且证据就在代码块中,而不只是在规范里。gofsh-results.sh 会读取 goFSH 自带的 RESULTS 表,为每个单元格添加标签,只统计已转换的资源(绝不统计 Invariants/Mappings/Aliases),并与 $N_IN 核对。这项核对正是整个代码块的目的——goFSH 的退出代码并非判断信号,计数才是;run 会保留每次实际的退出状态,并在每次调用时截断各原始日志,确保重新运行时测量的是本次结果,而不是两次运行的总和。
-
将 goFSH 指向仓库根目录,而不是某个资源文件夹。 参考模块将其 20 个资源保存在 五 个手动命名的目录中,无需暂存:经测量,goFSH 会递归遍历目录树,从根目录生成的 FSH 与从暂存的扁平目录生成的 FSH 逐字节相同。只有在有记录的理由时才进行暂存(规范 §5.1b.2)。
-
必须使用
-t json-and-xml;缺少该参数会静默失败。 goFSH 默认使用json-only:对于 Consent(19 个 XML + 1 个 JSON),不带该参数运行时退出码为 0,报告“0 Errors”,但只转换了一个资源,且仅警告“缺少 JSON 对应项的 XML 定义有 13 个”——这是 goFSH 自己统计的数量,并非输入中的 19 个文件(差异来自六个SearchParameter);务必说明所指的是哪个数字。真正用于判定的数字也不是这个,而是converted 1 of 20,gofsh-results.sh会输出并对其发出警告。务必与步骤 1 的清单核对,绝不能依据退出码。 -
**使用
-d <package>@<version>声明每个外部父 IG,**其信息来自资源自身的baseDefinition规范 URL;goFSH 提示“cannot find a definition for its parent … declaring that IG as a dependency”即为信号。应重新运行,而不是修补缺少依赖的输出——两者结果不同(未声明时有 12 个 mappings / 14 个 aliases,声明后有 0 / 8 个)。Forge 仓库不会提供包名或版本:应根据 FHIR 包注册表解析(规范 §5.1b.2);未找到即停止 Gate A。 -
goFSH 会自行写入
sushi-config.yaml,但它只是起点,并不代表身份信息正确:缺少id/name/title/publisher/packageId/license,version不可信(实测为1.0.8——来自某个 profile,而模块发布版本为2026.0.0),且dependencies仅包含你通过-d设置的内容。应按步骤 2 恢复身份信息。 -
脚本的两轮处理都是机械性的:处理
fhir_comments规则,以及未加引号的代码引用系统(其名称含空白字符),并使用 goFSH 自己报告的名称进行修复。脚本会在写入前分类;遇到不支持的结构时不会写入任何内容,并且具有幂等性。应提供完整的 FSH 目录树——缩窄路径后无法看到它要核对的声明,因此脚本会拒绝运行(退出码为 1;该状态由run返回,而tee会将其隐藏)。 -
随后 SUSHI 必须编译通过,只有确实无法解析的父项可以例外——实测错误数为 41 个,修复后为 5 个,两者都由上方代码块记录。解析错误会导致 SUSHI 停止读取文件剩余部分,但仍会导出该实例,造成静默截断(“已导出”不等于“已转换”):之前三个 Consent 示例各包含 1 个嵌套 provision;之后分别为 27 / 6 / 3 个。SUSHI 的退出状态就是错误数,因此
sushi-after退出码为 5——这是预期的形状 B 结果,--expected-nonzero会将其记录为升级处理,而非失败。 -
父包不包含 snapshots 时会阻碍导入——SUSHI 完全无法读取此类父包,导致相应 profiles 及所有声明
InstanceOf这些 profiles 的实例无法处理。先检测,再使用真正的生成器生成 snapshots;绝不可手工制作(规范 §5.1b.5):bash "$SKILL_DIR/scripts/parent-snapshots.sh" detect --package ID --version V会统计数量(实测:两个候选版本中均有 21 个 SD、0 个 snapshots;换个版本也无法解决),而… build … --validator validator_cli.jar --install --require <parent-url>…会调用官方 HL7 生成器(java -jar validator_cli.jar snapshot、ProfileUtilities),验证每个结果(如果 snapshot 的 element 数量只与 differential 相同,则该结果有误,会被拒绝),并安装新的缓存条目<id>#<version>-snapshots——绝不会覆盖上游内容。生成器拒绝处理说明上游存在缺陷,应升级处理,不可手动补全;重建对 CI 的成本(仅本地使用)由 Gate A 决定。近似模拟合并——切片、基数、element 顺序——会伪造父项(护栏 1 和 3)。随后重新固定版本,再运行 SUSHI,并记录两次错误数:Consent 的实测结果为 5 → 0。 goFSH 臆造的 id 和以 GUID 命名的文件应加入 ② 队列:生成的 id 会成为模块自身的 id,因此由 Gate A 确认。 -
验收标准:数量与清单一致;脚本退出码为 0;每个剩余的 SUSHI 错误都对应一个已命名的、无法解析的父项升级事项;所有内容均记录在
run.log中。路径 B 本身不会生成干净的构建结果,因此下方所有“干净构建”标准(步骤 3 和 7,验证部分)应用于形状 B 时,须遵循形状 B 限定条件(规范 §5.1b.4):不得有残留的机械性错误;每个剩余错误都须列入 ① 队列并由 Gate A 决定;不得伪造父项。容忍错误数量不等于通过。 2c. 当叙述内容不在仓库中时,采集叙述内容——始终适用于形态 B,也适用于页面托管在平台上的形态 A。规范 §5.1d 具有规范性(§5.1c 找到了指南)。来源按可信度从高到低排序,“什么都没有”不是第三种选择: -
① 经身份验证的项目下载——只要有凭据,就优先选择。
https://simplifier.net/<project-slug>/$actions/downloading会获取项目,其中包含作者撰写的叙述 Markdown,但需要登录 Simplifier:实测匿名访问会返回登录页面(/login?ReturnUrl=…),且所有经过探测的匿名替代方式都返回 404 或 HTML,因此不存在已验证的匿名项目下载方式。由人工在自己的浏览器中登录、下载归档,并将其保存到仓库外的路径;Agent 读取该文件并记录narrative-source=project-download。绝不臆造凭据机制、索要密码或存储令牌——访问门槛正是关键所在。若无人可操作:记录project-download-unavailable:,然后转到 ②。 -
② 指南采集——匿名、已验证,且为渲染结果(指令已展开;渲染后的产物视图并非它所渲染的资源)。直接调用;它会输出自己的运行日志行:
bash "$SKILL_DIR/scripts/guide-harvest.sh" \ --guide-url "https://simplifier.net/guide/<key>/<Root>?version=<version>" \ --out migration-log/guide-harvest/pagecontent --keep-html migration-log/guide-harvest/html它会根据根页面自身的
href值发现页面树——读取 slug,绝不自行构造(“Anwendungsfälle / Informationsmodell” →AnwendungsflleInformationsmodell),为每个页面固定?version=,通过逐层扫描<div>/</div>隔离各个内容区域<div id="preview-content">(若用正则匹配到下一个</div>,会在第一个嵌套项处截断;没有该区域的页面会跳过,绝不转换整个页面),写入带来源标头和TODO:REVIEW的 Markdown,并将每个页面归类为narrative或artefact-view。--out没有默认值——这是第 5 步的输入,而非模板的页面集。 验证是关键所在:每个发现的页面都会记录在migration-log/guide-harvest.tsv中,状态为已采集并附带计数,或因具体原因跳过;如果已采集数量小于发现数量,ratio会触发强制silent-partial-success:WARN;系统会统计并发出 WARN,记录每个页面中未能保留在转换结果里的源文本片段数量(missing_runs=)——对于产物视图,该信息会使用单独的generated-view-lossy:标记,以免生成的表格掩盖实际内容损失。在 Consent 2026.0.0 上的实测结果:发现 18 个页面,采集 18 个,跳过 0 个,没有叙述页面缺少内容;其中 14 个叙述页面、4 个产物视图,3 个资源文件。退出码 0 = 无问题;1 = 有内容被跳过或缺失。 -
③ “什么都没有”不是第三种选择。 对无法访问的来源予以报告,升级至 Gate B 并明确指出。
注册表包的职责没有变化:提供资源和身份信息(第 2 步),不包含叙述内容——它是验证采集页面集的依据。采集到的页面与其他所有来源形态遵循同一份契约:5.4c page-routing 建议运行会读取清单(--harvest-tsv,从目标的 migration-log/ 自动发现),并生成相应的映射行——采集跳过的页面会阻止覆盖率验证,直到采集完整无缺,或由人工在审核后的映射中将其退役——第 5 步只使用映射(规范 §9/§9f):绝不为每个采集页面创建一个目标页面,并按 Language 放置语言内容。
3. 创建骨架(规范 §5.2)。迁移原地进行:在模块现有仓库的工作分支上,使用前置条件 3 中检出的模板搭建骨架,并运行首次启动引导程序——不要新建仓库;历史记录、问题和使用者都保留原处(新建仓库是人工决定,须记录在报告中,绝不能作为默认操作)。迁移后的模块中不得 vendoring 模板(规范 §5.2,决策 2026-08-28):保留骨架 ig.ini 中的仓库 URL template = 行;如果检出的骨架仍包含 ig-template/ 和同步机制(sync-ig-template.yml、sync-ig-template.sh、resolve-ig-template-source.sh),则删除它们,并记录两条来源信息——5.2 skeleton-vendored ref=<module-template tag> commit=<sha> 和 5.2 template-reference url=<repo url> release=<latest ig-template release>(P2 和 P1 会读取这些信息)。替换步骤 2 中读取的标识信息所对应的每个 {{...}} 占位符。使 LICENSE 文件与 SOURCE 的许可证保持一致——license-align(调用方式见 Verification):使用源代码自身 LICENSE 文件的原文;若无,则使用所声明 ID 对应的 vendored 官方法律文本(references/licenses/);每次替换都必须公布从 → 到(添加 license-replaced: 日志行,并在报告中添加一条 FIX 行)。对于工具不得擅自决定的情况——任何地方都没有许可证、某个 ID 没有 vendored 文本、源代码内部存在矛盾——退出码为 1,无法通过 Gate A:模板的 CC-BY-4.0 文本绝不能在无提示的情况下改变模块的许可证。模板的 CRMI meta.profile 声明要求依赖项 hl7.fhir.uv.crmi——将其添加到继承的源代码依赖项中,并在 Gate A 记录(这是模板机制,不属于源代码标识信息)。骨架中包含模块名称的文件名必须根据模块 ID 生成,绝不能根据仓库 slug 生成(ig.ini 中的 ig= 路径、ImplementationGuide-<id> 引用、步骤 6 的 .po):SUSHI 只会根据 id 生成 ImplementationGuide-<id>.json,不会使用其他信息,因此使用 slug 生成的名称可能会构建成功,但发布时却找不到 IG 资源——P5 中的 ID 与 slug 分类检查会捕获此问题。然后删除模板的示例产物(input/fsh/profiles/example-patient.fsh、input/fsh/instances/example-patient-instance.fsh——根据你实际检出的模板核对路径),以免与模块的真实示例冲突。复制模板的 FSH 骨架之前(input/fsh/aliases.fsh、input/fsh/rulesets/*),将其中的 RuleSet:/Alias: 名称与模块的 FSH 对比——以模块定义为准,绝不修改模块的 FSH:对共享别名文件(aliases.fsh)逐条定义处理(只追加模块中缺少的模板定义);对所有其他冲突的骨架文件按文件处理(整份跳过)。记录两份清单——覆盖模块的 aliases.fsh 曾导致一次真实迁移出现 234 个 SUSHI 错误。验收条件:bash "$ML" run 5.2 sushi-skeleton -- npx --yes [email protected] . 运行干净(形态 B:按步骤 2b 中的限定),且日志中包含跳过和追加清单。
-
**转移产物——保留结构。**移动 FSH 源文件时,保留源代码的目录布局;如果源代码只有 JSON/XML,则使用固定版本
npx --yes [email protected]进行转换——形态 B 已在步骤 2b 中完成此操作,因此此处移动的是经过后处理的输出。ID 和 URL 保持不变。**验收依据是路径,而非数量:**对源代码和目标代码中相对于仓库根目录的 FSH 路径列表排序后执行comm -3,结果应为空,但日志中列出的骨架新增文件除外——只有数量相同无法证明结构一致。 -
迁移叙述性内容。其来源是步骤 2c;此步骤负责映射。将主文档内容移入
input/pagecontent/*.md,并将 Simplifier 和 FQL 指令转换为 IG Publisher 对应形式:bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict扫描会递归进行,迁移前包括
implementation-guides/**;它会按目标记录已扫描的文件,若某个命名目录没有贡献任何文件,则发出 WARN;目标集为空时退出码为 2——切勿将“未扫描任何内容”理解为“未发现任何内容”。--strict在发现任何问题时退出码为 1;若tee报告 0,run会保留这两种状态。应用每条打印出的建议;映射见 FQL 对照表,规则见references/fql-rules.tsv。如有疑问,写入TODO:REVIEW。写入之前,必须为每个源页面确定路由——规范 §9e,匹配第一项即生效: 单个工件 →
input/intro-notes/<Type>-<id>-intro.md(德语镜像,文件名相同,显示在工件页面顶部);系列概览 → 已存在的索引页中的一个章节;已有页面经协商负责承载 → 合并;只有跨领域叙述内容才成为独立页面——达到 ≥3 个子项时创建一个 hub(≤250 个词,每个子项占一行),只有在菜单预算内才添加菜单项(≤33 个条目、每个下拉菜单 ≤10 个、顶层 ≤8 个、深度 ≤2),否则使用pages:嵌套并添加链接。篇幅门槛: 超过 2500 个词、合并超过 4 个来源,或出现任何重复标题时,都必须重新执行路由(PROs 已发布过一个长达 6214 个词的主机页面,包含 13 个冲突锚点)。路由由程序生成,而非人工判断——5.4c page-routing运行即为page-structure-advice.py的--map(调用方式见 Verification):它根据这些分支和语义路由表references/routing-table.tsv写入migration-log/page-map.tsv(v2:source_page⇥target⇥reason⇥branch⇥measure),验证是否覆盖全部源页面(权威指南树 ∪input/pagecontent∪ 目录中未被 toc 列出的磁盘页面 ∪ 第 2c 步的收集清单;在每个页面都有目标且每条 RETIRED 行都有原因之前,退出码为 1——悬空的 toc 条目和收集时跳过的页面都属于发现的问题),并根据 Gate 0 提出 M9/其他分类的决策。映射表就是合同(规范 §9f): 人工必须先审阅并编辑映射表,再写入任何内容;此步骤随后只使用映射表——未列入映射表的页面不得写入,映射表中尚未处理的行属于缺陷;第 7b 步会依据同一映射表进行检查。你编写的文本(概览、hub 单行说明、衔接内容)都是派生内容——按 §9d 标注,以便显示为审核框;运行5.4d derived-scan(写入migration-log/derived-content.tsv,C7 会读取此文件,且 ② 由此生成);移动或拆分的内容绝不能标注。§9 记录了参考模块的用例缺口:报告该缺口,绝不可自行填补。模板 ≥ v0.8 的目标要求:规范 §9a 对页面集合具有规范性——拆分页面、仅包含链接的 Conformance 集群 +
datasets-and-descriptions(内容按 §9a 重新路由)、不再添加标题(M10)、两项必须记录在运行日志中的决策:5.4a optional-page-decisions(M9——根据测量结果:工件包数量为 0 → 删除,为 > 0 → 保留,工件绝不删除;§9a)、5.4b security-privacy-decision(M11)。索引样式须保持一致(作者/联系人使用列表,免责声明使用正文)、(de) 翻译标记的措辞,以及可通过单次提交撤销的修复流程,均见 §9a;若缺少 CapabilityStatement,则应根据模块的 profiles 检测并建议添加,并按 规范 §9b 直接显示在其页面中。 -
设置双语页面。 英文是默认语言;德文译文是
input/translations/de/pagecontent/下同名的文件。这些页面确实会呈现。菜单文件是input/includes/menu.xml,并有一个按语言对应的镜像文件input/translations/de/includes/menu.xml— 切勿在sushi-config.yaml中设置menu:属性,因为它会与此设置冲突。资源译文是input/translations/de/下的.po补充文件;投入制作前,请检查目标 IG 的配方,确认哪些资源类型确实会呈现。仅有德文源文件时,方向则相反 — 参见下文的 Language。页面标题(面包屑、目录、<title>)— 完整配方见规范 §5.5。 发布器确实会本地化这些标题,使用单个 IG 级目录文件input/translations/<lang>/ImplementationGuide-<ig-id>.po(在加载时导入 IG 资源 — 不是资源补充文件,因此不受其类型限制)。在第 3 步运行 SUSHI 后,根据 SUSHI 生成的 ImplementationGuide 资源(权威标题集)生成该文件 — 菜单仅用作翻译种子:bash "$ML" run 5.5 gen-page-title-po --emits-runlog -- \ python3 "$SKILL_DIR/scripts/gen-page-title-po.py" \ fsh-generated/resources/ImplementationGuide-<ig-id>.json \ migration-log/menu-titles-de.txt \ de input/translations/de/ImplementationGuide-<ig-id>.pomigration-log/menu-titles-de.txt是必需参数,没有默认值(每行一个英文标题 => 德文标题;按规范 §5.5 编写该文件,并使用-表示“无种子”选项 — 无法解析的路径属于设置错误,绝不能静默地使用空种子)。重新生成不会造成破坏;空的msgstr表示尚未翻译,并会进入 ② 队列。易错点: 语言必须出现在translation-sources中,而不能只出现在i18n-lang中,否则所有.po文件都会被静默忽略。来自模板 v0.5.0 的模块还会移除其面包屑覆盖项。 -
构建与 QA。 先运行 SUSHI,再运行 IG Publisher — 两者都通过
bash "$ML" run 5.6 …执行,这样本步骤要生成的两个数字都会写入日志:SUSHI 的错误计数,以及复制到 INFO 中的qa.txt摘要行(规范 §5.6 中有对应代码块)。目标项目会在构建工作流的env:块中固定工具链版本 — 请从那里读取固定版本,而不要从本文件读取。验收标准:qa.txt报告Errors: 0,且每个示例都通过验证 — 形状 B:按照第 2b 步的限定,除列出的升级处理项外,其他所有错误仍须阻止验收。然后使用fhir-ig-analysis运行同模块验证(先测量未迁移的源,再测量迁移后的树 — POSTFLIGHT 测量结果保存在migration-log/postflight-analysis.json;相同的packageId会触发比较,SOURCE 是第一个输入):identity、已发布的制品集和规范 URL 都必须显示 IDENTISCH,若显示 DIVERGIERT 则必须停止;各语言的叙述性表格应纳入报告的 QA 分类。然后使用scripts/prepost-delta.py比较两次测量结果(调用方式见 Verification;规范 §5.6):每个比较属性给出一个判定(unchanged · improved · REGRESSION · expected-change),并且 exit 1 = 某个属性变差了(制品数量减少、许可证变得矛盾、出现依赖注入风险、identity 字段发生变化)— 必须停止并修复,绝不能把它作为差异记录;当两次测量的清点mode字段不同时(reduced表示原始资源清点,static表示 FSH 声明清点 — 即采集到的形状),数量差异会报告为 expected-change,并注明具体模式,绝不会报告为回归(规范 §5.6)。同级 skill 是经过检查的前置条件,不能想当然 —bash "$SKILL_DIR/scripts/sibling-skill-check.sh" --skill-dir "$SKILL_DIR"会找到它,或发出包含确切固定版本npx skills add命令的 WARNsibling-skill-unavailable:,但绝不会安装它:获得工具授权不代表它是依赖项;无关运行不得写入操作员的 skills 目录(规范 §5.6a)。 7b. 验证——以机械方式完成,且失败时返回非零退出码。 规范 §11 具有规范性。它取代了原先由人工执行的文字检查清单:有 4 次实际迁移在构建成功的情况下通过了该清单,却仍交付了无法访问的制品、过时的渲染来源信息、损坏的页面标题、被静默截断的文件和错误的依赖版本固定项。bash "$ML" run 11 verify-migration --emits-runlog \ --expected-nonzero 'findings are this step OUTPUT (1 = DIVERGIERT, 3 = NICHT PRÜFBAR)' -- \ python3 "$SKILL_DIR/scripts/verify-migration.py" --target . --source <unmigrated-source> \ --rendered output --source-lang de --template-latest <latest module-template release>四层检查加上日志。完整性:每个源制品都存在,且可从
artifacts.html访问——存在与可访问是不同的属性,而步骤 7 的集合比较只能证明前者;源集合的并集中的每个页面(权威指南树 ∪input/pagecontent∪ 步骤 2c 的收集结果——绝不是回退链:一个 pagecontent 存根不得压过包含 149 个页面的指南树)都必须根据migration-log/page-map.tsv标记为已迁移/已退役/MISSING;该文件是经审查的契约,由5.4c page-routing建议运行生成;每段源文本都必须在某处出现。保真度:身份信息、与源内容完全一致的依赖版本固定项,以及从层级断言、绝不使用默认值的license——并与 LICENSE 文件自身的文本核对(不匹配为 DIVERGIERT,无法识别的文本为 NICHT PRÜFBAR)。**来源信息:**从渲染后的qa.html读取模板包及版本、发布者版本、固定的指南版本——同类项相互比较,因为 ig-template 包版本(旧版 modules 中的 vendored 副本,以及运行日志中 URL 形式的template-reference发布版本)与 module-template 仓库发布版本是不同的数字(实测:仓库v0.6.0内含包0.5.1)——另加 P5:ig.ini中的ig=必须指向 SUSHI 实际写入的 IG 资源(ImplementationGuide-<id>.json,由id推导,绝不从仓库 slug 推导)。渲染完整性:空表格/标签页、缺失图片、Unknown region code等标题标记,以及语言一致性——与默认语言逐字节相同的已翻译页面是回退内容,不是翻译。运行日志是第二个判定依据(§11.6):它记录每个步骤意图执行和测得的结果,而目录树记录的是最终结果,因此只有日志能发现未处理的silent-partial-success:WARN、完全没有输出任何日志行的步骤(清单为references/expected-steps.tsv)、尚未解决的identity-contradiction:,以及日志与制品数量不一致。三个判定结果,而非两个:IDENTISCH、DIVERGIERT(标明具体内容并提供证据)和NICHT PRÜFBAR,确保无法机械化检查的项目绝不会被静默写成通过。退出码 0 表示无问题 · 1 表示不一致 · 3 表示不完整,这不算通过。 -
报告。根据报告模板撰写
migration-log/migration-report.md——设立 3 个审阅队列(① 决定、② 审阅、③ 分流):每项待定决策、TODO:REVIEW和 QA 发现都必须且只能归入其中一个队列;QA 来源信息必须提供证据(构建未迁移的源内容,才能声称问题“原本就存在”)。报告读者从未看过此技能、规范或仓库:模板列出的每个标注行都是必填项(包括无人采取行动时由谁决定、所需工作量、可逆性);代码在出现处均须附释义;ID 必须将队列与 Sign-off 关联起来。两项自动生成的辅助内容须直接粘贴,不能重新输入(调用方式见模板中的 How to re-run):qa-checklist.py将台账中的每项未完成义务渲染为按关卡划分的- [ ]复选框,写入 Sign-off;comparison-table.py将映射渲染为可点击的渲染后源页面 ↔ 渲染后目标页面行,并附上模板页面来源信息,写入 Content map。协议部分根据migration-log/run.log生成(规范 §10.6):每项声明都必须能追溯到一条日志;每条 WARN/ERROR 都必须进入某个队列;以日志为准。 -
创建拉取请求,并将报告作为其描述。**不要发布。**将工作分支命名为
migration/<source-version>-template-<module-template-tag>(规范步骤 9——五次试运行各自提出了不同方案)。根据模块仓库自身的惯例确定 PR 的目标分支——先查明,不要假设: 查看默认分支、已合并拉取请求的目标分支,以及 CONTRIBUTING/README。模板会将每个非main分支预览到gh-pages下的branches/<branch>/,并将main和标签保留给发布,因此工作分支可进行预览而不会触碰默认分支。如果仓库有不同的惯例,则遵循该惯例并说明;如果该 PR 的目标分支本身就是发布分支,也要在 PR 和 Gate D 中说明:在这种情况下,合并即会发布。
运行日志
用途仅此一次:让人工读者无需重新运行任何操作,也无需依赖记忆,就能还原运行过哪些步骤、每步产生了什么——实际执行的命令、返回的计数以及退出状态。migration-log/run.log:纯文本,只追加,并与分支一同提交。报告的协议部分根据该日志生成(步骤 8),步骤 7b 也会读取日志,将其作为第二个校验依据(§11.6),因此报告不能声称运行中未执行过的内容。规范 §10 具有规范性——其 §10.5 表格列出了完整 API。所有日志行都必须通过随附的辅助脚本输出,ML="$SKILL_DIR/scripts/migration-log.sh",包括那些未运行任何随附脚本的步骤:begin LABEL(每个区块的第一行,也是唯一编号的 run-boundary)· info|warn|error STEP ACTION DETAIL [CONT …] · ratio [--exit N] STEP ACTION VERB NOUN EXPECTED ACTUAL(一条同时列出两个计数的 INFO,并且在 ACTUAL < EXPECTED 时必须附带 WARN)· run STEP ACTION [--emits-runlog] [--raw-log F] [--expected-nonzero WHY] -- CMD …(实际执行的命令、输出到 migration-log/<ACTION>.log 每次调用都会截断,以及其真实退出状态;状态会返回,不会被吞掉)。
绝不要使用 … 2>&1 | tee -a migration-log/run.log。管道的状态是 tee 的状态,而本技能的验收标准就是退出状态:实测中,fsh-sushi 以 41 退出、postprocess-gofsh.py 以 1 退出,但该管道报告的状态却是 0,于是失败步骤被误判为通过。run 从 PIPESTATUS[0] 获取状态。退出状态只有 8 位——256 个 SUSHI 错误会报告为 exit=0——因此 run 会将其与原始日志中的错误计数交叉核对(exit-status-truncated:)。--expected-nonzero WHY 用于标记非零退出属于文档记录的预期结果的步骤(shape-B sushi-after;步骤 7b 的验证),日志会记录一条指出升级处理的 WARN,而不是用 ERROR 将预期结果称为失败。
格式:<UTC ISO-8601> <LEVEL> <STEP> <ACTION> <DETAIL>,字段之间用两个空格,LEVEL 补齐至五个字符,DETAIL 包含实际执行的命令,格式为 cmd=`…` ,并附上实测的 key=value 结果;续行缩进四个空格——例如 … WARN 5.1b.2 gofsh-convert silent-partial-success: converted 1 of 20 inputs at exit 0。每个步骤至少输出一条 INFO 行,步骤 7b 的 L2 检查会报告未输出 INFO 行的步骤(references/expected-steps.tsv)——未运行的步骤在树中不可见。无提示的部分成功必须记录 WARN:必须通过 ratio 写明两个数字,绝不能手动编写——在那次运行中,其他所有信号都正常(postprocess 显示“无需修复”,SUSHI 错误数为 0),但 20 个资源中有 19 个缺失。使用 grep -E ' (WARN |ERROR) ' 回读日志。
护栏
具有约束力——即使迁移能够成功构建,只要违反其中任意一项,就不正确。
- 现有符合性资源的规范 URL 和 ID 绝不更改。
- FHIR R4(4.0.1)。
- 不得捏造。 每项工件和叙述章节都必须可追溯到源 URL 或仓库路径;
不确定之处标记为
TODO:REVIEW,绝不猜测。(TODO:REVIEW用于标记已迁移指南;目录中未完成的 技能 内容使用TODO(owner):标记——两者不可混淆。) - 人在回路中。 下文的审核关卡均为强制要求。Agent 不得发布。
- 迁移前删除模板示例,绝不与模块自身内容合并。
- 不得修改默认分支。 在分支上工作,并提交拉取请求。
- 可追溯性。 每个步骤运行时都必须通过
scripts/migration-log.sh输出运行日志行(见上文的 运行日志);每项假设和待解决问题都必须记录到migration-log/migration-report.md,其中的协议部分由该日志生成。 pagecontent中不得出现 Liquid 字面量,包括 HTML 注释内。 Jekyll 会在任何位置处理{% … %}和{{ … }}:无效的{% … %}会直接导致构建失败,未知的{{ … }}会被静默清空并泄漏到 HTML 中。请用文字描述此类机制。- 负向能力结论仅对实际测量过的工件有效。 每条“无法读取/不可用/无法实现”的结论都必须记录确切的 URL、端点或命令、日期和数值;绝不可将其推广到同级 URL、端点或机制;采取行动前必须重新测量。下游不会重新核验此类结论,因此它会悄然将缺失的能力变成缺失的交付物——这种情况已经发生过两次(页面标题本地化;Simplifier 指南,其错误结论导致迁移发布时仍包含模板的起始页面)。规范 §2.1.3、§4 护栏 10、§5.1c.2a——其中明确指出两个方向都成立:在某个实例上测得的正向特征也只是假设。
语言
有三点事实,很容易混淆。
- 目标模板的默认语言是英语,德语是翻译语言(
i18n-default-lang: en)。每次运行都要在目标的sushi-config.yaml中核实——此前已经变更过一次。FHIR 工件标识符始终使用英语。 de-DE不匹配警告是有条件触发的——仅当源 FSH 设置了^language = #de-DE时才会触发。此警告仅属外观问题,并通过input/ignoreWarnings.txt抑制(使用%通配符的 glob,而非正则表达式;匹配%(de-DE)%),FSH 保持不变。规范 §4.1。- 仅有德语的源会使方向反转——这正是此技能要处理的情况。 KDS 的常见情况是:源的叙述内容为德语,而目标的默认语言为英语,因此德语文本将成为尚不存在的英语页面的翻译。将其转移到
input/translations/de/pagecontent/,并将input/pagecontent/*.md生成为这些内容的机器翻译,每个页面均标记为TODO:REVIEW,并在关卡 C 审核——这是护栏 3 唯一允许的例外,因为每个页面都能追溯到其所呈现的页面。源文件顶层的language:是旧式单语言设置,不代表语言身份。步骤 7b 的 R3 检查用于证明结果正确:与默认页面逐字节相同的翻译页面属于回退内容,并非翻译。
验证
机械式检查,且退出代码非零——步骤 7b,规范 §11。 曾列在此处的句子清单现已成为 scripts/verify-migration.py:四层检查,另以运行日志作为第二判定依据;三种判定;每个受检对象在 migration-log/verification-findings.tsv 中占一行。人仍需处理每一条 NICHT PRÜFBAR 记录——注明对象、操作及门槛;退出代码 3 表示阶段尚未完成,而非通过。它会从六个方面将迁移后的指南与 Simplifier 渲染的源内容进行比较,而不是与自身比较;qa 错误计数无法捕获其中任何一项。工具链来源(P1–P5):渲染后的网站报告所用的模板包、构建实际使用的模板包(legacy modules 中是 vendored 副本;使用 URL 引用时,则是运行日志中 template-reference 行记录的版本——URL 引用旁存在 vendored 文件夹本身就构成偏差),以及最新发行版,是三个不同的版本号(§11.4——repo 标签 v0.6.0 vendored 的包版本为 0.5.1;拿第一个与第三个比较,会得出看似确定、实则错误的发现);此外还要检查发布方版本与工作流固定版本、固定的指南版本,以及 ig.ini 是否指向 SUSHI 实际写入的 IG 资源(P5,即 id 与 slug 不匹配这一类问题)。页面集与菜单(C3、C5):源页面集的并集(权威指南树 ∪ input/pagecontent ∪ 步骤 2c 的采集结果;按建议脚本使用的同一规则选取,并可通过 --source-guide-tree 覆盖——绝不采用非此即彼的回退方式)中的每个页面都必须已迁移、已退役并说明原因,或标记为 MISSING;每个菜单条目都必须指向有效位置;每个叙述性页面都必须列在菜单中;每个目标页面都必须能追溯到源页面或 references/template-pages.tsv;只要存在已翻译页面,就必须有已翻译的菜单。产物完整性与可达性(C1、C2):存在与已列出并非同一属性,两个方向都要检查——既检查 SUSHI 生成的产物,也检查从 input/resources 渲染而来、正向遍历无法看到的产物。C1 通过产物的id 或规范 url标识产物,因为 SearchParameter 可能只有后者,且迁移会重新分配 id;一个变体是会渲染页面的目录,因此多语言构建中的重定向存根 ROOT 不算变体(§11.5a——将其误读为变体,会导致全部四个迁移都被错误地报告为 BLOCKER)。渲染完整性(R1、R2):源内容中的表格、标签页和图像若非空,渲染后也必须非空;还要检查页眉/页脚区域;若发布方无法解析某个管辖区,应显示 Unknown region code,且 Errors: 0。内容位置(C6):检查的不是文本片段是否保留——那属于 C4——而是它落在哪个页面,并与映射表对照。语言一致性(R3、R5):与默认语言页面逐字节相同的已翻译页面属于回退内容,而非翻译内容。两个输入决定了其中有多少检查可机械化,且这些输入由前面的步骤写入,而非验证器写入:migration-log/page-map.tsv(v2,source_page⇥target⇥reason⇥branch⇥measure;验证器读取前三列——由 5.4c page-routing 建议运行生成并经人工审核,绝不可手写)以及步骤 5.1c 的采集结果。缺少这些输入时,结果为 NICHT PRÜFBAR,绝不会判为通过。F3 还会将 LICENSE 文件中的可识别文本与声明的许可证进行核对(不匹配则为 DIVERGIERT,无法识别则为 NICHT PRÜFBAR,文件缺失则记录 ok 说明)。
grep -rn '{{' . --include='*.yaml' --include='*.yml' --include='*.md' --include='*.json' | grep -v '\${{'
bash "$ML" run 7 sushi-verify -- npx --yes [email protected] .
bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict
bash "$ML" run 5.2 license-align --emits-runlog -- python3 "$SKILL_DIR/scripts/license-align.py" --source <src> --target .
bash "$ML" run 5.4c page-routing -- python3 "$SKILL_DIR/scripts/page-structure-advice.py" --source <src> --target . --out migration-log/page-structure-advice.md --map migration-log/page-map.tsv
bash "$ML" run 7 prepost-delta -- python3 "$SKILL_DIR/scripts/prepost-delta.py" --pre migration-log/preflight-analysis.json --post migration-log/postflight-analysis.json --out migration-log/prepost-delta.md --tsv migration-log/prepost-delta.tsv
bash "$ML" run 11 verify-migration --emits-runlog --expected-nonzero 'findings are the output' -- \
python3 "$SKILL_DIR/scripts/verify-migration.py" --target . --source <src> --rendered output
这些项目各自有验收标准:每个 {{...}} 都必须处理完(未替换的内容会悄无声息地生成错误的产物);SUSHI 检查干净,且 qa.txt 中为 Errors: 0 — 两者都按第 2b 步中的限定适用于形态 B,而 IDENTISCH 标准不受形态限定;fql-scan.sh --strict 退出码为 0,且扫描文件数非零(目标集为空时退出码为 2,不算通过),并且没有 [UNKNOWN] 发现项;建议运行会重新生成页面映射,且覆盖完整 — 退出码为 1 时会列出未路由的页面,不算通过(重新运行会覆盖已审核的映射,因此要重新应用并复核人工修改);prepost-delta.py 不报告 REGRESSION 行(退出码为 1 表示必须停止并修复);license-align 退出码为 0(已对齐,日志中有 from→to 声明)— 其退出码为 1 的 WARN(license-missing:/license-unvendored:/identity-contradiction:)是明确列出的 ① Gate-A 项,绝不能置之不理。其他所有内容 — 上述六个方面、身份/许可证/固定项、父级快照以及运行日志自身的完整性 — 都是在规范 §11 中按编号列出的检查,需实际测量,而非凭记忆判断。生成器会为所有打印出的代码添加行内释义(M1–M11、C1–C7、F/P/R/L、gate、marker kind),并将其列在 references/codes.md 中。
自动修复为可选且范围有限(规范 §12):bash "$SKILL_DIR/scripts/autofix-loop.sh" --skill-dir "$SKILL_DIR" 仅修复四类允许名单中的机械问题,最多迭代 3 次,为每次修复创建快照,回滚未能清除相应发现项的修复,并在发现项集合不再缩小时立即停止。按设计,身份、叙述内容、SOURCE 声明的任何内容以及所有判断事项都不在修复范围内 — 这些事项会进入 ① 队列。
强制人工审核关卡
| Gate | 步骤之后 | 审核内容 |
|---|---|---|
| A | 4 | 保留规范 URL、ID 以及许可证/身份;产物完整性;对于形态 B,还要审核 goFSH 生成的 id,以及所有无法解析父级的处理决定 |
| B | 5 | 叙述内容,尤其是为满足 Manteldokument 而添加的任何章节 |
| C | 6 | 语言处理和翻译,包括机器翻译的默认页面 |
| D | 合并前 | 按 KDS 治理流程发布(TF KDS / AG IOP / NSG)— 属于组织流程,而非技术流程。完成之前不得发布任何内容。 |
范围与界定
涵盖将现有指南迁移到模板:保留身份、转移产物、转换指令、设置双语,以及验证这些工作的 QA。以下内容有意不在范围内:编写新配置文件或重塑内容(迁移绝不会更改规范性决策);从头创建模块(模块模板附带自己的操作指南);翻译已使用模板的指南(目录中的 fhir-ig-translation);发布(不进行发布、登记注册或推送软件包);以及补充缺失的领域内容(源内容中的缺口应标为 TODO:REVIEW,而不是写作任务)。如果目录和本地副本都提供此技能,则以本地副本为准。
来源说明
改编自 forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziell 在提交
bd38e2722a594254f3450e73c3fcdbfc2c47b7e8 中的 skills/mii-ig-migration。
带日期的修订历史——每项变更及其背后的测量结果——见 references/provenance.md;它仅记录历史,不会改变每次运行时的操作方式。
2026-08-07 — 验证阶段,因为清单不等于检查。 验证 原本是由人工执行的文字流程;四次真实迁移都在构建成功的情况下通过了该流程,却交付了错误的依赖固定版本、页面标题中的 Unknown region code '276'、过时的渲染版来源说明,以及一个被静默截断的文件。步骤 7b、规范 §11–§12 以及 scripts/verify-migration.py + autofix-loop.sh + autofix-fix.py 以四个机械化层、回读运行日志作为第二判定依据、三种判定结果(因此无法机械化的检查绝不会被记为通过),以及范围受限且设有允许列表的自动修复循环取代了原流程。测量结果见 references/provenance.md。
原始许可证:CC-BY-4.0,由源代码仓库和源技能声明;scripts/ 采用 Apache-2.0,与本仓库的代码许可证一致。于 2026-08-05 升级为 stable,此前已完成两次真实任务的完整迁移(Dokument、Person);两次迁移均通过同一模块验证(标识、产物集、规范 URL 均为 IDENTISCH),且 QA 已通过基线验证;这两次运行也验证了 references/triggers.md 中的触发条件集。