@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,1559 @@
1
+ """Die Berechtigungsschicht: Koerbe, Importsteuerung, Skill-Sperren, Agentenprofile,
2
+ Werkzeugabbildung, Skillfreigaben, Mandat, Modellaufruf und MCP-Freigaben.
3
+
4
+ Pruefungen 22, 33, 34, 35, 37, 38, 39, 72, 88, 96, 97, 99, 100 und 101. Teil des
5
+ Validators validate-framework.py, seit 1.19.1 nach Gegenstand in Module geteilt (K-174).
6
+ Das Register aller Pruefungen steht im Kopfkommentar des Einstiegs, die Grenze jeder
7
+ einzelnen in ihrem Kopfkommentar hier."""
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import json
12
+ import os
13
+ import re
14
+ import sys
15
+
16
+ from .gemeinsam import (
17
+ _hook_interpreter, _hook_lauf, err, formatgebunden, fremde_praefixe, FRONTMATTER_BLOECKE,
18
+ KERN,
19
+ korb_zerlegung, parse_frontmatter, read, skill_dirs, soll_korbregeln,
20
+ tabellenzellen, TBD_RE, warn, yaml)
21
+
22
+
23
+ def installierte_skills(root: str, man: dict) -> set:
24
+ """Die Skills, die in DIESER Installation liegen - abgeleitet aus der Ablage."""
25
+ rel = man.get("skills_dir")
26
+ if not rel:
27
+ return set()
28
+ ablage = os.path.join(root, *rel.split("/"))
29
+ if not os.path.isdir(ablage):
30
+ return set()
31
+ return {n for n in os.listdir(ablage)
32
+ if os.path.isfile(os.path.join(ablage, n, "SKILL.md"))}
33
+
34
+
35
+ def skillfreigaben(root: str, man: dict) -> set:
36
+ """Die Korbeintraege, die einen Skill DIESER Installation beim Namen nennen.
37
+
38
+ \U0001F534 ZWEI PRUEFUNGEN DESSELBEN REPOSITORIUMS STANDEN GEGENEINANDER, UND DAS
39
+ IST GEMESSEN (2026-09-21, Herrichtung von Buendel 5). Pruefung 72 verlangt seit
40
+ `0.81.0` zu jedem Skill der Installation einen Eintrag der Berechtigungsdatei -
41
+ das ist der dritte Teil der Aktivierung eines Packs (D-238). Pruefung 37 hielt
42
+ denselben Eintrag fuer eine AUSWEITUNG, weil die Kernquelle ihn nicht erzeugt:
43
+ Am Messbaum von Buendel 5 meldete sie `Skill(role-re-ticket)` als eine Regel zu
44
+ viel im allow-Korb.
45
+
46
+ Damit war die Abhilfe von D-238 in keinem Projekt umsetzbar, ohne den eigenen
47
+ Validator rot zu faerben - und `0.81.0` hat das nicht gesehen, weil es den
48
+ Korbeintrag beim Messen noch gar nicht gab.
49
+
50
+ Wer eine Pruefung baut, die etwas VERLANGT, fragt, ob eine andere desselben
51
+ Repositoriums es VERBIETET.
52
+
53
+ Ein solcher Eintrag ist keine Ausweitung: Pruefung 72 deckt beide Richtungen ab -
54
+ ein Skill ohne Eintrag und ein Eintrag ohne Skill werden beide gemeldet. Was
55
+ bleibt, ist genau die Menge, die das Projekt durch seine Aktivierung entschieden
56
+ hat, und die Menge wird aus der Skillablage ABGELEITET, nicht gepflegt.
57
+ """
58
+ werkzeuge = tuple(w for w in (man.get("permission_tools") or {}).get("skill") or ()
59
+ if isinstance(w, str))
60
+ if not werkzeuge:
61
+ return set()
62
+ namen = installierte_skills(root, man)
63
+ return {f"{w}({n})" for w in werkzeuge for n in namen}
64
+
65
+
66
+ def _fremder_skilleintrag(regel: str, man: dict, fremd) -> bool:
67
+ """Der Korbeintrag eines deklarierten fremden Skills (1.21.0, K-31, D-514), auch als Muster."""
68
+ if not fremd:
69
+ return False
70
+ m = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\(([^()]*)\)$", regel.strip())
71
+ namen = [w for w in (man.get("permission_tools", {}).get("skill") or []) if isinstance(w, str)]
72
+ return bool(m and m.group(1) in namen and any(m.group(2).startswith(p) for p in fremd))
73
+
74
+
75
+ def check_berechtigungskoerbe(root: str, man: dict) -> None:
76
+ """Pruefung 37: Was die Kernquelle erzeugt, steht in der installierten Datei.
77
+
78
+ Warum nicht einfach _core_rules_integrity auf alle Regeln erweitern: Das faengt die
79
+ geloeschte und die verengte Regel, aber weder die ergaenzte allow-Zeile noch den
80
+ geleerten ask-Korb. Die Liste sagt, was fehlen darf - nicht, was zuviel sein darf.
81
+
82
+ Ein Platzhalterschlitz (Bash(<TEST_COMMAND>) und die drei Pfadschlitze) ist der
83
+ einzige Teil dieser Datei, der dem Projekt gehoert. Er darf gefuellt sein, gefuellt
84
+ zaehlt er gegen den Ueberschuss - und er darf das Praefixzeichen des Clients nicht
85
+ tragen: clientmap._befehl haengt es an einen offenen Projektplatzhalter bewusst nicht
86
+ an, weil der Overlay Owner dort den vollstaendigen Befehl eintraegt. Von Hand
87
+ nachgetragen macht es aus der Freigabe eines Befehls die Freigabe einer
88
+ Befehlsfamilie - am Piloten am 2026-09-13 so vorgefunden.
89
+ """
90
+ if formatgebunden(man, 37):
91
+ return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
92
+ rel = man["permissions_file"]
93
+ path = os.path.join(root, *rel.split("/"))
94
+ if not os.path.exists(path):
95
+ return
96
+ try:
97
+ cfg = json.loads(read(path))
98
+ except json.JSONDecodeError:
99
+ return # check_config hat das bereits gemeldet
100
+ soll = soll_korbregeln(root, man)
101
+ if soll is None:
102
+ return
103
+ if not any(soll.values()):
104
+ err(f"{KERN}/framework/runtime/permissions.json: die Abbildung erzeugt für das "
105
+ f"Client Pack {man.get('client', '?')} keine einzige Regel – Prüfung 37 hätte "
106
+ f"nichts zu vergleichen und bestünde leise")
107
+ return
108
+
109
+ perms = cfg.get("permissions", {})
110
+ exec_werkzeuge = tuple(man.get("permission_tools", {}).get("exec", ()))
111
+ praefixzeichen = (man.get("permission_exec_suffix", ":*")
112
+ if man.get("permission_exec_match") == "prefix" else None)
113
+ skillkorb = skillfreigaben(root, man)
114
+
115
+ for korb in ("deny", "ask", "allow"):
116
+ ist = [r for r in perms.get(korb, []) if isinstance(r, str)]
117
+ pflicht, schlitze, ungefuellt, zusatz = korb_zerlegung(ist, soll[korb])
118
+ for regel in pflicht:
119
+ if regel not in ist:
120
+ err(f"{rel}: die Kernquelle erzeugt für den {korb}-Korb die Regel "
121
+ f"'{regel}'; dort steht sie nicht. Eine fehlende Regel ist eine "
122
+ f"Lockerung, gleich in welchem Korb – Änderungen an der Regelmenge "
123
+ f"laufen über einen Änderungsantrag (V10, D-77)")
124
+ if korb == "deny":
125
+ # Eine zusaetzliche deny-Regel ist eine Verschaerfung und deshalb zulaessig.
126
+ continue
127
+ # Der Korbeintrag eines aktivierten Pack- oder Projektskills ist keine
128
+ # Ausweitung, sondern der dritte Teil seiner Aktivierung - Pruefung 72
129
+ # verlangt ihn und deckt beide Richtungen ab (D-238, D-243).
130
+ zusatz = [r for r in zusatz if r not in skillkorb]
131
+ # Ebenso der Eintrag eines deklarierten fremden Skills (1.21.0, K-31, D-514):
132
+ # Pruefung 72 verlangt ihn, Pruefung 111 haelt die Deklaration.
133
+ zusatz = [r for r in zusatz if not _fremder_skilleintrag(r, man, fremde_praefixe(root))]
134
+ # Gedeckt wird ein Ueberschuss nur von den Schlitzen, die NICHT mehr
135
+ # woertlich dastehen - ein woertlich vorhandener Schlitz ist ungefuellt.
136
+ offen = [s for s in schlitze if s not in ungefuellt]
137
+ if len(zusatz) > len(offen):
138
+ err(f"{rel}: der {korb}-Korb führt {len(zusatz)} Regel(n), die die Kernquelle "
139
+ f"nicht erzeugt, bei {len(offen)} gefüllten Platzhalterschlitz(en): "
140
+ f"{', '.join(zusatz)}. Eine zusätzliche Freigabe ist eine Ausweitung und "
141
+ f"nicht Sache des Overlays; ein weiterer freigegebener Befehl gehört in "
142
+ f"Abschnitt 6 des Overlays und damit in die Regelschicht (D-76)")
143
+ if not praefixzeichen or len(zusatz) > len(offen):
144
+ # Kein Praefixzeichen: Dieser Client sperrt Befehle woertlich.
145
+ # Ueberschuss groesser als die offenen Schlitze: Dann ist nicht entschieden,
146
+ # welche Zeile ein gefuellter Schlitz ist und welche eine hinzugefuegte
147
+ # Freigabe - und eine Meldung, die "gefuellter Platzhalterschlitz" sagt, wo
148
+ # keiner ist, sagt etwas anderes als der Fall hergibt. Die Meldung darueber
149
+ # benennt den Ueberschuss bereits richtig.
150
+ continue
151
+ for regel in zusatz:
152
+ teile = regel.split("(", 1)
153
+ if len(teile) != 2 or teile[0] not in exec_werkzeuge:
154
+ continue
155
+ inhalt = teile[1][:-1] if teile[1].endswith(")") else teile[1]
156
+ if inhalt.endswith(praefixzeichen):
157
+ err(f"{rel}: '{regel}' im {korb}-Korb trägt das Präfixzeichen "
158
+ f"'{praefixzeichen}'. Die Abbildung hängt es an einen gefüllten "
159
+ f"Projektplatzhalter bewusst nicht an – von Hand nachgetragen macht "
160
+ f"es aus der Freigabe eines Befehls die Freigabe einer "
161
+ f"Befehlsfamilie (D-77)")
162
+
163
+
164
+ def check_importsteuerung(root: str, man: dict) -> None:
165
+ """Pruefung 22 (D-37): Die Importsteuerung steht so, wie das Manifest sie abbildet.
166
+
167
+ GRENZE - vorab benannt wie bei Pruefung 19: Sie belegt **Anwesenheit und
168
+ Uebereinstimmung, nicht Richtigkeit und erst recht nicht Wirkung.** Dass die
169
+ Einstellung in der Datei steht, heisst nicht, dass sie greift: Die Benutzer-
170
+ konfiguration dieser Arbeitsstation hat Vorrang, in beide Richtungen gemessen
171
+ (K-27). Die Masznahme ist ein Standard, keine Schranke. Was wirkt, misst nur eine
172
+ Sitzung, die den Kontext prueft - nicht das Register des Clients (ERH-02).
173
+
174
+ Ein Pack ohne das Feld laeuft durch: Kennt ein Client keinen solchen Mechanismus -
175
+ oder liefert das Framework bewusst keine Vorgabe aus, wie bei claude-code -, bleibt
176
+ es bei der Auskunft im Client Pack (CR-2026-038 E2).
177
+ """
178
+ steuerung = man.get("import_control")
179
+ rechte = man.get("permissions_file")
180
+ if not rechte:
181
+ return
182
+ pfad = os.path.join(root, *rechte.split("/"))
183
+ if not os.path.isfile(pfad):
184
+ return
185
+ try:
186
+ installiert = json.loads(read(pfad))
187
+ except Exception:
188
+ return # ungueltiges JSON meldet Pruefung 2
189
+ if not steuerung:
190
+ return
191
+ schluessel = steuerung.get("key")
192
+ erwartet = steuerung.get("value")
193
+ if not schluessel:
194
+ err(f"{man.get('client', '?')}/manifest.json: import_control ohne 'key'")
195
+ return
196
+ ist = installiert.get(schluessel)
197
+ if ist is None:
198
+ err(f"{rechte}: Die Importsteuerung '{schluessel}' fehlt. Das Manifest des Packs "
199
+ f"'{man.get('client', '?')}' bildet sie ab (D-37): Das Framework importiert "
200
+ f"keine Regel- und Skillquellen fremder Werkzeugformate. Eine Installation, "
201
+ f"die sie verliert, laedt sie wieder - still")
202
+ return
203
+ if ist != erwartet:
204
+ err(f"{rechte}: Die Importsteuerung '{schluessel}' steht als {json.dumps(ist, ensure_ascii=False)}, "
205
+ f"das Manifest bildet {json.dumps(erwartet, ensure_ascii=False)} ab. Die "
206
+ f"maschinenlesbare Quelle gilt (D-37)")
207
+
208
+
209
+ # Pruefung 33: Die Abbildung von permissions.deny auf die Werkzeugsperre des Clients
210
+ # (Befund B01 in seiner Nachfolge, CR-2026-057, D-64 bis D-66).
211
+ #
212
+ # WARUM SIE DIE ERZEUGTE FASSUNG MISST UND NICHT DIE QUELLE: Genau daran ist B01
213
+ # vorbeigekommen. Zwoelf Quellskills fuehrten `permissions: deny: [edit, exec]`, das Feld
214
+ # stand in drop_fields, und keine installierte Fassung trug etwas davon - die Quelle sagte
215
+ # mehr, als die Installation hielt, und beides war fuer sich dokumentiert.
216
+ #
217
+ # WARUM SIE IHRE ERWARTUNG SELBST AUSRECHNET: Sie koennte deny_abbilden() aus install.py
218
+ # importieren. Dann pruefte sie aber nur noch, DASS der Installer gelaufen ist, nicht dass
219
+ # er richtig abbildet - sie teilte jeden Fehler der Abbildung. Die Verbtabelle steht
220
+ # deshalb bewusst ein zweites Mal hier. Laufen die beiden auseinander, faellt diese
221
+ # Pruefung; das ist der Zweck, nicht ein Versehen.
222
+ #
223
+ # WAS SIE NICHT LEISTET: Sie misst am erzeugten Text, nicht am Client. Dass
224
+ # disallowed-tools wirklich sperrt, belegt die Erhebung
225
+ # (tests/protocols/2026-09-13-erhebung-disallowed-tools.md), nicht der Validator.
226
+ #
227
+ # SEIT 0.40.0 fuehrt sie dasselbe Vokabular wie die Quelle (D-79): read, grep, glob,
228
+ # edit, exec. Bis dahin fuehrte sie 'write' und 'search' - zwei Verben der
229
+ # Durchsetzungsschicht - und kannte 'grep' und 'glob' nicht, obwohl jede der vierzehn
230
+ # Quellen sie im Nachbarfeld desselben Frontmatters nennt. Sie teilte damit genau die
231
+ # Luecke, die sie haette fangen sollen.
232
+ DENY_VERB_EIMER_33 = {"read": "read", "grep": "search", "glob": "search",
233
+ "edit": "write", "exec": "exec"}
234
+
235
+
236
+ def _deny_eintraege(fm_text: str) -> list[str]:
237
+ """Die Eintraege unter permissions.deny einer Quelldatei, in ihrer Reihenfolge."""
238
+ m = re.search(r"^permissions:[ \t]*\n((?:[ \t]+\S.*\n)+)", fm_text, re.M)
239
+ if not m:
240
+ return []
241
+ m2 = re.search(r"^([ \t]+)deny:[ \t]*\n((?:[ \t]+-[ \t]+.*\n)+)", m.group(1), re.M)
242
+ if not m2:
243
+ return []
244
+ return [z.strip().lstrip("-").strip() for z in m2.group(2).splitlines() if z.strip()]
245
+
246
+
247
+ def _werkzeugliste(wert) -> list[str]:
248
+ """Ein Frontmatter-Werkzeugfeld als Liste - die Quelle notiert es als Liste, die
249
+ erzeugte Fassung je nach Client als kommagetrennte Zeichenkette (AP2-CC-09)."""
250
+ if isinstance(wert, str):
251
+ return [x for x in re.split(r"[,\s]+", wert) if x]
252
+ return [str(x) for x in (wert or [])]
253
+
254
+
255
+ def _flaches_feld(fm, schluessel: str):
256
+ """Ein einzeiliges Frontmatter-Feld - auch ohne PyYAML (D-364).
257
+
258
+ Ohne PyYAML liefert parse_frontmatter nur den Rohtext. Bis 1.6.0 las Pruefung 33
259
+ daraus mit .get() NICHTS und meldete an jeder claude-code-Installation neun Skills
260
+ mit leerem disallowed-tools - falsche Fehler, waehrend die Warnung zu PyYAML
261
+ behauptete, es werde nur "eingeschraenkt" geprueft. Gemessen am 2026-09-25 mit
262
+ Python 3.8 ohne PyYAML; die anderen Packs blieben still. Das Feld ist in der
263
+ erzeugten Fassung eine einzeilige, kommagetrennte Zeichenkette (AP2-CC-09), und
264
+ genau diese Form liest der Rueckfall - eine Listenform bleibt PyYAML vorbehalten.
265
+ """
266
+ fm = fm or {}
267
+ if "_raw" not in fm:
268
+ return fm.get(schluessel)
269
+ for zeile in fm["_raw"].splitlines():
270
+ if zeile.startswith(schluessel + ":"):
271
+ wert = zeile[len(schluessel) + 1:].strip()
272
+ if len(wert) >= 2 and wert[0] == wert[-1] and wert[0] in "\"'":
273
+ wert = wert[1:-1]
274
+ return wert
275
+ return None
276
+
277
+
278
+ def check_skill_deny_abbildung(root: str, man: dict) -> None:
279
+ """Pruefung 33: permissions.deny wird abgebildet, und zwar ohne stille Schranken."""
280
+ fmt = (man or {}).get("skill_frontmatter", {})
281
+ deny_feld = fmt.get("skill_deny_field")
282
+ if not deny_feld:
283
+ return # Ein Pack, das permissions nicht verwirft, braucht keine Abbildung
284
+
285
+ # Die Sonde auf den verlorenen Anker: Diese Pruefung misst eine Abbildung, die in
286
+ # install.py liegt. Verschwindet sie, prueft die Pruefung einen Aufbau, den es nicht
287
+ # mehr gibt - und bestuende dabei leise (D-23).
288
+ inst = os.path.join(root, KERN, "install.py")
289
+ quelle = read(inst) if os.path.isfile(inst) else ""
290
+ # Der zweite Anker hiess bis 0.39.0 DENY_VERB_EIMER und lag in install.py; seit
291
+ # 0.40.0 steht die Bruecke in clientmap (D-78). Der Ankerwechsel ist nachgezogen
292
+ # worden, weil der Sondenlauf ihn gemeldet hat - der Validatorlauf des Repositoriums
293
+ # konnte es nicht, weil Pruefung 33 dort gar nicht laeuft (B02).
294
+ for anker in ("def deny_abbilden(", "clientmap.VERB_BRUECKE"):
295
+ if anker not in quelle:
296
+ err(f"{KERN}/install.py: '{anker}' fehlt. Pruefung 33 misst die Abbildung von "
297
+ f"permissions.deny auf {deny_feld}; ohne sie prueft sie einen Aufbau, den "
298
+ f"es nicht mehr gibt, und bestuende leise (D-23, CR-2026-057)")
299
+ return
300
+
301
+ # Was bewusst NICHT abgebildet wird, MUSS deklariert sein - nie erraten (D-47).
302
+ if not str(fmt.get("skill_deny_unmapped") or "").strip():
303
+ err(f"{man.get('client', '?')}/manifest.json: skill_frontmatter.skill_deny_field "
304
+ f"ist gesetzt, aber skill_deny_unmapped fehlt. Befehlsgenaue Verbote der Form "
305
+ f"Exec(git push) sind bei diesem Client nicht ausdrueckbar; was nicht "
306
+ f"abgebildet wird, wird deklariert und nicht verschwiegen (D-47, "
307
+ f"CR-2026-057 E3)")
308
+
309
+ eimer = man.get("hook_tools") or {}
310
+ qdir = os.path.join(root, KERN, "framework", "skills")
311
+ idir = os.path.join(root, *man["skills_dir"].split("/"))
312
+ if not os.path.isdir(qdir) or not os.path.isdir(idir):
313
+ return
314
+
315
+ for name in sorted(os.listdir(qdir)):
316
+ qpfad = os.path.join(qdir, name, "SKILL.md")
317
+ ipfad = os.path.join(idir, name, "SKILL.md")
318
+ if not os.path.isfile(qpfad) or not os.path.isfile(ipfad):
319
+ continue
320
+ qtext = read(qpfad)
321
+ qkopf = qtext.split("\n---\n", 1)[0][4:] if qtext.startswith("---\n") else ""
322
+ eintraege = _deny_eintraege(qkopf + "\n")
323
+
324
+ # Erwartung, unabhaengig ausgerechnet: grobe Verben ueber hook_tools, alles mit
325
+ # Klammer bleibt draussen (CR-2026-057 E3).
326
+ erwartet: list[str] = []
327
+ unabbildbar = [e for e in eintraege if "(" in e]
328
+ for e in eintraege:
329
+ if "(" in e:
330
+ continue
331
+ k = DENY_VERB_EIMER_33.get(e.strip().lower())
332
+ for w in eimer.get(k, []) if k else []:
333
+ if w not in erwartet:
334
+ erwartet.append(w)
335
+
336
+ ifm, _ = parse_frontmatter(read(ipfad))
337
+ ist = _werkzeugliste(_flaches_feld(ifm, deny_feld))
338
+ rel = f"{man['skills_dir']}/{name}/SKILL.md"
339
+
340
+ if set(ist) != set(erwartet):
341
+ err(f"{rel}: {deny_feld} traegt {ist or '[]'}; aus permissions.deny der Quelle "
342
+ f"ergibt sich {erwartet or '[]'}. Die Quelle sagt sonst mehr, als die "
343
+ f"Installation haelt - genau das war Befund B01 (D-65, CR-2026-057)")
344
+
345
+ # Ein Eintrag mit Klammer wirkt gemessen LAUTLOS gar nicht: Er blockiert nichts und
346
+ # meldet nichts. Er darf in keiner erzeugten Fassung stehen (D-66, E5).
347
+ for w in ist:
348
+ if "(" in w or ")" in w:
349
+ err(f"{rel}: {deny_feld} enthaelt das Argumentmuster '{w}'. Gemessen am "
350
+ f"2026-09-13 laesst ein solcher Eintrag den Befehl lautlos durchlaufen "
351
+ f"- wer ihn schreibt, hat gar keine Schranke, nicht bloss eine "
352
+ f"groebere. Nur der blosse Werkzeugname sperrt (D-66)")
353
+
354
+ # Ein Werkzeug in beiden Listen ist ein Widerspruch. Gemessen gewinnt die Sperre -
355
+ # aber ein Skill, der ein Werkzeug zugleich vorabfreigibt und entfernt, sagt zwei
356
+ # Dinge, und eines davon ist falsch.
357
+ doppelt = sorted(set(ist) & set(_werkzeugliste((ifm or {}).get("allowed-tools"))))
358
+ if doppelt:
359
+ err(f"{rel}: {', '.join(doppelt)} steht zugleich in allowed-tools und in "
360
+ f"{deny_feld}. Gemessen gewinnt die Sperre; die Vorabfreigabe daneben ist "
361
+ f"eine Aussage, die nicht stimmt (CR-2026-057)")
362
+
363
+ # Traegt die Quelle unabbildbare Eintraege, MUSS das Pack sie deklariert haben -
364
+ # oben schon geprueft; hier bleibt der Hinweis, dass es diesen Skill betrifft.
365
+ if unabbildbar and not str(fmt.get("skill_deny_unmapped") or "").strip():
366
+ err(f"{rel}: die Quelle nennt {len(unabbildbar)} befehlsgenaue(s) Verbot(e), "
367
+ f"die dieser Client nicht ausdruecken kann, und das Pack deklariert es "
368
+ f"nicht (D-47)")
369
+
370
+
371
+ # Pruefung 34: Das Startwerkzeug fuer Unteragenten ist genannt oder seine Abwesenheit
372
+ # erklaert (CR-2026-058 E2, D-70).
373
+ #
374
+ # Bauform wie Pruefung 26 (hook_tools_absent, D-47), eine Ebene weiter. Der Anlass ist ein
375
+ # gemessener Befund: Ein Skill kann einen Unteragenten starten, und das Startwerkzeug stand
376
+ # in KEINER Werkzeugliste eines Manifests - nicht in hook_tools, nicht in permission_tools,
377
+ # nicht in agent_frontmatter.tool_names. Ein Kanal ohne Deklaration ist genau das, was D-47
378
+ # abgestellt hat.
379
+ #
380
+ # Bei claude-code ist gemessen, dass beide Schreibweisen (Agent, Task) in disallowed-tools
381
+ # wirken; bei devin-desktop ist NICHTS gemessen, und die leere Liste sagt deshalb etwas
382
+ # ueber den Belegstand, nicht ueber den Client.
383
+ #
384
+ # EINE ERKLAERUNG REICHT NICHT, WENN DAS PACK A1 OHNE VORBEHALT ZUSAGT: Zeile A1
385
+ # verspricht ein rein lesendes Reviewprofil. Ein Pack, das diese Zeile auf [TECHNISCH]
386
+ # stellt UND keinen offenen Beleg mehr darauf fuehrt, behauptet, dass es
387
+ # Unteragenten gibt und dass ihre Werkzeuge beschraenkbar sind - dann ist "kennt kein
388
+ # Startwerkzeug" kein zulaessiger Stand.
389
+ #
390
+ # DER VORBEHALT GEHOERT DAZU, und das hat diese Pruefung bei ihrem ersten Lauf selbst
391
+ # gezeigt: Ohne ihn fiel devin-desktop durch. Dessen Zeile A1 steht auf [TECHNISCH], aber
392
+ # die Praeambel des Packs sagt ausdruecklich, die Spalte nenne die VORGESEHENE
393
+ # Durchsetzungstiefe, und die Zeile trug einen offenen Beleg auf genau die
394
+ # Profilwirkung. Die Einstufung allein sagt also nicht, ob eine Zusage schon gilt - das
395
+ # sagt die Belegzelle. Eine Pruefung, die beides verwechselt, meldet einen Fehler, wo das
396
+ # Pack ehrlich ist (CR-2026-058, Wirkungsnachweis).
397
+ #
398
+ # SEIT 0.87.0 STEHT DER VORBEHALT AUF DER NACHFOLGEFORM (CR-2026-121 E5, D-291). Die
399
+ # Markerform ist abgeschafft; der Nachweisstand steht in der Belegspalte, und eine Zeile
400
+ # ohne Beleg sagt BELEG OFFEN mit Grund und Datum. Haette der Vorbehalt den alten
401
+ # Suchtext behalten, waere seine Bedingung dauerhaft wahr - eine Ausnahme, die nichts
402
+ # mehr ausnimmt (0.57.1), und sie saehe wie Sorgfalt aus. HEUTE HAT SIE KEINEN FALL:
403
+ # Beide Packs fuehren agent_start_tools gefuellt, und der Zweig wird nicht erreicht.
404
+ # Sie ist Vorsorge fuer das naechste Pack, und Sonde 34e praepariert sie.
405
+ #
406
+ # WAS DIESE PRUEFUNG NICHT LEISTET: Sie prueft die Deklaration, nicht ihre Richtigkeit. Ob
407
+ # der genannte Name beim Client wirklich sperrt, belegt allein eine Erhebung.
408
+ def check_agent_startwerkzeug(root: str) -> None:
409
+ """Pruefung 34 (D-70): agent_start_tools ist genannt oder erklaert."""
410
+ basis = os.path.join(root, KERN, "clients")
411
+ if not os.path.isdir(basis):
412
+ return
413
+ for pack in sorted(os.listdir(basis)):
414
+ if pack.startswith("_"):
415
+ continue
416
+ pfad = os.path.join(basis, pack, "manifest.json")
417
+ if not os.path.isfile(pfad):
418
+ continue
419
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
420
+ try:
421
+ man = json.loads(read(pfad))
422
+ except ValueError:
423
+ continue
424
+ namen = man.get("agent_start_tools")
425
+ if namen is None:
426
+ err(f"{rel}: Feld 'agent_start_tools' fehlt. Ein Skill kann einen Unteragenten "
427
+ f"starten; mit welchem Werkzeug, gehoert deklariert - und wenn es "
428
+ f"unbekannt oder unerhoben ist, gehoert das ausdruecklich dorthin statt "
429
+ f"verschwiegen (D-70, Bauform wie hook_tools_absent nach D-47)")
430
+ continue
431
+ if not isinstance(namen, list):
432
+ err(f"{rel}: 'agent_start_tools' ist keine Liste")
433
+ continue
434
+ gefuellt = [n for n in namen if isinstance(n, str) and n.strip()]
435
+ abwesend = man.get("agent_start_tools_absent") or []
436
+ notiz = str(man.get("_agent_start_tools_absent_note") or "").strip()
437
+ if gefuellt:
438
+ if abwesend:
439
+ err(f"{rel}: 'agent_start_tools' nennt {gefuellt} und "
440
+ f"'agent_start_tools_absent' erklaert zugleich eine Abwesenheit. "
441
+ f"Beides zugleich geht nicht (D-70)")
442
+ continue
443
+ if not abwesend:
444
+ err(f"{rel}: 'agent_start_tools' ist leer, ohne dass "
445
+ f"'agent_start_tools_absent' die Abwesenheit erklaert. Eine leere Liste "
446
+ f"allein sagt nicht, ob der Client kein solches Werkzeug kennt oder ob es "
447
+ f"nur nicht erhoben ist - genau diese Unterscheidung ist der Zweck des "
448
+ f"Feldes (D-70, D-47)")
449
+ continue
450
+ if not notiz:
451
+ err(f"{rel}: 'agent_start_tools_absent' nennt {sorted(abwesend)}, aber "
452
+ f"'_agent_start_tools_absent_note' fehlt oder ist leer. Eine erklaerte "
453
+ f"Abwesenheit ohne Begruendung ist eine Behauptung (D-70, D-47)")
454
+ # Wer A1 auf [TECHNISCH] stellt, sagt zu, dass es Unteragenten gibt und dass ihre
455
+ # Werkzeuge beschraenkbar sind. Dann ist eine Erklaerung kein zulaessiger Stand.
456
+ pack_md = os.path.join(basis, pack, "CLIENT_PACK.md")
457
+ if not os.path.isfile(pack_md):
458
+ continue
459
+ for zeile in read(pack_md).replace("\r\n", "\n").split("\n"):
460
+ if (re.match(r"^\|\s*A1\s*\|", zeile) and "[TECHNISCH]" in zeile
461
+ and "BELEG OFFEN" not in zeile):
462
+ err(f"{rel}: Zeile A1 des Packs steht auf [TECHNISCH] - das Pack sagt ein "
463
+ f"rein lesendes Unteragentenprofil technisch zu -, aber "
464
+ f"'agent_start_tools' ist leer und nur erklaert. Wer Unteragenten "
465
+ f"zusagt, nennt das Werkzeug, mit dem sie starten (D-70)")
466
+ break
467
+
468
+
469
+
470
+ # Pruefung 35: Ein Agentenprofil bekommt kein Startwerkzeug (CR-2026-059 E1, D-73).
471
+ #
472
+ # Gemessen am 2026-09-13 (tests/protocols/2026-09-13-erhebung-unteragent-tiefe.md,
473
+ # Lauf STARTLOS): Ein Profil mit tools: Read, Grep, Glob - genau die Form, die
474
+ # fw-reviewer nach der Abbildung traegt - hat KEIN Startwerkzeug und konnte deshalb
475
+ # keinen weiteren Unteragenten starten, dessen Profil weniger beschraenkt waere. Ohne
476
+ # diesen Befund waere die Zusage A1 ueber eine zweite Ebene aushebelbar.
477
+ #
478
+ # WAS DIESE PRUEFUNG IST UND WAS NICHT: Sie faengt heute NICHTS. Die Abbildung
479
+ # agent_frontmatter.tool_names kennt gar kein Startwerkzeug, also kann keine erzeugte
480
+ # tools-Liste eines enthalten. Sie ist eine VERANKERUNG, keine Behebung - dieselbe
481
+ # Bauart wie der fuenfte Gegenstand der Pruefung 32, und sie wird mit derselben
482
+ # Ehrlichkeit begruendet: Erweitert jemand tool_names um ein Startwerkzeug, faellt die
483
+ # Zusage LAUTLOS, und niemand prueft es. Danach faengt diese Pruefung es.
484
+ #
485
+ # Geprueft wird an ZWEI Stellen, weil eine allein zu wenig waere:
486
+ # 1. Die ABBILDUNG - tool_names darf keinen Namen aus agent_start_tools fuehren.
487
+ # 2. Die QUELLE - kein ausgeliefertes Agentenprofil nennt eines direkt. Diese Haelfte
488
+ # faengt den Fall, dass jemand am Profil vorbei an der Abbildung schreibt.
489
+ def check_agent_profil_ohne_start(root: str) -> None:
490
+ """Pruefung 35 (D-73): Kein Agentenprofil bekommt ein Startwerkzeug."""
491
+ basis = os.path.join(root, KERN, "clients")
492
+ if not os.path.isdir(basis):
493
+ return
494
+ alle_start = set()
495
+ for pack in sorted(os.listdir(basis)):
496
+ if pack.startswith("_"):
497
+ continue
498
+ pfad = os.path.join(basis, pack, "manifest.json")
499
+ if not os.path.isfile(pfad):
500
+ continue
501
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
502
+ try:
503
+ man = json.loads(read(pfad))
504
+ except ValueError:
505
+ continue
506
+ start = {n.strip().lower() for n in (man.get("agent_start_tools") or [])
507
+ if isinstance(n, str) and n.strip()}
508
+ if not start:
509
+ continue # ob das zulaessig ist, entscheidet Pruefung 34
510
+ alle_start |= start
511
+
512
+ # 1. Die Abbildung darf kein Startwerkzeug fuehren.
513
+ abbildung = (man.get("agent_frontmatter") or {}).get("tool_names") or {}
514
+ for verb, namen in sorted(abbildung.items()):
515
+ for name in (namen if isinstance(namen, list) else []):
516
+ if isinstance(name, str) and name.strip().lower() in start:
517
+ err(f"{rel}: agent_frontmatter.tool_names bildet das Verb "
518
+ f"'{verb}' auf '{name}' ab - ein Werkzeug, mit dem ein "
519
+ f"Unteragent GESTARTET wird (agent_start_tools). Ein "
520
+ f"Agentenprofil, das es bekommt, kann eine zweite, weniger "
521
+ f"beschraenkte Ebene oeffnen; die Zusage A1 waere damit "
522
+ f"aushebelbar (D-73, CR-2026-059)")
523
+
524
+ # 2. Kein ausgeliefertes Agentenprofil nennt ein Startwerkzeug direkt.
525
+ #
526
+ # Diese Haelfte laeuft EINMAL ueber die Ablage des Kerns, nicht je Pack: Die Profile
527
+ # sind geteilt (shared_core), die Startwerkzeuge sind es nicht. Geprueft wird gegen
528
+ # die VEREINIGUNG aller Packs - ein Name, der bei einem Pack startet, gehoert in kein
529
+ # Profil, weil dasselbe Profil auch dort ausgeliefert wird.
530
+ if not alle_start:
531
+ return
532
+ adir = os.path.join(root, KERN, "framework", "runtime", "agents")
533
+ if not os.path.isdir(adir):
534
+ err(f"{KERN}/framework/runtime/agents: fehlt. Pruefung 35 misst die "
535
+ f"Agentenprofile des Kerns; ohne die Ablage prueft sie nichts und "
536
+ f"bestuende leise (D-23)")
537
+ return
538
+ for datei in sorted(os.listdir(adir)):
539
+ if not datei.endswith(".md"):
540
+ continue
541
+ rel = KERN + "/framework/runtime/agents/" + datei
542
+ roh = read(os.path.join(adir, datei)).replace("\r\n", "\n")
543
+ teile = roh.split("---")
544
+ if len(teile) < 3:
545
+ err(f"{rel}: kein Frontmatter zwischen zwei '---'. Ohne es prueft "
546
+ f"Pruefung 35 nichts und bestuende leise (D-23)")
547
+ continue
548
+ for name in sorted(alle_start):
549
+ muster = r"(?<![A-Za-z])%s(?![A-Za-z])" % re.escape(name.lower())
550
+ if re.search(muster, teile[1].lower()):
551
+ err(f"{rel}: Das Frontmatter nennt '{name}' - ein Werkzeug, mit dem "
552
+ f"ein Unteragent GESTARTET wird (agent_start_tools). Ein Profil, "
553
+ f"das es bekommt, kann eine zweite, weniger beschraenkte Ebene "
554
+ f"oeffnen, und die Zusage A1 waere aushebelbar (D-73, "
555
+ f"CR-2026-059)")
556
+
557
+
558
+ def _frontmatter_verben(text: str) -> tuple[list[str], list[str]]:
559
+ """Die groben Verben aus allowed-tools und permissions.deny eines Frontmatters."""
560
+ fm = text.replace("\r\n", "\n")
561
+ if not fm.startswith("---\n") or "\n---\n" not in fm:
562
+ return [], []
563
+ fm = fm.split("\n---\n", 1)[0][4:] + "\n"
564
+ m = re.search(r"^allowed-tools:[ \t]*\n((?:[ \t]+-[ \t]+\S+[ \t]*\n)+)", fm, re.M)
565
+ erlaubt = [x.strip("- \t") for x in m.group(1).strip().split("\n")] if m else []
566
+ verboten: list[str] = []
567
+ m = re.search(r"^permissions:[ \t]*\n((?:[ \t]+\S.*\n)+)", fm, re.M)
568
+ if m:
569
+ m2 = re.search(r"^([ \t]+)deny:[ \t]*\n((?:[ \t]+-[ \t]+.*\n)+)", m.group(1), re.M)
570
+ if m2:
571
+ verboten = [z.strip().lstrip("-").strip() for z in m2.group(2).splitlines()
572
+ if z.strip() and "(" not in z]
573
+ return erlaubt, verboten
574
+
575
+
576
+ def _quelldateien(root: str) -> list[tuple[str, str]]:
577
+ """Die ausgelieferten Quellen mit Frontmatter-Verben: Skills und Agentenprofile."""
578
+ out: list[tuple[str, str]] = []
579
+ basis = os.path.join(root, KERN, "framework")
580
+ for wurzel, verzeichnisse, dateien in os.walk(basis):
581
+ verzeichnisse[:] = sorted(d for d in verzeichnisse if d != "__pycache__")
582
+ for datei in sorted(dateien):
583
+ if datei != "SKILL.md" and not wurzel.endswith(os.sep + "agents"):
584
+ continue
585
+ if not datei.endswith(".md"):
586
+ continue
587
+ pfad = os.path.join(wurzel, datei)
588
+ out.append((os.path.relpath(pfad, root).replace(os.sep, "/"), read(pfad)))
589
+ return out
590
+
591
+
592
+ def check_werkzeugabbildung(root: str) -> None:
593
+ """Pruefung 38 (D-78 bis D-80): eine Quelle, ein Vokabular, eine Richtung."""
594
+ kern = os.path.join(root, KERN)
595
+ if kern not in sys.path:
596
+ sys.path.insert(0, kern)
597
+ try:
598
+ import clientmap
599
+ except ImportError:
600
+ warn(f"{KERN}/clientmap.py nicht gefunden - Pruefung 38 laeuft nicht")
601
+ return
602
+ # Die Sonde auf den verlorenen Anker (seit 0.32.0): Diese Pruefung findet ihren
603
+ # Gegenstand ueber zwei Namen des Moduls. Verschwinden sie, bestuende sie leise.
604
+ fehlend = [n for n in ("FRONTMATTER_VERBEN", "VERB_BRUECKE")
605
+ if not getattr(clientmap, n, None)]
606
+ if fehlend:
607
+ err(f"{KERN}/clientmap.py: {', '.join(fehlend)} fehlt - Pruefung 38 hat ihren "
608
+ f"Anker verloren und wuerde leise bestehen (D-23, D-78)")
609
+ return
610
+ vokabular = tuple(clientmap.FRONTMATTER_VERBEN)
611
+ bruecke = dict(clientmap.VERB_BRUECKE)
612
+
613
+ basis = os.path.join(root, KERN, "clients")
614
+ if not os.path.isdir(basis):
615
+ return
616
+ gesehen = 0
617
+ for pack in sorted(os.listdir(basis)):
618
+ if pack.startswith("_"):
619
+ continue
620
+ pfad = os.path.join(basis, pack, "manifest.json")
621
+ if not os.path.isfile(pfad):
622
+ continue
623
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
624
+ try:
625
+ man = json.loads(read(pfad))
626
+ except ValueError:
627
+ continue
628
+ gesehen += 1
629
+ hook = man.get("hook_tools") or {}
630
+ for block in FRONTMATTER_BLOECKE:
631
+ fmt = man.get(block)
632
+ if fmt is None:
633
+ err(f"{rel}: Block '{block}' fehlt - ohne ihn ist nicht entschieden, "
634
+ f"welche Werkzeugnamen die installierte Fassung traegt (D-78)")
635
+ continue
636
+ abbildung = fmt.get("tool_names")
637
+ if not isinstance(abbildung, dict):
638
+ err(f"{rel}: {block}.tool_names fehlt oder ist kein Objekt")
639
+ continue
640
+ erklaert = fmt.get("tool_names_unmapped") or []
641
+ if not isinstance(erklaert, list):
642
+ err(f"{rel}: {block}.tool_names_unmapped ist keine Liste")
643
+ erklaert = []
644
+ if erklaert and not str(fmt.get("_tool_names_unmapped_note") or "").strip():
645
+ err(f"{rel}: {block}.tool_names_unmapped nennt {sorted(erklaert)}, aber "
646
+ f"'_tool_names_unmapped_note' fehlt oder ist leer. Ein Verb nicht "
647
+ f"abzubilden ist eine Aussage ueber den Client oder ueber den "
648
+ f"Belegstand - sie gehoert begruendet, nicht bloss eingetragen "
649
+ f"(D-78, Bauform wie hook_tools_absent nach D-47)")
650
+ for schluessel in sorted(abbildung):
651
+ if schluessel not in vokabular:
652
+ err(f"{rel}: {block}.tool_names bildet '{schluessel}' ab - kein "
653
+ f"Verb des Frontmatter-Vokabulars {list(vokabular)}. Eine "
654
+ f"Quelle des Kerns kann es nicht schreiben, die Abbildung "
655
+ f"laeuft also ins Leere (D-78)")
656
+ for verb in vokabular:
657
+ hat = verb in abbildung
658
+ erklaert_hier = verb in erklaert
659
+ if hat and erklaert_hier:
660
+ err(f"{rel}: {block} bildet das Verb '{verb}' ab UND erklaert es "
661
+ f"zugleich fuer nicht abgebildet. Beides zugleich geht nicht - "
662
+ f"eine der beiden Aussagen ist falsch (D-78)")
663
+ elif not hat and not erklaert_hier:
664
+ err(f"{rel}: {block}.tool_names kennt das Werkzeugverb '{verb}' "
665
+ f"nicht, und tool_names_unmapped erklaert die Abwesenheit "
666
+ f"nicht. Bis 0.39.0 wurde das Verb dann WOERTLICH als "
667
+ f"Werkzeugname durchgereicht; eine Luecke allein ist keine "
668
+ f"Aussage (D-78)")
669
+
670
+ # Gegenstand 3: Die Sperrliste darf nicht enger sein als die Vorabfreigabe.
671
+ #
672
+ # Die Regel vergleicht zwei Listen und setzt damit voraus, dass sie
673
+ # DENSELBEN Namensraum fuehren. Bei claude-code tun sie das; bei
674
+ # devin-desktop nicht, und das ist gemessen (2026-09-14, D-88): Sein
675
+ # Frontmatter kennt ein eigenes, normalisiertes Vokabular - read,
676
+ # grep, glob, edit, exec, web_search -, waehrend seine Laufzeit das
677
+ # glob-foermige Werkzeug find_file_by_name nennt. Ein Vergleich der
678
+ # beiden Listen meldete dann einen Unterschied, den es nicht gibt.
679
+ #
680
+ # Ein Pack sagt das mit tool_names_namespace: 'eigen'. WAS DABEI
681
+ # VERLOREN GEHT, gehoert gesagt: Fuer ein solches Pack prueft niemand
682
+ # mehr, ob die Sperre die Vorabfreigabe deckt - die Frage von D-80
683
+ # bleibt dort offen und ist nur anders gestellt, nicht beantwortet.
684
+ if str(fmt.get("tool_names_namespace") or "") == "eigen":
685
+ if not str(fmt.get("_tool_names_note") or "").strip():
686
+ err(f"{rel}: {block}.tool_names_namespace ist 'eigen', aber "
687
+ f"'_tool_names_note' fehlt oder ist leer. Einen eigenen "
688
+ f"Namensraum zu erklaeren nimmt die Richtungsregel von "
689
+ f"D-80 ausser Kraft - das gehoert begruendet, nicht "
690
+ f"bloss eingetragen (D-88)")
691
+ continue
692
+ for verb in sorted(abbildung):
693
+ ziel = bruecke.get(verb)
694
+ if ziel is None or ziel not in hook:
695
+ continue
696
+ vorab = {str(x) for x in (abbildung.get(verb) or [])}
697
+ sperre = {str(x) for x in (hook.get(ziel) or [])}
698
+ zuviel = sorted(vorab - sperre)
699
+ if zuviel:
700
+ err(f"{rel}: {block}.tool_names['{verb}'] gibt {zuviel} vorab "
701
+ f"frei, hook_tools['{ziel}'] fuehrt sie nicht. Die Sperrliste "
702
+ f"ist damit enger als die Vorabfreigabe: Ein Skill, der "
703
+ f"'{verb}' freigibt und '{verb}' sperrt, bekaeme ein Werkzeug "
704
+ f"freigegeben, das die Sperre nicht erfasst. Die umgekehrte "
705
+ f"Abweichung ist zulaessig (D-80)")
706
+ if not gesehen:
707
+ err(f"{KERN}/clients: kein Manifest gefunden. Pruefung 38 misst die "
708
+ f"Werkzeugabbildungen der Packs; ohne sie prueft sie nichts und bestuende "
709
+ f"leise (D-23)")
710
+
711
+ # Gegenstand 4: die Quellen selbst.
712
+ quellen = _quelldateien(root)
713
+ if not quellen:
714
+ err(f"{KERN}/framework: keine Quelle mit Frontmatter gefunden. Pruefung 38 "
715
+ f"misst die Verben der ausgelieferten Skills und Agentenprofile; ohne sie "
716
+ f"prueft der vierte Gegenstand nichts und bestuende leise (D-23)")
717
+ return
718
+ for rel, text in quellen:
719
+ erlaubt, verboten = _frontmatter_verben(text)
720
+ for feld, verben in (("allowed-tools", erlaubt), ("permissions.deny", verboten)):
721
+ for verb in verben:
722
+ if verb not in vokabular:
723
+ err(f"{rel}: {feld} nennt das Verb '{verb}' - das Vokabular des "
724
+ f"Frontmatters kennt nur {list(vokabular)}. In allowed-tools "
725
+ f"wurde es bis 0.39.0 woertlich als Werkzeugname "
726
+ f"durchgereicht, in permissions.deny fiel es lautlos aus "
727
+ f"(D-78, D-79)")
728
+
729
+
730
+
731
+ # ---------------------------------------------------------------------------
732
+ # Pruefung 39: Die Vorabfreigabe des Skillaufrufs deckt sich mit den ausgelieferten
733
+ # Skills, und die Skillwahl steht an allen vier Traegern (CR-2026-063, D-81 bis D-84).
734
+ #
735
+ # ANLASS. Die Wurzel-Anweisungsdatei fordert seit jeher, fuer Standardaufgaben die
736
+ # Skills zu nutzen - und die ausgelieferte Berechtigungsdatei kannte das Werkzeug, mit
737
+ # dem das geht, in KEINEM Korb. Gemessen am 2026-09-14
738
+ # (tests/protocols/2026-09-14-erhebung-skillaufruf.md): Der Aufruf wurde abgewiesen, die
739
+ # Sitzung las die SKILL.md ersatzweise als Datei, und die Ausgabe sah aus wie ein
740
+ # gelungener Lauf. Die Ursache lag nicht im Pack, sondern im Vokabular der Kernquelle -
741
+ # sechs Verben, keines fuer den Skillaufruf.
742
+ #
743
+ # VIER GEGENSTAENDE:
744
+ # 1. Der verlorene Anker. Fehlt das Skillverzeichnis des Kerns oder fuehrt
745
+ # permissions.json keine einzige skill-Regel, meldet diese Pruefung das selbst -
746
+ # sonst bestuende sie leise (D-23).
747
+ # 2. Die Deckung in BEIDE Richtungen. Jeder ausgelieferte Skill hat genau eine
748
+ # allow-Regel, und jede Regel nennt einen ausgelieferten Skill. Das ist der Preis
749
+ # von D-81 E3: Die Regeln stehen in der Datei, statt aus dem Verzeichnis erzeugt zu
750
+ # werden - nach D-53 IST der Inhalt dieser Datei die Berechtigung. Ohne diese
751
+ # Pruefung waere das die zweite Liste fuer dieselbe Sache, also der Befundtyp von
752
+ # CR-2026-062.
753
+ # 3. Kein Musterzeichen. Gemessen am 2026-09-14 (D-82): Der Vergleich ist woertlich.
754
+ # Skill(fw-*) weist den Aufruf ab, Skill(fw-code-explain) laesst ihn durch,
755
+ # Skill(fw-plan) weist fw-code-explain ab (Kontrolllauf). Eine Regel mit * oder ?
756
+ # saehe richtig aus und gaebe LAUTLOS NICHTS frei - die Bauform von D-66 mit
757
+ # umgekehrtem Vorzeichen.
758
+ # 4. Die vier Regeltraeger. Die Skillwahl stand vor 0.41.0 an fuenf Stellen und
759
+ # erreichte den Agenten an keiner verbindlich. Sie steht jetzt in der
760
+ # Wurzel-Anweisungsdatei, in der always-on-Kurzfassung, im Arbeitsmodell und in den
761
+ # Prompting-Regeln. Geprueft wird die ANWESENHEIT je Traeger, nicht der Wortlaut:
762
+ # Eine Pruefung, die Prosa vergleicht, bricht bei jeder Umformulierung.
763
+ #
764
+ # WAS SIE HEUTE FAENGT: nichts - Gegenstand 2 und 3 sind mit demselben Release entstanden
765
+ # und passen per Konstruktion zu sich selbst; Gegenstand 4 ebenso. Das ist die Lage von
766
+ # Pruefung 37 und dieselbe Ehrlichkeit: Der Gegenbeweis gegen den Vorstand ist hier eine
767
+ # KONSTRUKTION, kein Abzaehlen, und was die Pruefung wert ist, haengt an ihren Sonden.
768
+ #
769
+ # WAS SIE NICHT LEISTET: Sie belegt nicht, dass der Agent den Skill waehlt. Das ist ein
770
+ # Sitzungstest, und er ist nicht gefahren. Sie belegt, dass die Freigabe zur Skillmenge
771
+ # passt und dass keiner der vier Traeger die Regel verliert.
772
+ SKILLWAHL_TRAEGER = (
773
+ ("framework/runtime/root-instruction.md",
774
+ "Bevor du einen Schritt beginnst"),
775
+ ("framework/runtime/rules/00-framework-core.md",
776
+ "Skillwahl vor dem Schritt"),
777
+ ("framework/core/05-working-model.md",
778
+ "ist er der vorgesehene Weg des Schrittes"),
779
+ ("framework/core/06-prompting-rules.md",
780
+ "Skills bevorzugen \u2013 von beiden Seiten"),
781
+ )
782
+
783
+
784
+ def _skill_regeln(root: str) -> list[str] | None:
785
+ """Die Aufrufnamen aus den skill-Regeln der Kernquelle, oder None ohne Quelle."""
786
+ pfad = os.path.join(root, KERN, "framework", "runtime", "permissions.json")
787
+ if not os.path.exists(pfad):
788
+ return None
789
+ try:
790
+ quelle = json.loads(read(pfad))
791
+ except json.JSONDecodeError:
792
+ return None
793
+ return [r.get("pattern", "") for r in quelle.get("allow", [])
794
+ if isinstance(r, dict) and r.get("tool") == "skill"]
795
+
796
+
797
+ def check_skillfreigabe(root: str) -> None:
798
+ """Pruefung 39 (D-81 bis D-84): Freigabe, Skillmenge und Regeltraeger decken sich."""
799
+ verzeichnis = os.path.join(root, KERN, "framework", "skills")
800
+ if not os.path.isdir(verzeichnis):
801
+ err(f"{KERN}/framework/skills: fehlt. Pruefung 39 misst die Vorabfreigabe des "
802
+ f"Skillaufrufs gegen die ausgelieferten Skills; ohne das Verzeichnis "
803
+ f"prueft sie nichts und bestuende leise (D-23)")
804
+ return
805
+ ausgeliefert = sorted(
806
+ name for name in os.listdir(verzeichnis)
807
+ if os.path.isfile(os.path.join(verzeichnis, name, "SKILL.md")))
808
+
809
+ regeln = _skill_regeln(root)
810
+ if regeln is None:
811
+ err(f"{KERN}/framework/runtime/permissions.json: nicht lesbar. Pruefung 39 hat "
812
+ f"ihren Anker verloren und bestuende sonst leise (D-23)")
813
+ return
814
+ if not regeln:
815
+ err(f"{KERN}/framework/runtime/permissions.json: keine einzige allow-Regel mit "
816
+ f"dem Verb 'skill'. Die Wurzel-Anweisungsdatei fordert in Abschnitt 17 die "
817
+ f"Nutzung der Skills; ohne Freigabe laeuft jeder Aufruf in die Rueckfrage "
818
+ f"und im rueckfragefreien Betrieb in die Abweisung (D-81)")
819
+ return
820
+
821
+ # Gegenstand 2: die Deckung, in beide Richtungen.
822
+ for name in ausgeliefert:
823
+ if name not in regeln:
824
+ err(f"{KERN}/framework/runtime/permissions.json: der ausgelieferte Skill "
825
+ f"'{name}' hat keine allow-Regel. Sein Aufruf laeuft in die Rueckfrage, "
826
+ f"und der Fehlschlag ist stumm - die Sitzung liest die SKILL.md "
827
+ f"ersatzweise als Datei, ohne die Werkzeugbeschraenkung des Skills "
828
+ f"(D-81, D-83)")
829
+ for name in regeln:
830
+ if name not in ausgeliefert:
831
+ err(f"{KERN}/framework/runtime/permissions.json: die allow-Regel fuer "
832
+ f"'{name}' nennt keinen ausgelieferten Skill. Eine Vorabfreigabe fuer "
833
+ f"einen Skill, den es nicht gibt, ist eine Zusage ohne Gegenstand "
834
+ f"(D-81)")
835
+ doppelt = sorted({n for n in regeln if regeln.count(n) > 1})
836
+ if doppelt:
837
+ err(f"{KERN}/framework/runtime/permissions.json: doppelte skill-Regel(n) fuer "
838
+ f"{', '.join(doppelt)}")
839
+
840
+ # Gegenstand 3: kein Musterzeichen.
841
+ for name in regeln:
842
+ if any(z in name for z in "*?["):
843
+ err(f"{KERN}/framework/runtime/permissions.json: die skill-Regel '{name}' "
844
+ f"traegt ein Musterzeichen. Der Vergleich ist woertlich - gemessen am "
845
+ f"2026-09-14 (D-82): Skill(fw-*) weist den Aufruf ab. Eine Regel mit "
846
+ f"Muster saehe richtig aus und gaebe lautlos nichts frei")
847
+
848
+ # Gegenstand 4: die vier Regeltraeger.
849
+ for rel, anker in SKILLWAHL_TRAEGER:
850
+ pfad = os.path.join(root, KERN, *rel.split("/"))
851
+ if not os.path.exists(pfad):
852
+ err(f"{KERN}/{rel}: fehlt. Pruefung 39 misst dort die Skillwahl (D-84)")
853
+ continue
854
+ if anker not in read(pfad):
855
+ err(f"{KERN}/{rel}: die Skillwahl fehlt (gesucht: '{anker}'). Sie stand vor "
856
+ f"0.41.0 an fuenf Stellen und erreichte den Agenten an keiner "
857
+ f"verbindlich; faellt ein Traeger weg, faellt sie leise zurueck (D-84)")
858
+
859
+
860
+ # --- Pruefung 72: Ein aktiviertes Pack steht auch im Berechtigungskorb -------------
861
+ #
862
+ # ANLASS, UND ER IST GEMESSEN (2026-09-21, Vorbedingungsdurchgang von Buendel 5,
863
+ # D-238). Bundel 5 misst `role-re-ticket`, den einzigen Skill dieses Frameworks, der
864
+ # nicht im Kern liegt. Nach der Aktivierung WORTGETREU nach
865
+ # `framework/role-packs/README.md` - Laufzeitfassung kopiert, Skillverzeichnis kopiert -
866
+ # und nach `install.py --update` stand im Messbaum:
867
+ #
868
+ # Skillverzeichnisse in .claude/skills/ 13
869
+ # Skill(...)-Eintraege im allow-Korb 12
870
+ # Skill(role-re-ticket) fehlt
871
+ # validate-framework.py --strict-overlay 0 Fehler, 0 Warnungen
872
+ #
873
+ # DIE REGELSCHICHT DES PACKS WAR VOLLSTAENDIG, DIE TECHNISCHE KANNTE ES NICHT. Und
874
+ # `defaultMode` steht auf `default`: Ein nicht genannter Aufruf faellt in den
875
+ # Rueckfragekorb, im nicht-interaktiven Betrieb also in die Abweisung. D-81 hat genau
876
+ # diesen Ausgang beschrieben - "der Fehlschlag ist stumm; die Sitzung liest die
877
+ # SKILL.md ersatzweise als Datei, OHNE die Werkzeugbeschraenkung des Skills".
878
+ #
879
+ # WARUM PRUEFUNG 39 ES NICHT SIEHT, OBWOHL SIE DAFUER GEBAUT IST. Sie haelt
880
+ # `framework/runtime/permissions.json` gegen `framework/skills/` - Regelmenge des Kerns
881
+ # gegen Skills des Kerns, beides Ebene 3, und dort deckt es sich (12 zu 12). Ein
882
+ # Packskill ist Ebene 6 und kommt in keiner der beiden Mengen vor. Derselbe Zuschnitt,
883
+ # der D-234 drei Tage zuvor unterlaufen ist: eine Stelle, die `framework/skills` sagt
884
+ # und `framework/role-packs/<pack>/skills` meint.
885
+ #
886
+ # WARUM DIE ABHILFE NICHT IN permissions.json LIEGT. Eine allow-Regel dort traegt JEDE
887
+ # Installation - auch die, die das Pack nicht aktiviert hat. Das waere "eine
888
+ # Vorabfreigabe fuer einen Skill, den es nicht gibt", und genau das verbietet
889
+ # Pruefung 39 in ihrer Gegenrichtung. Die Aktivierung ist eine Projektentscheidung
890
+ # (README Punkt 4) und hat deshalb DREI Teile, nicht zwei; diese Pruefung setzt den
891
+ # dritten durch, und zwar dort, wo er hingehoert: in der Installation.
892
+ #
893
+ # WARUM SIE BEI EINEM CLIENT SCHWEIGT, UND WARUM DAS KEINE STILLE NULL IST. Das
894
+ # Manifest fuehrt die Schreibweise des Skillaufrufs als `permission_tools.skill`. Bei
895
+ # `claude-code` steht dort `["Skill"]`, bei `devin-desktop` eine LEERE Liste - erhoben
896
+ # am 2026-09-14 (D-89): Der Aufruf ist dort zwar ein Werkzeugaufruf, aber es ist keine
897
+ # Schreibweise bekannt, mit der eine Regel ihn beim Namen nennt. Eine leere Liste ist
898
+ # deklariert; ein FEHLENDES Feld ist ein Befund, und den meldet sie - dieselbe
899
+ # Trennlinie zwischen "nicht abgebildet" und "gibt es nicht", die D-155 zieht.
900
+ P72_MUSTER = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)\(([^()]*)\)$")
901
+ P72_SELBSTPROBE = "Skill(role-beispiel)" # SYNTHETISCH
902
+
903
+
904
+ def _p72_eintraege(cfg) -> list:
905
+ """Jeder Regeleintrag der Berechtigungsdatei, ueber alle Koerbe.
906
+
907
+ Ueber die Koerbe zu laufen statt `allow` zu nennen ist Absicht: Ein Eintrag in
908
+ `ask` oder `deny` NENNT den Skill ebenso, und wer nur `allow` liest, meldet ein
909
+ ausdrueckliches Verbot als fehlenden Eintrag.
910
+ """
911
+ aus: list = []
912
+
913
+ def rein(o) -> None:
914
+ if isinstance(o, dict):
915
+ for v in o.values():
916
+ rein(v)
917
+ elif isinstance(o, list):
918
+ for x in o:
919
+ rein(x)
920
+ elif isinstance(o, str):
921
+ aus.append(o)
922
+
923
+ rein(cfg.get("permissions", cfg) if isinstance(cfg, dict) else cfg)
924
+ return aus
925
+
926
+
927
+ def check_pack_im_korb(root: str, man: dict) -> None:
928
+ """Pruefung 72 (D-238): Jeder Skill der Installation steht im Berechtigungskorb."""
929
+ if formatgebunden(man, 72):
930
+ return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
931
+ if not P72_MUSTER.match(P72_SELBSTPROBE):
932
+ err(f"Pruefung 72: das eigene Muster trifft {P72_SELBSTPROBE!r} nicht mehr - "
933
+ f"sie haette ihren Gegenstand verloren und bestuende leise (D-23, D-238)")
934
+ return
935
+ pack = man.get("client", "?")
936
+ werkzeuge = man.get("permission_tools")
937
+ if not isinstance(werkzeuge, dict) or "skill" not in werkzeuge:
938
+ err(f"clients/{pack}/manifest.json: Feld permission_tools.skill fehlt. Ein "
939
+ f"Pack fuehrt es, notfalls leer - der Unterschied zwischen „nicht "
940
+ f"abgebildet“ und „gibt es nicht“ gehoert deklariert und nicht aus einem "
941
+ f"fehlenden Feld erraten (D-155, D-238)")
942
+ return
943
+ namen = [w for w in (werkzeuge.get("skill") or []) if isinstance(w, str)]
944
+ if not namen:
945
+ return # Deklariert leer (devin-desktop, D-89) - keine Schreibweise bekannt
946
+ rel_skills = man.get("skills_dir")
947
+ rel_perm = man.get("permissions_file")
948
+ if not rel_skills or not rel_perm:
949
+ return
950
+ ablage = os.path.join(root, *rel_skills.split("/"))
951
+ pfad = os.path.join(root, *rel_perm.split("/"))
952
+ if not os.path.isdir(ablage) or not os.path.isfile(pfad):
953
+ return # Keine Installation an dieser Wurzel - hier gibt es nichts zu decken
954
+ try:
955
+ cfg = json.loads(read(pfad))
956
+ except json.JSONDecodeError:
957
+ return # Pruefung 3 meldet das bereits
958
+ im_baum = sorted(n for n in os.listdir(ablage)
959
+ if os.path.isfile(os.path.join(ablage, n, "SKILL.md")))
960
+ genannt = []
961
+ if man.get("permissions_format") == "kiro-agent":
962
+ # Das Agentenprofil nennt einen Skill nicht als Werkzeug(Name), sondern als
963
+ # Muster einer Regel mit der Faehigkeit des Skillaufrufs (D-414, D-416).
964
+ regeln = ((cfg.get("permissions") or {}).get("rules") or []
965
+ if isinstance(cfg, dict) else [])
966
+ for regel in regeln:
967
+ if isinstance(regel, dict) and regel.get("capability") in namen:
968
+ genannt.extend(m for m in (regel.get("match") or [])
969
+ if isinstance(m, str) and m.strip())
970
+ for eintrag in ([] if genannt else _p72_eintraege(cfg)):
971
+ m = P72_MUSTER.match(eintrag.strip())
972
+ if m and m.group(1) in namen and m.group(2):
973
+ genannt.append(m.group(2))
974
+ # Ein deklarierter fremder Skill (1.21.0, K-31) ist auch durch ein Muster seines
975
+ # Praefixes genannt, etwa Skill(openspec-*) - der Korb bleibt Pflicht, nur die
976
+ # Schreibweise ist die des fremden Rahmenwerks.
977
+ fremd = fremde_praefixe(root)
978
+ muster = [g[:-1] for g in genannt if g.endswith("*")]
979
+ for name in im_baum:
980
+ if name not in genannt and not (
981
+ any(name.startswith(p) for p in fremd)
982
+ and any(m and name.startswith(m) for m in muster)):
983
+ err(f"{rel_perm}: der Skill '{name}' liegt in {rel_skills}/, wird aber "
984
+ f"von keinem Eintrag der Berechtigungsdatei genannt. Sein Aufruf "
985
+ f"faellt in den Rueckfragekorb und im rueckfragefreien Betrieb in die "
986
+ f"Abweisung; die Sitzung liest die SKILL.md dann ersatzweise als "
987
+ f"Datei, ohne die Werkzeugbeschraenkung des Skills (D-81). Bei einem "
988
+ f"Pack-Skill ist das der dritte Teil der Aktivierung "
989
+ f"(framework/role-packs/README.md), nicht ein Schritt von install.py "
990
+ f"(D-238)")
991
+ for name in sorted(set(genannt)):
992
+ if name not in im_baum and not name.endswith("*"):
993
+ err(f"{rel_perm}: der Eintrag fuer '{name}' nennt keinen Skill in "
994
+ f"{rel_skills}/. Eine Freigabe fuer einen Skill, den diese "
995
+ f"Installation nicht hat, ist eine Zusage ohne Gegenstand (D-81, "
996
+ f"D-238)")
997
+
998
+
999
+ # ---------------------------------------------------------------------------
1000
+ # PRUEFUNG 88: DIE DATEI, DIE DIE WURZEL-ANWEISUNG VERDRAENGT
1001
+ # ---------------------------------------------------------------------------
1002
+ # ANLASS, UND ER IST DER SCHWERSTE BEFUND DER ERHEBUNG VON 1.3.0 (D-341, hier
1003
+ # durchgesetzt mit CR-2026-133). Bei einem Client kann eine Datei NEBEN der
1004
+ # Wurzel-Anweisung diese vollstaendig ersetzen: Liegt sie im Projekt, steht die
1005
+ # Wurzel-Anweisung des Frameworks in KEINER Nachricht der Sitzung - gemessen mit
1006
+ # Gegenprobe. Ein aufnehmendes Projekt, das die Datei in sein `.gitignore` schreibt -
1007
+ # der naheliegende Ort fuer eine persoenliche Fassung -, haette damit eine
1008
+ # unversionierte Ebene 1 je Arbeitsplatz.
1009
+ # ➡️ Eine Wurzel-Anweisung, die eine ungepruefte Datei im selben Verzeichnis ersetzen
1010
+ # kann, ist keine Ebene 1 - sie ist ein Standard.
1011
+ # Welches Pack eine solche Datei kennt, sagt das Manifest (root_instruction_override).
1012
+ # Der Schutz besteht aus drei Teilen, und dies ist der dritte: Der deny-Korb stellt die
1013
+ # Datei schreibgeschuetzt, der Schutz-Hook fuehrt sie in seinen Mustern - und diese
1014
+ # Pruefung MELDET SIE, wenn sie trotzdem da ist. Die ersten beiden verhindern, dass der
1015
+ # Agent sie anlegt; ein Mensch kann es weiterhin, und dann soll es nicht still bleiben.
1016
+ def check_verdraengende_wurzelanweisung(root: str, man: dict) -> None:
1017
+ """Pruefung 88 (D-341, D-347): Keine Datei, die die Wurzel-Anweisung verdraengt."""
1018
+ if not man.get("root_instruction_override"):
1019
+ return
1020
+ rel = (man.get("runtime_placeholders") or {}).get("<ROOT_INSTRUCTION_LOCAL>")
1021
+ if not rel:
1022
+ err(f"clients/{man.get('client', '?')}/manifest.json: root_instruction_override "
1023
+ f"ist gesetzt, aber <ROOT_INSTRUCTION_LOCAL> fehlt in runtime_placeholders – "
1024
+ f"Prüfung 88 wüsste nicht, welche Datei sie meldet (D-23)")
1025
+ return
1026
+ if os.path.exists(os.path.join(root, *rel.split("/"))):
1027
+ err(f"{rel}: liegt im Projekt und VERDRÄNGT bei diesem Client die "
1028
+ f"Wurzel-Anweisung {man.get('root_instruction_file', '?')} vollständig – "
1029
+ f"deren Text steht dann in keiner Nachricht der Sitzung. Ebene 1 der "
1030
+ f"Prioritätshierarchie wäre damit durch eine ungeprüfte Datei ersetzt, und "
1031
+ f"bis 1.4.0 meldete es nichts (D-341)")
1032
+
1033
+
1034
+ # ---------------------------------------------------------------------------
1035
+ # PRUEFUNG 96: DAS AGENTENPROFIL, DAS DIE BERECHTIGUNGEN TRAEGT, IST AUCH DAS AKTIVE
1036
+ # ---------------------------------------------------------------------------
1037
+ # ANLASS, GEMESSEN AM 2026-09-26 (CR-2026-150, D-414). Beim Client kiro stehen die
1038
+ # Berechtigungen des Frameworks in einem Agentenprofil, und sie wirken NUR, wenn es der
1039
+ # aktive Agent ist. Die Einstellungsdatei des Arbeitsbereichs waehlt es. Drei Befunde
1040
+ # machen daraus eine Pruefung:
1041
+ # * Zeigt die Einstellung auf ein Profil, das es nicht gibt, oder ist das Profil kein
1042
+ # gueltiges JSON, faellt der Client STILL auf seinen eingebauten Agenten zurueck -
1043
+ # eine Warnung auf stderr, und .env war lesbar (K15, K16).
1044
+ # * Eine Regel mit unbekannter Faehigkeit ueberspringt er und meldet es nur in seinem
1045
+ # Protokoll (K23). Sein eigenes Pruefkommando `agent validate` endet auch bei
1046
+ # kaputtem JSON mit Exit 0 und prueft keine Faehigkeit (K22).
1047
+ # * Die Pruefungen der Koerbe (37 und Verwandte) lesen deny/ask/allow und erreichen
1048
+ # diese Form nicht (D-416). Diese Pruefung tritt fuer das Agentenprofil an ihre
1049
+ # Stelle.
1050
+ # GEPRUEFT: (a) die Einstellungsdatei traegt die Werte des Manifests; (b) das Profil ist
1051
+ # ein JSON-Objekt mit dem Namen, den die Einstellung waehlt; (c) jede Regel hat eine
1052
+ # bekannte Faehigkeit, einen bekannten Effekt und eine Liste von Mustern; (d) jedes
1053
+ # deny- und ask-Muster der Kernquelle steht mit seinem Effekt (und seiner Ausnahme) im
1054
+ # Profil, und kein allow-Muster steht darin, das die Kernquelle nicht erzeugt - ausser
1055
+ # dem Namen eines installierten Skills (Bauform von Pruefung 72, D-238).
1056
+ # ⚠️ GRENZEN, BENANNT: Ein Muster mit offenem Platzhalter (<EXCLUDED_PATHS> ...) gehoert
1057
+ # dem Projekt und wird nicht verglichen. Und die Pruefung misst die Dateien, nicht den
1058
+ # Start: Wer den Client mit --agent auf einen anderen Agenten stellt, arbeitet ohne diese
1059
+ # Regeln, und keine Pruefung sieht es.
1060
+ P96_EFFEKTE = ("deny", "ask", "allow")
1061
+
1062
+
1063
+ def _p96_regeln(cfg: dict) -> list:
1064
+ return ((cfg.get("permissions") or {}).get("rules") or []) if isinstance(cfg, dict) else []
1065
+
1066
+
1067
+ def check_agentenprofil(root: str, man: dict) -> None:
1068
+ """Pruefung 96 (D-414): Die Einstellung waehlt das Profil, und das Profil traegt."""
1069
+ if man.get("permissions_format") != "kiro-agent":
1070
+ return
1071
+ pack = man.get("client", "?")
1072
+ kern = os.path.join(root, KERN)
1073
+ if kern not in sys.path:
1074
+ sys.path.insert(0, kern)
1075
+ try:
1076
+ import clientmap
1077
+ except ImportError:
1078
+ return # check_config meldet das fehlende Abbildungsmodul bereits
1079
+ if not hasattr(clientmap, "kiro_regeln") or not hasattr(clientmap, "KIRO_FAEHIGKEITEN"):
1080
+ err(f"{KERN}/clientmap.py: 'kiro_regeln' oder 'KIRO_FAEHIGKEITEN' fehlt – Prüfung "
1081
+ f"96 hat ihren Gegenstand verloren und bestünde sonst leise (D-23)")
1082
+ return
1083
+ # (a) Die Einstellungsdatei.
1084
+ einst_rel = man.get("client_settings_file")
1085
+ soll = man.get("client_settings") or {}
1086
+ profil_rel = man.get("permissions_file")
1087
+ if not einst_rel or not soll or not profil_rel:
1088
+ err(f"clients/{pack}/manifest.json: client_settings_file, client_settings und "
1089
+ f"permissions_file gehören zusammen – ohne sie weiß Prüfung 96 nicht, welche "
1090
+ f"Datei das Profil wählt (D-414)")
1091
+ return
1092
+ einst_pfad = os.path.join(root, *einst_rel.split("/"))
1093
+ profil_pfad = os.path.join(root, *profil_rel.split("/"))
1094
+ if not os.path.exists(einst_pfad) and not os.path.exists(profil_pfad):
1095
+ return # keine Installation dieses Packs an dieser Wurzel
1096
+ if not os.path.isfile(einst_pfad):
1097
+ err(f"{einst_rel}: fehlt. Ohne diese Datei startet der Client mit seinem "
1098
+ f"eingebauten Agenten und seiner Standard-Engine – die Berechtigungen in "
1099
+ f"{profil_rel} wirken dann nicht, und nichts meldet es (D-414)")
1100
+ else:
1101
+ try:
1102
+ einst = json.loads(read(einst_pfad))
1103
+ except json.JSONDecodeError:
1104
+ einst = None
1105
+ if not isinstance(einst, dict):
1106
+ err(f"{einst_rel}: kein JSON-Objekt. Der Client läse daraus weder Agent noch "
1107
+ f"Engine (D-414)")
1108
+ else:
1109
+ for schluessel, wert in soll.items():
1110
+ if einst.get(schluessel) != wert:
1111
+ err(f"{einst_rel}: '{schluessel}' ist {einst.get(schluessel)!r}, das "
1112
+ f"Pack verlangt {wert!r}. Mit einem anderen Wert wählt der Client "
1113
+ f"einen anderen Agenten oder eine Engine, die das Profilfeld "
1114
+ f"permissions nicht kennt – und fällt dabei still zurück (D-414)")
1115
+ # (b) Das Profil.
1116
+ if not os.path.isfile(profil_pfad):
1117
+ err(f"{profil_rel}: fehlt. Die Einstellung wählt ein Profil, das es nicht gibt – "
1118
+ f"der Client fällt STILL auf seinen eingebauten Agenten zurück, gemessen: "
1119
+ f".env war danach lesbar (D-414)")
1120
+ return
1121
+ try:
1122
+ cfg = json.loads(read(profil_pfad))
1123
+ except json.JSONDecodeError as exc:
1124
+ err(f"{profil_rel}: kein gültiges JSON ({exc.msg}, Zeile {exc.lineno}). Der Client "
1125
+ f"fällt dann STILL auf seinen eingebauten Agenten zurück – sein eigenes "
1126
+ f"Prüfkommando 'agent validate' endet dabei mit Exit 0 (D-414)")
1127
+ return
1128
+ name = man.get("permission_profile_name")
1129
+ if not isinstance(cfg, dict) or cfg.get("name") != name:
1130
+ err(f"{profil_rel}: das Profil heißt {cfg.get('name') if isinstance(cfg, dict) else '?'!r}, "
1131
+ f"die Einstellung wählt {name!r}. Ein Name, den kein Profil trägt, ist der "
1132
+ f"stille Rückfall aus D-414")
1133
+ return
1134
+ # (c) Jede Regel ist eine, die der Client ausführt.
1135
+ regeln = _p96_regeln(cfg)
1136
+ if not regeln:
1137
+ err(f"{profil_rel}: keine einzige Regel unter permissions.rules – das Profil wäre "
1138
+ f"aktiv und sperrte nichts (D-414)")
1139
+ return
1140
+ ist: dict = {e: {} for e in P96_EFFEKTE}
1141
+ for nr, regel in enumerate(regeln, 1):
1142
+ if not isinstance(regel, dict):
1143
+ err(f"{profil_rel}: Regel {nr} ist kein Objekt (D-414)")
1144
+ continue
1145
+ faehigkeit, effekt = regel.get("capability"), regel.get("effect")
1146
+ muster = regel.get("match")
1147
+ if faehigkeit not in clientmap.KIRO_FAEHIGKEITEN:
1148
+ err(f"{profil_rel}: Regel {nr} nennt die Fähigkeit {faehigkeit!r}, die der "
1149
+ f"Client nicht kennt. Er überspringt die Regel und meldet es nur in seinem "
1150
+ f"Protokoll – gemessen (D-414)")
1151
+ continue
1152
+ if effekt not in P96_EFFEKTE:
1153
+ err(f"{profil_rel}: Regel {nr} hat den Effekt {effekt!r}; bekannt sind "
1154
+ f"{', '.join(P96_EFFEKTE)} (D-414)")
1155
+ continue
1156
+ if not isinstance(muster, list) or not all(isinstance(m, str) for m in muster):
1157
+ err(f"{profil_rel}: Regel {nr} führt 'match' nicht als Liste von Mustern (D-414)")
1158
+ continue
1159
+ ausnahme = tuple(regel.get("exclude") or ())
1160
+ for m in muster:
1161
+ ist[effekt].setdefault((faehigkeit, m), set()).add(ausnahme)
1162
+ # (d) Gegen die Kernquelle.
1163
+ try:
1164
+ quelle = json.loads(clientmap.load_source(kern, "permissions.json"))
1165
+ erzeugt = clientmap.kiro_regeln(quelle, man)
1166
+ except (OSError, ValueError) as exc:
1167
+ err(f"Kernquelle der Berechtigungen nicht auswertbar: {exc}")
1168
+ return
1169
+ soll_regeln: dict = {e: {} for e in P96_EFFEKTE}
1170
+ for regel in erzeugt:
1171
+ for m in regel["match"]:
1172
+ soll_regeln[regel["effect"]].setdefault((regel["capability"], m), set()).add(
1173
+ tuple(regel.get("exclude") or ()))
1174
+ for effekt in ("deny", "ask"):
1175
+ for (faehigkeit, m), ausnahmen in soll_regeln[effekt].items():
1176
+ if m.startswith("<"):
1177
+ continue # Projektschlitz - gehört dem Overlay
1178
+ vorhanden = ist[effekt].get((faehigkeit, m))
1179
+ if not vorhanden:
1180
+ err(f"{profil_rel}: die Kernquelle erzeugt '{effekt} {faehigkeit} {m}'; im "
1181
+ f"Profil steht es nicht. Eine fehlende Regel ist eine Lockerung (D-77, "
1182
+ f"D-414)")
1183
+ elif not ausnahmen <= vorhanden:
1184
+ err(f"{profil_rel}: '{effekt} {faehigkeit} {m}' steht im Profil mit einer "
1185
+ f"anderen Ausnahme als in der Abbildung {sorted(ausnahmen)} – eine "
1186
+ f"breitere Ausnahme ist eine Lockerung (D-415)")
1187
+ skills = {f"skill {n}" for n in installierte_skills(root, man)}
1188
+ for (faehigkeit, m) in ist["allow"]:
1189
+ if (faehigkeit, m) in soll_regeln["allow"] or m.startswith("<"):
1190
+ continue
1191
+ if faehigkeit == "skill" and f"skill {m}" in skills:
1192
+ continue # aktivierter Pack-Skill, Bauform von Prüfung 72 (D-238)
1193
+ err(f"{profil_rel}: 'allow {faehigkeit} {m}' erzeugt die Kernquelle nicht. Eine "
1194
+ f"zusätzliche Freigabe ist eine Ausweitung und nicht Sache des Profils (D-77)")
1195
+
1196
+
1197
+ # ---------------------------------------------------------------------------
1198
+ # PRUEFUNGEN 91 BIS 94: DIE DOKUMENTATION (CR-2026-142, D-371 bis D-375)
1199
+
1200
+ # ---------------------------------------------------------------------------
1201
+ # PRUEFUNG 97: DIE BERECHTIGUNGSDATEI, MIT DER DER CLIENT STARTET UND DIE TRIFFT
1202
+ # ---------------------------------------------------------------------------
1203
+ # ANLASS, UND ER IST GEMESSEN (CR-2026-155, D-440). Am 2026-09-26 an cursor-agent
1204
+ # 2026.09.26 unter Windows:
1205
+ # * Mit den Schluesseln _comment und _core_rules_integrity in .cursor/cli.json brach
1206
+ # der Client mit Exit 1 ab ("Unrecognized key(s) in object") - ebenso bei kaputtem
1207
+ # JSON. Die Kernregeln koennen deshalb nicht in der Datei selbst stehen wie bei der
1208
+ # ersten Ausgabeform; diese Pruefung haelt sie gegen die Kernquelle.
1209
+ # * Ein Pfadmuster vergleicht der Client verankert mit dem ABSOLUTEN Pfad. 'Read(.env)'
1210
+ # und 'Read(**/.env)' liessen den Koeder durch, 'Read(*\.env)' nicht. Ein Verbot,
1211
+ # das so nie trifft, sieht aus wie eines.
1212
+ # * Die Pruefungen der Koerbe (37 und Verwandte) erreichen diese Form nicht (D-416).
1213
+ # GEPRUEFT: (a) gueltiges JSON, ein Objekt mit genau dem Schluessel permissions, darin nur
1214
+ # allow und deny als Listen von Zeichenketten; (b) jeder Eintrag hat die Gestalt
1215
+ # Typ(Argument) mit einem Regeltyp des Clients; (c) jedes deny der Kernquelle steht darin,
1216
+ # eine Kernzusage mit eigenem Wortlaut; kein allow, das die Kernquelle nicht erzeugt;
1217
+ # (d) Warnung fuer ein Pfadverbot ohne fuehrendes '*' und ohne absoluten Pfad.
1218
+ # GRENZE, BENANNT: Gemessen ist Windows. Die Schreibweise mit '/' fuer POSIX folgt aus dem
1219
+ # Programmcode des Clients, nicht aus einer Messung auf macOS oder Linux.
1220
+ P97_TYPEN = ("Shell", "Bash", "Read", "Write", "WebFetch", "Mcp")
1221
+ P97_EINTRAG_RE = re.compile(r"^\s*([A-Za-z]+)\s*\((.*)\)\s*$")
1222
+
1223
+
1224
+ def check_cursor_berechtigungen(root: str, man: dict) -> None:
1225
+ """Pruefung 97 (D-440): Die Datei laesst den Client starten, und ihre Verbote treffen."""
1226
+ if man.get("permissions_format") != "cursor-json":
1227
+ return
1228
+ rel = man.get("permissions_file") or ""
1229
+ pfad = os.path.join(root, *rel.split("/"))
1230
+ if not os.path.isfile(pfad):
1231
+ return # keine Installation dieses Packs an dieser Wurzel; Pflichtpfade melden es
1232
+ kern = os.path.join(root, KERN)
1233
+ if kern not in sys.path:
1234
+ sys.path.insert(0, kern)
1235
+ try:
1236
+ import clientmap
1237
+ except ImportError:
1238
+ return # check_config meldet das fehlende Abbildungsmodul bereits
1239
+ if not hasattr(clientmap, "cursor_koerbe") or not hasattr(clientmap, "cursor_kernregeln"):
1240
+ err(f"{KERN}/clientmap.py: 'cursor_koerbe' oder 'cursor_kernregeln' fehlt – Prüfung "
1241
+ f"97 hat ihren Gegenstand verloren und bestünde sonst leise (D-23)")
1242
+ return
1243
+ # (a) Die Gestalt, mit der der Client startet.
1244
+ try:
1245
+ cfg = json.loads(read(pfad))
1246
+ except json.JSONDecodeError as exc:
1247
+ err(f"{rel}: kein gültiges JSON ({exc.msg}, Zeile {exc.lineno}). Der Client startet "
1248
+ f"damit nicht – gemessen: Exit 1 (D-440)")
1249
+ return
1250
+ if not isinstance(cfg, dict) or set(cfg) != {"permissions"}:
1251
+ fremd = sorted(set(cfg) - {"permissions"}) if isinstance(cfg, dict) else ["?"]
1252
+ err(f"{rel}: die Datei trägt {', '.join(fremd) or 'keinen Schlüssel permissions'}. "
1253
+ f"Der Client nimmt auf Projektebene nur 'permissions' an und startet mit einem "
1254
+ f"weiteren Schlüssel nicht – gemessen mit _comment und _core_rules_integrity: "
1255
+ f"Exit 1, 'Unrecognized key(s)' (D-440)")
1256
+ return
1257
+ rechte = cfg["permissions"]
1258
+ if not isinstance(rechte, dict) or not set(rechte) <= {"allow", "deny"}:
1259
+ err(f"{rel}: 'permissions' darf nur allow und deny führen – dieser Client kennt "
1260
+ f"keinen Rückfragekorb (D-440)")
1261
+ return
1262
+ ist: dict = {}
1263
+ for korb in ("allow", "deny"):
1264
+ liste = rechte.get(korb, [])
1265
+ if not isinstance(liste, list) or not all(isinstance(e, str) for e in liste):
1266
+ err(f"{rel}: permissions.{korb} ist keine Liste von Zeichenketten (D-440)")
1267
+ return
1268
+ ist[korb] = set(liste)
1269
+ # (b) Jeder Eintrag ist eine Regel, die der Client kennt.
1270
+ for eintrag in liste:
1271
+ m = P97_EINTRAG_RE.match(eintrag)
1272
+ if not m or m.group(1) not in P97_TYPEN:
1273
+ err(f"{rel}: '{eintrag}' in permissions.{korb} ist keine Regel der Gestalt "
1274
+ f"Typ(Argument) mit einem Typ dieses Clients ({', '.join(P97_TYPEN)}). "
1275
+ f"Eine solche Zeile wertet der Client nicht aus (D-440)")
1276
+ continue
1277
+ # (d) Ein Pfadverbot, das nie trifft.
1278
+ argument = m.group(2).strip()
1279
+ if korb == "deny" and m.group(1) in ("Read", "Write") \
1280
+ and not argument.startswith(("*", "<", "/", "~")) \
1281
+ and not re.match(r"^[A-Za-z]:[\\/]", argument):
1282
+ warn(f"{rel}: '{eintrag}' trifft bei diesem Client nie. Er vergleicht das "
1283
+ f"Muster mit dem ABSOLUTEN Pfad – gemessen: 'Read(.env)' und "
1284
+ f"'Read(**/.env)' ließen den Köder durch. Die Schreibweise mit "
1285
+ f"führendem '*' in beiden Trennern: 'Read(*/<pfad>)' und "
1286
+ f"'Read(*\\<pfad>)' (D-440)")
1287
+ # (c) Gegen die Kernquelle.
1288
+ try:
1289
+ quelle = json.loads(clientmap.load_source(kern, "permissions.json"))
1290
+ soll = clientmap.cursor_koerbe(quelle, man)
1291
+ kernregeln = set(clientmap.cursor_kernregeln(quelle, man))
1292
+ except (OSError, ValueError) as exc:
1293
+ err(f"Kernquelle der Berechtigungen nicht auswertbar: {exc}")
1294
+ return
1295
+ for eintrag in soll["deny"]:
1296
+ if "<" in eintrag or eintrag in ist["deny"]:
1297
+ continue # Projektschlitz gehört dem Overlay
1298
+ if eintrag in kernregeln:
1299
+ err(f"{rel}: die Kernzusage '{eintrag}' fehlt in permissions.deny. Kernregeln "
1300
+ f"darf das Projekt nicht entfernen; diese Datei kann sie nicht selbst "
1301
+ f"auflisten, deshalb hält diese Prüfung sie gegen die Kernquelle (D-440)")
1302
+ else:
1303
+ err(f"{rel}: '{eintrag}' aus der Kernquelle fehlt in permissions.deny. Eine "
1304
+ f"Regel des Kerns zu streichen ist eine Lockerung (D-77, D-440)")
1305
+ for eintrag in sorted(ist["allow"] - set(soll["allow"])):
1306
+ err(f"{rel}: '{eintrag}' in permissions.allow erzeugt die Kernquelle nicht. Eine "
1307
+ f"zusätzliche Freigabe ist eine Ausweitung; ein freigegebener Befehl gehört in "
1308
+ f"Abschnitt 6 des Overlays und damit in die Regelschicht (D-77, D-440)")
1309
+ # (e) Die Ausschlussdatei (D-443): die zweite Lesesperre, und die einzige, die das
1310
+ # Suchwerkzeug beachtet - gemessen am 2026-09-26.
1311
+ ign_rel = man.get("ignore_file")
1312
+ if not ign_rel or not hasattr(clientmap, "cursor_ignore_muster"):
1313
+ return
1314
+ ign_pfad = os.path.join(root, *ign_rel.split("/"))
1315
+ if not os.path.isfile(ign_pfad):
1316
+ err(f"{ign_rel}: fehlt. Ohne sie wertet das Suchwerkzeug dieses Clients kein "
1317
+ f"Leseverbot aus – gemessen: der Köder aus secrets/ kam heraus (D-443)")
1318
+ return
1319
+ zeilen = {z.strip() for z in read(ign_pfad).splitlines()}
1320
+ for muster in clientmap.cursor_ignore_muster(quelle, man):
1321
+ if muster not in zeilen:
1322
+ err(f"{ign_rel}: das Leseverbot '{muster}' der Kernquelle fehlt. Das "
1323
+ f"Suchwerkzeug fände dort wieder, was die Berechtigungsdatei nur dem "
1324
+ f"Lesewerkzeug verbietet (D-443)")
1325
+ P99_ANKER = ("MANDAT_DATEI = ", "MANDATS_MUSTER = ", "def mandat_lesen(")
1326
+ P99_WERTE_RE = (re.compile(r'^MANDAT_DATEI = "([^"]+)"', re.M),
1327
+ re.compile(r"^MANDAT_HOECHSTDAUER_MIN = (\d+)", re.M),
1328
+ re.compile(r"^MANDAT_UMFAENGE = \{(.*?)\n\}", re.M | re.S),
1329
+ # Die Modusbindung liegt neben dem Mandat (D-501, K-179)
1330
+ re.compile(r'^MODUS_DATEI = "([^"]+)"', re.M),
1331
+ re.compile(r"^MODUS_GEBUNDEN = (\(.*?\))", re.M))
1332
+
1333
+
1334
+ def _p99_werte(text: str) -> tuple:
1335
+ return tuple((r.search(text).group(1).strip() if r.search(text) else None)
1336
+ for r in P99_WERTE_RE)
1337
+
1338
+
1339
+ # Seit 1.23.0 (CR-2026-169, K-201): M3 bis M5 binden Globs aus dem Overlay. mandat.py
1340
+ # zeigt sie an, der Hook wertet sie aus - mit je einer Funktion 'glob_muster'. Zwei
1341
+ # Lesarten desselben Globs hiessen: Der Mensch sieht eine Grenze, der Hook zieht eine
1342
+ # andere. Verglichen wird der Rumpf ohne Docstring, als Syntaxbaum.
1343
+ def _p99_glob_rumpf(text: str):
1344
+ try:
1345
+ baum = ast.parse(text)
1346
+ except SyntaxError:
1347
+ return None
1348
+ for knoten in baum.body:
1349
+ if isinstance(knoten, ast.FunctionDef) and knoten.name == "glob_muster":
1350
+ rumpf = list(knoten.body)
1351
+ if (rumpf and isinstance(rumpf[0], ast.Expr)
1352
+ and isinstance(getattr(rumpf[0], "value", None), ast.Constant)
1353
+ and isinstance(rumpf[0].value.value, str)):
1354
+ rumpf = rumpf[1:]
1355
+ return "".join(ast.dump(k) for k in rumpf)
1356
+ return None
1357
+
1358
+
1359
+ def check_mandatsschutz(root: str, man: dict) -> None:
1360
+ """Pruefung 99 (D-447, D-448): Das Mandat erteilt nur der Mensch, und es wirkt."""
1361
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
1362
+ mandat = os.path.join(root, KERN, "mandat.py")
1363
+ if not os.path.isfile(skript):
1364
+ return
1365
+ hook = read(skript)
1366
+ fehlend = [a for a in P99_ANKER if a not in hook]
1367
+ if fehlend:
1368
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: {', '.join(fehlend)} fehlt. Pruefung "
1369
+ f"99 misst den Schutz des Mandats; ohne ihn bestuende sie leise (D-23, D-447)")
1370
+ return
1371
+ if not os.path.isfile(mandat):
1372
+ err(f"{KERN}/mandat.py fehlt. Der Hook kennt ein Mandat, das niemand erteilen kann - "
1373
+ f"Modus M6 waere eine Zusage ohne Werkzeug (D-447)")
1374
+ return
1375
+ if _p99_werte(hook) != _p99_werte(read(mandat)):
1376
+ err(f"{KERN}/mandat.py und hook-check-secrets.py fuehren verschiedene Werte fuer "
1377
+ f"Dateiname, Hoechstdauer oder Umfaenge des Mandats: Hook {_p99_werte(hook)}, "
1378
+ f"mandat.py {_p99_werte(read(mandat))}. Ein Mandat, das das Werkzeug schreibt und "
1379
+ f"der Hook nicht liest, gibt nichts frei (D-447)")
1380
+ glob_hook, glob_mandat = _p99_glob_rumpf(hook), _p99_glob_rumpf(read(mandat))
1381
+ if glob_hook is None or glob_hook != glob_mandat:
1382
+ err(f"{KERN}/mandat.py und hook-check-secrets.py werten die Globs der Modusbindung "
1383
+ f"nicht gleich aus: Die Funktion glob_muster fehlt in einer der beiden Dateien "
1384
+ f"oder ihr Rumpf weicht ab. Der Mensch saehe beim Binden eine andere Grenze, als "
1385
+ f"der Hook zieht (K-201)")
1386
+ quelle = os.path.join(root, KERN, "framework", "runtime", "permissions.json")
1387
+ if os.path.isfile(quelle):
1388
+ try:
1389
+ regeln = json.loads(read(quelle)).get("deny", [])
1390
+ except (json.JSONDecodeError, AttributeError):
1391
+ regeln = []
1392
+ if any(isinstance(r, dict) and r.get("tool") == "write"
1393
+ and "project-overlay" in str(r.get("pattern", "")) for r in regeln):
1394
+ err(f"{KERN}/framework/runtime/permissions.json: sperrt das Overlay im deny-Korb. "
1395
+ f"Eine statische Sperre hebt kein Mandat auf; das Overlay sperrt der "
1396
+ f"Schutz-Hook (D-448)")
1397
+ interpreter = _hook_interpreter()
1398
+ if interpreter is None:
1399
+ return
1400
+ faelle = [
1401
+ ("Aufruf von mandat.py erteilen",
1402
+ {"tool_name": "exec", "tool_input": {"command":
1403
+ f"python {KERN}/mandat.py erteilen --rolle P99 --umfang overlay --minuten 5"}}, 2),
1404
+ ("Schreiben der Mandatsdatei",
1405
+ {"tool_name": "write", "tool_input": {"file_path": ".git/koolie-mandat.json",
1406
+ "content": "{}"}}, 2),
1407
+ ("die Auskunft mandat.py status",
1408
+ {"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py status"}}, 0),
1409
+ # claude-code schickt eine Beschreibung mit - sie darf die Auskunft nicht sperren
1410
+ # (gemessen am 2026-09-27, Lauf sk013n01)
1411
+ ("die Auskunft mit einer Beschreibung daneben",
1412
+ {"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py status",
1413
+ "description": "Mandatsstatus abfragen"}}, 0),
1414
+ ("die Auskunft mit einem zweiten Befehl dahinter",
1415
+ {"tool_name": "exec", "tool_input": {"command":
1416
+ f"python {KERN}/mandat.py status; python {KERN}/mandat.py erteilen --rolle P99"}}, 2),
1417
+ # Die Modusbindung setzt wie das Mandat nur der Mensch (D-501, K-179)
1418
+ ("Aufruf von mandat.py modus",
1419
+ {"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py modus aus"}},
1420
+ 2),
1421
+ ("Schreiben der Modusdatei",
1422
+ {"tool_name": "write", "tool_input": {"file_path": ".git/koolie-modus.json",
1423
+ "content": "{}"}}, 2),
1424
+ ]
1425
+ # Das Overlay ohne Mandat - nur, wenn im gepruefen Baum gerade keines erteilt ist: Ein
1426
+ # erteiltes Mandat ist ein gueltiger Zustand, kein Befund.
1427
+ if not os.path.exists(os.path.join(root, ".git", "koolie-mandat.json")):
1428
+ faelle.append(("Schreiben in das Overlay ohne Mandat",
1429
+ {"tool_name": "write", "tool_input": {
1430
+ "file_path": ".koolie/project-overlay/OVERLAY.md", "content": "x"}}, 2))
1431
+ for was, ereignis, erwartet in faelle:
1432
+ ergebnis = _hook_lauf(interpreter, skript, json.dumps(ereignis))
1433
+ if ergebnis != erwartet:
1434
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: {was} endet mit Exit {ergebnis}, "
1435
+ f"erwartet {erwartet}. Das Mandat erteilt nur der Mensch im eigenen Terminal; "
1436
+ f"der Client darf es weder anlegen noch aufrufen, nur nachsehen (D-447)")
1437
+
1438
+
1439
+ def check_modellaufruf_nur_lesend(root: str, man: dict) -> None:
1440
+ """Pruefung 100 (D-451): Ein Skill mit Trigger 'model' ist rein lesend."""
1441
+ if yaml is None:
1442
+ return # ohne PyYAML kein Frontmatter - die Warnung dazu gibt main()
1443
+ for skills_dir, prefix in skill_dirs(root, man):
1444
+ if not prefix.startswith(KERN + "/"):
1445
+ continue # die installierte Fassung traegt das Feld nicht in jedem Pack
1446
+ for name in sorted(os.listdir(skills_dir)):
1447
+ pfad = os.path.join(skills_dir, name, "SKILL.md")
1448
+ if not os.path.isfile(pfad):
1449
+ continue
1450
+ fm, _ = parse_frontmatter(read(pfad))
1451
+ if not isinstance(fm, dict) or "model" not in (fm.get("triggers") or []):
1452
+ continue
1453
+ deny = [str(d).strip().lower()
1454
+ for d in ((fm.get("permissions") or {}).get("deny") or [])]
1455
+ fehlt = [v for v in ("edit", "exec") if v not in deny]
1456
+ if fehlt:
1457
+ err(f"{prefix}/{name}/SKILL.md: traegt den Trigger 'model', sperrt aber "
1458
+ f"{', '.join(fehlt)} nicht in permissions.deny. Den Modellaufruf erlaubt "
1459
+ f"08-skill-conventions.md nur einem rein lesenden Skill (D-451)")
1460
+
1461
+
1462
+ # --- Pruefung 101: die MCP-Freigaben (CR-2026-157, D-459) ------------------------------
1463
+ P101_ABSCHNITT = "### 13.2 MCP-Server"
1464
+ P101_NAME_RE = re.compile(r"`([A-Za-z0-9_.-]+)`")
1465
+ P101_KERNQUELLE = "framework/runtime/permissions.json"
1466
+
1467
+
1468
+ def _p101_freigaben(text: str) -> list:
1469
+ """Die Zeilen der Tabelle in Overlay Abschnitt 13.2 - Server, Zweck, Werkzeuge."""
1470
+ if P101_ABSCHNITT not in text:
1471
+ return []
1472
+ teil = text.replace("\r\n", "\n").split(P101_ABSCHNITT, 1)[1]
1473
+ teil = re.split(r"\n#{1,3} ", teil, maxsplit=1)[0]
1474
+ zeilen = []
1475
+ for z in teil.split("\n"):
1476
+ if not z.strip().startswith("|"):
1477
+ continue
1478
+ zellen = tabellenzellen(z)
1479
+ if len(zellen) < 5 or zellen[0].startswith("Server") or not zellen[0].strip("-: "):
1480
+ continue
1481
+ server = zellen[0].strip().strip("`").strip()
1482
+ if not server or TBD_RE.search(zellen[0]) or server in ("–", "-", "keine"):
1483
+ continue
1484
+ zeilen.append({"server": server, "zweck": zellen[2].lower(),
1485
+ "lesen": P101_NAME_RE.findall(zellen[3]),
1486
+ "schreiben": P101_NAME_RE.findall(zellen[4])})
1487
+ return zeilen
1488
+
1489
+
1490
+ def check_mcp_freigaben(root: str, man: dict, strikt: bool) -> None:
1491
+ """Pruefung 101 (D-459): MCP-Werkzeuge stehen einzeln und nur lesend in allow."""
1492
+ kq = os.path.join(root, KERN, *P101_KERNQUELLE.split("/"))
1493
+ if os.path.isfile(kq):
1494
+ try:
1495
+ quelle = json.loads(read(kq))
1496
+ except json.JSONDecodeError:
1497
+ quelle = {}
1498
+ for regel in quelle.get("allow") or []:
1499
+ if isinstance(regel, dict) and regel.get("tool") == "mcp":
1500
+ err(f"{KERN}/{P101_KERNQUELLE}: gibt ein MCP-Werkzeug im allow-Korb frei "
1501
+ f"({json.dumps(regel, ensure_ascii=False)}). Eine MCP-Freigabe gehört dem "
1502
+ f"Projekt: Sie nennt Server, Zweck und Werkzeuge im Overlay Abschnitt 13.2, "
1503
+ f"und der Kern gibt nichts vorab frei (02-privacy.md 3.8, D-459)")
1504
+
1505
+ regelform = (man or {}).get("mcp_permission_rule")
1506
+ overlay = os.path.join(root, ".koolie", "project-overlay", "OVERLAY.md")
1507
+ rel = (man or {}).get("permissions_file")
1508
+ if not isinstance(regelform, dict) or not rel or not os.path.isfile(overlay):
1509
+ return
1510
+ pfad = os.path.join(root, *rel.split("/"))
1511
+ if not os.path.isfile(pfad):
1512
+ return
1513
+ try:
1514
+ cfg = json.loads(read(pfad))
1515
+ except json.JSONDecodeError:
1516
+ return # Pruefung 3 meldet das bereits
1517
+ rechte = cfg.get("permissions") if isinstance(cfg, dict) else None
1518
+ if not isinstance(rechte, dict):
1519
+ return
1520
+ allow = [r for r in rechte.get("allow") or [] if isinstance(r, str)]
1521
+ ask = [r for r in rechte.get("ask") or [] if isinstance(r, str)]
1522
+ form = str(regelform.get("werkzeug", ""))
1523
+ praefix = form.split("{server}", 1)[0]
1524
+ if not praefix:
1525
+ return
1526
+ freigaben = _p101_freigaben(read(overlay))
1527
+ lesen = {form.format(server=f["server"], werkzeug=w)
1528
+ for f in freigaben if "lesen" in f["zweck"] for w in f["lesen"]}
1529
+ schreiben = {form.format(server=f["server"], werkzeug=w)
1530
+ for f in freigaben for w in f["schreiben"]}
1531
+ # Der Serveranteil der Regelform ('mcp__atlassian', 'Mcp(atlassian') - eine Regel, die nur
1532
+ # ihn nennt, gilt fuer jedes Werkzeug des Servers.
1533
+ serverteil = form.split("{werkzeug}", 1)[0]
1534
+ ganze_server = {serverteil.format(server=f["server"]).rstrip("_:(") for f in freigaben}
1535
+ for regel in allow:
1536
+ if not regel.startswith(praefix) or regel in lesen:
1537
+ continue
1538
+ if regel in schreiben:
1539
+ grund = "ein Schreibwerkzeug – es verlangt bei jedem Aufruf die Bestätigung des Menschen"
1540
+ elif "*" in regel or regel.rstrip("_:()") in ganze_server:
1541
+ grund = "ein Muster für mehrere Werkzeuge – es gäbe auch die Schreibwerkzeuge frei"
1542
+ else:
1543
+ grund = "ein Werkzeug, das Overlay Abschnitt 13.2 keinem Server zum Lesen zuweist"
1544
+ err(f"{rel}: „{regel}“ steht in allow – {grund}. In allow stehen nur die "
1545
+ f"Lesewerkzeuge der Freigabe, einzeln; Schreibwerkzeuge gehören einzeln in ask "
1546
+ f"(02-privacy.md 3.8, D-459)")
1547
+ if not strikt:
1548
+ return
1549
+ for regel in sorted(lesen - set(allow)):
1550
+ err(f"{rel}: das freigegebene Lesewerkzeug „{regel}“ fehlt in allow. Overlay "
1551
+ f"Abschnitt 13.2 gibt es zum Lesen frei; ohne die Regel fragt der Client bei "
1552
+ f"jedem Aufruf nach, und ein nicht-interaktiver Lauf wird abgewiesen (D-459)")
1553
+ pauschal = regelform.get("pauschal")
1554
+ if lesen and pauschal in ask and regelform.get("rueckfrage_schlaegt_freigabe"):
1555
+ err(f"{rel}: „{pauschal}“ steht im ask-Korb, während Overlay Abschnitt 13.2 Werkzeuge "
1556
+ f"zum Lesen freigibt. Bei diesem Client schlägt die Rückfrage die Freigabe – "
1557
+ f"auch das einzeln freigegebene Lesewerkzeug wird abgewiesen (gemessen, D-459). "
1558
+ f"Die Pauschale durch die Einzelregeln ersetzen: Lesewerkzeuge in allow, "
1559
+ f"Schreibwerkzeuge in ask")