@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,1314 @@
1
+ """Die Client Packs als Dokumente: Quellenauskunft, Dokumenttabellen, normative Kommentare,
2
+ Regelablage, Ausfall mit Ersatz, Werkzeugabwesenheit, Zusagenfelder, Abwesenheitsbeleg,
3
+ Schlitzinhalte, Zusatzschluessel, Matrixzeilen, Vorlage und formatgebundene Pruefungen.
4
+
5
+ Pruefungen 19, 20, 23, 24, 25, 26, 27, 41, 42, 54, 74, 84, 87, 105, 108 und 109. 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 json
12
+ import os
13
+ import re
14
+
15
+ from .gemeinsam import (
16
+ _client_packs, _walk_text_files, err, formatgebunden, FORMATGEBUNDENE_PRUEFUNGEN,
17
+ FRONTMATTER_BLOECKE, KERN, korb_zerlegung, P73_ZEILE_RE, PROJEKTPLATZHALTER, read,
18
+ skill_dirs, soll_korbregeln, tabellenzellen, warn)
19
+
20
+
21
+ def nicht_erreicht(man: dict) -> set:
22
+ """Die Nummern, die die Ausgabeform dieses Packs nicht erreichen (Pruefung 87)."""
23
+ form = man.get("permissions_format", "json")
24
+ return {n for n, (_, formen) in FORMATGEBUNDENE_PRUEFUNGEN.items() if form not in formen}
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Pruefungen 19 bis 24 (Release 0.26.0)
29
+ # ---------------------------------------------------------------------------
30
+
31
+ AUSKUNFT_UEBERSCHRIFT = "Anweisungs- und Konfigurationsquellen außerhalb des Projekts"
32
+ DATUM_RE = re.compile(r"\b\d{4}-\d{2}-\d{2}\b")
33
+
34
+
35
+ def _abschnitt(text: str, ueberschrift: str) -> str | None:
36
+ """Text eines Abschnitts bis zur naechsten gleichrangigen Ueberschrift."""
37
+ m = re.search(r"^(#{2,3})\s*\d*[a-z]?\.?\s*" + re.escape(ueberschrift) + r"\s*$",
38
+ text, re.M)
39
+ if not m:
40
+ return None
41
+ ebene = len(m.group(1))
42
+ rest = text[m.end():]
43
+ weiter = re.search(r"^#{1,%d}\s" % ebene, rest, re.M)
44
+ return rest[:weiter.start()] if weiter else rest
45
+
46
+
47
+ def check_quellenauskunft(root: str) -> None:
48
+ """Pruefung 19 (D-34, D-37): Jedes Client Pack gibt Auskunft ueber Quellen ausserhalb.
49
+
50
+ GRENZE DIESER PRUEFUNG - sie steht hier, weil sie im Antrag vorab benannt und nicht
51
+ spaeter gefunden werden soll (CR-2026-031 E4): Geprueft wird die **Anwesenheit** der
52
+ Auskunft, nicht ihre **Richtigkeit**. Eine falsche oder veraltete Zeile besteht sie.
53
+ Ein Abwesenheitsbeleg altert, und dieses Skript sieht den Unterschied nicht - "keine
54
+ bekannt, Stand 2026-09-11" ist am Tag der naechsten Clientversion eine Behauptung
55
+ ueber die Vergangenheit. Die Richtigkeit haengt an einer Erhebung, nicht an einem
56
+ Skript; dasselbe gilt fuer Pruefung 22.
57
+ """
58
+ for kennung, pdir, _man in _client_packs(root):
59
+ rel = f"{KERN}/clients/{kennung}/CLIENT_PACK.md"
60
+ text = read(os.path.join(pdir, "CLIENT_PACK.md"))
61
+ abschnitt = _abschnitt(text, AUSKUNFT_UEBERSCHRIFT)
62
+ if abschnitt is None:
63
+ err(f"{rel}: Abschnitt '{AUSKUNFT_UEBERSCHRIFT}' fehlt. Jedes Pack fuehrt die "
64
+ f"bekannten Quellen ausserhalb des Repositoriums - Regeltexte, Skills, "
65
+ f"Agentenprofile, Berechtigungen, Hooks - oder einen datierten "
66
+ f"Abwesenheitsbeleg (D-34, D-37)")
67
+ continue
68
+ zeilen = [z for z in abschnitt.splitlines()
69
+ if z.strip().startswith("|") and not re.match(r"^\|[\s:|-]+\|?$", z.strip())]
70
+ inhalt = [z for z in zeilen
71
+ if not re.search(r"\|\s*(Quelle|Ladebedingung|Wirkung)\s*\|", z)]
72
+ abwesend = re.search(r"keine bekannt", abschnitt, re.I)
73
+ if not inhalt and not abwesend:
74
+ err(f"{rel}: Abschnitt '{AUSKUNFT_UEBERSCHRIFT}' ist leer. Er braucht je "
75
+ f"bekannter Quelle eine Zeile oder die ausdrueckliche Angabe 'keine "
76
+ f"bekannt' mit Datum und Erhebungsweg - ein leerer Abschnitt ist kein "
77
+ f"Abwesenheitsbeleg")
78
+ continue
79
+ if kennung.startswith("_"):
80
+ continue # Vorlage: Erhebungsstand steht als <TBD>
81
+ if not DATUM_RE.search(abschnitt):
82
+ err(f"{rel}: Abschnitt '{AUSKUNFT_UEBERSCHRIFT}' nennt keinen Erhebungsstand "
83
+ f"(JJJJ-MM-TT). Eine Quellenliste ohne Datum ist keine Auskunft, sondern "
84
+ f"eine Behauptung mit Fussnote")
85
+
86
+
87
+ # Begriff der Dokumenttabellen -> Platzhalter des Manifests. Ein Begriff ohne Eintrag
88
+ # bleibt unbeanstandet: "Nutzerlokale Ueberschreibung" hat kein Manifestfeld und ist
89
+ # zugleich die Gegenprobe dieser Pruefung.
90
+ GLOSSAR_ZU_PLATZHALTER = {
91
+ "Wurzel-Anweisungsdatei": "<ROOT_INSTRUCTION_FILE>",
92
+ "Laufzeitschicht": "<RUNTIME_DIR>",
93
+ "Berechtigungsdatei": "<PERMISSIONS_FILE>",
94
+ "Regelablage": "<RULES_DIR>",
95
+ "Skill-Ablage": "<SKILLS_DIR>",
96
+ "Agentenprofile": "<AGENTS_DIR>",
97
+ "Hook-Konfiguration": "<HOOKS_FILE>",
98
+ "MCP-Konfiguration": "<MCP_FILE>",
99
+ }
100
+ # <CORE_DIR> ist keine Eigenschaft eines Clients, sondern dieser Installation; ein Pack
101
+ # darf ihn nicht belegen (PLACEHOLDER_REGISTRY.md).
102
+ PLATZHALTER_OHNE_PACK = ("<CORE_DIR>",)
103
+
104
+
105
+ def _tabellenzeilen(text: str):
106
+ for zeile in text.splitlines():
107
+ z = zeile.strip()
108
+ if not z.startswith("|") or re.match(r"^\|[\s:|-]+\|?$", z):
109
+ continue
110
+ yield tabellenzellen(z)
111
+
112
+
113
+ def _spalten_je_pack(felder: list[str], packs: list[str]) -> dict[str, int]:
114
+ zuordnung: dict[str, int] = {}
115
+ for i, feld in enumerate(felder):
116
+ nackt = feld.strip("`* ")
117
+ if nackt in packs:
118
+ zuordnung[nackt] = i
119
+ return zuordnung
120
+
121
+
122
+ def _wert_passt(zelle: str, erwartet: str) -> bool:
123
+ """Der Manifestwert steht in der Zelle - Schreibvarianten zugelassen.
124
+
125
+ Die Tabellen schreiben ein Verzeichnis mal mit und mal ohne abschliessenden
126
+ Schraegstrich und setzen den Wert gelegentlich in einen erklaerenden Satz. Geprueft
127
+ wird deshalb Enthaltensein, nicht Gleichheit: Diese Pruefung soll eine **abweichende**
128
+ Angabe finden, nicht eine anders formulierte.
129
+ """
130
+ zelle = zelle.replace("\\", "/")
131
+ kandidaten = {erwartet, erwartet.rstrip("/") + "/", erwartet.rstrip("/")}
132
+ return any(k and k in zelle for k in kandidaten)
133
+
134
+
135
+ def _spalten_vollstaendig(traeger: str, gefunden: dict, namen: list) -> None:
136
+ """Jedes Pack mit Manifest hat in der Tabelle eine Spalte (1.14.0, D-421).
137
+
138
+ ANLASS: Die Pruefung verglich nur die Spalten, die es gab. Ein Pack ohne Spalte
139
+ fiel still heraus - mit 1.13.0 fuehrten beide Tabellen kiro nicht, und keine
140
+ Meldung sagte es. Eine Uebereinstimmung ueber die Haelfte der Packs ist keine.
141
+ """
142
+ for pack in sorted(set(namen) - set(gefunden)):
143
+ err(f"{traeger}: die Tabelle fuehrt keine Spalte fuer '{pack}' - ein Pack ohne "
144
+ f"Spalte faellt aus dem Abgleich mit seinem Manifest still heraus (D-421)")
145
+
146
+
147
+ def check_dokumenttabellen(root: str) -> None:
148
+ """Pruefung 20 (CR-2026-036): Dokumenttabellen und Manifest sagen dasselbe.
149
+
150
+ GRENZE: Sie prueft **Uebereinstimmung, nicht Richtigkeit.** Steht im Manifest ein
151
+ falscher Pfad, sind Tabelle und Manifest danach einig - und beide falsch. Was den
152
+ Manifestwert prueft, ist die Installation selbst und der Nachweis an ihr (D-23).
153
+ Abgedeckt sind nur Zeilen mit Manifestentsprechung; Begriffe ohne Feld altern weiter
154
+ still.
155
+ """
156
+ packs = {k: m for k, _p, m in _client_packs(root) if m}
157
+ if not packs:
158
+ return
159
+ namen = sorted(packs)
160
+
161
+ registry = f"{KERN}/docs/PLACEHOLDER_REGISTRY.md"
162
+ pfad = os.path.join(root, *registry.split("/"))
163
+ if os.path.isfile(pfad):
164
+ text = read(pfad)
165
+ spalten: dict[str, int] = {}
166
+ for felder in _tabellenzeilen(text):
167
+ gefunden = _spalten_je_pack(felder, namen)
168
+ if gefunden:
169
+ spalten = gefunden
170
+ _spalten_vollstaendig(registry, gefunden, namen)
171
+ continue
172
+ platzhalter = felder[0].strip("`* ")
173
+ if not spalten or not platzhalter.startswith("<"):
174
+ continue
175
+ if platzhalter in PLATZHALTER_OHNE_PACK:
176
+ continue
177
+ for pack, i in spalten.items():
178
+ erwartet = (packs[pack].get("runtime_placeholders") or {}).get(platzhalter)
179
+ if erwartet is None or i >= len(felder):
180
+ continue
181
+ if not _wert_passt(felder[i].strip("`* "), erwartet):
182
+ err(f"{registry}: {platzhalter} steht fuer '{pack}' als "
183
+ f"'{felder[i]}', das Manifest fuehrt '{erwartet}'. Die "
184
+ f"maschinenlesbare Quelle gilt; die Tabelle wird nachgezogen")
185
+
186
+ glossar = f"{KERN}/docs/RUNTIME_GLOSSARY.md"
187
+ pfad = os.path.join(root, *glossar.split("/"))
188
+ if os.path.isfile(pfad):
189
+ text = read(pfad)
190
+ spalten = {}
191
+ for felder in _tabellenzeilen(text):
192
+ gefunden = _spalten_je_pack(felder, namen)
193
+ if gefunden:
194
+ spalten = gefunden
195
+ _spalten_vollstaendig(glossar, gefunden, namen)
196
+ continue
197
+ if not spalten:
198
+ continue
199
+ begriff = felder[0].strip("`* ")
200
+ platzhalter = GLOSSAR_ZU_PLATZHALTER.get(begriff)
201
+ if not platzhalter:
202
+ continue # Begriff ohne Manifestfeld - siehe Kommentar oben
203
+ for pack, i in spalten.items():
204
+ erwartet = (packs[pack].get("runtime_placeholders") or {}).get(platzhalter)
205
+ if erwartet is None or i >= len(felder):
206
+ continue
207
+ if not _wert_passt(felder[i], erwartet):
208
+ err(f"{glossar}: '{begriff}' steht fuer '{pack}' als '{felder[i]}', "
209
+ f"das Manifest fuehrt '{erwartet}'. Die maschinenlesbare Quelle "
210
+ f"gilt; die Tabelle wird nachgezogen")
211
+
212
+
213
+ # Normative Schluesselwoerter. Ein Kommentar traegt Herkunft - Ebene, Version, Owner,
214
+ # Ladeverhalten -, keine Anweisung: Er erreicht nicht jede Sitzung (ERH-01, K-28, D-38).
215
+ NORMATIVE_WOERTER = (
216
+ re.compile(r"\bMUSS\b"),
217
+ re.compile(r"\bMUESSEN\b"),
218
+ re.compile(r"\bDARF NICHT\b"),
219
+ re.compile(r"\bDUERFEN NICHT\b"),
220
+ re.compile(r"\bSOLL\b"),
221
+ re.compile(r"\bNICHT ZULAESSIG\b"),
222
+ re.compile(r"(?i)nur über den änderungsprozess"),
223
+ re.compile(r"(?i)darf nur über"),
224
+ )
225
+ HTML_KOMMENTAR_RE = re.compile(r"<!--(.*?)-->", re.S)
226
+
227
+
228
+ def _laufzeitartefakte(root: str, man: dict):
229
+ """Die Artefakte, die in einer Sitzung geladen werden - Quelle und Installation."""
230
+ for unterbau in (f"{KERN}/framework/runtime", f"{KERN}/templates/rules"):
231
+ basis = os.path.join(root, *unterbau.split("/"))
232
+ if os.path.isdir(basis):
233
+ for pfad in _walk_text_files(basis):
234
+ if pfad.endswith((".md", ".template")):
235
+ yield pfad
236
+ orte = [man.get("root_instruction_file"),
237
+ (man.get("runtime_placeholders") or {}).get("<RULES_DIR>")]
238
+ for ort in orte:
239
+ if not ort:
240
+ continue
241
+ ziel = os.path.join(root, *ort.split("/"))
242
+ if os.path.isfile(ziel):
243
+ yield ziel
244
+ elif os.path.isdir(ziel):
245
+ for pfad in _walk_text_files(ziel):
246
+ if pfad.endswith((".md", ".template")):
247
+ yield pfad
248
+
249
+
250
+ def check_normative_kommentare(root: str, man: dict) -> None:
251
+ """Pruefung 23 (D-38): Kein normatives Schluesselwort in einem HTML-Kommentar.
252
+
253
+ Gemessen an **beiden** Clients, mit entgegengesetztem Ergebnis. Bei 'claude-code' blieb
254
+ dieselbe Messmarke unsichtbar, solange sie in Kommentarklammern stand, und war im
255
+ Klartext sofort im Kontext - in der Wurzel-Anweisungsdatei und in der Regelablage
256
+ (2026-09-11, ERH-01). Bei 'devin-desktop' steht der Kommentar **woertlich** in dem
257
+ Regelblock, den der Client bildet; die Sitzung gab die Marke aus dem Kommentar zurueck,
258
+ ohne eine Datei zu lesen (2026-09-12, K-28).
259
+
260
+ Genau das traegt die Pruefung. Ein Ablageort, an dem eine Aussage bei dem einen Client
261
+ verschwindet und beim anderen mitlaeuft, ist fuer eine normative Aussage untauglich:
262
+ Was gilt, darf nicht davon abhaengen, mit welchem Werkzeug gearbeitet wird. Die
263
+ Begruendung ist damit belastbarer als vorher - die alte ('erreicht die Sitzung nicht')
264
+ waere mit einem Client, der Kommentare durchreicht, hinfaellig gewesen; genau so einer
265
+ ist gemessen worden. Was gilt, steht im Fliesstext.
266
+
267
+ GRENZE: Eine Wortlistenpruefung meldet auch eine zutreffende Erwaehnung - etwa den
268
+ Verweis auf eine Regel, die 'MUSS' enthaelt. Fehlalarme sind moeglich und beim
269
+ Beschluss ausdruecklich in Kauf genommen (CR-2026-039 E3). Die strengere Lesart gilt
270
+ fuer beide Packs - nicht mehr vorsorglich, weil das Verhalten des zweiten unerhoben
271
+ waere, sondern weil es erhoben ist und **abweicht** (K-28, CR-2026-040).
272
+ """
273
+ gesehen = set()
274
+ for pfad in _laufzeitartefakte(root, man):
275
+ if pfad in gesehen:
276
+ continue
277
+ gesehen.add(pfad)
278
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
279
+ text = read(pfad)
280
+ for m in HTML_KOMMENTAR_RE.finditer(text):
281
+ inhalt = m.group(1)
282
+ zeile = text.count("\n", 0, m.start()) + 1
283
+ for muster in NORMATIVE_WOERTER:
284
+ treffer = muster.search(inhalt)
285
+ if not treffer:
286
+ continue
287
+ err(f"{rel}:{zeile}: '{treffer.group(0)}' steht in einem HTML-Kommentar. "
288
+ f"Ein Kommentar erreicht nicht jede Sitzung (ERH-01, K-28); eine normative "
289
+ f"Aussage gehoert in den Fliesstext, im Kommentar bleibt die Herkunft "
290
+ f"(D-38)")
291
+ break
292
+
293
+
294
+ # Was in der Vorlage einer Regelablage liegen darf: Regeltexte nach dem Nummernschema
295
+ # und Vorlagen. Alles andere ist ein Nicht-Regeltext - der Client registriert, was in
296
+ # der Ablage liegt, und macht es damit ladbar (AP2-DD-17, K-26).
297
+ REGELDATEI_RE = re.compile(r"^\d[\dN]-[A-Za-z0-9._-]+\.md(\.template)?$")
298
+
299
+
300
+ def check_regelablage_sauber(root: str) -> None:
301
+ """Pruefung 24 (D-36): In der Vorlage der Regelablage liegt nur, was Regel ist.
302
+
303
+ Bis 0.25.0 lag dort je Pack eine erklaerende README. Bei einem Pack fuehrte der
304
+ Client sie als Regel mit Trigger 'manual' (AP2-DD-17), beim anderen stand sie sogar
305
+ unbedingt im Kontext (K-26) - mit Belegvorbehalten, die damit den Rang eines
306
+ Regeltexts trugen. Der erklaerende Text steht seither eine Ebene hoeher, in der
307
+ Laufzeit-README.
308
+
309
+ GRENZE: Geprueft wird die **Vorlage** des Packs, nicht die installierte Ablage. Was
310
+ ein Projekt dort selbst ablegt, sieht diese Pruefung nicht.
311
+ """
312
+ for kennung, pdir, man in _client_packs(root):
313
+ if not man:
314
+ continue
315
+ regelablage = (man.get("runtime_placeholders") or {}).get("<RULES_DIR>")
316
+ if not regelablage:
317
+ continue
318
+ basis = os.path.join(pdir, "root-template", *regelablage.split("/"))
319
+ if not os.path.isdir(basis):
320
+ continue
321
+ for name in sorted(os.listdir(basis)):
322
+ if os.path.isdir(os.path.join(basis, name)):
323
+ err(f"{KERN}/clients/{kennung}/root-template/{regelablage}/{name}: "
324
+ f"Unterverzeichnis in der Vorlage der Regelablage")
325
+ continue
326
+ if REGELDATEI_RE.match(name):
327
+ continue
328
+ err(f"{KERN}/clients/{kennung}/root-template/{regelablage}/{name}: kein "
329
+ f"Regeltext. Die Regelablage enthaelt ausschliesslich Regeln (D-36) - "
330
+ f"der Client registriert, was dort liegt, und macht es ladbar. "
331
+ f"Erklaerender Text gehoert in die Laufzeit-README eine Ebene hoeher")
332
+
333
+
334
+ # Pruefung 25: Eine Zeile auf [NICHT ABBILDBAR] nennt den Ersatz.
335
+ #
336
+ # Die Einstufungsklasse sagt "der Client bietet keinen Mechanismus". Sie sagt nicht, was an
337
+ # seine Stelle tritt - und genau dort entsteht die stillschweigende Verschlechterung: Ein
338
+ # Ausfall wird eingetragen, niemand widerspricht, und die Zusage ist weg, ohne dass eine
339
+ # Entscheidung darueber gefallen waere.
340
+ #
341
+ # D-41 unterscheidet deshalb Kernzusagen von Faehigkeitszusagen. Eine Kernzusage auf
342
+ # [NICHT ABBILDBAR] sperrt die Inbetriebnahme; eine Faehigkeitszusage sperrt nicht, MUSS aber
343
+ # den Ersatz benennen. Diese Pruefung setzt den zweiten Teil durch - den ersten kann kein
344
+ # Skript durchsetzen, er ist eine Freigabe durch einen Menschen.
345
+ NICHT_ABBILDBAR_RE = re.compile(r"`\[NICHT ABBILDBAR\]`")
346
+ # Die Zeilenkennung der Matrix: ein oder zwei Grossbuchstaben und eine Zahl. Bis
347
+ # 1.19.1 stand dieses Muster unter demselben Namen wie das von Pruefung 31, und die
348
+ # spaetere Bindung galt fuer beide - Pruefung 25 sah zweibuchstabige Kennungen nicht,
349
+ # ohne dass ein Lauf es zeigte (D-480). Jede der beiden hat jetzt ihr eigenes.
350
+ P25_MATRIXZEILE_RE = re.compile(r"^\|\s*([A-Z]{1,2}\d+)\s*\|")
351
+
352
+
353
+ def check_ausfall_mit_ersatz(root: str) -> None:
354
+ """Pruefung 25 (D-41): Kein Ausfall ohne benannten Ersatz.
355
+
356
+ Geprueft werden die Matrixzeilen der Client Packs - erkennbar an der Zeilenkennung am
357
+ Zeilenanfang (B3, S5, X2). Die Zusammenfassungstabellen desselben Dokuments fuehren
358
+ dieselbe Klasse als Zeilenbeschriftung; sie sind keine Zusagen und bleiben unberuehrt.
359
+
360
+ Verlangt wird das Wort 'Ersatz' in der Zeile. Das ist bewusst grob: Die Pruefung kann
361
+ nicht beurteilen, ob ein Ersatz taugt - sie kann nur erzwingen, dass jemand die Frage
362
+ gestellt und beantwortet hat. Ein 'kein Ersatz' genuegt ihr, und das ist richtig so:
363
+ Der Satz 'hierfuer gibt es keinen Ersatz' ist eine Aussage, die jemand verantwortet.
364
+
365
+ GRENZE: Eine Wortpruefung. Wer 'Ersatz' hinschreibt, ohne einen zu nennen, kommt durch.
366
+ Sie faengt das Vergessen, nicht die Absicht - wie Pruefung 23 (CR-2026-041 E3).
367
+ """
368
+ basis = os.path.join(root, KERN, "clients")
369
+ if not os.path.isdir(basis):
370
+ return
371
+ for pack in sorted(os.listdir(basis)):
372
+ if pack.startswith("_"):
373
+ continue
374
+ pfad = os.path.join(basis, pack, "CLIENT_PACK.md")
375
+ if not os.path.isfile(pfad):
376
+ continue
377
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
378
+ for i, zeile in enumerate(read(pfad).splitlines(), 1):
379
+ if not NICHT_ABBILDBAR_RE.search(zeile):
380
+ continue
381
+ m = P25_MATRIXZEILE_RE.match(zeile)
382
+ if not m:
383
+ continue
384
+ if "ersatz" in zeile.lower():
385
+ continue
386
+ err(f"{rel}:{i}: Zeile {m.group(1)} steht auf [NICHT ABBILDBAR], ohne einen "
387
+ f"Ersatz zu benennen. Ein Ausfall, der nur eingetragen und nicht ersetzt "
388
+ f"wird, ist eine stillschweigende Verschlechterung: Die Zeile nennt den "
389
+ f"Ersatz oder haelt fest, dass es keinen gibt (D-41)")
390
+
391
+
392
+ # Pruefung 26: Eine erklaerte Werkzeugabwesenheit muss belegt und folgerichtig sein.
393
+ #
394
+ # Seit CR-2026-047 darf ein Pack im Feld hook_tools_absent erklaeren, dass sein Client
395
+ # eine Werkzeugklasse gar nicht kennt; die Abbildung laesst den Hook-Matcher dann ohne
396
+ # diese Klasse durchlaufen, statt abzubrechen. Das ist noetig - ein Client ohne eigenes
397
+ # Suchwerkzeug kann keines abbilden - und es ist zugleich ein Schlupfloch: Wer 'write'
398
+ # dort eintraege, naehme das schreibende Werkzeug aus der Durchsetzung, und nichts
399
+ # meldete es.
400
+ #
401
+ # Diese Pruefung schliesst es an zwei Stellen:
402
+ # 1. Die Abwesenheit muss folgerichtig sein - ein Verb, das hier steht, muss auch in
403
+ # permission_tools leer sein. Ein Client, fuer den die Berechtigungsschicht ein
404
+ # Werkzeug dieser Klasse kennt, hat eines.
405
+ # 2. Die Abwesenheit muss erklaert sein - ein nicht leerer Begleitsatz. Dieselbe
406
+ # Begruendung wie bei Pruefung 25: Ein Ausfall, den jemand verantwortet, ist etwas
407
+ # anderes als einer, der eingetragen wurde.
408
+ #
409
+ # GRENZE: Die Pruefung belegt nicht, dass der Client das Werkzeug wirklich nicht hat -
410
+ # das kann nur eine Erhebung. Sie belegt, dass die beiden Felder, die es behaupten,
411
+ # einander nicht widersprechen und dass jemand den Satz dazu geschrieben hat.
412
+ def check_werkzeugabwesenheit(root: str) -> None:
413
+ """Pruefung 26 (D-47): hook_tools_absent ist folgerichtig und erklaert."""
414
+ basis = os.path.join(root, KERN, "clients")
415
+ if not os.path.isdir(basis):
416
+ return
417
+ for pack in sorted(os.listdir(basis)):
418
+ if pack.startswith("_"):
419
+ continue
420
+ pfad = os.path.join(basis, pack, "manifest.json")
421
+ if not os.path.isfile(pfad):
422
+ continue
423
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
424
+ try:
425
+ man = json.loads(read(pfad))
426
+ except ValueError:
427
+ continue
428
+ _p26_unerhoben_und_mcp(rel, man)
429
+ abwesend = man.get("hook_tools_absent") or []
430
+ if not abwesend:
431
+ continue
432
+ if not isinstance(abwesend, list):
433
+ err(f"{rel}: hook_tools_absent ist keine Liste")
434
+ continue
435
+ notiz = str(man.get("_hook_tools_absent_note") or "").strip()
436
+ if not notiz:
437
+ err(f"{rel}: hook_tools_absent nennt {sorted(abwesend)}, aber "
438
+ f"_hook_tools_absent_note fehlt oder ist leer. Eine Werkzeugklasse aus "
439
+ f"der Durchsetzung zu nehmen, ist eine Aussage ueber den Client - sie "
440
+ f"gehoert begruendet, nicht bloss eingetragen (D-47)")
441
+ werkzeuge = man.get("permission_tools") or {}
442
+ for verb in sorted(abwesend):
443
+ if werkzeuge.get(verb):
444
+ err(f"{rel}: hook_tools_absent erklaert das Verb '{verb}' fuer abwesend, "
445
+ f"permission_tools nennt dafuer aber {werkzeuge[verb]}. Beides "
446
+ f"zugleich geht nicht: Kennt die Berechtigungsschicht ein Werkzeug "
447
+ f"dieser Klasse, hat der Client eines, und der Hook muss es "
448
+ f"erreichen (D-47)")
449
+
450
+
451
+ def _p26_unerhoben_und_mcp(rel: str, man: dict) -> None:
452
+ """Pruefung 26, seit 1.20.0 (K-184, D-486): der dritte Zustand und das Verb mcp.
453
+
454
+ 'unerhoben' nimmt ein Verb aus dem Matcher wie 'abwesend' - deshalb dieselben zwei
455
+ Auflagen: begruendet und nicht zugleich abgebildet. Dazu das Verb mcp: Sein Matcher
456
+ und das Praefix, an dem der Hook es erkennt, stehen nur zusammen, und der Matcher
457
+ trifft das Praefix. Sonst liefe der Hook an, erkennte das Werkzeug aber nicht (dann
458
+ gaelte die strengste Lesart), oder er erkennte es und liefe nie an.
459
+ """
460
+ unerhoben = man.get("hook_tools_unerhoben") or []
461
+ abbildung = man.get("hook_tools") or {}
462
+ if unerhoben:
463
+ if not isinstance(unerhoben, list):
464
+ err(f"{rel}: hook_tools_unerhoben ist keine Liste")
465
+ return
466
+ if not str(man.get("_hook_tools_unerhoben_note") or "").strip():
467
+ err(f"{rel}: hook_tools_unerhoben nennt {sorted(unerhoben)}, aber "
468
+ f"_hook_tools_unerhoben_note fehlt oder ist leer - ein ungemessenes Verb "
469
+ f"gehoert begruendet, nicht bloss eingetragen (D-486)")
470
+ for verb in sorted(unerhoben):
471
+ if abbildung.get(verb):
472
+ err(f"{rel}: hook_tools_unerhoben fuehrt das Verb '{verb}', hook_tools bildet "
473
+ f"es zugleich ab ({abbildung[verb]}) - gemessen oder nicht, beides geht "
474
+ f"nicht (D-486)")
475
+ matcher = abbildung.get("mcp") or []
476
+ praefixe = man.get("hook_mcp_prefixes") or []
477
+ if bool(matcher) != bool(praefixe):
478
+ err(f"{rel}: hook_tools.mcp und hook_mcp_prefixes stehen nur zusammen - ohne "
479
+ f"Praefix erkennt der Hook das Werkzeug nicht, ohne Matcher laeuft er nie an "
480
+ f"(K-184, D-486)")
481
+ return
482
+ for praefix in praefixe:
483
+ probe = praefix + "server__werkzeug"
484
+ try:
485
+ getroffen = any(re.fullmatch(m, probe) for m in matcher)
486
+ except re.error:
487
+ getroffen = False
488
+ if not getroffen:
489
+ err(f"{rel}: Kein Matcher in hook_tools.mcp trifft ein Werkzeug mit dem Praefix "
490
+ f"'{praefix}' (D-486)")
491
+
492
+
493
+ # Pruefung 27: Ein verworfenes Zusagenfeld eines Skills nennt seinen Ersatz.
494
+ #
495
+ # Dieselbe Bauform wie Pruefung 26, eine Ebene weiter: Ein Pack darf ein Frontmatter-Feld
496
+ # verwerfen, das sein Client nicht kennt - aber nicht eines, das eine Zusage traegt, ohne
497
+ # zu sagen, was an seine Stelle tritt.
498
+ #
499
+ # Zwei Faelle gab es dafuer schon: 'triggers' verfiel beim Rendern, bis AP2-CC-01 es fand
500
+ # und eine Abbildung bekam (model_invocation_field). 'permissions' verfiel bis 0.30.0 auf
501
+ # genau dieselbe Weise - im Manifest unter drop_fields deklariert, in der Wirkung
502
+ # unbemerkt, und dabei trug es das 'deny: edit, exec' von zwoelf Skills (B01, D-50).
503
+ #
504
+ # install.py bricht seit 0.31.0 ab, wenn das passiert. Diese Pruefung findet denselben
505
+ # Fehler **ohne** Installation - im Repositorium, wo ein neues Pack entsteht.
506
+ #
507
+ # GRENZE: Eine Anwesenheitspruefung auf den Begleitsatz. Ob der genannte Ersatz taugt,
508
+ # kann kein Skript beurteilen - wie bei Pruefung 25 wird erzwungen, dass jemand die Frage
509
+ # gestellt und beantwortet hat.
510
+ ZUSAGENFELDER_ERSATZ = {
511
+ "permissions": "skill_permissions_ersatz",
512
+ "triggers": "model_invocation_field",
513
+ }
514
+
515
+
516
+ def check_zusagenfelder(root: str) -> None:
517
+ """Pruefung 27 (D-50): Kein verworfenes Zusagenfeld ohne benannten Ersatz."""
518
+ basis = os.path.join(root, KERN, "clients")
519
+ if not os.path.isdir(basis):
520
+ return
521
+ for pack in sorted(os.listdir(basis)):
522
+ if pack.startswith("_"):
523
+ continue
524
+ pfad = os.path.join(basis, pack, "manifest.json")
525
+ if not os.path.isfile(pfad):
526
+ continue
527
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
528
+ try:
529
+ man = json.loads(read(pfad))
530
+ except ValueError:
531
+ continue
532
+ fmt = (man.get("skill_frontmatter") or {})
533
+ entfallend = set(fmt.get("drop_fields") or [])
534
+ if fmt.get("tools_format") == "csv":
535
+ entfallend.add("allowed-tools")
536
+ for feld, ersatzfeld in sorted(ZUSAGENFELDER_ERSATZ.items()):
537
+ if feld not in entfallend:
538
+ continue
539
+ if str(fmt.get(ersatzfeld) or "").strip():
540
+ continue
541
+ err(f"{rel}: Das Skill-Frontmatter-Feld '{feld}' traegt eine Zusage und steht "
542
+ f"in skill_frontmatter.drop_fields - es entfaellt damit ersatzlos. Das Pack "
543
+ f"MUSS in skill_frontmatter.{ersatzfeld} benennen, was an seine Stelle "
544
+ f"tritt, oder festhalten, dass es keinen Ersatz gibt und was stattdessen "
545
+ f"traegt. Ein folgenlos verworfenes Feld ist der Befund AP2-CC-01, und bei "
546
+ f"'permissions' war es B01 (D-18, D-50)")
547
+
548
+
549
+ # ---------------------------------------------------------------------------
550
+ # Pruefung 41: Eine Abwesenheitserklaerung ist Enthaltung oder Beleg
551
+ # ---------------------------------------------------------------------------
552
+ #
553
+ # ANLASS. D-47 wurde gebaut, damit ein Pack eine Werkzeugklasse nicht dadurch aus der
554
+ # Durchsetzung nehmen kann, dass es sie weglaesst: Eine Abwesenheit MUSS erklaert werden.
555
+ # Pruefung 26 erzwingt seither, dass die Erklaerung DA ist und mit permission_tools
556
+ # widerspruchsfrei. Am 2026-09-14 war beides erfuellt und die Erklaerung trotzdem falsch
557
+ # (tests/protocols/2026-09-14-erhebung-devin-werkzeuge.md, CR-2026-065):
558
+ # _hook_tools_absent_note sagte "Dieser Client fuehrt kein eigenes Suchwerkzeug" - und der
559
+ # Client fuehrt zwei, grep und find_file_by_name. Der erzeugte Hook-Matcher kannte die
560
+ # Suchklasse deshalb nicht, und in einer Umgebung mit nur diesem Hook kam derselbe
561
+ # Secret-Wert, den ein read-Aufruf nicht bekam, ueber einen grep-Aufruf woertlich heraus.
562
+ #
563
+ # WAS PRUEFBAR IST. Nicht die Wahrheit einer Aussage - das kann kein Skript. Wohl aber,
564
+ # ob die Aussage sich als das ausweist, was sie ist. Zwei Bauformen sind redlich:
565
+ # * die ENTHALTUNG - "UNERHOBEN, nicht abwesend" (agent_start_tools_absent seit D-70);
566
+ # * der BELEG - ein Datum UND eine Fundstelle unter tests/protocols/. Ein
567
+ # blosses Wort wie "gemessen" genuegt nicht - "nicht gemessen" enthaelt es
568
+ # auch, und genau diese Falle stand im Entwurf dieser Pruefung.
569
+ # Die dritte Bauform ist die, die hier Schaden angerichtet hat: eine BEHAUPTUNG, die wie
570
+ # eine Feststellung klingt und weder das eine noch das andere trägt.
571
+ #
572
+ # GRENZE. Sie prueft eine FORM, nicht eine Tatsache. Wer ein Datum und einen Protokollpfad
573
+ # in die Note schreibt, besteht sie - auch wenn das Protokoll etwas anderes sagt. Und sie
574
+ # faengt nach 0.43.0 nichts mehr: Beide Packs sind in Ordnung. Ihr Wert haengt an ihren
575
+ # Sonden und daran, dass die naechste Abwesenheitserklaerung nicht mehr unbelegt bleibt.
576
+ ABWESENHEITSFELDER = (
577
+ ("hook_tools_absent", "_hook_tools_absent_note", None),
578
+ ("agent_start_tools_absent", "_agent_start_tools_absent_note", None),
579
+ ("tool_names_unmapped", "_tool_names_unmapped_note", FRONTMATTER_BLOECKE),
580
+ ("skill_deny_unmapped", "_skill_deny_unmapped_note", ("skill_frontmatter",)),
581
+ )
582
+ ENTHALTUNG = re.compile(r"unerhoben", re.I)
583
+ DATUM = re.compile(r"\b20\d\d-\d\d-\d\d\b")
584
+ FUNDSTELLE = re.compile(r"tests/protocols/")
585
+
586
+
587
+ def _abwesenheitserklaerungen(man: dict):
588
+ """(Feldname, Notizname, Wert, Notiz) je erklaerter Abwesenheit dieses Manifests."""
589
+ for feld, notiz, bloecke in ABWESENHEITSFELDER:
590
+ if bloecke is None:
591
+ wert = man.get(feld)
592
+ if wert:
593
+ yield feld, notiz, wert, man.get(notiz)
594
+ continue
595
+ for block in bloecke:
596
+ fmt = man.get(block) or {}
597
+ wert = fmt.get(feld)
598
+ if wert:
599
+ yield f"{block}.{feld}", f"{block}.{notiz}", wert, fmt.get(notiz)
600
+
601
+
602
+ def check_abwesenheitsbeleg(root: str) -> None:
603
+ """Pruefung 41 (D-88): Eine erklaerte Abwesenheit weist sich aus - oder sie belegt sich."""
604
+ basis = os.path.join(root, KERN, "clients")
605
+ if not os.path.isdir(basis):
606
+ return
607
+ manifeste = 0
608
+ erklaerungen = 0
609
+ for pack in sorted(os.listdir(basis)):
610
+ if pack.startswith("_"):
611
+ continue
612
+ pfad = os.path.join(basis, pack, "manifest.json")
613
+ if not os.path.isfile(pfad):
614
+ continue
615
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
616
+ try:
617
+ man = json.loads(read(pfad))
618
+ except ValueError:
619
+ continue
620
+ manifeste += 1
621
+ for feld, notizname, _wert, notiz in _abwesenheitserklaerungen(man):
622
+ erklaerungen += 1
623
+ text = str(notiz or "").strip()
624
+ if not text:
625
+ continue # Die Pruefungen 26 und 38 melden die fehlende Notiz bereits
626
+ if ENTHALTUNG.search(text):
627
+ continue
628
+ if DATUM.search(text) and FUNDSTELLE.search(text):
629
+ continue
630
+ err(f"{rel}: {notizname} erklärt eine Abwesenheit, weist sie aber weder als "
631
+ f"Enthaltung aus noch belegt sie sie. Redlich sind zwei Bauformen: das "
632
+ f"Wort 'unerhoben' – oder ein Datum zusammen mit einer Fundstelle "
633
+ f"('tests/protocols/…'). Eine "
634
+ f"Behauptung ohne beides nimmt eine Werkzeugklasse aus der Durchsetzung "
635
+ f"und begründet es – genau so ist am 2026-09-14 der Schutz-Hook um die "
636
+ f"Suchklasse gekommen (D-88)")
637
+ if not manifeste:
638
+ err(f"{KERN}/clients: kein Manifest gefunden. Prüfung 41 misst die "
639
+ f"Abwesenheitserklärungen der Packs; ohne sie prüft sie nichts und bestünde "
640
+ f"leise (D-23)")
641
+ return
642
+ if not erklaerungen:
643
+ err(f"{KERN}/clients: kein Pack führt noch eine Abwesenheitserklärung. Prüfung "
644
+ f"41 hat ihren Gegenstand verloren und würde leise bestehen – fällt das Feld "
645
+ f"weg, gehört die Prüfung ausgebaut und nicht stillgelegt (D-23, D-88)")
646
+
647
+
648
+ # ---------------------------------------------------------------------------
649
+ # Pruefung 42: Ein gefuellter Schlitz traegt, was das Overlay erklaert
650
+ # ---------------------------------------------------------------------------
651
+ #
652
+ # ANLASS. Gemessen am 2026-09-14
653
+ # (tests/protocols/2026-09-14-gegenpruefung-schlitzdeckung.md, CR-2026-066): Die
654
+ # Kernquelle haelt im ask-Korb DREI Befehlsschlitze bereit. Pruefung 37 vergleicht
655
+ # Mengen und zaehlt den Ueberschuss gegen die Zahl der offenen Schlitze - WELCHER
656
+ # Eintrag WELCHER Schlitz ist, steht dort nicht und kann dort nicht stehen. Gemessen
657
+ # laufen deshalb drei beliebige Befehlsfreigaben durch, darunter eine mit Fernwirkung,
658
+ # und zwar in beiden Packs und auch dann, wenn das Overlay dreimal <TBD> sagt, also gar
659
+ # nichts erklaert. Am Piloten steht der Fall seit dem Heben auf 0.37.0:
660
+ # Bash(mvn -B -q compile) im ask-Korb, waehrend Abschnitt 6 <LINT_COMMAND> als "nicht
661
+ # vorhanden" erklaert.
662
+ #
663
+ # DIE LUECKE WAR ERKLAERT - AN EINER STELLE. CR-2026-061 Abschnitt 4 nimmt genau diesen
664
+ # Fall ausdruecklich aus, und D-76 wie D-77 bleiben genau. DREI ausgelieferte Texte aus
665
+ # DEMSELBEN Commit (34d850e, Release 0.39.0) taten es nicht: der Absatz zum Wirkungsort
666
+ # in templates/project-overlay/OVERLAY.md, der Kommentarkopf JEDER erzeugten
667
+ # Berechtigungsdatei in clientmap._kommentar und framework/core/03-security.md. Alle drei
668
+ # sind mit diesem Release berichtigt. Die Lehre ist die von 0.42.0, eine Ebene tiefer:
669
+ # Eine Enthaltung, die nur in einem Antrag steht, haelt nicht einmal bis zum Ende
670
+ # desselben Patches.
671
+ #
672
+ # WOHER DIE ZUORDNUNG KOMMT (CR-2026-066 E1). Es gibt sie genau einmal, im Overlay:
673
+ # Jeder der drei Platzhalter steht dort in genau EINER Tabellenzeile, und der Wert steht
674
+ # in der Zelle RECHTS DANEBEN - gemessen in allen vier geprueften Overlays, auch im
675
+ # Overlay des Piloten, dessen Abschnitt 6 aus einer aelteren Vorlage stammt und eine
676
+ # andere Spaltenueberschrift fuehrt. docs/PLACEHOLDER_REGISTRY.md weist die Herkunft
677
+ # ohnehin aus (Overlay 5 beziehungsweise Overlay 6); diese Pruefung setzt eine
678
+ # Behauptung durch, die das Register seit jeher macht. Die Zeile wird ueber die
679
+ # PLATZHALTERZELLE gefunden, nie ueber eine Spaltennummer - ein Overlay, das eine Spalte
680
+ # ergaenzt, bricht sie deshalb nicht.
681
+ #
682
+ # WARUM NICHT DIE LAUFZEITFASSUNG (E2). 20-project-overlay.md fuehrt dieselben drei
683
+ # Werte, aber in einer Fliesszeile ohne Schluesselspalte - und der Pilot hat sie bereits
684
+ # umgebaut (zwei Zeilen statt einer, ein zusaetzliches Feld). Eine Pruefung darueber
685
+ # fiele beim ersten echten Projekt an der FORM, nicht an der Sache.
686
+ #
687
+ # WARUM EINE EIGENE NUMMER (E6). Pruefung 37 haengt allein an der Kernquelle und laeuft
688
+ # ohne Overlay; diese hier enthaelt sich ohne Overlay. Zwei Gegenstaende, zwei Nummern -
689
+ # und 37 behaelt ihre Arithmetik, damit ihr Ergebnis nicht von der Anwesenheit eines
690
+ # Nachbardokuments abhaengt. Am Piloten meldet 37 deshalb weiterhin nichts, waehrend 42
691
+ # meldet.
692
+ #
693
+ # GRENZE, UND SIE STEHT HIER UND NICHT NUR IM ANTRAG (E5). Geprueft werden die drei
694
+ # BEFEHLSSCHLITZE. Die vier PFADSCHLITZE (<EXCLUDED_PATHS> zweimal, <CI_CONFIG_PATHS>,
695
+ # <QUALITY_GATE_CONFIG_PATHS>) bleiben ungeprueft: Sie stehen saemtlich im deny-Korb, wo
696
+ # Ueberzaehliges ohnehin zulaessig ist, und ihr Vergleich waere n:1 - eine Liste im
697
+ # Overlay gegen eine Regel in der Datei. Ein zu ENG gefuellter <EXCLUDED_PATHS>-Schlitz
698
+ # ist damit weiterhin eine stille Lockerung. Das ist eine Enthaltung, keine Stille.
699
+ #
700
+ # ZWEITE GRENZE. Diese Pruefung vergleicht Zeichenketten. Ob der erklaerte Befehl
701
+ # fachlich der richtige ist, ob er tut, was Abschnitt 6 von ihm behauptet, und ob ein
702
+ # Client die Regel so auswertet, wie sie gemeint ist, sagt sie nicht.
703
+ OVERLAY_QUELLE = ".koolie/project-overlay/OVERLAY.md"
704
+ # Werte, die kein Befehl sind. Die Schreibweisen stammen aus der Vorlage und aus dem
705
+ # Overlay des Piloten; der Vergleich laeuft in Kleinschreibung und ohne umschliessende
706
+ # Auszeichnung. Ein Wert mit <TBD faellt ohnehin darunter.
707
+ KEIN_BEFEHL = ("nicht vorhanden", "nicht erforderlich", "keine", "keiner", "keines",
708
+ "entfaellt", "entfällt", "nur in ci", "-", "–", "—", "")
709
+
710
+
711
+ def overlay_befehlswert(text: str, platzhalter: str) -> tuple:
712
+ """Wie oft nennt eine Tabellenzeile den Platzhalter, und was steht rechts daneben?
713
+
714
+ Rueckgabe (anzahl, wert). wert ist None, wenn die Zelle keinen Befehl traegt - ein
715
+ offener <TBD>-Wert, ein "nicht vorhanden" oder ein Gedankenstrich. Steht in der Zelle
716
+ ein Abschnitt zwischen Gegenstrichen, gilt dieser als der Wert; so ueberlebt die
717
+ Auswertung einen erlaeuternden Zusatz hinter dem Befehl.
718
+ """
719
+ treffer = []
720
+ for felder in _tabellenzeilen(text):
721
+ for i, feld in enumerate(felder):
722
+ if feld.strip("` *") == platzhalter and i + 1 < len(felder):
723
+ treffer.append(felder[i + 1])
724
+ if len(treffer) != 1:
725
+ return len(treffer), None
726
+ zelle = treffer[0]
727
+ ausgezeichnet = re.search(r"`([^`]+)`", zelle)
728
+ wert = (ausgezeichnet.group(1) if ausgezeichnet else zelle).strip(" *")
729
+ if "<TBD" in wert or wert.strip().lower() in KEIN_BEFEHL:
730
+ return 1, None
731
+ return 1, wert
732
+
733
+
734
+ def check_schlitzinhalte(root: str, man: dict) -> None:
735
+ """Pruefung 42 (D-90, D-91): Der Schlitz traegt den Befehl, den das Overlay erklaert.
736
+
737
+ Enthaltung ohne Overlay: Eine Installation ohne .koolie/project-overlay/OVERLAY.md ist ein
738
+ zulaessiger Zustand (Kandidatenphase), und eine Pruefung, die ihn beanstandet, waere
739
+ eine Pruefung ueber die Reihenfolge der Uebernahme. Fehlt dagegen ein PLATZHALTER in
740
+ einem vorhandenen Overlay, ist das der verlorene Anker und ein Fehler (D-23).
741
+ """
742
+ if formatgebunden(man, 42):
743
+ return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
744
+ overlay_pfad = os.path.join(root, ".koolie/project-overlay", "OVERLAY.md")
745
+ rel = man["permissions_file"]
746
+ pfad = os.path.join(root, *rel.split("/"))
747
+ if not os.path.exists(overlay_pfad) or not os.path.exists(pfad):
748
+ return
749
+ try:
750
+ cfg = json.loads(read(pfad))
751
+ except json.JSONDecodeError:
752
+ return # check_config hat das bereits gemeldet
753
+ soll = soll_korbregeln(root, man)
754
+ if soll is None:
755
+ return
756
+ overlay = read(overlay_pfad)
757
+ perms = cfg.get("permissions", {})
758
+ exec_werkzeuge = tuple(man.get("permission_tools", {}).get("exec", ()))
759
+ if not exec_werkzeuge:
760
+ return # Ein Pack ohne Befehlswerkzeug hat keine Befehlsschlitze.
761
+
762
+ for korb in ("ask", "allow"):
763
+ ist = [r for r in perms.get(korb, []) if isinstance(r, str)]
764
+ _, schlitze, _, zusatz = korb_zerlegung(ist, soll[korb])
765
+ befehlsschlitze = [s for s in schlitze if s.split("(", 1)[0] in exec_werkzeuge]
766
+ if not befehlsschlitze:
767
+ continue
768
+ erklaert: dict = {}
769
+ ohne_befehl: list = []
770
+ for schlitz in befehlsschlitze:
771
+ namen = PROJEKTPLATZHALTER.findall(schlitz)
772
+ if len(namen) != 1:
773
+ continue
774
+ platzhalter = namen[0]
775
+ anzahl, wert = overlay_befehlswert(overlay, platzhalter)
776
+ if anzahl == 0:
777
+ err(f"{OVERLAY_QUELLE}: keine Tabellenzeile nennt {platzhalter} – "
778
+ f"Prüfung 42 liest dort den Befehl, den dieser Schlitz der "
779
+ f"Berechtigungsdatei tragen darf, und hat ihren Gegenstand verloren. "
780
+ f"Der Platzhalter gehört in die Platzhalterspalte von Abschnitt 5 "
781
+ f"oder 6, der Befehl in die Zelle rechts daneben (D-91)")
782
+ continue
783
+ if anzahl > 1:
784
+ err(f"{OVERLAY_QUELLE}: {anzahl} Tabellenzeilen nennen {platzhalter}. "
785
+ f"Prüfung 42 braucht genau eine – bei mehreren ist nicht "
786
+ f"entschieden, welcher Befehl für den Schlitz gilt (D-91)")
787
+ continue
788
+ if wert is None:
789
+ ohne_befehl.append(platzhalter)
790
+ continue # Kein erklaerter Befehl: Dieser Schlitz deckt nichts.
791
+ regel = schlitz.replace(platzhalter, wert)
792
+ erklaert[regel] = platzhalter
793
+ if regel in ist:
794
+ continue
795
+ if schlitz in ist:
796
+ err(f"{rel}: das Overlay erklärt für {platzhalter} den Befehl "
797
+ f"'{wert}'; der {korb}-Korb trägt aber noch den offenen Schlitz "
798
+ f"'{schlitz}'. Entweder wird der Wert dort eingetragen ('{regel}'), "
799
+ f"oder Abschnitt 5 beziehungsweise 6 nimmt ihn zurück – zwei "
800
+ f"Träger derselben Freigabe dürfen nicht auseinanderlaufen "
801
+ f"(D-90)")
802
+ else:
803
+ err(f"{rel}: das Overlay erklärt für {platzhalter} den Befehl "
804
+ f"'{wert}'; der {korb}-Korb trägt weder '{regel}' noch den offenen "
805
+ f"Schlitz '{schlitz}'. Eine erklärte Freigabe, die die Datei nicht "
806
+ f"gewährt, ist eine Abweichung – gleich in welche Richtung sie "
807
+ f"aufgelöst wird (D-90)")
808
+ for regel in zusatz:
809
+ if regel.split("(", 1)[0] not in exec_werkzeuge:
810
+ continue
811
+ if regel in erklaert:
812
+ continue
813
+ err(f"{rel}: '{regel}' steht im {korb}-Korb, und kein Platzhalter des "
814
+ f"Overlays erklärt diesen Befehl. Ein Platzhalterschlitz darf gefüllt "
815
+ f"sein – aber mit dem Wert, den Abschnitt 5 oder 6 für ihn nennt. Ohne "
816
+ f"erklärten Befehl deckt ein Schlitz keine zusätzliche Freigabe; ohne "
817
+ f"diesen Satz decken drei offene Schlitze drei beliebige Befehle, gemessen "
818
+ f"bis hin zu einem mit Fernwirkung (D-90). Ohne erklärten Befehl sind "
819
+ f"hier: {', '.join(ohne_befehl) if ohne_befehl else 'keiner'}")
820
+
821
+
822
+ # --- Pruefung 54 -------------------------------------------------------------------
823
+ # Das Framework liefert die Berechtigungsdatei eines Packs erzeugt aus, und ein Pack darf
824
+ # darin Schluessel setzen, die nicht aus der neutralen Regelmenge stammen. Bis 0.61.0 gab
825
+ # es dafuer genau ein Feld: permissions_extra - und es landet INNERHALB des
826
+ # permissions-Objekts. Fuer einen Schluessel, den der Client auf der obersten Ebene liest,
827
+ # war das die falsche Stelle, und es gab keine richtige.
828
+ #
829
+ # DIE EBENE IST KEINE KOSMETIK. Ein Schluessel an der falschen Stelle wird nicht gelesen,
830
+ # die Datei bleibt gueltiges JSON, der Validator meldete nichts - und die Verschaerfung,
831
+ # die das Pack auszuliefern glaubt, wirkt nicht. Das ist die Bauform "die Zusage, die mehr
832
+ # verspricht als ihr Mechanismus haelt", angewandt auf eine Dateiebene.
833
+ #
834
+ # ANLASS IST EINE FREMDE MESSUNG, KEINE EIGENE (FW-AK-01, CR-2026-087, 2026-09-18): Beim
835
+ # Client des Schwesterpacks ist genau das als CVE-2026-81376 aufgetreten - der Restricted
836
+ # Mode setzte eine eingeschraenkte Arbeitsbereichseinstellung in GEPUNKTETER Schreibweise
837
+ # durch und dieselbe Einstellung in VERSCHACHTELTER Form nicht. Behoben in 3.10.31 vom
838
+ # 2026-09-16; die verbindliche Zielspanne des Packs devin-desktop (3.9.x) liegt
839
+ # vollstaendig davor. Dieselbe Regel, zwei Ausdrucksformen, durchgesetzt nur in einer -
840
+ # die Bauform von Pruefung 51, diesmal im Produkt statt im eigenen Bestand.
841
+ #
842
+ # WAS SIE NICHT LEISTET: Sie prueft, ob ein DEKLARIERTER Schluessel ankommt, nicht ob er
843
+ # der richtige ist. Ob autoMemoryEnabled der Schluessel ist, den dieser Client liest, sagt
844
+ # die Herstellerdokumentation - und dafuer gibt es FW-AK-01, nicht den Validator.
845
+ P54_FELDER = ("settings_extra", "permissions_extra")
846
+
847
+
848
+ def check_zusatzschluessel(root: str, man: dict) -> None:
849
+ """Pruefung 54 (D-155): Deklarierte Zusatzschluessel stehen auf ihrer Ebene."""
850
+ if formatgebunden(man, 54):
851
+ return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
852
+ rel = man.get("permissions_file")
853
+ if not rel:
854
+ return
855
+ pfad = os.path.join(root, *rel.split("/"))
856
+ if not os.path.isfile(pfad):
857
+ return
858
+ try:
859
+ cfg = json.loads(read(pfad))
860
+ except json.JSONDecodeError:
861
+ return # Pruefung 3 meldet das bereits; hier waere es eine zweite Meldung
862
+ if not isinstance(cfg, dict):
863
+ return
864
+ pack = man.get("client", "?")
865
+ for feld in P54_FELDER:
866
+ erklaert = man.get(feld)
867
+ if erklaert is None:
868
+ err(f"clients/{pack}/manifest.json: Feld {feld} fehlt. Ein Pack führt "
869
+ f"beide Felder, notfalls leer - der Unterschied zwischen „nicht "
870
+ f"abgebildet“ und „gibt es nicht“ gehört "
871
+ f"deklariert und nicht aus einem fehlenden Feld erraten (D-155)")
872
+ continue
873
+ if not isinstance(erklaert, dict):
874
+ err(f"clients/{pack}/manifest.json: Feld {feld} ist kein Objekt (D-155)")
875
+ continue
876
+ eigen = cfg if feld == "settings_extra" else cfg.get("permissions", {})
877
+ fremd = cfg.get("permissions", {}) if feld == "settings_extra" else cfg
878
+ ebene = ("der obersten Ebene" if feld == "settings_extra"
879
+ else "dem Objekt permissions")
880
+ gegen = ("dem Objekt permissions" if feld == "settings_extra"
881
+ else "der obersten Ebene")
882
+ if not isinstance(eigen, dict):
883
+ continue
884
+ for schluessel, wert in erklaert.items():
885
+ if schluessel in eigen:
886
+ if eigen[schluessel] != wert:
887
+ err(f"{rel}: Schlüssel „{schluessel}“ trägt "
888
+ f"{json.dumps(eigen[schluessel], ensure_ascii=False)}, das "
889
+ f"Manifest deklariert in {feld} aber "
890
+ f"{json.dumps(wert, ensure_ascii=False)} (D-155)")
891
+ continue
892
+ if isinstance(fremd, dict) and schluessel in fremd:
893
+ err(f"{rel}: Schlüssel „{schluessel}“ steht in {gegen} "
894
+ f"statt in {ebene}. Das Manifest deklariert ihn in {feld}; auf der "
895
+ f"falschen Ebene wird er stillschweigend nicht gelesen (D-155)")
896
+ continue
897
+ err(f"{rel}: Schlüssel „{schluessel}“ aus {feld} fehlt in "
898
+ f"{ebene}. Das Pack liefert eine Verschärfung aus, die in der "
899
+ f"erzeugten Datei nicht ankommt (D-155)")
900
+
901
+
902
+ # --- Pruefung 74: Eine Matrixzeile steht in ihrer Tabelle ---------------------------
903
+ #
904
+ # ANLASS, UND ER STAND 73 RELEASES DA (gemessen 2026-09-22, D-264). Das Pack
905
+ # devin-desktop fuehrt sieben Modus-Zeilen. Zwischen M5 und M6 stand eine LEERZEILE -
906
+ # seit 0.26.0, dem Release, das M6 und M7 ueberhaupt erst angelegt hat, weil AP2-DD-03
907
+ # gefunden hatte, dass zwei geregelte Modi keine Matrixzeile haben. Die Abhilfe gab
908
+ # ihnen eine Zeile, die KEINE Tabellenzeile ist: Nach einer Leerzeile beginnt in
909
+ # Markdown ein neuer Block, und ein Block aus "| M6 | ... |" ohne Kopf- und Trennzeile
910
+ # ist ein Absatz. Beide Zeilen erscheinen im Hauptdokument als Fliesstext mit Strichen.
911
+ #
912
+ # UND DIE BUCHFUEHRUNG ZAEHLT SIE MIT: Die Zusammenfassung desselben Packs fuehrt
913
+ # "20 von 36" - 36 ist die Zeilenzahl EINSCHLIESSLICH M6 und M7. Die Zahl stimmte, die
914
+ # Tabelle zeigte sie nicht. Das ist der wiederkehrende Befundtyp mit umgekehrtem
915
+ # Vorzeichen: nicht der Text verspricht mehr als der Mechanismus haelt, sondern die
916
+ # Buchfuehrung sagt etwas ueber eine Zeile, die der Leser gar nicht als Zeile sieht.
917
+ #
918
+ # WARUM EINE EIGENE PRUEFUNG UND NICHT EIN TEIL VON 73. Der Gegenstand ist ein anderer:
919
+ # 73 fragt, WAS in einer Zeile steht, 74 fragt, ob sie ueberhaupt eine ist. Eine
920
+ # gebrochene Tabelle laesst 73 unberuehrt - sie liest die Zeile weiterhin, weil sie sie
921
+ # am Muster erkennt und nicht am Block. Genau deshalb hat 73 Releases lang niemand
922
+ # etwas gemerkt.
923
+ P74_TRENNZEILE_RE = re.compile(r"^\|\s*:?-{3,}")
924
+
925
+
926
+ def check_matrixzeile_in_tabelle(root: str) -> None:
927
+ """Pruefung 74 (D-264): Jede Matrixzeile steht in ihrer Tabelle."""
928
+ if P74_TRENNZEILE_RE.match("| M6 | synthetisch |"):
929
+ err("Pruefung 74: das eigene Muster haelt eine Datenzeile fuer eine "
930
+ "Trennzeile - sie meldete nichts mehr und bestuende leise (D-23)")
931
+ return
932
+ verz = os.path.join(root, KERN, "clients")
933
+ if not os.path.isdir(verz):
934
+ return
935
+ packs = sorted(n for n in os.listdir(verz)
936
+ if os.path.isfile(os.path.join(verz, n, "CLIENT_PACK.md")))
937
+ if not packs:
938
+ err(f"{KERN}/clients/: kein Client Pack mit CLIENT_PACK.md - Pruefung 74 "
939
+ f"haette keinen Gegenstand (D-23)")
940
+ return
941
+ for pack in packs:
942
+ rel = f"clients/{pack}/CLIENT_PACK.md"
943
+ zeilen = read(os.path.join(verz, pack, "CLIENT_PACK.md")) \
944
+ .replace("\r\n", "\n").split("\n")
945
+ drin = False
946
+ for i, zeile in enumerate(zeilen):
947
+ if zeile.startswith("## 2. "):
948
+ drin = True
949
+ continue
950
+ if drin and re.match(r"^## \d", zeile):
951
+ drin = False
952
+ if not drin or not P73_ZEILE_RE.match(zeile):
953
+ continue
954
+ # Rueckwaerts bis zur Trennzeile - ohne Leerzeile und ohne Fremdzeile.
955
+ j, getragen = i - 1, False
956
+ while j >= 0:
957
+ vor = zeilen[j].strip()
958
+ if not vor or not vor.startswith("|"):
959
+ break
960
+ if P74_TRENNZEILE_RE.match(vor):
961
+ getragen = True
962
+ break
963
+ j -= 1
964
+ if not getragen:
965
+ kennung = P73_ZEILE_RE.match(zeile).group(1)
966
+ err(f"{rel}:{i + 1}: Zeile {kennung} sieht aus wie eine Matrixzeile "
967
+ f"und steht in keiner Tabelle - zwischen ihr und der Trennzeile "
968
+ f"liegt eine Leerzeile oder Fremdtext. Markdown rendert sie als "
969
+ f"Absatz mit Strichen, im Pack wie im Hauptdokument, waehrend die "
970
+ f"Zusammenfassung desselben Packs sie mitzaehlt (D-264)")
971
+
972
+
973
+ # Pruefung 84: die Vorlage ist keine Client Pack (CR-2026-131, D-336)
974
+ # ---------------------------------------------------------------------------
975
+ #
976
+ # ANLASS, GEMESSEN MIT EINEM PROBEMANIFEST UND WIEDER ENTFERNT. `clients/README.md`
977
+ # Abschnitt 5 Schritt 1 sagt "`_template/` nach `<client-name>/` kopieren", Schritt 5
978
+ # sagt "`manifest.json` anlegen", und Abschnitt 3 sagt "Ohne Manifest ist ein Pack nicht
979
+ # installierbar". Die Vorlage traegt EINES VON DREI Bestandteilen.
980
+ #
981
+ # 🔴 UND SIE STEHT IN DER PACKMENGE DES PRUEFAPPARATS. `_client_packs()` nimmt jedes
982
+ # Verzeichnis unter `clients/` auf, das eine CLIENT_PACK.md traegt - `_template` erfuellt
983
+ # das. Der Docstring der Funktion hielt die Annahme fest, die sie trug: "_template ohne
984
+ # Manifest".
985
+ #
986
+ # GEMESSEN am 2026-09-23: Ein Probemanifest angelegt (eine Kopie des Manifests von
987
+ # `claude-code`, also eines, das einen FREMDEN Client beschreibt), Validator gefahren,
988
+ # Probemanifest entfernt. `_client_packs()` lieferte drei Packs MIT Manifest, und der
989
+ # Validator meldete 0 Fehler, 0 Warnungen.
990
+ # ➡️ EINE VORLAGE, DIE NUR DESHALB KEINE PRUEFUNG AUSLOEST, WEIL IHR EIN BESTANDTEIL
991
+ # FEHLT, IST NICHT AUSGENOMMEN - SIE IST UNVOLLSTAENDIG. Und der Tag, an dem jemand sie
992
+ # nach der eigenen Anleitung vervollstaendigt, ist der Tag, an dem sie geprueft wird,
993
+ # ohne dass es jemand entschieden hat.
994
+ #
995
+ # 🔴 DIE BAUFORM "ZWEI STELLEN, DIE EINANDER DECKEN" (0.57.0): Die unvollstaendige
996
+ # Vorlage verhindert, dass die fehlende Ausnahme je auffaellt. Einzeln waere jede
997
+ # aufgefallen; zusammen sahen sie aus wie ein Lauf ohne Befund.
998
+ #
999
+ # ⚠️ UND DIE AUSNAHME EXISTIERTE BEREITS - AN ZWEI ANDEREN STELLEN. Pruefung 73 schliesst
1000
+ # `_template` ausdruecklich aus, und LINK_PATH_EXCEPTIONS fuehrt den Pfad mit Begruendung.
1001
+ # Der zentrale Iterator tat es nicht.
1002
+ # ➡️ WER EINE AUSNAHME AN ZWEI STELLEN FUEHRT UND AN DER DRITTEN VERGISST, HAT SIE NICHT
1003
+ # VERGESSEN - ER HAT KEINE STELLE, AN DER SIE STEHT. Seit D-336 steht sie in
1004
+ # `_client_packs()`, also dort, wo die Packmenge ENTSTEHT.
1005
+ #
1006
+ # ZUSCHNITT: Diese Pruefung haelt fest, was die Ausnahme voraussetzt - dass die Vorlage
1007
+ # eine Vorlage BLEIBT. Traegt sie die Bestandteile eines vollstaendigen Packs, ist
1008
+ # entweder die Ausnahme falsch oder die Vorlage ein Pack; beides gehoert entschieden und
1009
+ # nicht stillschweigend gefahren.
1010
+ #
1011
+ # 🔴 GRENZE, BENANNT: Sie prueft die ANWESENHEIT von Bestandteilen, nicht deren Inhalt.
1012
+ # Eine `CLIENT_PACK.md`, die einen echten Client beschreibt statt Platzhalter zu fuehren,
1013
+ # kommt durch - das faengt Pruefung 7 ueber die Markerform.
1014
+ P84_VORLAGE = "_template"
1015
+ P84_VERBOTEN = ("manifest.json", "root-template")
1016
+
1017
+
1018
+ def check_vorlage_kein_pack(root: str) -> None:
1019
+ """Pruefung 84 (D-336): Die Client-Pack-Vorlage traegt keine Packbestandteile."""
1020
+ vdir = os.path.join(root, KERN, "clients", P84_VORLAGE)
1021
+ if not os.path.isdir(vdir):
1022
+ err(f"{KERN}/clients/{P84_VORLAGE}/: fehlt. `clients/README.md` Abschnitt 5 "
1023
+ f"Schritt 1 baut jedes neue Client Pack aus ihr, und `_client_packs()` "
1024
+ f"nimmt sie seit D-336 ausdruecklich NICHT auf - ohne den Gegenstand "
1025
+ f"bestuende Pruefung 84 leise (D-23, D-336)")
1026
+ return
1027
+ if not os.path.isfile(os.path.join(vdir, "CLIENT_PACK.md")):
1028
+ err(f"{KERN}/clients/{P84_VORLAGE}/CLIENT_PACK.md: fehlt. Sie ist der einzige "
1029
+ f"Bestandteil, den die Vorlage traegt (D-336)")
1030
+ for name in P84_VERBOTEN:
1031
+ pfad = os.path.join(vdir, name)
1032
+ if not os.path.exists(pfad):
1033
+ continue
1034
+ err(f"{KERN}/clients/{P84_VORLAGE}/{name}: die Vorlage traegt einen Bestandteil, "
1035
+ f"den nur ein vollstaendiges Client Pack traegt. `_client_packs()` nimmt "
1036
+ f"`{P84_VORLAGE}` seit D-336 nicht auf - damit liefe dieser Bestandteil "
1037
+ f"ungeprueft mit, waehrend er in jedem echten Pack geprueft wird. Gemessen "
1038
+ f"am 2026-09-23 mit einem Probemanifest: alle Pruefungen blieben gruen. "
1039
+ f"Entweder ist die Ausnahme falsch oder die Vorlage ein Pack - beides "
1040
+ f"gehoert entschieden (D-336)")
1041
+
1042
+
1043
+ # ---------------------------------------------------------------------------
1044
+ # PRUEFUNG 87: DIE FORMATGEBUNDENEN PRUEFUNGEN STEHEN IM PACK
1045
+ # ---------------------------------------------------------------------------
1046
+ # ANLASS (CR-2026-133, D-346). Mit dem dritten Client Pack gibt es zum ersten Mal zwei
1047
+ # Ausgabeformen der Berechtigungsdatei. Zwoelf Pruefungen lesen die eine; fuer die
1048
+ # andere haben sie keinen Gegenstand. Sie still zu ueberspringen, waere die Bauform von
1049
+ # 0.57.0 - "zwei Stellen, die einander decken": Der Validator liefe gruen, und niemand
1050
+ # wuesste, dass sechs Pruefungen dieses Pack nicht erreichen.
1051
+ # ➡️ Eine Luecke, die erklaert ist, ist eine Aussage; eine, die nur besteht, ist ein
1052
+ # blinder Fleck.
1053
+ # Diese Pruefung verlangt deshalb: Ein Pack mit einer anderen Ausgabeform nennt in
1054
+ # Abschnitt 5 seines CLIENT_PACK.md JEDE Nummer aus FORMATGEBUNDENE_PRUEFUNGEN - und
1055
+ # keine Nummer mehr, die dort nicht steht.
1056
+ # ⚠️ GRENZE, BENANNT: Sie prueft die NENNUNG, nicht die Richtigkeit der Begruendung -
1057
+ # dieselbe Bauform wie Pruefung 19. Und sie haengt daran, dass jemand eine neu
1058
+ # guardete Pruefung in die Menge eintraegt; die Funktion `formatgebunden` erzwingt
1059
+ # das, indem sie eine unbekannte Nummer als Fehler wirft.
1060
+ def check_formatgebundene_pruefungen(root: str) -> None:
1061
+ """Pruefung 87 (D-346): Ein Pack mit eigener Ausgabeform nennt die Pruefungen,
1062
+ die es damit nicht erreichen."""
1063
+ packs = os.path.join(root, KERN, "clients")
1064
+ if not os.path.isdir(packs):
1065
+ return
1066
+ for name in sorted(os.listdir(packs)):
1067
+ mf = os.path.join(packs, name, "manifest.json")
1068
+ if not os.path.exists(mf):
1069
+ continue
1070
+ try:
1071
+ man = json.loads(read(mf))
1072
+ except (OSError, ValueError):
1073
+ continue
1074
+ if man.get("permissions_format", "json") == "json":
1075
+ continue
1076
+ pack = os.path.join(packs, name, "CLIENT_PACK.md")
1077
+ if not os.path.exists(pack):
1078
+ continue
1079
+ text = read(pack)
1080
+ abschnitt = re.split(r"^## 5\.", text, maxsplit=1, flags=re.M)
1081
+ if len(abschnitt) < 2:
1082
+ err(f"{KERN}/clients/{name}/CLIENT_PACK.md: Abschnitt 5 fehlt; dort gehört "
1083
+ f"die Liste der Prüfungen, die dieses Pack wegen seiner Ausgabeform "
1084
+ f"'{man['permissions_format']}' nicht erreichen (D-346)")
1085
+ continue
1086
+ rumpf = re.split(r"^## 6\.", abschnitt[1], maxsplit=1, flags=re.M)[0]
1087
+ genannt = {int(n) for n in re.findall(r"Prüfung\s+(\d+)", rumpf)}
1088
+ erwartet = nicht_erreicht(man)
1089
+ fehlend = sorted(erwartet - genannt)
1090
+ if fehlend:
1091
+ err(f"{KERN}/clients/{name}/CLIENT_PACK.md Abschnitt 5: die Prüfung(en) "
1092
+ f"{', '.join(str(n) for n in fehlend)} sind an die Ausgabeform 'json' "
1093
+ f"gebunden und erreichen dieses Pack nicht – der Abschnitt nennt sie "
1094
+ f"nicht. Eine Lücke, die nirgends steht, ist ein blinder Fleck (D-346)")
1095
+ # Gegenrichtung, seit 1.13.0 je Form (D-416): Genannt werden darf nur, was
1096
+ # DIESE Form nicht erreicht - auch eine Nummer der Menge, die das Pack doch
1097
+ # erreicht, ist eine behauptete Luecke.
1098
+ zuviel = sorted(genannt - erwartet)
1099
+ if zuviel:
1100
+ err(f"{KERN}/clients/{name}/CLIENT_PACK.md Abschnitt 5: die Prüfung(en) "
1101
+ f"{', '.join(str(n) for n in zuviel)} werden als formatgebunden "
1102
+ f"genannt, erreichen die Ausgabeform '{man['permissions_format']}' "
1103
+ f"aber (FORMATGEBUNDENE_PRUEFUNGEN). Eine behauptete Lücke, die es nicht "
1104
+ f"gibt, ist so falsch wie eine verschwiegene (D-346, D-416)")
1105
+
1106
+
1107
+ # ---------------------------------------------------------------------------
1108
+ # Pruefung 105: Die gepruefte Clientversion liegt in der Zielspanne (CR-2026-161, D-482)
1109
+ # ---------------------------------------------------------------------------
1110
+ #
1111
+ # ANLASS (K-40). Die Zielspanne ist eine Festlegung des Framework Owners (D-112, D-113),
1112
+ # und keine Pruefung hielt sie. Beim Pack claude-code stand der Punktwert 2.1.267 ueber
1113
+ # vierzig Releases unveraendert, bis er sechs Patchstaende alt war - der unbezahlte Preis
1114
+ # von D-113.
1115
+ #
1116
+ # WAS SIE PRUEFT, je Client Pack an den Steckbriefzeilen "Verbindliche Zielversion" und
1117
+ # "Gepruefte Clientversion":
1118
+ # (1) Die ERSTE Punktversion der geprueften Zeile liegt in einer Spanne der Zielzeile.
1119
+ # Weitere Punktversionen duerfen Nebenbestandteile sein (kiro nennt den
1120
+ # Agentenserver, cursor die installierte IDE).
1121
+ # (2) Jede Spanne der Zielzeile ist durch eine Punktversion der geprueften Zeile
1122
+ # belegt - eine gehobene Spanne ohne Messung faellt so auf.
1123
+ # Eine Spanne ist ein Wert wie `2.1.x` IN BACKTICKS; "liegt in" heisst: beginnt mit
1124
+ # "2.1.". Die Backticks sind der Unterschied zwischen einer Festlegung und einer
1125
+ # Erwaehnung: devin-desktop schreibt in dieselbe Zelle, die Spanne werde "ohne Messung
1126
+ # gegen 3.10.x nicht gehoben" - der erste Entwurf dieser Pruefung hielt das fuer eine
1127
+ # Zielspanne ohne Messung.
1128
+ #
1129
+ # GRENZE, UND SIE STEHT AUCH IN DER MELDUNG: Das ist eine Aussage ueber die
1130
+ # SCHREIBWEISE. Dass 3.9.19 in 3.9.x liegt, belegt nicht, dass der Mechanismus in der
1131
+ # ganzen Spanne gleich ist. Deshalb eine WARNUNG und kein Fehler (D-482).
1132
+ P105_ZIEL = "Verbindliche Zielversion"
1133
+ P105_GEPRUEFT = "Geprüfte Clientversion"
1134
+ P105_SPANNE_RE = re.compile(r"`(\d+(?:\.\d+)*)\.x`")
1135
+ P105_VERSION_RE = re.compile(r"(?<![\w.])(\d+(?:\.\d+)+)(?:-[0-9a-z]+)?(?![\w.])")
1136
+ P105_GRENZE = ("Ein Praefixvergleich der Schreibweise, kein Beleg, dass der Mechanismus "
1137
+ "in der ganzen Spanne gleich ist (K-40, D-482)")
1138
+
1139
+
1140
+ def _p105_zelle(text: str, feld: str) -> str | None:
1141
+ """Die Wertzelle der Steckbriefzeile `feld` - oder None."""
1142
+ for zeile in text.splitlines():
1143
+ if not zeile.lstrip().startswith("|"):
1144
+ continue
1145
+ zellen = tabellenzellen(zeile)
1146
+ if len(zellen) >= 2 and zellen[0] == feld:
1147
+ return zellen[1]
1148
+ return None
1149
+
1150
+
1151
+ def check_clientversion_in_spanne(root: str) -> None:
1152
+ """Pruefung 105 (D-482): Die gepruefte Clientversion liegt in der Zielspanne."""
1153
+ basis = os.path.join(root, KERN, "clients")
1154
+ if not os.path.isdir(basis):
1155
+ return
1156
+ for pack in sorted(os.listdir(basis)):
1157
+ pfad = os.path.join(basis, pack, "CLIENT_PACK.md")
1158
+ if pack.startswith("_") or not os.path.isfile(pfad):
1159
+ continue
1160
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
1161
+ text = read(pfad)
1162
+ ziel, geprueft = _p105_zelle(text, P105_ZIEL), _p105_zelle(text, P105_GEPRUEFT)
1163
+ if ziel is None or geprueft is None:
1164
+ warn(f"{rel}: Pruefung 105 findet die Steckbriefzeile "
1165
+ f"'{P105_ZIEL if ziel is None else P105_GEPRUEFT}' nicht und haelt die "
1166
+ f"Zielspanne dieses Packs nicht (K-40, D-482)")
1167
+ continue
1168
+ spannen = P105_SPANNE_RE.findall(ziel)
1169
+ versionen = [m.group(1) for m in P105_VERSION_RE.finditer(geprueft)]
1170
+ if not spannen or not versionen:
1171
+ warn(f"{rel}: Pruefung 105 findet in der Zeile "
1172
+ f"'{P105_ZIEL if not spannen else P105_GEPRUEFT}' keine "
1173
+ f"{'Spanne der Form N.N.x' if not spannen else 'Punktversion'} und haelt "
1174
+ f"die Zielspanne dieses Packs nicht (K-40, D-482)")
1175
+ continue
1176
+ if not any(versionen[0].startswith(s + ".") for s in spannen):
1177
+ warn(f"{rel}: die gepruefte Clientversion {versionen[0]} liegt in keiner "
1178
+ f"Zielspanne ({', '.join(s + '.x' for s in spannen)}). {P105_GRENZE}")
1179
+ for s in spannen:
1180
+ if not any(v.startswith(s + ".") for v in versionen):
1181
+ warn(f"{rel}: die Zielspanne {s}.x ist durch keine gepruefte Version "
1182
+ f"belegt - gehoben, ohne gemessen zu sein? {P105_GRENZE}")
1183
+
1184
+
1185
+ # --- Pruefung 108: ein allow-Muster, das ein deny-Praefix umschliesst (CR-2026-163, D-494, K-47)
1186
+ #
1187
+ # ANLASS, GEMESSEN (D-123, 2026-09-17): Bei allow = Bash(git:*) und deny = Bash(git push:*)
1188
+ # wies der Client 'git push' ab und liess 'git -C <pfad> push' durch - der Commit stand am
1189
+ # Remote. Die ausgelieferte Fassung haelt, weil der allow-Korb nur fuenf lesende git-Befehle
1190
+ # fuehrt. Wer ihn verbreitert, verliert den Schutz ohne Meldung; keine Pruefung sah es.
1191
+ #
1192
+ # DIE BILLIGE ZWISCHENSTUFE, NICHT DIE SACHE: Gemeldet wird, wenn der Befehl eines
1193
+ # allow-Eintrags ein echtes Wortpraefix des Befehls eines deny-Eintrags desselben Werkzeugs
1194
+ # ist. Befehlsaequivalenz ('git -C x push' = 'git push') erkennt keine Zeichenkettenpruefung.
1195
+ # WARNUNG, kein Fehler: Ein Projekt darf den Korb bewusst weiten; es soll es wissen.
1196
+ #
1197
+ # GRENZEN, BENANNT: nur Befehlsregeln der Packs mit Praefixabgleich (permission_exec_match
1198
+ # 'prefix') und JSON-Korb - gemessen ist allein Bash(...). Read- und Edit-Pfadmuster sind
1199
+ # nicht gemessen und bleiben aussen vor. Ein leeres allow-Muster ('*') meldet die
1200
+ # Pruefung ebenfalls: Es umschliesst jedes deny-Praefix.
1201
+ P108_KERNQUELLE = "framework/runtime/permissions.json"
1202
+ P108_GRENZE = ("Die Prüfung vergleicht die Schreibweise, nicht die Befehlsäquivalenz "
1203
+ "(K-47, D-123)")
1204
+ P108_REGEL_RE = re.compile(r"^([A-Za-z_][\w-]*)\((.*)\)$")
1205
+
1206
+
1207
+ def _p108_befehl(muster: str, suffix: str) -> str:
1208
+ """Der Befehl eines Praefixmusters ohne Suffix und Stern ('git:*' -> 'git')."""
1209
+ m = muster.strip()
1210
+ for ende in (suffix, ":*", " *", "*"):
1211
+ if ende and m.endswith(ende):
1212
+ m = m[:-len(ende)]
1213
+ break
1214
+ return m.strip()
1215
+
1216
+
1217
+ def _p108_umschliesst(erlaubt: str, verboten: str) -> bool:
1218
+ return erlaubt != verboten and (not erlaubt or verboten.startswith(erlaubt + " "))
1219
+
1220
+
1221
+ def _p108_paare(allow: list, deny: list) -> list:
1222
+ """(allow, deny) je Paar, dessen allow-Befehl das deny-Praefix umschliesst."""
1223
+ return [(a, d) for a in allow for d in deny if _p108_umschliesst(a, d)]
1224
+
1225
+
1226
+ def check_allow_umschliesst_deny(root: str, man: dict) -> None:
1227
+ """Pruefung 108 (D-494, K-47): kein allow-Befehl umschliesst ein deny-Praefix."""
1228
+ kq = os.path.join(root, KERN, *P108_KERNQUELLE.split("/"))
1229
+ if os.path.isfile(kq):
1230
+ try:
1231
+ quelle = json.loads(read(kq))
1232
+ except json.JSONDecodeError:
1233
+ quelle = {}
1234
+
1235
+ def befehle(korb: str) -> list:
1236
+ return [(r.get("prefix") or r.get("command") or "").strip()
1237
+ for r in quelle.get(korb, []) if isinstance(r, dict)
1238
+ and r.get("tool") == "exec" and r.get("command")
1239
+ and not PROJEKTPLATZHALTER.search(r.get("command"))]
1240
+ for a, d in _p108_paare(befehle("allow"), befehle("deny")):
1241
+ warn(f"{KERN}/{P108_KERNQUELLE}: der allow-Befehl '{a}' umschließt das "
1242
+ f"deny-Präfix '{d}' - bei Praefixabgleich läuft '{d}' in einer anderen "
1243
+ f"Schreibweise durch. {P108_GRENZE}")
1244
+ if man.get("permission_exec_match") != "prefix":
1245
+ return
1246
+ rel = man.get("permissions_file") or ""
1247
+ pfad = os.path.join(root, *rel.split("/")) if rel else ""
1248
+ if not rel.endswith(".json") or not os.path.isfile(pfad):
1249
+ return
1250
+ try:
1251
+ cfg = json.loads(read(pfad))
1252
+ except json.JSONDecodeError:
1253
+ return # check_config hat das bereits gemeldet
1254
+ koerbe = cfg.get("permissions") if isinstance(cfg.get("permissions"), dict) else cfg
1255
+ werkzeuge = set((man.get("permission_tools") or {}).get("exec") or ())
1256
+ suffix = man.get("permission_exec_suffix") or ""
1257
+
1258
+ def regeln(korb: str) -> dict:
1259
+ je: dict = {}
1260
+ for r in koerbe.get(korb, []) if isinstance(koerbe.get(korb), list) else []:
1261
+ m = P108_REGEL_RE.match(r.strip()) if isinstance(r, str) else None
1262
+ if m and m.group(1) in werkzeuge and not PROJEKTPLATZHALTER.search(m.group(2)):
1263
+ je.setdefault(m.group(1), []).append(_p108_befehl(m.group(2), suffix))
1264
+ return je
1265
+ erlaubt, verboten = regeln("allow"), regeln("deny")
1266
+ for werkzeug in sorted(werkzeuge):
1267
+ for a, d in _p108_paare(erlaubt.get(werkzeug, []), verboten.get(werkzeug, [])):
1268
+ warn(f"{rel}: {werkzeug}({a}{suffix}) im allow-Korb umschließt "
1269
+ f"{werkzeug}({d}{suffix}) im deny-Korb - gemessen lief 'git -C <pfad> push' "
1270
+ f"an Bash(git push:*) vorbei, als Bash(git:*) erlaubt war. {P108_GRENZE}")
1271
+
1272
+ # --- Pruefung 109: die beiden Werkzeugfelder eines Skills (CR-2026-164, D-505, K-93)
1273
+ #
1274
+ # ANLASS, GEMESSEN (D-287, 2026-09-22 an devin-desktop): Die Werkzeugbeschraenkung eines
1275
+ # Skills wirkt dort nur, wenn das Frontmatter BEIDE Felder traegt - 'allowed-tools' ohne das
1276
+ # Werkzeug UND einen 'permissions'-Block. Jedes Feld allein bleibt folgenlos, und der Client
1277
+ # meldet nichts. Alle ausgelieferten Skills tragen beide; der Validator verlangte nur
1278
+ # 'allowed-tools'. Ein neuer Skill mit nur einem Feld haette eine Beschraenkung behauptet,
1279
+ # die er nicht hat.
1280
+ #
1281
+ # GEPRUEFT: jede Ablage, in der das Feld 'permissions' die installierte Fassung erreicht -
1282
+ # die Quellablagen des Kerns und der Packs und die Skill-Ablage eines Packs, das
1283
+ # 'permissions' nicht verwirft. Bei claude-code wird es auf 'disallowed-tools' abgebildet
1284
+ # (D-64); dort haelt Pruefung 33 die Abbildung.
1285
+ # GRENZE: die Anwesenheit beider Felder, nicht ihre Wirkung - warum der Client beide
1286
+ # braucht, sagt keine Quelle des Herstellers.
1287
+ P109_FELDER = ("allowed-tools", "permissions")
1288
+
1289
+
1290
+ def _p109_felder(text: str) -> set:
1291
+ """Die Werkzeugfelder, die das Frontmatter auf oberster Ebene fuehrt."""
1292
+ m = re.match(r"^---[ \t]*\r?\n(.*?)\r?\n---[ \t]*(\r?\n|$)", text, re.S)
1293
+ if not m:
1294
+ return set()
1295
+ return {f for f in P109_FELDER if re.search(r"^" + re.escape(f) + r"[ \t]*:", m.group(1), re.M)}
1296
+
1297
+
1298
+ def check_skill_werkzeugfelder(root: str, man: dict) -> None:
1299
+ """Pruefung 109 (D-505, K-93): Fuehrt ein Skill ein Werkzeugfeld, fuehrt er beide."""
1300
+ runtime = man.get("skills_dir")
1301
+ verworfen = "permissions" in ((man.get("skill_frontmatter") or {}).get("drop_fields") or [])
1302
+ for skills_dir, prefix in skill_dirs(root, man):
1303
+ if prefix == runtime and verworfen:
1304
+ continue
1305
+ for name in sorted(os.listdir(skills_dir)):
1306
+ pfad = os.path.join(skills_dir, name, "SKILL.md")
1307
+ if not os.path.isfile(pfad):
1308
+ continue
1309
+ da = _p109_felder(read(pfad))
1310
+ if da and da != set(P109_FELDER):
1311
+ fehlt = [f for f in P109_FELDER if f not in da][0]
1312
+ err(f"{prefix}/{name}/SKILL.md: führt {sorted(da)[0]}, aber nicht {fehlt}. "
1313
+ f"Die Werkzeugbeschränkung eines Skills wirkt nur mit beiden Feldern; "
1314
+ f"eines allein bleibt ohne Meldung folgenlos (D-287, K-93)")