@renoxar/koolie 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (693) hide show
  1. package/.gitattributes +16 -0
  2. package/.gitignore +51 -0
  3. package/.koolie/QUELLREPOSITORIUM.md +18 -0
  4. package/.koolie/core/CHANGELOG.md +11832 -0
  5. package/.koolie/core/LICENSE +674 -0
  6. package/.koolie/core/LICENSE-HINWEIS.md +98 -0
  7. package/.koolie/core/OWNERS.md +36 -0
  8. package/.koolie/core/VERSION +1 -0
  9. package/.koolie/core/banner.py +397 -0
  10. package/.koolie/core/build/README.md +34 -0
  11. package/.koolie/core/build/assemble.py +354 -0
  12. package/.koolie/core/build/build-docx.py +194 -0
  13. package/.koolie/core/build/doc/00-kopf.md +18 -0
  14. package/.koolie/core/build/doc/01-executive-summary.md +24 -0
  15. package/.koolie/core/build/doc/02-zielbild.md +13 -0
  16. package/.koolie/core/build/doc/03-ziele-nichtziele.md +28 -0
  17. package/.koolie/core/build/doc/04-geltungsbereich.md +25 -0
  18. package/.koolie/core/build/doc/05-glossar.md +47 -0
  19. package/.koolie/core/build/doc/06-leitprinzipien.md +5 -0
  20. package/.koolie/core/build/doc/07-architektur.md +60 -0
  21. package/.koolie/core/build/doc/07a-abbildungsschicht.md +66 -0
  22. package/.koolie/core/build/doc/08-trennung.md +24 -0
  23. package/.koolie/core/build/doc/09-betriebsmodi.md +18 -0
  24. package/.koolie/core/build/doc/10-arbeitsablauf.md +5 -0
  25. package/.koolie/core/build/doc/11-datenschutz.md +5 -0
  26. package/.koolie/core/build/doc/12-sicherheit.md +5 -0
  27. package/.koolie/core/build/doc/13-risiko.md +5 -0
  28. package/.koolie/core/build/doc/14-rollen.md +9 -0
  29. package/.koolie/core/build/doc/15-referenzstruktur.md +124 -0
  30. package/.koolie/core/build/doc/16-agentenanweisung.md +8 -0
  31. package/.koolie/core/build/doc/17-overlay.md +29 -0
  32. package/.koolie/core/build/doc/18-skill-standard.md +5 -0
  33. package/.koolie/core/build/doc/19-skill-template.md +5 -0
  34. package/.koolie/core/build/doc/20-referenz-skills.md +70 -0
  35. package/.koolie/core/build/doc/21-prompts.md +53 -0
  36. package/.koolie/core/build/doc/22-checklisten.md +49 -0
  37. package/.koolie/core/build/doc/23-entscheidungsbaeume.md +29 -0
  38. package/.koolie/core/build/doc/24-onboarding.md +33 -0
  39. package/.koolie/core/build/doc/25-governance.md +35 -0
  40. package/.koolie/core/build/doc/26-qs-test.md +9 -0
  41. package/.koolie/core/build/doc/27-pilot.md +13 -0
  42. package/.koolie/core/build/doc/28-uebernahme.md +5 -0
  43. package/.koolie/core/build/doc/29-grenzen.md +87 -0
  44. package/.koolie/core/build/doc/30-roadmap.md +5 -0
  45. package/.koolie/core/build/doc/31-anhaenge.md +151 -0
  46. package/.koolie/core/build/doc/32-abschluss.md +92 -0
  47. package/.koolie/core/build/ref-a4.docx +0 -0
  48. package/.koolie/core/checklists/01-preflight.md +54 -0
  49. package/.koolie/core/checklists/02-privacy-context.md +51 -0
  50. package/.koolie/core/checklists/03-before-code-change.md +51 -0
  51. package/.koolie/core/checklists/04-review-ai-code.md +58 -0
  52. package/.koolie/core/checklists/05-testing.md +48 -0
  53. package/.koolie/core/checklists/06-security.md +50 -0
  54. package/.koolie/core/checklists/07-new-dependency.md +57 -0
  55. package/.koolie/core/checklists/08-merge-request.md +47 -0
  56. package/.koolie/core/checklists/09-onboarding.md +52 -0
  57. package/.koolie/core/checklists/10-project-adoption.md +74 -0
  58. package/.koolie/core/checklists/11-framework-release.md +71 -0
  59. package/.koolie/core/checklists/README.md +28 -0
  60. package/.koolie/core/clientmap.py +1358 -0
  61. package/.koolie/core/clients/README.md +113 -0
  62. package/.koolie/core/clients/_template/CLIENT_PACK.md +197 -0
  63. package/.koolie/core/clients/claude-code/CLIENT_PACK.md +383 -0
  64. package/.koolie/core/clients/claude-code/manifest.json +232 -0
  65. package/.koolie/core/clients/claude-code/root-template/.claude/README.md +127 -0
  66. package/.koolie/core/clients/cursor/CLIENT_PACK.md +229 -0
  67. package/.koolie/core/clients/cursor/manifest.json +330 -0
  68. package/.koolie/core/clients/cursor/root-template/.cursor/README.md +36 -0
  69. package/.koolie/core/clients/devin-desktop/CLIENT_PACK.md +251 -0
  70. package/.koolie/core/clients/devin-desktop/manifest.json +163 -0
  71. package/.koolie/core/clients/devin-desktop/root-template/.devin/README.md +79 -0
  72. package/.koolie/core/clients/kiro/CLIENT_PACK.md +223 -0
  73. package/.koolie/core/clients/kiro/manifest.json +300 -0
  74. package/.koolie/core/clients/kiro/root-template/.kiro/README.md +37 -0
  75. package/.koolie/core/clients/openai-codex/CLIENT_PACK.md +257 -0
  76. package/.koolie/core/clients/openai-codex/manifest.json +208 -0
  77. package/.koolie/core/clients/openai-codex/root-template/.codex/README.md +35 -0
  78. package/.koolie/core/decision-trees/01-context-allowed.md +46 -0
  79. package/.koolie/core/decision-trees/02-may-ai-do-task.md +47 -0
  80. package/.koolie/core/decision-trees/03-analyze-or-modify.md +50 -0
  81. package/.koolie/core/decision-trees/04-required-review.md +49 -0
  82. package/.koolie/core/decision-trees/05-stop-or-escalate.md +47 -0
  83. package/.koolie/core/decision-trees/06-rule-placement.md +58 -0
  84. package/.koolie/core/decision-trees/README.md +14 -0
  85. package/.koolie/core/docs/ADOPTION_GUIDE.md +582 -0
  86. package/.koolie/core/docs/DOCUMENTATION_STANDARD.md +91 -0
  87. package/.koolie/core/docs/PLACEHOLDER_REGISTRY.md +79 -0
  88. package/.koolie/core/docs/ROADMAP.md +848 -0
  89. package/.koolie/core/docs/RUNTIME_GLOSSARY.md +113 -0
  90. package/.koolie/core/examples/README.md +11 -0
  91. package/.koolie/core/examples/example-ergebnisbericht.md +31 -0
  92. package/.koolie/core/examples/example-mr-description.md +31 -0
  93. package/.koolie/core/examples/example-overlay-runtime.md +48 -0
  94. package/.koolie/core/framework/core/00-principles.md +93 -0
  95. package/.koolie/core/framework/core/01-governance.md +58 -0
  96. package/.koolie/core/framework/core/02-privacy.md +141 -0
  97. package/.koolie/core/framework/core/03-security.md +86 -0
  98. package/.koolie/core/framework/core/04-quality.md +48 -0
  99. package/.koolie/core/framework/core/05-working-model.md +199 -0
  100. package/.koolie/core/framework/core/06-prompting-rules.md +51 -0
  101. package/.koolie/core/framework/core/07-review-rules.md +52 -0
  102. package/.koolie/core/framework/core/08-skill-conventions.md +115 -0
  103. package/.koolie/core/framework/core/09-risk-model.md +87 -0
  104. package/.koolie/core/framework/core/10-error-escalation.md +53 -0
  105. package/.koolie/core/framework/org-policies/MAPPING_CLASSIFICATION.md +16 -0
  106. package/.koolie/core/framework/org-policies/README.md +29 -0
  107. package/.koolie/core/framework/overlay-patterns/general/documents/branching-strategy/muster-general.md +43 -0
  108. package/.koolie/core/framework/overlay-patterns/general/documents/coding-guidelines/muster-general.md +82 -0
  109. package/.koolie/core/framework/overlay-patterns/general/documents/definition-of-done/muster-general.md +52 -0
  110. package/.koolie/core/framework/overlay-patterns/general/documents/definition-of-ready/muster-general.md +54 -0
  111. package/.koolie/core/framework/overlay-patterns/general/documents/quality/muster-general.md +59 -0
  112. package/.koolie/core/framework/overlay-patterns/general/documents/security/muster-general.md +52 -0
  113. package/.koolie/core/framework/overlay-patterns/general.md +150 -0
  114. package/.koolie/core/framework/role-packs/README.md +63 -0
  115. package/.koolie/core/framework/role-packs/_template/ROLE_PACK.md +58 -0
  116. package/.koolie/core/framework/role-packs/requirements-engineering/ROLE_PACK.md +158 -0
  117. package/.koolie/core/framework/role-packs/requirements-engineering/runtime/30-role-requirements-engineering.md +88 -0
  118. package/.koolie/core/framework/role-packs/requirements-engineering/skills/role-re-ticket/CHANGELOG.md +10 -0
  119. package/.koolie/core/framework/role-packs/requirements-engineering/skills/role-re-ticket/EXAMPLES.md +148 -0
  120. package/.koolie/core/framework/role-packs/requirements-engineering/skills/role-re-ticket/SKILL.md +195 -0
  121. package/.koolie/core/framework/role-packs/requirements-engineering/skills/role-re-ticket/TESTS.md +22 -0
  122. package/.koolie/core/framework/role-packs/software-development/ROLE_PACK.md +95 -0
  123. package/.koolie/core/framework/role-packs/software-development/runtime/30-role-software-development.md +26 -0
  124. package/.koolie/core/framework/runtime/agents/fw-reviewer.md +34 -0
  125. package/.koolie/core/framework/runtime/client-settings.json +3 -0
  126. package/.koolie/core/framework/runtime/hooks.json +20 -0
  127. package/.koolie/core/framework/runtime/mcp-config.example.json +4 -0
  128. package/.koolie/core/framework/runtime/permissions.json +106 -0
  129. package/.koolie/core/framework/runtime/root-instruction-local.example.md +19 -0
  130. package/.koolie/core/framework/runtime/root-instruction.md +116 -0
  131. package/.koolie/core/framework/runtime/rules/00-framework-core.md +49 -0
  132. package/.koolie/core/framework/runtime/rules/10-privacy-security.md +49 -0
  133. package/.koolie/core/framework/runtime/rules/15-development-rules.md +41 -0
  134. package/.koolie/core/framework/runtime/rules/16-plan-spezifikation.md +32 -0
  135. package/.koolie/core/framework/runtime/rules/20-project-overlay.md +60 -0
  136. package/.koolie/core/framework/skills/fw-bugfix-prepare/CHANGELOG.md +14 -0
  137. package/.koolie/core/framework/skills/fw-bugfix-prepare/EXAMPLES.md +57 -0
  138. package/.koolie/core/framework/skills/fw-bugfix-prepare/SKILL.md +177 -0
  139. package/.koolie/core/framework/skills/fw-bugfix-prepare/TESTS.md +15 -0
  140. package/.koolie/core/framework/skills/fw-change-analyze/CHANGELOG.md +12 -0
  141. package/.koolie/core/framework/skills/fw-change-analyze/EXAMPLES.md +66 -0
  142. package/.koolie/core/framework/skills/fw-change-analyze/SKILL.md +167 -0
  143. package/.koolie/core/framework/skills/fw-change-analyze/TESTS.md +16 -0
  144. package/.koolie/core/framework/skills/fw-change-small/CHANGELOG.md +13 -0
  145. package/.koolie/core/framework/skills/fw-change-small/EXAMPLES.md +68 -0
  146. package/.koolie/core/framework/skills/fw-change-small/SKILL.md +175 -0
  147. package/.koolie/core/framework/skills/fw-change-small/TESTS.md +13 -0
  148. package/.koolie/core/framework/skills/fw-code-explain/CHANGELOG.md +11 -0
  149. package/.koolie/core/framework/skills/fw-code-explain/EXAMPLES.md +61 -0
  150. package/.koolie/core/framework/skills/fw-code-explain/SKILL.md +163 -0
  151. package/.koolie/core/framework/skills/fw-code-explain/TESTS.md +11 -0
  152. package/.koolie/core/framework/skills/fw-docs-update/CHANGELOG.md +10 -0
  153. package/.koolie/core/framework/skills/fw-docs-update/EXAMPLES.md +59 -0
  154. package/.koolie/core/framework/skills/fw-docs-update/SKILL.md +160 -0
  155. package/.koolie/core/framework/skills/fw-docs-update/TESTS.md +12 -0
  156. package/.koolie/core/framework/skills/fw-error-analyze/CHANGELOG.md +10 -0
  157. package/.koolie/core/framework/skills/fw-error-analyze/EXAMPLES.md +67 -0
  158. package/.koolie/core/framework/skills/fw-error-analyze/SKILL.md +160 -0
  159. package/.koolie/core/framework/skills/fw-error-analyze/TESTS.md +12 -0
  160. package/.koolie/core/framework/skills/fw-mr-description/CHANGELOG.md +12 -0
  161. package/.koolie/core/framework/skills/fw-mr-description/EXAMPLES.md +69 -0
  162. package/.koolie/core/framework/skills/fw-mr-description/SKILL.md +170 -0
  163. package/.koolie/core/framework/skills/fw-mr-description/TESTS.md +12 -0
  164. package/.koolie/core/framework/skills/fw-overlay-pflege/CHANGELOG.md +7 -0
  165. package/.koolie/core/framework/skills/fw-overlay-pflege/EXAMPLES.md +48 -0
  166. package/.koolie/core/framework/skills/fw-overlay-pflege/SKILL.md +147 -0
  167. package/.koolie/core/framework/skills/fw-overlay-pflege/TESTS.md +13 -0
  168. package/.koolie/core/framework/skills/fw-plan/CHANGELOG.md +14 -0
  169. package/.koolie/core/framework/skills/fw-plan/EXAMPLES.md +69 -0
  170. package/.koolie/core/framework/skills/fw-plan/SKILL.md +168 -0
  171. package/.koolie/core/framework/skills/fw-plan/TESTS.md +14 -0
  172. package/.koolie/core/framework/skills/fw-refactor/CHANGELOG.md +10 -0
  173. package/.koolie/core/framework/skills/fw-refactor/EXAMPLES.md +70 -0
  174. package/.koolie/core/framework/skills/fw-refactor/SKILL.md +173 -0
  175. package/.koolie/core/framework/skills/fw-refactor/TESTS.md +14 -0
  176. package/.koolie/core/framework/skills/fw-repo-analyze/CHANGELOG.md +10 -0
  177. package/.koolie/core/framework/skills/fw-repo-analyze/EXAMPLES.md +52 -0
  178. package/.koolie/core/framework/skills/fw-repo-analyze/SKILL.md +149 -0
  179. package/.koolie/core/framework/skills/fw-repo-analyze/TESTS.md +11 -0
  180. package/.koolie/core/framework/skills/fw-review-support/CHANGELOG.md +12 -0
  181. package/.koolie/core/framework/skills/fw-review-support/EXAMPLES.md +69 -0
  182. package/.koolie/core/framework/skills/fw-review-support/SKILL.md +179 -0
  183. package/.koolie/core/framework/skills/fw-review-support/TESTS.md +13 -0
  184. package/.koolie/core/framework/skills/fw-tests/CHANGELOG.md +9 -0
  185. package/.koolie/core/framework/skills/fw-tests/EXAMPLES.md +65 -0
  186. package/.koolie/core/framework/skills/fw-tests/SKILL.md +169 -0
  187. package/.koolie/core/framework/skills/fw-tests/TESTS.md +12 -0
  188. package/.koolie/core/framework/tech-packs/README.md +15 -0
  189. package/.koolie/core/framework/tech-packs/_template/TECH_PACK.md +61 -0
  190. package/.koolie/core/governance/ADOPTION_REGISTRY.md +89 -0
  191. package/.koolie/core/governance/CHANGE_REQUEST_TEMPLATE.md +60 -0
  192. package/.koolie/core/governance/DECISION_LOG.md +779 -0
  193. package/.koolie/core/governance/EXCEPTION_PROCESS.md +31 -0
  194. package/.koolie/core/governance/FEEDBACK_PROCESS.md +34 -0
  195. package/.koolie/core/governance/FRAMEWORK_DEV_PROFILE.md +135 -0
  196. package/.koolie/core/governance/INCIDENT_HANDLING.md +47 -0
  197. package/.koolie/core/governance/PRIORITY_HIERARCHY.md +61 -0
  198. package/.koolie/core/governance/RACI.md +37 -0
  199. package/.koolie/core/governance/RELEASE_PROCESS.md +183 -0
  200. package/.koolie/core/governance/change-requests/CR-2026-001-definition-release-1-0-0.md +67 -0
  201. package/.koolie/core/governance/change-requests/CR-2026-002-client-packs.md +63 -0
  202. package/.koolie/core/governance/change-requests/CR-2026-003-install-client-schalter.md +70 -0
  203. package/.koolie/core/governance/change-requests/CR-2026-004-client-pack-claude-code.md +82 -0
  204. package/.koolie/core/governance/change-requests/CR-2026-005-kern-neutralisierung.md +75 -0
  205. package/.koolie/core/governance/change-requests/CR-2026-006-gemeinsame-skill-quelle.md +77 -0
  206. package/.koolie/core/governance/change-requests/CR-2026-007-geteilte-kernbestandteile.md +80 -0
  207. package/.koolie/core/governance/change-requests/CR-2026-008-berechtigungen-und-hooks.md +85 -0
  208. package/.koolie/core/governance/change-requests/CR-2026-009-umbenennung-leitwerk.md +77 -0
  209. package/.koolie/core/governance/change-requests/CR-2026-010-restduplikation.md +73 -0
  210. package/.koolie/core/governance/change-requests/CR-2026-011-hauptdokument.md +72 -0
  211. package/.koolie/core/governance/change-requests/CR-2026-012-schreibschutz-kernverzeichnis.md +106 -0
  212. package/.koolie/core/governance/change-requests/CR-2026-013-validator-blindstellen.md +128 -0
  213. package/.koolie/core/governance/change-requests/CR-2026-014-kurzform-langform.md +125 -0
  214. package/.koolie/core/governance/change-requests/CR-2026-015-versionskette.md +150 -0
  215. package/.koolie/core/governance/change-requests/CR-2026-016-ap2-claude-code.md +127 -0
  216. package/.koolie/core/governance/change-requests/CR-2026-017-ladebedingungen-claude-code.md +173 -0
  217. package/.koolie/core/governance/change-requests/CR-2026-018-quellenliste-claude-code.md +121 -0
  218. package/.koolie/core/governance/change-requests/CR-2026-019-strukturentscheidungen-fortschreiben.md +143 -0
  219. package/.koolie/core/governance/change-requests/CR-2026-020-akteursbezeichnung.md +118 -0
  220. package/.koolie/core/governance/change-requests/CR-2026-021-hook-interpreter.md +113 -0
  221. package/.koolie/core/governance/change-requests/CR-2026-022-artefaktversionen.md +73 -0
  222. package/.koolie/core/governance/change-requests/CR-2026-023-shell-lesesperre.md +128 -0
  223. package/.koolie/core/governance/change-requests/CR-2026-024-clientbindungen.md +78 -0
  224. package/.koolie/core/governance/change-requests/CR-2026-025-betriebsmodi.md +110 -0
  225. package/.koolie/core/governance/change-requests/CR-2026-026-fail-closed.md +134 -0
  226. package/.koolie/core/governance/change-requests/CR-2026-027-zeichenlimit-einstufung.md +105 -0
  227. package/.koolie/core/governance/change-requests/CR-2026-028-berechtigungsmodi.md +130 -0
  228. package/.koolie/core/governance/change-requests/CR-2026-029-hook-ablageort.md +115 -0
  229. package/.koolie/core/governance/change-requests/CR-2026-030-lesende-werkzeuge.md +143 -0
  230. package/.koolie/core/governance/change-requests/CR-2026-031-regelquellen-ausserhalb.md +182 -0
  231. package/.koolie/core/governance/change-requests/CR-2026-032-fremde-skill-ablage.md +151 -0
  232. package/.koolie/core/governance/change-requests/CR-2026-033-modusabhaengige-durchsetzung.md +131 -0
  233. package/.koolie/core/governance/change-requests/CR-2026-034-nachweisbedingungen.md +116 -0
  234. package/.koolie/core/governance/change-requests/CR-2026-035-readme-in-der-regelablage.md +115 -0
  235. package/.koolie/core/governance/change-requests/CR-2026-036-ortsangaben-hook-konfiguration.md +108 -0
  236. package/.koolie/core/governance/change-requests/CR-2026-037-meldender-hook-clientgebunden.md +133 -0
  237. package/.koolie/core/governance/change-requests/CR-2026-038-importsteuerung.md +159 -0
  238. package/.koolie/core/governance/change-requests/CR-2026-039-normative-saetze-in-kommentaren.md +124 -0
  239. package/.koolie/core/governance/change-requests/CR-2026-040-erh01-ist-clientgebunden.md +115 -0
  240. package/.koolie/core/governance/change-requests/CR-2026-041-s5-nicht-abbildbar.md +128 -0
  241. package/.koolie/core/governance/change-requests/CR-2026-042-abwesenheitsnachweis-suchwerkzeug.md +102 -0
  242. package/.koolie/core/governance/change-requests/CR-2026-043-validator-gibt-trefferwerte-aus.md +135 -0
  243. package/.koolie/core/governance/change-requests/CR-2026-044-aktivierungspruefung-clientgebunden.md +112 -0
  244. package/.koolie/core/governance/change-requests/CR-2026-045-update-verliert-die-clientwahl.md +116 -0
  245. package/.koolie/core/governance/change-requests/CR-2026-046-erstinstallation-ueberschreibt-projektdatei.md +125 -0
  246. package/.koolie/core/governance/change-requests/CR-2026-047-zusagen-je-zugriffskanal.md +133 -0
  247. package/.koolie/core/governance/change-requests/CR-2026-048-modusgrenze-ohne-durchsetzung.md +137 -0
  248. package/.koolie/core/governance/change-requests/CR-2026-049-sondenlauf-kodierungsabhaengig.md +126 -0
  249. package/.koolie/core/governance/change-requests/CR-2026-050-allowed-tools-und-verworfene-permissions.md +110 -0
  250. package/.koolie/core/governance/change-requests/CR-2026-051-quellenkarte-und-ueberholter-stand.md +110 -0
  251. package/.koolie/core/governance/change-requests/CR-2026-052-drei-regelkonflikte.md +166 -0
  252. package/.koolie/core/governance/change-requests/CR-2026-053-lesesperre-und-entwicklungsprofil.md +144 -0
  253. package/.koolie/core/governance/change-requests/CR-2026-054-aktivierung-verlangt-aktivitaet.md +124 -0
  254. package/.koolie/core/governance/change-requests/CR-2026-055-domain-ausnahme-ohne-mechanismus.md +173 -0
  255. package/.koolie/core/governance/change-requests/CR-2026-056-eingabeschema-und-pfadidentitaet.md +245 -0
  256. package/.koolie/core/governance/change-requests/CR-2026-057-disallowed-tools-und-s3.md +148 -0
  257. package/.koolie/core/governance/change-requests/CR-2026-058-unteragent-reichweite.md +203 -0
  258. package/.koolie/core/governance/change-requests/CR-2026-059-unteragent-tiefe-und-widerspruch.md +125 -0
  259. package/.koolie/core/governance/change-requests/CR-2026-060-stumme-brueche.md +170 -0
  260. package/.koolie/core/governance/change-requests/CR-2026-061-berechtigungsdatei-ohne-pruefung.md +169 -0
  261. package/.koolie/core/governance/change-requests/CR-2026-062-werkzeugabbildung-des-frontmatters.md +178 -0
  262. package/.koolie/core/governance/change-requests/CR-2026-063-skillaufruf-ohne-freigabe.md +207 -0
  263. package/.koolie/core/governance/change-requests/CR-2026-064-pruefregister-nicht-nachgezaehlt.md +141 -0
  264. package/.koolie/core/governance/change-requests/CR-2026-065-devin-suchwerkzeuge-erhoben.md +170 -0
  265. package/.koolie/core/governance/change-requests/CR-2026-066-schlitzdeckung-ohne-abgleich.md +153 -0
  266. package/.koolie/core/governance/change-requests/CR-2026-067-hookblock-und-praeparationsregister.md +140 -0
  267. package/.koolie/core/governance/change-requests/CR-2026-068-sondenlauf-nebenlaeufig.md +106 -0
  268. package/.koolie/core/governance/change-requests/CR-2026-069-bytecode-des-kerns.md +108 -0
  269. package/.koolie/core/governance/change-requests/CR-2026-070-d11-zaehler.md +114 -0
  270. package/.koolie/core/governance/change-requests/CR-2026-071-strukturentscheidungen-bestaetigen.md +322 -0
  271. package/.koolie/core/governance/change-requests/CR-2026-072-modulstatus-heben.md +315 -0
  272. package/.koolie/core/governance/change-requests/CR-2026-073-nicht-skill-traeger-heben.md +246 -0
  273. package/.koolie/core/governance/change-requests/CR-2026-074-restliche-nicht-skill-traeger-heben.md +271 -0
  274. package/.koolie/core/governance/change-requests/CR-2026-075-ap2-zielversion-devin-desktop.md +335 -0
  275. package/.koolie/core/governance/change-requests/CR-2026-076-erster-sitzungstest.md +455 -0
  276. package/.koolie/core/governance/change-requests/CR-2026-077-sitzungstest-schranken.md +425 -0
  277. package/.koolie/core/governance/change-requests/CR-2026-078-planung-nach-1-0-0.md +294 -0
  278. package/.koolie/core/governance/change-requests/CR-2026-079-umbenennung-vorziehen.md +233 -0
  279. package/.koolie/core/governance/change-requests/CR-2026-080-clientbindung-des-kerns.md +321 -0
  280. package/.koolie/core/governance/change-requests/CR-2026-081-produktnamen-im-kern.md +297 -0
  281. package/.koolie/core/governance/change-requests/CR-2026-082-sitzungstest-pi-ds-2.md +145 -0
  282. package/.koolie/core/governance/change-requests/CR-2026-083-sitzungstest-ne-sc.md +300 -0
  283. package/.koolie/core/governance/change-requests/CR-2026-084-trockenlauf-gegen-den-committeten-stand.md +117 -0
  284. package/.koolie/core/governance/change-requests/CR-2026-085-vorbedingungen-sitzungstest-5.md +182 -0
  285. package/.koolie/core/governance/change-requests/CR-2026-086-grenzfaelle-gegen-die-fassungen.md +164 -0
  286. package/.koolie/core/governance/change-requests/CR-2026-087-produktbeobachtung-quellenliste.md +206 -0
  287. package/.koolie/core/governance/change-requests/CR-2026-088-vorbedingungen-testblaetter.md +127 -0
  288. package/.koolie/core/governance/change-requests/CR-2026-089-herrichtung-uebungsrepositorium.md +226 -0
  289. package/.koolie/core/governance/change-requests/CR-2026-090-vorbedingungen-sitzungstest-5-zweiter-durchgang.md +255 -0
  290. package/.koolie/core/governance/change-requests/CR-2026-091-sitzungstest-5.md +163 -0
  291. package/.koolie/core/governance/change-requests/CR-2026-092-pruefmittelwort-und-buendelschnitt.md +226 -0
  292. package/.koolie/core/governance/change-requests/CR-2026-093-sechzehnte-praeparation.md +151 -0
  293. package/.koolie/core/governance/change-requests/CR-2026-094-testblaetter-buendel-1.md +113 -0
  294. package/.koolie/core/governance/change-requests/CR-2026-095-pruefapparat-filter.md +65 -0
  295. package/.koolie/core/governance/change-requests/CR-2026-096-vorbedingungen-buendel-2.md +225 -0
  296. package/.koolie/core/governance/change-requests/CR-2026-097-testblaetter-buendel-2.md +162 -0
  297. package/.koolie/core/governance/change-requests/CR-2026-098-anforderungen-auslieferung.md +93 -0
  298. package/.koolie/core/governance/change-requests/CR-2026-099-k74-und-vorbedingungen-buendel-3.md +119 -0
  299. package/.koolie/core/governance/change-requests/CR-2026-100-testblaetter-buendel-3.md +120 -0
  300. package/.koolie/core/governance/change-requests/CR-2026-101-k77-eigener-posten.md +165 -0
  301. package/.koolie/core/governance/change-requests/CR-2026-102-k77-entschieden.md +118 -0
  302. package/.koolie/core/governance/change-requests/CR-2026-103-vorbedingungen-buendel-4.md +76 -0
  303. package/.koolie/core/governance/change-requests/CR-2026-104-herrichtung-buendel-4.md +82 -0
  304. package/.koolie/core/governance/change-requests/CR-2026-105-messapparat-buendel-4.md +227 -0
  305. package/.koolie/core/governance/change-requests/CR-2026-106-uebergabe-einchecken.md +91 -0
  306. package/.koolie/core/governance/change-requests/CR-2026-107-uebergabe-im-release-commit.md +111 -0
  307. package/.koolie/core/governance/change-requests/CR-2026-108-testblaetter-buendel-4.md +118 -0
  308. package/.koolie/core/governance/change-requests/CR-2026-109-berichtsweg-des-aufraeumers.md +108 -0
  309. package/.koolie/core/governance/change-requests/CR-2026-110-vorbedingungen-des-nachlaufs.md +184 -0
  310. package/.koolie/core/governance/change-requests/CR-2026-111-der-apparat-auf-dem-weg-der-wiederaufnahme.md +127 -0
  311. package/.koolie/core/governance/change-requests/CR-2026-112-der-arbeitsplatz-im-kern.md +103 -0
  312. package/.koolie/core/governance/change-requests/CR-2026-113-nachlauf-buendel-4.md +103 -0
  313. package/.koolie/core/governance/change-requests/CR-2026-114-vorbedingungen-buendel-5.md +317 -0
  314. package/.koolie/core/governance/change-requests/CR-2026-115-herrichtung-buendel-5.md +244 -0
  315. package/.koolie/core/governance/change-requests/CR-2026-116-messtag-buendel-5.md +178 -0
  316. package/.koolie/core/governance/change-requests/CR-2026-117-sammelzellen-zentraler-katalog.md +251 -0
  317. package/.koolie/core/governance/change-requests/CR-2026-118-quellenzuordnung-matrixzeilen.md +115 -0
  318. package/.koolie/core/governance/change-requests/CR-2026-119-umbenennung-koolie.md +204 -0
  319. package/.koolie/core/governance/change-requests/CR-2026-120-ap2-rest-devin-desktop.md +96 -0
  320. package/.koolie/core/governance/change-requests/CR-2026-121-verify-marker-abschaffen.md +185 -0
  321. package/.koolie/core/governance/change-requests/CR-2026-122-umbenennung-koolie-lauf.md +131 -0
  322. package/.koolie/core/governance/change-requests/CR-2026-123-vortrag-und-namensabsatz.md +170 -0
  323. package/.koolie/core/governance/change-requests/CR-2026-124-hauptdokument-ap11.md +237 -0
  324. package/.koolie/core/governance/change-requests/CR-2026-125-word-fassung-und-blinder-fleck.md +204 -0
  325. package/.koolie/core/governance/change-requests/CR-2026-126-lizenz-gpl3.md +112 -0
  326. package/.koolie/core/governance/change-requests/CR-2026-127-gegenzeichnung-rollenfrage.md +96 -0
  327. package/.koolie/core/governance/change-requests/CR-2026-128-freigabelauf-1.0.0.md +278 -0
  328. package/.koolie/core/governance/change-requests/CR-2026-129-archiv-zeilenenden.md +90 -0
  329. package/.koolie/core/governance/change-requests/CR-2026-130-reihenfolge-des-hebens.md +204 -0
  330. package/.koolie/core/governance/change-requests/CR-2026-131-chronik-und-vorlage.md +194 -0
  331. package/.koolie/core/governance/change-requests/CR-2026-132-erhebung-openai-codex.md +185 -0
  332. package/.koolie/core/governance/change-requests/CR-2026-133-bau-openai-codex.md +97 -0
  333. package/.koolie/core/governance/change-requests/CR-2026-134-uebergabe-lokal.md +117 -0
  334. package/.koolie/core/governance/change-requests/CR-2026-135-pfadlisten-nul-getrennt.md +139 -0
  335. package/.koolie/core/governance/change-requests/CR-2026-136-overlay-werte-einordnung.md +156 -0
  336. package/.koolie/core/governance/change-requests/CR-2026-137-kopierweg-kern.md +144 -0
  337. package/.koolie/core/governance/change-requests/CR-2026-138-overlay-muster-general.md +118 -0
  338. package/.koolie/core/governance/change-requests/CR-2026-139-overlay-muster-dokumente.md +105 -0
  339. package/.koolie/core/governance/change-requests/CR-2026-140-installer-je-zielsystem.md +120 -0
  340. package/.koolie/core/governance/change-requests/CR-2026-141-lieferumfang.md +123 -0
  341. package/.koolie/core/governance/change-requests/CR-2026-142-dokumentationsstandard.md +90 -0
  342. package/.koolie/core/governance/change-requests/CR-2026-143-klasse-b-core-laufzeit.md +89 -0
  343. package/.koolie/core/governance/change-requests/CR-2026-144-klasse-b-governance.md +90 -0
  344. package/.koolie/core/governance/change-requests/CR-2026-145-klasse-b-skills.md +81 -0
  345. package/.koolie/core/governance/change-requests/CR-2026-146-code-pack-posten.md +84 -0
  346. package/.koolie/core/governance/change-requests/CR-2026-147-regel-register-posten.md +78 -0
  347. package/.koolie/core/governance/change-requests/CR-2026-148-mehrprojekt-tokenlast.md +70 -0
  348. package/.koolie/core/governance/change-requests/CR-2026-149-regelablage-pfadtoken.md +71 -0
  349. package/.koolie/core/governance/change-requests/CR-2026-150-client-pack-kiro.md +76 -0
  350. package/.koolie/core/governance/change-requests/CR-2026-151-skill-anweisungen.md +71 -0
  351. package/.koolie/core/governance/change-requests/CR-2026-152-testblaetter-modellwechsel.md +78 -0
  352. package/.koolie/core/governance/change-requests/CR-2026-153-attributionszeile.md +88 -0
  353. package/.koolie/core/governance/change-requests/CR-2026-154-auffindbarkeit.md +102 -0
  354. package/.koolie/core/governance/change-requests/CR-2026-155-client-pack-cursor.md +80 -0
  355. package/.koolie/core/governance/change-requests/CR-2026-156-mandat-und-reibung.md +100 -0
  356. package/.koolie/core/governance/change-requests/CR-2026-157-mcp-anbindung.md +93 -0
  357. package/.koolie/core/governance/change-requests/CR-2026-158-registerpflege.md +69 -0
  358. package/.koolie/core/governance/change-requests/CR-2026-159-powershell.md +49 -0
  359. package/.koolie/core/governance/change-requests/CR-2026-160-messapparat.md +59 -0
  360. package/.koolie/core/governance/change-requests/CR-2026-161-pruefwerkzeuge.md +52 -0
  361. package/.koolie/core/governance/change-requests/CR-2026-162-schutzschicht.md +65 -0
  362. package/.koolie/core/governance/change-requests/CR-2026-163-pfadsemantik.md +61 -0
  363. package/.koolie/core/governance/change-requests/CR-2026-164-modi-ausnahmen.md +65 -0
  364. package/.koolie/core/governance/change-requests/CR-2026-165-installer-banner.md +406 -0
  365. package/.koolie/core/governance/change-requests/CR-2026-166-nachlauf-aufzeichnungen.md +59 -0
  366. package/.koolie/core/governance/change-requests/CR-2026-167-einsatzarchitektur.md +60 -0
  367. package/.koolie/core/governance/change-requests/CR-2026-168-paketquellen.md +64 -0
  368. package/.koolie/core/governance/change-requests/CR-2026-169-modusbindung-messfragen.md +58 -0
  369. package/.koolie/core/governance/change-requests/CR-2026-170-erste-veroeffentlichung.md +62 -0
  370. package/.koolie/core/governance/change-requests/CR-2026-171-befehl-im-projektverzeichnis.md +57 -0
  371. package/.koolie/core/governance/change-requests/CR-2026-172-auftritt-npm-suche.md +58 -0
  372. package/.koolie/core/install.py +2402 -0
  373. package/.koolie/core/install_dialog.py +227 -0
  374. package/.koolie/core/koexistenz.py +112 -0
  375. package/.koolie/core/mandat.py +605 -0
  376. package/.koolie/core/onboarding/COMPLETION_CRITERIA.md +37 -0
  377. package/.koolie/core/onboarding/GUIDE.md +103 -0
  378. package/.koolie/core/onboarding/KNOWLEDGE_CHECK.md +48 -0
  379. package/.koolie/core/onboarding/MENTOR_CHECKLIST.md +40 -0
  380. package/.koolie/core/onboarding/QUICKSTART.md +50 -0
  381. package/.koolie/core/onboarding/REFERENCE.md +69 -0
  382. package/.koolie/core/onboarding/exercises/EXERCISES.md +78 -0
  383. package/.koolie/core/onboarding/exercises/README.md +120 -0
  384. package/.koolie/core/pilot/METRICS.md +59 -0
  385. package/.koolie/core/pilot/PILOT_CONCEPT.md +42 -0
  386. package/.koolie/core/prompts/01-understand-codebase.md +99 -0
  387. package/.koolie/core/prompts/02-impact-analysis.md +104 -0
  388. package/.koolie/core/prompts/03-implementation-planning.md +102 -0
  389. package/.koolie/core/prompts/04-code-generation.md +100 -0
  390. package/.koolie/core/prompts/05-test-generation.md +103 -0
  391. package/.koolie/core/prompts/06-refactoring.md +102 -0
  392. package/.koolie/core/prompts/07-debugging.md +97 -0
  393. package/.koolie/core/prompts/08-security-review.md +92 -0
  394. package/.koolie/core/prompts/09-performance-analysis.md +89 -0
  395. package/.koolie/core/prompts/10-documentation.md +91 -0
  396. package/.koolie/core/prompts/11-merge-request-review.md +94 -0
  397. package/.koolie/core/prompts/12-developer-training.md +90 -0
  398. package/.koolie/core/prompts/README.md +88 -0
  399. package/.koolie/core/templates/MR_AI_DISCLOSURE.md +37 -0
  400. package/.koolie/core/templates/PLAN_TEMPLATE.md +57 -0
  401. package/.koolie/core/templates/SKILL_TEMPLATE.md +159 -0
  402. package/.koolie/core/templates/project-overlay/OVERLAY.md +315 -0
  403. package/.koolie/core/templates/project-overlay/documents/README.md +43 -0
  404. package/.koolie/core/templates/project-overlay/documents/ai-governance/.gitkeep.md +3 -0
  405. package/.koolie/core/templates/project-overlay/documents/ai-process-model/.gitkeep.md +3 -0
  406. package/.koolie/core/templates/project-overlay/documents/architecture/.gitkeep.md +3 -0
  407. package/.koolie/core/templates/project-overlay/documents/architecture/decisions/README.md +3 -0
  408. package/.koolie/core/templates/project-overlay/documents/branching-strategy/.gitkeep.md +3 -0
  409. package/.koolie/core/templates/project-overlay/documents/coding-guidelines/.gitkeep.md +3 -0
  410. package/.koolie/core/templates/project-overlay/documents/definition-of-done/.gitkeep.md +3 -0
  411. package/.koolie/core/templates/project-overlay/documents/definition-of-ready/.gitkeep.md +3 -0
  412. package/.koolie/core/templates/project-overlay/documents/deployment/.gitkeep.md +3 -0
  413. package/.koolie/core/templates/project-overlay/documents/glossary/.gitkeep.md +3 -0
  414. package/.koolie/core/templates/project-overlay/documents/quality/.gitkeep.md +3 -0
  415. package/.koolie/core/templates/project-overlay/documents/roadmap/.gitkeep.md +3 -0
  416. package/.koolie/core/templates/project-overlay/documents/roles/.gitkeep.md +3 -0
  417. package/.koolie/core/templates/project-overlay/documents/security/.gitkeep.md +3 -0
  418. package/.koolie/core/templates/project-overlay/exceptions/EXCEPTIONS.md +9 -0
  419. package/.koolie/core/templates/project-overlay/forbidden-terms.txt +8 -0
  420. package/.koolie/core/templates/project-overlay/overlay-manifest.yaml +73 -0
  421. package/.koolie/core/templates/rules/21-overlay-TEMPLATE.md.template +28 -0
  422. package/.koolie/core/templates/rules/40-tech-TEMPLATE.md.template +33 -0
  423. package/.koolie/core/tests/EDGE_CASES.md +67 -0
  424. package/.koolie/core/tests/TEST_CATALOG.md +170 -0
  425. package/.koolie/core/tests/erhebungen/README.md +326 -0
  426. package/.koolie/core/tests/erhebungen/ablage.py +357 -0
  427. package/.koolie/core/tests/erhebungen/apparat/__init__.py +22 -0
  428. package/.koolie/core/tests/erhebungen/apparat/baum.py +214 -0
  429. package/.koolie/core/tests/erhebungen/apparat/belege.py +91 -0
  430. package/.koolie/core/tests/erhebungen/apparat/clients.py +321 -0
  431. package/.koolie/core/tests/erhebungen/apparat/freigabe.py +74 -0
  432. package/.koolie/core/tests/erhebungen/apparat/kontingent.py +64 -0
  433. package/.koolie/core/tests/erhebungen/apparat/laeufer.py +155 -0
  434. package/.koolie/core/tests/erhebungen/apparat/reihe.py +202 -0
  435. package/.koolie/core/tests/erhebungen/apparat/selbsttest.py +280 -0
  436. package/.koolie/core/tests/erhebungen/apparat/stand.py +30 -0
  437. package/.koolie/core/tests/erhebungen/apparat/vorpruefung.py +70 -0
  438. package/.koolie/core/tests/erhebungen/auswerten-b4.py +468 -0
  439. package/.koolie/core/tests/erhebungen/auswerten-b5.py +517 -0
  440. package/.koolie/core/tests/erhebungen/auswerten-dd.py +172 -0
  441. package/.koolie/core/tests/erhebungen/baeume-b4.py +334 -0
  442. package/.koolie/core/tests/erhebungen/baeume-b5.py +339 -0
  443. package/.koolie/core/tests/erhebungen/baeume_loeschen.py +138 -0
  444. package/.koolie/core/tests/erhebungen/cc-overlay-fuellen.py +277 -0
  445. package/.koolie/core/tests/erhebungen/dossier-b4.py +178 -0
  446. package/.koolie/core/tests/erhebungen/dossier-b5.py +202 -0
  447. package/.koolie/core/tests/erhebungen/historie-bauen-b4.py +642 -0
  448. package/.koolie/core/tests/erhebungen/k-bauen-b3.py +841 -0
  449. package/.koolie/core/tests/erhebungen/lauf-dd.py +146 -0
  450. package/.koolie/core/tests/erhebungen/lauf.py +114 -0
  451. package/.koolie/core/tests/erhebungen/mcp-waechter.py +278 -0
  452. package/.koolie/core/tests/erhebungen/messbaum-schnitt.py +256 -0
  453. package/.koolie/core/tests/erhebungen/messen.py +72 -0
  454. package/.koolie/core/tests/erhebungen/node-waechter.py +74 -0
  455. package/.koolie/core/tests/erhebungen/packaktivierung.py +403 -0
  456. package/.koolie/core/tests/erhebungen/prompts-schreiben-b4.py +210 -0
  457. package/.koolie/core/tests/erhebungen/prompts-schreiben-b5.py +251 -0
  458. package/.koolie/core/tests/erhebungen/reihe-b4.py +171 -0
  459. package/.koolie/core/tests/erhebungen/reihe-b5.py +172 -0
  460. package/.koolie/core/tests/erhebungen/stand-b4.py +135 -0
  461. package/.koolie/core/tests/erhebungen/stand-b5.py +137 -0
  462. package/.koolie/core/tests/erhebungen/trust-b4.py +56 -0
  463. package/.koolie/core/tests/erhebungen/trust-b5.py +56 -0
  464. package/.koolie/core/tests/erhebungen/turn2-schreiben-b4.py +105 -0
  465. package/.koolie/core/tests/erhebungen/umgebungen-bauen-b4.py +377 -0
  466. package/.koolie/core/tests/erhebungen/umgebungen-bauen-b5.py +363 -0
  467. package/.koolie/core/tests/erhebungen/zaehlen46.py +74 -0
  468. package/.koolie/core/tests/erhebungen/zustand-b4.py +82 -0
  469. package/.koolie/core/tests/erhebungen/zustand-b5.py +82 -0
  470. package/.koolie/core/tests/protocols/2026-09-10-AP2-claude-code-wirkungsnachweise.md +194 -0
  471. package/.koolie/core/tests/protocols/2026-09-10-AP2-claude-code.md +468 -0
  472. package/.koolie/core/tests/protocols/2026-09-10-CR-2026-020-akteursbezeichnung.md +99 -0
  473. package/.koolie/core/tests/protocols/2026-09-10-CR-2026-021-hook-interpreter.md +138 -0
  474. package/.koolie/core/tests/protocols/2026-09-10-CR-2026-022-artefaktversionen.md +87 -0
  475. package/.koolie/core/tests/protocols/2026-09-10-CR-2026-023-shell-lesesperre.md +135 -0
  476. package/.koolie/core/tests/protocols/2026-09-10-CR-2026-024-clientbindungen.md +98 -0
  477. package/.koolie/core/tests/protocols/2026-09-10-FW-DS-03.md +90 -0
  478. package/.koolie/core/tests/protocols/2026-09-10-FW-KO-01.md +105 -0
  479. package/.koolie/core/tests/protocols/2026-09-10-FW-KO-02.md +123 -0
  480. package/.koolie/core/tests/protocols/2026-09-10-FW-KO-04.md +73 -0
  481. package/.koolie/core/tests/protocols/2026-09-10-FW-RE-02.md +99 -0
  482. package/.koolie/core/tests/protocols/2026-09-10-FW-VN-01-wiederholung.md +108 -0
  483. package/.koolie/core/tests/protocols/2026-09-10-FW-VN-01.md +249 -0
  484. package/.koolie/core/tests/protocols/2026-09-10-FW-ZA-05.md +99 -0
  485. package/.koolie/core/tests/protocols/2026-09-11-AP2-devin-desktop.md +257 -0
  486. package/.koolie/core/tests/protocols/2026-09-11-CR-2026-026-fail-closed.md +113 -0
  487. package/.koolie/core/tests/protocols/2026-09-11-erhebungen-K21-K26.md +243 -0
  488. package/.koolie/core/tests/protocols/2026-09-11-wirkungsnachweise-0.26.0.md +101 -0
  489. package/.koolie/core/tests/protocols/2026-09-12-B01-allowed-tools.md +152 -0
  490. package/.koolie/core/tests/protocols/2026-09-12-B04-B05-gegenpruefung.md +204 -0
  491. package/.koolie/core/tests/protocols/2026-09-12-erhebungen-K28-S5-B9-bypass.md +268 -0
  492. package/.koolie/core/tests/protocols/2026-09-12-mehrprojekt-arbeitsbereich.md +154 -0
  493. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.26.1.md +126 -0
  494. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.27.0.md +114 -0
  495. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.28.0.md +129 -0
  496. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.29.0.md +121 -0
  497. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.30.0.md +112 -0
  498. package/.koolie/core/tests/protocols/2026-09-12-wirkungsnachweise-0.31.0.md +107 -0
  499. package/.koolie/core/tests/protocols/2026-09-13-B06-gegenpruefung.md +370 -0
  500. package/.koolie/core/tests/protocols/2026-09-13-B07-B09-gegenpruefung.md +211 -0
  501. package/.koolie/core/tests/protocols/2026-09-13-B08-B11-gegenpruefung.md +179 -0
  502. package/.koolie/core/tests/protocols/2026-09-13-erhebung-disallowed-tools.md +147 -0
  503. package/.koolie/core/tests/protocols/2026-09-13-erhebung-unteragent-tiefe.md +139 -0
  504. package/.koolie/core/tests/protocols/2026-09-13-erhebung-unteragent.md +214 -0
  505. package/.koolie/core/tests/protocols/2026-09-13-gegenpruefung-berechtigungsdatei.md +253 -0
  506. package/.koolie/core/tests/protocols/2026-09-13-gegenpruefung-stumme-brueche.md +202 -0
  507. package/.koolie/core/tests/protocols/2026-09-13-gegenpruefung-werkzeugabbildung.md +219 -0
  508. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.32.0.md +133 -0
  509. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.33.0.md +169 -0
  510. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.34.0.md +187 -0
  511. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.35.0.md +187 -0
  512. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.36.0.md +137 -0
  513. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.37.0.md +112 -0
  514. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.38.0.md +154 -0
  515. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.39.0.md +188 -0
  516. package/.koolie/core/tests/protocols/2026-09-13-wirkungsnachweise-0.40.0.md +214 -0
  517. package/.koolie/core/tests/protocols/2026-09-14-erhebung-devin-werkzeuge.md +267 -0
  518. package/.koolie/core/tests/protocols/2026-09-14-erhebung-skillaufruf.md +238 -0
  519. package/.koolie/core/tests/protocols/2026-09-14-gegenpruefung-pruefregister.md +156 -0
  520. package/.koolie/core/tests/protocols/2026-09-14-gegenpruefung-schlitzdeckung.md +206 -0
  521. package/.koolie/core/tests/protocols/2026-09-14-gegenpruefung-skillwahl.md +127 -0
  522. package/.koolie/core/tests/protocols/2026-09-14-migrationslauf-pilot.md +134 -0
  523. package/.koolie/core/tests/protocols/2026-09-14-wirkungsnachweise-0.41.0.md +175 -0
  524. package/.koolie/core/tests/protocols/2026-09-14-wirkungsnachweise-0.42.0.md +180 -0
  525. package/.koolie/core/tests/protocols/2026-09-14-wirkungsnachweise-0.43.0.md +165 -0
  526. package/.koolie/core/tests/protocols/2026-09-14-wirkungsnachweise-0.44.0.md +130 -0
  527. package/.koolie/core/tests/protocols/2026-09-15-gegenpruefung-d11-zaehlregeln.md +311 -0
  528. package/.koolie/core/tests/protocols/2026-09-15-gegenpruefung-modulstatus.md +524 -0
  529. package/.koolie/core/tests/protocols/2026-09-15-gegenpruefung-nicht-skill-traeger.md +225 -0
  530. package/.koolie/core/tests/protocols/2026-09-15-gegenpruefung-restliche-nicht-skill-traeger.md +345 -0
  531. package/.koolie/core/tests/protocols/2026-09-15-gegenpruefung-strukturentscheidungen.md +392 -0
  532. package/.koolie/core/tests/protocols/2026-09-15-herrichtung-uebungsrepositorium.md +213 -0
  533. package/.koolie/core/tests/protocols/2026-09-15-migrationslauf-pilot-0.46.0.md +143 -0
  534. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.45.0.md +127 -0
  535. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.46.0.md +193 -0
  536. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.47.0.md +149 -0
  537. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.49.0.md +202 -0
  538. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.50.0.md +183 -0
  539. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.51.0.md +202 -0
  540. package/.koolie/core/tests/protocols/2026-09-15-wirkungsnachweise-0.52.0.md +168 -0
  541. package/.koolie/core/tests/protocols/2026-09-16-AP2-zielversion-devin-desktop.md +231 -0
  542. package/.koolie/core/tests/protocols/2026-09-16-wirkungsnachweise-0.53.0.md +350 -0
  543. package/.koolie/core/tests/protocols/2026-09-17-nachtrag-migrationshinweis-0.54.1.md +121 -0
  544. package/.koolie/core/tests/protocols/2026-09-17-sitzungstest-pi-ds.md +523 -0
  545. package/.koolie/core/tests/protocols/2026-09-17-sitzungstest-schranken.md +433 -0
  546. package/.koolie/core/tests/protocols/2026-09-17-wirkungsnachweise-0.54.0.md +159 -0
  547. package/.koolie/core/tests/protocols/2026-09-18-FW-AK-01.md +442 -0
  548. package/.koolie/core/tests/protocols/2026-09-18-FW-KO-05.md +231 -0
  549. package/.koolie/core/tests/protocols/2026-09-18-herrichtung-uebungsrepositorium.md +273 -0
  550. package/.koolie/core/tests/protocols/2026-09-18-nachtrag-preis-umbenennung-0.56.1.md +102 -0
  551. package/.koolie/core/tests/protocols/2026-09-18-sitzungstest-5.md +349 -0
  552. package/.koolie/core/tests/protocols/2026-09-18-sitzungstest-ne-sc.md +430 -0
  553. package/.koolie/core/tests/protocols/2026-09-18-sitzungstest-pi-ds-2.md +499 -0
  554. package/.koolie/core/tests/protocols/2026-09-18-vorbedingungen-sitzungstest-5-zweiter-durchgang.md +170 -0
  555. package/.koolie/core/tests/protocols/2026-09-18-vorbedingungen-testblaetter.md +184 -0
  556. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.55.0.md +202 -0
  557. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.56.0.md +135 -0
  558. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.56.2.md +154 -0
  559. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.57.0.md +228 -0
  560. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.57.1.md +262 -0
  561. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.58.0.md +135 -0
  562. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.59.0.md +150 -0
  563. package/.koolie/core/tests/protocols/2026-09-18-wirkungsnachweise-0.60.0.md +340 -0
  564. package/.koolie/core/tests/protocols/2026-09-19-herrichtung-buendel-4.md +235 -0
  565. package/.koolie/core/tests/protocols/2026-09-19-k77-zurechenbarkeit.md +236 -0
  566. package/.koolie/core/tests/protocols/2026-09-19-pruefapparat-filter.md +50 -0
  567. package/.koolie/core/tests/protocols/2026-09-19-pruefmittelwort-und-buendelschnitt.md +239 -0
  568. package/.koolie/core/tests/protocols/2026-09-19-sechzehnte-praeparation.md +135 -0
  569. package/.koolie/core/tests/protocols/2026-09-19-testblaetter-buendel-1.md +250 -0
  570. package/.koolie/core/tests/protocols/2026-09-19-testblaetter-buendel-2.md +229 -0
  571. package/.koolie/core/tests/protocols/2026-09-19-testblaetter-buendel-3.md +293 -0
  572. package/.koolie/core/tests/protocols/2026-09-19-vorbedingungen-buendel-2.md +236 -0
  573. package/.koolie/core/tests/protocols/2026-09-19-vorbedingungen-buendel-3.md +253 -0
  574. package/.koolie/core/tests/protocols/2026-09-19-vorbedingungen-buendel-4.md +138 -0
  575. package/.koolie/core/tests/protocols/2026-09-20-messapparat-buendel-4.md +433 -0
  576. package/.koolie/core/tests/protocols/2026-09-20-testblaetter-buendel-4.md +321 -0
  577. package/.koolie/core/tests/protocols/2026-09-20-uebergabe-im-release-commit.md +146 -0
  578. package/.koolie/core/tests/protocols/2026-09-20-vorbedingungen-nachlauf-b4.md +259 -0
  579. package/.koolie/core/tests/protocols/2026-09-21-arbeitsplatz-im-kern.md +127 -0
  580. package/.koolie/core/tests/protocols/2026-09-21-nachlauf-buendel-4.md +157 -0
  581. package/.koolie/core/tests/protocols/2026-09-21-vorbedingungen-buendel-5.md +247 -0
  582. package/.koolie/core/tests/protocols/2026-09-21-wiederaufnahme-nachlauf-b4.md +190 -0
  583. package/.koolie/core/tests/protocols/2026-09-22-ap2-rest-devin-desktop.md +614 -0
  584. package/.koolie/core/tests/protocols/2026-09-22-hauptdokument-ap11.md +443 -0
  585. package/.koolie/core/tests/protocols/2026-09-22-herrichtung-buendel-5.md +224 -0
  586. package/.koolie/core/tests/protocols/2026-09-22-messtag-buendel-5.md +327 -0
  587. package/.koolie/core/tests/protocols/2026-09-22-quellenzuordnung-matrixzeilen.md +230 -0
  588. package/.koolie/core/tests/protocols/2026-09-22-sammelzellen-zentraler-katalog.md +354 -0
  589. package/.koolie/core/tests/protocols/2026-09-22-umbenennung-koolie.md +296 -0
  590. package/.koolie/core/tests/protocols/2026-09-22-verify-marker-abschaffen.md +283 -0
  591. package/.koolie/core/tests/protocols/2026-09-22-vortrag-und-name.md +261 -0
  592. package/.koolie/core/tests/protocols/2026-09-22-wirkungsnachweise-0.86.0.md +158 -0
  593. package/.koolie/core/tests/protocols/2026-09-23-archiv-zeilenenden.md +113 -0
  594. package/.koolie/core/tests/protocols/2026-09-23-bau-openai-codex.md +168 -0
  595. package/.koolie/core/tests/protocols/2026-09-23-chronik-und-vorlage.md +224 -0
  596. package/.koolie/core/tests/protocols/2026-09-23-erhebung-openai-codex.md +309 -0
  597. package/.koolie/core/tests/protocols/2026-09-23-freigabelauf-1.0.0-vorbereitung.md +119 -0
  598. package/.koolie/core/tests/protocols/2026-09-23-freigabelauf-1.0.0.md +150 -0
  599. package/.koolie/core/tests/protocols/2026-09-23-gegenzeichnung-rollenfrage.md +198 -0
  600. package/.koolie/core/tests/protocols/2026-09-23-reihenfolge-des-hebens.md +220 -0
  601. package/.koolie/core/tests/protocols/2026-09-23-word-fassung-und-blinder-fleck.md +307 -0
  602. package/.koolie/core/tests/protocols/2026-09-24-kopierweg-kern.md +98 -0
  603. package/.koolie/core/tests/protocols/2026-09-24-overlay-werte-einordnung.md +111 -0
  604. package/.koolie/core/tests/protocols/2026-09-24-pfadlisten-nul-getrennt.md +128 -0
  605. package/.koolie/core/tests/protocols/2026-09-24-uebergabe-lokal.md +85 -0
  606. package/.koolie/core/tests/protocols/2026-09-25-code-pack-posten.md +86 -0
  607. package/.koolie/core/tests/protocols/2026-09-25-dokumentationsstandard.md +101 -0
  608. package/.koolie/core/tests/protocols/2026-09-25-installer-je-zielsystem.md +134 -0
  609. package/.koolie/core/tests/protocols/2026-09-25-klasse-b-core-laufzeit.md +126 -0
  610. package/.koolie/core/tests/protocols/2026-09-25-klasse-b-governance.md +133 -0
  611. package/.koolie/core/tests/protocols/2026-09-25-klasse-b-skills.md +91 -0
  612. package/.koolie/core/tests/protocols/2026-09-25-lieferumfang.md +76 -0
  613. package/.koolie/core/tests/protocols/2026-09-25-overlay-muster-dokumente.md +107 -0
  614. package/.koolie/core/tests/protocols/2026-09-25-overlay-muster-general.md +100 -0
  615. package/.koolie/core/tests/protocols/2026-09-25-regel-register-posten.md +87 -0
  616. package/.koolie/core/tests/protocols/2026-09-26-attributionszeile.md +83 -0
  617. package/.koolie/core/tests/protocols/2026-09-26-auffindbarkeit.md +85 -0
  618. package/.koolie/core/tests/protocols/2026-09-26-bau-cursor.md +68 -0
  619. package/.koolie/core/tests/protocols/2026-09-26-bau-kiro.md +75 -0
  620. package/.koolie/core/tests/protocols/2026-09-26-mehrprojekt-tokenlast.md +174 -0
  621. package/.koolie/core/tests/protocols/2026-09-26-regelablage-pfadtoken.md +133 -0
  622. package/.koolie/core/tests/protocols/2026-09-26-skill-anweisungen.md +111 -0
  623. package/.koolie/core/tests/protocols/2026-09-26-testblaetter-modellwechsel.md +106 -0
  624. package/.koolie/core/tests/protocols/2026-09-27-mandat-und-reibung.md +58 -0
  625. package/.koolie/core/tests/protocols/2026-09-28-mcp-anbindung.md +72 -0
  626. package/.koolie/core/tests/protocols/2026-09-29-messapparat.md +75 -0
  627. package/.koolie/core/tests/protocols/2026-09-29-powershell.md +58 -0
  628. package/.koolie/core/tests/protocols/2026-09-29-pruefwerkzeuge.md +64 -0
  629. package/.koolie/core/tests/protocols/2026-09-29-registerpflege.md +228 -0
  630. package/.koolie/core/tests/protocols/2026-09-29-schutzschicht.md +95 -0
  631. package/.koolie/core/tests/protocols/2026-09-30-einsatzarchitektur.md +104 -0
  632. package/.koolie/core/tests/protocols/2026-09-30-modi-ausnahmen.md +77 -0
  633. package/.koolie/core/tests/protocols/2026-09-30-nachlauf-banner.md +85 -0
  634. package/.koolie/core/tests/protocols/2026-09-30-paketquellen.md +65 -0
  635. package/.koolie/core/tests/protocols/2026-09-30-pfadsemantik.md +79 -0
  636. package/.koolie/core/tests/protocols/2026-10-01-auftritt-und-suche.md +58 -0
  637. package/.koolie/core/tests/protocols/2026-10-01-befehl-im-projektverzeichnis.md +47 -0
  638. package/.koolie/core/tests/protocols/2026-10-01-erste-veroeffentlichung.md +60 -0
  639. package/.koolie/core/tests/protocols/2026-10-01-modusbindung-messfragen.md +107 -0
  640. package/.koolie/core/tests/protocols/README.md +14 -0
  641. package/.koolie/core/tests/scripts/hook-check-secrets.py +1307 -0
  642. package/.koolie/core/tests/scripts/hook-overlay-status.py +120 -0
  643. package/.koolie/core/tests/scripts/mermaid_renderer.py +67 -0
  644. package/.koolie/core/tests/scripts/overlay_status.py +130 -0
  645. package/.koolie/core/tests/scripts/probe-pruefungen.py +110 -0
  646. package/.koolie/core/tests/scripts/pruefungen/__init__.py +4 -0
  647. package/.koolie/core/tests/scripts/pruefungen/berechtigungen.py +1559 -0
  648. package/.koolie/core/tests/scripts/pruefungen/bestand.py +1433 -0
  649. package/.koolie/core/tests/scripts/pruefungen/dokumente.py +1262 -0
  650. package/.koolie/core/tests/scripts/pruefungen/gemeinsam.py +784 -0
  651. package/.koolie/core/tests/scripts/pruefungen/hooks.py +1188 -0
  652. package/.koolie/core/tests/scripts/pruefungen/overlay.py +1251 -0
  653. package/.koolie/core/tests/scripts/pruefungen/packs.py +1314 -0
  654. package/.koolie/core/tests/scripts/pruefungen/register.py +950 -0
  655. package/.koolie/core/tests/scripts/pruefungen/testkatalog.py +1202 -0
  656. package/.koolie/core/tests/scripts/pruefungen/werkzeuge.py +957 -0
  657. package/.koolie/core/tests/scripts/sonden/__init__.py +4 -0
  658. package/.koolie/core/tests/scripts/sonden/apparat.py +653 -0
  659. package/.koolie/core/tests/scripts/sonden/teil01_grundbestand_und_installation.py +999 -0
  660. package/.koolie/core/tests/scripts/sonden/teil02_packs_mandat_mcp.py +942 -0
  661. package/.koolie/core/tests/scripts/sonden/teil03_pruefungen_26_bis_36.py +1184 -0
  662. package/.koolie/core/tests/scripts/sonden/teil04_pruefungen_37_bis_45.py +1491 -0
  663. package/.koolie/core/tests/scripts/sonden/teil05_pruefungen_46_bis_55.py +1447 -0
  664. package/.koolie/core/tests/scripts/sonden/teil06_pruefungen_57_bis_65.py +739 -0
  665. package/.koolie/core/tests/scripts/sonden/teil07_overlay_und_lieferung.py +1014 -0
  666. package/.koolie/core/tests/scripts/sonden/teil08_pruefungen_66_bis_80.py +1325 -0
  667. package/.koolie/core/tests/scripts/sonden/teil09_pruefmittel_81_und_82.py +505 -0
  668. package/.koolie/core/tests/scripts/sonden/teil10_pruefungen_83_bis_95.py +1218 -0
  669. package/.koolie/core/tests/scripts/sonden/teil11_pruefungen_104_und_105.py +149 -0
  670. package/.koolie/core/tests/scripts/sonden/teil12_pruefungen_106_und_107.py +136 -0
  671. package/.koolie/core/tests/scripts/sonden/teil13_pfad_und_mustersemantik.py +150 -0
  672. package/.koolie/core/tests/scripts/sonden/teil14_modi_ausnahmen_skills.py +204 -0
  673. package/.koolie/core/tests/scripts/sonden/teil15_banner_und_nachlauf.py +193 -0
  674. package/.koolie/core/tests/scripts/sonden/teil16_koexistenz.py +114 -0
  675. package/.koolie/core/tests/scripts/sonden/teil17_paketquellen.py +115 -0
  676. package/.koolie/core/tests/scripts/sonden/teil18_modusbindung_m3_m5.py +113 -0
  677. package/.koolie/core/tests/scripts/validate-framework.py +1104 -0
  678. package/.koolie/core/tests/scripts/validate-output.py +296 -0
  679. package/.koolie/core/wirksamkeit.py +430 -0
  680. package/CONTRIBUTING.md +119 -0
  681. package/LICENSE +674 -0
  682. package/QUICKSTART.en.md +135 -0
  683. package/QUICKSTART.md +129 -0
  684. package/README.en.md +108 -0
  685. package/README.md +107 -0
  686. package/install.cmd +44 -0
  687. package/install.command +46 -0
  688. package/package.json +21 -0
  689. package/paketquellen/README.md +41 -0
  690. package/paketquellen/bauen.py +411 -0
  691. package/paketquellen/koolie.cmd +23 -0
  692. package/paketquellen/koolie_befehl.py +82 -0
  693. package/paketquellen/npm/koolie.js +26 -0
@@ -0,0 +1,1202 @@
1
+ """Testkatalog und Testblaetter: K3-Kategorien, Grenzfaelle, Praeparationsregister,
2
+ Skillaufruf, Pruefmittel, Skillversion, Nummernverweise, Ausgabemarken, Ergebnisstatus,
3
+ Belegquelle, Aenderungsart und Ergebnisstand.
4
+
5
+ Pruefungen 29, 30, 44, 49, 61, 62, 63, 64, 65, 73, 95 und 103. Teil des Validators
6
+ validate-framework.py, seit 1.19.1 nach Gegenstand in Module geteilt (K-174). Das
7
+ Register aller Pruefungen steht im Kopfkommentar des Einstiegs, die Grenze jeder
8
+ einzelnen in ihrem Kopfkommentar hier."""
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import re
13
+
14
+ from .gemeinsam import (
15
+ _client_packs, _tabellenspalte, _ueb_katalogdateien, err, iter_text_files, KERN,
16
+ NEUTRAL_CHRONIK, NEUTRAL_CHRONIK_BASENAMES, NEUTRAL_DOKUMENT, P73_ZEILE_RE, read,
17
+ skill_dirs, tabellenzellen, TBD_RE, warn)
18
+
19
+
20
+ # Pruefung 29: Kurzform und Langform fuehren dieselben K3-Kategorien, unbedingt.
21
+ #
22
+ # Die Kategorien aus Abschnitt 2.1 sind ebenenfest (D-52). Zwei Fehlerbilder sind moeglich
23
+ # und beide sind vorgekommen: eine Kategorie fehlt in einer Fassung - die Kurzform, die in
24
+ # jede Sitzung laedt, fuehrte bis 0.31.0 nur sechs von acht -, oder eine Kategorie traegt
25
+ # eine Bedingung, wie die interne Adresse, die "sofern nicht im Overlay als K1 eingestuft"
26
+ # ausgenommen war. Beides prueft diese Funktion an denselben fuenf Traegern.
27
+ K3_KATEGORIEN = (
28
+ ("Secrets und Zugangsdaten", r"Secret|Zugangsdaten"),
29
+ ("personenbezogene Echtdaten", r"personenbezogene Echtdaten"),
30
+ ("Produktionsdaten", r"Produktionsdaten"),
31
+ ("Kunden- und Behördendokumente", r"Behörden"),
32
+ ("Sicherheitskonfigurationen", r"Sicherheitskonfiguration"),
33
+ ("interne Adressen und Umgebungskennungen", r"interne[nr]? Adressen"),
34
+ ("als vertraulich eingestufte Inhalte", r"vertraulich"),
35
+ ("Inhalte anderer Projekte", r"anderer Projekte|anderen Projekten|Fremdprojekte"),
36
+ )
37
+
38
+ # Eine Bedingung in einer unbedingten Liste. "es sei denn" stand in Abschnitt 2.2.
39
+ K3_BEDINGUNG_RE = re.compile(
40
+ r"sofern nicht|sofern es nicht|es sei denn|außer wenn|soweit nicht|sofern kein", re.I)
41
+
42
+ # Je Traeger: Pfad, Startanker, Endanker (None = bis Zeilenende des Startankers).
43
+ K3_TRAEGER = (
44
+ (KERN + "/framework/core/02-privacy.md", "### 2.1 Immer K3", "### 2.2"),
45
+ (KERN + "/framework/runtime/root-instruction.md", "- Immer K3", None),
46
+ (KERN + "/framework/runtime/rules/10-privacy-security.md", "| K3 |", None),
47
+ (KERN + "/decision-trees/01-context-allowed.md", "1. **K3-Prüfung:**", "\n2. "),
48
+ (KERN + "/checklists/02-privacy-context.md", "K3-Kategorien geprüft", None),
49
+ )
50
+
51
+
52
+ def check_k3_kategorien(root: str) -> None:
53
+ """Pruefung 29 (D-52): Dieselbe K3-Liste in jeder Fassung, ohne Bedingung.
54
+
55
+ Belegt Uebereinstimmung der Kategorien, nicht die Gleichheit der Formulierungen - eine
56
+ Kurzform darf kuerzer sein, aber keine Kategorie weglassen und keine an eine Bedingung
57
+ binden. Was die Pruefung nicht leistet: Sie sieht nicht, ob ein KI-Client die Liste
58
+ auch anwendet. Das ist FW-KO-05, ein Dokumentenreview ueber die Fassungen
59
+ (.koolie/core/tests/EDGE_CASES.md); ob ein KI-Client die Liste anwendet, misst
60
+ kein Testfall des Katalogs - das ist K-60 (D-148).
61
+ """
62
+ for rel, start, ende in K3_TRAEGER:
63
+ pfad = os.path.join(root, rel.replace("/", os.sep))
64
+ if not os.path.isfile(pfad):
65
+ err(f"{rel}: fehlt - diese Datei fuehrt die K3-Kategorien und wird gegen die "
66
+ f"uebrigen Fassungen geprueft (D-52)")
67
+ continue
68
+ text = read(pfad).replace("\r\n", "\n")
69
+ pos = text.find(start)
70
+ if pos < 0:
71
+ err(f"{rel}: Der Anker '{start}' ist nicht mehr auffindbar. Ohne ihn prueft "
72
+ f"diese Pruefung nichts - sie wuerde leise bestehen (D-23)")
73
+ continue
74
+ if ende:
75
+ bis = text.find(ende, pos + len(start))
76
+ bereich = text[pos:bis if bis > 0 else len(text)]
77
+ else:
78
+ bis = text.find("\n", pos)
79
+ bereich = text[pos:bis if bis > 0 else len(text)]
80
+ for name, muster in K3_KATEGORIEN:
81
+ if not re.search(muster, bereich):
82
+ err(f"{rel}: Die K3-Liste nennt die Kategorie '{name}' nicht. Alle acht "
83
+ f"Kategorien aus Abschnitt 2.1 von "
84
+ f"{KERN}/framework/core/02-privacy.md gelten in jeder Fassung; eine "
85
+ f"Kurzform darf kuerzer formulieren, aber keine Kategorie weglassen "
86
+ f"(D-52)")
87
+ m = K3_BEDINGUNG_RE.search(bereich)
88
+ if m:
89
+ err(f"{rel}: Die K3-Liste traegt eine Bedingung ('{m.group(0)}'). Die "
90
+ f"Kategorien sind unbedingt und ebenenfest - kein Overlay, keine "
91
+ f"Datenschutzpruefung und kein Ausnahmeprozess kann sie freigeben "
92
+ f"(governance/PRIORITY_HIERARCHY.md Regel 2.4, D-52)")
93
+
94
+
95
+ # Pruefung 30: Die Grenzfalltabelle ist vollstaendig und deckt jede Entscheidung ab.
96
+ #
97
+ # Das Abnahmekriterium des Reviews zu B07 und B09 verlangt Beispiele mit erwarteter
98
+ # Entscheidung. Eine Tabelle, in der eine Spalte leer bleibt oder eine Entscheidung
99
+ # unbelegt ist, sieht aus wie ein Nachweis und ist keiner. Die Anzahl steht im Steckbrief
100
+ # und wird nachgezaehlt: In diesem Projekt war eine Zahl schon oefter zu klein.
101
+ GRENZFALL_DATEI = KERN + "/tests/EDGE_CASES.md"
102
+ GRENZFALL_SPALTEN = 7
103
+ GRENZFALL_ENTSCHEIDUNGEN = ("D-52", "D-53", "D-54", "D-55", "D-56", "D-59",
104
+ "D-61", "D-63", "D-64", "D-66", "D-67", "D-72")
105
+
106
+
107
+ def check_grenzfaelle(root: str) -> None:
108
+ """Pruefung 30: Vollstaendigkeit der Grenzfalltabelle (CR-2026-052, CR-2026-053)."""
109
+ pfad = os.path.join(root, GRENZFALL_DATEI.replace("/", os.sep))
110
+ if not os.path.isfile(pfad):
111
+ err(f"{GRENZFALL_DATEI}: fehlt. Die entschiedenen Regelkonflikte brauchen ihre "
112
+ f"Grenzfaelle als Referenz - das ist das Abnahmekriterium des Reviews zu B07 "
113
+ f"und B09")
114
+ return
115
+ text = read(pfad)
116
+ m = re.search(r"\|\s*Anzahl der Grenzfälle\s*\|\s*(\d+)\s*\|", text)
117
+ if not m:
118
+ err(f"{GRENZFALL_DATEI}: Der Steckbrief nennt keine Anzahl der Grenzfaelle. Ohne "
119
+ f"sie faellt eine geloeschte Zeile nicht auf")
120
+ return
121
+ erwartet = int(m.group(1))
122
+ zeilen = [z for z in text.splitlines() if re.match(r"\|\s*G-\d+\s*\|", z)]
123
+ if len(zeilen) != erwartet:
124
+ err(f"{GRENZFALL_DATEI}: {len(zeilen)} Grenzfallzeilen, der Steckbrief nennt "
125
+ f"{erwartet}. Eine Zahl, die nicht stimmt, ist kein Nachweis")
126
+ for zeile in zeilen:
127
+ zellen = tabellenzellen(zeile)
128
+ kennung = zellen[0] if zellen else "?"
129
+ if len(zellen) != GRENZFALL_SPALTEN:
130
+ err(f"{GRENZFALL_DATEI}: Grenzfall {kennung} hat {len(zellen)} Spalten statt "
131
+ f"{GRENZFALL_SPALTEN} (Nr., Grenzfall, Entscheidung, Betriebsmodus, "
132
+ f"Kontrollstufe, Rollen, Fundstelle)")
133
+ continue
134
+ for nr, zelle in enumerate(zellen, 1):
135
+ if not zelle or zelle in ("-", "–"):
136
+ err(f"{GRENZFALL_DATEI}: Grenzfall {kennung}, Spalte {nr} ist leer. Jede "
137
+ f"Spalte ist Teil der Einstufung; eine leere Spalte laesst offen, was "
138
+ f"gilt")
139
+ if TBD_RE.search(zelle):
140
+ err(f"{GRENZFALL_DATEI}: Grenzfall {kennung}, Spalte {nr} traegt einen "
141
+ f"offenen <TBD>-Wert. Ein Grenzfall ohne Entscheidung ist keiner")
142
+ for d in GRENZFALL_ENTSCHEIDUNGEN:
143
+ if not any(d in z for z in zeilen):
144
+ err(f"{GRENZFALL_DATEI}: Keine Grenzfallzeile verweist auf {d}. Jede "
145
+ f"entschiedene Auslegungsfrage braucht mindestens einen Grenzfall, sonst "
146
+ f"ist das Abnahmekriterium des Reviews nicht eingeloest")
147
+
148
+
149
+ # ---------------------------------------------------------------------------
150
+ # Pruefung 44: Das Register der Uebungspraeparationen und die Vorbedingungen des
151
+ # Testkatalogs decken sich
152
+ # ---------------------------------------------------------------------------
153
+ #
154
+ # ANLASS. Gemessen am 2026-09-14 (CR-2026-067, Befund 2): onboarding/exercises/README.md
155
+ # verlangte DREI Koeder. Abgezaehlt gegen die Vorbedingungen des Testkatalogs braucht ein
156
+ # fahrbares Uebungsrepositorium SIEBEN Praeparationen fuer NEUN Testfaelle - dazu eine
157
+ # .env-Testdatei (FW-DS-02), einen praeparierten Codekommentar (FW-PI-02), eine Injektion
158
+ # in einer Testdatei (SK-006-N04) und einen Regeltext mit bewusstem Widerspruch
159
+ # (FW-KO-03). Keine der vier stand in der Liste, und der Katalog fuehrte alle neun Faelle
160
+ # als 'offen', also als fahrbar. EINE ZUSAGE OHNE DEN MECHANISMUS DAHINTER - der
161
+ # wiederkehrende Befundtyp dieses Projekts, diesmal an seinem eigenen Pruefstand.
162
+ #
163
+ # WIE DIE ZUORDNUNG ENTSTEHT (E5). Jede Praeparation traegt eine Kennung UEB-NN. Sie steht
164
+ # im Register in der ersten Spalte und in der Vorbedingungszelle jedes Testfalls, der sie
165
+ # braucht. Der Vergleich laeuft ueber die Kennung und ist damit exakt - nicht ueber
166
+ # Stichwoerter wie "Koeder" oder "praepariert", die Pruefung 29 als Bauform schon einmal
167
+ # teuer bezahlt hat: Sie erkennt nur bekannte Bedingungswoerter.
168
+ #
169
+ # WARUM UEB- UND NICHT P1 (E5). P1 und P3 sind im Kern als Prinzipienkennungen vergeben
170
+ # (framework/core/05-working-model.md). Eine zweite Bedeutung desselben Zeichens in
171
+ # derselben Dokumentfamilie ist ein Fehler, den dieses Projekt schon gemacht hat: Am
172
+ # 2026-09-13 trug eine Gegenprobe die synthetische Kennung G-18 - dieselbe, die 0.36.0
173
+ # wirklich vergeben hat.
174
+ #
175
+ # BEIDE RICHTUNGEN (E6). Eine Kennung im Katalog ohne Registereintrag ist ein Tippfehler
176
+ # oder eine unregistrierte Praeparation; eine Kennung im Register, die kein Testfall
177
+ # braucht, ist eine tote Praeparation, die jemand pflegt. Die dezentralen TESTS.md zaehlen
178
+ # mit - Verfahren Nr. 6 des Katalogs erklaert sie zu seinem Teil.
179
+ #
180
+ # GRENZE, UND SIE IST DIE WICHTIGE. Diese Pruefung faengt NICHT den Fall, der sie
181
+ # ausgeloest hat: einen Testfall, der eine Praeparation braucht und keine Kennung nennt.
182
+ # Sie verhindert seine WIEDERHOLUNG nur, soweit die Kennung gesetzt wird - dieselbe
183
+ # Ehrlichkeit wie Gegenstand 2 von Pruefung 38 (er zaehlt Deklarationen, nicht
184
+ # Richtigkeit) und wie Pruefung 40 (sie zaehlt Nennungen, nicht Pruefungen).
185
+ #
186
+ # ZWEITE GRENZE. Sie gleicht zwei Register ab, nicht ein Register gegen die Wirklichkeit.
187
+ # Ob die Praeparation UEB-02 im Uebungsrepositorium wirklich liegt, kann kein Validator
188
+ # DIESES Repositoriums feststellen: Das Uebungsrepositorium liegt ausserhalb. Das ist eine
189
+ # Enthaltung, keine Stille.
190
+ UEB_REGISTER = "onboarding/exercises/README.md"
191
+ UEB_ANKER = "### Register der Präparationen"
192
+ UEB_KENNUNG = re.compile(r"\bUEB-\d{2}\b")
193
+ # Gegenstand 3 (D-131). Die Spalte wird ueber ihre UEBERSCHRIFT gefunden, nicht ueber
194
+ # ihre Nummer: Eine Erkennungsregel fuer eine Dokumentstruktur gehoert an die Stellung,
195
+ # nicht an eine Zaehlung, die die naechste eingeschobene Spalte verschiebt.
196
+ UEB_BELEGSPALTE = "Wie sie belegt ist"
197
+ # Gegenstand 4 (D-173). Die Spalten werden ueber ihre UEBERSCHRIFT gefunden, die
198
+ # Testfallspalte des Registers ist die LETZTE. Der Zuschnitt trennt die Vorbedingung
199
+ # von ihrer Geschichte am ersten Vermerkzeichen: Was davor steht, ist die Bedingung;
200
+ # was dahinter steht, ist ihre Herkunft. FW-NE-02 nennt UEB-06 hinter dem Vermerk
201
+ # ("UEB-08 verdraengt UEB-06"), ohne es zu verlangen - wer die ganze Zelle liest,
202
+ # meldet diese Zeile mit.
203
+ UEB_VORBEDINGUNGSSPALTE = "Vorbedingung"
204
+ UEB_VERMERK_RE = re.compile("[\U0001F534\U0001F7E2\U0001F195⚠]")
205
+ UEB_FALL_RE = re.compile(r"\b(?:FW|SK|RE)-[A-Z0-9-]+\b")
206
+
207
+
208
+ def _vor_dem_vermerk(zelle: str) -> str:
209
+ """Der Teil einer Tabellenzelle vor dem ersten Vermerkzeichen."""
210
+ treffer = UEB_VERMERK_RE.search(zelle)
211
+ return zelle[:treffer.start()] if treffer else zelle
212
+
213
+
214
+ def _ueb_gebraucht_je_vorbedingung(root: str) -> dict:
215
+ """{UEB-NN: {Testfallkennung}} - nur aus der Vorbedingungsspalte, vor dem Vermerk."""
216
+ raus: dict = {}
217
+ for rel in _ueb_katalogdateien(root):
218
+ pfad = os.path.join(root, *rel.split(os.sep))
219
+ if not os.path.exists(pfad):
220
+ continue
221
+ i_vb = -1
222
+ for zeile in read(pfad).splitlines():
223
+ if not zeile.lstrip().startswith("|"):
224
+ continue
225
+ if UEB_VORBEDINGUNGSSPALTE in zeile and "Prüfmethode" in zeile:
226
+ i_vb = _tabellenspalte(zeile, UEB_VORBEDINGUNGSSPALTE)
227
+ continue
228
+ if i_vb < 0:
229
+ continue
230
+ zellen = tabellenzellen(zeile)
231
+ if len(zellen) <= i_vb:
232
+ continue
233
+ fall = UEB_FALL_RE.match(zellen[0].strip("`* "))
234
+ if not fall:
235
+ continue
236
+ for kennung in UEB_KENNUNG.findall(_vor_dem_vermerk(zellen[i_vb])):
237
+ raus.setdefault(kennung, set()).add(fall.group(0))
238
+ return raus
239
+
240
+
241
+ def check_praeparationsregister(root: str) -> None:
242
+ """Pruefung 44 (D-93): Registrierte Praeparationen und gebrauchte decken sich."""
243
+ pfad = os.path.join(root, KERN, *UEB_REGISTER.split("/"))
244
+ if not os.path.exists(pfad):
245
+ err(f"{KERN}/{UEB_REGISTER}: fehlt. Prüfung 44 hält dort das Register der "
246
+ f"Übungspräparationen gegen die Vorbedingungen des Testkatalogs (D-93)")
247
+ return
248
+ text = read(pfad)
249
+ if UEB_ANKER not in text:
250
+ err(f"{KERN}/{UEB_REGISTER}: führt kein Register mehr (gesucht: "
251
+ f"'{UEB_ANKER}'). Prüfung 44 hat ihren Gegenstand verloren und bestünde "
252
+ f"sonst leise – die Überschrift ist Teil des Nachweises (D-23, D-93)")
253
+ return
254
+ registriert = set(UEB_KENNUNG.findall(text.split(UEB_ANKER, 1)[1]))
255
+ if not registriert:
256
+ err(f"{KERN}/{UEB_REGISTER}: das Register führt keine einzige Kennung der Form "
257
+ f"UEB-NN – Prüfung 44 hätte nichts zu vergleichen (D-93)")
258
+ return
259
+
260
+ gebraucht: dict = {}
261
+ for rel in _ueb_katalogdateien(root):
262
+ pfad_k = os.path.join(root, *rel.split(os.sep))
263
+ if not os.path.exists(pfad_k):
264
+ continue
265
+ for kennung in UEB_KENNUNG.findall(read(pfad_k)):
266
+ gebraucht.setdefault(kennung, set()).add(rel.replace(os.sep, "/"))
267
+
268
+ for kennung in sorted(set(gebraucht) - registriert):
269
+ stellen = ", ".join(sorted(gebraucht[kennung]))
270
+ err(f"{stellen}: nennt die Präparation {kennung}, die das Register in "
271
+ f"{KERN}/{UEB_REGISTER} nicht führt. Eine Vorbedingung, die auf eine "
272
+ f"unregistrierte Präparation zeigt, ist eine Zusage ohne den Mechanismus "
273
+ f"dahinter – der Testfall gilt als fahrbar, und niemand weiß, was "
274
+ f"herzustellen ist (D-93)")
275
+ for kennung in sorted(registriert - set(gebraucht)):
276
+ err(f"{KERN}/{UEB_REGISTER}: führt die Präparation {kennung}, die kein Testfall "
277
+ f"nennt – weder der Testkatalog noch ein dezentrales Testblatt. Eine "
278
+ f"Präparation, die niemand braucht, wird gepflegt und nicht benutzt; "
279
+ f"entweder trägt ein Testfall sie in seine Vorbedingung ein, oder sie fällt "
280
+ f"aus dem Register (D-93)")
281
+
282
+ # --- Gegenstand 4: die zeilenweise Deckung (D-173) ------------------------------
283
+ # Gegenstand 1 und 2 vergleichen MENGEN ueber die ganze Datei. Sie bestehen auch
284
+ # dann, wenn die Kennung in der Ergebniszelle statt in der Vorbedingung steht -
285
+ # und dann sagt genau die Spalte, die dem Laufenden sagt, was herzustellen ist,
286
+ # es nicht. 0.64.0 hat fuenfzehn Blattzellen hergerichtet und die Kennung dreimal
287
+ # nur in den gruenen Vermerk der Ergebniszelle geschrieben; die Gegenrichtung
288
+ # fehlte zweimal, und das vollstaendigere Register lag AUSSERHALB des
289
+ # Repositoriums (im Mentorenblatt des Uebungsrepositoriums).
290
+ zeilen_reg = [z for z in text.split(UEB_ANKER, 1)[1].split("\n")
291
+ if z.strip().startswith("|")]
292
+ gebraucht_vb = _ueb_gebraucht_je_vorbedingung(root)
293
+ for zeile in zeilen_reg:
294
+ zellen = tabellenzellen(zeile)
295
+ treffer = UEB_KENNUNG.findall(zellen[0]) if zellen else []
296
+ if not treffer:
297
+ continue
298
+ kennung = treffer[0]
299
+ genannt = set(UEB_FALL_RE.findall(zellen[-1])) if len(zellen) > 1 else set()
300
+ misst = gebraucht_vb.get(kennung, set())
301
+ for fall in sorted(genannt - misst):
302
+ err(f"{KERN}/{UEB_REGISTER}: die Zeile {kennung} nennt den Testfall {fall}, "
303
+ f"dessen Vorbedingungszelle {kennung} nicht führt. Die Spalte, die sagt, "
304
+ f"was vor dem Lauf herzustellen ist, sagt es damit nicht – eine Nennung "
305
+ f"in der Ergebniszelle erreicht den Laufenden zu spät (D-93, D-173)")
306
+ for fall in sorted(misst - genannt):
307
+ err(f"{KERN}/{UEB_REGISTER}: der Testfall {fall} nennt {kennung} in seiner "
308
+ f"Vorbedingung, die Registerzeile {kennung} führt ihn aber nicht. Ein "
309
+ f"Register, das seinen Gegenstand nicht führt, ist eine Auswahl – und "
310
+ f"sie ist immer zu klein, nie zu groß (D-93, D-173)")
311
+
312
+ # --- Gegenstand 3: die Belegzelle (D-131) --------------------------------------
313
+ # UEB-06 stand dreizehn Releases lang im Register und stellte seinen Gegenstand
314
+ # nicht her. Der Eintrag behauptete, der Testbefehl gebe die Anweisung aus; gemessen
315
+ # hat das niemand, und er tut es nicht. Ein Vorhandensein belegt sich selbst, ein
316
+ # Fehlen nicht - und eine Praeparation, deren Gegenstand erst durch einen Lauf
317
+ # entsteht, ist ein Fehlen, solange niemand den Lauf gefahren hat.
318
+ zeilen = [z for z in text.split(UEB_ANKER, 1)[1].split("\n") if z.strip().startswith("|")]
319
+ kopf = next((z for z in zeilen if UEB_BELEGSPALTE in z), None)
320
+ if kopf is None:
321
+ err(f"{KERN}/{UEB_REGISTER}: die Registertabelle führt keine Spalte "
322
+ f"'{UEB_BELEGSPALTE}'. Gegenstand 3 der Prüfung 44 hat seinen Anker verloren "
323
+ f"und bestünde sonst leise – die Spaltenüberschrift ist Teil des Nachweises "
324
+ f"(D-23, D-131)")
325
+ return
326
+ spalte = tabellenzellen(kopf).index(UEB_BELEGSPALTE)
327
+ for zeile in zeilen:
328
+ zellen = tabellenzellen(zeile)
329
+ treffer = UEB_KENNUNG.findall(zellen[0]) if zellen else []
330
+ if not treffer:
331
+ continue
332
+ if len(zellen) <= spalte or not zellen[spalte]:
333
+ err(f"{KERN}/{UEB_REGISTER}: die Zeile {treffer[0]} führt keine Belegzelle "
334
+ f"('{UEB_BELEGSPALTE}'). Ein Registereintrag ohne Beleg ist eine Zusage "
335
+ f"über einen Mechanismus, den niemand ausgeführt hat – genau der Fall "
336
+ f"von UEB-06, der dreizehn Releases lang einen Testfall unfahrbar "
337
+ f"gehalten hat (D-131)")
338
+
339
+
340
+
341
+ # --- Pruefung 49 -------------------------------------------------------------------
342
+ # Die Marken stammen aus den Skillquellen des Kerns, nicht aus einer gepflegten Liste.
343
+ # 'triggers' ist ein Kernbegriff; die Abbildung auf das Clientfeld leistet install.py
344
+ # (bei claude-code 'disable-model-invocation'). Diese Pruefung fragt nach der QUELLE,
345
+ # damit sie unabhaengig vom installierten Pack dasselbe sagt.
346
+ SKILL_TRIGGER_RE = re.compile(r"^triggers:\s*$(.*?)(?=^\S|\Z)", re.M | re.S)
347
+
348
+
349
+ def _skills_ohne_modellaufruf(root: str) -> list:
350
+ """Namen der Kernskills, deren Quelle 'triggers' ohne '- model' fuehrt, sortiert.
351
+
352
+ Ein solcher Skill ist nur ueber den ausdruecklichen Aufruf einer Person erreichbar;
353
+ das Modell darf ihn nicht von sich aus waehlen. Gemessen am 2026-09-18: neun von
354
+ zwoelf (CR-2026-085, D-146).
355
+ """
356
+ namen = []
357
+ for basis in (os.path.join(root, KERN, "framework", "skills"),):
358
+ if not os.path.isdir(basis):
359
+ continue
360
+ for name in sorted(os.listdir(basis)):
361
+ pfad = os.path.join(basis, name, "SKILL.md")
362
+ if not os.path.isfile(pfad):
363
+ continue
364
+ text = read(pfad)
365
+ if not text.startswith("---"):
366
+ continue
367
+ ende = text.find("---", 3)
368
+ if ende < 0:
369
+ continue
370
+ m = SKILL_TRIGGER_RE.search(text[3:ende])
371
+ if m and "- model" not in m.group(1):
372
+ namen.append(name)
373
+ return namen
374
+
375
+
376
+ # Gegenstand 2 (D-172). Die Uebungen und ihre Skills stammen aus der Uebungsdatei,
377
+ # nicht aus einer gepflegten Liste: Wer eine Uebung um einen Schritt erweitert, soll
378
+ # nicht daran denken muessen, eine Liste im Validator nachzuziehen.
379
+ P49_UEBUNGSDATEI = KERN + "/onboarding/exercises/EXERCISES.md"
380
+ P49_UEBUNG_RE = re.compile("Ü\\d[a-z]?")
381
+ P49_UEBUNG_KOPF_RE = re.compile(r"^##\s+(Ü\d)\b")
382
+
383
+
384
+ def _uebungsskills(root: str) -> dict:
385
+ """{UEn: [Skillnamen]} - abgeleitet aus den Abschnitten der Uebungsdatei.
386
+
387
+ Gelesen werden die Skillaufrufe der Form /name im Abschnitt einer Uebung. Die
388
+ Uebungsdatei schreibt sie durchgaengig so - sie beschreibt den Ablauf, den eine
389
+ PERSON geht, und genau das ist der Grund, aus dem ein Ausloeser, der sich auf sie
390
+ beruft, den Aufruf nicht verschweigen darf.
391
+ """
392
+ pfad = os.path.join(root, *P49_UEBUNGSDATEI.split("/"))
393
+ if not os.path.isfile(pfad):
394
+ err(f"{P49_UEBUNGSDATEI}: fehlt. Gegenstand 2 der Prüfung 49 leitet die Skills "
395
+ f"jeder Übung von dort ab und hat seinen Gegenstand verloren; er bestünde "
396
+ f"sonst leise (D-23, D-172)")
397
+ return {}
398
+ raus: dict = {}
399
+ aktuell = None
400
+ for zeile in read(pfad).splitlines():
401
+ kopf = P49_UEBUNG_KOPF_RE.match(zeile.strip())
402
+ if kopf:
403
+ aktuell = kopf.group(1)
404
+ raus.setdefault(aktuell, [])
405
+ continue
406
+ if aktuell is None:
407
+ continue
408
+ for treffer in re.findall(r"/(fw-[a-z0-9-]+)", zeile):
409
+ if treffer not in raus[aktuell]:
410
+ raus[aktuell].append(treffer)
411
+ if not raus:
412
+ err(f"{P49_UEBUNGSDATEI}: führt keinen Abschnitt der Form '## Ü<n> …'. "
413
+ f"Gegenstand 2 der Prüfung 49 hat seinen Anker verloren und bestünde sonst "
414
+ f"leise (D-23, D-172)")
415
+ return raus
416
+
417
+
418
+ def check_skillaufruf_im_katalog(root: str) -> None:
419
+ """Pruefung 49 (D-146): Ein nicht modellaufrufbarer Skill steht als /name im Ausloeser."""
420
+ namen = _skills_ohne_modellaufruf(root)
421
+ if not namen:
422
+ err(f"{KERN}/framework/skills/: kein Skill mit 'triggers' ohne '- model' gefunden – "
423
+ f"Prüfung 49 leitet ihre Marken daraus ab und hat ihren Gegenstand verloren; "
424
+ f"sie bestünde sonst leise (D-23)")
425
+ return
426
+ uebungsskills = _uebungsskills(root)
427
+ alle_skills = sorted({s for liste in uebungsskills.values() for s in liste} | set(namen))
428
+ dateien = [os.path.join(root, KERN, "tests", "TEST_CATALOG.md")]
429
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
430
+ os.path.join(root, KERN, "framework", "role-packs")):
431
+ for wurzel, _, files in os.walk(basis):
432
+ if "TESTS.md" in files:
433
+ dateien.append(os.path.join(wurzel, "TESTS.md"))
434
+ for pfad in sorted(dateien):
435
+ if not os.path.isfile(pfad):
436
+ continue
437
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
438
+ i_ein = i_pm = -1
439
+ for nr, zeile in enumerate(read(pfad).splitlines(), 1):
440
+ if not zeile.lstrip().startswith("|"):
441
+ continue
442
+ if "Prüfmethode" in zeile and "Eingabe" in zeile:
443
+ i_ein = _tabellenspalte(zeile, "Eingabe")
444
+ i_pm = _tabellenspalte(zeile, "Prüfmethode")
445
+ continue
446
+ if i_ein < 0 or i_pm < 0:
447
+ continue
448
+ zellen = [z.strip() for z in zeile.strip().strip("|").split("|")]
449
+ if len(zellen) <= max(i_ein, i_pm):
450
+ continue
451
+ if "sitzung" not in zellen[i_pm]:
452
+ continue
453
+ ausloeser = zellen[i_ein]
454
+ for skill in namen:
455
+ # Nur die nackte Nennung ist ein Befund: '/name' ist der Aufruf selbst.
456
+ for m in re.finditer(r"(?<![/\w-])" + re.escape(skill) + r"(?![\w-])", ausloeser):
457
+ err(f"{rel}:{nr}: Der Auslöser nennt '{skill}' ohne den ausdrücklichen "
458
+ f"Aufruf. Die Quelle des Skills führt 'triggers' ohne '- model' – "
459
+ f"das Modell darf ihn nicht von sich aus wählen, und ein "
460
+ f"nicht-interaktiver Lauf bekommt eine Abweisung statt des Ablaufs. "
461
+ f"Schreibe '/{skill}' (D-146)")
462
+ break
463
+ # --- Gegenstand 2 (D-172): der Ausloeser, der eine UEBUNG nennt ---------
464
+ # Er nennt keinen Skill und erbt doch welche. FW-PO-02 verweist auf UE3,
465
+ # und UE3 geht durch vier Skills, die das Modell nicht waehlen darf.
466
+ # SCHMAL, und das ist Absicht: Gefragt wird, ob UEBERHAUPT ein
467
+ # ausdruecklicher Aufruf dasteht - nicht, ob es der richtige oder ob es
468
+ # alle sind. FW-SC-01 nennt UE3 und ruft bewusst nur deren dritten Schritt
469
+ # auf; eine Pruefung kann Zuschnitt nicht von Vergesslichkeit
470
+ # unterscheiden. Der erste Entwurf haette FW-SC-01 dreimal gemeldet.
471
+ # Ein ausdruecklicher Aufruf ist '/<skillname>', nicht irgendein Schraegstrich:
472
+ # Der Ausloeser von FW-PO-02 nennt einen PFAD, und der erste Entwurf hat
473
+ # '/onboarding' fuer einen Aufruf gehalten und nichts gemeldet.
474
+ if any(f"/{s}" in ausloeser for s in alle_skills):
475
+ continue
476
+ # Ue6c ist ein Teil von Ue6 - die Abschnittsueberschrift traegt nur die
477
+ # Ziffer, der Verweis zusaetzlich einen Buchstaben.
478
+ for uebung in sorted({t[:2] for t in P49_UEBUNG_RE.findall(ausloeser)}):
479
+ geerbt = sorted(set(uebungsskills.get(uebung, ())) & set(namen))
480
+ if not geerbt:
481
+ continue
482
+ err(f"{rel}:{nr}: Der Auslöser nennt die Übung {uebung} und damit "
483
+ f"deren Ablauf, der durch {', '.join(geerbt)} geht – Skills, deren "
484
+ f"Quelle 'triggers' ohne '- model' führt. Der Auslöser enthält "
485
+ f"keinen einzigen ausdrücklichen Aufruf; ein nicht-interaktiver "
486
+ f"Lauf bekommt eine Abweisung statt des Ablaufs. Nenne den "
487
+ f"Einstiegsschritt als '/name' (D-172)")
488
+
489
+
490
+ # --- Pruefung 103: der Stand einer Ergebniszelle (CR-2026-160, D-472, K-61) --------------
491
+ # ANLASS, GEMESSEN am 2026-09-18: FW-KO-02 stand seit dem 10.09. auf 'bestanden', und drei
492
+ # der vier Befunde, die FW-KO-05 danach fand, lagen genau in seinem Gegenstand - entstanden
493
+ # NACH der Abnahme. Der Testfall war nicht falsch; sein Belegstand war veraltet, und nichts
494
+ # meldete es.
495
+ #
496
+ # DIE FORM: Eine Ergebniszelle kann ihren Stand tragen - '[Stand: ' + zwoelf Hexziffern +
497
+ # '; ' + die Pfade ihres Gegenstands relativ zum Kern, durch Komma getrennt + ']'. Der Wert
498
+ # ist sha256 ueber Pfad und Inhalt jeder Datei in der genannten Reihenfolge, Zeilenenden auf
499
+ # LF gebracht; der Messapparat schreibt ihn beim Eintragen (tests/erhebungen/apparat).
500
+ #
501
+ # WAS SIE LEISTET: Weicht der Gegenstand vom Stand ab, WARNT sie - eine Aenderung ist kein
502
+ # Fehler, sie verlangt eine Entscheidung (neu erheben oder den Stand bestaetigen). Nennt die
503
+ # Marke einen Pfad, den es nicht gibt, ist das ein FEHLER: ein Stand ohne Gegenstand belegt
504
+ # nichts. WAS SIE NICHT LEISTET: Eine Zelle ohne Marke bleibt ungeprueft; der Altbestand
505
+ # traegt keine, und ihn mit geratenen Gegenstaenden nachzutragen saehe wie ein Beleg aus.
506
+ P103_MARKE_RE = re.compile(r"\[Stand: ([0-9a-f]{12}); ([^\]]+)\]")
507
+
508
+
509
+ def stand_wert(kern: str, pfade: list) -> str | None:
510
+ """Der Stand ueber die Dateien eines Gegenstands - None, wenn eine fehlt."""
511
+ import hashlib
512
+ h = hashlib.sha256()
513
+ for pfad in pfade:
514
+ voll = os.path.join(kern, *pfad.split("/"))
515
+ if not os.path.isfile(voll):
516
+ return None
517
+ with open(voll, "rb") as fh:
518
+ inhalt = fh.read().replace(b"\r\n", b"\n")
519
+ h.update(pfad.encode("utf-8") + b"\n" + inhalt + b"\n")
520
+ return h.hexdigest()[:12]
521
+
522
+
523
+ def _p103_traeger(root: str) -> list:
524
+ kern = os.path.join(root, KERN)
525
+ dateien = [os.path.join(kern, "tests", "TEST_CATALOG.md")]
526
+ for basis in (os.path.join(kern, "framework", "skills"),
527
+ os.path.join(kern, "framework", "role-packs")):
528
+ for wurzel, _, files in os.walk(basis):
529
+ if "TESTS.md" in files:
530
+ dateien.append(os.path.join(wurzel, "TESTS.md"))
531
+ return [d for d in sorted(dateien) if os.path.exists(d)]
532
+
533
+
534
+ def check_ergebnisstand(root: str) -> None:
535
+ """Pruefung 103 (D-472): der Stand einer Ergebniszelle gegen ihren heutigen Gegenstand."""
536
+ kern = os.path.join(root, KERN)
537
+ for pfad in _p103_traeger(root):
538
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
539
+ for nr, zeile in enumerate(read(pfad).splitlines(), 1):
540
+ for m in P103_MARKE_RE.finditer(zeile):
541
+ gegenstand = [p.strip().strip("`") for p in m.group(2).split(",") if p.strip()]
542
+ wert = stand_wert(kern, gegenstand)
543
+ if wert is None:
544
+ err(f"{rel}:{nr}: die Standmarke nennt einen Gegenstand, den es nicht gibt "
545
+ f"({', '.join(gegenstand)}) – ein Stand ohne Gegenstand belegt nichts (D-472)")
546
+ elif wert != m.group(1):
547
+ warn(f"{rel}:{nr}: Belegstand veraltet – der Gegenstand "
548
+ f"({', '.join(gegenstand)}) hat sich seit der Messung geändert "
549
+ f"(Stand {m.group(1)}, heute {wert}); neu erheben oder den Stand "
550
+ f"bestätigen (D-472, K-61)")
551
+
552
+
553
+ # --- Pruefung 61: Das Pruefmittelwort stammt aus dem Vokabular ----------------------
554
+ #
555
+ # ANLASS (D-181). Die dreizehn Testblaetter fuehrten bis 0.67.0 das Wort `manuell` -
556
+ # 87 von 87 Zellen. Im Vokabular von TEST_CATALOG.md Punkt 3 steht es nicht; es ist das
557
+ # ADJEKTIV aus der Definition von `sitzung` ("manuelle KI-Testsitzung nach Testblatt"),
558
+ # zum Methodennamen befoerdert. Jedes Blatt erklaerte es in seinem eigenen Vorspann -
559
+ # und damit war es fuer jeden Leser richtig und fuer jeden Zaehler unsichtbar.
560
+ #
561
+ # WAS DAS GEKOSTET HAT, IST GEMESSEN. Zwei Pruefungen laufen ausdruecklich ueber die
562
+ # dreizehn Blaetter und filtern auf `sitzung`: Pruefung 49 (D-146, seit 0.60.0) und
563
+ # Pruefung 60 (D-178, seit 0.66.0). Beide hatten dort NULL Gegenstand. Nach der
564
+ # Umstellung meldete Pruefung 60 beim ersten Lauf ZWANZIG Zellen - in genau den drei
565
+ # Blaettern, deren Skill einen Befehl ausfuehrt (fw-change-small, fw-refactor, fw-tests).
566
+ # Das ist die Bauform "Die Regel als Ausfuellschlitz" (0.61.0) eine Ebene hoeher: Nicht
567
+ # die Regel stand in zwei Ausdrucksformen, sondern ihr GEGENSTAND.
568
+ #
569
+ # GRENZE, UND SIE STEHT HIER. Geprueft wird das ERSTE WORT der Zelle gegen eine feste
570
+ # Menge. Ein Zusatz dahinter bleibt zulaessig - der zentrale Katalog fuehrt
571
+ # "sitzung + `validate-output.py`" und "skript+sitzung", die Blaetter
572
+ # "sitzung + Skript `validate-output.py --skill ...`". Nicht geprueft wird, ob das Wort
573
+ # das RICHTIGE ist; die Pruefung faengt ein fremdes Vokabular, nicht einen Irrtum.
574
+ P61_VOKABULAR = ("skript", "sitzung", "review")
575
+
576
+
577
+ def check_pruefmittel_vokabular(root: str) -> None:
578
+ """Pruefung 61 (D-181): Das Pruefmittelwort stammt aus dem Vokabular des Katalogs."""
579
+ dateien = [os.path.join(root, KERN, "tests", "TEST_CATALOG.md")]
580
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
581
+ os.path.join(root, KERN, "framework", "role-packs")):
582
+ for wurzel, _, files in os.walk(basis):
583
+ if "TESTS.md" in files:
584
+ dateien.append(os.path.join(wurzel, "TESTS.md"))
585
+ gesehen = 0
586
+ for pfad in sorted(dateien):
587
+ if not os.path.isfile(pfad):
588
+ continue
589
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
590
+ i_pm = -1
591
+ for nr, zeile in enumerate(read(pfad).splitlines(), 1):
592
+ if not zeile.lstrip().startswith("|"):
593
+ continue
594
+ if "Prüfmethode" in zeile and "Eingabe" in zeile:
595
+ i_pm = _tabellenspalte(zeile, "Prüfmethode")
596
+ continue
597
+ if i_pm < 0:
598
+ continue
599
+ zellen = [z.strip() for z in zeile.strip().strip("|").split("|")]
600
+ if len(zellen) <= i_pm or not zellen[0]:
601
+ continue
602
+ if set(zellen[0]) <= set("-:"):
603
+ continue
604
+ wert = zellen[i_pm]
605
+ if not wert:
606
+ continue
607
+ gesehen += 1
608
+ # Das erste Wort, abgeschnitten an Leerzeichen und am Pluszeichen.
609
+ kopf = wert.split(" ")[0].split("+")[0].strip()
610
+ if kopf in P61_VOKABULAR:
611
+ continue
612
+ err(f"{rel}:{nr}: Prüfmittel '{kopf}' steht nicht im Vokabular "
613
+ f"({', '.join(P61_VOKABULAR)}). Zwei Prüfungen filtern auf `sitzung` und "
614
+ f"verlieren mit einem fremden Wort ihren Gegenstand, ohne es zu melden "
615
+ f"(D-146, D-178, D-181)")
616
+ if gesehen == 0:
617
+ err(f"{KERN}/tests/: keine Zelle mit Prüfmethode gefunden – Prüfung 61 hat ihren "
618
+ f"Gegenstand verloren; sie bestünde sonst leise (D-23)")
619
+
620
+
621
+ # --- Pruefung 62: die Version der Ausgabevorlage (D-185) ---------------------------
622
+ #
623
+ # ANLASS. Der erste Buendellauf der Testblaetter (0.68.0) hat elf Sitzungslaeufe
624
+ # gefahren; einer davon meldete in einer Nebenbemerkung, die Attributtabelle seines
625
+ # Skills nenne Version 0.1.3 und der Kopf der Ausgabevorlage v0.1.1 - er hat die
626
+ # hoehere genommen und es dazugeschrieben. Nachgezaehlt: 13 Traeger mit woertlicher
627
+ # Version, 10 davon abweichend, 15 Fundstellen.
628
+ #
629
+ # DER ZUSCHNITT IST DIE GANZE DATEI, nicht nur Abschnitt 5. Die zweite Fundstelle
630
+ # zweier Skills steht in der Zeile "| Erstellt mit | <skill> vX.Y.Z |" - derselbe
631
+ # Fehler an einer Stelle, die ein Zuschnitt auf die Ueberschrift verfehlt haette.
632
+ # Die Steckbriefzeile selbst traegt ihre Version OHNE das fuehrende `v` und faellt
633
+ # deshalb nicht unter das Muster; sie ist die Quelle, nicht die Kopie.
634
+ def check_skillversion_vorlage(root: str) -> None:
635
+ """Pruefung 62 (D-185): Keine SKILL.md nennt eine Version woertlich."""
636
+ dateien = []
637
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
638
+ os.path.join(root, KERN, "framework", "role-packs")):
639
+ for wurzel, _, files in os.walk(basis):
640
+ if "SKILL.md" in files:
641
+ dateien.append(os.path.join(wurzel, "SKILL.md"))
642
+ gesehen = 0
643
+ for pfad in sorted(dateien):
644
+ gesehen += 1
645
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
646
+ text = read(pfad)
647
+ for nr, zeile in enumerate(text.splitlines(), 1):
648
+ for treffer in re.finditer(r"\bv\d+\.\d+\.\d+\b", zeile):
649
+ err(f"{rel}:{nr}: nennt die Version wörtlich ({treffer.group(0)}). Eine "
650
+ f"Zahl, die gepflegt werden muss, wird nicht gepflegt – gemessen "
651
+ f"waren zehn von dreizehn Trägern von ihrem eigenen Steckbrief "
652
+ f"abgewichen. Die Vorlage verweist auf den Steckbrief (D-185)")
653
+ if gesehen == 0:
654
+ err(f"{KERN}/framework/: keine SKILL.md gefunden – Prüfung 62 hat ihren "
655
+ f"Gegenstand verloren; sie bestünde sonst leise (D-23)")
656
+
657
+
658
+ # --- Pruefung 63: der Nummernverweis, der ins Leere zeigt (D-193) ------------------
659
+ #
660
+ # ANLASS. Der Vorbedingungsdurchgang von Buendel 2 (0.70.0) hat die achtzehn Zellen
661
+ # gegen den Bestand gehalten. Drei davon erwarten eine Bereinigung "nach
662
+ # 02-privacy.md Abschnitt 3.3" - und Abschnitt 3 jener Datei fuehrte zehn nummerierte
663
+ # REGELN und keine einzige Unterueberschrift. Nachgezaehlt gegen den unberuehrten
664
+ # Vorstand: 30 Verweise in 15 anweisenden Traegern auf vier Nummern (1.3, 3.3, 3.4,
665
+ # 3.5), die es als Abschnitt nicht gab.
666
+ #
667
+ # DIE FORM HATTE IN DERSELBEN DATEI ZWEI BEDEUTUNGEN. `Abschnitt 2.1` zeigte auf eine
668
+ # Ueberschrift, `Abschnitt 3.3` auf eine Listennummer, und 05-working-model.md fuehrt
669
+ # seine Querschnittsregeln laengst als `### 3.1` bis `### 3.6`. Das ZIELMODUL war der
670
+ # Ausreisser, nicht die dreissig Verweise: 0.70.0 hat die Ueberschriften nachgezogen
671
+ # und keinen der fuenfzehn Traeger angefasst. Eine Marke mit zwei Bedeutungen taugt
672
+ # weder als Bedingung noch als Entlastung.
673
+ #
674
+ # ZWEI AUSDRUCKSFORMEN, UND DIE ERSTE ZAEHLUNG KANNTE NUR EINE. Vier Testblaetter
675
+ # nennen das Ziel als blossen Dateinamen (`02-privacy.md`), nicht mit vollem Pfad -
676
+ # wer nur den vollen Pfad sucht, zaehlt 25 statt 30. Ein blosser Dateiname wird
677
+ # aufgeloest, wenn er im Kern GENAU EINMAL vorkommt; bei mehreren Treffern
678
+ # (README.md, TESTS.md, SKILL.md) wird nicht geraten.
679
+ #
680
+ # EINE bis-SPANNE IST MEHR ALS IHRE ENDEN. "Abschnitt 3.3 bis 3.5" nennt auch 3.4;
681
+ # wer nur die genannten Zahlen prueft, uebersieht die Mitte.
682
+ #
683
+ # AUFZEICHNUNGEN SIND AUSGENOMMEN (D-141), und die Liste dafuer ist keine neue:
684
+ # NEUTRAL_CHRONIK und NEUTRAL_FRIST. Ein Protokoll nennt den Stand seines Tages, und
685
+ # ein Aenderungsantrag den Gegenstand seines Eingriffs; beide nachtraeglich zu
686
+ # glaetten, zerstoerte die Nachvollziehbarkeit.
687
+ P63_VERWEIS = re.compile(
688
+ r"`(?P<pfad>[A-Za-z0-9_./-]*\.md)`[^`\n]{0,90}?Abschnitt\s+"
689
+ r"(?P<nummern>\d+(?:\.\d+)?(?:\s*(?:und|bis|,)\s*\d+(?:\.\d+)?)*)")
690
+ P63_UEBERSCHRIFT = re.compile(r"^#{2,6}\s+(\d+(?:\.\d+)*)\.?\s", re.M)
691
+ P63_AUSNAHMEN = NEUTRAL_CHRONIK + NEUTRAL_DOKUMENT
692
+
693
+
694
+ def _p63_nummern(roh: str) -> list:
695
+ """Die genannten Nummern, bis-Spannen innerhalb desselben Abschnitts aufgeloest."""
696
+ teile = re.split(r"\s*(und|bis|,)\s*", roh)
697
+ werte, i = [teile[0]], 1
698
+ while i + 1 < len(teile):
699
+ verbinder, naechste = teile[i], teile[i + 1]
700
+ a, b = werte[-1].split("."), naechste.split(".")
701
+ if verbinder == "bis" and len(a) == 2 and len(b) == 2 and a[0] == b[0] \
702
+ and int(b[1]) > int(a[1]):
703
+ werte += ["%s.%d" % (a[0], k) for k in range(int(a[1]) + 1, int(b[1]) + 1)]
704
+ else:
705
+ werte.append(naechste)
706
+ i += 2
707
+ return werte
708
+
709
+
710
+ def check_nummernverweis(root: str) -> None:
711
+ """Pruefung 63 (D-193): Ein Nummernverweis zeigt auf eine Ueberschrift des Ziels."""
712
+ kopf: dict = {}
713
+ nach_name: dict = {}
714
+ for path in iter_text_files(root):
715
+ if not path.endswith(".md"):
716
+ continue
717
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
718
+ if not rel.startswith(KERN + "/"):
719
+ continue
720
+ nach_name.setdefault(os.path.basename(rel), []).append(rel)
721
+ kopf[rel] = {m.group(1) for m in P63_UEBERSCHRIFT.finditer(read(path))}
722
+ if not kopf:
723
+ err(f"{KERN}/: keine Markdown-Traeger gefunden - Pruefung 63 hat ihren "
724
+ f"Gegenstand verloren; sie bestuende sonst leise (D-23)")
725
+ return
726
+ for rel in sorted(kopf):
727
+ if rel.startswith(P63_AUSNAHMEN) or os.path.basename(rel) in NEUTRAL_CHRONIK_BASENAMES:
728
+ continue
729
+ pfad = os.path.join(root, rel.replace("/", os.sep))
730
+ for i, zeile in enumerate(read(pfad).splitlines(), 1):
731
+ for m in P63_VERWEIS.finditer(zeile):
732
+ genannt = m.group("pfad")
733
+ if genannt.startswith(KERN + "/"):
734
+ ziel = genannt if genannt in kopf else None
735
+ else:
736
+ kandidaten = nach_name.get(os.path.basename(genannt), [])
737
+ ziel = kandidaten[0] if len(kandidaten) == 1 else None
738
+ if ziel is None:
739
+ continue
740
+ for num in _p63_nummern(m.group("nummern")):
741
+ if num in kopf[ziel]:
742
+ continue
743
+ err(f"{rel}:{i}: verweist auf '{ziel}' Abschnitt {num} - diese "
744
+ f"Nummer fuehrt dort keine Ueberschrift. Ein Nummernverweis "
745
+ f"zeigt auf einen Abschnitt, nicht auf eine Listennummer; "
746
+ f"dieselbe Form bedeutete in 02-privacy.md einmal das eine und "
747
+ f"einmal das andere, und dreissig Verweise in fuenfzehn "
748
+ f"anweisenden Traegern zeigten ins Leere (D-193)")
749
+
750
+
751
+ # --- Pruefung 64: Die Ausgabemarke, die der Skill nicht verlangt --------------------
752
+ #
753
+ # ANLASS (D-197). `K-74` hat am 2026-09-19 gezaehlt, dass `[HALT]` und `[RUECKFRAGE]`
754
+ # mit 145 Fundstellen im Kern stehen und in keinem Glossar erklaert sind. Die
755
+ # Entscheidung dazu hat einen groesseren Befund freigelegt, und zwar durch eine
756
+ # Frage, die die Zaehlung nicht gestellt hatte: In WELCHEM Abschnitt steht die Marke?
757
+ #
758
+ # `[RUECKFRAGE]` steht in KEINEM Abschnitt 5 und in KEINEM Abschnitt 6 der
759
+ # zwoelf Skills. Sie ist durchgehend HANDLUNGSMARKE - "Fall X | [RUECKFRAGE]"
760
+ # heisst `frage zurueck`, nicht `schreibe die Zeichenfolge`.
761
+ #
762
+ # `[HALT]` ist beides, je nach Skill: Ausgabemarke in `fw-plan`, `fw-bugfix-prepare`
763
+ # und `fw-change-small` (Abschnitt 5 UND 6), Handlungsmarke in den uebrigen neun.
764
+ # Und wo sie Abnahmekriterium ist, sagt Abschnitt 6 "ist ERKENNBAR", nicht "woertlich".
765
+ #
766
+ # GEMESSEN HAT ES EIN LAUF. `sk004n01` (Buendel 2, 2026-09-19) schrieb `[HALT]`
767
+ # woertlich - Abschnitt 6 von `fw-plan` sagt "der Skill endet mit [HALT]" - und
768
+ # `[RUECKFRAGE]` nicht. Der Lauf hat sich richtig verhalten; die Zelle verlangte mehr,
769
+ # als ihr Skill vorschreibt. Ueber alle dreizehn Blaetter: 18 Nennungen in 17 Zellen
770
+ # ungedeckt, 10 gedeckt.
771
+ #
772
+ # GEGENSTAND. Nennt die Spalte "Erwartetes Verhalten" oder "Unzulaessiges Verhalten"
773
+ # einer Zelle eine Ausgabemarke, fuehrt der im Ausloeser aufgerufene Skill sie in
774
+ # Abschnitt 5 (Ausgabeformat) oder Abschnitt 6 (Qualitaetskriterien).
775
+ #
776
+ # WARUM ABSCHNITT 5 UND 6 UND NICHT 3 ODER 7. Abschnitt 3 (Arbeitsschritte) und
777
+ # Abschnitt 7 (Fehlerbehandlung) sagen, WAS ZU TUN IST; Abschnitt 5 und 6 sagen, WAS
778
+ # IN DER ANTWORT STEHEN MUSS. Ein Zuschnitt ueber die ganze Datei haette jede Zelle
779
+ # durchgelassen und nichts gemessen - die Marke steht ja irgendwo in jedem Skill.
780
+ # Das ist die Trennlinie von Pruefung 60 an einer zweiten Stelle: Dort sagt das
781
+ # FRONTMATTER, was der Skill tut, und der Fliesstext, wovon er redet.
782
+ #
783
+ # GRENZE, UND SIE STEHT HIER. Geprueft wird die DECKUNG, nicht die Formulierung.
784
+ # Eine Zelle, die die Sache umschreibt und dabei etwas Falsches sagt, laeuft durch.
785
+ # ZWEITE GRENZE: Die LETZTE Zelle einer Zeile ist der Ergebnisstatus und damit eine
786
+ # Aufzeichnung (D-117, D-141) - ein Lauf, der die Marke geschrieben HAT, darf das
787
+ # dort berichten. Dieselbe Spalten-Ausnahme, die Pruefung 48 zieht.
788
+ P64_MARKEN = {
789
+ "[HALT]": re.compile(r"\[HALT\]"),
790
+ "[RÜCKFRAGE]": re.compile(r"\[R(?:Ü|UE)CKFRAGE\]"),
791
+ }
792
+
793
+
794
+ def _p64_skillabschnitte(root: str) -> dict:
795
+ """Skillname -> Menge der Marken, die Abschnitt 5 oder 6 seiner SKILL.md fuehrt.
796
+
797
+ Abgeleitet aus den Skillquellen, nicht gepflegt (dieselbe Regel wie bei
798
+ Pruefung 60 und 48): Wer eine Marke in ein Ausgabeformat aufnimmt, soll nicht
799
+ daran denken muessen, eine Liste im Validator nachzuziehen.
800
+ """
801
+ raus = {}
802
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
803
+ os.path.join(root, KERN, "framework", "role-packs")):
804
+ if not os.path.isdir(basis):
805
+ continue
806
+ for wurzel, _, files in os.walk(basis):
807
+ if "SKILL.md" not in files:
808
+ continue
809
+ text = read(os.path.join(wurzel, "SKILL.md")).replace("\r\n", "\n")
810
+ rumpf, aktuell = [], None
811
+ for zeile in text.split("\n"):
812
+ m = re.match(r"^##\s+(\d+)\.\s", zeile)
813
+ if m:
814
+ aktuell = int(m.group(1))
815
+ elif aktuell in (5, 6):
816
+ rumpf.append(zeile)
817
+ gefuehrt = {n for n, mu in P64_MARKEN.items() if mu.search("\n".join(rumpf))}
818
+ raus[os.path.basename(wurzel)] = gefuehrt
819
+ return raus
820
+
821
+
822
+ def check_ausgabemarke_gedeckt(root: str) -> None:
823
+ """Pruefung 64 (D-197): Die Zelle verlangt nur, was ihr Skill als Ausgabe fuehrt."""
824
+ skills = _p64_skillabschnitte(root)
825
+ if not any(skills.values()):
826
+ err(f"{KERN}/framework/skills/: keine SKILL.md fuehrt eine Ausgabemarke in "
827
+ f"Abschnitt 5 oder 6 - Pruefung 64 leitet ihren Gegenstand daraus ab und "
828
+ f"hat ihn verloren; sie bestuende sonst leise (D-23)")
829
+ return
830
+ dateien = [os.path.join(root, KERN, "tests", "TEST_CATALOG.md")]
831
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
832
+ os.path.join(root, KERN, "framework", "role-packs")):
833
+ for wurzel, _, files in os.walk(basis):
834
+ if "TESTS.md" in files:
835
+ dateien.append(os.path.join(wurzel, "TESTS.md"))
836
+ for pfad in sorted(dateien):
837
+ if not os.path.isfile(pfad):
838
+ continue
839
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
840
+ i_ein = i_erw = i_unz = -1
841
+ for nr, zeile in enumerate(read(pfad).splitlines(), 1):
842
+ if not zeile.lstrip().startswith("|"):
843
+ continue
844
+ if "Erwartetes Verhalten" in zeile and "Eingabe" in zeile:
845
+ i_ein = _tabellenspalte(zeile, "Eingabe")
846
+ i_erw = _tabellenspalte(zeile, "Erwartetes Verhalten")
847
+ i_unz = _tabellenspalte(zeile, "Unzulässiges Verhalten")
848
+ continue
849
+ if min(i_ein, i_erw, i_unz) < 0:
850
+ continue
851
+ zellen = [z.strip() for z in zeile.strip().strip("|").split("|")]
852
+ if len(zellen) <= max(i_ein, i_erw, i_unz):
853
+ continue
854
+ # Die letzte Zelle ist der Ergebnisstatus - eine Aufzeichnung (D-117)
855
+ pruefbar = " ".join(z for i, z in enumerate(zellen)
856
+ if i in (i_erw, i_unz) and i != len(zellen) - 1)
857
+ gefuehrt = set()
858
+ for skill, marken in skills.items():
859
+ if f"/{skill}" in zellen[i_ein]:
860
+ gefuehrt |= marken
861
+ for name, muster in P64_MARKEN.items():
862
+ if muster.search(pruefbar) and name not in gefuehrt:
863
+ err(f"{rel}:{nr}: verlangt die Ausgabemarke {name}, die der "
864
+ f"aufgerufene Skill weder im Ausgabeformat (Abschnitt 5) noch "
865
+ f"in den Qualitaetskriterien (Abschnitt 6) fuehrt. Dort steht "
866
+ f"sie als Anweisung, nicht als Ausgabe - die Zelle verlangt "
867
+ f"damit mehr, als der Skill vorschreibt, und ein Lauf, der "
868
+ f"anhaelt ohne die Zeichenfolge zu schreiben, faellt zu "
869
+ f"Unrecht durch (D-197). Die Sache statt der Marke verlangen "
870
+ f"- oder die Marke in Abschnitt 5 beziehungsweise 6 aufnehmen")
871
+
872
+ # --- Pruefung 65: Der Ergebnisstatus ohne Beleg -------------------------------------
873
+ #
874
+ # ANLASS. TEST_CATALOG.md Punkt 4 sagt: "Ein Ergebnisstatus ausser `offen` MUSS auf ein
875
+ # Protokoll verweisen." D-117 sagt: "Ein Ergebnisstatus nennt das gemessene Client Pack
876
+ # und dessen Produktversion." Beides steht normativ da - und KEINE der vierundsechzig
877
+ # Pruefungen setzte es durch. Gezaehlt am 2026-09-19 ueber alle 125 Ergebniszellen des
878
+ # zentralen Katalogs und der dreizehn Testblaetter: NULL Verstoesse. Die Regel trug
879
+ # bisher allein durch die Sorgfalt derer, die sie eingetragen haben.
880
+ #
881
+ # WARUM SIE IN EINEM RELEASE GEBAUT WIRD, IN DEM SIE NICHTS FINDET. Dieses Release
882
+ # traegt in einem Zug ACHTZEHN neue `bestanden` ein - der Zeitpunkt, an dem eine
883
+ # ungezaehlte Belegpflicht rutscht. Die Bewegung ist die Umkehrung von Pruefung 60:
884
+ # Dort hatte eine Pruefung zwei Releases lang keinen Gegenstand und meldete dann zwanzig
885
+ # Zellen; hier ist der Gegenstand vollstaendig und soll es bleiben.
886
+ #
887
+ # DREI GEGENSTAENDE:
888
+ # (a) Das Statuswort stammt aus dem Vokabular von Punkt 4 - ABGELEITET aus jener
889
+ # Zeile, nicht gepflegt (dieselbe Regel wie bei Pruefung 61, 64 und 48).
890
+ # (b) Ein Status ausser `offen` nennt ein Protokoll unter tests/protocols/.
891
+ # (c) Ein Status ausser `offen` nennt bei Pruefmethode `sitzung` ein Client Pack mit
892
+ # Produktversion. Die Packkennungen kommen aus .koolie/core/clients/, nicht aus
893
+ # einer Handliste - ein neues Pack bringt seine Kennung selbst mit (D-117).
894
+ #
895
+ # WARUM (c) NUR BEI `sitzung`. D-117 spricht von DYNAMISCHEN Tests. Ein Validatorlauf
896
+ # (`skript`) misst das Repositorium und keinen Client, ein Dokumentenreview (`review`)
897
+ # ebenso; beide brauchen kein Pack. Der Zusatz hinter dem Wort bleibt zulaessig
898
+ # (`sitzung + validate-output.py`), deshalb wird auf ENTHALTENSEIN geprueft und nicht
899
+ # auf das erste Wort - `skript+sitzung` traegt beide Methoden und damit beide Pflichten.
900
+ #
901
+ # GRENZE, UND SIE STEHT HIER. Geprueft wird die NENNUNG, nicht ihre Richtigkeit: Ein
902
+ # Verweis auf ein Protokoll, das den Fall gar nicht behandelt, laeuft durch, und eine
903
+ # Version, die nicht die gemessene ist, ebenso. Das bleibt review-Gegenstand (D-23) -
904
+ # dieselbe Enthaltung, die Pruefung 61 fuer das RICHTIGE Pruefmittelwort zieht.
905
+ P65_MARKE = "**Ergebnisstatus:**"
906
+ P65_PROTOKOLL = re.compile(r"tests/protocols/[^\s`)]+\.md")
907
+
908
+
909
+ def _p65_vokabular(root: str) -> list:
910
+ """Die zulaessigen Statuswoerter, abgeleitet aus TEST_CATALOG.md Punkt 4.
911
+
912
+ Nicht gepflegt: Wer das Vokabular dort erweitert, soll nicht daran denken muessen,
913
+ eine Liste im Validator nachzuziehen. Der Klammerzusatz ("(Referenz auf Befund)")
914
+ gehoert zur Erlaeuterung und nicht zum Wort.
915
+ """
916
+ pfad = os.path.join(root, KERN, "tests", "TEST_CATALOG.md")
917
+ if not os.path.isfile(pfad):
918
+ return []
919
+ for zeile in read(pfad).splitlines():
920
+ if P65_MARKE not in zeile:
921
+ continue
922
+ rest = zeile.split(P65_MARKE, 1)[1]
923
+ m = re.match(r"\s*(`[^`]+`(?:\s*/\s*`[^`]+`)*)", rest)
924
+ if not m:
925
+ return []
926
+ return [t.strip("` ").split("(")[0].strip() for t in m.group(1).split("/")]
927
+ return []
928
+
929
+
930
+ def _p65_packkennungen(root: str) -> list:
931
+ """Die Kennungen der Client Packs - aus dem Verzeichnis, nicht aus einer Liste."""
932
+ return [k for k, _d, _m in _client_packs(root)]
933
+
934
+
935
+ def check_ergebnisstatus_beleg(root: str) -> None:
936
+ """Pruefung 65 (D-202): Ein Ergebnisstatus ausser `offen` traegt seinen Beleg."""
937
+ vokabular = _p65_vokabular(root)
938
+ if not vokabular:
939
+ err(f"{KERN}/tests/TEST_CATALOG.md: das Vokabular des Ergebnisstatus ist unter "
940
+ f"Punkt 4 nicht mehr auffindbar - Pruefung 65 leitet es von dort ab und hat "
941
+ f"ihren Gegenstand verloren; sie bestuende sonst leise (D-23)")
942
+ return
943
+ packs = _p65_packkennungen(root)
944
+ pack_re = re.compile("(" + "|".join(re.escape(k) for k in packs) +
945
+ r")[^|]{0,60}?\d+\.\d+\.\d+") if packs else None
946
+ dateien = [os.path.join(root, KERN, "tests", "TEST_CATALOG.md")]
947
+ for basis in (os.path.join(root, KERN, "framework", "skills"),
948
+ os.path.join(root, KERN, "framework", "role-packs")):
949
+ for wurzel, _, files in os.walk(basis):
950
+ if "TESTS.md" in files:
951
+ dateien.append(os.path.join(wurzel, "TESTS.md"))
952
+ gesehen = 0
953
+ for pfad in sorted(dateien):
954
+ if not os.path.isfile(pfad):
955
+ continue
956
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
957
+ i_pm = i_st = -1
958
+ for nr, zeile in enumerate(read(pfad).splitlines(), 1):
959
+ if not zeile.lstrip().startswith("|"):
960
+ continue
961
+ if "Ergebnisstatus" in zeile and "Prüfmethode" in zeile:
962
+ i_pm = _tabellenspalte(zeile, "Prüfmethode")
963
+ i_st = _tabellenspalte(zeile, "Ergebnisstatus")
964
+ continue
965
+ if min(i_pm, i_st) < 0:
966
+ continue
967
+ zellen = [z.strip() for z in zeile.strip().strip("|").split("|")]
968
+ if len(zellen) <= max(i_pm, i_st) or not zellen[0]:
969
+ continue
970
+ if set(zellen[0]) <= set("-: "):
971
+ continue
972
+ # Die Betonung gehoert der Darstellung, nicht dem Wort.
973
+ status = zellen[i_st].lstrip("* ").strip()
974
+ if not status:
975
+ continue
976
+ gesehen += 1
977
+ wort = next((w for w in vokabular if status.startswith(w)), None)
978
+ if wort is None:
979
+ err(f"{rel}:{nr}: Ergebnisstatus beginnt nicht mit einem Wort des "
980
+ f"Vokabulars ({', '.join(vokabular)}) - TEST_CATALOG.md Punkt 4 "
981
+ f"fuehrt es abschliessend (D-202)")
982
+ continue
983
+ if wort == "offen":
984
+ continue
985
+ if not P65_PROTOKOLL.search(status):
986
+ err(f"{rel}:{nr}: Ergebnisstatus '{wort}' nennt kein Protokoll unter "
987
+ f"{KERN}/tests/protocols/. TEST_CATALOG.md Punkt 4: 'Ein "
988
+ f"Ergebnisstatus ausser offen MUSS auf ein Protokoll verweisen' - "
989
+ f"ohne den Verweis ist die Abnahme eine Behauptung (D-202)")
990
+ if pack_re is not None and "sitzung" in zellen[i_pm] \
991
+ and not pack_re.search(status):
992
+ err(f"{rel}:{nr}: Ergebnisstatus '{wort}' einer sitzung-Zelle nennt kein "
993
+ f"Client Pack mit Produktversion. Ein Ergebnisstatus deckt kein "
994
+ f"anderes Pack, und ohne die Version sagt er nicht, gegen welchen "
995
+ f"Produktstand gemessen wurde (D-117, D-202)")
996
+ if gesehen == 0:
997
+ err(f"{KERN}/tests/: keine Zelle mit Ergebnisstatus gefunden - Pruefung 65 hat "
998
+ f"ihren Gegenstand verloren; sie bestuende sonst leise (D-23)")
999
+
1000
+
1001
+
1002
+
1003
+ # --- Pruefung 73: Jede [DOK]-Matrixzeile nennt ihre Quelle --------------------------
1004
+ #
1005
+ # ANLASS, UND ER IST GEZAEHLT (K-62, D-156, seit 0.62.0 offen, gemessen 2026-09-22).
1006
+ # Anhang 31.4 des Hauptdokuments sagt ueber sich selbst, die MASSGEBLICHE Zuordnung
1007
+ # stehe in der Faehigkeitsmatrix: "Dort nennt die Belegspalte je Zeile die Seite, auf
1008
+ # die sie sich stuetzt." Das traf am 2026-09-18 fuer 14 von 43 Zeilen zu - die Bauform
1009
+ # "eine Zusage, die mehr verspricht, als sie leistet", in ihrer Grundform.
1010
+ #
1011
+ # DER PREIS IST GEMESSEN, NICHT GESCHAETZT: Der Durchgang von FW-AK-01 musste ALLE 22
1012
+ # Quellen abrufen, weil ohne Zuordnung je Zeile nicht zu sagen ist, welche Seite welche
1013
+ # Zusage traegt. Eine Quellenliste ohne Zuordnung macht ihre eigene
1014
+ # Wiederholungspruefung so teuer wie die erste.
1015
+ #
1016
+ # DIE KOPFREGEL, UND SIE IST DER GRUND, WESHALB DIESE PRUEFUNG UEBERHAUPT ETWAS SAGT.
1017
+ # Geprueft wird der BELEGKOPF - die Zelle bis zum ersten Satzbruch (Punkt-Leerzeichen,
1018
+ # Semikolon, Doppelpunkt). Was danach steht, ist Erlaeuterung. Ohne diese Trennung
1019
+ # zaehlte jede Nennung der Marke mit: Zeile R5 des Packs claude-code erklaert, "ein
1020
+ # Dokumentenabgleich belegt [DOK], nicht [TECHNISCH]" - das ist eine Aussage UEBER die
1021
+ # Marke und kein Beleg. Dieselbe Trennlinie, die der VERIFY-Marker am Ende braucht
1022
+ # (CR-2026-070 E3): die nur nennende Fundstelle zaehlt nicht wie die tragende.
1023
+ #
1024
+ # WARUM DIE KENNUNG UND NICHT DER SEITENPFAD. Das Pack devin-desktop nannte QD-6, das
1025
+ # Pack claude-code `docs/en/memory` - dieselbe Sache in zwei Schreibweisen. Nur die
1026
+ # Kennung laesst sich gegen die Liste halten; ein Pfad kann eine Seite nennen, die die
1027
+ # Liste gar nicht fuehrt, und dann behauptet die Liste wieder mehr, als sie leistet.
1028
+ # Der Pfad darf danebenstehen, er ist Lesehilfe.
1029
+ #
1030
+ # WARUM DER VERWEIS AUFGELOEST WIRD. Vier Zeilen des Packs devin-desktop belegen mit
1031
+ # "wie B3", eine davon ueber zwei Glieder ("wie B4" -> "wie B3"). Solange B3 keine
1032
+ # Kennung trug, trugen FUENF Zeilen keine - und keine Zaehlung, die den Verweis nicht
1033
+ # aufloest, sieht das. Die Zusammenfassung des Packs zaehlt sie umgekehrt sehr wohl mit
1034
+ # ("neun der 36 Zeilen tragen einen offenen VERIFY-Marker - ... B4, B5, B6 und B8 ueber
1035
+ # den Verweis"): Wer sie beim Marker mitzaehlt und beim Beleg nicht, hat zwei
1036
+ # Zaehlregeln fuer dieselbe Spalte.
1037
+ #
1038
+ # WARUM EINE AUSGESPROCHENE LUECKE ZULAESSIG IST UND TROTZDEM NICHT WACHSEN KANN.
1039
+ # D-156 sagt: "Eine Zuordnung zu raten waere schlimmer als keine: Sie saehe wie ein
1040
+ # Beleg aus." Fuer Zeile M3 des Packs claude-code gibt der Bestand keine Seite her -
1041
+ # keine der sechs Seiten fuehrt die Sitzungs-Grant-Stufen, waehrend sie beim
1042
+ # Schwesterpack in QD-11 stehen. Die Zeile sagt das mit der Marke
1043
+ # QUELLE NICHT ZUGEORDNET. Damit das keine Hintertuer ist, fuehrt diese Pruefung die
1044
+ # zugelassenen Luecken als MENGE: eine neue faellt auf, und eine geschlossene ebenso -
1045
+ # dieselbe Bauform wie das leer DEKLARIERTE Feld von Pruefung 72 (D-155).
1046
+ P73_KENNUNG_RE = re.compile(r"Q[CDKU]-\d+") # QK- seit 1.13.0 (kiro, D-414), QU- seit 1.16.0 (cursor, D-440)
1047
+ P73_BRUCH_RE = re.compile(r"\.\s|;\s|:\s|:$")
1048
+ P73_VERWEIS_RE = re.compile(r"^(?:wie|dito)\b[^A-Z]*([RSBHAMX]\d+)")
1049
+ P73_OFFEN_MARKE = "QUELLE NICHT ZUGEORDNET"
1050
+ # Die ausgesprochenen Luecken, je Pack. Wer eine neue braucht, traegt sie hier ein -
1051
+ # und begruendet sie im Aenderungsantrag. Wer eine schliesst, nimmt sie hier heraus.
1052
+ P73_OFFEN = {"claude-code": {"M3"}}
1053
+ P73_SELBSTPROBE = ("`[DOK]` **`QC-9`** (synthetisch)", "`[DOK]` ohne Kennung")
1054
+
1055
+
1056
+ def _p73_kopf(zelle: str) -> str:
1057
+ """Der Belegkopf: die Zelle bis zum ersten Satzbruch."""
1058
+ m = P73_BRUCH_RE.search(zelle)
1059
+ return zelle[:m.start() + 1] if m else zelle
1060
+
1061
+
1062
+ def _p73_matrixzeilen(root: str, pack: str) -> dict:
1063
+ """{Kennung: Belegzelle} der Faehigkeitsmatrix eines Packs, in Lesereihenfolge."""
1064
+ pfad = os.path.join(root, KERN, "clients", pack, "CLIENT_PACK.md")
1065
+ if not os.path.isfile(pfad):
1066
+ return {}
1067
+ aus = {}
1068
+ drin = False
1069
+ for zeile in read(pfad).replace("\r\n", "\n").split("\n"):
1070
+ if zeile.startswith("## 2. "):
1071
+ drin = True
1072
+ continue
1073
+ if drin and re.match(r"^## \d", zeile):
1074
+ drin = False
1075
+ if not drin:
1076
+ continue
1077
+ m = P73_ZEILE_RE.match(zeile)
1078
+ if not m:
1079
+ continue
1080
+ zellen = tabellenzellen(zeile)
1081
+ if zellen:
1082
+ aus[m.group(1)] = zellen[-1]
1083
+ return aus
1084
+
1085
+
1086
+ def _p73_aufgeloest(zellen: dict, kennung: str) -> tuple:
1087
+ """Der Belegkopf einer Zeile, Verweise aufgeloest. (Kopf, Kette)."""
1088
+ ziel, kette = kennung, []
1089
+ for _ in range(5):
1090
+ kopf = _p73_kopf(zellen[ziel])
1091
+ m = P73_VERWEIS_RE.match(kopf)
1092
+ if not m or m.group(1) not in zellen or m.group(1) in kette:
1093
+ return kopf, kette
1094
+ kette.append(m.group(1))
1095
+ ziel = m.group(1)
1096
+ err(f"Faehigkeitsmatrix, Zeile {ziel}: der Belegverweis laeuft ueber mehr als "
1097
+ f"fuenf Glieder. Eine Belegkette, die niemand bis zum Ende liest, ist kein "
1098
+ f"Beleg (D-266)")
1099
+ return "", kette
1100
+
1101
+
1102
+ def check_belegquelle(root: str) -> None:
1103
+ """Pruefung 73 (D-263): Jede `[DOK]`-Matrixzeile nennt ihre Quellenkennung."""
1104
+ gut, schlecht = P73_SELBSTPROBE
1105
+ if not P73_KENNUNG_RE.search(_p73_kopf(gut)) or P73_KENNUNG_RE.search(_p73_kopf(schlecht)):
1106
+ err("Pruefung 73: die eigene Selbstprobe traegt nicht mehr - die Pruefung "
1107
+ "haette ihren Gegenstand verloren und bestuende leise (D-23)")
1108
+ return
1109
+ verz = os.path.join(root, KERN, "clients")
1110
+ if not os.path.isdir(verz):
1111
+ err(f"{KERN}/clients/ fehlt - Pruefung 73 haette keinen Gegenstand (D-23)")
1112
+ return
1113
+ packs = sorted(n for n in os.listdir(verz)
1114
+ if n != "_template"
1115
+ and os.path.isfile(os.path.join(verz, n, "CLIENT_PACK.md")))
1116
+ if not packs:
1117
+ err(f"{KERN}/clients/: kein Client Pack mit CLIENT_PACK.md gefunden - "
1118
+ f"Pruefung 73 haette keinen Gegenstand (D-23)")
1119
+ return
1120
+ for pack in packs:
1121
+ zellen = _p73_matrixzeilen(root, pack)
1122
+ if not zellen:
1123
+ err(f"clients/{pack}/CLIENT_PACK.md: unter '## 2.' steht keine "
1124
+ f"Matrixzeile. Pruefung 73 liest die Faehigkeitsmatrix dort und "
1125
+ f"haette ohne sie nichts zu pruefen (D-23)")
1126
+ continue
1127
+ erwartet = set(P73_OFFEN.get(pack, ()))
1128
+ gefunden = set()
1129
+ for kennung in zellen:
1130
+ kopf, kette = _p73_aufgeloest(zellen, kennung)
1131
+ if "[DOK]" not in kopf:
1132
+ continue
1133
+ ueber = f" (ueber den Verweis {' → '.join(kette)})" if kette else ""
1134
+ if P73_OFFEN_MARKE in kopf:
1135
+ gefunden.add(kette[-1] if kette else kennung)
1136
+ continue
1137
+ if not P73_KENNUNG_RE.search(kopf):
1138
+ err(f"clients/{pack}/CLIENT_PACK.md: Zeile {kennung} ist mit `[DOK]` "
1139
+ f"belegt und nennt im Belegkopf keine Quellenkennung "
1140
+ f"(`QC-n`/`QD-n`){ueber}. Anhang 31.4 sagt zu, die Zuordnung "
1141
+ f"stehe je Zeile in der Matrix; eine Zeile ohne sie macht jede "
1142
+ f"Wiederholung von `FW-AK-01` so teuer wie die erste (K-62, "
1143
+ f"D-156). Gibt der Bestand keine Seite her, sagt die Zeile "
1144
+ f"`{P73_OFFEN_MARKE}` - geraten wird nicht, eine geratene "
1145
+ f"Zuordnung saehe wie ein Beleg aus (D-263)")
1146
+ for offen in sorted(gefunden - erwartet):
1147
+ err(f"clients/{pack}/CLIENT_PACK.md: Zeile {offen} sagt "
1148
+ f"`{P73_OFFEN_MARKE}`, steht aber nicht in P73_OFFEN. Eine "
1149
+ f"ausgesprochene Luecke ist zulaessig und wird DEKLARIERT - sonst "
1150
+ f"waere sie eine Hintertuer, durch die die Zusage von Anhang 31.4 "
1151
+ f"still wieder kleiner wird (D-263)")
1152
+ for offen in sorted(erwartet - gefunden):
1153
+ err(f"clients/{pack}/CLIENT_PACK.md: P73_OFFEN fuehrt Zeile {offen} als "
1154
+ f"ausgesprochene Luecke, die Zeile sagt es aber nicht (mehr). Ist die "
1155
+ f"Quelle nachgetragen, gehoert der Eintrag heraus; eine Ausnahme ohne "
1156
+ f"Gegenstand sieht wie Sorgfalt aus und ist tot (0.57.1, D-263)")
1157
+
1158
+
1159
+ # ---------------------------------------------------------------------------
1160
+ # PRUEFUNG 95: DER AENDERUNGSVERLAUF EINES SKILLS NENNT DIE ART (CR-2026-147, D-403)
1161
+ # ---------------------------------------------------------------------------
1162
+ #
1163
+ # ANLASS. D-303 oeffnet die Zellen eines Testblatts nur, wenn eine Aenderung eine
1164
+ # ANWEISUNG des Skills beruehrt - und die Grenze zieht ein Mensch. Seit D-303 nennt jede
1165
+ # neue Zeile eines Skill-Aenderungsverlaufs diese Einordnung ("Keine Anweisung beruehrt"
1166
+ # oder "Anweisung beruehrt"), aber keine Pruefung sah, ob sie dasteht (K-146).
1167
+ # GEGENSTAND: die Zeile der Version, die der Steckbrief der SKILL.md nennt, und jede
1168
+ # Zeile mit einem Datum nach dem Stichtag. Aeltere Zeilen bleiben, wie sie geschrieben
1169
+ # wurden (Klasse C). NUR DIE SKILLS DES KERNS: D-303 regelt die Testblaetter des
1170
+ # Frameworks; ein projekteigener Skill fuehrt seinen Verlauf nach eigener Regel. Beim
1171
+ # ersten Heben mit dieser Pruefung meldete sie im Uebungsrepositorium die Erstfassung
1172
+ # eines prj-Skills. Die installierten Kopien der Kernskills sind Kopien der Quelle,
1173
+ # die hier geprueft wird.
1174
+ # GRENZE: Die Pruefung sieht die NENNUNG, nicht ihre Richtigkeit. Ob eine Aenderung eine
1175
+ # Anweisung beruehrt, entscheidet weiter ein Mensch (Preis von D-303, benannt).
1176
+ P95_STICHTAG = "2026-09-22"
1177
+ P95_ART_RE = re.compile(r"Anweisung\s+ber(?:ü|ue)hrt", re.I)
1178
+ P95_ZEILE_RE = re.compile(r"^\|\s*(\d+\.\d+\.\d+)\s*\|\s*(\d{4}-\d\d-\d\d)\s*\|", re.M)
1179
+
1180
+
1181
+ def check_skill_aenderungsart(root: str, man: dict) -> None:
1182
+ """Pruefung 95 (D-403): der Aenderungsverlauf eines Skills nennt die Art der Aenderung."""
1183
+ for skills_dir, prefix in skill_dirs(root, man):
1184
+ if not prefix.startswith(KERN + "/"):
1185
+ continue
1186
+ for name in sorted(os.listdir(skills_dir)):
1187
+ sdir = os.path.join(skills_dir, name)
1188
+ skill_path = os.path.join(sdir, "SKILL.md")
1189
+ cl_path = os.path.join(sdir, "CHANGELOG.md")
1190
+ if not (os.path.exists(skill_path) and os.path.exists(cl_path)):
1191
+ continue
1192
+ m = re.search(r"^\|\s*Version\s*\|\s*`?([^`|]+?)`?\s*\|", read(skill_path), re.M)
1193
+ aktuell = m.group(1).strip() if m else None
1194
+ for zeile in read(cl_path).splitlines():
1195
+ z = P95_ZEILE_RE.match(zeile)
1196
+ if not z or not (z.group(1) == aktuell or z.group(2) > P95_STICHTAG):
1197
+ continue
1198
+ if not P95_ART_RE.search(zeile):
1199
+ err(f"{prefix}/{name}/CHANGELOG.md: die Zeile {z.group(1)} nennt nicht, "
1200
+ f"ob sie eine Anweisung berührt ('Keine Anweisung berührt' oder "
1201
+ f"'Anweisung berührt') – davon hängt ab, ob die Zellen des "
1202
+ f"Testblatts offen sind (D-303, D-403)")