mii-ig-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Migrating an MII KDS module IG onto the module template

将MII KDS模块实施指南迁移至模块模板

This skill supports and partly automates the migration. It never publishes, and four human review gates are mandatory. The full procedure, with acceptance criteria per step, is in the migration specification; this file is the operating summary.
本技能支持并部分自动化迁移流程。它不会执行发布操作,且必须经过四道人工审核关卡。包含各步骤验收标准的完整流程请参阅迁移规范;本文档为操作摘要。

Preconditions

前置条件

Discover the context: assume none of it, create nothing that is missing.
  1. The source guide. Two inputs come from the human and cannot be derived: the URL of the rendered Simplifier IG and the URL of its source GitHub repository. If either is absent, ask for it and stop. Everything else about the module's identity is read, not asked (step 2).
  2. The module repository — classify the source shape. Two shapes are in scope and take different routes to the same place; a third is not. Decide first, and record it (spec §5.1b).
    • Shape A — a SUSHI / IG-Publisher project. A
      sushi-config.yaml
      or an
      ig.ini
      at the repository root plus an
      input/fsh/
      — the FHIR IG ecosystem conventions that make this skill portable. The FSH exists; step 4 transfers it.
      input/fsh/
      present but empty → report it; a migration with no artefacts to move is a configuration error, not a no-op.
    • Shape B — a raw FHIR resource repository. No scaffolding — checked to depth 3 (spec §5.1b): a SUSHI project nested below root (strukturdaten) is shape A (nested), re-rooted with a
      decision
      line — goFSH-ing it discards the author's FSH with the ratio green. This is the normal state of a module authored in Forge and published on Simplifier, and it is fully in scope — the most authentic case this skill exists for. Step 2b derives the FSH with goFSH; from step 3 the two paths are identical. Detect it by content, not by folder name (spec §5.1b.1): a file is a FHIR resource if it parses and carries a
      resourceType
      ; folder names are hand-chosen and often German, so no conventional-name glob finds them. The rendered guide's narrative is on Simplifier, not in the repository — no
      implementation-guides/**
      tree, so step 1 takes the page structure from the rendered IG and
      fql-scan.sh
      rightly exits 2 on an empty target set before migration. Not in git is not unobtainable: step 2c harvests it, and skipping that ships the template's starter pages under the module's name. Nor is it "no narrative in the repository": the reference module ships a 43-line German
      README.md
      and a 126-line CodeSystem mirror. Inventory every narrative-bearing text file, with a disposition each.
    • Neither — no scaffolding and no FHIR resources: not a FHIR IG project. Say so and stop. Do not scaffold one. Path B is no exception: it creates no artefacts, deriving FSH from resources that already exist, each tracing to a source file (guardrail 3).
  3. The target template. Determine which state the module is in — discovery only here, the skeleton comes in step 3 of the procedure. In every state, read
    forschungsgruppe-digital-health/mii-kds-module-template
    at the ref you intend to use rather than relying on this skill's description of it.
    • Already on the module template — a vendored
      ig-template/
      , or an
      ig.ini
      template
      entry pointing at it. A re-migration: report what is in place before changing anything.
    • Plain Simplifier project — Simplifier files only (
      .simplifier/
      ,
      project.yaml
      ,
      implementation-guides/
      ), no IG-Publisher scaffolding. The normal starting state.
    • Hybrid, or on another template — IG-Publisher files beside the Simplifier ones: an
      ig.ini
      naming another template,
      _genonce.sh
      & co., a committed
      fsh-generated/
      or rendered output (possibly live GitHub Pages). Still a migration. Inventory them and record which the template replaces (
      ig.ini
      ,
      _gen*
      /
      _update*
      ), which carry content to transfer (
      input/
      ,
      fsh-generated/
      ), and which retire only after Gate D. List any unrecognized top-level entry too (e.g.
      validator/
      ) with a retain/retire proposal — list, do not remove.
  4. Unreplaced placeholders. The template does not build until every
    {{...}}
    placeholder is replaced, and an unreplaced one ships a bogus artefact rather than failing loudly. Before and after migrating, grep the tree for
    {{
    and account for every hit — excluding
    .github/**
    (Actions
    ${{ … }}
    matches the pattern) and counting Simplifier directives in narrative sources as accounted (step-5 material). Census by EXCLUSION (
    grep -rIl '{{' . --exclude-dir=.git
    ), not an
    --include
    list:
    it misses
    ig.ini
    , whose slug killed a publisher run (PROs try-run).
  5. The toolchain — invoke SUSHI and goFSH only as a version-pinned
    npx
    .
    Neither is normally installed (
    which gofsh
    finds nothing on the reference machine), so a bare
    sushi
    /
    gofsh
    is unrunnable and appears nowhere in this skill
    : write
    npx --yes fsh-sushi@3.20.0
    and
    npx --yes gofsh@2.6.1
    — the npm package for SUSHI is
    fsh-sushi
    , not
    sushi
    . What the "no fetching a toolchain" rule protects is an exact, recorded version, which the pin supplies and an unpinned
    npx
    does not; let the pin be the record, carried in the log's
    cmd=
    token. (
    allowed-tools
    grants
    Bash(npx:*)
    ;
    Bash(gofsh:*)
    never matches an
    npx
    command line.) goFSH is required for shape B, for shape A only where the source ships JSON/XML; the IG Publisher is needed from step 7. Missing node/npx → say which and stop after step 2. A parent package without snapshots additionally needs java and a pinned
    validator_cli.jar
    — fetched only when that condition is actually detected (spec §5.1b.5), never hand-substituted.
明确场景背景:不做任何预设,不创建缺失的内容。
  1. 源指南。需人工提供两个无法自动推导的输入项:已渲染的Simplifier IG的URL,以及其源代码GitHub仓库的URL。若其中任意一项缺失,需向用户索要并暂停操作。模块标识的其他所有信息均通过读取获取,而非询问(步骤2)。
  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资源;目录名通常为德语,无法通过常规名称通配符匹配。 已渲染指南的叙述内容存储在Simplifier上,而非仓库中——不存在
      implementation-guides/**
      目录结构,因此步骤1需从已渲染的IG中获取页面结构,
      fql-scan.sh
      会因目标集为空而正常退出2,之后再执行迁移。未存储在git中的内容并非无法获取:步骤2c将提取这些内容,若跳过此步骤,则会在模块名称下使用模板的初始页面。也并非“仓库中无叙述内容”:参考模块包含一个43行的德语
      README.md
      和一个126行的CodeSystem镜像。需盘点所有包含叙述内容的文本文件,并记录每个文件的处理方式。
    • 既非形态A也非形态B——无脚手架结构无FHIR资源:不属于FHIR IG项目。需告知用户并停止操作。请勿搭建脚手架。形态B也不例外:它不会创建任何工件,而是从已存在的资源中生成FSH,每个工件都可追溯到源文件(防护规则3)。
  3. 目标模板。确定当前模块所处的状态——此处仅做发现,骨架将在流程的步骤3中生成。无论处于何种状态,都需读取
    forschungsgruppe-digital-health/mii-kds-module-template
    仓库中你计划使用的版本,而非依赖本技能对模板的描述。
    • 已基于模块模板——存在 vendored 的
      ig-template/
      目录,或
      ig.ini
      中的
      template
      条目指向该模板。属于重新迁移:在修改任何内容之前,需先报告当前已有的配置。
    • 纯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/
      )、哪些需在关卡D之后停用。同时列出所有无法识别的顶级条目(如
      validator/
      目录),并提出保留/停用建议——仅列出,请勿删除。
  4. 未替换的占位符。模板需替换所有
    {{...}}
    占位符才能构建,未替换的占位符会生成虚假工件而非直接报错。迁移前后,需遍历目录树查找
    {{
    ,并记录所有匹配项——排除
    .github/**
    目录(Actions中的
    ${{ … }}
    会匹配该模式),同时将叙述源中的Simplifier指令视为已处理内容(步骤5的处理对象)。通过排除法统计
    grep -rIl '{{' . --exclude-dir=.git
    ),而非使用
    --include
    列表:否则会遗漏
    ig.ini
    ,其slug曾导致发布运行失败(PROs试运行)。
  5. 工具链——仅通过版本固定的
    npx
    调用SUSHI和goFSH
    。通常不会安装这两个工具(参考机器上
    which gofsh
    无输出),因此本技能中不会出现裸调用
    sushi
    /
    gofsh
    的情况
    :需编写为
    npx --yes fsh-sushi@3.20.0
    npx --yes gofsh@2.6.1
    ——SUSHI的npm包名为**
    fsh-sushi
    ,而非
    sushi
    。“不获取工具链”规则旨在保证
    精确、可记录的版本**,版本固定可实现此目标,而未固定版本的
    npx
    则无法保证;版本信息将作为记录保存在日志的
    cmd=
    标记中。(
    allowed-tools
    允许
    Bash(npx:*)
    Bash(gofsh:*)
    永远不会匹配
    npx
    命令行。)goFSH是形态B的必需工具,对于形态A,仅当源项目包含JSON/XML资源时才需要;从步骤7开始需要IG Publisher。若缺少node/npx,需告知用户并在步骤2后停止操作。若父包无快照,还需Java和固定版本的
    validator_cli.jar
    ——仅当实际检测到该条件时才获取(规范§5.1b.5),切勿手动替换。

Procedure

流程

Written in English; the artefacts operated on are German-language KDS documents, and German terms of art are kept as such. Output language follows the target template: English is the default language and German is the translation. This reverses the older convention — see Language below, and verify it against the target's
sushi-config.yaml
rather than trusting this sentence.
Resolve the script path first. The commands below name tools relative to this skill's own directory, not to your working directory — which is the project being migrated. Set
SKILL_DIR=<the directory containing this SKILL.md>
(e.g.
.claude/skills/mii-ig-migration
) and
ML="$SKILL_DIR/scripts/migration-log.sh"
, and use them in every invocation. A bare
scripts/...
from the project root does not merely fail: if the project has its own
scripts/
with a same-named file, it silently runs that instead.
  1. Inventory the source — Gate 0 FIRST (spec §9c): run the sibling
    fhir-ig-analysis
    on the unmigrated source (
    bash "$ML" run 1 preflight-analysis …
    migration-log/preflight-analysis.json
    ); its artifact counts drive the measured M9 decisions and the §9b CS-absence path,
    special_url_prediction
    pre-builds the special-url list, and
    qa_baseline: None
    means: obtain the source QA proof now. Then, from the rendered IG and the source repository, extract every artefact (profiles, extensions, value sets, code systems, capability statements, examples) and the narrative structure, each with its source path →
    migration-log/source-inventory.json
    . When
    implementation-guides/
    holds several guide trees (versions × languages + shared assets — a real module ships six), apply spec §5.1a: pick the authoritative tree, mark parallel-language trees as harvest seeds, retain the rest. No rendered-IG URL? Discover it — spec §5.1c.
    bash "$SKILL_DIR/scripts/simplifier-discover.sh" --org ORG --module SLUG
    walks org project list → package → project →
    /<project>/filterprojectguides
    (no tilde; the
    ~
    variants return 200 and yield nothing) →
    /published-guide/<key>/versions
    → the server-rendered guide root, WARNing at any hop that yields nothing. Pin a PUBLISHED, read-only version and record it like the source commit SHA — never
    ?version=current
    , the live editable project.
    Keys and page slugs are discovered, never constructed (a constructed key 404s; the renderer de-punctuates slugs). The PROJECT page is client-rendered and yields nothing while the GUIDE pages yield everything — generalizing that one measurement to the platform is what once cost this skill the procedure. One instance is not the class, in BOTH directions (spec §5.1c.2a): measure a negative on the exact artefact you claim it about, and before encoding a shape as normative measure it on more than one instance, choosing the one most likely to differ — the same day's
    data-url="/guide/<key>"
    reading was taken on the one module where every key is bare and silently dropped keys on two others (consent 3 of 3, mikrobiologie 2 of 3, person 0 of 3, all at exit 0).
  2. Read the module's identity — do not ask for it, and do not invent it. From the source's
    sushi-config.yaml
    and
    package.json
    (absent a
    sushi-config.yaml
    :
    package.json
    plus the
    ImplementationGuide
    resource) read
    title
    ,
    packageId
    ,
    canonical
    ,
    status
    ,
    releaseLabel
    ,
    license
    ,
    dependencies
    and
    publisher
    , and carry them over unchanged. On disagreement
    sushi-config.yaml
    wins — it is what the build reads; record it. A field in neither file comes from the generated
    ImplementationGuide
    ; absent everywhere it takes the template default, at Gate A. Resolve floating pins (
    1.5.x
    ) per spec §2.1, recording the pick and its evidence. Shape B often has none of the three files — and a repository carrying no identity is not an identity-less module. Identity is then RECOVERED from several sources in this order, each field recorded with the source it came from (spec §2.1; the scripts write the ledger themselves):
    • P — the published package.
      bash "$SKILL_DIR/scripts/package-identity.sh" --package ID --version V
      logs
      packageId
      ,
      version
      ,
      description
      ,
      fhirVersions
      ,
      jurisdiction
      and the dependency pins — source evidence, outranking any
      dist-tags.latest
      — plus the
      canonical
      derived from the packaged resources' own urls by common prefix, unanimous or a WARN, never a majority vote. No manifest carries
      title
      ,
      license
      or
      publisher
      ;
      author
      is a registry account, not a publisher.
    • R — the source repository.
      bash "$SKILL_DIR/scripts/repo-identity.sh" --dir DIR --repo OWNER/NAME --rendered URL
      reads the LICENSE text's SPDX id — real licence evidence, the field that must never default (§2.2) — the README's first heading as a
      title
      candidate, the repo description, and the release tags, whose match with P's version ties that release to the commit. An unrecognized licence text yields nothing (
      license-text-unrecognized:
      ); the GitHub owner is not a
      publisher
      .
    • H — Simplifier. TWO URL SPACES, TWO ANSWERS — do not carry one to the other (spec §2.1.3). The project page (
      simplifier.net/<Project>/
      ) is measured client-rendered (HTTP 200, ~56 KB, 52 script markers, no identity metadata in the DOM): a human reference at Gate A for what no machine source carries, not a scrape target, and the script measures that and extracts nothing. The guide pages (
      simplifier.net/guide/<key>/<Root>[/<Page>]?version=<v>
      ) are a different URL space and are SERVER-RENDERED — measured on Consent 2026.0.0: root 24509 bytes carrying the whole page tree (18 page links), leaf 20481 bytes carrying
      <h1 id="page-title">
      and the real German narrative. They yield the narrative, not identity: harvest them per step 2c.
    Whatever no tier yields stays Gate A — measured on the reference module,
    publisher
    alone. Every value is claimed with its evidence (
    bash "$ML" claim 2.1 ACTION FIELD VALUE TIER SOURCE
    ), and a second source with a different value raises
    identity-contradiction:
    reported, never resolved (measured: goFSH's
    version: 1.0.8
    against the package's
    2026.0.0
    ; a source pin
    2.0.2
    against
    dist-tags.latest
    2.0.3
    ).
    bash "$ML" claims --markdown
    is the report's identity table. NEVER ALTER EXISTING METADATA from a recovered value, even where the recovery shows it to be inconsistent: recovery is evidence for Gate A. Spec §2.1.2–§2.1.4.
    Log each value read, and each divergence as a WARN. The target version is the only identity value that is a human decision: MII CalVer
    YYYY.n.n
    , not SemVer, defaulting to the source's. When the source and the template disagree, the source wins — the template's
    canonical
    and
    packageId
    patterns are what a new module gets, and changing a published canonical breaks every consumer. Report each divergence and let a human decide; never normalize silently. That covers every value the template pre-fills as a literal rather than a placeholder,
    license
    above all: the template ships
    CC-BY-4.0
    , no placeholder check flags it, and MII modules commonly declare
    CC0-1.0
    . Relicensing is a human decision, never a default. Spec §2.2.
2b. Source shape B only — derive the FSH from the raw resources. Runs before the skeleton, which step 3 merges into FSH that must already exist. Work in a scratch directory outside the module repository; skip for shape A. Spec §5.1b is normative here (measured with goFSH 2.6.1 and SUSHI 3.20.0 on
medizininformatik-initiative/kerndatensatzmodul-consent
).
bash
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 fsh-sushi@3.20.0"
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 gofsh@2.6.1 "$SRC" -o "$OUT" -s file-per-definition -t json-and-xml \
  -d <parent-ig-package>@<version> -d hl7.fhir.r4.core@4.0.1
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"
Run verbatim, in that order, from the same directory: the post-processor reads
$GLOG
, and the
E
helper reads each SUSHI error count back out of its raw log into an INFO line — that pair is the 41 → 5 evidence, and it is in the block, not only in the spec.
gofsh-results.sh
reads goFSH's own RESULTS table back, labels every cell, counts converted resources only (never Invariants/Mappings/Aliases) and reconciles them against
$N_IN
. That reconciliation is the point of the whole block — goFSH's exit code is not the signal, its counts are — and
run
keeps each real exit status, truncating each raw log per invocation so a re-run measures itself and not the sum of both.
  • Point goFSH at the repository ROOT, not at one resource folder. The reference module keeps its 20 resources in five hand-named directories and needs no staging: measured, goFSH walks the tree recursively and the FSH from the root is byte-identical to that from a staged flat directory. Stage only for a recorded reason (spec §5.1b.2).
  • -t json-and-xml
    is mandatory; its absence fails SILENTLY.
    goFSH defaults to
    json-only
    : on Consent (19 XML + 1 JSON) the flagless run exited 0, reported "0 Errors" and converted exactly ONE resource, warning only that "13 XML definition(s)" lacked a JSON counterpart — goFSH's own count, not the input's 19 files (the difference is the six
    SearchParameter
    s); say which you mean. The deciding number is neither: it is
    converted 1 of 20
    , which
    gofsh-results.sh
    emits and WARNs on. Reconcile against step 1's inventory, never the exit code.
  • Declare every foreign parent IG with
    -d <package>@<version>
    ,
    found in the resources' own
    baseDefinition
    canonicals; goFSH's "cannot find a definition for its parent … declaring that IG as a dependency" is the signal. Re-run rather than patch the dependency-less output — the two differ (12 mappings / 14 aliases without, 0 / 8 with). A Forge repo names neither package nor version: resolve it against the FHIR package registry (spec §5.1b.2); no hit is a Gate-A stop.
  • goFSH writes the
    sushi-config.yaml
    itself but it is a STARTING POINT, NOT IDENTITY
    : no
    id
    /
    name
    /
    title
    /
    publisher
    /
    packageId
    /
    license
    , an untrusted
    version
    (measured
    1.0.8
    — one profile's — against the module's published
    2026.0.0
    ), and
    dependencies
    that are only whatever
    -d
    set you passed. Recover identity per step 2 instead.
  • The script's two passes are mechanical:
    fhir_comments
    rules and unquoted code-reference systems whose name carries whitespace, repaired with the name goFSH itself reports. It classifies before writing, writes nothing on a shape it does not model, and is idempotent. Give it the whole FSH tree — a narrowed path cannot see the declarations it checks against, so it refuses (exit 1, which
    run
    returns and a
    tee
    would have hidden).
  • Then SUSHI must compile clean apart from genuinely unresolvable parents — measured 41 errors before, 5 after, both logged by the block above. A parse error stops SUSHI reading the rest of a file while it still exports the instance, silently truncated ("exported" is not "converted"): the three Consent examples carried 1 nested provision each before, 27 / 6 / 3 after. SUSHI's exit status is its error count, so
    sushi-after
    exits 5 — the anticipated shape-B outcome, which
    --expected-nonzero
    logs as an escalation rather than as a failure.
  • A parent package that ships no snapshots blocks import — SUSHI cannot read such a parent at all, blocking those profiles and every instance declaring
    InstanceOf
    them. Detect it, then generate the snapshots with a real generator; never hand-roll one (spec §5.1b.5):
    bash "$SKILL_DIR/scripts/parent-snapshots.sh" detect --package ID --version V
    counts them (measured: 21 SDs, 0 snapshots, in both candidate versions — another version does not fix it), and
    … build … --validator validator_cli.jar --install --require <parent-url>…
    drives the official HL7 generator (
    java -jar validator_cli.jar snapshot
    , ProfileUtilities), verifies every result (a snapshot whose element count matches only the differential is WRONG and is refused), and installs a new cache entry
    <id>#<version>-snapshots
    — upstream is never overwritten. A generator refusal is an upstream defect to escalate, not to hand-finish; what the rebuild costs CI (it is local-only) is a Gate-A decision. Approximating a merge — slicing, cardinalities, element order — fabricates a parent (guardrails 1 and 3). Then re-pin, re-run SUSHI and log both error counts: measured on Consent, 5 → 0. goFSH-invented ids and GUID-named files go to the ② queue: minted ids become the module's, so Gate A confirms them.
  • Acceptance: counts match the inventory; the script exits 0; every remaining SUSHI error is a named unresolvable-parent escalation; all of it is in
    run.log
    . Path B does not by itself produce a clean build, so every "clean build" criterion below (steps 3 and 7, Verification) is read for shape B through the shape-B qualifier, spec §5.1b.4: no mechanical error left, every residual in the ① queue with a Gate-A decision, no parent fabricated. A tolerated error count is not a pass.
2c. Harvest the narrative when it is not in the repository — shape B always, and any shape A whose pages live on the platform. Spec §5.1d is normative (§5.1c found the guide). Order of sources, most trustworthy first, and "nothing" is not the third one:
  • ① The authenticated project download — preferred whenever credentials exist.
    https://simplifier.net/<project-slug>/$actions/downloading
    yields the project including the narrative markdown as the author wrote it, behind a Simplifier login: measured, anonymous access returns the login page (
    /login?ReturnUrl=…
    ) and every anonymous alternative probed 404s or returns HTML, so no verified anonymous project download exists. A human signs in in their own browser, downloads the archive and names a path outside the repository; the agent reads it and logs
    narrative-source=project-download
    . Never invent a credential mechanism, ask for a password or store a token — the gate is the point. Nobody available: log
    project-download-unavailable:
    and fall through to ②.
  • ② The guide harvest — anonymous, verified, and a rendering (directives already expanded; a rendered artefact view is not the resource it renders). Call it directly, it emits its own run-log lines:
    bash
    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
    It discovers the page tree from the root's own
    href
    values — slugs are read, never constructed
    ("Anwendungsfälle / Informationsmodell" →
    AnwendungsflleInformationsmodell
    ), pins every page to
    ?version=
    , isolates each content region
    <div id="preview-content">
    by depth-scanning
    <div>
    /
    </div>
    (a regex to the next
    </div>
    truncates at the first nested one; a page without the region is skipped, never converted whole), writes Markdown with a provenance header +
    TODO:REVIEW
    , and classifies each page
    narrative
    or
    artefact-view
    .
    --out
    has no default — this is step 5's input, not the template's page set. Verification is the point: every discovered page is in
    migration-log/guide-harvest.tsv
    harvested-with-counts or skipped-with-a-reason;
    ratio
    raises the mandatory
    silent-partial-success:
    WARN when harvested < discovered; per page the source text runs that did not survive the conversion are counted (
    missing_runs=
    ) and WARNed — under a separate
    generated-view-lossy:
    token on artefact views, so generated tables cannot bury real losses. Measured on Consent 2026.0.0: 18 discovered, 18 harvested, 0 skipped, 0 narrative pages short, 14 narrative + 4 artefact-view, 3 assets. Exit 0 = clean; 1 = something skipped or short.
  • ③ Nothing is not the third option. An unreachable source is reported, escalated to Gate B and named.
The registry package has not changed role: resources and identity (step 2), no narrative — it is what the harvested set is verified against. The harvested pages enter the SAME contract as every other source shape: the
5.4c page-routing
advice run reads the manifest (
--harvest-tsv
, auto-discovered from the target's
migration-log/
) and GENERATES their map rows — a page the harvest skipped blocks the coverage validation until the harvest is clean or a human retires it in the reviewed map — and step 5 consumes only the map (spec §9/§9f): never one target page per harvested page, and place the language per Language.
  1. Create the skeleton (spec §5.2). The migration happens in place: on a working branch of the module's existing repository, vendor the template checked out in Preconditions 3 and run its first-run bootstrap — do not mint a new repository; history, issues and consumers stay where they are (a new repository is a human decision, recorded in the report, never a default). Replace every
    {{...}}
    placeholder from the identity read in step 2. The template's CRMI
    meta.profile
    claims require the
    hl7.fhir.uv.crmi
    dependency
    — add it to the carried source dependencies, recorded at Gate A (template machinery, not source identity). Scaffold filenames that embed the module's name derive from the module ID, never the repository slug (
    ig.ini
    's
    ig=
    path,
    ImplementationGuide-<id>
    references, the step-6
    .po
    ): SUSHI writes
    ImplementationGuide-<id>.json
    from the
    id
    and nothing else, so a slug-derived name builds green until the publisher finds no IG resource — the id-vs-slug class check P5 catches. Then delete the template's example artefacts (
    input/fsh/profiles/example-patient.fsh
    ,
    input/fsh/instances/example-patient-instance.fsh
    — verify the paths against the template you actually checked out) so they cannot collide with the module's real examples. Before copying the template's FSH scaffold (
    input/fsh/aliases.fsh
    ,
    input/fsh/rulesets/*
    ), diff its
    RuleSet:
    /
    Alias:
    names against the module's FSH — module definitions win, the module's FSH is never changed: a shared alias file (
    aliases.fsh
    ) is resolved per DEFINITION (append only the template definitions the module lacks), every other colliding scaffold file per file (skipped whole). Log both lists — overwriting a module's
    aliases.fsh
    broke a real migration with 234 SUSHI errors. Acceptance:
    bash "$ML" run 5.2 sushi-skeleton -- npx --yes fsh-sushi@3.20.0 .
    runs clean (shape B: as qualified in step 2b), and the skip/append lists are in the log.
  2. Transfer the artefacts — structure-preserving. Move the FSH sources across keeping the source's directory layout; convert JSON/XML with a pinned
    npx --yes gofsh@2.6.1
    where that is all the source has — for shape B that happened in step 2b, so what moves here is its post-processed output. IDs and URLs unchanged. Acceptance is path-level, not count-level:
    comm -3
    over the sorted repo-relative FSH path lists of source and target is empty apart from scaffold additions named in the log — counts alone prove nothing about structure.
  3. Migrate the narrative. Its source is step 2c; this step maps it. Move the Manteldokument content into
    input/pagecontent/*.md
    and translate Simplifier and FQL directives into IG Publisher equivalents:
    bash
    bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict
    The scan is recursive and pre-migration includes
    implementation-guides/**
    ; it logs files scanned per target, WARNs when a named directory contributed none, and exits 2 on an empty target set — never read "nothing scanned" as "nothing found".
    --strict
    exits 1 on any finding;
    run
    keeps both statuses where a
    tee
    reports 0. Apply each printed recommendation; mapping in the FQL crosswalk, rules in
    references/fql-rules.tsv
    . In doubt, write
    TODO:REVIEW
    .
    Route every source page BEFORE writing — spec §9e, first match wins: one artefact →
    input/intro-notes/<Type>-<id>-intro.md
    (German mirror, same filename, renders atop the artifact page); a family overview → a section on an index page that exists; an agreed page owns it → merge; only cross-cutting narrative becomes a page — a hub (≤250 words, one line per child) at ≥3 children, with a menu entry only inside the budget (≤33 entries, ≤10 per dropdown, top level ≤8, depth ≤2), else
    pages:
    -nested and linked. Size gate: >2500 words, >4 merged sources or ANY repeated heading re-runs it (PROs shipped a 6214-word host, 13 colliding anchors). The routing is GENERATED, not judged — the
    5.4c page-routing
    run IS
    page-structure-advice.py
    --map
    (invocation under Verification): it WRITES
    migration-log/page-map.tsv
    (v2:
    source_page⇥target⇥reason⇥branch⇥measure
    ) from these branches plus the semantic routing table
    references/routing-table.tsv
    , VALIDATES coverage of the full source page universe (authoritative guide tree ∪
    input/pagecontent
    ∪ on-disk pages no toc lists ∪ the step-2c harvest manifest; exit 1 until every page has a target and every RETIRED row a reason — dangling toc entries and harvest-skipped pages are findings) and proposes the M9/other-bucket decisions from Gate 0. The map is THE contract (spec §9f): a human reviews/edits it BEFORE anything is written; this step then consumes ONLY the map — a page not in the map is not written, a map row left unhandled is a defect; step 7b checks against the same map. Text you WRITE (overviews, hub one-liners, bridges) is DERIVED — mark it per §9d so it renders as a review box; run
    5.4d derived-scan
    (writes
    migration-log/derived-content.tsv
    , which C7 reads and ② is generated from); moved or split content is never marked. §9 records the reference module's use-case gap: report it, never fill it.
    Template ≥ v0.8 targets: spec §9a is normative for the page set — split pages, link-only Conformance cluster +
    datasets-and-descriptions
    (content re-routes per §9a), no re-added title headings (M10), two REQUIRED run-logged decisions:
    5.4a optional-page-decisions
    (M9 — measured: package artifact count 0 → remove, > 0 → keep, artifacts never deleted; §9a),
    5.4b security-privacy-decision
    (M11). Index style parity (authors/contacts as lists, disclaimer as prose), the
    (de)
    translation-marker wording and the one-commit revertible-fix protocol are §9a; an absent CapabilityStatement is detected, SUGGESTED from the module's profiles and rendered INLINE on its page per spec §9b.
  4. Set up the bilingual pages. English is the default; German is the translation, a same-named file under
    input/translations/de/pagecontent/
    . These do render. The menu is
    input/includes/menu.xml
    with a per-language mirror at
    input/translations/de/includes/menu.xml
    — never a
    menu:
    property in
    sushi-config.yaml
    , which competes with it. Resource translations are
    .po
    supplements under
    input/translations/de/
    ; check the target's recipe for which resource types actually render before investing in one. A German-only source inverts the direction — see Language below. Page titles (breadcrumbs, table of contents,
    <title>
    ) — full recipe in spec §5.5.
    The publisher does localize them, through one IG-level catalogue
    input/translations/<lang>/ImplementationGuide-<ig-id>.po
    (imported into the IG resource at load time — not a resource supplement, so their type restriction does not apply). Generate it after the step-3 SUSHI run from the SUSHI-generated ImplementationGuide resource, the authoritative title set — the menus serve only as a translation seed:
    bash
    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>.po
    migration-log/menu-titles-de.txt
    is a required argument with no default (one
    English Title => Deutscher Titel
    per line; build it, and the
    -
    "no seed" option, per spec §5.5 — an unresolvable path is a setup error, never a silent empty seed). Regenerating is non-destructive; an empty
    msgstr
    means untranslated and goes to the ② queue. Footgun: the language must appear in
    translation-sources
    , not only
    i18n-lang
    , or every
    .po
    is silently ignored. Modules from template v0.5.0 also drop its breadcrumb override.
  5. Build and QA. SUSHI, then the IG Publisher — both through
    bash "$ML" run 5.6 …
    , so the two numbers this step exists to produce end up in the log: SUSHI's error count, and
    qa.txt
    's summary line copied into an INFO (spec §5.6 has the block). The target pins its toolchain in the build workflow's
    env:
    block — read the pins from there rather than from this file. Acceptance:
    qa.txt
    reports
    Errors: 0
    and every example validates — shape B: as qualified in step 2b, the named escalations excepted and every other error still a stop. Then run the same-module verification with
    fhir-ig-analysis
    (measure the unmigrated source, then the migrated tree — the POSTFLIGHT measurement, kept as
    migration-log/postflight-analysis.json
    ; an equal
    packageId
    triggers the comparison, the SOURCE is the first input): identity, published artifact set and canonical URLs must all read IDENTISCH and a DIVERGIERT is a stop; the narrative per-language table goes into the report's QA triage. Then DIFF the two measurements with
    scripts/prepost-delta.py
    (invocation under Verification; spec §5.6): one verdict per compared property (
    unchanged · improved · REGRESSION · expected-change
    ), and exit 1 = a property got WORSE (an artefact count dropped, licence turned contradictory, dependency-injection risk appeared, an identity field changed) — a stop to fix, never a delta to file; when the two measurements' census
    mode
    fields differ (
    reduced
    raw-resource vs
    static
    FSH-declaration census — the harvested shape), count differences report as expected-change with the modes named, never as regressions (spec §5.6). That sibling skill is a checked precondition, not an assumption
    bash "$SKILL_DIR/scripts/sibling-skill-check.sh" --skill-dir "$SKILL_DIR"
    finds it or WARNs
    sibling-skill-unavailable:
    with the exact pinned
    npx skills add
    command, and never installs it: a tool grant is permission, not a dependency, and an unrelated run must not write into the operator's skills directory (spec §5.6a).
7b. Verify — mechanically, and it exits non-zero. Spec §11 is normative. It replaces the prose checklist a human used to perform: four real migrations passed that checklist on a green build while shipping unreachable artefacts, a stale rendered provenance, a broken page header, a silently truncated file and a wrong dependency pin.
bash
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>
Four layers plus the log. Conservation: every source artefact present and reachable from
artifacts.html
— present is a different property, and step 7's set comparison proves only the first; every page of the UNION source set (authoritative guide tree ∪
input/pagecontent
∪ the step-2c harvest — never a fallback chain: one pagecontent stub must not suppress a 149-page guide tree) migrated/retired/MISSING per
migration-log/page-map.tsv
, the reviewed contract the
5.4c page-routing
advice run generated; every source text run present somewhere. Fidelity: identity, dependency pins identical to the source's,
license
asserted from a tier and never defaulted — and reconciled against the LICENSE file's own text (mismatch DIVERGIERT, unrecognized text NICHT PRÜFBAR). Provenance: the template package+version read out of the rendered
qa.html
, the publisher version, the pinned guide version — comparing like with like, because the ig-template PACKAGE version and the module-template REPO release are different numbers (measured: repo
v0.6.0
vendors package
0.5.1
) — plus P5:
ig.ini
's
ig=
names the IG resource SUSHI actually writes (
ImplementationGuide-<id>.json
, derived from the
id
, never from the repo slug). Rendering integrity: empty tables/tabs, missing images, header markers like
Unknown region code
, and language parity — a translated page byte-identical to the default is a fallback, not a translation. The run log is the SECOND ORACLE (§11.6): it records what each step intended and measured where the tree records the outcome, so it alone catches an unactioned
silent-partial-success:
WARN, a step that emitted no line at all (
references/expected-steps.tsv
is the manifest), an open
identity-contradiction:
, and log-versus-artefact count mismatches. Three verdicts, not two:
IDENTISCH
,
DIVERGIERT
(named, with evidence) and
NICHT PRÜFBAR
, so an unmechanisable check is never silently written as a pass. Exit 0 clean · 1 divergence · 3 incomplete, which is not a pass.
  1. Report. Write
    migration-log/migration-report.md
    from the report template — three reviewer queues (① decide, ② review, ③ triage): every open decision,
    TODO:REVIEW
    and QA finding lands in exactly one, and QA provenance requires proof (build the unmigrated source to claim "pre-existing"). Its reader has never seen this skill, spec or repo: every labelled line the template lists is mandatory (incl. if nobody acts, who decides, effort, reversibility), codes are glossed where used, and ids tie the queues to Sign-off. Two GENERATED aids are pasted, never retyped (invocations in the template's How to re-run):
    qa-checklist.py
    renders every open obligation from the ledgers as per-gate
    - [ ]
    checkboxes into Sign-off, and
    comparison-table.py
    renders the map as clickable rendered-source ↔ rendered-target rows plus the template-page provenance into Content map. The protocol section is generated FROM
    migration-log/run.log
    (spec §10.6): every claim traces to a line, every WARN/ERROR lands in a queue, the log wins.
  2. Open a pull request with the report as its description. Do not publish. Name the working branch
    migration/<source-version>-template-<module-template-tag>
    (spec step 9 — five try-runs each invented a different scheme). Determine the PR's target branch from the module repository's own convention — discover it, do not assume it: the default branch, the bases of merged pull requests, CONTRIBUTING/README. The template previews every non-
    main
    branch to
    gh-pages
    under
    branches/<branch>/
    and reserves
    main
    and tags for publication, so a working branch previews without touching the default branch. Follow a different convention where the repository has one and say so — and if that PR base is itself the publication branch, say so in the PR and at Gate D: there, merging publishes.
本文档使用英文编写;操作的工件为德语KDS文档,德语专业术语将保留原样。输出语言遵循目标模板:默认语言为英语,德语为翻译版本。这与旧约定相反——请参阅下文的「语言」部分,并以目标模板的
sushi-config.yaml
为准,而非本文档的描述。
首先解析脚本路径。以下命令中工具的路径是相对于本技能自身的目录,而非你的工作目录(即正在迁移的项目目录)。设置
SKILL_DIR=<包含本SKILL.md的目录>
(例如
.claude/skills/mii-ig-migration
)和
ML="$SKILL_DIR/scripts/migration-log.sh"
,并在所有调用中使用它们。若直接从项目根目录调用
scripts/...
,不仅会失败:若项目自身有
scripts/
目录且包含同名文件,会静默运行项目自身的脚本
  1. 盘点源项目——首先执行关卡0(规范§9c):对未迁移的源项目运行同级的
    fhir-ig-analysis
    工具(
    bash "$ML" run 1 preflight-analysis …
    migration-log/preflight-analysis.json
    );其工件计数将驱动M9决策和§9b中CS缺失路径的处理,
    special_url_prediction
    将预构建特殊URL列表,
    qa_baseline: None
    意味着:需立即获取源项目的QA证明。然后,从已渲染的IG和源仓库中提取所有工件(配置文件、扩展、值集、代码系统、能力声明、示例)和叙述结构,并记录每个工件的源路径 →
    migration-log/source-inventory.json
    。若
    implementation-guides/
    目录包含多个指南目录结构(版本×语言 + 共享资源——实际模块包含6个),需遵循规范§5.1a:选择权威目录结构,将并行语言目录标记为提取源,保留其余目录。 无已渲染IG的URL?请查找——规范§5.1c
    bash "$SKILL_DIR/scripts/simplifier-discover.sh" --org ORG --module SLUG
    将遍历组织项目列表 → 包 → 项目 →
    /<project>/filterprojectguides
    (无波浪线;带
    ~
    的变体返回200但无内容) →
    /published-guide/<key>/versions
    → 服务器渲染的指南根目录,若任何环节无输出则发出警告。固定一个已发布的只读版本,并像记录源提交SHA一样记录它——切勿使用
    ?version=current
    ,这是可编辑的实时项目
    。密钥和页面slug需通过发现获取,切勿构造(构造的密钥会返回404;渲染器会去除slug中的标点)。PROJECT页面是客户端渲染的,DOM中无身份元数据;GUIDE页面是服务器渲染的,包含所有内容——将单个实例的测量结果推广到整个平台曾导致本技能流程出错。单个实例不能代表全部,正反方向均如此(规范§5.1c.2a):需针对你所声称的精确工件进行负面测量,在将一种「形态」编码为标准形态之前,需在多个实例上进行测量,选择最可能不同的实例——同一天对
    data-url="/guide/<key>"
    的测量在一个模块中所有密钥均为裸值,而在另外两个模块中密钥被静默丢弃(3个同意模块中的3个,2个微生物模块中的2个,0个人物模块,均正常退出)。
  2. 读取模块标识——请勿询问,也请勿编造。从源项目的
    sushi-config.yaml
    package.json
    中读取
    title
    packageId
    canonical
    status
    releaseLabel
    license
    dependencies
    publisher
    ,并原样保留。若存在冲突,以
    sushi-config.yaml
    为准——这是构建过程读取的文件;需记录冲突情况。若上述两个文件中均无某个字段,则从生成的
    ImplementationGuide
    资源中获取;若所有来源均无该字段,则使用模板默认值,待关卡A确认。解析浮动版本号(如
    1.5.x
    )需遵循规范§2.1,记录选择结果及依据。 形态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
      URL——需所有资源一致,否则发出警告,切勿采用多数投票。清单中无
      title
      license
      publisher
      字段;
      author
      是注册表账户,而非发布者。
    • R——源仓库
      bash "$SKILL_DIR/scripts/repo-identity.sh" --dir DIR --repo OWNER/NAME --rendered URL
      将读取LICENSE文本的SPDX标识符——这是真实的许可证证据,此字段绝不能使用默认值(§2.2)——README的第一个标题作为
      title
      候选、仓库描述、发布标签,发布标签与P的版本匹配可将该发布与提交关联。无法识别的许可证文本无输出(
      license-text-unrecognized:
      );GitHub所有者并非
      publisher
    • H——Simplifier两个URL空间,两种结果——请勿混淆(规范§2.1.3)。项目页面
      simplifier.net/<Project>/
      )是客户端渲染的(HTTP 200,约56 KB,52个脚本标记,DOM中无身份元数据):仅作为关卡A的人工参考,用于机器源无法获取的信息,而非抓取目标;脚本会测量此情况并无提取内容。指南页面
      simplifier.net/guide/<key>/<Root>[/<Page>]?version=<v>
      )属于不同的URL空间,且是服务器渲染的——在Consent 2026.0.0版本上测量:根页面24509字节,包含整个页面树(18个页面链接);叶子页面20481字节,包含
      <h1 id="page-title">
      和真实的德语叙述内容。这些页面提供叙述内容,而非标识信息:需按步骤2c提取。
    若所有层级均无某个字段的值,需留待关卡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
    将生成报告的标识表。切勿使用恢复的值修改现有元数据,即使恢复结果显示现有元数据不一致:恢复结果仅作为关卡A的证据。规范§2.1.2–§2.1.4。
    记录每个读取的值,以及每个差异作为警告。目标版本是唯一需要人工决策的标识值:需使用MII CalVer格式
    YYYY.n.n
    ,而非SemVer,默认使用源项目的版本。若源项目与模板存在冲突,以源项目为准——模板的
    canonical
    packageId
    模式适用于模块,修改已发布的canonical URL会破坏所有消费者。需上报每个差异并由人工决策;切勿静默标准化。这涵盖了模板预填充为字面量而非占位符的所有值,尤其是
    license
    :模板默认使用
    CC-BY-4.0
    ,占位符检查不会标记它,但MII模块通常声明
    CC0-1.0
    。重新许可属于人工决策,切勿使用默认值。规范§2.2。
2b. 仅适用于源形态B——从原始资源生成FSH。需在生成骨架之前执行,步骤3会将骨架合并到已存在的FSH中。在模块仓库外的临时目录中操作;形态A可跳过此步骤。规范§5.1b为标准要求(使用goFSH 2.6.1和SUSHI 3.20.0
medizininformatik-initiative/kerndatensatzmodul-consent
上测量)。
bash
mkdir -p migration-log
ML="$SKILL_DIR/scripts/migration-log.sh"   # 运行日志助手——请参阅下文的「运行日志」
SRC=<source-repo-root>; OUT=<scratch-dir>; GLOG=migration-log/gofsh.log
SUSHI="npx --yes fsh-sushi@3.20.0"
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"   # 运行边界:重新运行时可区分
rm -rf "$OUT"     # goFSH拒绝非空的-o目录;$OUT为生成目录,因此可安全清空

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 ' ')                                     # 输入项,按内容统计
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 gofsh@2.6.1 "$SRC" -o "$OUT" -s file-per-definition -t json-and-xml \
  -d <parent-ig-package>@<version> -d hl7.fhir.r4.core@4.0.1
GOFSH_EXIT=$?
bash "$SKILL_DIR/scripts/gofsh-results.sh" --log "$GLOG" --inputs "$N_IN" \
  --exit $GOFSH_EXIT                          # <- 此处会触发强制警告
# 转换失败则停止:以下所有操作均基于$OUT(规范§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 ."                               # 从"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 ."                               # 最终为"5"
A=$(E migration-log/sushi-after.log)   # 若运行未输出计数(崩溃/终止)则为n/a
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表,标记每个单元格,仅统计已转换的资源(不包括约束/映射/别名),并与
$N_IN
核对。核对是整个代码块的核心——goFSH的退出码并非信号,其计数才是——
run
会保留每个真实的退出状态,每次调用时截断原始日志,因此重新运行时仅测量本次运行,而非两次运行的总和。
  • 请将goFSH指向仓库根目录,而非单个资源文件夹。参考模块的20个资源存储在5个手动命名的目录中,无需 staging:测量显示,goFSH会递归遍历目录树,从根目录生成的FSH与从staging的扁平目录生成的FSH字节完全相同。仅当有记录的原因时才进行staging(规范§5.1b.2)。
  • -t json-and-xml
    是必需的;缺失会静默失败
    。goFSH默认使用
    json-only
    :在Consent模块(19个XML + 1个JSON)上,无此标志的运行退出码为0,报告"0 Errors",但仅转换了一个资源,仅警告"13 XML definition(s)"缺少JSON对应项——这是goFSH自身的计数,而非输入的19个文件(差异为6个
    SearchParameter
    );需明确说明你指的是哪个计数。决定性数字既不是输入数也不是goFSH的计数:而是
    converted 1 of 20
    gofsh-results.sh
    会输出此结果并发出警告。需与步骤1的盘点结果核对,切勿依赖退出码。
  • 需使用
    -d <package>@<version>
    声明所有外部父IG
    ,可从资源自身的
    baseDefinition
    canonical URL中找到;goFSH的"cannot find a definition for its parent … declaring that IG as a dependency"是触发信号。需重新运行,而非修补无依赖的输出——两种结果不同(无依赖时为12个映射/14个别名,有依赖时为0/8)。Forge仓库既不指定包也不指定版本:需在FHIR包注册表中解析(规范§5.1b.2);若无匹配结果,需在关卡A停止操作。
  • goFSH会自行生成
    sushi-config.yaml
    ,但这只是起点,并非标识信息
    :无
    id
    /
    name
    /
    title
    /
    publisher
    /
    packageId
    /
    license
    字段,
    version
    不可信(测量示例:
    1.0.8
    ——来自一个配置文件——与模块已发布的
    2026.0.0
    冲突),
    dependencies
    仅包含你通过
    -d
    设置的内容。需按步骤2恢复标识信息。
  • 脚本的两次处理是机械性的:修复
    fhir_comments
    规则和名称包含空格的未引用代码参考系统,使用goFSH自身报告的名称进行修复。它会先分类再写入,对无法建模的形态不写入任何内容,且具有幂等性。需提供完整的FSH目录——缩小路径会导致它无法看到所需的声明,因此会拒绝执行(退出码1,
    run
    会返回此状态,而
    tee
    会隐藏此状态)。
  • 之后SUSHI必须编译通过,除了真正无法解析的父项——测量结果为处理前41个错误,处理后5个,均由上述代码块记录。解析错误会导致SUSHI停止读取文件的剩余部分,但仍会导出实例,且静默截断("导出"不等于"转换"):三个Consent示例处理前每个包含1个嵌套规则,处理后分别为27/6/3个。SUSHI的退出状态即为错误计数,因此
    sushi-after
    会退出5——这是形态B的预期结果,
    --expected-nonzero
    会将其记录为升级而非失败。
  • 若父包无快照,会阻止导入——SUSHI完全无法读取此类父包,从而阻止这些配置文件以及所有声明
    InstanceOf
    它们的实例。需检测此情况,然后使用真实的生成器生成快照;切勿手动构建(规范§5.1b.5):
    bash "$SKILL_DIR/scripts/parent-snapshots.sh" detect --package ID --version V
    会统计快照数量(测量示例:21个SD,0个快照,两个候选版本均如此——其他版本也无法解决),
    … build … --validator validator_cli.jar --install --require <parent-url>…
    会驱动官方HL7生成器
    java -jar validator_cli.jar snapshot
    ,ProfileUtilities),验证每个结果(快照的元素计数仅与差异部分匹配是错误的,会被拒绝),并安装一个新的缓存条目
    <id>#<version>-snapshots
    ——绝不会覆盖上游内容。生成器拒绝属于上游缺陷,需升级处理,切勿手动完成;重建对CI的成本(仅本地)是关卡A的决策内容。近似合并——切片、基数、元素顺序——会构造父项(防护规则1和3)。然后重新固定版本,重新运行SUSHI并记录两个错误计数:在Consent模块上测量,从5→0goFSH生成的id和GUID命名的文件进入②队列:生成的id将成为模块的一部分,需在关卡A确认。
  • 验收标准:计数与盘点结果匹配;脚本退出码为0;所有剩余的SUSHI错误均为已命名的无法解析父项升级;所有内容均记录在
    run.log
    中。形态B本身不会产生干净的构建,因此以下所有"干净构建"标准(步骤3和7,「验证」部分)针对形态B时需遵循形态B限定条件,规范§5.1b.4:无机械错误,所有剩余错误均在①队列中并由关卡A决策,无构造的父项。允许的错误计数不等于通过。
2c. 当叙述内容不在仓库中时提取——形态B始终如此,形态A若页面存储在平台上也需提取规范§5.1d为标准要求(§5.1c已找到指南)。源的优先级从高到低,"无"并非第三个选项:
  • ① 已认证的项目下载——只要有凭证,优先选择
    https://simplifier.net/<project-slug>/$actions/downloading
    可获取项目包括作者编写的叙述markdown,但需要Simplifier登录:测量显示,匿名访问会返回登录页面(
    /login?ReturnUrl=…
    ),所有尝试的匿名替代方案均返回404或HTML,因此不存在经过验证的匿名项目下载方式。需人工在自己的浏览器中登录,下载归档文件并指定仓库外的路径;代理会读取该文件并记录
    narrative-source=project-download
    切勿发明凭证机制、索要密码或存储令牌——关卡的意义正在于此。若无人可用,需记录
    project-download-unavailable:
    并转向②。
  • ② 指南提取——匿名、经过验证,属于渲染结果(指令已展开;渲染的工件视图并非其渲染的资源)。直接调用即可,它会自行记录运行日志:
    bash
    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:
    警告;每个页面会统计未完成转换的源文本行数(
    missing_runs=
    )并发出警告——在工件视图上使用单独的
    generated-view-lossy:
    标记,因此生成的表格不会掩盖真实的损失。在Consent 2026.0.0版本上测量:发现18个页面,提取18个,跳过0个,叙述页面无缺失,14个叙述页面 + 4个工件视图,3个资源。退出码0表示提取干净;1表示有页面被跳过或内容缺失。
  • ③ "无"并非第三个选项。若源不可访问,需上报并升级到关卡B,同时命名该源。
注册表包的角色未变:提供资源和标识信息(步骤2),无叙述内容——它是提取集验证的依据。提取的页面与其他源形态遵循相同的约定:
5.4c page-routing
建议运行会读取清单(
--harvest-tsv
,从目标的
migration-log/
中自动发现)并生成映射行——提取跳过的页面会阻止覆盖验证,直到提取干净或人工在已审核的映射中标记为停用——步骤5仅使用映射(规范§9/§9f):绝非一个提取页面对应一个目标页面,需根据「语言」部分放置语言版本。
  1. 创建骨架(规范§5.2)。迁移在原地进行:在模块现有仓库的工作分支上,引入前置条件3中检出的模板并运行其首次引导程序——切勿创建新仓库;历史记录、问题和消费者需保留在原位置(创建新仓库是人工决策,需记录在报告中,绝非默认操作)。使用步骤2读取的标识信息替换所有
    {{...}}
    占位符。模板的CRMI
    meta.profile
    声明需要
    hl7.fhir.uv.crmi
    依赖
    ——需将其添加到迁移的源依赖中,在关卡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 fsh-sushi@3.20.0 .
    运行成功(形态B:需遵循步骤2b的限定条件),且跳过/追加列表已记录在日志中。
  2. 迁移工件——保留结构。迁移FSH源时保留源的目录结构;若源仅包含JSON/XML,需使用固定版本的
    npx --yes gofsh@2.6.1
    转换——形态B已在步骤2b完成此操作,因此此处迁移的是其后处理输出。ID和URL保持不变。验收标准基于路径,而非计数:对源和目标的仓库相对FSH路径列表进行排序后,
    comm -3
    的结果除日志中命名的脚手架添加项外为空——仅计数无法证明结构一致。
  3. 迁移叙述内容为步骤2c;此步骤进行映射。将Manteldokument内容迁移到
    input/pagecontent/*.md
    ,并将Simplifier和FQL指令转换为IG Publisher等效指令:
    bash
    bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict
    扫描是递归的,迁移前包括
    implementation-guides/**
    目录;它会记录每个目标扫描的文件,若指定目录无贡献则发出警告,若目标集为空则退出2——切勿将"无扫描内容"解读为"无发现"。
    --strict
    会在任何发现时退出1;
    run
    会保留两个状态,而
    tee
    会报告0。需应用每个打印的建议;映射请参阅FQL对照表,规则请参阅
    references/fql-rules.tsv
    。若有疑问,请写入
    TODO:REVIEW
    在写入前需路由所有源页面——规范§9e,匹配优先:单个工件 →
    input/intro-notes/<Type>-<id>-intro.md
    (德语镜像使用相同文件名,渲染在工件页面顶部);家族概述 → 现有索引页面的一个章节;已有页面包含该内容 → 合并;仅交叉叙述内容成为页面——中心页面(≤250字,每个子项一行)适用于≥3个子项,仅在预算内添加菜单条目(≤33个条目,每个下拉菜单≤10个,顶级≤8个,深度≤2),否则嵌套在
    pages:
    中并链接。大小限制:>2500字、>4个合并源或任何重复标题需重新路由(PROs曾发布一个6214字的宿主页面,13个冲突锚点)。路由是生成的,而非判断的——
    5.4c page-routing
    运行即
    page-structure-advice.py
    --map
    (调用方式请参阅「验证」部分):它会从这些分支以及语义路由表
    references/routing-table.tsv
    生成
    migration-log/page-map.tsv
    (v2:
    source_page⇥target⇥reason⇥branch⇥measure
    ),验证源页面全集的覆盖情况(权威指南树 ∪
    input/pagecontent
    ∪ 磁盘上无目录列表的页面 ∪ 步骤2c的提取清单;退出码1直到每个页面都有目标,每个RETIRED行都有原因——悬空的目录条目和提取跳过的页面均为发现结果),并从关卡0提出M9/其他分类决策。映射是契约(规范§9f):人工需在写入任何内容之前审核/编辑映射;此步骤仅使用映射——映射中未包含的页面不写入,映射行未处理属于缺陷;步骤7b会对照同一映射检查。 你编写的文本(概述、中心页面单行内容、过渡内容)是派生的——需按§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)。索引样式一致(作者/联系人列为列表,免责声明为 prose)、
    (de)
    翻译标记措辞和单次提交可恢复修复协议均遵循§9a;若检测到缺失CapabilityStatement,需根据模块的配置文件建议并在其页面上内联渲染(规范§9b)。
  4. 搭建双语页面。默认语言为英语;德语为翻译版本,存储在
    input/translations/de/pagecontent/
    下的同名文件中。这些文件会正常渲染。菜单为
    input/includes/menu.xml
    ,每个语言的镜像为
    input/translations/de/includes/menu.xml
    ——切勿使用
    sushi-config.yaml
    中的
    menu:
    属性,否则会冲突。资源翻译为
    input/translations/de/
    下的
    .po
    补充文件;在投入资源之前,需检查目标的构建说明,确认哪些资源类型会实际渲染。若源仅为德语,需反转方向——请参阅下文的「语言」部分。 页面标题(面包屑、目录、
    <title>
    )——完整说明请参阅规范§5.5
    。发布者会本地化标题,通过一个IG级别的目录
    input/translations/<lang>/ImplementationGuide-<ig-id>.po
    (加载时导入到IG资源中——并非资源补充,因此不适用其类型限制)。需在步骤3的SUSHI运行后,从SUSHI生成的ImplementationGuide资源(权威标题集)生成该文件——菜单仅作为翻译种子:
    bash
    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>.po
    migration-log/menu-titles-de.txt
    必需参数,无默认值(每行一个
    English Title => Deutscher Titel
    ;需按规范§5.5构建,或使用
    -
    "无种子"选项)——路径无法解析属于设置错误,绝非静默使用空种子。重新生成不会破坏现有内容;空的
    msgstr
    表示未翻译,进入②队列。陷阱:语言必须出现在**
    translation-sources
    中,而非仅
    i18n-lang
    ,否则所有
    .po
    文件会被静默忽略。模板
    v0.5.0**及以上版本的模块还需移除其面包屑覆盖。
  5. 构建与QA。先运行SUSHI,再运行IG Publisher——均通过
    bash "$ML" run 5.6 …
    执行,因此此步骤生成的两个关键数字会记录在日志中:SUSHI的错误计数,以及
    qa.txt
    的摘要行复制到INFO中(规范§5.6包含代码块)。目标在构建工作流的
    env:
    块中固定了工具链——需从那里读取版本固定信息,而非本文档。验收标准:
    qa.txt
    报告
    Errors: 0
    ,且所有示例验证通过——形态B:需遵循步骤2b的限定条件,已命名的升级除外,其他任何错误均需停止操作。然后使用
    fhir-ig-analysis
    运行同模块验证(测量未迁移的源,然后测量迁移后的目录——POSTFLIGHT测量,保存为
    migration-log/postflight-analysis.json
    ;相同的
    packageId
    会触发比较,SOURCE为第一个输入):标识、已发布工件集和canonical URL必须全部显示为IDENTISCH,若显示为DIVERGIERT则需停止操作;每种语言的叙述内容表会进入报告的QA分类。然后对比两次测量结果,使用
    scripts/prepost-delta.py
    (调用方式请参阅「验证」部分;规范§5.6):每个对比属性一个 verdict(
    unchanged · improved · REGRESSION · expected-change
    ),退出码1表示某个属性变差(工件计数减少、许可证矛盾、依赖注入风险出现、标识字段变更)——需停止操作修复,绝非记录差异;若两次测量的普查
    mode
    字段不同(
    reduced
    原始资源 vs
    static
    FSH声明普查——提取的形态),计数差异会报告为expected-change并命名模式,绝非回归(规范§5.6)。该同级技能是已检查的前置条件,而非假设——
    bash "$SKILL_DIR/scripts/sibling-skill-check.sh" --skill-dir "$SKILL_DIR"
    会查找该技能,若找不到则发出
    sibling-skill-unavailable:
    警告,并提供精确的固定版本
    npx skills add
    命令,且绝不安装:工具授权是许可,而非依赖,无关运行不得写入操作员的技能目录(规范§5.6a)。
7b. 验证——机械性验证,非零退出码。规范§11为标准要求。它取代了人工曾使用的 prose 检查表:四个真实迁移通过了该检查表且构建成功,但却发布了不可访问的工件、过时的渲染来源、损坏的页面页眉、静默截断的文件和错误的依赖版本固定。
bash
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的提取内容——绝非 fallback 链:一个pagecontent stub不得掩盖149页的指南树)的每个页面均已迁移/停用/MISSING,符合
migration-log/page-map.tsv
5.4c page-routing
建议运行生成的已审核契约);每个源文本行均存在于某处。保真度:标识、依赖版本固定与源完全相同
license
从某个层级获取且未使用默认值——并与LICENSE 文件自身的文本核对(不匹配则为DIVERGIERT,无法识别则为NICHT PRÜFBAR)。来源:从渲染的
qa.html
中读取的模板包+版本、发布者版本、固定的指南版本——需同类对比,因为ig-template包版本与module-template仓库发布版本是不同的数字(测量示例:仓库
v0.6.0
引入了包
0.5.1
)——加上P5
ig.ini
ig=
指向SUSHI实际生成的IG资源(
ImplementationGuide-<id>.json
,从
id
派生,绝非仓库slug)。渲染完整性:空表格/标签页、缺失的图片、页眉标记如
Unknown region code
,以及语言一致性——翻译页面与默认页面字节完全相同属于 fallback,而非翻译。运行日志是第二个权威来源(§11.6):它记录了每个步骤的意图和测量结果,而目录记录的是结果,因此只有它能捕获未处理的
silent-partial-success:
警告、完全未输出任何行的步骤
references/expected-steps.tsv
是清单)、未解决的
identity-contradiction:
,以及日志与工件计数不匹配的情况。三个 verdict,而非两个
IDENTISCH
DIVERGIERT
(已命名,带证据)和**
NICHT PRÜFBAR
**,因此无法机械验证的检查绝不会被静默标记为通过。退出码0表示干净 · 1表示差异 · 3表示不完整,并非通过
  1. 报告。从报告模板编写
    migration-log/migration-report.md
    ——三个审核队列(① 决策,② 审核,③ 分类):每个未解决的决策、
    TODO:REVIEW
    和QA发现均需进入恰好一个队列,QA来源需要证明(构建未迁移的源以声明"预先存在")。读者从未见过本技能、规范或仓库:模板列出的每个标记行都是必需的(包括若无人操作,谁决策、工作量、可逆性),代码在使用时需附带注释,id需将队列与签署关联。需粘贴两个生成的辅助内容,切勿重新输入(模板的「如何重新运行」部分包含调用方式):
    qa-checklist.py
    会从分类账中渲染每个未完成的义务,作为每个关卡的
    - [ ]
    复选框进入签署部分;
    comparison-table.py
    会将映射渲染为可点击的渲染源 ↔ 渲染目标行,加上模板页面来源,进入内容映射部分。协议部分从
    migration-log/run.log
    生成
    (规范§10.6):每个声明都可追溯到一行,每个WARN/ERROR均进入队列,日志为准。
  2. 创建拉取请求,将报告作为描述。请勿发布。工作分支命名为
    migration/<source-version>-template-<module-template-tag>
    (规范步骤9——五次试运行各发明了不同的命名方案)。需根据模块仓库自身的约定确定PR的目标分支——需发现,切勿假设:默认分支、已合并拉取请求的基础分支、CONTRIBUTING/README。模板会将所有非
    main
    分支预览到
    gh-pages
    branches/<branch>/
    下,
    main
    分支和标签保留用于发布,因此工作分支可预览而不影响默认分支。若仓库有其他约定,请遵循并说明——若该PR基础分支本身是发布分支,需在PR和关卡D中说明:合并后即发布。

Run log

运行日志

What it is for, once: so a human reader can reconstruct which steps ran and what each produced — the command actually executed, the counts it returned, the status it exited with — without re-running anything and without trusting recollection.
migration-log/run.log
: plain text, append-only, committed with the branch. The report's protocol section is generated from it (step 8) and step 7b reads it back as a second oracle (§11.6), so it cannot claim what the run did not do. Spec §10 is normative — its §10.5 table is the full API. Emit every line through the bundled helper,
ML="$SKILL_DIR/scripts/migration-log.sh"
, including from the many steps that run no bundled script:
begin LABEL
(one numbered
run-boundary
, first in every block) ·
info|warn|error STEP ACTION DETAIL [CONT …]
·
ratio [--exit N] STEP ACTION VERB NOUN EXPECTED ACTUAL
(an INFO naming both counts plus the mandatory WARN when ACTUAL < EXPECTED) ·
run STEP ACTION [--emits-runlog] [--raw-log F] [--expected-nonzero WHY] -- CMD …
(the command actually executed, its output at
migration-log/<ACTION>.log
truncated per invocation, and its real exit status, returned rather than swallowed).
Never
… 2>&1 | tee -a migration-log/run.log
.
A pipeline's status is
tee
's, and this skill's acceptance criteria are exit statuses: measured, that pipeline reported 0 where
fsh-sushi
exited 41 and
postprocess-gofsh.py
exited 1, so failed steps read as passed.
run
takes the status from
PIPESTATUS[0]
. An exit status is eight bits — 256 SUSHI errors report as
exit=0
— so
run
cross-checks it against the raw log's error count (
exit-status-truncated:
).
--expected-nonzero WHY
marks a step whose non-zero exit is the documented outcome (shape-B
sushi-after
; the step-7b verification), logging a WARN naming the escalation rather than an ERROR calling the expected result a failure.
Format:
<UTC ISO-8601>  <LEVEL>  <STEP>  <ACTION>  <DETAIL>
, two spaces between fields,
LEVEL
padded to five,
DETAIL
carrying the command actually executed as
cmd=`…` 
plus measured
key=value
outcomes; continuations indented four spaces — e.g.
… WARN   5.1b.2  gofsh-convert  silent-partial-success: converted 1 of 20 inputs at exit 0
. Every step emits at least one INFO line, and step 7b's L2 check reports the ones that did not (
references/expected-steps.tsv
) — a step that did not run is invisible in the tree. WARN is mandatory for silent partial success: name both numbers, via
ratio
, never by hand — on that run every other signal was green (postprocess "nothing to repair", SUSHI 0 errors) while 19 of 20 resources were missing. Read it back with
grep -E '  (WARN |ERROR)  '
.
用途:让人工读者无需重新运行任何操作,也无需依赖记忆,即可重建哪些步骤已运行以及每个步骤产生的结果——实际执行的命令、返回的计数、退出状态
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时的强制警告) ·
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
标记退出码非零为文档化结果的步骤(形态B的
sushi-after
;步骤7b的验证),记录为警告(命名升级)而非错误(将预期结果视为失败)。
格式:
<UTC ISO-8601>  <LEVEL>  <STEP>  <ACTION>  <DETAIL>
,字段间用两个空格分隔,
LEVEL
填充为5个字符,
DETAIL
包含实际执行的命令,格式为
cmd=`…` 
,加上测量的
key=value
结果;续行缩进四个空格——例如
… WARN   5.1b.2  gofsh-convert  silent-partial-success: converted 1 of 20 inputs at exit 0
每个步骤至少输出一个INFO行,步骤7b的L2检查会报告未输出的步骤
references/expected-steps.tsv
)——未运行的步骤在目录中不可见。WARN是静默部分成功的强制要求:需通过
ratio
命名两个数字,切勿手动输入——在那次运行中,其他所有信号均为绿色(后处理"无内容修复",SUSHI 0错误),但20个资源中有19个缺失。可使用
grep -E '  (WARN |ERROR)  '
读取。

Guardrails

防护规则

Binding — a migration that violates one is wrong even if it builds.
  1. Canonical URLs and IDs of existing conformance resources are never changed.
  2. FHIR R4 (4.0.1).
  3. No fabrication. Every artefact and narrative section traces to a source URL or repo path; uncertainty is marked
    TODO:REVIEW
    , never guessed. (
    TODO:REVIEW
    marks the migrated guide; the catalog's marker for unfinished skill content is
    TODO(owner):
    — do not mix them.)
  4. Human in the loop. The review gates below are mandatory. The agent does not publish.
  5. Template examples are deleted before migrating, never merged with the module's own.
  6. The default branch is not modified. Work on a branch, deliver a pull request.
  7. Traceability. Every step emits run-log lines as it runs, through
    scripts/migration-log.sh
    (Run log, above), and every assumption and open point reaches
    migration-log/migration-report.md
    , whose protocol section is generated from that log.
  8. No Liquid literals in
    pagecontent
    , including inside HTML comments.
    Jekyll evaluates
    {% … %}
    and
    {{ … }}
    everywhere: an invalid
    {% … %}
    breaks the build hard, an unknown
    {{ … }}
    silently empties and leaks into the HTML. Describe such mechanisms in prose.
  9. A negative capability finding is only valid for the artefact it was measured on. Record every "cannot be read / not available / not possible" with the exact URL, endpoint or command, the date and the numbers; never generalise one to a sibling URL, endpoint or mechanism; re-measure before acting on one. Nothing downstream re-checks such a claim, so it silently turns a missing capability into a missing deliverable — twice already (page-title localization; the Simplifier guide, whose false claim shipped a migration with the template's starter pages). Spec §2.1.3, §4 guardrail 10, §5.1c.2a — which states it in both directions: a positive shape measured on one instance is a hypothesis too.
具有约束力——违反任何一条的迁移即使构建成功也是错误的。
  1. 现有一致性资源的Canonical URL和ID绝不能修改
  2. 使用FHIR R4(4.0.1)版本
  3. 不得构造内容。每个工件和叙述章节均可追溯到源URL或仓库路径;不确定性需标记为
    TODO:REVIEW
    ,切勿猜测。(
    TODO:REVIEW
    标记迁移后的指南;目录中未完成技能内容的标记为
    TODO(owner):
    ——请勿混淆。)
  4. 人工参与。以下审核关卡是必需的。代理不会执行发布操作。
  5. 迁移前需删除模板示例,绝不与模块自身的示例合并。
  6. 不得修改默认分支。在分支上工作,交付拉取请求。
  7. 可追溯性。每个步骤运行时需通过
    scripts/migration-log.sh
    输出运行日志行(上文的「运行日志」),每个假设和未解决的问题均需记录到
    migration-log/migration-report.md
    ,其协议部分从该日志生成
  8. pagecontent
    中不得包含Liquid字面量,包括HTML注释内的
    。Jekyll会在所有地方解析
    {% … %}
    {{ … }}
    :无效的
    {% … %}
    严重破坏构建,未知的
    {{ … }}
    会静默清空并泄露到HTML中。需用 prose 描述此类机制。
  9. 负面能力发现仅对测量的工件有效。记录每个"无法读取/不可用/不可能"的情况需包含精确的URL、端点或命令、日期和数字切勿将一个实例推广到同级URL、端点或机制;操作前需重新测量。下游不会重新检查此类声明,因此会静默将缺失的能力转换为缺失的交付物——已发生两次(页面标题本地化;Simplifier指南,错误声明导致迁移发布了模板的初始页面)。规范§2.1.3、§4防护规则10、§5.1c.2a——正反方向均如此:单个实例的正面形态测量也是假设。

Language

语言

Three facts, easy to conflate.
  • The target template's default language is English, German the translation (
    i18n-default-lang: en
    ). Verify it in the target's
    sushi-config.yaml
    on every run — it moved once already. FHIR artefact identifiers stay English regardless.
  • A
    de-DE
    mismatch warning is conditional
    — it fires only when the source FSH sets
    ^language = #de-DE
    , is cosmetic, and is suppressed in
    input/ignoreWarnings.txt
    (glob with
    %
    wildcards, not regex; match
    %(de-DE)%
    ), leaving the FSH untouched. Spec §4.1.
  • A German-only source inverts the direction — and that is this skill's to handle. The normal KDS case: the source's narrative is German while the target's default is English, so the German text becomes the translation of English pages that do not yet exist. Transfer it to
    input/translations/de/pagecontent/
    and produce
    input/pagecontent/*.md
    as machine translations of it, every page marked
    TODO:REVIEW
    , reviewed at Gate C — the one sanctioned exception to guardrail 3, since each traces to the page it renders. A top-level
    language:
    in the source is old single-language setup, not identity. Step 7b's R3 check is what proves the result: a translated page byte-identical to the default is a fallback, not a translation.
三个易混淆的事实。
  • 目标模板的默认语言为英语,德语为翻译版本(
    i18n-default-lang: en
    )。每次运行需在目标的
    sushi-config.yaml
    中验证——它已变更过一次。FHIR工件标识符始终为英语,无论其他内容如何。
  • de-DE
    不匹配警告是有条件的
    ——仅当源FSH设置
    ^language = #de-DE
    时触发,属于 cosmetic 问题,可在
    input/ignoreWarnings.txt
    中抑制(使用
    %
    通配符的glob,而非正则;匹配
    %(de-DE)%
    ),FSH保持不变。规范§4.1。
  • 仅德语的源需反转方向——本技能会处理此情况。这是KDS的常见情况:源的叙述内容为德语,而目标的默认语言为英语,因此德语文本成为尚未存在的英语页面的翻译。需将其迁移到
    input/translations/de/pagecontent/
    ,并将
    input/pagecontent/*.md
    生成为其机器翻译,每个页面标记
    TODO:REVIEW
    ,在关卡C审核——这是防护规则3的唯一批准例外,因为每个页面均可追溯到其渲染的源页面。源中的顶级
    language:
    是旧的单语言设置,而非标识信息。步骤7b的R3检查会验证结果:翻译页面与默认页面字节完全相同属于 fallback,而非翻译。

Verification

验证

Mechanical, and it exits non-zero — step 7b, spec §11. The list of sentences that used to stand here is now
scripts/verify-migration.py
: four layers plus the run log as a second oracle, three verdicts, one row per checked subject in
migration-log/verification-findings.tsv
. What a human still owes is each NICHT PRÜFBAR row — named, with its action and its gate; exit 3 says the phase is incomplete, not passed. It compares the migrated guide against the Simplifier-rendered source, not against itself, on six aspects — a qa error count catches none of them. Toolchain provenance (
P1
P5
): the template package the rendered site reports, the one the tree carries, and the latest release are three different numbers (§11.4 — repo tag
v0.6.0
vendors package
0.5.1
; comparing the first against the third manufactures a confident, wrong finding), plus the publisher version against the workflow pin, the pinned guide version, and
ig.ini
pointing at the IG resource SUSHI actually writes (
P5
, the id-vs-slug class). Page set and menu (
C3
,
C5
): every page of the union source set (authoritative guide tree ∪
input/pagecontent
∪ the step-2c harvest, chosen by the same rule as the advice script and overridable via
--source-guide-tree
— never an all-or-nothing fallback) migrated / retired-with-a-reason / MISSING; every menu entry leading somewhere; every narrative page in a menu; every target page traceable to a source page or to
references/template-pages.tsv
; a translated menu wherever there are translated pages. Artefact completeness and reachability (
C1
,
C2
): present is not the same property as listed, in both directions — artefacts SUSHI generated, and artefacts rendered from
input/resources
that the forward pass cannot see.
C1
names an artefact by its id or its canonical url, because a SearchParameter may have only the latter and the migration reassigns the id; a variant is a directory that renders pages, so the multi-language build's redirect-stub ROOT is not one (§11.5a — reading it as one reported a false BLOCKER on all four migrations). Rendering integrity (
R1
,
R2
): tables, tabs and images non-empty where the source's were, and the header/footer regions, where a jurisdiction the publisher cannot resolve renders as
Unknown region code
at
Errors: 0
. Content placement (
C6
): not whether a text run survived — that is
C4
— but which page it landed on, against the map. Language parity (
R3
,
R5
): a translated page byte-identical to the default is a fallback, not a translation. Two inputs decide how much of that is mechanisable, and earlier steps write them, not the verifier:
migration-log/page-map.tsv
(v2,
source_page⇥target⇥reason⇥branch⇥measure
, of which the verifier reads the first three columns — generated by the
5.4c page-routing
advice run and human-reviewed, never hand-written
) — without it
C3
cannot run at all and
C6
degrades to a landing distribution a human reads — and the step-5.1c harvest. Absent, they yield NICHT PRÜFBAR, never a pass.
F3
also reconciles the LICENSE file's recognized text against the declared licence (mismatch DIVERGIERT, unrecognized NICHT PRÜFBAR, absent an ok note).
bash
grep -rn '{{' . --include='*.yaml' --include='*.yml' --include='*.md' --include='*.json' | grep -v '\${{'
bash "$ML" run 7 sushi-verify -- npx --yes fsh-sushi@3.20.0 .
bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict
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
These keep their own acceptance: every
{{...}}
accounted for (an unreplaced one ships a bogus artefact silently); SUSHI clean and
qa.txt
Errors: 0
— both shape B as qualified in step 2b, while the IDENTISCH criteria are not qualified by shape;
fql-scan.sh --strict
exits 0 with a non-zero scanned-file count (an empty target set exits 2 and is not a pass) and no
[UNKNOWN]
findings; the advice run regenerates the page map with full coverage — exit 1 names the unrouted page and is not a pass (re-running overwrites the reviewed map, so re-apply and re-review human edits);
prepost-delta.py
reports no REGRESSION row (exit 1 is a stop to fix). Everything else — the six aspects above, identity/licence/pins, parent snapshots, and the run log's own completeness — is a numbered check in spec §11, measured rather than recalled. Every code printed anywhere (M1–M11, C1–C7, F/P/R/L, gates, marker kinds) is glossed inline by the generator and listed in
references/codes.md
.
Auto-fix is optional and bounded (spec §12):
bash "$SKILL_DIR/scripts/autofix-loop.sh" --skill-dir "$SKILL_DIR"
repairs only the four allowlisted mechanical classes, at most 3 iterations, snapshotting each fix, reverting any whose finding did not clear, and stopping the moment the finding set stops shrinking. Identity, narrative, anything the SOURCE declares and every judgement call are excluded by construction — they go to the ① queue.
机械性验证,非零退出码——步骤7b,规范§11。曾在此处的句子列表现已变为
scripts/verify-migration.py
:四层验证加上运行日志作为第二个权威来源,三个 verdict,
migration-log/verification-findings.tsv
中每个检查主题一行。人工仍需处理每个NICHT PRÜFBAR行——命名、说明操作和关卡;退出码3表示阶段不完整,并非通过。它会将迁移后的指南与Simplifier渲染的源对比,而非自身对比,涉及六个方面——qa错误计数无法捕获任何一个。工具链来源
P1
P5
):渲染站点报告的模板包、目录中的模板包、最新发布版本是三个不同的数字(§11.4——仓库标签
v0.6.0
引入了包
0.5.1
;将第一个与第三个对比会产生自信但错误的发现),加上发布者版本与工作流固定版本对比、固定的指南版本、
ig.ini
指向SUSHI实际生成的IG资源(
P5
,id与slug对比)。页面集和菜单
C3
C5
):源页面全集(权威指南树 ∪
input/pagecontent
∪ 步骤2c的提取内容,选择规则与建议脚本相同,可通过
--source-guide-tree
覆盖——绝非全有或全无的fallback)的每个页面均已迁移/停用(带原因)/MISSING;每个菜单条目均指向有效页面;每个叙述页面均在菜单中;每个目标页面均可追溯到源页面或
references/template-pages.tsv
;有翻译页面的地方需有翻译菜单。工件完整性和可访问性
C1
C2
):存在与列出是不同的属性,正反方向均如此——SUSHI生成的工件,以及从
input/resources
渲染的工件,正向流程无法看到。
C1
通过id或canonical url命名工件,因为SearchParameter可能只有后者,且迁移会重新分配id;变体是渲染页面的目录,因此多语言构建的重定向 stub ROOT不属于此类(§11.5a——将其视为工件会在四个迁移中报告错误的BLOCKER)。渲染完整性
R1
R2
):表格、标签页和图片在源中有内容的地方非空,页眉/页脚区域中,发布者无法解析的辖区会在
Errors: 0
时渲染为
Unknown region code
内容放置
C6
):不是文本行是否存活——这是
C4
——而是它落在哪个页面上,与映射对比。语言一致性
R3
R5
):翻译页面与默认页面字节完全相同属于 fallback,而非翻译。两个输入决定了可机械化的程度,早期步骤会写入,而非验证器
migration-log/page-map.tsv
(v2,
source_page⇥target⇥reason⇥branch⇥measure
,验证器仅读取前三列——
5.4c page-routing
建议运行生成并人工审核,绝非手动编写
)——若无此文件,
C3
完全无法运行,
C6
退化为人工读取的着陆分布;以及步骤5.1c的提取内容。若缺失,会产生NICHT PRÜFBAR,绝非通过。
F3
还会将LICENSE 文件的可识别文本与声明的许可证核对(不匹配则为DIVERGIERT,无法识别则为NICHT PRÜFBAR,缺失则为ok备注)。
bash
grep -rn '{{' . --include='*.yaml' --include='*.yml' --include='*.md' --include='*.json' | grep -v '\${{'
bash "$ML" run 7 sushi-verify -- npx --yes fsh-sushi@3.20.0 .
bash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict
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
——形态B均需遵循步骤2b的限定条件,而IDENTISCH标准不按形态限定;
fql-scan.sh --strict
退出码为0 且扫描文件计数非零(目标集为空则退出2,并非通过)且无
[UNKNOWN]
发现;建议运行重新生成页面映射且覆盖完整——退出码1表示存在未路由页面,并非通过(重新运行会覆盖已审核的映射,因此需重新应用并重新审核人工编辑);
prepost-delta.py
报告无REGRESSION行(退出码1表示需停止操作修复)。其他所有内容——上述六个方面、标识/许可证/版本固定、父快照、运行日志自身的完整性——均为规范§11中的编号检查,需测量而非回忆。本文档中打印的所有代码(M1–M11、C1–C7、F/P/R/L、关卡、标记类型)均会由生成器内联注释,并在
references/codes.md
中列出
自动修复是可选且有限的(规范§12):
bash "$SKILL_DIR/scripts/autofix-loop.sh" --skill-dir "$SKILL_DIR"
仅修复四个允许的机械类问题,最多3次迭代,每次修复均快照,若发现未清除则恢复,一旦发现集停止缩小则停止。标识、叙述内容、SOURCE声明的任何内容以及所有判断性决策均被排除——它们进入①队列。

Mandatory human review gates

必需的人工审核关卡

GateAfter stepWhat is reviewed
A4Canonical URL, ID and licence/identity preservation; artefact completeness; for shape B also the ids goFSH minted and every unresolvable-parent decision
B5The narrative, especially any section added to satisfy the Manteldokument
C6Language handling and translation, including machine-translated default pages
Dbefore mergeRelease per KDS governance (TF KDS / AG IOP / NSG) — organizational, not technical. Nothing publishes before it.
关卡步骤之后审核内容
A4Canonical URL、ID 以及许可证/标识的保留;工件完整性;形态B还需审核goFSH生成的id和每个无法解析父项的决策
B5叙述内容,尤其是为满足Manteldokument而添加的任何章节
C6语言处理和翻译,包括机器翻译的默认页面
D合并前遵循KDS治理的发布流程(TF KDS / AG IOP / NSG)——组织流程,而非技术流程。发布前必须通过此关卡。

Scope and delimitation

范围与界限

Covers moving an existing guide onto the template: identity preservation, artefact transfer, directive translation, bilingual setup, and the QA that proves it. Does not cover, deliberately: authoring new profiles or remodelling content (migration never changes normative decisions); creating a module from scratch (the module template ships its own recipe); translating a guide already on the template (the catalog's
fhir-ig-translation
); publishing (no release, no registry entry, no package push); and filling in missing domain content (a gap in the source is a
TODO:REVIEW
, not a writing task). If the catalog and a local copy both provide this skill, local wins.
涵盖将现有指南迁移到模板:标识保留、工件迁移、指令翻译、双语搭建,以及证明这些的QA。明确不涵盖:编写新配置文件或重构内容(迁移绝不更改规范性决策);从零创建模块(模块模板自带说明);迁移已基于模板的指南(目录中的
fhir-ig-translation
工具);发布(无版本发布、无注册表条目、无包推送);填补缺失的领域内容(源中的缺口需标记为
TODO:REVIEW
,而非编写任务)。若目录和本地副本均提供此技能,本地副本优先。

Provenance

来源

Derived from
skills/mii-ig-migration
in
forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziell
at commit
bd38e2722a594254f3450e73c3fcdbfc2c47b7e8
. The dated revision history — every change and the measurement that forced it — is references/provenance.md; it is history, and nothing in it changes what to do on a run.
2026-08-07 — the verification phase, because a checklist is not a check. Verification was prose a human performed; four real migrations passed it on a green build while shipping a wrong dependency pin,
Unknown region code '276'
in a page header, a stale rendered provenance and a silently truncated file. Step 7b, spec §11–§12 and
scripts/verify-migration.py
+
autofix-loop.sh
+
autofix-fix.py
replace it with four mechanical layers, the run log read back as a second oracle, three verdicts (so an unmechanisable check is never written as a pass) and a bounded, allowlisted auto-fix loop. Measurements in references/provenance.md.
Original licence: CC-BY-4.0, as declared by the source repository and the source skill;
scripts/
is Apache-2.0, matching this repository's code licence. Promoted to
stable
on 2026-08-05 after two full real-task migrations (Dokument, Person), both passing the same-module verification (identity, artifact set, canonical URLs all IDENTISCH) with baseline-proven QA; the trigger set in references/triggers.md was exercised by those runs.
派生自
forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziell
仓库中
skills/mii-ig-migration
的提交
bd38e2722a594254f3450e73c3fcdbfc2c47b7e8
带日期的修订历史——每个变更及强制变更的测量结果——请参阅references/provenance.md;这是历史记录,不会影响运行时的操作。
2026-08-07——验证阶段,因为检查表不等于检查。「验证」曾是人工执行的prose;四个真实迁移通过了该检查表且构建成功,但却发布了错误的依赖版本固定、页面页眉中的
Unknown region code '276'
、过时的渲染来源和静默截断的文件。步骤7b、规范§11–§12以及
scripts/verify-migration.py
+
autofix-loop.sh
+
autofix-fix.py
将其替换为四层机械验证、运行日志作为第二个权威来源、三个 verdict(因此无法机械化的检查绝不会被静默标记为通过)以及有限的允许自动修复循环。测量结果请参阅references/provenance.md
原始许可证:CC-BY-4.0,由源仓库和源技能声明;
scripts/
目录使用Apache-2.0许可证,与本仓库的代码许可证一致。在两次完整的真实任务迁移(Dokument、Person)后,于2026-08-05升级为
stable
版本,两次迁移均通过同模块验证(标识、工件集、canonical URL均为IDENTISCH),且QA有基线证明;references/triggers.md中的触发集已被这些运行验证。