@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,1188 @@
1
+ """Der Schutz-Hook: Interpreter, Werkzeugabdeckung, fail-closed, Ablageort, neutrale
2
+ Skripte, Eingabeschema, Durchsetzungstiefe, Hookblock, Sperrform und Ziel statt Inhalt.
3
+
4
+ Pruefungen 15, 16, 17, 18, 21, 31, 32, 43, 86, 98 und 106. Teil des Validators validate-
5
+ framework.py, seit 1.19.1 nach Gegenstand in Module geteilt (K-174). Das Register aller
6
+ Pruefungen steht im Kopfkommentar des Einstiegs, die Grenze jeder einzelnen in ihrem
7
+ Kopfkommentar hier."""
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import re
13
+ import shlex
14
+ import subprocess
15
+ import sys
16
+
17
+ from .gemeinsam import (
18
+ _client_packs, _hook_interpreter, _hook_lauf, _walk_text_files, err,
19
+ formatgebunden, HOOK_SONDE, KERN, read, tabellenzellen, warn)
20
+
21
+
22
+ def _hook_kommandos(root: str, man: dict) -> list[tuple[str, str]]:
23
+ """(Fundstelle, Kommando) je konfiguriertem Hook - aus beiden Ablageformen."""
24
+ treffer: list[tuple[str, str]] = []
25
+ # Der Ort der Hook-Konfiguration steht in der Platzhalterabbildung des Packs; bei
26
+ # einem Client ohne eigene Hook-Datei zeigt sie auf die Berechtigungsdatei.
27
+ orte = {man.get("permissions_file"),
28
+ (man.get("runtime_placeholders") or {}).get("<HOOKS_FILE>")}
29
+ for rel in sorted(k for k in orte if k):
30
+ pfad = os.path.join(root, *rel.split("/"))
31
+ if not os.path.exists(pfad):
32
+ continue
33
+ try:
34
+ daten = json.loads(read(pfad))
35
+ except json.JSONDecodeError:
36
+ continue
37
+ # Dritte Ablageform (seit 1.13.0, D-414): eine LISTE von Hooks mit Ausloeser je
38
+ # Eintrag und dem Kommando unter action. Bis dahin las diese Funktion nur
39
+ # Objekte je Ereignis - und brach an einer Liste mit einer Ausnahme ab.
40
+ if isinstance(daten, dict) and isinstance(daten.get("hooks"), list):
41
+ for h in daten["hooks"]:
42
+ if not isinstance(h, dict):
43
+ continue
44
+ befehl = (h.get("action") or {}).get("command")
45
+ if befehl:
46
+ treffer.append((f"{rel}:{h.get('trigger', '?')}", befehl))
47
+ continue
48
+ # Eigene Hook-Datei: das Objekt steht oben. Hooks in der Berechtigungsdatei:
49
+ # unter dem Schluessel "hooks".
50
+ hooks = daten.get("hooks") if "hooks" in daten else daten
51
+ if not isinstance(hooks, dict):
52
+ continue
53
+ for ereignis, eintraege in hooks.items():
54
+ if ereignis.startswith("_") or not isinstance(eintraege, list):
55
+ continue
56
+ for eintrag in eintraege:
57
+ if not isinstance(eintrag, dict):
58
+ continue # eine Liste ohne Hook-Eintraege, etwa "tools" eines Profils
59
+ for h in (eintrag or {}).get("hooks", []):
60
+ befehl = h.get("command")
61
+ if befehl:
62
+ treffer.append((f"{rel}:{ereignis}", befehl))
63
+ return treffer
64
+
65
+
66
+ def check_hook_interpreter(root: str, man: dict) -> None:
67
+ """Pruefung 15 (AP2-CC-13): Ein Hook, der nicht startet, setzt nichts durch.
68
+
69
+ Die Hooks tragen die Zusage H2 der Faehigkeitsmatrix und die Overlay-Statusmeldung.
70
+ Wird der Aufruf mit einem Interpreternamen erzeugt, den es auf der Zielmaschine
71
+ nicht gibt - unter Windows ist "python3" haeufig der Microsoft-Store-Alias -, endet
72
+ der Hook mit Fehler statt mit einer Entscheidung, und die Zusage gilt dort nicht.
73
+
74
+ Geprueft wird die Wirkung: Der Interpreter muss eine Sonde ausgeben. Anwesenheit im
75
+ Pfad ist kein Nachweis; genau daran ist der Store-Alias vorbeigekommen.
76
+ """
77
+ kommandos = _hook_kommandos(root, man)
78
+ if not kommandos:
79
+ return
80
+ geprueft: dict[str, str] = {}
81
+ for fundstelle, befehl in kommandos:
82
+ name = shlex.split(befehl, posix=False)[0].strip('"') if befehl.strip() else ""
83
+ if not name:
84
+ continue
85
+ if name not in geprueft:
86
+ try:
87
+ lauf = subprocess.run(
88
+ [name, "-c", "import sys; sys.stdout.write('%s')" % HOOK_SONDE],
89
+ capture_output=True, text=True, timeout=15)
90
+ ok = lauf.returncode == 0 and HOOK_SONDE in (lauf.stdout or "")
91
+ geprueft[name] = "" if ok else f"Exit {lauf.returncode}"
92
+ except (OSError, subprocess.SubprocessError) as fehler:
93
+ geprueft[name] = type(fehler).__name__
94
+ if geprueft[name]:
95
+ err(f"{fundstelle}: Der Hook wird mit '{name}' aufgerufen, das auf dieser "
96
+ f"Maschine keinen Python-Interpreter startet ({geprueft[name]}). Der Hook "
97
+ f"laeuft damit nicht, und ein Schutz-Hook, der nicht laeuft, blockiert "
98
+ f"nichts (AP2-CC-13). 'install.py --update' erzeugt den Aufruf mit einem "
99
+ f"Interpreter, der hier funktioniert")
100
+
101
+
102
+ def check_hook_tool_coverage(root: str, man: dict) -> None:
103
+ """Pruefung 16 (AP2-CC-16): Der Schutz-Hook erkennt jeden abgebildeten Werkzeugnamen.
104
+
105
+ Das Manifest jedes Client Packs bildet die Verben der Kernquelle auf die
106
+ Werkzeugnamen seines Clients ab (hook_tools). Diese Abbildung galt bis 0.21.0 nur
107
+ fuer den Matcher der Hook-Konfiguration - der Hook selbst verglich gegen die
108
+ generischen Verbnamen. Folge: Bei einem Client, dessen Ausfuehrungswerkzeug nicht
109
+ 'exec' heisst, lief die Pfadpruefung fuer Shell-Befehle ins Leere.
110
+
111
+ Geprueft wird die Wirkung, nicht die Uebereinstimmung zweier Listen: Der Hook wird
112
+ je Werkzeugname mit einer Sonde aufgerufen, die er blockieren MUSS. Eine Zusage,
113
+ die man nur durch Vergleich zweier Listen belegt, ist genau die Art Pruefung, an
114
+ der dieser Befund vorbeigekommen ist.
115
+
116
+ Dazu je eine Gegenprobe, die NICHT blockiert werden darf: Ein lesender oder
117
+ ausfuehrender Zugriff auf einen Kernpfad. Ohne sie bliebe unbemerkt, wenn die
118
+ Secret-Sperre sich still in eine Sperre auf das gesamte Kernverzeichnis verwandelt.
119
+
120
+ Die Grenze bleibt: Geprueft werden die Verben, die das Manifest fuehrt. Ein Client
121
+ mit einem vierten Werkzeugverb faellt hier nicht auf - das kann nur eine Sitzung
122
+ erheben, die die Werkzeugnamen misst (AP2-DD-11).
123
+ """
124
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
125
+ if not os.path.isfile(skript):
126
+ return
127
+ interpreter = _hook_interpreter()
128
+ if interpreter is None:
129
+ return # Pruefung 15 meldet diesen Fall bereits
130
+ # Alle Packs, nicht nur das installierte: Der Hook liegt einmal im Kern und wird
131
+ # von allen geteilt. Ein Werkzeugname, den ein anderes Pack abbildet, faellt sonst
132
+ # erst dort auf, wo es keine Installation zum Pruefen gibt - genau die Lage, in der
133
+ # AP2-CC-16 acht Releases lang unbemerkt blieb.
134
+ abbildungen = []
135
+ packs = os.path.join(root, KERN, "clients")
136
+ for name in sorted(os.listdir(packs)) if os.path.isdir(packs) else []:
137
+ mf = os.path.join(packs, name, "manifest.json")
138
+ if not os.path.isfile(mf):
139
+ continue
140
+ try:
141
+ daten = json.loads(read(mf)) or {}
142
+ except json.JSONDecodeError:
143
+ continue
144
+ abbildungen.append((daten.get("client", name), daten.get("hook_tools") or {},
145
+ daten.get("hook_mcp_prefixes") or []))
146
+ # Je Verb eine Sonde auf einen Secret-Pfad, die blockiert werden MUSS. Das Leseverb
147
+ # steht seit D-33 dabei: D-30 hatte Secret-Pfade auch gegen lesende Werkzeuge
148
+ # durchgesetzt, der Code loeste es nicht ein, und diese Pruefung konnte es nicht
149
+ # finden - sie sondierte genau die beiden Verben, die das Manifest fuehrte, und mass
150
+ # die Abbildung damit an sich selbst (AP2-DD-11).
151
+ sonden = {"exec": {"command": "cat .env"}, "write": {"file_path": ".env", "content": "x"},
152
+ "read": {"file_path": ".env"}}
153
+ # Gegenprobe je Verb: Ein Zugriff auf einen Kernpfad, der NICHT blockiert werden darf.
154
+ # Lesende und ausfuehrende Werkzeuge duerfen den Kern lesen - P4 setzt das voraus, und
155
+ # ohne diese Probe verwandelte die Erweiterung die Secret-Sperre still in eine Sperre
156
+ # auf das gesamte Kernverzeichnis (D-30, zweites Schutzziel).
157
+ # Der Pfad muss ein Strukturpfad sein, sonst prueft die Gegenprobe nichts: Ein
158
+ # beliebiger Kernpfad wie VERSION steht in keiner der beiden Musterlisten und waere
159
+ # auch dann nicht blockiert, wenn die Trennung aufgehoben ist. Genau daran ist die
160
+ # erste Fassung dieser Gegenprobe vorbeigelaufen - sie bestand, ohne etwas zu messen.
161
+ gegenproben = {"exec": {"command": f"git diff {KERN}/framework/core/00-purpose.md"},
162
+ "read": {"file_path": f"{KERN}/framework/core/00-purpose.md"}}
163
+ for pack, abbildung, praefixe in abbildungen:
164
+ for verb, eingabe in sonden.items():
165
+ for werkzeug in abbildung.get(verb) or []:
166
+ payload = json.dumps({"tool_name": werkzeug, "tool_input": eingabe})
167
+ try:
168
+ lauf = subprocess.run([interpreter, skript], input=payload,
169
+ capture_output=True, text=True, timeout=20)
170
+ except (OSError, subprocess.SubprocessError) as fehler:
171
+ err(f"Schutz-Hook nicht ausfuehrbar ({type(fehler).__name__})")
172
+ return
173
+ if lauf.returncode != 2:
174
+ err(f"{pack}/manifest.json: Der Schutz-Hook erkennt den "
175
+ f"Werkzeugnamen '{werkzeug}' nicht, den dieses Pack fuer das Verb "
176
+ f"'{verb}' abbildet - eine Sonde auf einen Secret-Pfad wurde nicht "
177
+ f"blockiert (Exit {lauf.returncode} statt 2). Die Abbildung erreicht "
178
+ f"den Matcher, aber nicht die Pruefung im Hook (AP2-CC-16)")
179
+ for verb, eingabe in gegenproben.items():
180
+ for werkzeug in abbildung.get(verb) or []:
181
+ payload = json.dumps({"tool_name": werkzeug, "tool_input": eingabe})
182
+ try:
183
+ lauf = subprocess.run([interpreter, skript], input=payload,
184
+ capture_output=True, text=True, timeout=20)
185
+ except (OSError, subprocess.SubprocessError):
186
+ continue # der Sondenlauf oben meldet einen nicht ausfuehrbaren Hook
187
+ if lauf.returncode != 0:
188
+ err(f"{pack}/manifest.json: Der Schutz-Hook blockiert einen nicht "
189
+ f"schreibenden Zugriff auf einen Strukturpfad des Kerns mit dem "
190
+ f"Werkzeug '{werkzeug}' (Verb '{verb}', Exit {lauf.returncode} "
191
+ f"statt 0). "
192
+ f"Strukturpfade sind integritaetsgeschuetzt und gelten nur fuer "
193
+ f"schreibende Werkzeuge; P4 setzt voraus, dass der Kern lesbar "
194
+ f"bleibt (D-30)")
195
+ # SEIT 1.20.0 DAS VERB mcp (K-184, D-486). Ein MCP-Werkzeug hat keinen festen
196
+ # Namen; der Hook erkennt es am Praefix aus hook_mcp_prefixes. Zwei Sonden, die
197
+ # sperren MUESSEN - der Inhalt mit einem Secret-Muster und ein Pfadfeld auf .env
198
+ # (ein Dateisystem-Server) -, und eine Gegenprobe, die durchgehen MUSS: Eine Notiz,
199
+ # die .env und die Laufzeitschicht nur NENNT, liest und schreibt nichts. Ohne sie
200
+ # verwandelte sich die Pruefung still in die strengste Lesart des unbekannten
201
+ # Werkzeugs, und jede Doku-Seite ueber das Framework waere gesperrt.
202
+ for praefix in praefixe:
203
+ name = praefix + "koolie_sonde__werkzeug"
204
+ for titel, eingabe, soll in (
205
+ ("Inhalt mit Secret-Muster", {"text": "password=KOOLIE-SONDE-16-wert"}, 2),
206
+ ("Pfadfeld auf .env", {"path": ".env"}, 2),
207
+ ("Notiz, die Pfade nur nennt",
208
+ {"text": "Siehe .env.example und .claude/settings.json."}, 0)):
209
+ payload = json.dumps({"tool_name": name, "tool_input": eingabe})
210
+ try:
211
+ lauf = subprocess.run([interpreter, skript], input=payload,
212
+ capture_output=True, text=True, timeout=20)
213
+ except (OSError, subprocess.SubprocessError):
214
+ continue
215
+ if lauf.returncode != soll:
216
+ err(f"{pack}/manifest.json: Der Schutz-Hook entscheidet ein MCP-Werkzeug "
217
+ f"mit dem Praefix '{praefix}' falsch ({titel}: Exit "
218
+ f"{lauf.returncode} statt {soll}). Geprueft werden Inhalt gegen die "
219
+ f"Secret-Muster und Pfadfelder gegen die Secret-Pfade, nicht die "
220
+ f"Strukturpfade (K-184, D-486)")
221
+
222
+
223
+ # Pruefung 17: Der Schutz-Hook laesst eine unlesbare Eingabe nur dort durch, wo das
224
+ # Client Pack das Eingabeschema als unbestaetigt fuehrt (D-31).
225
+ HOOK_UNLESBAR = "kein json {"
226
+
227
+
228
+ def _hook_selbsttest(interpreter: str, skript: str, argumente: list[str]) -> int:
229
+ """Ruft den Schutz-Hook mit einer nicht parsebaren Eingabe auf und liefert den Exit-Code."""
230
+ try:
231
+ lauf = subprocess.run([interpreter, skript] + argumente, input=HOOK_UNLESBAR,
232
+ capture_output=True, text=True, timeout=20,
233
+ env={k: v for k, v in os.environ.items()
234
+ if k != "FW_HOOK_FAIL_CLOSED"})
235
+ except (OSError, subprocess.SubprocessError):
236
+ return -1
237
+ return lauf.returncode
238
+
239
+
240
+ def check_hook_fail_closed(root: str, man: dict) -> None:
241
+ """Pruefung 17 (D-31): Fail-closed gilt dort, wo das Pack es zusagt - und wirkt.
242
+
243
+ Der Schutz-Hook laesst eine Eingabe, die er nicht als JSON lesen kann, standardmaessig
244
+ durch. Das war richtig, solange kein Eingabeschema gegen eine Installation bestaetigt
245
+ war; fuer ein Pack, das es fuehrt, ist es eine Luecke. Der Schalter steht im
246
+ Aufrufkommando (--fail-closed), nicht in der Umgebung: Ob ein Client env an den
247
+ Hook-Prozess weiterreicht, ist fuer keines der Packs belegt.
248
+
249
+ Geprueft wird in zwei Ebenen, und beide sind noetig:
250
+
251
+ 1. Am Skript: Das Argument muss ueberhaupt wirken - mit --fail-closed Exit 2, ohne
252
+ Exit 0. Diese Ebene laeuft auch dort, wo es keine Installation gibt. Ohne sie
253
+ faellt ein Schalter, der ins Leere greift, erst in einer Installation auf - genau
254
+ die Lage, in der AP2-CC-16 acht Releases lang unbemerkt blieb.
255
+ 2. An der erzeugten Konfiguration: Das Kommando wird so aufgerufen, wie es dort steht,
256
+ und sein Verhalten gegen die Zusage des Packs gehalten. Diese Ebene prueft die
257
+ ganze Kette - Manifest, Abbildung, Konfiguration, Verhalten -, nicht die
258
+ Uebereinstimmung zweier Felder.
259
+
260
+ Die Umgebungsvariable wird fuer den Aufruf entfernt: Sonst bestuende der Test auch
261
+ dann, wenn das Argument nichts bewirkt.
262
+ """
263
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
264
+ if not os.path.isfile(skript):
265
+ return
266
+ interpreter = _hook_interpreter()
267
+ if interpreter is None:
268
+ return # Pruefung 15 meldet diesen Fall bereits
269
+
270
+ # Ebene 1: Wirkt das Argument?
271
+ mit = _hook_selbsttest(interpreter, skript, ["--fail-closed"])
272
+ ohne = _hook_selbsttest(interpreter, skript, [])
273
+ if mit != 2:
274
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Der Aufruf mit --fail-closed "
275
+ f"blockiert eine nicht lesbare Eingabe nicht (Exit {mit} statt 2). Ein Pack, "
276
+ f"das fail-closed zusagt, erzeugt damit ein Kommando ohne Wirkung (D-31)")
277
+ if ohne != 0:
278
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Der Aufruf ohne --fail-closed "
279
+ f"laesst eine nicht lesbare Eingabe nicht durch (Exit {ohne} statt 0). Bei "
280
+ f"einem Pack mit unbestaetigtem Eingabeschema blockierte damit jede Sitzung, "
281
+ f"deren Schema abweicht (D-31)")
282
+
283
+ # Ebene 2: Traegt die erzeugte Konfiguration die Zusage ihres Packs?
284
+ zusage = man.get("hook_fail_closed") is True
285
+ for fundstelle, befehl in _hook_kommandos(root, man):
286
+ teile = [t.strip('"') for t in shlex.split(befehl, posix=False)]
287
+ if not any(os.path.basename(t) == "hook-check-secrets.py" for t in teile[1:]):
288
+ continue
289
+ argumente = [t for t in teile[2:] if t.startswith("-")]
290
+ ergebnis = _hook_selbsttest(interpreter, skript, argumente)
291
+ erwartet = 2 if zusage else 0
292
+ if ergebnis != erwartet:
293
+ wie = ("blockiert eine nicht lesbare Eingabe nicht" if zusage
294
+ else "blockiert eine nicht lesbare Eingabe, statt sie durchzulassen")
295
+ # Liegt die Hook-Konfiguration in der Berechtigungsdatei, ist sie Saat und
296
+ # gehoert nach der Erstinstallation dem Projekt: '--update' fasst sie nie an.
297
+ # Ein Rat, der dorthin verweist, saegte eine Behebung zu, die nicht eintritt.
298
+ in_saat = fundstelle.split(":")[0] == man.get("permissions_file")
299
+ weg = ("Bei diesem Client steht die Hook-Konfiguration in der "
300
+ "Berechtigungsdatei und damit in der Saat; 'install.py --update' "
301
+ "fasst sie nicht an. Das Kommando ist von Hand nachzuziehen"
302
+ if in_saat else "'install.py --update' erzeugt das Kommando neu")
303
+ err(f"{fundstelle}: Das Pack '{man.get('client', '?')}' fuehrt "
304
+ f"hook_fail_closed={str(zusage).lower()}, aber das erzeugte Kommando "
305
+ f"{wie} (Exit {ergebnis} statt {erwartet}). Die Abbildung erreicht die "
306
+ f"Konfiguration nicht, oder das Kommando traegt das falsche Argument "
307
+ f"(D-31). {weg}")
308
+
309
+
310
+ # Pruefung 18: Die Hook-Konfiguration steht dort, wo der Client sie liest - und nirgends
311
+ # sonst. Ein zweiter, verwaister Ort ist gefaehrlicher als gar keiner (D-32).
312
+ VERWAISTE_HOOK_DATEIEN = ("hooks.v1.json",)
313
+
314
+
315
+ def _check_vorlagen_ohne_hookdatei(root: str) -> None:
316
+ """Teil von Pruefung 18 (CR-2026-036 E3): Keine Vorlage liefert eine Hook-Datei aus.
317
+
318
+ Ein ausgeliefertes Dokument beschrieb bis 0.25.0 genau den Zustand, den dieselbe
319
+ Pruefung als Altlast meldete: Die Laufzeit-README fuehrte die eigene Hook-Datei unter
320
+ den gelieferten Dateien, obwohl die Installation sie nicht mehr anlegt (D-32).
321
+
322
+ Gemeldet wird deshalb nur die **Behauptung einer Lieferung** - der Dateiname als
323
+ erste Zelle einer Tabellenzeile, dort steht in diesen READMEs das gelieferte
324
+ Artefakt. Eine Nennung im Fliesstext, die die Abwesenheit **erklaert**, bleibt
325
+ unbeanstandet; sie ist der Migrationshinweis und soll dort stehen. Der Fehlalarm,
326
+ den eine reine Namenssuche erzeugt haette, ist damit vermieden.
327
+ """
328
+ for name in sorted(os.listdir(os.path.join(root, KERN, "clients"))
329
+ if os.path.isdir(os.path.join(root, KERN, "clients")) else []):
330
+ vorlage = os.path.join(root, KERN, "clients", name, "root-template")
331
+ if not os.path.isdir(vorlage):
332
+ continue
333
+ for pfad in _walk_text_files(vorlage):
334
+ if not pfad.endswith(".md"):
335
+ continue
336
+ rel = os.path.relpath(pfad, root).replace(os.sep, "/")
337
+ for i, zeile in enumerate(read(pfad).splitlines(), 1):
338
+ zellen = tabellenzellen(zeile) \
339
+ if zeile.strip().startswith("|") else []
340
+ erste = zellen[0].strip("` *") if zellen else ""
341
+ if erste in VERWAISTE_HOOK_DATEIEN or \
342
+ any(erste.endswith("/" + v) for v in VERWAISTE_HOOK_DATEIEN):
343
+ err(f"{rel}:{i}: Die Vorlage fuehrt '{erste}' als geliefertes "
344
+ f"Artefakt. Seit D-32 legt die Installation keine eigene "
345
+ f"Hook-Datei an; ein ausgeliefertes Dokument beschriebe damit "
346
+ f"genau den Zustand, den Pruefung 18 als Altlast meldet")
347
+
348
+
349
+ def check_hook_ablageort(root: str, man: dict) -> None:
350
+ """Pruefung 18 (D-32): Keine verwaiste Hook-Datei neben der wirksamen.
351
+
352
+ Ob ein Client eine Datei **liest**, kann kein Validator feststellen - das kann nur
353
+ eine Sitzung, und genau dort ist AP2-DD-10 aufgefallen: Aus der eigenen Hook-Datei
354
+ des Packs fuehrte der KI-Client keinen Hook aus, dieselbe Konfiguration in der
355
+ Berechtigungsdatei lief sofort. Seit D-32 liegen die Hooks dort.
356
+
357
+ Was diese Pruefung leisten kann, ist das Aufraeumen danach: 'install.py --update'
358
+ schreibt die neue Datei, entfernt die alte aber nicht. Zurueck bleibt eine
359
+ Hook-Konfiguration, die aussieht, als gaelte sie - und die bei einer kuenftigen
360
+ Clientversion, die den Ort doch liest, eine zweite, veraltete Regelmenge waere.
361
+
362
+ Sie belegt damit nichts ueber die Wirkung; sie haelt einen Zustand fest, der nach
363
+ einer Migration entsteht. Das ist ausdruecklich weniger als ein Wirkungsnachweis
364
+ nach D-23 und hier auch nicht mehr moeglich.
365
+ """
366
+ _check_vorlagen_ohne_hookdatei(root)
367
+ rechte = man.get("permissions_file")
368
+ hooks_ort = (man.get("runtime_placeholders") or {}).get("<HOOKS_FILE>")
369
+ if not rechte or hooks_ort != rechte:
370
+ return # Client mit eigener Hook-Datei - nichts aufzuraeumen
371
+ runtime = (man.get("runtime_placeholders") or {}).get("<RUNTIME_DIR>")
372
+ if not runtime:
373
+ return
374
+ for name in VERWAISTE_HOOK_DATEIEN:
375
+ pfad = os.path.join(root, *runtime.split("/"), name)
376
+ if os.path.exists(pfad):
377
+ warn(f"{runtime}/{name} liegt neben der wirksamen Hook-Konfiguration in "
378
+ f"{rechte}. Dieses Pack fuehrt seine Hooks seit 0.25.0 in der "
379
+ f"Berechtigungsdatei, weil der Client die eigene Datei nicht liest "
380
+ f"(AP2-DD-10, D-32); 'install.py --update' entfernt sie nicht. Die "
381
+ f"verwaiste Datei ist von Hand zu loeschen - sonst steht dort eine "
382
+ f"Regelmenge, die aussieht, als gaelte sie")
383
+
384
+
385
+ PFAD_HERLEITUNG_RE = re.compile(r"os\.path\.join|os\.environ|os\.getenv")
386
+
387
+
388
+ def check_hook_skripte_neutral(root: str) -> None:
389
+ """Pruefung 21 (D-30, CR-2026-037): Hook-Skripte raten die Laufzeitschicht nicht.
390
+
391
+ Ein Skript, das in einer Sitzung laeuft, bekommt Projektverzeichnis und Regelablage
392
+ aus der Semantikabbildung seines Client Packs - es leitet sie nicht aus einem
393
+ clientgebundenen Namen ab. Gemeldet wird deshalb zweierlei: die Umgebungsvariable
394
+ eines Packs (hook_project_dir_var) an jeder Stelle, und ein Laufzeitpfad eines Packs
395
+ dort, wo ein Pfad gebaut oder aus der Umgebung gelesen wird.
396
+
397
+ GRENZE - und zugleich die Gegenprobe: Die **Schutzmuster** von hook-check-secrets.py
398
+ nennen die Laufzeitpfade beider Packs ausdruecklich. Das ist Absicht (ein zusaetzlich
399
+ geschuetzter Pfad ist eine Verschaerfung) und bleibt unbeanstandet, weil dort kein
400
+ Pfad hergeleitet wird. Ausgenommen ist ferner der Validator selbst: Er fuehrt eine
401
+ dokumentierte Rueckfallabbildung, wenn kein Pack gefunden wird (CR-2026-037 E3).
402
+
403
+ Die Pruefung sieht Skripte, nicht Wirkung. Ein Hook mit sauber uebergebenen Pfaden,
404
+ der trotzdem den falschen Status meldet, faellt ihr nicht auf.
405
+ """
406
+ variablen: dict[str, str] = {}
407
+ pfade: dict[str, str] = {}
408
+ for kennung, _pdir, man in _client_packs(root):
409
+ if not man:
410
+ continue
411
+ v = man.get("hook_project_dir_var")
412
+ if v:
413
+ variablen[v] = kennung
414
+ for schluessel in ("<RUNTIME_DIR>", "<RULES_DIR>", "<SKILLS_DIR>", "<AGENTS_DIR>",
415
+ "<PERMISSIONS_FILE>", "<HOOKS_FILE>", "<MCP_FILE>"):
416
+ wert = (man.get("runtime_placeholders") or {}).get(schluessel)
417
+ if wert:
418
+ pfade[wert] = kennung
419
+ sdir = os.path.join(root, KERN, "tests", "scripts")
420
+ if not os.path.isdir(sdir):
421
+ return
422
+ for name in sorted(os.listdir(sdir)):
423
+ if not name.startswith("hook-") or not name.endswith(".py"):
424
+ continue
425
+ rel = f"{KERN}/tests/scripts/{name}"
426
+ for i, zeile in enumerate(read(os.path.join(sdir, name)).splitlines(), 1):
427
+ ohne_kommentar = zeile.split("#", 1)[0]
428
+ for variable, kennung in variablen.items():
429
+ if variable in ohne_kommentar:
430
+ err(f"{rel}:{i}: '{variable}' ist die Umgebungsvariable des Packs "
431
+ f"'{kennung}'. Ein Hook-Skript des Kerns nennt sie nicht - der "
432
+ f"Wert kommt als Argument aus der Semantikabbildung (D-30)")
433
+ if not PFAD_HERLEITUNG_RE.search(ohne_kommentar):
434
+ continue
435
+ for wert, kennung in sorted(pfade.items()):
436
+ if re.search(r"['\"]%s['\"/]" % re.escape(wert), ohne_kommentar) or \
437
+ re.search(r"['\"]%s['\"]" % re.escape(wert.split("/")[0]),
438
+ ohne_kommentar):
439
+ err(f"{rel}:{i}: Hier wird ein Pfad aus '{wert}' gebaut - der "
440
+ f"Laufzeitschicht des Packs '{kennung}'. Ein Skript des Kerns "
441
+ f"kennt sie nicht; sie kommt als Argument (D-30)")
442
+ break
443
+
444
+
445
+
446
+ # Pruefung 32: Eingabeschema und Pfadidentitaet des Schutz-Hooks (B06, CR-2026-056).
447
+ #
448
+ # Pruefung 16 belegt, dass der Hook jeden abgebildeten WERKZEUGNAMEN erkennt. Sie ruft ihn
449
+ # dafuer mit {"tool_name": ..., "tool_input": ...} auf - OHNE Umschlag. Genau daran ist der
450
+ # schwerste Teil von B06 vorbeigekommen: Der Client claude-code fuehrt in jedem Ereignis
451
+ # transcript_path, der unter ~/.claude/projects/ liegt und damit das Strukturmuster der
452
+ # Laufzeitschicht trifft. Bis 0.33.0 blockierte der Hook deshalb JEDEN Schreibzugriff
453
+ # dieses Packs, unabhaengig vom Ziel - am Client nachgemessen, mit Kontrolllauf
454
+ # (tests/protocols/2026-09-13-B06-gegenpruefung.md).
455
+ #
456
+ # DAS IST DIE LEHRE, UND SIE GEHOERT HIERHER: Eine Pruefung, die ihren Gegenstand mit
457
+ # selbst gebauter Eingabe aufruft, misst die selbst gebaute Eingabe. Diese Pruefung ruft
458
+ # den Hook deshalb mit dem VOLLSTAENDIGEN Umschlag JEDES aufgezeichneten Schemas auf. Bis
459
+ # 0.35.0 stand hier "beider" - es sind seit dem 2026-09-13 drei, weil ein Aufruf aus einem
460
+ # Unteragenten zwei zusaetzliche Felder fuehrt (CR-2026-058, Befund 5).
461
+ #
462
+ # Fuenf Gegenstaende, je Pack, jeder an der Wirkung gemessen und nicht am Vergleich zweier
463
+ # Listen:
464
+ # 1. Ereignisschema - was kein Ereignis ist, blockiert mit --fail-closed (D-61).
465
+ # 2. Umschlag - ein Nebenfeld darf die Entscheidung NICHT aendern (D-62).
466
+ # 2b. Unteragenten-Umschlag - agent_id und agent_type aendern sie ebenso wenig.
467
+ # 3. Unbekanntes Werkzeug - wird nach der strengsten Liste gemessen (CR-2026-056 E2).
468
+ # 4. Pfadidentitaet - Varianten derselben Datei entscheiden gleich (D-63).
469
+ #
470
+ # WAS DIESE PRUEFUNG NICHT LEISTET: Sie misst am Hook, nicht am Client. Dass ein Client
471
+ # den Matcher einhaelt und genau dieses Schema sendet, belegt nur eine Sitzung (AP2). Und
472
+ # sie misst die Pfadvarianten dieser Plattform: Der 8.3-Kurzname und die Junction sind
473
+ # unter NTFS gepruefte Faelle, symbolische Verknuepfungen unter POSIX sind es nicht - sie
474
+ # brauchen eine Datei im Dateisystem und gehoeren deshalb in die Sonde, nicht hierher.
475
+ HOOK_UMSCHLAEGE = {
476
+ # Aufgezeichnet, nicht angenommen: die Felder stammen aus den AP2-Mitschriften beider
477
+ # Packs (leitwerk-erhebungen-2026-09-12 bzw. lw-tech). Der Unterschied ist der Punkt:
478
+ # Nur eines der beiden Schemata fuehrt transcript_path und cwd.
479
+ "claude-code": {"session_id": "s", "transcript_path": "/home/u/.claude/projects/p/s.jsonl", # SYNTHETISCH: Sondeneingabe, kein Arbeitsplatz (D-231)
480
+ "cwd": "/projekt", "permission_mode": "default",
481
+ "hook_event_name": "PreToolUse", "tool_use_id": "t"},
482
+ "devin-desktop": {"session_id": "s", "prompt_id": "p", "hook_event_name": "PreToolUse",
483
+ "tool_use_id": "t"},
484
+ # Aufgezeichnet am 2026-09-26 in einer interaktiven Sitzung (CR-2026-150): fuenf
485
+ # Felder, cwd ist die Projektwurzel.
486
+ "kiro": {"session_id": "s", "hook_event_name": "PreToolUse", "cwd": "/projekt"},
487
+ }
488
+
489
+ # Der DRITTE aufgezeichnete Umschlag (CR-2026-058 E3, Erhebung vom 2026-09-13): Ein
490
+ # Werkzeugaufruf aus einem UNTERAGENTEN traegt zusaetzlich agent_id und agent_type. Bis
491
+ # 0.35.0 sagte der Kopfkommentar dieser Pruefung, sie messe "mit dem vollstaendigen
492
+ # Umschlag beider aufgezeichneter Schemata" - es sind drei.
493
+ #
494
+ # WARUM DAS HIER STEHT, und warum nicht mehr behauptet wird als gemessen ist: Weder
495
+ # agent_id noch agent_type traegt einen Pfad; heute faengt dieser Fall nichts. Der Grund
496
+ # ist ein anderer - eine Pruefung, die ihre Grundlage benennt, darf sie nicht ueberholt
497
+ # tragen. Und der Grundsatz aus D-62 gilt hier woertlich: Ein zusaetzliches Umschlagfeld
498
+ # ist kein Pruefmaterial. Genau daran ist B06 gescheitert, mit transcript_path.
499
+ HOOK_UMSCHLAG_UNTERAGENT = {"agent_id": "a1cae648c0189377c", "agent_type": "fw-reviewer"}
500
+ KEIN_EREIGNIS = ("", " ", "[]", "null", '"x"', "42", '{"tool_input": {}}',
501
+ '{"tool_name": "", "tool_input": {}}', '{"tool_name": "Write"}',
502
+ '{"tool_name": "Write", "tool_input": "x"}')
503
+
504
+
505
+ def _msys_form(root: str) -> str:
506
+ """Die Projektwurzel in POSIX-Schreibweise (/c/...) - nur unter Windows, sonst ''."""
507
+ if os.name != "nt":
508
+ return ""
509
+ echt = os.path.realpath(root)
510
+ if len(echt) < 2 or echt[1] != ":":
511
+ return "" # Netzpfad: keine Laufwerksform
512
+ return "/" + echt[0].lower() + echt[2:].replace("\\", "/").rstrip("/")
513
+
514
+
515
+ def check_hook_eingabeschema(root: str, man: dict) -> None:
516
+ """Pruefung 32 (D-61 bis D-63): Ereignisschema, Umschlag und Pfadidentitaet."""
517
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
518
+ if not os.path.isfile(skript):
519
+ return
520
+ interpreter = _hook_interpreter()
521
+ if interpreter is None:
522
+ return # Pruefung 15 meldet diesen Fall bereits
523
+
524
+ # Die Sonde auf den verlorenen Anker: Diese Pruefung misst die drei Stufen des Hooks.
525
+ # Verschwinden sie, prueft sie einen Aufbau, den es nicht mehr gibt - und bestuende
526
+ # dabei leise. Sie meldet ihr Fehlen deshalb selbst (D-23).
527
+ quelle = read(skript)
528
+ for anker in ("def ereignis_lesen(", "class Unpruefbar", "def aufloesen("):
529
+ if anker not in quelle:
530
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: '{anker}' fehlt. Pruefung 32 "
531
+ f"misst Eingabeschema und Pfadidentitaet des Hooks; ohne diese Stufen "
532
+ f"prueft sie einen Aufbau, den es nicht mehr gibt, und bestuende leise "
533
+ f"(D-23, CR-2026-056)")
534
+ return
535
+
536
+ # 1. Ereignisschema. Mit --fail-closed MUSS jede Nicht-Ereignisform blockieren.
537
+ for eingabe in KEIN_EREIGNIS:
538
+ if _hook_lauf(interpreter, skript, eingabe) != 2:
539
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Die Eingabe "
540
+ f"{eingabe!r} ist kein Werkzeugereignis, wird mit --fail-closed aber "
541
+ f"nicht blockiert. 'Nichts gefunden' und 'nicht gesucht' duerfen nicht "
542
+ f"denselben Exit-Code haben (D-61, Befund B06)")
543
+ # Gegenprobe: Ein vollstaendiges, harmloses Ereignis mit zusaetzlichen unbekannten
544
+ # Feldern MUSS durchlaufen. Ohne sie bestuende ein Hook, der einfach alles blockiert -
545
+ # und eine additive Erweiterung des Clients wuerde ihn ausfallen lassen.
546
+ harmlos = {"tool_name": "read", "tool_input": {"file_path": "src/app.py"},
547
+ "ein_neues_feld_das_kein_pack_kennt": {"tief": ["x"]}}
548
+ if _hook_lauf(interpreter, skript, json.dumps(harmlos)) != 0:
549
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Ein harmloses Ereignis mit "
550
+ f"zusaetzlichen unbekannten Feldern wird blockiert. Eine additive Erweiterung "
551
+ f"des Clients darf den Hook nicht ausfallen lassen (CR-2026-056 E1)")
552
+
553
+ cdir = os.path.join(root, KERN, "clients")
554
+ for pack in sorted(os.listdir(cdir)) if os.path.isdir(cdir) else []:
555
+ if pack.startswith("_"):
556
+ continue
557
+ mf = os.path.join(cdir, pack, "manifest.json")
558
+ if not os.path.isfile(mf):
559
+ continue
560
+ try:
561
+ daten = json.loads(read(mf)) or {}
562
+ except json.JSONDecodeError:
563
+ continue
564
+ schreibwerkzeuge = [w for w in ((daten.get("hook_tools") or {}).get("write") or [])
565
+ if isinstance(w, str) and w.strip()]
566
+ if not schreibwerkzeuge:
567
+ continue
568
+ werkzeug = schreibwerkzeuge[0]
569
+ umschlag = HOOK_UMSCHLAEGE.get(daten.get("client") or pack, {})
570
+
571
+ def ereignis(name: str, eingabe: dict) -> str:
572
+ voll = dict(umschlag)
573
+ voll.update({"tool_name": name, "tool_input": eingabe})
574
+ return json.dumps(voll)
575
+
576
+ # 2. Der Umschlag darf die Entscheidung nicht aendern: zweimal dasselbe
577
+ # tool_input, einmal nackt und einmal im vollen Umschlag des Clients.
578
+ harmlose_eingabe = {"file_path": "src/app.py", "content": "x"}
579
+ ohne = _hook_lauf(interpreter, skript, json.dumps(
580
+ {"tool_name": werkzeug, "tool_input": harmlose_eingabe}))
581
+ mit = _hook_lauf(interpreter, skript, ereignis(werkzeug, harmlose_eingabe))
582
+ if ohne != mit:
583
+ err(f"{pack}/manifest.json: Der Schutz-Hook entscheidet dieselbe Operation "
584
+ f"verschieden, je nachdem ob der Umschlag des Clients mitgeschickt wird "
585
+ f"(ohne: Exit {ohne}, mit: Exit {mit}). Geprueft wird die Operation, "
586
+ f"nicht der Umschlag - ein Nebenfeld wie transcript_path oder cwd ist "
587
+ f"kein Ziel (D-62, Befund B06)")
588
+ elif mit != 0:
589
+ err(f"{pack}/manifest.json: Der Schutz-Hook blockiert eine harmlose "
590
+ f"Schreiboperation im vollen Umschlag dieses Clients (Exit {mit}). Bis "
591
+ f"0.33.0 war das der Normalfall des Packs claude-code - jeder "
592
+ f"Schreibzugriff scheiterte an einem Nebenfeld (D-62, Befund B06)")
593
+
594
+ # 2b. Der Unteragenten-Umschlag ist ein Umschlag wie jeder andere: Dieselbe
595
+ # Operation MUSS gleich entscheiden, ob sie aus dem Hauptagenten oder aus einem
596
+ # Unteragenten kommt - bei der harmlosen wie bei der geschuetzten Operation. Die
597
+ # zweite Haelfte ist die wichtigere: Eine Pruefung, die nur den harmlosen Fall
598
+ # misst, bestuende auch dann, wenn der Hook mit diesen Feldern gar nichts mehr
599
+ # entscheidet (CR-2026-058 E3).
600
+ for name, eingabe, erwartet in (
601
+ ("harmlos", {"file_path": "src/app.py", "content": "x"}, 0),
602
+ ("geschuetzt", {"file_path": f"{KERN}/VERSION", "content": "9"}, 2)):
603
+ voll = dict(umschlag)
604
+ voll.update(HOOK_UMSCHLAG_UNTERAGENT)
605
+ voll.update({"tool_name": werkzeug, "tool_input": eingabe})
606
+ aus_unteragent = _hook_lauf(interpreter, skript, json.dumps(voll))
607
+ if aus_unteragent != erwartet:
608
+ err(f"{pack}/manifest.json: Der Schutz-Hook entscheidet eine {name}e "
609
+ f"Operation aus einem Unteragenten anders als erwartet "
610
+ f"(Exit {aus_unteragent}, erwartet {erwartet}). agent_id und "
611
+ f"agent_type sind Umschlagfelder wie transcript_path - kein "
612
+ f"Pruefmaterial und kein Grund, die Pruefung zu ueberspringen "
613
+ f"(D-62, CR-2026-058 E3)")
614
+
615
+ # 3. Ein unbekanntes Werkzeug gilt als die strengste Operation, nicht als keine.
616
+ if _hook_lauf(interpreter, skript, ereignis(
617
+ "EinWerkzeugDasKeinPackKennt",
618
+ {"file_path": f"{KERN}/VERSION", "content": "9"})) != 2:
619
+ err(f"{pack}/manifest.json: Der Schutz-Hook laesst ein unbekanntes Werkzeug "
620
+ f"in das Kernverzeichnis schreiben. Eine unbekannte Operation gilt als "
621
+ f"die strengste, nicht als gar keine (CR-2026-056 E2, Befund B06)")
622
+
623
+ # 4. Pfadidentitaet: Varianten, die dieselbe Datei bezeichnen, entscheiden gleich.
624
+ for variante in (KERN.upper() + "/VERSION", "./" + KERN + "/VERSION",
625
+ KERN + "/tests/../VERSION", KERN + "\\VERSION"):
626
+ if _hook_lauf(interpreter, skript, ereignis(
627
+ werkzeug, {"file_path": variante, "content": "9"})) != 2:
628
+ err(f"{pack}/manifest.json: Der Schutz-Hook entscheidet die Pfadvariante "
629
+ f"'{variante}' anders als die Standardschreibweise, obwohl beide "
630
+ f"dieselbe Datei bezeichnen. Geprueft wird die Pfadidentitaet, nicht "
631
+ f"die Zeichenkette (D-63, Befund B06)")
632
+ # Zwei Faelle, die je nur EIN Mechanismus faengt. Die Schreibvariante oben
633
+ # faengt beides - re.I am Rohtext und die Aufloesung, die die Schreibweise
634
+ # kanonisiert. Faellt einer der beiden aus, bestuende die Pruefung trotzdem, und
635
+ # keine Sonde koennte es zeigen. Diese zwei trennen sie:
636
+ #
637
+ # a) Nur die AUFLOESUNG: ein relativer Pfad, der den Kern nicht nennt. Bis
638
+ # 0.33.0 blockierte er, weil cwd damals Pruefmaterial war - genau der
639
+ # Fehler, den D-62 abstellt. Jetzt traegt ihn allein die Aufloesung.
640
+ nur_aufloesung = dict(umschlag)
641
+ nur_aufloesung.update({
642
+ "cwd": os.path.join(root, KERN, "tests"),
643
+ "tool_name": werkzeug,
644
+ "tool_input": {"file_path": "../VERSION", "content": "9"}})
645
+ if _hook_lauf(interpreter, skript, json.dumps(nur_aufloesung)) != 2:
646
+ err(f"{pack}/manifest.json: Ein relativer Pfad aus dem Kern heraus erreicht "
647
+ f"das Kernverzeichnis, ohne es zu nennen, und wird nicht blockiert. Die "
648
+ f"Pfadaufloesung gegen cwd traegt diesen Fall allein - die Muster sehen "
649
+ f"nur '../VERSION' (D-63, CR-2026-056 E5)")
650
+ # b) Nur re.I: eine Secret-Datei, die es NICHT gibt. realpath kann eine nicht
651
+ # vorhandene Datei nicht kanonisieren, die Schreibweise bleibt also stehen.
652
+ lesewerkzeuge = [w for w in ((daten.get("hook_tools") or {}).get("read") or [])
653
+ if isinstance(w, str) and w.strip()]
654
+ if lesewerkzeuge:
655
+ nur_schreibweise = dict(umschlag)
656
+ nur_schreibweise.update({
657
+ "tool_name": lesewerkzeuge[0],
658
+ "tool_input": {"file_path": "gibt-es-nicht/.ENV"}})
659
+ if _hook_lauf(interpreter, skript, json.dumps(nur_schreibweise)) != 2:
660
+ err(f"{pack}/manifest.json: Der Schutz-Hook laesst einen Secret-Pfad in "
661
+ f"abweichender Schreibweise durch, wenn die Datei nicht existiert. "
662
+ f"Eine nicht vorhandene Datei kann die Aufloesung nicht "
663
+ f"kanonisieren; hier traegt allein re.I (D-63)")
664
+
665
+ # c) Nur die ZWEITE LESART (CR-2026-163, D-491, K-96): die POSIX-Schreibweise
666
+ # /c/... der Projektwurzel mit einem Punktsegment. Der Rohtext nennt den
667
+ # Kern nicht ('.koolie/./core'), und Python liest /c/ als C:\c\ - ausserhalb
668
+ # des Projekts. Bis 1.20.0 ging der Schreibvorgang durch. Nur unter Windows:
669
+ # unter POSIX ist /c/ ein gewoehnlicher Pfad.
670
+ msys = _msys_form(root)
671
+ if msys:
672
+ teile = KERN.split("/")
673
+ variante = "/".join([msys] + teile[:1] + ["."] + teile[1:] + ["VERSION"])
674
+ if _hook_lauf(interpreter, skript, ereignis(
675
+ werkzeug, {"file_path": variante, "content": "9"})) != 2:
676
+ err(f"{pack}/manifest.json: Der Schutz-Hook laesst die POSIX-Schreibweise "
677
+ f"'{variante}' in das Kernverzeichnis schreiben. Unter MSYS bezeichnet "
678
+ f"/c/ das Laufwerk C:, der Hook muss beide Lesarten aufloesen "
679
+ f"(D-491, K-96)")
680
+
681
+ # Gegenprobe: Ein Verzeichnis, das nur so ANFAENGT wie der Kern, ist kein Kind
682
+ # von ihm. Ohne sie bestuende eine Praefixpruefung, die jeden Nachbarn sperrt.
683
+ if _hook_lauf(interpreter, skript, ereignis(
684
+ werkzeug, {"file_path": KERN + "-notizen/x.txt", "content": "x"})) != 0:
685
+ err(f"{pack}/manifest.json: Der Schutz-Hook blockiert ein Verzeichnis, das "
686
+ f"nur so anfaengt wie das Kernverzeichnis. Ein Verzeichnis mit aehnlichem "
687
+ f"Namensanfang ist kein Kind des geschuetzten (CR-2026-056 E5)")
688
+
689
+
690
+ # Pruefung 31: Die Zusammenfassung der Durchsetzungstiefe stimmt mit der Matrix.
691
+ #
692
+ # Die Summen sind dreimal gedriftet, und jedes Mal von Hand berichtigt worden: mit 0.26.0
693
+ # fuehrte ein Pack "von 26", waehrend die Matrix 29 Zeilen trug (A2, M4, M5 kamen mit
694
+ # CR-2026-025 hinzu); mit 0.31.0 wanderte S3 auf [NICHT ABBILDBAR], ohne dass die
695
+ # Zusammenfassung es nachzog; und die vier Zeilen mit einer Kanalgrenze zaehlten weiter als
696
+ # technisch, obwohl D-47 sie je Kanal ausweist - die Tabelle ueberzeichnete die
697
+ # Durchsetzungstiefe damit um vier Zeilen und ihre Ueberschrift um drei Kernzusagen (B11,
698
+ # D-60). Eine Zahl, die dreimal von Hand stimmen musste, gehoert ausgerechnet.
699
+ #
700
+ # ZAEHLREGEL, identisch zu der im Pack: Eine Zeile zaehlt bei ihrer SCHWAECHSTEN
701
+ # Einstufung. [NICHT ABBILDBAR] vor [TEXTUELL] vor [TECHNISCH]. Eine Zeile ohne jede
702
+ # Einstufung ist ein Fehler - sie sagt nichts zu.
703
+ #
704
+ # WAS DIESE PRUEFUNG NICHT LEISTET: Sie prueft die Arithmetik, nicht die Einstufung. Ob
705
+ # eine Zeile richtig eingestuft ist, kann kein Skript beurteilen; das leistet die Erhebung
706
+ # an einer Installation (AP2). Eine Matrix, in der jede Zeile falsch eingestuft ist,
707
+ # besteht diese Pruefung.
708
+ MATRIXZEILE_RE = re.compile(r"^\|\s*([A-Z]\d+)\s*\|")
709
+ EINSTUFUNGEN = ("[NICHT ABBILDBAR]", "[TEXTUELL]", "[TECHNISCH]")
710
+ SUMMENZEILE_RE = re.compile(
711
+ r"^\|\s*\*{0,2}`\[([A-Z ]+)\]`\*{0,2}\s*\|\s*\*{0,2}(\d+)\s+von\s+(\d+)", re.M)
712
+
713
+
714
+ def check_durchsetzungstiefe(root: str) -> None:
715
+ """Pruefung 31 (D-60): Die Summen der Fachmatrix sind aus ihr ausgerechnet."""
716
+ cdir = os.path.join(root, KERN, "clients")
717
+ if not os.path.isdir(cdir):
718
+ return
719
+ for pack in sorted(os.listdir(cdir)):
720
+ # Die Vorlage fuer ein neues Pack traegt ueberall <TBD> statt einer Einstufung -
721
+ # sie hat nichts zu summieren. Ausgenommen wird sie ueber die
722
+ # Unterstrich-Konvention der Ablage, nicht ueber ihren Namen: Ein zweites
723
+ # Vorlagenverzeichnis wuerde sonst durchfallen, und eine Ausnahme je Name waere
724
+ # eine gepflegte Liste (dieselbe Begruendung wie bei Pruefung 14).
725
+ if pack.startswith("_"):
726
+ continue
727
+ pfad = os.path.join(cdir, pack, "CLIENT_PACK.md")
728
+ if not os.path.isfile(pfad):
729
+ continue
730
+ rel = f"{KERN}/clients/{pack}/CLIENT_PACK.md"
731
+ text = read(pfad).replace("\r\n", "\n")
732
+ marke = text.find("## 3. Zusammenfassung")
733
+ if marke < 0:
734
+ err(f"{rel}: kein Abschnitt '## 3. Zusammenfassung der Durchsetzungstiefe'. "
735
+ f"Ohne ihn prueft Pruefung 31 nichts und bestuende leise (D-23)")
736
+ continue
737
+
738
+ gezaehlt = {k: [] for k in EINSTUFUNGEN}
739
+ ohne = []
740
+ for zeile in text[:marke].split("\n"):
741
+ m = MATRIXZEILE_RE.match(zeile)
742
+ if not m:
743
+ continue
744
+ for k in EINSTUFUNGEN: # schwaechste zuerst
745
+ if k in zeile:
746
+ gezaehlt[k].append(m.group(1))
747
+ break
748
+ else:
749
+ ohne.append(m.group(1))
750
+ summe = sum(len(v) for v in gezaehlt.values()) + len(ohne)
751
+ if ohne:
752
+ err(f"{rel}: Matrixzeile(n) ohne Einstufung: {', '.join(ohne)}. Eine Zeile "
753
+ f"ohne Einstufung sagt nichts zu und zaehlt in keiner Klasse")
754
+ if not summe:
755
+ err(f"{rel}: keine Matrixzeile gefunden. Der Anker dieser Pruefung ist die "
756
+ f"Zeilenform '| <Kennung> |'; ohne sie prueft sie nichts (D-23)")
757
+ continue
758
+
759
+ gefunden = SUMMENZEILE_RE.findall(text[marke:])
760
+ if not gefunden:
761
+ err(f"{rel}: Die Zusammenfassung fuehrt keine Zeile der Form "
762
+ f"'| `[KLASSE]` | N von M |'. Ohne sie ist die Summe nicht pruefbar")
763
+ continue
764
+ for klasse, anzahl, gesamt in gefunden:
765
+ k = "[%s]" % klasse.strip()
766
+ if k not in gezaehlt:
767
+ continue
768
+ if int(gesamt) != summe:
769
+ err(f"{rel}: Die Zusammenfassung nennt fuer {k} eine Gesamtzahl von "
770
+ f"{gesamt} Matrixzeilen; gezaehlt sind {summe}. Die Summen sind "
771
+ f"dreimal gedriftet, deshalb werden sie ausgerechnet (D-60)")
772
+ if int(anzahl) != len(gezaehlt[k]):
773
+ err(f"{rel}: Die Zusammenfassung nennt {anzahl} Zeile(n) als {k}; "
774
+ f"gezaehlt sind {len(gezaehlt[k])} ({', '.join(gezaehlt[k]) or 'keine'}). "
775
+ f"Zaehlregel: eine Zeile zaehlt bei ihrer schwaechsten Einstufung, "
776
+ f"weil eine Kanalgrenze keine technische Durchsetzung ist (D-47, D-60)")
777
+
778
+ # DIESELBE ZAHL STEHT EIN ZWEITES MAL, in der Uebersicht der Ablage - und dort
779
+ # rechnete sie bis 0.35.0 niemand nach. Ergebnis: 'claude-code' fuehrte dort
780
+ # "25 von 29", waehrend das Pack selbst einen Absatz darueber traegt, dass diese
781
+ # Zahl mit 0.33.0 auf 22 von 31 berichtigt wurde; 'devin-desktop' fuehrte
782
+ # "24 von 34" statt 20 von 36. Die Berichtigung hatte die zweite Stelle nicht
783
+ # erreicht (CR-2026-058 E7, D-71).
784
+ _uebersicht_pruefen(root, pack, len(gezaehlt["[TECHNISCH]"]), summe)
785
+
786
+
787
+ UEBERSICHT_DATEI = KERN + "/clients/README.md"
788
+
789
+
790
+ def _uebersicht_pruefen(root: str, pack: str, technisch: int, summe: int) -> None:
791
+ """Die Zeile dieses Packs in clients/README.md fuehrt dieselbe Zahl (D-71)."""
792
+ pfad = os.path.join(root, UEBERSICHT_DATEI.replace("/", os.sep))
793
+ if not os.path.isfile(pfad):
794
+ err(f"{UEBERSICHT_DATEI}: fehlt. Die Uebersicht der Ablage fuehrt je Pack die "
795
+ f"Zahl der technisch durchgesetzten Zeilen und wird gegen die Matrix "
796
+ f"gerechnet (D-71)")
797
+ return
798
+ text = read(pfad).replace("\r\n", "\n")
799
+ treffer = None
800
+ for zeile in text.split("\n"):
801
+ if not zeile.startswith("| `%s`" % pack):
802
+ continue
803
+ treffer = tabellenzellen(zeile)
804
+ break
805
+ if treffer is None:
806
+ err(f"{UEBERSICHT_DATEI}: kein Eintrag fuer das Client Pack '{pack}'. Jedes Pack "
807
+ f"der Ablage gehoert in die Uebersicht (Abschnitt 5, Schritt 8)")
808
+ return
809
+ zahl = next((z for z in treffer if re.fullmatch(r"\d+ von \d+", z)), None)
810
+ if zahl is None:
811
+ err(f"{UEBERSICHT_DATEI}: Die Zeile fuer '{pack}' fuehrt keine Zelle der Form "
812
+ f"'N von M'. Ohne sie prueft diese Haelfte nichts und bestuende leise (D-23)")
813
+ return
814
+ soll = "%d von %d" % (technisch, summe)
815
+ if zahl != soll:
816
+ err(f"{UEBERSICHT_DATEI}: Die Uebersicht nennt fuer '{pack}' '{zahl}' technisch "
817
+ f"durchgesetzte Zeilen; aus der Faehigkeitsmatrix gezaehlt sind '{soll}'. "
818
+ f"Eine Zahl mit eindeutiger Grenze gehoert ausgerechnet, nicht an zweiter "
819
+ f"Stelle gepflegt - hier ist sie zweimal gedriftet (D-71, CR-2026-058 E7)")
820
+
821
+
822
+ # ---------------------------------------------------------------------------
823
+ # Pruefung 43: Wo ein Pack seine Hooks fuehrt, stehen sie auch
824
+ # ---------------------------------------------------------------------------
825
+ #
826
+ # ANLASS. Gemessen am 2026-09-14 beim Herrichten des Uebungsrepositoriums
827
+ # (tests/protocols/2026-09-15-herrichtung-uebungsrepositorium.md, CR-2026-067): Seine
828
+ # Berechtigungsdatei trug KEINEN hooks-Block. Die Hooks des Projekts standen in
829
+ # .devin/hooks.v1.json - der Datei, aus der dieser Client keinen Hook ausfuehrt
830
+ # (AP2-DD-10, D-32, seit 0.25.0). Der Hook war damit seit der Erstinstallation
831
+ # wirkungslos, EINUNDDREISSIG Releases lang, und der Validator meldete durchgehend
832
+ # 0 Fehler.
833
+ #
834
+ # GEMESSEN, NICHT GESCHLOSSEN. In einer frischen 0.44.0-Installation wurde der Block
835
+ # entfernt und der Lauf wiederholt: mit Block 2 Fehler, ohne Block DIESELBEN 2 Fehler
836
+ # (beide Artefakte der Testinstallation). Kein Lauf sieht das Fehlen.
837
+ #
838
+ # WARUM DAS SCHWERER WIEGT ALS EINE VERWAISTE DATEI. Bei beiden ausgelieferten Packs ist
839
+ # dieser Hook die einzige technische Schranke, die eine Werkzeugeingabe prueft, bevor sie
840
+ # wirkt - Secrets nach 02-privacy.md und Schreibzugriffe auf den Kern ueber Werkzeuge, die
841
+ # die Berechtigungsdatei nicht erfasst. Die Berechtigungsdatei steht in shared_seed und
842
+ # wird nach der Erstinstallation nie wieder geschrieben (D-76): Ein Projekt, das den Block
843
+ # einmal nicht hat, bekommt ihn durch kein Update.
844
+ #
845
+ # UND PRUEFUNG 18 BESCHRIEB DIE HALBE MIGRATION. Ihre Warnung sagt, die verwaiste Datei
846
+ # sei von Hand zu loeschen. Wer ihr woertlich folgt und sonst nichts tut, hat danach GAR
847
+ # KEINEN Hook mehr. Ihre Meldung nennt seit diesem Release beide Haelften; nachgezaehlt
848
+ # wird die zweite hier.
849
+ #
850
+ # ENTHALTUNG (CR-2026-067 E3). Geprueft wird nur ein Pack, das seine Hooks in der
851
+ # Berechtigungsdatei fuehrt - erkennbar daran, dass <HOOKS_FILE> und permissions_file auf
852
+ # dieselbe Datei zeigen. Beide ausgelieferten Packs tun das; ein kuenftiges Pack mit
853
+ # eigener Hook-Datei bleibt ungeprueft. Dieselbe Bedingung hat Pruefung 18.
854
+ #
855
+ # GRENZE (E2). Geprueft wird das VORHANDENSEIN eines nichtleeren PreToolUse-Blocks, nicht
856
+ # sein Inhalt. Den pruefen 15 (Interpreter), 16 (Werkzeugabdeckung) und 17 (fail-closed) -
857
+ # sie greifen, sobald der Block da ist. Ein Block, der auf ein fremdes Skript zeigt, laeuft
858
+ # durch DIESE Pruefung und wird von 15 gefangen.
859
+
860
+
861
+ def check_hookblock(root: str, man: dict) -> None:
862
+ """Pruefung 43 (D-92): Die Berechtigungsdatei traegt den Hook, den das Pack dort fuehrt."""
863
+ if formatgebunden(man, 43):
864
+ return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
865
+ rel = man.get("permissions_file")
866
+ hooks_ort = (man.get("runtime_placeholders") or {}).get("<HOOKS_FILE>")
867
+ if not rel or hooks_ort != rel:
868
+ return # Pack mit eigener Hook-Datei - Enthaltung (E3)
869
+ pfad = os.path.join(root, *rel.split("/"))
870
+ if not os.path.exists(pfad):
871
+ return # check_required meldet die fehlende Datei bereits
872
+ try:
873
+ daten = json.loads(read(pfad))
874
+ except json.JSONDecodeError:
875
+ return # check_config hat das bereits gemeldet
876
+ hooks = daten.get("hooks")
877
+ eintraege = hooks.get("PreToolUse") if isinstance(hooks, dict) else None
878
+ kommandos = [h for e in (eintraege or []) if isinstance(e, dict)
879
+ for h in (e.get("hooks") or []) if isinstance(h, dict) and h.get("command")]
880
+ if kommandos:
881
+ return
882
+
883
+ runtime = (man.get("runtime_placeholders") or {}).get("<RUNTIME_DIR>") or ""
884
+ verwaist = [n for n in VERWAISTE_HOOK_DATEIEN
885
+ if runtime and os.path.exists(os.path.join(root, *runtime.split("/"), n))]
886
+ daneben = (f" Daneben liegt {runtime}/{verwaist[0]}: Dort steht eine Regelmenge, die "
887
+ f"aussieht, als gälte sie – der Client liest diese Datei nicht (D-32)."
888
+ if verwaist else "")
889
+ fehlt = "trägt keinen 'hooks'-Block" if not isinstance(hooks, dict) else \
890
+ "trägt einen 'hooks'-Block ohne ein einziges PreToolUse-Kommando"
891
+ err(f"{rel}: {fehlt}. Dieses Pack führt seine Hooks in der Berechtigungsdatei, und "
892
+ f"dieser Hook ist seine einzige technische Schranke vor dem Werkzeugaufruf – "
893
+ f"Secrets in der Eingabe und Schreibzugriffe auf den Kern über Wege, welche die "
894
+ f"Berechtigungsregeln nicht erfassen.{daneben} Die Datei wird nach der "
895
+ f"Erstinstallation nie wieder geschrieben (D-76): Der Block kommt durch kein "
896
+ f"Update nach, er ist von Hand aus einer frischen Installation zu übernehmen "
897
+ f"(D-92)")
898
+
899
+
900
+
901
+ # ---------------------------------------------------------------------------
902
+ # PRUEFUNG 86: DIE SPERRFORM DES SCHUTZ-HOOKS
903
+ # ---------------------------------------------------------------------------
904
+ # ANLASS, UND ER IST GEMESSEN (CR-2026-133, D-347). Ein Hook, der laeuft, ist nicht
905
+ # dasselbe wie ein Hook, der sperrt. Am 2026-09-23 ist an einer realen Installation von
906
+ # openai-codex gemessen worden, was die bis dahin einzige Sperrform des Schutz-Hooks -
907
+ # das Objekt {"decision": "block"} und Exit-Code 2 - bei diesem Client bewirkt: NICHTS.
908
+ # Der Client meldet den Hook als fehlgeschlagen und fuehrt die Operation aus; im
909
+ # Gegenlauf kam der Koederinhalt woertlich heraus. Dieselbe Sperre in der Form, die
910
+ # dieser Client liest, blockiert - und zwar auch in dem Betriebsmodus, der Rueckfragen
911
+ # und Sandkasten abschaltet.
912
+ # ➡️ Eine Sperre ist eine AUSSAGE AN DEN CLIENT, und ihre Form ist clientgebunden.
913
+ # Ein Pack, das die falsche nennt, liefert einen Schutz-Hook aus, der laeuft,
914
+ # etwas ausgibt und nichts verhindert - und nichts meldet es.
915
+ #
916
+ # GEPRUEFT WIRD DIE KETTE, NICHT DAS MANIFESTFELD - dieselbe Bauform wie Pruefung 17:
917
+ # (1) die vom Pack genannte Form steht in der Formenliste des Skripts,
918
+ # (2) das Skript gibt fuer sie wirklich eine Sperre dieser Form aus (Aufruf mit einer
919
+ # Eingabe, die es blockieren muss),
920
+ # (3) die erzeugte Hook-Konfiguration reicht die Form am Kommando durch.
921
+ # Ohne (2) bliebe es bei einem Listenvergleich, und ein Listenvergleich belegt
922
+ # Uebereinstimmung, nicht Wirkung.
923
+ def check_sperrform(root: str, man: dict) -> None:
924
+ """Pruefung 86 (D-347): Die Sperrform des Schutz-Hooks wirkt beim genannten Client."""
925
+ kern = os.path.join(root, KERN)
926
+ skript = os.path.join(kern, "tests", "scripts", "hook-check-secrets.py")
927
+ if not os.path.exists(skript):
928
+ return
929
+ quelle = read(skript)
930
+ m = re.search(r"^SPERRFORMEN\s*=\s*\(([^)]*)\)", quelle, re.M)
931
+ if not m:
932
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: keine Liste SPERRFORMEN. "
933
+ f"Ohne sie kann kein Pack seine Sperrform nennen, und Prüfung 86 hätte "
934
+ f"keinen Gegenstand (D-23)")
935
+ return
936
+ bekannt = set(re.findall(r'"([^"]+)"', m.group(1)))
937
+
938
+ form = man.get("hook_block_form")
939
+ if form and form not in bekannt:
940
+ err(f"clients/{man.get('client', '?')}/manifest.json: hook_block_form "
941
+ f"'{form}' kennt das Skript des Schutz-Hooks nicht. Bekannt sind "
942
+ f"{', '.join(sorted(bekannt))}. Eine Sperrform, die das Skript nicht kennt, "
943
+ f"fällt auf die Standardform zurück – und die ist bei diesem Client "
944
+ f"gemessen wirkungslos (D-347)")
945
+ return
946
+
947
+ # (2) Wirkung: Das Skript blockiert eine Eingabe, die es blockieren MUSS, und gibt
948
+ # dabei die verlangte Form aus.
949
+ eingabe = json.dumps({
950
+ "hook_event_name": "PreToolUse", "cwd": root,
951
+ "tool_name": "Write", "tool_input": {"file_path": ".env"}})
952
+ argumente = [sys.executable, skript]
953
+ if form:
954
+ argumente += ["--sperrform", form]
955
+ try:
956
+ lauf = subprocess.run(argumente, input=eingabe, capture_output=True,
957
+ text=True, timeout=30)
958
+ except (OSError, subprocess.SubprocessError) as exc:
959
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: nicht ausführbar ({exc}); "
960
+ f"Prüfung 86 kann die Sperrform nicht an ihrer Wirkung messen")
961
+ return
962
+ try:
963
+ ausgabe = json.loads(lauf.stdout.strip() or "{}")
964
+ except json.JSONDecodeError:
965
+ ausgabe = {}
966
+ if form == "stderr-grund":
967
+ # D-417: Exit 2 UND ein nicht leerer Grund auf stderr - mit leerem Grund laesst
968
+ # dieser Client die Operation laufen, gemessen am 2026-09-26.
969
+ if lauf.returncode != 2 or not (lauf.stderr or "").strip():
970
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Sperrform 'stderr-grund' "
971
+ f"verlangt Exit 2 und einen Grund auf stderr; gemessen wurden Exit "
972
+ f"{lauf.returncode} und {len((lauf.stderr or '').strip())} Zeichen auf "
973
+ f"stderr. Ohne Grund lässt der Client die Operation laufen (D-417)")
974
+ elif form == "permission-json":
975
+ # D-441: Sperre mit permission deny UND Exit 2 - und, das ist die zweite Haelfte,
976
+ # ein DURCHLASS mit gueltigem JSON. Mit failClosed wertet dieser Client einen
977
+ # Hook ohne Ausgabe als gescheitert und sperrt jede Operation, gemessen am
978
+ # 2026-09-26. Die Durchlassprobe ist eine harmlose Leseoperation.
979
+ if ausgabe.get("permission") != "deny" or lauf.returncode != 2:
980
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Sperrform 'permission-json' "
981
+ f"verlangt permission 'deny' und Exit 2; gemessen wurden "
982
+ f"'{ausgabe.get('permission')}' und Exit {lauf.returncode} (D-441)")
983
+ harmlos = json.dumps({"hook_event_name": "preToolUse", "tool_name": "Read",
984
+ "tool_input": {"file_path": "README.md"}})
985
+ try:
986
+ durch = subprocess.run(argumente + ["--fail-closed"], input=harmlos,
987
+ capture_output=True, text=True, timeout=30, cwd=root)
988
+ except (OSError, subprocess.SubprocessError):
989
+ durch = None
990
+ try:
991
+ antwort = json.loads(durch.stdout.strip()) if durch else None
992
+ except ValueError:
993
+ antwort = None
994
+ if durch is None or durch.returncode != 0 or not isinstance(antwort, dict) \
995
+ or antwort.get("permission") == "deny":
996
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Sperrform 'permission-json' "
997
+ f"verlangt beim Durchlass Exit 0 und ein JSON-Objekt ohne deny; gemessen "
998
+ f"wurde {('Exit ' + str(durch.returncode)) if durch else 'kein Lauf'} mit "
999
+ f"{(durch.stdout or '').strip()[:40]!r}. Ohne Antwort wertet der Client den "
1000
+ f"Hook mit failClosed als gescheitert und sperrt JEDE Operation (D-441)")
1001
+ elif form == "hook-specific-output":
1002
+ entscheidung = (ausgabe.get("hookSpecificOutput") or {}).get("permissionDecision")
1003
+ if entscheidung != "deny" or lauf.returncode != 0:
1004
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Sperrform "
1005
+ f"'hook-specific-output' verlangt permissionDecision 'deny' und "
1006
+ f"Exit 0; gemessen wurden '{entscheidung}' und Exit {lauf.returncode}. "
1007
+ f"Ein Exit ungleich 0 ist bei diesem Client ein FEHLGESCHLAGENER Hook, "
1008
+ f"und ein fehlgeschlagener Hook lässt die Operation laufen (D-347)")
1009
+ else:
1010
+ if ausgabe.get("decision") != "block" or lauf.returncode != 2:
1011
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: Standard-Sperrform "
1012
+ f"verlangt decision 'block' und Exit 2; gemessen wurden "
1013
+ f"'{ausgabe.get('decision')}' und Exit {lauf.returncode}")
1014
+
1015
+ # (3) Die erzeugte Konfiguration reicht die Form durch.
1016
+ if not form:
1017
+ return
1018
+ hooks_rel = (man.get("runtime_placeholders") or {}).get("<HOOKS_FILE>")
1019
+ if not hooks_rel:
1020
+ return
1021
+ pfad = os.path.join(root, *hooks_rel.split("/"))
1022
+ if not os.path.exists(pfad):
1023
+ return
1024
+ text = read(pfad)
1025
+ if "hook-check-secrets.py" in text and f"--sperrform {form}" not in text:
1026
+ err(f"{hooks_rel}: das Kommando des Schutz-Hooks trägt die Sperrform "
1027
+ f"'{form}' nicht. Das Pack nennt sie, die erzeugte Datei reicht sie nicht "
1028
+ f"durch – der Hook liefe mit der Standardform und sperrte bei diesem "
1029
+ f"Client nichts (D-347)")
1030
+
1031
+
1032
+ # ---------------------------------------------------------------------------
1033
+ # PRUEFUNGEN 98 BIS 100 (CR-2026-156): DER ERSTE PROJEKTEINSATZ
1034
+ # ---------------------------------------------------------------------------
1035
+ # ANLASS: Befunde aus dem Einsatz von 1.16.0 in einem Projekt mit devin-desktop
1036
+ # (2026-09-27): Der Schutz-Hook sperrte einen Aenderungsantrag nach seinem INHALT (A2),
1037
+ # drei rein lesende Skills waren dem Client verwehrt (A1), und besprochene Entscheidungen
1038
+ # liessen sich nur als Vorlage zum Abschreiben liefern (Modus M6, D-446).
1039
+ P98_ANKER = "def ziele_der_schreiboperation("
1040
+ P98_INHALT = ("siehe .koolie/core/VERSION, .koolie/project-overlay/OVERLAY.md, AGENTS.md "
1041
+ "und .devin/rules/00-framework-core.md")
1042
+
1043
+
1044
+ def _p98_werkzeuge(root: str) -> list:
1045
+ """(Pack, erstes Schreibwerkzeug) je Manifest."""
1046
+ out = []
1047
+ packs = os.path.join(root, KERN, "clients")
1048
+ for name in sorted(os.listdir(packs)) if os.path.isdir(packs) else []:
1049
+ mf = os.path.join(packs, name, "manifest.json")
1050
+ if not os.path.isfile(mf):
1051
+ continue
1052
+ try:
1053
+ daten = json.loads(read(mf)) or {}
1054
+ except json.JSONDecodeError:
1055
+ continue
1056
+ schreiben = [w for w in ((daten.get("hook_tools") or {}).get("write") or [])
1057
+ if isinstance(w, str) and w.strip()]
1058
+ if schreiben:
1059
+ out.append((daten.get("client", name), schreiben))
1060
+ return out
1061
+
1062
+
1063
+ def check_hook_ziel_statt_inhalt(root: str, man: dict) -> None:
1064
+ """Pruefung 98 (D-449): Der Hook misst das Ziel einer Schreiboperation, nicht ihren Inhalt."""
1065
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
1066
+ if not os.path.isfile(skript):
1067
+ return
1068
+ if P98_ANKER not in read(skript):
1069
+ err(f"{KERN}/tests/scripts/hook-check-secrets.py: '{P98_ANKER}' fehlt. Pruefung 98 "
1070
+ f"misst, dass eine Schreiboperation an ihren Zielen gemessen wird; ohne diese "
1071
+ f"Stufe bestuende sie leise (D-23, D-449)")
1072
+ return
1073
+ interpreter = _hook_interpreter()
1074
+ if interpreter is None:
1075
+ return
1076
+ for pack, werkzeuge in _p98_werkzeuge(root):
1077
+ for werkzeug in werkzeuge:
1078
+ if werkzeug.lower() == "apply_patch":
1079
+ frei = {"command": "*** Begin Patch\n*** Add File: docs/p98.md\n+"
1080
+ + P98_INHALT + "\n*** End Patch"}
1081
+ kern = {"command": "*** Begin Patch\n*** Add File: " + KERN
1082
+ + "/p98.txt\n+x\n*** End Patch"}
1083
+ else:
1084
+ frei = {"file_path": "docs/p98.md", "content": P98_INHALT}
1085
+ kern = {"file_path": KERN + "/p98.txt", "content": "x"}
1086
+ ergebnis = _hook_lauf(interpreter, skript, json.dumps(
1087
+ {"tool_name": werkzeug, "tool_input": frei}))
1088
+ if ergebnis != 0:
1089
+ err(f"{pack}/manifest.json: Der Schutz-Hook sperrt das Werkzeug '{werkzeug}' mit "
1090
+ f"einem freien Ziel (docs/), weil sein Inhalt geschuetzte Pfade NENNT "
1091
+ f"(Exit {ergebnis}). Gemessen wird das Ziel, nicht der Inhalt - sonst kann "
1092
+ f"kein Aenderungsantrag die Pfade nennen, um die es geht (D-449)")
1093
+ ergebnis = _hook_lauf(interpreter, skript, json.dumps(
1094
+ {"tool_name": werkzeug, "tool_input": kern}))
1095
+ if ergebnis != 2:
1096
+ err(f"{pack}/manifest.json: Der Schutz-Hook laesst das Werkzeug '{werkzeug}' in "
1097
+ f"das Kernverzeichnis schreiben (Exit {ergebnis} statt 2) - die Messung am "
1098
+ f"Ziel darf das Ziel nicht verlieren (D-449)")
1099
+
1100
+
1101
+ # Pruefung 106: Das Entscheidungsprotokoll des Schutz-Hooks (K-192, D-487).
1102
+ #
1103
+ # ANLASS. Sperren und Durchlaesse hinterliessen bis 1.19.1 keine Spur ausserhalb der
1104
+ # Mitschrift des Clients. Seit 1.20.0 schreibt der Hook je Entscheidung auf ein Ereignis
1105
+ # eines Clients eine Zeile in <git-verzeichnis>/koolie-hook.jsonl. Die Pruefung haelt die
1106
+ # drei Zusagen, die dabei zaehlen, in einem Wegwerfbaum mit einer Kopie des Hooks:
1107
+ # 1. Eine Entscheidung auf ein Client-Ereignis steht im Protokoll - gesperrt und
1108
+ # durchgelassen, mit genau den Feldern zeit, werkzeug, verb, ergebnis, grund.
1109
+ # 2. Das Protokoll traegt NIE den Inhalt und nie einen Pfadwert (D-39): weder den
1110
+ # Koederwert noch den Pfad der Sonde.
1111
+ # 3. Ein Aufruf ohne hook_event_name (eine Sonde wie diese Pruefung selbst) steht nicht
1112
+ # darin, und 'hook_protokoll: aus' im Overlay-Manifest schaltet es ab.
1113
+ # GRENZE: Ob ein Client den Hook so aufruft, belegt die Messung (Mitschriften der Reihe
1114
+ # 1200), nicht diese Pruefung; das Protokoll ist nicht manipulationsgeschuetzt.
1115
+ P106_FELDER = {"zeit", "werkzeug", "verb", "ergebnis", "grund"}
1116
+ P106_KOEDER = "KOOLIE-SONDE-106-geheimwert"
1117
+ P106_PFAD = "sonde106-verzeichnis/.env"
1118
+
1119
+
1120
+ def check_hook_protokoll(root: str) -> None:
1121
+ """Pruefung 106 (K-192, D-487): Das Protokoll haelt Entscheidungen fest, nie Inhalte."""
1122
+ import shutil
1123
+ import tempfile
1124
+ skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
1125
+ if not os.path.isfile(skript):
1126
+ return
1127
+ interpreter = _hook_interpreter()
1128
+ if interpreter is None:
1129
+ return
1130
+ with tempfile.TemporaryDirectory(prefix="koolie-p106-") as w:
1131
+ ziel = os.path.join(w, *KERN.split("/"), "tests", "scripts")
1132
+ os.makedirs(ziel)
1133
+ shutil.copy2(skript, ziel)
1134
+ os.makedirs(os.path.join(w, ".git"))
1135
+ kopie = os.path.join(ziel, "hook-check-secrets.py")
1136
+ protokoll = os.path.join(w, ".git", "koolie-hook.jsonl")
1137
+
1138
+ def rufen(ereignis: dict) -> int:
1139
+ try:
1140
+ return subprocess.run([interpreter, kopie], input=json.dumps(ereignis),
1141
+ capture_output=True, text=True, timeout=20).returncode
1142
+ except (OSError, subprocess.SubprocessError):
1143
+ return -1
1144
+
1145
+ def zeilen() -> list:
1146
+ try:
1147
+ with open(protokoll, encoding="utf-8") as fh:
1148
+ return [z for z in fh.read().splitlines() if z.strip()]
1149
+ except OSError:
1150
+ return []
1151
+
1152
+ e = {"hook_event_name": "PreToolUse", "tool_name": "Read"}
1153
+ rufen(dict(e, tool_input={"file_path": P106_PFAD, "text": P106_KOEDER}))
1154
+ rufen(dict(e, tool_input={"file_path": "README.md"}))
1155
+ rufen({"tool_name": "Read", "tool_input": {"file_path": "ohne-ereignis.md"}})
1156
+ z = zeilen()
1157
+ if len(z) != 2:
1158
+ err(f"Pruefung 106: Das Entscheidungsprotokoll des Schutz-Hooks fuehrt nach zwei "
1159
+ f"Client-Ereignissen und einer Sonde ohne hook_event_name {len(z)} Zeile(n) "
1160
+ f"statt 2 (K-192, D-487)")
1161
+ return
1162
+ roh = "\n".join(z)
1163
+ if P106_KOEDER in roh or "sonde106" in roh:
1164
+ err("Pruefung 106: Das Entscheidungsprotokoll des Schutz-Hooks traegt den Inhalt "
1165
+ "oder einen Pfadwert der Werkzeugeingabe - es darf nur Kategorien nennen "
1166
+ "(D-39, D-487)")
1167
+ try:
1168
+ daten = [json.loads(x) for x in z]
1169
+ except ValueError:
1170
+ err("Pruefung 106: Das Entscheidungsprotokoll des Schutz-Hooks ist kein JSONL")
1171
+ return
1172
+ for d in daten:
1173
+ if set(d) != P106_FELDER:
1174
+ err(f"Pruefung 106: Eine Zeile des Entscheidungsprotokolls fuehrt die Felder "
1175
+ f"{sorted(d)} statt {sorted(P106_FELDER)} (D-487)")
1176
+ return
1177
+ if [d.get("ergebnis") for d in daten] != ["gesperrt", "durchgelassen"]:
1178
+ err(f"Pruefung 106: Das Entscheidungsprotokoll haelt "
1179
+ f"{[d.get('ergebnis') for d in daten]} fest statt ['gesperrt', "
1180
+ f"'durchgelassen'] (D-487)")
1181
+ os.makedirs(os.path.join(w, ".koolie", "project-overlay"))
1182
+ with open(os.path.join(w, ".koolie", "project-overlay", "overlay-manifest.yaml"),
1183
+ "w", encoding="utf-8") as fh:
1184
+ fh.write("manifest_version: 1\nhook_protokoll: aus\n")
1185
+ rufen(dict(e, tool_input={"file_path": "README.md"}))
1186
+ if len(zeilen()) != 2:
1187
+ err("Pruefung 106: 'hook_protokoll: aus' im overlay-manifest.yaml schaltet das "
1188
+ "Entscheidungsprotokoll des Schutz-Hooks nicht ab (D-487)")