mii-ig-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMigrating 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.
-
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).
-
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 or an
sushi-config.yamlat the repository root plus anig.ini— 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.input/fsh/ - 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 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
decision; 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 — noresourceTypetree, so step 1 takes the page structure from the rendered IG andimplementation-guides/**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 Germanfql-scan.shand a 126-line CodeSystem mirror. Inventory every narrative-bearing text file, with a disposition each.README.md - 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).
- Shape A — a SUSHI / IG-Publisher project. A
-
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, readat the ref you intend to use rather than relying on this skill's description of it.
forschungsgruppe-digital-health/mii-kds-module-template- Already on the module template — a vendored , or an
ig-template/ig.inientry pointing at it. A re-migration: report what is in place before changing anything.template - Plain Simplifier project — Simplifier files only (,
.simplifier/,project.yaml), no IG-Publisher scaffolding. The normal starting state.implementation-guides/ - Hybrid, or on another template — IG-Publisher files beside the Simplifier ones: an naming another template,
ig.ini& co., a committed_genonce.shor rendered output (possibly live GitHub Pages). Still a migration. Inventory them and record which the template replaces (fsh-generated/,ig.ini/_gen*), which carry content to transfer (_update*,input/), and which retire only after Gate D. List any unrecognized top-level entry too (e.g.fsh-generated/) with a retain/retire proposal — list, do not remove.validator/
- Already on the module template — a vendored
-
Unreplaced placeholders. The template does not build until everyplaceholder 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{{(Actions.github/**matches the pattern) and counting Simplifier directives in narrative sources as accounted (step-5 material). Census by EXCLUSION (${{ … }}), not angrep -rIl '{{' . --exclude-dir=.gitlist: it misses--include, whose slug killed a publisher run (PROs try-run).ig.ini -
The toolchain — invoke SUSHI and goFSH only as a version-pinned. Neither is normally installed (
npxfinds nothing on the reference machine), so a barewhich gofsh/sushiis unrunnable and appears nowhere in this skill: writegofshandnpx --yes fsh-sushi@3.20.0— the npm package for SUSHI isnpx --yes gofsh@2.6.1, notfsh-sushi. What the "no fetching a toolchain" rule protects is an exact, recorded version, which the pin supplies and an unpinnedsushidoes not; let the pin be the record, carried in the log'snpxtoken. (cmd=grantsallowed-tools;Bash(npx:*)never matches anBash(gofsh:*)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 pinnednpx— fetched only when that condition is actually detected (spec §5.1b.5), never hand-substituted.validator_cli.jar
明确场景背景:不做任何预设,不创建缺失的内容。
-
源指南。需人工提供两个无法自动推导的输入项:已渲染的Simplifier IG的URL,以及其源代码GitHub仓库的URL。若其中任意一项缺失,需向用户索要并暂停操作。模块标识的其他所有信息均通过读取获取,而非询问(步骤2)。
-
模块仓库——分类源项目形态。涵盖两种符合范围的形态,它们会通过不同路径达成同一目标;第三种形态不在范围内。需先判定形态并记录(规范§5.1b)。
- 形态A——SUSHI/IG-Publisher项目。仓库根目录下存在或
sushi-config.yaml,且包含ig.ini目录——这是FHIR IG生态系统的约定,也是本技能可移植的基础。FSH内容已存在;步骤4将迁移该内容。若input/fsh/目录存在但为空,需上报此情况:无工件可迁移的迁移操作属于配置错误,而非无操作。input/fsh/ - 形态B——原始FHIR资源仓库。无脚手架结构——需检查至3级目录深度(规范§5.1b):若SUSHI项目嵌套在根目录下(如strukturdaten目录),则属于形态A(嵌套),需通过行重新指定根目录——使用goFSH转换会丢弃作者的FSH内容,且绿色比率会受影响。 这是在Forge中编写并在Simplifier上发布的模块的常见状态,完全在本技能的支持范围内——也是本技能针对的最典型场景。步骤2b将通过goFSH生成FSH;从步骤3开始,两种形态的处理路径完全一致。需通过内容检测形态(规范§5.1b.1):若文件可解析且包含
decision字段,则为FHIR资源;目录名通常为德语,无法通过常规名称通配符匹配。 已渲染指南的叙述内容存储在Simplifier上,而非仓库中——不存在resourceType目录结构,因此步骤1需从已渲染的IG中获取页面结构,implementation-guides/**会因目标集为空而正常退出2,之后再执行迁移。未存储在git中的内容并非无法获取:步骤2c将提取这些内容,若跳过此步骤,则会在模块名称下使用模板的初始页面。也并非“仓库中无叙述内容”:参考模块包含一个43行的德语fql-scan.sh和一个126行的CodeSystem镜像。需盘点所有包含叙述内容的文本文件,并记录每个文件的处理方式。README.md - 既非形态A也非形态B——无脚手架结构且无FHIR资源:不属于FHIR IG项目。需告知用户并停止操作。请勿搭建脚手架。形态B也不例外:它不会创建任何工件,而是从已存在的资源中生成FSH,每个工件都可追溯到源文件(防护规则3)。
- 形态A——SUSHI/IG-Publisher项目。仓库根目录下存在
-
目标模板。确定当前模块所处的状态——此处仅做发现,骨架将在流程的步骤3中生成。无论处于何种状态,都需读取仓库中你计划使用的版本,而非依赖本技能对模板的描述。
forschungsgruppe-digital-health/mii-kds-module-template- 已基于模块模板——存在 vendored 的目录,或
ig-template/中的ig.ini条目指向该模板。属于重新迁移:在修改任何内容之前,需先报告当前已有的配置。template - 纯Simplifier项目——仅包含Simplifier相关文件(、
.simplifier/、project.yaml),无IG-Publisher脚手架结构。这是常见的初始状态。implementation-guides/ - 混合形态或基于其他模板——除Simplifier文件外,还存在IG-Publisher文件:如指定了其他模板、
ig.ini等脚本、已提交的_genonce.sh目录或渲染输出(可能是GitHub Pages的实时内容)。仍属于迁移场景。需盘点这些文件,记录哪些会被模板替换(如fsh-generated/、ig.ini/_gen*脚本)、哪些需迁移内容(如_update*、input/)、哪些需在关卡D之后停用。同时列出所有无法识别的顶级条目(如fsh-generated/目录),并提出保留/停用建议——仅列出,请勿删除。validator/
- 已基于模块模板——存在 vendored 的
-
未替换的占位符。模板需替换所有占位符才能构建,未替换的占位符会生成虚假工件而非直接报错。迁移前后,需遍历目录树查找
{{...}},并记录所有匹配项——排除{{目录(Actions中的.github/**会匹配该模式),同时将叙述源中的Simplifier指令视为已处理内容(步骤5的处理对象)。通过排除法统计(${{ … }}),而非使用grep -rIl '{{' . --exclude-dir=.git列表:否则会遗漏--include,其slug曾导致发布运行失败(PROs试运行)。ig.ini -
工具链——仅通过版本固定的调用SUSHI和goFSH。通常不会安装这两个工具(参考机器上
npx无输出),因此本技能中不会出现裸调用which gofsh/sushi的情况:需编写为gofsh和npx --yes fsh-sushi@3.20.0——SUSHI的npm包名为**npx --yes gofsh@2.6.1,而非fsh-sushi。“不获取工具链”规则旨在保证精确、可记录的版本**,版本固定可实现此目标,而未固定版本的sushi则无法保证;版本信息将作为记录保存在日志的npx标记中。(cmd=允许allowed-tools;Bash(npx:*)永远不会匹配Bash(gofsh:*)命令行。)goFSH是形态B的必需工具,对于形态A,仅当源项目包含JSON/XML资源时才需要;从步骤7开始需要IG Publisher。若缺少node/npx,需告知用户并在步骤2后停止操作。若父包无快照,还需Java和固定版本的npx——仅当实际检测到该条件时才获取(规范§5.1b.5),切勿手动替换。validator_cli.jar
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 rather than trusting this sentence.
sushi-config.yamlResolve 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(e.g.SKILL_DIR=<the directory containing this SKILL.md>) and.claude/skills/mii-ig-migration, and use them in every invocation. A bareML="$SKILL_DIR/scripts/migration-log.sh"from the project root does not merely fail: if the project has its ownscripts/...with a same-named file, it silently runs that instead.scripts/
-
Inventory the source — Gate 0 FIRST (spec §9c): run the siblingon the unmigrated source (
fhir-ig-analysis→bash "$ML" run 1 preflight-analysis …); its artifact counts drive the measured M9 decisions and the §9b CS-absence path,migration-log/preflight-analysis.jsonpre-builds the special-url list, andspecial_url_predictionmeans: 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 →qa_baseline: None. Whenmigration-log/source-inventory.jsonholds 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.implementation-guides/walks org project list → package → project →bash "$SKILL_DIR/scripts/simplifier-discover.sh" --org ORG --module SLUG(no tilde; the/<project>/filterprojectguidesvariants return 200 and yield nothing) →~→ 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/published-guide/<key>/versions, 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?version=currentreading 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).data-url="/guide/<key>" -
Read the module's identity — do not ask for it, and do not invent it. From the source'sand
sushi-config.yaml(absent apackage.json:sushi-config.yamlplus thepackage.jsonresource) readImplementationGuide,title,packageId,canonical,status,releaseLabel,licenseanddependencies, and carry them over unchanged. On disagreementpublisherwins — it is what the build reads; record it. A field in neither file comes from the generatedsushi-config.yaml; absent everywhere it takes the template default, at Gate A. Resolve floating pins (ImplementationGuide) 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):1.5.x- P — the published package. logs
bash "$SKILL_DIR/scripts/package-identity.sh" --package ID --version V,packageId,version,description,fhirVersionsand the dependency pins — source evidence, outranking anyjurisdiction— plus thedist-tags.latestderived from the packaged resources' own urls by common prefix, unanimous or a WARN, never a majority vote. No manifest carriescanonical,titleorlicense;publisheris a registry account, not a publisher.author - R — the source repository. 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
bash "$SKILL_DIR/scripts/repo-identity.sh" --dir DIR --repo OWNER/NAME --rendered URLcandidate, 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 (title); the GitHub owner is not alicense-text-unrecognized:.publisher - H — Simplifier. TWO URL SPACES, TWO ANSWERS — do not carry one to the other (spec §2.1.3). The project page () 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/<Project>/) 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 carryingsimplifier.net/guide/<key>/<Root>[/<Page>]?version=<v>and the real German narrative. They yield the narrative, not identity: harvest them per step 2c.<h1 id="page-title">
Whatever no tier yields stays Gate A — measured on the reference module,alone. Every value is claimed with its evidence (publisher), and a second source with a different value raisesbash "$ML" claim 2.1 ACTION FIELD VALUE TIER SOURCE— reported, never resolved (measured: goFSH'sidentity-contradiction:against the package'sversion: 1.0.8; a source pin2026.0.0against2.0.2dist-tags.latest).2.0.3is 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.bash "$ML" claims --markdownLog each value read, and each divergence as a WARN. The target version is the only identity value that is a human decision: MII CalVer, not SemVer, defaulting to the source's. When the source and the template disagree, the source wins — the template'sYYYY.n.nandcanonicalpatterns 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,packageIdabove all: the template shipslicense, no placeholder check flags it, and MII modules commonly declareCC-BY-4.0. Relicensing is a human decision, never a default. Spec §2.2.CC0-1.0 - P — the published package.
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-consentbash
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 , and the
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.
reads goFSH's own RESULTS table back, labels every cell, counts converted resources only
(never Invariants/Mappings/Aliases) and reconciles them against . That reconciliation is
the point of the whole block — goFSH's exit code is not the signal, its counts are — and
keeps each real exit status, truncating each raw log per invocation so a re-run measures itself
and not the sum of both.
$GLOGEgofsh-results.sh$N_INrun- 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).
- is mandatory; its absence fails SILENTLY. goFSH defaults to
-t json-and-xml: 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 sixjson-onlys); say which you mean. The deciding number is neither: it isSearchParameter, whichconverted 1 of 20emits and WARNs on. Reconcile against step 1's inventory, never the exit code.gofsh-results.sh - Declare every foreign parent IG with , found in the resources' own
-d <package>@<version>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.baseDefinition - goFSH writes the itself but it is a STARTING POINT, NOT IDENTITY: no
sushi-config.yaml/id/name/title/publisher/packageId, an untrustedlicense(measuredversion— one profile's — against the module's published1.0.8), and2026.0.0that are only whateverdependenciesset you passed. Recover identity per step 2 instead.-d - The script's two passes are mechanical: 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
fhir_commentsreturns and arunwould have hidden).tee - 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 exits 5 — the anticipated shape-B outcome, which
sushi-afterlogs as an escalation rather than as a failure.--expected-nonzero - A parent package that ships no snapshots blocks import — SUSHI cannot read such a parent at
all, blocking those profiles and every instance declaring them. Detect it, then generate the snapshots with a real generator; never hand-roll one (spec §5.1b.5):
InstanceOfcounts them (measured: 21 SDs, 0 snapshots, in both candidate versions — another version does not fix it), andbash "$SKILL_DIR/scripts/parent-snapshots.sh" detect --package ID --version Vdrives the official HL7 generator (… build … --validator validator_cli.jar --install --require <parent-url>…, ProfileUtilities), verifies every result (a snapshot whose element count matches only the differential is WRONG and is refused), and installs a new cache entryjava -jar validator_cli.jar snapshot— 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.<id>#<version>-snapshots - Acceptance: counts match the inventory; the script exits 0; every remaining SUSHI error is a
named unresolvable-parent escalation; all of it is in . 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.
run.log
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.yields the project including the narrative markdown as the author wrote it, behind a Simplifier login: measured, anonymous access returns the login page (
https://simplifier.net/<project-slug>/$actions/downloading) 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/login?ReturnUrl=…. Never invent a credential mechanism, ask for a password or store a token — the gate is the point. Nobody available: lognarrative-source=project-downloadand fall through to ②.project-download-unavailable: -
② 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/htmlIt discovers the page tree from the root's ownvalues — slugs are read, never constructed ("Anwendungsfälle / Informationsmodell" →href), pins every page toAnwendungsflleInformationsmodell, isolates each content region?version=by depth-scanning<div id="preview-content">/<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 +</div>, and classifies each pageTODO:REVIEWornarrative.artefact-viewhas no default — this is step 5's input, not the template's page set. Verification is the point: every discovered page is in--outharvested-with-counts or skipped-with-a-reason;migration-log/guide-harvest.tsvraises the mandatoryratioWARN when harvested < discovered; per page the source text runs that did not survive the conversion are counted (silent-partial-success:) and WARNed — under a separatemissing_runs=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.generated-view-lossy: -
③ 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 advice run reads the manifest (, auto-discovered from the target's ) 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.
5.4c page-routing--harvest-tsvmigration-log/-
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 everyplaceholder from the identity read in step 2. The template's CRMI
{{...}}claims require themeta.profiledependency — 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 (hl7.fhir.uv.crmi'sig.inipath,ig=references, the step-6ImplementationGuide-<id>): SUSHI writes.pofrom theImplementationGuide-<id>.jsonand 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 (id,input/fsh/profiles/example-patient.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/instances/example-patient-instance.fsh,input/fsh/aliases.fsh), diff itsinput/fsh/rulesets/*/RuleSet:names against the module's FSH — module definitions win, the module's FSH is never changed: a shared alias file (Alias:) 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'saliases.fshbroke a real migration with 234 SUSHI errors. Acceptance:aliases.fshruns clean (shape B: as qualified in step 2b), and the skip/append lists are in the log.bash "$ML" run 5.2 sushi-skeleton -- npx --yes fsh-sushi@3.20.0 . -
Transfer the artefacts — structure-preserving. Move the FSH sources across keeping the source's directory layout; convert JSON/XML with a pinnedwhere 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:
npx --yes gofsh@2.6.1over 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.comm -3 -
Migrate the narrative. Its source is step 2c; this step maps it. Move the Manteldokument content intoand translate Simplifier and FQL directives into IG Publisher equivalents:
input/pagecontent/*.mdbashbash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strictThe scan is recursive and pre-migration includes; 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".implementation-guides/**exits 1 on any finding;--strictkeeps both statuses where arunreports 0. Apply each printed recommendation; mapping in the FQL crosswalk, rules intee. In doubt, writereferences/fql-rules.tsv.TODO:REVIEWRoute every source page BEFORE writing — spec §9e, first match wins: one artefact →(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), elseinput/intro-notes/<Type>-<id>-intro.md-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 — thepages:run IS5.4c page-routingpage-structure-advice.py(invocation under Verification): it WRITES--map(v2:migration-log/page-map.tsv) from these branches plus the semantic routing tablesource_page⇥target⇥reason⇥branch⇥measure, VALIDATES coverage of the full source page universe (authoritative guide tree ∪references/routing-table.tsv∪ 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; runinput/pagecontent(writes5.4d derived-scan, 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.migration-log/derived-content.tsvTemplate ≥ v0.8 targets: spec §9a is normative for the page set — split pages, link-only Conformance cluster +(content re-routes per §9a), no re-added title headings (M10), two REQUIRED run-logged decisions:datasets-and-descriptions(M9 — measured: package artifact count 0 → remove, > 0 → keep, artifacts never deleted; §9a),5.4a optional-page-decisions(M11). Index style parity (authors/contacts as lists, disclaimer as prose), the5.4b security-privacy-decisiontranslation-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.(de) -
Set up the bilingual pages. English is the default; German is the translation, a same-named file under. These do render. The menu is
input/translations/de/pagecontent/with a per-language mirror atinput/includes/menu.xml— never ainput/translations/de/includes/menu.xmlproperty inmenu:, which competes with it. Resource translations aresushi-config.yamlsupplements under.po; 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,input/translations/de/) — full recipe in spec §5.5. The publisher does localize them, through one IG-level catalogue<title>(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:input/translations/<lang>/ImplementationGuide-<ig-id>.pobashbash "$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>.pois a required argument with no default (onemigration-log/menu-titles-de.txtper line; build it, and theEnglish Title => Deutscher Titel"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-means untranslated and goes to the ② queue. Footgun: the language must appear inmsgstr, not onlytranslation-sources, or everyi18n-langis silently ignored. Modules from template v0.5.0 also drop its breadcrumb override..po -
Build and QA. SUSHI, then the IG Publisher — both through, so the two numbers this step exists to produce end up in the log: SUSHI's error count, and
bash "$ML" run 5.6 …'s summary line copied into an INFO (spec §5.6 has the block). The target pins its toolchain in the build workflow'sqa.txtblock — read the pins from there rather than from this file. Acceptance:env:reportsqa.txtand 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 withErrors: 0(measure the unmigrated source, then the migrated tree — the POSTFLIGHT measurement, kept asfhir-ig-analysis; an equalmigration-log/postflight-analysis.jsontriggers 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 withpackageId(invocation under Verification; spec §5.6): one verdict per compared property (scripts/prepost-delta.py), 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' censusunchanged · improved · REGRESSION · expected-changefields differ (moderaw-resource vsreducedFSH-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 —staticfinds it or WARNsbash "$SKILL_DIR/scripts/sibling-skill-check.sh" --skill-dir "$SKILL_DIR"with the exact pinnedsibling-skill-unavailable: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).npx skills add
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 — 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 ∪ ∪ the step-2c
harvest — never a fallback chain: one pagecontent stub must not suppress a 149-page guide tree) migrated/retired/MISSING per ,
the reviewed contract the advice run generated; every source text run present somewhere. Fidelity: identity, dependency pins identical to the source's,
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 , 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
vendors package ) — plus P5: 's names the IG resource SUSHI actually writes (, derived from the
, never from the repo slug). Rendering integrity: empty tables/tabs, missing images, header markers like , 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 WARN, a step
that emitted no line at all ( is the manifest), an open , and log-versus-artefact count
mismatches. Three verdicts, not two: , (named, with evidence) and , so an unmechanisable check is never
silently written as a pass. Exit 0 clean · 1 divergence · 3 incomplete, which is not a pass.
artifacts.htmlinput/pagecontentmigration-log/page-map.tsv5.4c page-routinglicenseqa.htmlv0.6.00.5.1ig.iniig=ImplementationGuide-<id>.jsonidUnknown region codesilent-partial-success:references/expected-steps.tsvidentity-contradiction:IDENTISCHDIVERGIERTNICHT PRÜFBAR-
Report. Writefrom the report template — three reviewer queues (① decide, ② review, ③ triage): every open decision,
migration-log/migration-report.mdand 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):TODO:REVIEWrenders every open obligation from the ledgers as per-gateqa-checklist.pycheckboxes into Sign-off, and- [ ]renders the map as clickable rendered-source ↔ rendered-target rows plus the template-page provenance into Content map. The protocol section is generated FROMcomparison-table.py(spec §10.6): every claim traces to a line, every WARN/ERROR lands in a queue, the log wins.migration-log/run.log -
Open a pull request with the report as its description. Do not publish. Name the working branch(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-
migration/<source-version>-template-<module-template-tag>branch tomainundergh-pagesand reservesbranches/<branch>/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.main
本文档使用英文编写;操作的工件为德语KDS文档,德语专业术语将保留原样。输出语言遵循目标模板:默认语言为英语,德语为翻译版本。这与旧约定相反——请参阅下文的「语言」部分,并以目标模板的为准,而非本文档的描述。
sushi-config.yaml首先解析脚本路径。以下命令中工具的路径是相对于本技能自身的目录,而非你的工作目录(即正在迁移的项目目录)。设置(例如SKILL_DIR=<包含本SKILL.md的目录>)和.claude/skills/mii-ig-migration,并在所有调用中使用它们。若直接从项目根目录调用ML="$SKILL_DIR/scripts/migration-log.sh",不仅会失败:若项目自身有scripts/...目录且包含同名文件,会静默运行项目自身的脚本。scripts/
-
盘点源项目——首先执行关卡0(规范§9c):对未迁移的源项目运行同级的工具(
fhir-ig-analysis→bash "$ML" run 1 preflight-analysis …);其工件计数将驱动M9决策和§9b中CS缺失路径的处理,migration-log/preflight-analysis.json将预构建特殊URL列表,special_url_prediction意味着:需立即获取源项目的QA证明。然后,从已渲染的IG和源仓库中提取所有工件(配置文件、扩展、值集、代码系统、能力声明、示例)和叙述结构,并记录每个工件的源路径 →qa_baseline: None。若migration-log/source-inventory.json目录包含多个指南目录结构(版本×语言 + 共享资源——实际模块包含6个),需遵循规范§5.1a:选择权威目录结构,将并行语言目录标记为提取源,保留其余目录。 无已渲染IG的URL?请查找——规范§5.1c。implementation-guides/将遍历组织项目列表 → 包 → 项目 →bash "$SKILL_DIR/scripts/simplifier-discover.sh" --org ORG --module SLUG(无波浪线;带/<project>/filterprojectguides的变体返回200但无内容) →~→ 服务器渲染的指南根目录,若任何环节无输出则发出警告。固定一个已发布的只读版本,并像记录源提交SHA一样记录它——切勿使用/published-guide/<key>/versions,这是可编辑的实时项目。密钥和页面slug需通过发现获取,切勿构造(构造的密钥会返回404;渲染器会去除slug中的标点)。PROJECT页面是客户端渲染的,DOM中无身份元数据;GUIDE页面是服务器渲染的,包含所有内容——将单个实例的测量结果推广到整个平台曾导致本技能流程出错。单个实例不能代表全部,正反方向均如此(规范§5.1c.2a):需针对你所声称的精确工件进行负面测量,在将一种「形态」编码为标准形态之前,需在多个实例上进行测量,选择最可能不同的实例——同一天对?version=current的测量在一个模块中所有密钥均为裸值,而在另外两个模块中密钥被静默丢弃(3个同意模块中的3个,2个微生物模块中的2个,0个人物模块,均正常退出)。data-url="/guide/<key>" -
读取模块标识——请勿询问,也请勿编造。从源项目的和
sushi-config.yaml中读取package.json、title、packageId、canonical、status、releaseLabel、license和dependencies,并原样保留。若存在冲突,以publisher为准——这是构建过程读取的文件;需记录冲突情况。若上述两个文件中均无某个字段,则从生成的sushi-config.yaml资源中获取;若所有来源均无该字段,则使用模板默认值,待关卡A确认。解析浮动版本号(如ImplementationGuide)需遵循规范§2.1,记录选择结果及依据。 形态B通常无上述三个文件——但无标识信息的仓库并非无标识模块。需从多个来源按以下顺序恢复标识信息,每个字段需记录其来源(规范§2.1;脚本会自动记录):1.5.x- P——已发布的包。将记录
bash "$SKILL_DIR/scripts/package-identity.sh" --package ID --version V、packageId、version、description、fhirVersions和依赖版本固定信息——这是源证据,优先级高于任何jurisdiction——以及通过打包资源自身URL的公共前缀推导的dist-tags.latestURL——需所有资源一致,否则发出警告,切勿采用多数投票。清单中无canonical、title或license字段;publisher是注册表账户,而非发布者。author - R——源仓库。将读取LICENSE文本的SPDX标识符——这是真实的许可证证据,此字段绝不能使用默认值(§2.2)——README的第一个标题作为
bash "$SKILL_DIR/scripts/repo-identity.sh" --dir DIR --repo OWNER/NAME --rendered URL候选、仓库描述、发布标签,发布标签与P的版本匹配可将该发布与提交关联。无法识别的许可证文本无输出(title);GitHub所有者并非license-text-unrecognized:。publisher - H——Simplifier。两个URL空间,两种结果——请勿混淆(规范§2.1.3)。项目页面()是客户端渲染的(HTTP 200,约56 KB,52个脚本标记,DOM中无身份元数据):仅作为关卡A的人工参考,用于机器源无法获取的信息,而非抓取目标;脚本会测量此情况并无提取内容。指南页面(
simplifier.net/<Project>/)属于不同的URL空间,且是服务器渲染的——在Consent 2026.0.0版本上测量:根页面24509字节,包含整个页面树(18个页面链接);叶子页面20481字节,包含simplifier.net/guide/<key>/<Root>[/<Page>]?version=<v>和真实的德语叙述内容。这些页面提供叙述内容,而非标识信息:需按步骤2c提取。<h1 id="page-title">
若所有层级均无某个字段的值,需留待关卡A确认——在参考模块上测量,仅字段如此。每个值需记录其来源(publisher),若第二个来源的值不同,需标记bash "$ML" claim 2.1 ACTION FIELD VALUE TIER SOURCE——仅上报,切勿自行解决(测量示例:goFSH的identity-contradiction:与包的version: 1.0.8冲突;源版本固定为2026.0.0与2.0.2的dist-tags.latest冲突)。2.0.3将生成报告的标识表。切勿使用恢复的值修改现有元数据,即使恢复结果显示现有元数据不一致:恢复结果仅作为关卡A的证据。规范§2.1.2–§2.1.4。bash "$ML" claims --markdown记录每个读取的值,以及每个差异作为警告。目标版本是唯一需要人工决策的标识值:需使用MII CalVer格式,而非SemVer,默认使用源项目的版本。若源项目与模板存在冲突,以源项目为准——模板的YYYY.n.n和canonical模式适用于新模块,修改已发布的canonical URL会破坏所有消费者。需上报每个差异并由人工决策;切勿静默标准化。这涵盖了模板预填充为字面量而非占位符的所有值,尤其是packageId:模板默认使用license,占位符检查不会标记它,但MII模块通常声明CC-BY-4.0。重新许可属于人工决策,切勿使用默认值。规范§2.2。CC0-1.0 - P——已发布的包。
2b. 仅适用于源形态B——从原始资源生成FSH。需在生成骨架之前执行,步骤3会将骨架合并到已存在的FSH中。在模块仓库外的临时目录中操作;形态A可跳过此步骤。规范§5.1b为标准要求(使用goFSH 2.6.1和SUSHI 3.20.0在上测量)。
medizininformatik-initiative/kerndatensatzmodul-consentbash
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"需按顺序逐字运行,且在同一目录中:后处理器会读取,助手会从每个SUSHI的原始日志中读取错误计数并写入INFO行——这对就是从41→5的证据,且包含在代码块中,而非仅在规范中。会读取goFSH自身的RESULTS表,标记每个单元格,仅统计已转换的资源(不包括约束/映射/别名),并与核对。核对是整个代码块的核心——goFSH的退出码并非信号,其计数才是——会保留每个真实的退出状态,每次调用时截断原始日志,因此重新运行时仅测量本次运行,而非两次运行的总和。
$GLOGEgofsh-results.sh$N_INrun- 请将goFSH指向仓库根目录,而非单个资源文件夹。参考模块的20个资源存储在5个手动命名的目录中,无需 staging:测量显示,goFSH会递归遍历目录树,从根目录生成的FSH与从staging的扁平目录生成的FSH字节完全相同。仅当有记录的原因时才进行staging(规范§5.1b.2)。
- 是必需的;缺失会静默失败。goFSH默认使用
-t json-and-xml:在Consent模块(19个XML + 1个JSON)上,无此标志的运行退出码为0,报告"0 Errors",但仅转换了一个资源,仅警告"13 XML definition(s)"缺少JSON对应项——这是goFSH自身的计数,而非输入的19个文件(差异为6个json-only);需明确说明你指的是哪个计数。决定性数字既不是输入数也不是goFSH的计数:而是SearchParameter,converted 1 of 20会输出此结果并发出警告。需与步骤1的盘点结果核对,切勿依赖退出码。gofsh-results.sh - 需使用声明所有外部父IG,可从资源自身的
-d <package>@<version>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停止操作。baseDefinition - goFSH会自行生成,但这只是起点,并非标识信息:无
sushi-config.yaml/id/name/title/publisher/packageId字段,license不可信(测量示例:version——来自一个配置文件——与模块已发布的1.0.8冲突),2026.0.0仅包含你通过dependencies设置的内容。需按步骤2恢复标识信息。-d - 脚本的两次处理是机械性的:修复规则和名称包含空格的未引用代码参考系统,使用goFSH自身报告的名称进行修复。它会先分类再写入,对无法建模的形态不写入任何内容,且具有幂等性。需提供完整的FSH目录——缩小路径会导致它无法看到所需的声明,因此会拒绝执行(退出码1,
fhir_comments会返回此状态,而run会隐藏此状态)。tee - 之后SUSHI必须编译通过,除了真正无法解析的父项——测量结果为处理前41个错误,处理后5个,均由上述代码块记录。解析错误会导致SUSHI停止读取文件的剩余部分,但仍会导出实例,且静默截断("导出"不等于"转换"):三个Consent示例处理前每个包含1个嵌套规则,处理后分别为27/6/3个。SUSHI的退出状态即为错误计数,因此会退出5——这是形态B的预期结果,
sushi-after会将其记录为升级而非失败。--expected-nonzero - 若父包无快照,会阻止导入——SUSHI完全无法读取此类父包,从而阻止这些配置文件以及所有声明它们的实例。需检测此情况,然后使用真实的生成器生成快照;切勿手动构建(规范§5.1b.5):
InstanceOf会统计快照数量(测量示例:21个SD,0个快照,两个候选版本均如此——其他版本也无法解决),bash "$SKILL_DIR/scripts/parent-snapshots.sh" detect --package ID --version V会驱动官方HL7生成器(… build … --validator validator_cli.jar --install --require <parent-url>…,ProfileUtilities),验证每个结果(快照的元素计数仅与差异部分匹配是错误的,会被拒绝),并安装一个新的缓存条目java -jar validator_cli.jar snapshot——绝不会覆盖上游内容。生成器拒绝属于上游缺陷,需升级处理,切勿手动完成;重建对CI的成本(仅本地)是关卡A的决策内容。近似合并——切片、基数、元素顺序——会构造父项(防护规则1和3)。然后重新固定版本,重新运行SUSHI并记录两个错误计数:在Consent模块上测量,从5→0。 goFSH生成的id和GUID命名的文件进入②队列:生成的id将成为模块的一部分,需在关卡A确认。<id>#<version>-snapshots - 验收标准:计数与盘点结果匹配;脚本退出码为0;所有剩余的SUSHI错误均为已命名的无法解析父项升级;所有内容均记录在中。形态B本身不会产生干净的构建,因此以下所有"干净构建"标准(步骤3和7,「验证」部分)针对形态B时需遵循形态B限定条件,规范§5.1b.4:无机械错误,所有剩余错误均在①队列中并由关卡A决策,无构造的父项。允许的错误计数不等于通过。
run.log
2c. 当叙述内容不在仓库中时提取——形态B始终如此,形态A若页面存储在平台上也需提取。规范§5.1d为标准要求(§5.1c已找到指南)。源的优先级从高到低,"无"并非第三个选项:
-
① 已认证的项目下载——只要有凭证,优先选择。可获取项目包括作者编写的叙述markdown,但需要Simplifier登录:测量显示,匿名访问会返回登录页面(
https://simplifier.net/<project-slug>/$actions/downloading),所有尝试的匿名替代方案均返回404或HTML,因此不存在经过验证的匿名项目下载方式。需人工在自己的浏览器中登录,下载归档文件并指定仓库外的路径;代理会读取该文件并记录/login?ReturnUrl=…。切勿发明凭证机制、索要密码或存储令牌——关卡的意义正在于此。若无人可用,需记录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它会从根页面自身的值中发现页面树——slug需读取获取,切勿构造("Anwendungsfälle / Informationsmodell" →href),将每个页面固定到AnwendungsflleInformationsmodell,通过深度扫描?version=/<div>隔离每个内容区域</div>(正则表达式匹配到下一个<div id="preview-content">会在第一个嵌套处截断;无该区域的页面会被跳过,绝不完整转换),写入带有来源头 +</div>的Markdown,并将每个页面分类为TODO:REVIEW或narrative。artefact-view无默认值——这是步骤5的输入,而非模板的页面集。 验证是核心:每个发现的页面均在--out中标记为已提取(带计数)或已跳过(带原因);当提取数量 < 发现数量时,migration-log/guide-harvest.tsv会触发强制ratio警告;每个页面会统计未完成转换的源文本行数(silent-partial-success:)并发出警告——在工件视图上使用单独的missing_runs=标记,因此生成的表格不会掩盖真实的损失。在Consent 2026.0.0版本上测量:发现18个页面,提取18个,跳过0个,叙述页面无缺失,14个叙述页面 + 4个工件视图,3个资源。退出码0表示提取干净;1表示有页面被跳过或内容缺失。generated-view-lossy: -
③ "无"并非第三个选项。若源不可访问,需上报并升级到关卡B,同时命名该源。
注册表包的角色未变:提供资源和标识信息(步骤2),无叙述内容——它是提取集验证的依据。提取的页面与其他源形态遵循相同的约定:建议运行会读取清单(,从目标的中自动发现)并生成映射行——提取跳过的页面会阻止覆盖验证,直到提取干净或人工在已审核的映射中标记为停用——步骤5仅使用映射(规范§9/§9f):绝非一个提取页面对应一个目标页面,需根据「语言」部分放置语言版本。
5.4c page-routing--harvest-tsvmigration-log/-
创建骨架(规范§5.2)。迁移在原地进行:在模块现有仓库的工作分支上,引入前置条件3中检出的模板并运行其首次引导程序——切勿创建新仓库;历史记录、问题和消费者需保留在原位置(创建新仓库是人工决策,需记录在报告中,绝非默认操作)。使用步骤2读取的标识信息替换所有占位符。模板的CRMI
{{...}}声明需要meta.profile依赖——需将其添加到迁移的源依赖中,在关卡A记录(属于模板机制,而非源标识)。嵌入模块名称的脚手架文件名需从模块ID派生,而非仓库slug(hl7.fhir.uv.crmi的ig.ini路径、ig=引用、步骤6的ImplementationGuide-<id>文件):SUSHI会从.po生成id,而非其他内容,因此从slug派生的名称会构建成功,但发布者会找不到IG资源——P5检查会捕获id与slug不匹配的情况。然后删除模板的示例工件(ImplementationGuide-<id>.json、input/fsh/profiles/example-patient.fsh——需根据你实际检出的模板验证路径),避免与模块的真实示例冲突。在复制模板的FSH脚手架(input/fsh/instances/example-patient-instance.fsh、input/fsh/aliases.fsh)之前,需将其input/fsh/rulesets/*/RuleSet:名称与模块的FSH进行对比——模块定义优先,绝不修改模块的FSH:共享别名文件(Alias:)需按定义解析(仅追加模块缺少的模板定义),其他冲突的脚手架文件按文件处理(完全跳过)。需记录两个列表——覆盖模块的aliases.fsh曾导致真实迁移出现234个SUSHI错误。 验收标准:aliases.fsh运行成功(形态B:需遵循步骤2b的限定条件),且跳过/追加列表已记录在日志中。bash "$ML" run 5.2 sushi-skeleton -- npx --yes fsh-sushi@3.20.0 . -
迁移工件——保留结构。迁移FSH源时保留源的目录结构;若源仅包含JSON/XML,需使用固定版本的转换——形态B已在步骤2b完成此操作,因此此处迁移的是其后处理输出。ID和URL保持不变。验收标准基于路径,而非计数:对源和目标的仓库相对FSH路径列表进行排序后,
npx --yes gofsh@2.6.1的结果除日志中命名的脚手架添加项外为空——仅计数无法证明结构一致。comm -3 -
迁移叙述内容。源为步骤2c;此步骤进行映射。将Manteldokument内容迁移到,并将Simplifier和FQL指令转换为IG Publisher等效指令:
input/pagecontent/*.mdbashbash "$ML" run 5.4 fql-scan --emits-runlog -- bash "$SKILL_DIR/scripts/fql-scan.sh" --strict扫描是递归的,迁移前包括目录;它会记录每个目标扫描的文件,若指定目录无贡献则发出警告,若目标集为空则退出2——切勿将"无扫描内容"解读为"无发现"。implementation-guides/**会在任何发现时退出1;--strict会保留两个状态,而run会报告0。需应用每个打印的建议;映射请参阅FQL对照表,规则请参阅tee。若有疑问,请写入references/fql-rules.tsv。TODO:REVIEW在写入前需路由所有源页面——规范§9e,匹配优先:单个工件 →(德语镜像使用相同文件名,渲染在工件页面顶部);家族概述 → 现有索引页面的一个章节;已有页面包含该内容 → 合并;仅交叉叙述内容成为页面——中心页面(≤250字,每个子项一行)适用于≥3个子项,仅在预算内添加菜单条目(≤33个条目,每个下拉菜单≤10个,顶级≤8个,深度≤2),否则嵌套在input/intro-notes/<Type>-<id>-intro.md中并链接。大小限制:>2500字、>4个合并源或任何重复标题需重新路由(PROs曾发布一个6214字的宿主页面,13个冲突锚点)。路由是生成的,而非判断的——pages:运行即5.4c page-routing的page-structure-advice.py(调用方式请参阅「验证」部分):它会从这些分支以及语义路由表--map生成references/routing-table.tsv(v2:migration-log/page-map.tsv),验证源页面全集的覆盖情况(权威指南树 ∪source_page⇥target⇥reason⇥branch⇥measure∪ 磁盘上无目录列表的页面 ∪ 步骤2c的提取清单;退出码1直到每个页面都有目标,每个RETIRED行都有原因——悬空的目录条目和提取跳过的页面均为发现结果),并从关卡0提出M9/其他分类决策。映射是契约(规范§9f):人工需在写入任何内容之前审核/编辑映射;此步骤仅使用映射——映射中未包含的页面不写入,映射行未处理属于缺陷;步骤7b会对照同一映射检查。 你编写的文本(概述、中心页面单行内容、过渡内容)是派生的——需按§9d标记,使其渲染为审核框;运行input/pagecontent(写入5.4d derived-scan,C7会读取,②会生成);移动或拆分的内容无需标记。§9记录了参考模块的用例缺口:需上报,切勿填补。migration-log/derived-content.tsv模板≥v0.8版本目标:规范§9a为页面集的标准要求——拆分页面、仅链接的Conformance集群 +(内容按§9a重新路由),不重新添加标题(M10),两个需记录运行日志的决策:datasets-and-descriptions(M9 — 测量结果:包工件计数为0 → 删除,>0 → 保留,绝不删除工件;§9a)、5.4a optional-page-decisions(M11)。索引样式一致(作者/联系人列为列表,免责声明为 prose)、5.4b security-privacy-decision翻译标记措辞和单次提交可恢复修复协议均遵循§9a;若检测到缺失CapabilityStatement,需根据模块的配置文件建议并在其页面上内联渲染(规范§9b)。(de) -
搭建双语页面。默认语言为英语;德语为翻译版本,存储在下的同名文件中。这些文件会正常渲染。菜单为
input/translations/de/pagecontent/,每个语言的镜像为input/includes/menu.xml——切勿使用input/translations/de/includes/menu.xml中的sushi-config.yaml属性,否则会冲突。资源翻译为menu:下的input/translations/de/补充文件;在投入资源之前,需检查目标的构建说明,确认哪些资源类型会实际渲染。若源仅为德语,需反转方向——请参阅下文的「语言」部分。 页面标题(面包屑、目录、.po)——完整说明请参阅规范§5.5。发布者会本地化标题,通过一个IG级别的目录<title>(加载时导入到IG资源中——并非资源补充,因此不适用其类型限制)。需在步骤3的SUSHI运行后,从SUSHI生成的ImplementationGuide资源(权威标题集)生成该文件——菜单仅作为翻译种子:input/translations/<lang>/ImplementationGuide-<ig-id>.pobashbash "$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;需按规范§5.5构建,或使用English Title => Deutscher Titel"无种子"选项)——路径无法解析属于设置错误,绝非静默使用空种子。重新生成不会破坏现有内容;空的-表示未翻译,进入②队列。陷阱:语言必须出现在**msgstr中,而非仅translation-sources,否则所有i18n-lang文件会被静默忽略。模板v0.5.0**及以上版本的模块还需移除其面包屑覆盖。.po -
构建与QA。先运行SUSHI,再运行IG Publisher——均通过执行,因此此步骤生成的两个关键数字会记录在日志中:SUSHI的错误计数,以及
bash "$ML" run 5.6 …的摘要行复制到INFO中(规范§5.6包含代码块)。目标在构建工作流的qa.txt块中固定了工具链——需从那里读取版本固定信息,而非本文档。验收标准:env:报告qa.txt,且所有示例验证通过——形态B:需遵循步骤2b的限定条件,已命名的升级除外,其他任何错误均需停止操作。然后使用Errors: 0运行同模块验证(测量未迁移的源,然后测量迁移后的目录——POSTFLIGHT测量,保存为fhir-ig-analysis;相同的migration-log/postflight-analysis.json会触发比较,SOURCE为第一个输入):标识、已发布工件集和canonical URL必须全部显示为IDENTISCH,若显示为DIVERGIERT则需停止操作;每种语言的叙述内容表会进入报告的QA分类。然后对比两次测量结果,使用packageId(调用方式请参阅「验证」部分;规范§5.6):每个对比属性一个 verdict(scripts/prepost-delta.py),退出码1表示某个属性变差(工件计数减少、许可证矛盾、依赖注入风险出现、标识字段变更)——需停止操作修复,绝非记录差异;若两次测量的普查unchanged · improved · REGRESSION · expected-change字段不同(mode原始资源 vsreducedFSH声明普查——提取的形态),计数差异会报告为expected-change并命名模式,绝非回归(规范§5.6)。该同级技能是已检查的前置条件,而非假设——static会查找该技能,若找不到则发出bash "$SKILL_DIR/scripts/sibling-skill-check.sh" --skill-dir "$SKILL_DIR"警告,并提供精确的固定版本sibling-skill-unavailable:命令,且绝不安装:工具授权是许可,而非依赖,无关运行不得写入操作员的技能目录(规范§5.6a)。npx skills add
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>四层验证加上日志。守恒性:每个源工件都存在且可从访问——存在与可访问是不同的属性,步骤7的集合比较仅证明前者;源页面全集(权威指南树 ∪ ∪ 步骤2c的提取内容——绝非 fallback 链:一个pagecontent stub不得掩盖149页的指南树)的每个页面均已迁移/停用/MISSING,符合(建议运行生成的已审核契约);每个源文本行均存在于某处。保真度:标识、依赖版本固定与源完全相同、从某个层级获取且未使用默认值——并与LICENSE 文件自身的文本核对(不匹配则为DIVERGIERT,无法识别则为NICHT PRÜFBAR)。来源:从渲染的中读取的模板包+版本、发布者版本、固定的指南版本——需同类对比,因为ig-template包版本与module-template仓库发布版本是不同的数字(测量示例:仓库引入了包)——加上P5:的指向SUSHI实际生成的IG资源(,从派生,绝非仓库slug)。渲染完整性:空表格/标签页、缺失的图片、页眉标记如,以及语言一致性——翻译页面与默认页面字节完全相同属于 fallback,而非翻译。运行日志是第二个权威来源(§11.6):它记录了每个步骤的意图和测量结果,而目录记录的是结果,因此只有它能捕获未处理的警告、完全未输出任何行的步骤(是清单)、未解决的,以及日志与工件计数不匹配的情况。三个 verdict,而非两个:、(已命名,带证据)和****,因此无法机械验证的检查绝不会被静默标记为通过。退出码0表示干净 · 1表示差异 · 3表示不完整,并非通过。
artifacts.htmlinput/pagecontentmigration-log/page-map.tsv5.4c page-routinglicenseqa.htmlv0.6.00.5.1ig.iniig=ImplementationGuide-<id>.jsonidUnknown region codesilent-partial-success:references/expected-steps.tsvidentity-contradiction:IDENTISCHDIVERGIERTNICHT PRÜFBAR-
报告。从报告模板编写——三个审核队列(① 决策,② 审核,③ 分类):每个未解决的决策、
migration-log/migration-report.md和QA发现均需进入恰好一个队列,QA来源需要证明(构建未迁移的源以声明"预先存在")。读者从未见过本技能、规范或仓库:模板列出的每个标记行都是必需的(包括若无人操作,谁决策、工作量、可逆性),代码在使用时需附带注释,id需将队列与签署关联。需粘贴两个生成的辅助内容,切勿重新输入(模板的「如何重新运行」部分包含调用方式):TODO:REVIEW会从分类账中渲染每个未完成的义务,作为每个关卡的qa-checklist.py复选框进入签署部分;- [ ]会将映射渲染为可点击的渲染源 ↔ 渲染目标行,加上模板页面来源,进入内容映射部分。协议部分从comparison-table.py生成(规范§10.6):每个声明都可追溯到一行,每个WARN/ERROR均进入队列,日志为准。migration-log/run.log -
创建拉取请求,将报告作为描述。请勿发布。工作分支命名为(规范步骤9——五次试运行各发明了不同的命名方案)。需根据模块仓库自身的约定确定PR的目标分支——需发现,切勿假设:默认分支、已合并拉取请求的基础分支、CONTRIBUTING/README。模板会将所有非
migration/<source-version>-template-<module-template-tag>分支预览到main的gh-pages下,branches/<branch>/分支和标签保留用于发布,因此工作分支可预览而不影响默认分支。若仓库有其他约定,请遵循并说明——若该PR基础分支本身是发布分支,需在PR和关卡D中说明:合并后即发布。main
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. : 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, , including from the many steps that run no bundled script: (one numbered , first in every block) · · (an INFO naming both counts plus the mandatory WARN when ACTUAL < EXPECTED) · (the command actually executed, its output at truncated per invocation, and its real exit status, returned rather than swallowed).
migration-log/run.logML="$SKILL_DIR/scripts/migration-log.sh"begin LABELrun-boundaryinfo|warn|error STEP ACTION DETAIL [CONT …]ratio [--exit N] STEP ACTION VERB NOUN EXPECTED ACTUALrun STEP ACTION [--emits-runlog] [--raw-log F] [--expected-nonzero WHY] -- CMD …migration-log/<ACTION>.logNever . A pipeline's status is 's, and this skill's acceptance criteria are exit statuses: measured, that pipeline reported 0 where exited 41 and exited 1, so failed steps read as passed. takes the status from . An exit status is eight bits — 256 SUSHI errors report as — so cross-checks it against the raw log's error count (). marks a step whose non-zero exit is the documented outcome (shape-B ; the step-7b verification), logging a WARN naming the escalation rather than an ERROR calling the expected result a failure.
… 2>&1 | tee -a migration-log/run.logteefsh-sushipostprocess-gofsh.pyrunPIPESTATUS[0]exit=0runexit-status-truncated:--expected-nonzero WHYsushi-afterFormat: , two spaces between fields, padded to five, carrying the command actually executed as plus measured outcomes; continuations indented four spaces — e.g. . Every step emits at least one INFO line, and step 7b's L2 check reports the ones that did not () — a step that did not run is invisible in the tree. WARN is mandatory for silent partial success: name both numbers, via , 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 .
<UTC ISO-8601> <LEVEL> <STEP> <ACTION> <DETAIL>LEVELDETAILcmd=`…` key=value… WARN 5.1b.2 gofsh-convert silent-partial-success: converted 1 of 20 inputs at exit 0references/expected-steps.tsvratiogrep -E ' (WARN |ERROR) '用途:让人工读者无需重新运行任何操作,也无需依赖记忆,即可重建哪些步骤已运行以及每个步骤产生的结果——实际执行的命令、返回的计数、退出状态。:纯文本,仅追加,随分支提交。报告的协议部分从它生成(步骤8),步骤7b会将其作为第二个权威来源读取(§11.6),因此它不能声称未执行的操作。规范§10为标准要求——其§10.5表格为完整API。所有行均需通过捆绑的助手输出,,包括许多未运行捆绑脚本的步骤:(每个代码块开头的编号) · · (一个INFO,命名两个计数加上ACTUAL < EXPECTED时的强制警告) · (实际执行的命令,输出存储在中每次调用时截断,以及真实的退出状态,会返回而非吞掉)。
migration-log/run.logML="$SKILL_DIR/scripts/migration-log.sh"begin LABELrun-boundaryinfo|warn|error STEP ACTION DETAIL [CONT …]ratio [--exit N] STEP ACTION VERB NOUN EXPECTED ACTUALrun STEP ACTION [--emits-runlog] [--raw-log F] [--expected-nonzero WHY] -- CMD …migration-log/<ACTION>.log切勿使用。管道的状态是的状态,而本技能的验收标准就是退出状态:测量显示,该管道在退出41、退出1时报告0,因此失败的步骤会被读取为通过。从获取状态。退出状态为8位——256个SUSHI错误会报告为——因此会将其与原始日志的错误计数交叉核对()。标记退出码非零为文档化结果的步骤(形态B的;步骤7b的验证),记录为警告(命名升级)而非错误(将预期结果视为失败)。
… 2>&1 | tee -a migration-log/run.logteefsh-sushipostprocess-gofsh.pyrunPIPESTATUS[0]exit=0runexit-status-truncated:--expected-nonzero WHYsushi-after格式:,字段间用两个空格分隔,填充为5个字符,包含实际执行的命令,格式为,加上测量的结果;续行缩进四个空格——例如。每个步骤至少输出一个INFO行,步骤7b的L2检查会报告未输出的步骤()——未运行的步骤在目录中不可见。WARN是静默部分成功的强制要求:需通过命名两个数字,切勿手动输入——在那次运行中,其他所有信号均为绿色(后处理"无内容修复",SUSHI 0错误),但20个资源中有19个缺失。可使用读取。
<UTC ISO-8601> <LEVEL> <STEP> <ACTION> <DETAIL>LEVELDETAILcmd=`…` key=value… WARN 5.1b.2 gofsh-convert silent-partial-success: converted 1 of 20 inputs at exit 0references/expected-steps.tsvratiogrep -E ' (WARN |ERROR) 'Guardrails
防护规则
Binding — a migration that violates one is wrong even if it builds.
- Canonical URLs and IDs of existing conformance resources are never changed.
- FHIR R4 (4.0.1).
- No fabrication. Every artefact and narrative section traces to a source URL or repo path;
uncertainty is marked , never guessed. (
TODO:REVIEWmarks the migrated guide; the catalog's marker for unfinished skill content isTODO:REVIEW— do not mix them.)TODO(owner): - Human in the loop. The review gates below are mandatory. The agent does not publish.
- Template examples are deleted before migrating, never merged with the module's own.
- The default branch is not modified. Work on a branch, deliver a pull request.
- Traceability. Every step emits run-log lines as it runs, through (Run log, above), and every assumption and open point reaches
scripts/migration-log.sh, whose protocol section is generated from that log.migration-log/migration-report.md - No Liquid literals in , including inside HTML comments. Jekyll evaluates
pagecontentand{% … %}everywhere: an invalid{{ … }}breaks the build hard, an unknown{% … %}silently empties and leaks into the HTML. Describe such mechanisms in prose.{{ … }} - 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.
具有约束力——违反任何一条的迁移即使构建成功也是错误的。
- 现有一致性资源的Canonical URL和ID绝不能修改。
- 使用FHIR R4(4.0.1)版本。
- 不得构造内容。每个工件和叙述章节均可追溯到源URL或仓库路径;不确定性需标记为,切勿猜测。(
TODO:REVIEW标记迁移后的指南;目录中未完成技能内容的标记为TODO:REVIEW——请勿混淆。)TODO(owner): - 人工参与。以下审核关卡是必需的。代理不会执行发布操作。
- 迁移前需删除模板示例,绝不与模块自身的示例合并。
- 不得修改默认分支。在分支上工作,交付拉取请求。
- 可追溯性。每个步骤运行时需通过输出运行日志行(上文的「运行日志」),每个假设和未解决的问题均需记录到
scripts/migration-log.sh,其协议部分从该日志生成。migration-log/migration-report.md - 中不得包含Liquid字面量,包括HTML注释内的。Jekyll会在所有地方解析
pagecontent和{% … %}:无效的{{ … }}会严重破坏构建,未知的{% … %}会静默清空并泄露到HTML中。需用 prose 描述此类机制。{{ … }} - 负面能力发现仅对测量的工件有效。记录每个"无法读取/不可用/不可能"的情况需包含精确的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
(). Verify it in the target's
i18n-default-lang: enon every run — it moved once already. FHIR artefact identifiers stay English regardless.sushi-config.yaml - A mismatch warning is conditional — it fires only when the source FSH sets
de-DE, is cosmetic, and is suppressed in^language = #de-DE(glob withinput/ignoreWarnings.txtwildcards, not regex; match%), leaving the FSH untouched. Spec §4.1.%(de-DE)% - 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
and produce
input/translations/de/pagecontent/as machine translations of it, every page markedinput/pagecontent/*.md, reviewed at Gate C — the one sanctioned exception to guardrail 3, since each traces to the page it renders. A top-levelTODO:REVIEWin 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.language:
三个易混淆的事实。
- 目标模板的默认语言为英语,德语为翻译版本()。每次运行需在目标的
i18n-default-lang: en中验证——它已变更过一次。FHIR工件标识符始终为英语,无论其他内容如何。sushi-config.yaml - 不匹配警告是有条件的——仅当源FSH设置
de-DE时触发,属于 cosmetic 问题,可在^language = #de-DE中抑制(使用input/ignoreWarnings.txt通配符的glob,而非正则;匹配%),FSH保持不变。规范§4.1。%(de-DE)% - 仅德语的源需反转方向——本技能会处理此情况。这是KDS的常见情况:源的叙述内容为德语,而目标的默认语言为英语,因此德语文本成为尚未存在的英语页面的翻译。需将其迁移到,并将
input/translations/de/pagecontent/生成为其机器翻译,每个页面标记input/pagecontent/*.md,在关卡C审核——这是防护规则3的唯一批准例外,因为每个页面均可追溯到其渲染的源页面。源中的顶级TODO:REVIEW是旧的单语言设置,而非标识信息。步骤7b的R3检查会验证结果:翻译页面与默认页面字节完全相同属于 fallback,而非翻译。language:
Verification
验证
Mechanical, and it exits non-zero — step 7b, spec §11. The list of sentences that used to stand here is now : four layers plus the run log as a second oracle, three verdicts, one row per checked subject in . 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 (–): the template package the rendered site reports, the one the tree carries, and the latest release are three different numbers (§11.4 — repo tag vendors package ; comparing the first against the third manufactures a confident, wrong finding), plus the publisher version against the workflow pin, the pinned guide version, and pointing at the IG resource SUSHI actually writes (, the id-vs-slug class). Page set and menu (, ): every page of the union source set (authoritative guide tree ∪ ∪ the step-2c harvest, chosen by the same rule as the advice script and overridable via — 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 ; a translated menu wherever there are translated pages. Artefact completeness and reachability (, ): present is not the same property as listed, in both directions — artefacts SUSHI generated, and artefacts rendered from that the forward pass cannot see. 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 (, ): 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 at . Content placement (): not whether a text run survived — that is — but which page it landed on, against the map. Language parity (, ): 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: (v2, , of which the verifier reads the first three columns — generated by the advice run and human-reviewed, never hand-written) — without it cannot run at all and degrades to a landing distribution a human reads — and the step-5.1c harvest. Absent, they yield NICHT PRÜFBAR, never a pass. also reconciles the LICENSE file's recognized text against the declared licence (mismatch DIVERGIERT, unrecognized NICHT PRÜFBAR, absent an ok note).
scripts/verify-migration.pymigration-log/verification-findings.tsvP1P5v0.6.00.5.1ig.iniP5C3C5input/pagecontent--source-guide-treereferences/template-pages.tsvC1C2input/resourcesC1R1R2Unknown region codeErrors: 0C6C4R3R5migration-log/page-map.tsvsource_page⇥target⇥reason⇥branch⇥measure5.4c page-routingC3C6F3bash
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 outputThese keep their own acceptance: every accounted for (an unreplaced one ships a bogus artefact silently); SUSHI clean and — both shape B as qualified in step 2b, while the IDENTISCH criteria are not qualified by shape; exits 0 with a non-zero scanned-file count (an empty target set exits 2 and is not a pass) and no 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); 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 .
{{...}}qa.txtErrors: 0fql-scan.sh --strict[UNKNOWN]prepost-delta.pyreferences/codes.mdAuto-fix is optional and bounded (spec §12): 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.
bash "$SKILL_DIR/scripts/autofix-loop.sh" --skill-dir "$SKILL_DIR"机械性验证,非零退出码——步骤7b,规范§11。曾在此处的句子列表现已变为:四层验证加上运行日志作为第二个权威来源,三个 verdict,中每个检查主题一行。人工仍需处理每个NICHT PRÜFBAR行——命名、说明操作和关卡;退出码3表示阶段不完整,并非通过。它会将迁移后的指南与Simplifier渲染的源对比,而非自身对比,涉及六个方面——qa错误计数无法捕获任何一个。工具链来源(–):渲染站点报告的模板包、目录中的模板包、最新发布版本是三个不同的数字(§11.4——仓库标签引入了包;将第一个与第三个对比会产生自信但错误的发现),加上发布者版本与工作流固定版本对比、固定的指南版本、指向SUSHI实际生成的IG资源(,id与slug对比)。页面集和菜单(、):源页面全集(权威指南树 ∪ ∪ 步骤2c的提取内容,选择规则与建议脚本相同,可通过覆盖——绝非全有或全无的fallback)的每个页面均已迁移/停用(带原因)/MISSING;每个菜单条目均指向有效页面;每个叙述页面均在菜单中;每个目标页面均可追溯到源页面或;有翻译页面的地方需有翻译菜单。工件完整性和可访问性(、):存在与列出是不同的属性,正反方向均如此——SUSHI生成的工件,以及从渲染的工件,正向流程无法看到。通过id或canonical url命名工件,因为SearchParameter可能只有后者,且迁移会重新分配id;变体是渲染页面的目录,因此多语言构建的重定向 stub ROOT不属于此类(§11.5a——将其视为工件会在四个迁移中报告错误的BLOCKER)。渲染完整性(、):表格、标签页和图片在源中有内容的地方非空,页眉/页脚区域中,发布者无法解析的辖区会在时渲染为。内容放置():不是文本行是否存活——这是——而是它落在哪个页面上,与映射对比。语言一致性(、):翻译页面与默认页面字节完全相同属于 fallback,而非翻译。两个输入决定了可机械化的程度,早期步骤会写入,而非验证器:(v2,,验证器仅读取前三列——由建议运行生成并人工审核,绝非手动编写)——若无此文件,完全无法运行,退化为人工读取的着陆分布;以及步骤5.1c的提取内容。若缺失,会产生NICHT PRÜFBAR,绝非通过。还会将LICENSE 文件的可识别文本与声明的许可证核对(不匹配则为DIVERGIERT,无法识别则为NICHT PRÜFBAR,缺失则为ok备注)。
scripts/verify-migration.pymigration-log/verification-findings.tsvP1P5v0.6.00.5.1ig.iniP5C3C5input/pagecontent--source-guide-treereferences/template-pages.tsvC1C2input/resourcesC1R1R2Errors: 0Unknown region codeC6C4R3R5migration-log/page-map.tsvsource_page⇥target⇥reason⇥branch⇥measure5.4c page-routingC3C6F3bash
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运行成功且显示——形态B均需遵循步骤2b的限定条件,而IDENTISCH标准不按形态限定;退出码为0 且扫描文件计数非零(目标集为空则退出2,并非通过)且无发现;建议运行重新生成页面映射且覆盖完整——退出码1表示存在未路由页面,并非通过(重新运行会覆盖已审核的映射,因此需重新应用并重新审核人工编辑);报告无REGRESSION行(退出码1表示需停止操作修复)。其他所有内容——上述六个方面、标识/许可证/版本固定、父快照、运行日志自身的完整性——均为规范§11中的编号检查,需测量而非回忆。本文档中打印的所有代码(M1–M11、C1–C7、F/P/R/L、关卡、标记类型)均会由生成器内联注释,并在中列出。
{{...}}qa.txtErrors: 0fql-scan.sh --strict[UNKNOWN]prepost-delta.pyreferences/codes.md自动修复是可选且有限的(规范§12):仅修复四个允许的机械类问题,最多3次迭代,每次修复均快照,若发现未清除则恢复,一旦发现集停止缩小则停止。标识、叙述内容、SOURCE声明的任何内容以及所有判断性决策均被排除——它们进入①队列。
bash "$SKILL_DIR/scripts/autofix-loop.sh" --skill-dir "$SKILL_DIR"Mandatory human review gates
必需的人工审核关卡
| Gate | After step | What is reviewed |
|---|---|---|
| A | 4 | Canonical URL, ID and licence/identity preservation; artefact completeness; for shape B also the ids goFSH minted and every unresolvable-parent decision |
| B | 5 | The narrative, especially any section added to satisfy the Manteldokument |
| C | 6 | Language handling and translation, including machine-translated default pages |
| D | before merge | Release per KDS governance (TF KDS / AG IOP / NSG) — organizational, not technical. Nothing publishes before it. |
| 关卡 | 步骤之后 | 审核内容 |
|---|---|---|
| A | 4 | Canonical URL、ID 以及许可证/标识的保留;工件完整性;形态B还需审核goFSH生成的id和每个无法解析父项的决策 |
| B | 5 | 叙述内容,尤其是为满足Manteldokument而添加的任何章节 |
| C | 6 | 语言处理和翻译,包括机器翻译的默认页面 |
| 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 ); publishing (no release, no registry
entry, no package push); and filling in missing domain content (a gap in the source is a
, not a writing task). If the catalog and a local copy both provide this skill, local wins.
fhir-ig-translationTODO:REVIEW涵盖将现有指南迁移到模板:标识保留、工件迁移、指令翻译、双语搭建,以及证明这些的QA。明确不涵盖:编写新配置文件或重构内容(迁移绝不更改规范性决策);从零创建模块(模块模板自带说明);迁移已基于模板的指南(目录中的工具);发布(无版本发布、无注册表条目、无包推送);填补缺失的领域内容(源中的缺口需标记为,而非编写任务)。若目录和本地副本均提供此技能,本地副本优先。
fhir-ig-translationTODO:REVIEWProvenance
来源
Derived from in
at commit
. 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.
skills/mii-ig-migrationforschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziellbd38e2722a594254f3450e73c3fcdbfc2c47b7e82026-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, in a page header, a stale rendered provenance and a silently truncated file. Step 7b, spec §11–§12 and + + 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.
Unknown region code '276'scripts/verify-migration.pyautofix-loop.shautofix-fix.pyOriginal licence: CC-BY-4.0, as declared by the source repository and the source skill; is
Apache-2.0, matching this repository's code licence. Promoted to 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.
scripts/stable派生自仓库中的提交。带日期的修订历史——每个变更及强制变更的测量结果——请参阅references/provenance.md;这是历史记录,不会影响运行时的操作。
forschungsgruppe-digital-health/mii-kds-sample-ig-inoffiziellskills/mii-ig-migrationbd38e2722a594254f3450e73c3fcdbfc2c47b7e82026-08-07——验证阶段,因为检查表不等于检查。「验证」曾是人工执行的prose;四个真实迁移通过了该检查表且构建成功,但却发布了错误的依赖版本固定、页面页眉中的、过时的渲染来源和静默截断的文件。步骤7b、规范§11–§12以及 + + 将其替换为四层机械验证、运行日志作为第二个权威来源、三个 verdict(因此无法机械化的检查绝不会被静默标记为通过)以及有限的允许自动修复循环。测量结果请参阅references/provenance.md。
Unknown region code '276'scripts/verify-migration.pyautofix-loop.shautofix-fix.py原始许可证:CC-BY-4.0,由源仓库和源技能声明;目录使用Apache-2.0许可证,与本仓库的代码许可证一致。在两次完整的真实任务迁移(Dokument、Person)后,于2026-08-05升级为版本,两次迁移均通过同模块验证(标识、工件集、canonical URL均为IDENTISCH),且QA有基线证明;references/triggers.md中的触发集已被这些运行验证。
scripts/stable