@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,1307 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Framework-Hook: PreToolUse-Pruefung auf Secrets und geschuetzte Pfade.
4
+
5
+ Status: entwurf (Belegstatus des Hook-Mechanismus: [DOK]; Eingabeschema gegen die
6
+ Aufzeichnungen beider Packs belegt, siehe unten).
7
+
8
+ AUFBAU IN DREI STUFEN (seit 0.34.0, CR-2026-056, Befund B06):
9
+
10
+ 1. EREIGNIS - ist die Eingabe ueberhaupt ein Werkzeugaufruf?
11
+ 2. OPERATION - welches Verb, und welche Pfade nennt die Eingabe wirklich?
12
+ 3. REGELN - die Musterlisten, unveraendert in ihrem Inhalt.
13
+
14
+ Bis 0.33.0 gab es Stufe 1 und 2 nicht. Das Skript nannte sich "bewusst schema-agnostisch"
15
+ und durchsuchte alle Zeichenketten der gelieferten JSON-Struktur. Daraus folgten ZWEI
16
+ entgegengesetzte Fehler mit derselben Ursache (gemessen, tests/protocols/
17
+ 2026-09-13-B06-gegenpruefung.md):
18
+
19
+ - ES LIESS ZU VIEL DURCH. Sechs Eingabeformen passierten auch mit --fail-closed: leere
20
+ Eingabe, nur Weissraum, [], null, eine Zeichenkette, eine Zahl. Ein unbekannter, nicht
21
+ leerer Werkzeugname uebersprang beide Pfadbloecke. Und der Rueckfall fuer die
22
+ unbekannte Operation nahm ausdruecklich fuer sich in Anspruch, "die strengere Liste"
23
+ zu sein - er war die einzige Stelle, an der das Kernverzeichnis UNGESCHUETZT war.
24
+ - ES BLOCKIERTE ZU VIEL, und das wog schwerer. Der Client claude-code fuehrt im
25
+ Umschlag jedes Ereignisses transcript_path, und der liegt unter ~/.claude/projects/.
26
+ Damit traf das Strukturmuster fuer die Laufzeitschicht bei JEDEM Schreibzugriff -
27
+ unabhaengig vom Ziel. Am Client nachgemessen: In einer Umgebung ohne Regeltexte
28
+ blockierte der Hook eine harmlose Schreibprobe; der Kontrolllauf ohne Hook legte
29
+ dieselbe Datei an. Dasselbe gilt fuer cwd, wenn die Sitzung im Kernverzeichnis
30
+ startet. Es war kein Feld, es war eine Gattung.
31
+
32
+ Geprueft wird deshalb die OPERATION, nicht der Umschlag: ausschliesslich tool_input.
33
+ cwd dient als Aufloesungsbasis fuer relative Pfade und ist nie Pruefmaterial (D-62).
34
+
35
+ Verhalten:
36
+ - Fund eines Secret-Musters oder eines geschuetzten Pfads in der Werkzeugeingabe
37
+ -> Ausgabe {"decision": "block", "reason": "..."} und Exit-Code 2 (blockiert laut Doku).
38
+ - Schreibende Werkzeuge zusaetzlich: jeder Pfad im Kernverzeichnis. Ausfuehrende Werkzeuge
39
+ sind davon ausgenommen, damit die Skripte des Kerns (Validator, install.py --check)
40
+ weiterhin aufrufbar bleiben; dort greift die deny-Regel der Berechtigungsdatei.
41
+ - UNPRUEFBARE Eingabe (kein Ereignis, kein aufloesbarer Pfad) -> Exit-Code 0 mit Warnung
42
+ auf stderr (fail-open) oder, mit --fail-closed, Blockade. Das ist ein eigener dritter
43
+ Ausgang neben "sauber" und "gefunden" (D-61): Er sagt nicht, dass nichts gefunden wurde,
44
+ sondern dass nicht gesucht werden konnte.
45
+ Wo --fail-closed steht, entscheidet das Client Pack: Das Manifest fuehrt
46
+ hook_fail_closed, und clientmap.py haengt das Argument beim Rendern an das
47
+ Hook-Kommando. Das Argument steht damit in der Konfiguration, die der Client ohnehin
48
+ ausfuehrt - laeuft der Hook, kommt es an. Eine Umgebungsvariable haette die Zusage an
49
+ eine zweite, unbestaetigte Clientzusage gehaengt (D-31).
50
+ FW_HOOK_FAIL_CLOSED=1 wirkt weiterhin und bleibt der Weg fuer eine Installation, die
51
+ fail-closed ohne Neuinstallation erproben will.
52
+ - Kein Fund -> Exit-Code 0.
53
+
54
+ SEIT 1.17.0 (CR-2026-156) ZWEI AENDERUNGEN AM SCHREIBWEG:
55
+ - DAS ZIEL, NICHT DER INHALT (D-449). Bei einem Schreibwerkzeug werden die Pfadmuster
56
+ nur noch gegen die ZIELE der Operation gehalten - die Pfadfelder und die Dateikoepfe
57
+ eines Patchtextes -, nicht mehr gegen den Dateiinhalt. Gemessen am 2026-09-27 in einem
58
+ Projekt mit devin-desktop: Ein Aenderungsantrag unter docs/, der Overlay- und
59
+ Kernpfade NENNT, wurde gesperrt, als schriebe er in sie. Ein unbekanntes Werkzeug
60
+ bleibt bei der strengsten Lesart (alle Zeichenketten); ein Schreibwerkzeug ohne
61
+ Pfadfeld ist unpruefbar. Die Muster fuer Secrets IM Inhalt gelten unveraendert.
62
+ - DAS MANDAT (D-447, D-448). Das Overlay ist nicht mehr durch die Berechtigungsdatei
63
+ gesperrt, sondern allein durch diesen Hook - und der laesst ein Schreiben dorthin
64
+ zu, solange ein gueltiges Mandat es deckt. Das Mandat legt nur der Mensch an
65
+ (mandat.py im Kern, im eigenen Terminal); Datei und Befehl sind fuer jede nicht
66
+ lesende Operation gesperrt.
67
+ - Jede Sperre nennt in vier Zeilen, was gesperrt ist, warum, was der Mensch tun kann
68
+ und was daraus folgt (Blockade-Hinweis, D-450).
69
+
70
+ SEIT 1.20.0 (CR-2026-162) ZWEI ERWEITERUNGEN:
71
+ - MCP-WERKZEUGE (K-184, D-486). Ein MCP-Aufruf lief an allen fuenf Matchern vorbei, sein
72
+ Inhalt verliess das Haus ungeprueft. Er traegt jetzt das Verb 'mcp', erkannt am
73
+ Namenspraefix aus hook_mcp_prefixes der Manifeste (die Namen selbst waehlt der Server).
74
+ Geprueft werden der Inhalt gegen die Secret-Muster und die Pfadfelder gegen die
75
+ Secret-Pfadmuster - ein Dateisystem-Server liest sonst .env. Die Struktur- und
76
+ Kernpfade gelten NICHT: Eine Doku-Seite, die .claude/ oder den Kern nennt, schreibt
77
+ nicht dorthin. Grenze: Ein MCP-Server, der selbst lokal schreibt, ist ueber die
78
+ Strukturmuster nicht gesperrt; Schreibwerkzeuge stehen nie auf allow (D-459).
79
+ - DAS ENTSCHEIDUNGSPROTOKOLL (K-192, D-487). Jede Entscheidung auf ein Ereignis eines
80
+ Clients (erkennbar an hook_event_name) wird als eine JSON-Zeile in
81
+ <git-verzeichnis>/koolie-hook.jsonl festgehalten: Zeit, Werkzeug, Verb, Ergebnis und
82
+ die erste Zeile des Grundes. NIE der Inhalt und nie ein Pfadwert - der Grund nennt
83
+ eine Kategorie (D-39). Abschalten: 'hook_protokoll: aus' im overlay-manifest.yaml.
84
+ Ohne Git-Verzeichnis kein Protokoll (wie beim Mandat). Ein Fehler beim Schreiben
85
+ aendert keine Entscheidung. Grenze: Das Protokoll ist nicht manipulationsgeschuetzt.
86
+
87
+ SEIT 1.20.2 (CR-2026-164) ZWEI SPERREN MEHR:
88
+ - DIE MODUSBINDUNG (K-179, D-501; K-201). Bindet der Mensch einen Modus im eigenen
89
+ Terminal ('mandat.py modus'), sperrt der Hook jeden Schreibaufruf (M1), jeden ausserhalb
90
+ der Plan-Ablage (M2) oder jeden ausserhalb der Pfade des Modus aus dem Overlay, die beim
91
+ Binden kopiert wurden (M3 bis M5). Datei und Befehl sind wie das Mandat fuer den Client gesperrt. Ohne
92
+ Bindung aendert sich nichts. Grenze: Ein Shell-Befehl, der schreibt, entgeht ihr.
93
+ - DIE UEBERTRAGUNG VON SECRETS NACH AUSSEN (K-94, D-503). 'cloud drs secret-create' - der
94
+ Weg des eingebauten Skills 'upload-secrets' von devin-desktop - ist fuer jedes
95
+ ausfuehrende Werkzeug gesperrt, weil die CLI die Werte selbst liest.
96
+
97
+ Das Skript gibt gefundene Secrets niemals aus; es nennt nur die Musterkategorie. Dasselbe
98
+ gilt fuer einen unpruefbaren Pfadwert: Genannt wird die Position, nicht der Wert (D-39).
99
+ Alle Muster sind generisch; sie enthalten keine realen Werte.
100
+ """
101
+ import datetime
102
+ import io
103
+ import json
104
+ import os
105
+ import re
106
+ import sys
107
+
108
+ SECRET_PATTERNS = [
109
+ ("privater Schluessel", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")),
110
+ ("Bearer-Token", re.compile(r"\bBearer\s+[A-Za-z0-9\-_\.=]{20,}", re.I)),
111
+ ("Zugangsdaten-Zuweisung", re.compile(r"(?i)\b(password|passwd|pwd|secret|api[_-]?key|access[_-]?key|token)\b\s*[:=]\s*['\"]?[^\s'\"]{8,}")),
112
+ ("Verbindungszeichenfolge mit Anmeldedaten", re.compile(r"(?i)\b[a-z][a-z0-9+\-.]*://[^/\s:]+:[^@\s]+@")),
113
+ ("Cloud-Zugangsschluessel (generisches Muster)", re.compile(r"\b(AKIA|ASIA)[0-9A-Z]{16}\b")),
114
+ ("JWT", re.compile(r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b")),
115
+ ]
116
+
117
+ # Zwei Schutzziele, zwei Listen. Sie standen bisher in einer, und das verwischte den
118
+ # Unterschied: Ein Secret-Pfad ist vertraulich - er darf auch nicht GELESEN werden. Ein
119
+ # Strukturpfad ist integritaetsgeschuetzt - er darf nicht GESCHRIEBEN werden, gelesen
120
+ # aber sehr wohl. Zusammengelegt blockierte die Liste auch ein 'git diff' auf einen
121
+ # Kernpfad - eine Operation, die das Framework an anderer Stelle ausdruecklich
122
+ # voraussetzt (Befunde mit Fundstellen, P4).
123
+ #
124
+ # ALLE Pfadmuster laufen mit re.I (D-63). Bis 0.33.0 taten es zwei von neun - die
125
+ # Endungsliste und secrets/ -, die uebrigen sieben nicht. Das war keine Entscheidung fuer
126
+ # POSIX-Semantik, sondern eine Ungleichbehandlung innerhalb derselben Datei: Unter Windows
127
+ # bezeichnen .KOOLIE/CORE/VERSION und .koolie/core/VERSION dieselbe Datei, und der Hook
128
+ # entschied sie verschieden (gemessen mit os.path.samefile als Vorpruefung). Auf POSIX ist
129
+ # re.I eine Verschaerfung - dieselbe Begruendung, mit der ein Muster schon bisher AGENTS
130
+ # und CLAUDE gemeinsam fuehrt: Ein zusaetzlich geschuetzter Pfad ist keine Lockerung.
131
+ # DIE GRENZE VOR EINEM PFAD - UND WARUM SIE SEIT 1.4.0 AUCH EIN LEERZEICHEN IST
132
+ # (CR-2026-133, D-347). Bis 1.3.0 hiess sie `(^|[\\/])`: Ein Pfad musste am Anfang der
133
+ # Zeichenkette stehen oder auf einen Schraegstrich folgen. Das traegt, solange der Pfad
134
+ # in einem eigenen Feld oder als eigenes Befehlstoken steht - und genau das ist beim
135
+ # dritten Client Pack nicht mehr der Fall. Gemessen am 2026-09-23 an einer realen
136
+ # Installation: Das Schreibwerkzeug von openai-codex fuehrt keinen Pfad in einem Feld;
137
+ # es fuehrt einen PATCHTEXT, und der Pfad steht darin hinter einem Leerzeichen
138
+ # ("*** Add File: .koolie/core/notiz.txt"). Der Hook lief, sah den Text - und die
139
+ # Musterpruefung traf nicht. Die Datei wurde angelegt.
140
+ # ➡️ Eine Grenze, die nur den Schraegstrich kennt, misst die Schreibweise und nicht
141
+ # die Sache.
142
+ # Die Erweiterung ist eine VERSCHAERFUNG und gilt fuer alle drei Packs: Es kommen
143
+ # Treffer hinzu, es faellt keiner weg.
144
+ _GRENZE = r"(^|[\s\\/])"
145
+
146
+ SECRET_PATH_PATTERNS = [
147
+ re.compile(_GRENZE + r"\.env(\.|$)", re.I),
148
+ re.compile(r"\.(pem|key|p12|pfx|jks|keystore)$", re.I),
149
+ re.compile(_GRENZE + r"id_(rsa|ed25519|ecdsa)", re.I),
150
+ # SEIT 1.16.0 AUCH OHNE NACHFOLGENDEN TRENNER (CR-2026-155, D-442): Ein
151
+ # Suchwerkzeug nennt das VERZEICHNIS - "secrets", nicht "secrets/pw.txt" -, und ein
152
+ # Befehl wie "grep -r KOEDER secrets" ebenso. Gemessen am 2026-09-26 an cursor-agent:
153
+ # Das Muster mit Pflicht-Trenner liess die Suche ueber den Ordner durch. Das gilt fuer
154
+ # jeden Client mit einem Suchwerkzeug; die Erweiterung ist eine Verschaerfung.
155
+ re.compile(_GRENZE + r"secrets?([\\/]|$)", re.I),
156
+ ]
157
+
158
+ STRUCTURE_PATH_PATTERNS = [
159
+ # Wurzel-Anweisungsdatei und Laufzeitschicht heissen je nach Client anders. Bewusst
160
+ # alle Formen: Das Skript wird von allen Client Packs geteilt, und ein zusaetzlich
161
+ # geschuetzter Pfad ist eine Verschaerfung, keine Lockerung.
162
+ #
163
+ # AGENTS.override.md STEHT SEIT 1.4.0 MIT, und der Grund ist gemessen: Bei
164
+ # openai-codex verdraengt diese Datei die Wurzel-Anweisung VOLLSTAENDIG - liegt sie
165
+ # im Projekt, steht die Anweisung des Frameworks in keiner Nachricht der Sitzung.
166
+ # Eine Datei, die Ebene 1 lautlos ersetzt, ist mindestens so schutzwuerdig wie die
167
+ # Ebene selbst.
168
+ re.compile(_GRENZE + r"(AGENTS|CLAUDE)(\.[A-Za-z0-9_-]+)?\.md$", re.I),
169
+ # .cursor SEIT 1.16.0 (CR-2026-155): Laufzeitschicht, Berechtigungsdatei
170
+ # (.cursor/cli.json) und Hook-Datei des Packs cursor liegen dort.
171
+ re.compile(_GRENZE + r"\.(devin|claude|codex|cursor)[\\/]", re.I),
172
+ # .kiro SEIT 1.13.0 (CR-2026-150, D-414, D-415) - mit EINER Ausnahme: .kiro/specs/
173
+ # ist bei diesem Client die Ablage seines Planartefakts, und das ist nach D-415 der
174
+ # Traeger des Plans, kein Artefakt des Frameworks. Das Agentenprofil nimmt denselben
175
+ # Teilbaum ueber das Feld exclude aus seinem Schreibverbot aus; ohne die Ausnahme
176
+ # hier sperrte der Hook in der interaktiven Sitzung, was die Berechtigungen zulassen.
177
+ re.compile(_GRENZE + r"\.kiro[\\/](?!specs[\\/])", re.I),
178
+ # Das Overlay steht als EIGENES Objekt in der Liste (OVERLAY_MUSTER unten): Seit
179
+ # 1.17.0 kann ein Mandat genau dieses eine Muster fuer ein gedecktes Ziel aufheben
180
+ # (D-447) - und nur dieses.
181
+ re.compile(_GRENZE + r"\.koolie[\\/]project-overlay[\\/]", re.I),
182
+ re.compile(_GRENZE + r"framework[\\/]core[\\/]", re.I),
183
+ ]
184
+ OVERLAY_MUSTER = STRUCTURE_PATH_PATTERNS[-2]
185
+
186
+ # Fuer schreibende Werkzeuge gelten beide Ziele. Die Zusatzmuster aus der Umgebung
187
+ # haengen an dieser Summe: Ein Projekt meint sie als Verbot, nicht als Leseerlaubnis.
188
+ PROTECTED_PATH_PATTERNS = SECRET_PATH_PATTERNS + STRUCTURE_PATH_PATTERNS
189
+
190
+ # Das Kernverzeichnis als Ganzes. Es steht bewusst in einer zweiten Liste: Die Muster
191
+ # oben gelten auch fuer 'exec', und ein Befehl, der lediglich einen Kernpfad nennt -
192
+ # der Validator, install.py --check, ein git diff - muss weiterhin laufen koennen. Fuer
193
+ # schreibende Werkzeuge gilt das Verbot vollstaendig. Die Liste ist damit rein additiv:
194
+ # Sie verschaerft, ohne eine bisher blockierte Operation freizugeben.
195
+ #
196
+ # WAS SIE NICHT LEISTET, und das stand hier bis 0.29.0 falsch: Ein Shell-Befehl, der in
197
+ # das Kernverzeichnis schreibt, wird von dieser Liste nicht erfasst - und auch nicht von
198
+ # der Berechtigungsdatei, auf die der Kommentar dafuer verwies. Diese fuehrt fuer 'exec'
199
+ # ausschliesslich Befehlsverbote und keine einzige Pfadregel. Gemessen am 2026-09-12
200
+ # (B04, Laeufe B04-1 bis B04-3): "sed -i ... <kern>/VERSION" passiert den Hook. Was den
201
+ # Shell-Schreibweg aufhaelt, ist die Regelschicht - also Modellverhalten, [TEXTUELL].
202
+ # Die Fachmatrizen beider Packs weisen das bei B4 und B5 je Zugriffskanal aus
203
+ # (CR-2026-047, D-47); eine technische Durchsetzung braucht eine Isolationsschicht des
204
+ # Betriebssystems und ist unerhoben. CR-2026-056 aendert daran NICHTS - K-32 bleibt offen.
205
+ #
206
+ # Die LAGE des Kernverzeichnisses ist keine Eigenschaft eines Clients, sondern dieser
207
+ # Installation (<CORE_DIR>, docs/PLACEHOLDER_REGISTRY.md).
208
+ #
209
+ # 0.88.0: Bis dahin stand hier os.path.basename() ueber drei dirname-Aufrufe, mit der
210
+ # Begruendung, eine Umbenennung des Kerns erreiche den Hook damit von selbst. Sie tat
211
+ # es nicht: Der Kern liegt seit diesem Release unter `.koolie/core`, und basename()
212
+ # liefert davon "core". Der Schreibschutz des Kernverzeichnisses haette dann auf ein
213
+ # Verzeichnis `core/` in der Projektwurzel gezeigt, das es nicht gibt - eine Schranke,
214
+ # die nichts mehr schuetzt und dabei aussieht wie eine (D-299).
215
+ #
216
+ # Die Lage steht deshalb als Zeichenkette hier. Der Hook importiert bewusst nichts aus
217
+ # dem Kern: Er muss auch dann entscheiden, wenn eine Installation unvollstaendig ist.
218
+ # Pruefung 75 haelt diesen Wert gegen clientmap.CORE_REL und den KERN des Validators.
219
+ CORE_REL = ".koolie/core"
220
+ CORE_TIEFE = CORE_REL.count("/") + 1
221
+
222
+ # Die Projektwurzel aus demselben Ort: <WURZEL>/<CORE_DIR>/tests/scripts/dieses_skript.
223
+ # Die Zahl der Ebenen wird aus CORE_TIEFE gerechnet und NICHT im Quelltext gezaehlt:
224
+ # Bis 0.87.0 standen hier vier dirname-Aufrufe, und mit dem zweiten Segment des Kerns
225
+ # waeren es fuenf - vier haetten `.koolie/` als Projektwurzel geliefert, also die obere
226
+ # Freigabegrenze der Pfadaufloesung um eine Ebene zu tief gesetzt (D-299).
227
+ # Sie ist die obere Freigabegrenze der Pfadaufloesung und die Basis fuer relative Pfade,
228
+ # wenn das Ereignis kein cwd fuehrt. Bewusst aus dem eigenen Ort und NICHT aus einer
229
+ # Umgebungsvariablen: Ob ein Client <CLIENT>_PROJECT_DIR an den Hook-Prozess
230
+ # weiterreicht, ist dieselbe unbelegte Clientzusage, an der AP2-CC-13 haengen blieb
231
+ # (D-31). realpath, damit eine Installation hinter einer Verknuepfung vergleichbar bleibt.
232
+ _KERN = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
233
+ _wurzel = _KERN
234
+ for _ in range(CORE_TIEFE):
235
+ _wurzel = os.path.dirname(_wurzel)
236
+ PROJEKTWURZEL = os.path.realpath(_wurzel)
237
+
238
+ # Werkzeugnamen je Verb. Der Hook darf sie nicht festschreiben: Das Manifest jedes
239
+ # Client Packs bildet die Verben der Kernquelle auf die Werkzeugnamen seines Clients ab
240
+ # (hook_tools), und diese Abbildung galt bisher nur fuer den Matcher der
241
+ # Hook-Konfiguration - nicht fuer die Pruefungen in diesem Skript. Folge: Bei einem
242
+ # Client, dessen Ausfuehrungswerkzeug nicht "exec" heisst, lief die Pfadpruefung fuer
243
+ # Shell-Befehle ins Leere (AP2-CC-16). Die Namen werden deshalb aus den Manifesten
244
+ # gelesen, wie Pruefung 14 die Clientnamen aus den Pack-Kennungen liest (D-28).
245
+ #
246
+ # Gelesen werden ALLE Packs, nicht nur das installierte: Das Skript wird von allen
247
+ # geteilt, und ein zusaetzlich erkannter Werkzeugname ist eine Verschaerfung, keine
248
+ # Lockerung - dieselbe Begruendung wie bei den Pfadmustern oben.
249
+ BASIS_WRITE_TOOLS = ("edit", "write", "notebookedit")
250
+ BASIS_EXEC_TOOLS = ("exec",)
251
+ # Lesende Werkzeuge werden allein an den Secret-Pfaden gemessen. D-30 hatte das bereits
252
+ # entschieden - "Secret-Pfade sind vertraulich und werden auch gegen lesende Werkzeuge
253
+ # durchgesetzt" -, der Code loeste es nicht ein: Ein Leseverb stand weder in der
254
+ # Hook-Quelle noch in einer Werkzeugliste, und ein Zugriff mit tool_name "read" lief
255
+ # mit Exit 0 durch. Beobachtet in AP2, als ein blockiertes "cat .env" den Agenten dazu
256
+ # brachte, dieselbe Datei mit dem Lesewerkzeug zu oeffnen (AP2-DD-11, D-33).
257
+ BASIS_READ_TOOLS = ("read",)
258
+ # Suchende Werkzeuge werden wie lesende an den Secret-Pfaden gemessen. Sie standen bis
259
+ # 0.29.0 in keiner Liste und in keinem hook_tools-Eintrag: Eine Suche ueber einen
260
+ # Secret-Pfad erreichte den Hook nicht einmal. Gemessen am 2026-09-12 (B04,
261
+ # tests/protocols/2026-09-12-B04-B05-gegenpruefung.md, Lauf B04-5); dieselbe Luecke wie
262
+ # AP2-DD-11, ein Werkzeug weiter, und derselbe Anspruch aus D-30.
263
+ #
264
+ # Eine Berechtigungsregel traegt hier nicht: Bei claude-code werten die Suchwerkzeuge
265
+ # keine Pfadregeln aus (AP2-CC-02), eine solche Regel waere angenommen und nie
266
+ # konsultiert. Der Hook ist fuer diesen Kanal die einzige technische Schranke
267
+ # (CR-2026-047 E3, D-47).
268
+ BASIS_SEARCH_TOOLS = ("search",)
269
+ # MCP-Werkzeuge haben keine festen Namen: Der Server waehlt sie, der Client stellt ein
270
+ # Praefix voran (claude-code: mcp__<server>__<werkzeug>). Erkannt wird deshalb am
271
+ # Praefix, das jedes Pack in hook_mcp_prefixes fuehrt (K-184, D-486). Die Basisliste ist
272
+ # leer: Ein Praefix, das kein Pack gemessen hat, waere eine Zusage ohne Mechanismus.
273
+ BASIS_MCP_PREFIXES = ()
274
+
275
+ # Die Felder von tool_input, die einen Pfad tragen. Sie entscheiden, welcher Wert
276
+ # zusaetzlich AUFGELOEST und dann noch einmal gegen die Muster gehalten wird - das ist
277
+ # die Pfadidentitaet aus D-63. Wie die Werkzeugnamen kommen sie aus den Manifesten
278
+ # (hook_path_fields), ueber dieser Basisliste und ueber alle Packs vereinigt.
279
+ #
280
+ # Die Basisliste muss gut sein: Ein Pack ohne den Eintrag faellt auf sie zurueck. Sie
281
+ # fuehrt deshalb die Feldnamen beider aufgezeichneter Schemata und die gaengigen Formen
282
+ # daneben. Ein zusaetzlich erkanntes Feld ist eine Verschaerfung.
283
+ BASIS_PATH_FIELDS = ("file_path", "filepath", "path", "paths", "file_paths",
284
+ "notebook_path", "target_file", "old_path", "new_path",
285
+ "source", "destination", "directory", "dir")
286
+
287
+
288
+ def _aus_manifesten() -> tuple:
289
+ """(write-, exec-, read-, search-Namen, Pfadfelder) aus den Manifesten aller Packs.
290
+
291
+ Faellt auf die Basisnamen zurueck, wenn kein Manifest lesbar ist: Ein Hook, der
292
+ wegen einer fehlenden Datei gar nichts mehr blockiert, waere die schlechtere Lage.
293
+ """
294
+ schreiben = set(BASIS_WRITE_TOOLS)
295
+ ausfuehren = set(BASIS_EXEC_TOOLS)
296
+ lesen = set(BASIS_READ_TOOLS)
297
+ suchen = set(BASIS_SEARCH_TOOLS)
298
+ pfadfelder = set(BASIS_PATH_FIELDS)
299
+ praefixe = set(BASIS_MCP_PREFIXES)
300
+
301
+ def fertig():
302
+ return (tuple(sorted(schreiben)), tuple(sorted(ausfuehren)),
303
+ tuple(sorted(lesen)), tuple(sorted(suchen)), frozenset(pfadfelder),
304
+ tuple(sorted(praefixe)))
305
+
306
+ kern = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
307
+ packs = os.path.join(kern, "clients")
308
+ try:
309
+ eintraege = sorted(os.listdir(packs))
310
+ except OSError:
311
+ return fertig()
312
+ for name in eintraege:
313
+ pfad = os.path.join(packs, name, "manifest.json")
314
+ if not os.path.isfile(pfad):
315
+ continue
316
+ try:
317
+ with io.open(pfad, encoding="utf-8") as fh:
318
+ daten = json.load(fh) or {}
319
+ except (OSError, ValueError):
320
+ continue
321
+ abbildung = daten.get("hook_tools") or {}
322
+ for verb, ziel in (("write", schreiben), ("exec", ausfuehren),
323
+ ("read", lesen), ("search", suchen)):
324
+ for werkzeug in abbildung.get(verb) or []:
325
+ if isinstance(werkzeug, str) and werkzeug.strip():
326
+ ziel.add(werkzeug.strip().lower())
327
+ for feld in daten.get("hook_path_fields") or []:
328
+ if isinstance(feld, str) and feld.strip():
329
+ pfadfelder.add(feld.strip().lower())
330
+ for praefix in daten.get("hook_mcp_prefixes") or []:
331
+ if isinstance(praefix, str) and praefix.strip():
332
+ praefixe.add(praefix.strip().lower())
333
+ return fertig()
334
+
335
+
336
+ (WRITE_TOOLS, EXEC_TOOLS, READ_TOOLS, SEARCH_TOOLS, PATH_FIELDS,
337
+ MCP_PREFIXES) = _aus_manifesten()
338
+
339
+ # Ein Shell-Befehl ist keine Pfadangabe: In "cat .env" steht der Pfad mitten im String,
340
+ # und die Pfadmuster verlangen einen Zeilenanfang oder ein Trennzeichen davor. Fuer
341
+ # ausfuehrende Werkzeuge wird die Eingabe deshalb zusaetzlich in Tokens zerlegt und
342
+ # jedes Token wie eine Pfadangabe geprueft (AP2-CC-15).
343
+ SHELL_TRENNER = re.compile(r"[\s;|&()<>\"'`,=]+")
344
+
345
+ # Wie viele Tokens eines Befehls hoechstens AUFGELOEST werden. Die Musterpruefung selbst
346
+ # ist unbegrenzt; gedeckelt ist nur der Teil, der das Dateisystem anfasst. Ohne Deckel
347
+ # koennte eine sehr lange Befehlszeile den Hook in den Zeitablauf laufen lassen - und ein
348
+ # Hook, der in den Zeitablauf laeuft, entscheidet nichts (AP2-CC-13).
349
+ TOKEN_DECKEL = 64
350
+
351
+ # Der Kernpfad hat seit 0.88.0 MEHRERE Segmente. re.escape() laesst den Schraegstrich
352
+ # stehen, und ein so gebautes Muster traefe unter Windows die Schreibweise mit
353
+ # Gegenschraegstrich nicht mehr - eine Schranke, an der man vorbeigeht, indem man
354
+ # anders tippt (D-277, die Lehre von B3). Die Segmente werden deshalb einzeln
355
+ # maskiert und mit einem Trenner verbunden, der beide Formen nimmt.
356
+ _CORE_MUSTER = r"[\\/]".join(re.escape(_s) for _s in CORE_REL.split("/"))
357
+
358
+ PROTECTED_WRITE_PATH_PATTERNS = [
359
+ re.compile(_GRENZE + _CORE_MUSTER + r"[\\/]", re.I),
360
+ ]
361
+
362
+ # DAS MANDAT (CR-2026-156, D-447). Ein Mandat ist die Erlaubnis eines Menschen, dass der
363
+ # Agent fuer eine begrenzte Zeit Entscheidungen in das Overlay eintraegt (Modus M6). Es
364
+ # liegt im Git-Verzeichnis des Projekts und NICHT im Arbeitsbaum: Es kann so nie
365
+ # versehentlich eingecheckt werden und damit auf einem anderen Arbeitsplatz gelten.
366
+ # Ohne Git-Verzeichnis gibt es kein Mandat - der Merge Request ist die Pruefung, die das
367
+ # Mandat voraussetzt. Die Werte stehen hier und in mandat.py; Pruefung 99 haelt sie
368
+ # gleich (der Hook importiert bewusst nichts aus dem Kern, siehe CORE_REL).
369
+ MANDAT_DATEI = "koolie-mandat.json"
370
+ MANDAT_HOECHSTDAUER_MIN = 480
371
+ MANDAT_UMFAENGE = {
372
+ "overlay": ".koolie/project-overlay/",
373
+ "dokumente": ".koolie/project-overlay/documents/",
374
+ }
375
+ # Datei und Befehl des Mandats sind fuer jede NICHT lesende Operation gesperrt - auch
376
+ # fuer einen Shell-Befehl, der sie nur nennt. Ein Agent, der sich sein Mandat selbst
377
+ # gibt, haette keines. Die Grenze ist dieselbe wie bei K-32: Ein Befehl, der den Namen
378
+ # verschleiert, entgeht dem Muster; getragen wird der Rest von der Regelschicht.
379
+ MANDATS_MUSTER = [
380
+ re.compile(r"koolie-mandat", re.I),
381
+ re.compile(r"koolie-modus", re.I),
382
+ re.compile(r"(^|[\s\\/])mandat\.py", re.I),
383
+ ]
384
+
385
+ # DIE MODUSBINDUNG (CR-2026-164, D-501, K-179). Dieselbe Bauform wie das Mandat, fuer die
386
+ # Modi, deren Schreibgrenze sich ohne Overlay-Pfadliste festmachen laesst: M1 schreibt
387
+ # nichts, M2 nur in die Plan-Ablage, die der Mensch beim Binden nennt. Der Mensch bindet
388
+ # im eigenen Terminal ('mandat.py modus'); ohne Bindung aendert sich nichts. Die Bindung
389
+ # trifft Schreibwerkzeuge - ein Shell-Befehl, der schreibt, entgeht ihr wie dem
390
+ # Kernschutz (D-30, K-32). Diese Grenze ist benannt, nicht verschwiegen.
391
+ #
392
+ # M3 BIS M5 (CR-2026-169, K-201). Ihre Schreibgrenze steht im Overlay: M3 <ALLOWED_PATHS>,
393
+ # M4 <TEST_PATHS>, M5 <DOC_PATHS>. Der Hook liest das Overlay trotzdem nicht - 'mandat.py
394
+ # modus' kopiert die Globs beim Binden in die Bindungsdatei ("pfade", bei M3 optional
395
+ # "umfang"), dazu <READ_ONLY_PATHS> ("nur_lesen"). Ein Ziel ist frei, wenn JEDE Lesart im
396
+ # Projekt liegt, auf ein Muster von "pfade" (und "umfang") passt und auf keines von
397
+ # "nur_lesen". Aendert sich das Overlay, gilt das erst nach neuem Binden.
398
+ MODUS_DATEI = "koolie-modus.json"
399
+ MODUS_GEBUNDEN = ("M1", "M2", "M3", "M4", "M5")
400
+ MODUS_MIT_PFADEN = ("M3", "M4", "M5")
401
+
402
+ # DIE UEBERTRAGUNG VON SECRETS NACH AUSSEN (CR-2026-164, D-503, K-94). Der eingebaute
403
+ # Skill 'upload-secrets' von devin-desktop liest eine Secret-Datei nicht mit einem
404
+ # Dateiwerkzeug: Er ruft die CLI des Clients, und die liest die Werte selbst und schickt
405
+ # sie an den Secrets-Speicher des Anbieters (erhoben am 2026-09-30 mit 'devin skills show
406
+ # upload-secrets', 3000.11.3). Ein Leseverbot der Berechtigungsschicht trifft diesen Weg
407
+ # nicht, und mit '--from-env <VAR>' steht nicht einmal ein Pfad im Befehl. Gesperrt wird
408
+ # deshalb der Befehl selbst - fuer jedes ausfuehrende Werkzeug, auch als Probelauf.
409
+ UEBERTRAGUNGS_MUSTER = [
410
+ re.compile(r"\bcloud\s+drs\s+secret-create\b", re.I),
411
+ ]
412
+ MANDAT_BEFEHL = ("python " + CORE_REL + "/mandat.py erteilen --rolle <Rolle> "
413
+ "--umfang overlay --minuten 60")
414
+
415
+
416
+ # Die eine Ausnahme: die Auskunft. 'mandat.py status' schreibt nichts, und der Client soll
417
+ # vor einer Arbeit in M6 selbst nachsehen koennen, statt es auf eine Sperre ankommen zu
418
+ # lassen. Genau diese Form und nichts dahinter - kein Semikolon, keine Verkettung.
419
+ MANDATSAUSKUNFT = re.compile(r"^\s*(python3?|py(\s+-3)?)\s+(\.[\\/])?\.koolie[\\/]core[\\/]mandat\.py\s+status\s*$", re.I)
420
+
421
+
422
+ def nur_mandatsauskunft(tool_input) -> bool:
423
+ """Ist der Befehl allein 'mandat.py status'?
424
+
425
+ Gemessen wird das Befehlsfeld, nicht jede Zeichenkette der Eingabe: claude-code
426
+ schickt neben dem Befehl eine Beschreibung ("Mandatsstatus abfragen"), und die erste
427
+ Fassung verlangte, dass JEDE Zeichenkette mit "mandat" genau der Befehl sei - sie
428
+ sperrte damit die Auskunft, die sie freigeben sollte (gemessen am 2026-09-27 im Lauf
429
+ sk013n01 der Messung zu 1.17.0). Die uebrigen Felder bleiben Pruefmaterial: Nennt die
430
+ Beschreibung mandat.py, sperrt das Muster darunter wie bisher.
431
+ """
432
+ befehl = tool_input.get("command") if isinstance(tool_input, dict) else None
433
+ return isinstance(befehl, str) and bool(MANDATSAUSKUNFT.match(befehl))
434
+
435
+
436
+ def git_verzeichnis():
437
+ """Das Git-Verzeichnis des Projekts - oder None.
438
+
439
+ '.git' ist ein Verzeichnis oder, bei einem zusaetzlichen Arbeitsbaum, eine Datei
440
+ mit 'gitdir: <pfad>'. Der Hook ruft git bewusst nicht auf: Ein Prozessstart je
441
+ Werkzeugaufruf kostet Zeit, und ein Hook im Zeitablauf entscheidet nichts.
442
+ """
443
+ punkt_git = os.path.join(PROJEKTWURZEL, ".git")
444
+ if os.path.isdir(punkt_git):
445
+ return punkt_git
446
+ if os.path.isfile(punkt_git):
447
+ try:
448
+ with io.open(punkt_git, encoding="utf-8") as fh:
449
+ inhalt = fh.read().strip()
450
+ except OSError:
451
+ return None
452
+ if inhalt.lower().startswith("gitdir:"):
453
+ ziel = inhalt[len("gitdir:"):].strip()
454
+ if not os.path.isabs(ziel):
455
+ ziel = os.path.join(PROJEKTWURZEL, ziel)
456
+ return os.path.realpath(ziel)
457
+ return None
458
+
459
+
460
+ def mandat_lesen():
461
+ """Das gueltige Mandat als dict - oder None.
462
+
463
+ Gueltig heisst: lesbar, eine Rolle, nur bekannte Umfaenge, ein Ende in der Zukunft
464
+ und nicht weiter entfernt als die Hoechstdauer, und dieses Projekt. Jede Abweichung
465
+ gilt als KEIN Mandat - ein kaputtes Mandat oeffnet nichts.
466
+ """
467
+ gd = git_verzeichnis()
468
+ if not gd:
469
+ return None
470
+ try:
471
+ with io.open(os.path.join(gd, MANDAT_DATEI), encoding="utf-8") as fh:
472
+ daten = json.load(fh)
473
+ except (OSError, ValueError):
474
+ return None
475
+ if not isinstance(daten, dict):
476
+ return None
477
+ rolle, umfang, bis = daten.get("rolle"), daten.get("umfang"), daten.get("bis")
478
+ if not isinstance(rolle, str) or not rolle.strip():
479
+ return None
480
+ if (not isinstance(umfang, list) or not umfang
481
+ or not all(isinstance(u, str) and u in MANDAT_UMFAENGE for u in umfang)):
482
+ return None
483
+ try:
484
+ ende = datetime.datetime.strptime(str(bis), "%Y-%m-%dT%H:%M:%SZ")
485
+ except ValueError:
486
+ return None
487
+ jetzt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
488
+ if ende <= jetzt or ende - jetzt > datetime.timedelta(minutes=MANDAT_HOECHSTDAUER_MIN + 1):
489
+ return None
490
+ projekt = daten.get("projekt")
491
+ if (not isinstance(projekt, str)
492
+ or os.path.normcase(os.path.realpath(projekt)) != os.path.normcase(PROJEKTWURZEL)):
493
+ return None
494
+ return daten
495
+
496
+
497
+ def mandat_deckt(rel: str, mandat) -> bool:
498
+ """Deckt das Mandat den projektrelativen Pfad rel?"""
499
+ if not mandat:
500
+ return False
501
+ rel = rel.replace("\\", "/")
502
+ return any(rel.lower().startswith(MANDAT_UMFAENGE[u].lower()) for u in mandat["umfang"])
503
+
504
+
505
+ def modus_lesen():
506
+ """Die gueltige Modusbindung als dict - oder None.
507
+
508
+ Dieselben Bedingungen wie beim Mandat: lesbar, bekannter Modus, ein Ende in der
509
+ Zukunft und nicht weiter als die Hoechstdauer, dieses Projekt. M2 braucht zusaetzlich
510
+ eine projektrelative Plan-Ablage ausserhalb von .koolie/. Eine kaputte Bindung gilt
511
+ als KEINE - sie sperrt dann nichts, und die Regelschicht traegt den Modus wie vor
512
+ 1.20.2. Das ist die schwaechere Seite, und sie ist gewaehlt: Eine Bindung ist eine
513
+ zusaetzliche Sperre, kein Schutz, von dem etwas anderes abhaengt.
514
+ """
515
+ gd = git_verzeichnis()
516
+ if not gd:
517
+ return None
518
+ try:
519
+ with io.open(os.path.join(gd, MODUS_DATEI), encoding="utf-8") as fh:
520
+ daten = json.load(fh)
521
+ except (OSError, ValueError):
522
+ return None
523
+ if not isinstance(daten, dict) or daten.get("modus") not in MODUS_GEBUNDEN:
524
+ return None
525
+ try:
526
+ ende = datetime.datetime.strptime(str(daten.get("bis")), "%Y-%m-%dT%H:%M:%SZ")
527
+ except ValueError:
528
+ return None
529
+ jetzt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
530
+ if ende <= jetzt or ende - jetzt > datetime.timedelta(minutes=MANDAT_HOECHSTDAUER_MIN + 1):
531
+ return None
532
+ projekt = daten.get("projekt")
533
+ if (not isinstance(projekt, str)
534
+ or os.path.normcase(os.path.realpath(projekt)) != os.path.normcase(PROJEKTWURZEL)):
535
+ return None
536
+ if daten["modus"] == "M2":
537
+ ablage = daten.get("ablage")
538
+ if not isinstance(ablage, str) or not ablage.strip():
539
+ return None
540
+ ablage = ablage.replace("\\", "/").strip().strip("/") + "/"
541
+ if (os.path.isabs(ablage) or ablage.startswith(("../", "./", ".koolie/"))
542
+ or "/../" in "/" + ablage):
543
+ return None
544
+ daten["ablage"] = ablage
545
+ if daten["modus"] in MODUS_MIT_PFADEN:
546
+ for feld, pflicht in (("pfade", True), ("umfang", False), ("nur_lesen", False)):
547
+ wert = daten.get(feld)
548
+ if wert is None and not pflicht:
549
+ daten[feld] = []
550
+ continue
551
+ if (not isinstance(wert, list) or (pflicht and not wert)
552
+ or not all(isinstance(g, str) and g.strip() for g in wert)):
553
+ return None
554
+ return daten
555
+
556
+
557
+ def glob_muster(glob: str):
558
+ """Ein Glob des Overlays als regulaerer Ausdruck ueber den projektrelativen Pfad.
559
+
560
+ '**' steht fuer beliebig viele Segmente (auch keines), '*' und '?' bleiben in einem
561
+ Segment, ein Muster ohne Joker ist genau diese Datei. Klammern und Mengen kennt das
562
+ Overlay nicht; sie gelten woertlich - ein Muster, das dadurch nichts trifft, sperrt
563
+ mehr, nicht weniger. Ohne Gross- und Kleinschreibung wie jeder Pfadvergleich des
564
+ Hooks (D-492). Dieselbe Funktion steht in mandat.py; Pruefung 99 haelt beide gleich.
565
+ """
566
+ g = glob.replace("\\", "/").strip()
567
+ while g.startswith("./"):
568
+ g = g[2:]
569
+ g = g.lstrip("/")
570
+ raus, i = "", 0
571
+ while i < len(g):
572
+ if g.startswith("**/", i):
573
+ raus, i = raus + "(?:[^/]*/)*", i + 3
574
+ elif g.startswith("**", i):
575
+ raus, i = raus + ".*", i + 2
576
+ elif g[i] == "*":
577
+ raus, i = raus + "[^/]*", i + 1
578
+ elif g[i] == "?":
579
+ raus, i = raus + "[^/]", i + 1
580
+ else:
581
+ raus, i = raus + re.escape(g[i]), i + 1
582
+ return re.compile("^" + raus + "$", re.I)
583
+
584
+
585
+ def glob_trifft(rel: str, globs: list) -> bool:
586
+ """Passt der projektrelative Pfad (mit '/') auf eines der Muster?"""
587
+ return any(glob_muster(g).match(rel) for g in globs)
588
+
589
+
590
+ def modus_erlaubt(rel: str, bindung: dict) -> bool:
591
+ """Darf im gebundenen Modus unter diesem projektrelativen Pfad geschrieben werden?"""
592
+ modus = bindung["modus"]
593
+ if modus == "M1":
594
+ return False
595
+ if modus == "M2":
596
+ return rel.lower().startswith(bindung["ablage"].lower())
597
+ if not glob_trifft(rel, bindung["pfade"]):
598
+ return False
599
+ if bindung["umfang"] and not glob_trifft(rel, bindung["umfang"]):
600
+ return False
601
+ return not glob_trifft(rel, bindung["nur_lesen"])
602
+
603
+
604
+ # Die Dateikoepfe eines Patchtextes (openai-codex, apply_patch). Nur sie sind Ziele;
605
+ # was darunter steht, ist Inhalt.
606
+ PATCH_KOPF = re.compile(r"^\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+?)\s*$", re.M)
607
+
608
+
609
+ def ziele_der_schreiboperation(tool_input) -> list:
610
+ """Die Ziele eines Schreibwerkzeugs: Pfadfelder, ein Patchtext durch seine Koepfe.
611
+
612
+ Ein Pfadfeld mit Zeilenumbruch ist kein Pfad, sondern ein Patchtext. Traegt er
613
+ keinen Kopf, bleibt er als Ganzes stehen - die strengere Lesart (D-449).
614
+ """
615
+ ziele = []
616
+ for wert in pfadwerte(tool_input):
617
+ if "\n" in wert:
618
+ koepfe = PATCH_KOPF.findall(wert)
619
+ ziele.extend(koepfe if koepfe else [wert])
620
+ else:
621
+ ziele.append(wert)
622
+ return ziele
623
+
624
+
625
+ def hinweis(gesperrt: str, warum: str, loesung: str, folge: str) -> str:
626
+ """Der Blockade-Hinweis (D-450): vier Zeilen, damit niemand nachfragen muss."""
627
+ return (f"Gesperrt: {gesperrt}\nWarum: {warum}\nLoesung: {loesung}\n"
628
+ f"Folge: {folge}")
629
+
630
+
631
+ # Zusätzliche projektspezifische Muster können über die Umgebungsvariable
632
+ # FW_HOOK_EXTRA_PATH_PATTERNS (durch ';' getrennte reguläre Ausdrücke) ergänzt werden.
633
+ # Auch sie laufen mit re.I: Ein Projekt meint sie als Verbot, und ein Verbot, das an der
634
+ # Schreibweise scheitert, ist keines (D-63).
635
+ for _nr, extra in enumerate(
636
+ filter(None, os.environ.get("FW_HOOK_EXTRA_PATH_PATTERNS", "").split(";")), 1):
637
+ try:
638
+ PROTECTED_PATH_PATTERNS.append(re.compile(extra, re.I))
639
+ except re.error:
640
+ # Der Wert wird nicht wiedergegeben: Projektspezifische Pfadmuster tragen Projekt-,
641
+ # Kunden- und Hostnamen - dieselbe Datenart wie die Sperrbegriffe (B03, D-39). Die
642
+ # Position macht das Muster in der Umgebungsvariablen auffindbar.
643
+ print(f"[fw-hook] Ungueltiges Zusatzmuster an Position {_nr} ignoriert "
644
+ f"(Wert nicht wiedergegeben).", file=sys.stderr)
645
+
646
+
647
+ def iter_strings(obj):
648
+ if isinstance(obj, str):
649
+ yield obj
650
+ elif isinstance(obj, dict):
651
+ for k, v in obj.items():
652
+ yield from iter_strings(k)
653
+ yield from iter_strings(v)
654
+ elif isinstance(obj, (list, tuple)):
655
+ for v in obj:
656
+ yield from iter_strings(v)
657
+
658
+
659
+ def shell_tokens(strings):
660
+ for s in strings:
661
+ for token in SHELL_TRENNER.split(s):
662
+ if token:
663
+ yield token
664
+
665
+
666
+ def fail_closed() -> bool:
667
+ """Wahr, wenn eine unpruefbare Eingabe blockiert statt durchgelassen wird.
668
+
669
+ Der Schalter steht im Aufrufargument, nicht in der Umgebung. Das Argument ist Teil
670
+ des Kommandos, das die Hook-Konfiguration fuehrt und der Client ohnehin ausfuehrt:
671
+ Laeuft der Hook - was Pruefung 15 an seiner Wirkung belegt -, dann kommt es an. Eine
672
+ Umgebungsvariable haette die Zusage zusaetzlich davon abhaengig gemacht, dass der
673
+ Client sie an den Hook-Prozess weiterreicht; das ist eine Clientzusage, die fuer
674
+ keines der Packs belegt ist, und genau die Art unbelegter Annahme, an der AP2-CC-13
675
+ acht Releases lang hing (D-31).
676
+
677
+ Die Umgebungsvariable bleibt zusaetzlich wirksam, damit eine bestehende Installation
678
+ fail-closed erproben kann, ohne neu installiert zu werden.
679
+ """
680
+ return "--fail-closed" in sys.argv[1:] or os.environ.get("FW_HOOK_FAIL_CLOSED") == "1"
681
+
682
+
683
+ # DIE SPERRFORMEN, UND WARUM ES MEHR ALS EINE GIBT (CR-2026-133, D-347).
684
+ # Eine Sperre ist eine Aussage an den Client, und ihre Form ist clientgebunden. Bis
685
+ # 1.3.0 kannte dieses Skript genau eine: das Objekt {"decision": "block"} auf stdout
686
+ # und Exit-Code 2. Am 2026-09-23 ist an einer realen Installation von openai-codex
687
+ # gemessen worden, was diese Form dort bewirkt: NICHTS. Der Client meldet den Hook als
688
+ # fehlgeschlagen und fuehrt die Operation aus - im Lauf kam der Koederinhalt woertlich
689
+ # heraus. Dieselbe Sperre in der Form, die dieser Client liest, blockiert; und sie
690
+ # blockiert auch in dem Modus, der Rueckfragen und Sandkasten abschaltet.
691
+ # ➡️ Ein Hook, der laeuft, dessen Sperrform der Client aber nicht liest, ist eine
692
+ # Zusage ohne Mechanismus - und nichts meldet es.
693
+ # Welche Form gilt, sagt das Client Pack (hook_block_form im Manifest); die Abbildung
694
+ # haengt sie als --sperrform an das Kommando, aus demselben Grund wie --fail-closed
695
+ # (D-31). Ohne Angabe bleibt es bei der bisherigen Form - ein Pack erbt die neue Form
696
+ # nicht durch Schweigen.
697
+ #
698
+ # DIE DRITTE FORM (CR-2026-150, D-417), gemessen am 2026-09-26 an kiro-cli 2.24.1 in
699
+ # einer interaktiven Sitzung: Der Client wertet bei PreToolUse den Exit-Code 2 als
700
+ # Sperre - aber er uebergibt dem Aufrufer als Grund allein stderr, und ein LEERER Grund
701
+ # laesst die Operation laufen. Mit der Standardform (Grund nur auf stdout) endete der
702
+ # Hook mit Exit 2, und der Koederinhalt kam heraus; derselbe Lauf mit dem Grund auf
703
+ # stderr wurde abgewiesen. Dieselbe Bauform wie D-347: ein Hook, der laeuft und sperrt,
704
+ # und ein Client, der die Sperre nicht liest.
705
+ #
706
+ # DIE VIERTE FORM (CR-2026-155, D-441), gemessen am 2026-09-26 an cursor-agent
707
+ # 2026.09.26: Der Client sperrt bei Exit 2 - aber mit failClosed wertet er einen Hook,
708
+ # der OHNE AUSGABE endet, als fehlgeschlagen und sperrt die Operation. Der saubere
709
+ # Durchlass muss hier also etwas sagen. Er sagt "{}": eine gueltige Antwort ohne
710
+ # Entscheidung. Ein "allow" waere mehr, als der Hook weiss - der Client fuehrt die
711
+ # Antworten aller Hooks zusammen, und eine Freigabe koennte eine Rueckfrage uebergehen,
712
+ # die die Berechtigungsschicht verlangt. Gesperrt wird mit permission deny UND Exit 2.
713
+ SPERRFORMEN = ("decision-block", "hook-specific-output", "stderr-grund", "permission-json")
714
+
715
+
716
+ # DAS ENTSCHEIDUNGSPROTOKOLL (K-192, D-487). Der Zustand der laufenden Entscheidung -
717
+ # gesetzt in main(), sobald das Ereignis gelesen ist; bis dahin ist nichts zu protokollieren
718
+ # ausser dem unpruefbaren Fall, und der steht ohne Werkzeugnamen.
719
+ PROTOKOLL_DATEI = "koolie-hook.jsonl"
720
+ PROTOKOLL_HOECHSTGROESSE = 5 * 1024 * 1024
721
+ OVERLAY_MANIFEST = ".koolie/project-overlay/overlay-manifest.yaml"
722
+ PROTOKOLL_SCHALTER = re.compile(r"^hook_protokoll:\s*[\"']?([A-Za-z]+)[\"']?\s*(#.*)?$",
723
+ re.M)
724
+ _ENTSCHEIDUNG = {"ereignis": False, "werkzeug": "", "verb": "", "probe": False}
725
+
726
+
727
+ def protokoll_an() -> bool:
728
+ """Nur fuer ein Ereignis eines Clients, und nur, wenn das Overlay es nicht abschaltet.
729
+
730
+ Ein Aufruf ohne hook_event_name ist eine Sonde des Validators oder ein Handaufruf -
731
+ er gehoert nicht in das Protokoll des Projekts (und der Validator schriebe sonst bei
732
+ jedem Lauf in das Git-Verzeichnis, das er prueft).
733
+ """
734
+ if not _ENTSCHEIDUNG["ereignis"]:
735
+ return False
736
+ try:
737
+ with io.open(os.path.join(PROJEKTWURZEL, OVERLAY_MANIFEST), encoding="utf-8-sig") as fh:
738
+ m = PROTOKOLL_SCHALTER.search(fh.read())
739
+ except OSError:
740
+ m = None
741
+ return not (m and m.group(1).lower() == "aus")
742
+
743
+
744
+ def protokollieren(ergebnis: str, grund: str = "") -> None:
745
+ """Eine Zeile - nie der Inhalt, nie ein Pfadwert. Ein Fehler aendert nichts."""
746
+ try:
747
+ if not protokoll_an():
748
+ return
749
+ gitdir = git_verzeichnis()
750
+ if not gitdir:
751
+ return
752
+ ziel = os.path.join(gitdir, PROTOKOLL_DATEI)
753
+ if os.path.isfile(ziel) and os.path.getsize(ziel) > PROTOKOLL_HOECHSTGROESSE:
754
+ os.replace(ziel, ziel + ".1")
755
+ erste = grund.strip().splitlines()[0] if grund.strip() else ""
756
+ if erste.startswith("Gesperrt: "):
757
+ erste = erste[len("Gesperrt: "):]
758
+ zeile = {"zeit": datetime.datetime.now(datetime.timezone.utc).strftime(
759
+ "%Y-%m-%dT%H:%M:%SZ"),
760
+ "werkzeug": _ENTSCHEIDUNG["werkzeug"], "verb": _ENTSCHEIDUNG["verb"],
761
+ "ergebnis": ergebnis, "grund": erste[:200]}
762
+ if _ENTSCHEIDUNG["probe"]:
763
+ zeile["probe"] = True
764
+ with io.open(ziel, "a", encoding="utf-8", newline="\n") as fh:
765
+ fh.write(json.dumps(zeile, ensure_ascii=False) + "\n")
766
+ except Exception: # noqa: BLE001 - das Protokoll entscheidet nichts
767
+ return
768
+
769
+
770
+ def durchlassen(ergebnis: str = "durchgelassen", grund: str = "") -> None:
771
+ """Sauberer Ausgang: kein Fund. Bei permission-json mit einer leeren Antwort.
772
+
773
+ Auch der unpruefbare Fall ohne --fail-closed endet hier - mit seinem eigenen Ergebnis
774
+ im Protokoll (D-487), aber durch dieselbe Antwort an den Client.
775
+ """
776
+ protokollieren(ergebnis, grund)
777
+ if sperrform() == "permission-json":
778
+ print("{}")
779
+ sys.exit(0)
780
+
781
+
782
+ def sperrform() -> str:
783
+ argv = sys.argv[1:]
784
+ if "--sperrform" in argv:
785
+ i = argv.index("--sperrform")
786
+ if i + 1 < len(argv) and argv[i + 1] in SPERRFORMEN:
787
+ return argv[i + 1]
788
+ return "decision-block"
789
+
790
+
791
+ def block(reason: str) -> None:
792
+ protokollieren("gesperrt", reason)
793
+ form = sperrform()
794
+ if form == "hook-specific-output":
795
+ # Diese Form traegt ihren Grund IM Objekt und endet mit Exit 0: Der Client
796
+ # liest die Entscheidung, nicht den Exit-Code. Ein Exit 2 waere hier ein
797
+ # fehlgeschlagener Hook und damit eine durchgelassene Operation.
798
+ print(json.dumps({"hookSpecificOutput": {
799
+ "hookEventName": "PreToolUse",
800
+ "permissionDecision": "deny",
801
+ "permissionDecisionReason": reason}}, ensure_ascii=False))
802
+ sys.exit(0)
803
+ if form == "permission-json":
804
+ # Beide Signale: die Entscheidung im Objekt und Exit 2. Der Client wertet Exit 2
805
+ # als Sperre; der Grund erreicht Nutzer und Modell ueber die beiden Meldungsfelder.
806
+ print(json.dumps({"permission": "deny", "user_message": reason,
807
+ "agent_message": reason}, ensure_ascii=False))
808
+ sys.exit(2)
809
+ if form == "stderr-grund":
810
+ # Der Grund steht auf stderr und ist NIE leer - ein leerer Grund ist bei diesem
811
+ # Client eine durchgelassene Operation (D-417). stdout bleibt leer.
812
+ sys.stderr.write(reason.strip() or "Framework-Regel: gesperrt.")
813
+ sys.stderr.write("\n")
814
+ sys.exit(2)
815
+ print(json.dumps({"decision": "block", "reason": reason}, ensure_ascii=False))
816
+ sys.exit(2)
817
+
818
+
819
+ class Unpruefbar(Exception):
820
+ """Die Eingabe laesst sich nicht pruefen - der dritte Ausgang neben sauber und Fund.
821
+
822
+ Der Unterschied ist die ganze Sache an D-61: 'Nichts gefunden' und 'nicht gesucht'
823
+ sahen bis 0.33.0 gleich aus (Exit 0), und das war der Weg, auf dem sechs
824
+ Eingabeformen an --fail-closed vorbeikamen.
825
+
826
+ Der Grund nennt eine KATEGORIE, nie einen Wert: Eine unpruefbare Eingabe kann
827
+ Projekt-, Kunden- und Hostnamen tragen, und ein Schutzlauf, der sie ausgibt, ist
828
+ genau der Befund, den D-39 abgestellt hat.
829
+ """
830
+
831
+
832
+ def unpruefbar(grund: str) -> None:
833
+ """fail-closed: blockieren. Sonst: warnen und durchlassen."""
834
+ if fail_closed():
835
+ block(hinweis(
836
+ f"eine Werkzeugeingabe, die der Schutz-Hook nicht pruefen kann ({grund}).",
837
+ "Ungeprueft laesst der Hook nichts durch (fail-closed); moeglicherweise hat "
838
+ "sich das Eingabeschema des Clients geaendert.",
839
+ "Denselben Schritt mit einem anderen Werkzeug versuchen (etwa dem "
840
+ "Dateiwerkzeug statt der Shell); bleibt es gesperrt, die Fundstelle dem "
841
+ f"Framework Owner melden ({CORE_REL}/governance/FEEDBACK_PROCESS.md).",
842
+ "Die Operation laeuft nicht; am Projekt aendert sich nichts."))
843
+ print(f"[fw-hook] Eingabe nicht pruefbar ({grund}); Schema gegen aktuelle "
844
+ f"Clientdokumentation pruefen.", file=sys.stderr)
845
+ durchlassen("unpruefbar-durchgelassen", "Gesperrt: " + grund)
846
+
847
+
848
+ # ---------------------------------------------------------------- Stufe 1: Ereignis
849
+
850
+ def ereignis_lesen(raw: str) -> tuple:
851
+ """(tool_name klein, tool_input) - oder Unpruefbar.
852
+
853
+ Ein Ereignis ist ein JSON-OBJEKT mit einem nicht leeren tool_name vom Typ
854
+ Zeichenkette und einem tool_input, das vorhanden und ein Objekt ist. Zusaetzliche,
855
+ unbekannte Felder bleiben ausdruecklich zulaessig: Eine additive Erweiterung des
856
+ Clients darf den Hook nicht ausfallen lassen (CR-2026-056 E1).
857
+
858
+ Warum tool_input Pflicht ist: Geprueft wird seit D-62 nur noch die Operation. Fehlt
859
+ das Feld, in dem sie steht, kann der Hook nicht wissen, ob die Eingabe harmlos ist
860
+ oder ihre Pfade woanders fuehrt. Genau das ist ein unpruefbarer Fall.
861
+ """
862
+ if not raw.strip():
863
+ raise Unpruefbar("leere Eingabe")
864
+ try:
865
+ payload = json.loads(raw)
866
+ except json.JSONDecodeError:
867
+ raise Unpruefbar("nicht als JSON lesbar")
868
+ if not isinstance(payload, dict):
869
+ raise Unpruefbar(f"JSON-Wurzel ist kein Objekt, sondern {type(payload).__name__}")
870
+ tool_name = payload.get("tool_name")
871
+ if not isinstance(tool_name, str) or not tool_name.strip():
872
+ raise Unpruefbar("tool_name fehlt, ist leer oder keine Zeichenkette")
873
+ if "tool_input" not in payload:
874
+ raise Unpruefbar("tool_input fehlt")
875
+ tool_input = payload.get("tool_input")
876
+ if not isinstance(tool_input, dict):
877
+ raise Unpruefbar(f"tool_input ist kein Objekt, sondern "
878
+ f"{type(tool_input).__name__}")
879
+ basis = payload.get("cwd")
880
+ if not isinstance(basis, str) or not basis.strip():
881
+ basis = PROJEKTWURZEL
882
+ _ENTSCHEIDUNG["ereignis"] = isinstance(payload.get("hook_event_name"), str)
883
+ _ENTSCHEIDUNG["probe"] = payload.get("koolie_probe") is True
884
+ _ENTSCHEIDUNG["werkzeug"] = tool_name.strip().lower()[:120]
885
+ return tool_name.strip().lower(), tool_input, basis
886
+
887
+
888
+ # --------------------------------------------------------------- Stufe 2: Operation
889
+
890
+ def verb_von(tool_name: str) -> str:
891
+ """Das Verb der Operation - oder 'unbekannt'.
892
+
893
+ 'unbekannt' wird nach der STRENGSTEN Liste gemessen, einschliesslich
894
+ Kernverzeichnis. Bis 0.33.0 uebersprang ein unbekannter Name die Pfadpruefung ganz,
895
+ und der Fall ohne Namen bekam zwar die mittlere Liste, aber nicht die strengste -
896
+ obwohl der Kommentar daneben genau das behauptete.
897
+
898
+ Blockiert wird ein unbekannter Name NICHT (CR-2026-056 E2). Das haenge den Hook
899
+ daran, dass der Client seinen Matcher einhaelt; der Matcher wird zwar aus hook_tools
900
+ erzeugt, aber dass ein Client ihn befolgt, ist eine Clientzusage - dieselbe Art
901
+ unbelegter Annahme wie bei AP2-CC-13 (D-31).
902
+ """
903
+ if tool_name in WRITE_TOOLS:
904
+ return "write"
905
+ if tool_name in EXEC_TOOLS:
906
+ return "exec"
907
+ if tool_name in READ_TOOLS:
908
+ return "read"
909
+ if tool_name in SEARCH_TOOLS:
910
+ return "search"
911
+ if any(tool_name.startswith(p) for p in MCP_PREFIXES):
912
+ return "mcp"
913
+ return "unbekannt"
914
+
915
+
916
+ def pfadwerte(obj) -> list:
917
+ """Die Werte aus tool_input, deren Feldname einen Pfad ankuendigt - rekursiv."""
918
+ treffer = []
919
+ if isinstance(obj, dict):
920
+ for k, v in obj.items():
921
+ if isinstance(k, str) and k.strip().lower() in PATH_FIELDS:
922
+ treffer.extend(s for s in iter_strings(v) if s.strip())
923
+ else:
924
+ treffer.extend(pfadwerte(v))
925
+ elif isinstance(obj, (list, tuple)):
926
+ for v in obj:
927
+ treffer.extend(pfadwerte(v))
928
+ return treffer
929
+
930
+
931
+ def sieht_nach_pfad_aus(token: str) -> bool:
932
+ r"""Grobfilter fuer Befehlstokens - er entscheidet nur, was AUFGELOEST wird.
933
+
934
+ Die Musterpruefung sieht jedes Token; hier geht es allein darum, welche davon das
935
+ Dateisystem anfassen. Ausgenommen bleiben Netz- und Geraeteformen (\\\\server\\share,
936
+ //host/pfad, schema://...): Eine Aufloesung darauf kann in eine Netzabfrage laufen,
937
+ und ein Hook, der in den Zeitablauf laeuft, entscheidet nichts (AP2-CC-13). Ihr
938
+ Rohtext wird weiterhin gegen die Muster gehalten.
939
+ """
940
+ if token.startswith("\\\\") or token.startswith("//") or "://" in token:
941
+ return False
942
+ return ("/" in token or "\\" in token or token.startswith(".")
943
+ or bool(re.match(r"^[A-Za-z]:", token)))
944
+
945
+
946
+ def aufloesen(wert: str, basis: str) -> str:
947
+ r"""Der reale Pfad hinter einer Angabe - oder Unpruefbar.
948
+
949
+ Gemessen (tests/protocols/2026-09-13-B06-gegenpruefung.md, Abschnitt 4):
950
+ os.path.realpath fuehrt Grossschreibung, den 8.3-Kurznamen, eine Junction,
951
+ Punktsegmente, einen Punkt oder ein Leerzeichen am Ende und den Datenstrom-Zusatz
952
+ ::$DATA auf die Standardschreibweise zurueck - auch fuer ein nicht existierendes
953
+ Ziel ueber seinen existierenden Elternpfad. Jede dieser Varianten bezeichnete
954
+ dieselbe Datei (mit os.path.samefile vorgeprueft) und wurde bis 0.33.0 anders
955
+ entschieden.
956
+
957
+ NICHT aufgeloest wird der Geraetepfad-Praefix: \\?\ ueberlebt realpath und wird
958
+ deshalb vorher entfernt; der Geraetenamensraum \\.\ bezeichnet keine Projektdatei
959
+ und gilt als unpruefbar (CR-2026-056 E5).
960
+ """
961
+ w = wert.strip()
962
+ if not w:
963
+ raise Unpruefbar("leerer Pfadwert")
964
+ if w.startswith("\\\\.\\") or w.startswith("//./"):
965
+ raise Unpruefbar("Geraetepfad im \\\\.\\-Namensraum")
966
+ if w[:8].upper() == "\\\\?\\UNC\\":
967
+ w = "\\\\" + w[8:]
968
+ elif w.startswith("\\\\?\\"):
969
+ w = w[4:]
970
+ try:
971
+ if not os.path.isabs(w):
972
+ w = os.path.join(basis, w)
973
+ return os.path.realpath(w)
974
+ except (OSError, ValueError):
975
+ raise Unpruefbar("Pfadwert nicht aufloesbar")
976
+
977
+
978
+ # Die POSIX-Schreibweise eines Laufwerks: /c/... (MSYS, Git Bash). Nur unter Windows ein
979
+ # Doppelsinn - unter POSIX ist /c/ ein gewoehnlicher Pfad.
980
+ MSYS_LAUFWERK_RE = re.compile(r"^/([A-Za-z])(?=/|$)")
981
+
982
+
983
+ def lesarten(wert: str, basis: str) -> list:
984
+ r"""Alle realen Pfade, die eine Angabe bezeichnen kann - die strengere gewinnt.
985
+
986
+ ZWEI LESARTEN VON /c/... (CR-2026-163, D-491, K-96). Python unter Windows liest
987
+ /c/x als C:\c\x, und so liest es auch devin-desktop (D-285). Eine Shell unter MSYS
988
+ und ein Client, der ihre Ausgabe weiterreicht, meinen C:\x. Gemessen am 2026-09-30:
989
+ Ein Schreibwerkzeug auf /c/<projekt>/KOOLIE~1/core/VERSION ging mit Exit 0 durch,
990
+ dieselbe Angabe in Windows-Form sperrte. Der Pfad C:\c\... existiert nicht, realpath
991
+ loeste den Kurznamen deshalb nicht auf, und der Pfad galt als ausserhalb des
992
+ Projekts. Der Ort ist die Aufloesung (D-63), nicht die Musterliste: Welche Lesart der
993
+ Client waehlt, weiss der Hook nicht - er misst beide.
994
+
995
+ GRENZE, BENANNT: /cygdrive/c/ und /mnt/c/ sind nicht gemessen und bleiben bei der
996
+ einen Lesart.
997
+ """
998
+ echt = aufloesen(wert, basis)
999
+ m = MSYS_LAUFWERK_RE.match(wert.strip()) if os.name == "nt" else None
1000
+ if not m:
1001
+ return [echt]
1002
+ msys = aufloesen(m.group(1) + ":" + (wert.strip()[2:] or "/"), basis)
1003
+ return [msys] if os.path.normcase(msys) == os.path.normcase(echt) else [msys, echt]
1004
+
1005
+
1006
+ def innerhalb(pfad: str, wurzel: str) -> bool:
1007
+ """Ist pfad ein Kind von wurzel? Kein Praefixvergleich.
1008
+
1009
+ Ein Verzeichnis mit aehnlichem Namensanfang ist kein Kind des erlaubten
1010
+ Verzeichnisses - deshalb commonpath und nicht startswith.
1011
+ """
1012
+ try:
1013
+ return os.path.commonpath([os.path.normcase(pfad),
1014
+ os.path.normcase(wurzel)]) == os.path.normcase(wurzel)
1015
+ except ValueError:
1016
+ return False # verschiedene Laufwerke
1017
+
1018
+
1019
+ def aufgeloestes_material(werte, basis: str) -> tuple:
1020
+ """(Material fuer alle Muster, Material nur fuer die Secret-Muster).
1021
+
1022
+ Ein aufgeloester Pfad INNERHALB der Projektwurzel wird projektrelativ gemessen -
1023
+ in der Form also, fuer die die Muster geschrieben sind. Ein Pfad AUSSERHALB wird
1024
+ absolut gemessen und nur gegen die Secret-Muster: Ein Secret bleibt ausserhalb ein
1025
+ Secret, ein '.koolie/project-overlay/' ausserhalb der Projektwurzel ist nicht unseres.
1026
+ """
1027
+ voll, nur_secret = [], []
1028
+ for nr, wert in enumerate(werte, 1):
1029
+ try:
1030
+ alle = lesarten(wert, basis)
1031
+ except Unpruefbar as fehler:
1032
+ raise Unpruefbar(f"{fehler.args[0]} an Position {nr} der Pfadfelder")
1033
+ for echt in alle:
1034
+ if innerhalb(echt, PROJEKTWURZEL):
1035
+ rel = os.path.relpath(echt, PROJEKTWURZEL)
1036
+ voll.append(rel)
1037
+ else:
1038
+ nur_secret.append(echt)
1039
+ return voll, nur_secret
1040
+
1041
+
1042
+ def eingabe_lesen() -> str:
1043
+ """Die Hook-Eingabe als Text - UTF-8, JEDES vorangestellte BOM entfernt.
1044
+
1045
+ WARUM NICHT sys.stdin.read() (CR-2026-155, D-441). Gemessen am 2026-09-26 an
1046
+ cursor-agent 2026.09.26 unter Windows: Der Client reicht die Eingabe durch eine
1047
+ PowerShell-Huelle weiter, und die stellt ihr ein UTF-8-BOM voran. sys.stdin.read()
1048
+ dekodiert unter Windows ausserdem mit der Codepage des Systems - aus dem BOM wurden
1049
+ drei Zeichen vor der oeffnenden Klammer, json.loads scheiterte, und der Hook sperrte
1050
+ fail-closed JEDE Operation, auch das Lesen einer harmlosen Datei. JSON ist UTF-8; die
1051
+ Codepage ist nur der Rueckfall fuer eine Eingabe, die kein gueltiges UTF-8 ist.
1052
+
1053
+ ZWEI BOM (CR-2026-157, D-463). Gemessen am 2026-09-28 an derselben Clientversion: Die
1054
+ Eingabe kam mit ZWEI vorangestellten BOM an ('EF BB BF EF BB BF'). 'utf-8-sig' nimmt
1055
+ genau eines weg; das zweite blieb als Zeichen vor der Klammer, und der Hook sperrte
1056
+ wieder fail-closed jede Operation. Ein BOM ist an dieser Stelle nie Inhalt - weg mit
1057
+ allen.
1058
+ """
1059
+ puffer = getattr(sys.stdin, "buffer", None)
1060
+ if puffer is None:
1061
+ return sys.stdin.read().lstrip("")
1062
+ roh = puffer.read()
1063
+ try:
1064
+ return roh.decode("utf-8-sig").lstrip("")
1065
+ except UnicodeDecodeError:
1066
+ return roh.decode(sys.getfilesystemencoding() or "utf-8", errors="replace").lstrip("")
1067
+
1068
+
1069
+ # ------------------------------------------------------------------ Stufe 3: Regeln
1070
+
1071
+ def main() -> None:
1072
+ try:
1073
+ tool_name, tool_input, basis = ereignis_lesen(eingabe_lesen())
1074
+ except Unpruefbar as fehler:
1075
+ unpruefbar(fehler.args[0])
1076
+ return
1077
+
1078
+ verb = verb_von(tool_name)
1079
+ _ENTSCHEIDUNG["verb"] = verb
1080
+ # 'unbekannt' zaehlt bei jeder Frage zur strengeren Seite: schreibend wie ein
1081
+ # Schreibwerkzeug, tokenisiert wie ein Befehl. Eine unbekannte Operation als lesend
1082
+ # zu behandeln, waere die Annahme zugunsten des Zugriffs.
1083
+ schreibend = verb in ("write", "unbekannt")
1084
+ tokenisieren = verb in ("exec", "unbekannt")
1085
+
1086
+ # Geprueft wird die OPERATION, nicht der Umschlag (D-62). cwd ist Aufloesungsbasis
1087
+ # und niemals Pruefmaterial: Bis 0.33.0 lief transcript_path als Pruefmaterial mit
1088
+ # und blockierte bei claude-code jeden Schreibzugriff.
1089
+ strings = list(iter_strings(tool_input))
1090
+
1091
+ for label, pattern in SECRET_PATTERNS:
1092
+ if any(pattern.search(s) for s in strings):
1093
+ block(hinweis(
1094
+ f"eine Werkzeugeingabe mit einem Muster der Kategorie '{label}'.",
1095
+ "Secrets duerfen nicht verarbeitet werden "
1096
+ f"({CORE_REL}/framework/core/02-privacy.md, Abschnitt 5).",
1097
+ "Den Wert entfernen oder durch einen Platzhalter ersetzen. Ist es ein "
1098
+ "echtes Secret: Sitzung anhalten und die Fundstelle der Sicherheitsrolle "
1099
+ "des Projekts melden.",
1100
+ "Die Operation laeuft nicht; ohne das Muster geht sie durch."))
1101
+
1102
+ # Das Mandat gibt sich der Agent nicht selbst (D-447): Datei und Befehl sind fuer
1103
+ # jede nicht lesende Operation gesperrt, gemessen am Rohtext UND an den Tokens.
1104
+ auskunft = verb == "exec" and nur_mandatsauskunft(tool_input)
1105
+ if verb not in ("read", "search"):
1106
+ # Bei einem MCP-Werkzeug zaehlen nur die Pfadfelder: Eine Doku-Seite, die den
1107
+ # Mandatsbefehl beschreibt, erteilt keines; ein Dateisystem-Server, der die
1108
+ # Mandatsdatei schreibt, nennt sie in einem Pfadfeld (D-486).
1109
+ mandatsmaterial = (pfadwerte(tool_input) if verb == "mcp" else
1110
+ strings + (list(shell_tokens(strings)) if tokenisieren else []))
1111
+ for s in mandatsmaterial:
1112
+ if auskunft and s in (tool_input["command"],) + tuple(
1113
+ shell_tokens([tool_input["command"]])):
1114
+ continue
1115
+ if any(p.search(s) for p in MANDATS_MUSTER):
1116
+ block(hinweis(
1117
+ "das Anlegen, Aendern oder Aufrufen eines Mandats "
1118
+ f"({CORE_REL}/mandat.py, {MANDAT_DATEI}).",
1119
+ "Ein Mandat erteilt nur der Mensch - ein Agent, der es sich selbst "
1120
+ "gibt, haette keines (Modus M6).",
1121
+ f"Die Person fuehrt im eigenen Terminal aus: {MANDAT_BEFEHL}",
1122
+ "Danach darf der Agent im Umfang des Mandats in das Overlay "
1123
+ "schreiben; 'mandat.py status' zeigt es, 'mandat.py beenden' hebt "
1124
+ "es auf."))
1125
+
1126
+ if tokenisieren and any(p.search(s) for p in UEBERTRAGUNGS_MUSTER for s in strings):
1127
+ block(hinweis(
1128
+ "das Hochladen von Secrets zu einem Dienst ausserhalb des Projekts "
1129
+ "(cloud drs secret-create).",
1130
+ "Secrets verlassen das Projekt nie ueber den KI-Client - auch nicht verschluesselt "
1131
+ f"und auch nicht als Probelauf ({CORE_REL}/framework/core/02-privacy.md, K3).",
1132
+ "Die Person laedt Secrets selbst hoch, im eigenen Terminal und nach den Regeln "
1133
+ "der Organisation.",
1134
+ "Die Operation laeuft nicht; nichts wird uebertragen."))
1135
+
1136
+ # Das Material der Pfadpruefung. Ein Schreibwerkzeug wird an seinen ZIELEN gemessen,
1137
+ # nicht an seinem Inhalt (D-449); ein unbekanntes Werkzeug an allem.
1138
+ if verb == "write":
1139
+ ziele = ziele_der_schreiboperation(tool_input)
1140
+ if not ziele:
1141
+ unpruefbar("Schreibwerkzeug ohne erkanntes Pfadfeld")
1142
+ return
1143
+ zu_pruefen = list(ziele)
1144
+ elif verb == "mcp":
1145
+ # Nur die Pfadfelder, nicht der Inhalt: Eine Notiz, die '.env' als Wort nennt,
1146
+ # liest keine Datei (D-486). Die Secret-Muster fuer den INHALT liefen oben.
1147
+ ziele = []
1148
+ zu_pruefen = pfadwerte(tool_input)
1149
+ else:
1150
+ ziele = []
1151
+ zu_pruefen = list(strings)
1152
+ if tokenisieren:
1153
+ # Bei einem ausfuehrenden Werkzeug steht der Pfad mitten im Befehl; die
1154
+ # Pfadmuster verlangen davor einen Zeilenanfang oder ein Trennzeichen. Die
1155
+ # Eingabe wird deshalb zusaetzlich tokenisiert (AP2-CC-15).
1156
+ tokens = list(shell_tokens(strings))
1157
+ zu_pruefen += tokens
1158
+ kandidaten = [t for t in tokens if sieht_nach_pfad_aus(t)][:TOKEN_DECKEL]
1159
+ else:
1160
+ kandidaten = []
1161
+
1162
+ kandidaten += ziele if verb == "write" else pfadwerte(tool_input)
1163
+
1164
+ try:
1165
+ aufgeloest_voll, aufgeloest_secret = aufgeloestes_material(kandidaten, basis)
1166
+ except Unpruefbar as fehler:
1167
+ unpruefbar(fehler.args[0])
1168
+ return
1169
+
1170
+ # Zwei Schutzziele, zwei Listen (D-30): Ein ausfuehrendes, lesendes oder suchendes
1171
+ # Werkzeug wird an den Secret-Pfaden gemessen, nicht an den Strukturpfaden - ein
1172
+ # Befehl darf den Kern lesen, ein Secret nie. Fuer schreibende und unbekannte
1173
+ # Operationen gelten beide Listen. Ohne die Trennung blockierte ein 'git diff' auf
1174
+ # einen Kernpfad oder das Lesen einer Regeldatei, also Operationen, die das
1175
+ # Framework voraussetzt.
1176
+ muster = PROTECTED_PATH_PATTERNS if schreibend else SECRET_PATH_PATTERNS
1177
+
1178
+ # Die Ziele, die ein gueltiges Mandat deckt - nur fuer ein ERKANNTES
1179
+ # Schreibwerkzeug. Gedeckt ist ein Ziel, dessen aufgeloester Pfad im Umfang liegt;
1180
+ # aufgehoben wird fuer dieses Ziel allein das Overlay-Muster (D-447).
1181
+ mandat = mandat_lesen() if verb == "write" else None
1182
+ gedeckt = set()
1183
+ if mandat:
1184
+ for roh in ziele:
1185
+ try:
1186
+ alle = lesarten(roh, basis)
1187
+ except Unpruefbar:
1188
+ continue
1189
+ for echt in alle:
1190
+ if innerhalb(echt, PROJEKTWURZEL):
1191
+ rel = os.path.relpath(echt, PROJEKTWURZEL)
1192
+ if mandat_deckt(rel, mandat):
1193
+ gedeckt.update((roh, rel))
1194
+
1195
+ for s in zu_pruefen + aufgeloest_voll:
1196
+ for pattern in muster:
1197
+ if pattern is OVERLAY_MUSTER and s in gedeckt:
1198
+ continue
1199
+ if not pattern.search(s):
1200
+ continue
1201
+ if pattern is OVERLAY_MUSTER:
1202
+ deckung = (" Das bestehende Mandat deckt dieses Ziel nicht (Umfang: %s)."
1203
+ % ", ".join(mandat["umfang"])) if mandat else ""
1204
+ block(hinweis(
1205
+ "Schreiben in .koolie/project-overlay/.",
1206
+ "Overlay-Aenderungen entscheidet der Mensch; der Agent traegt sie "
1207
+ "nur mit Mandat ein (Modus M6, V10)." + deckung,
1208
+ f"Die Person fuehrt im eigenen Terminal aus: {MANDAT_BEFEHL} "
1209
+ "(nur die Projektdokumente: --umfang dokumente).",
1210
+ "Fuer die Dauer des Mandats schreibt der Agent dort direkt; jede "
1211
+ "Aenderung steht im Diff und wird im Merge Request geprueft. Das "
1212
+ "Mandat endet von selbst oder mit 'mandat.py beenden'."))
1213
+ if pattern in SECRET_PATH_PATTERNS:
1214
+ block(hinweis(
1215
+ "ein Zugriff auf einen Secret-Pfad.",
1216
+ "Secrets duerfen nicht verarbeitet werden "
1217
+ f"({CORE_REL}/framework/core/02-privacy.md, Abschnitt 5).",
1218
+ "Mit einer Beispieldatei ohne echte Werte arbeiten (etwa .env.example "
1219
+ "mit Platzhaltern) oder die Aufgabe ohne diese Datei loesen.",
1220
+ "Die Operation laeuft nicht; die Datei bleibt unberuehrt."))
1221
+ block(hinweis(
1222
+ "Schreiben auf einen geschuetzten Pfad (Wurzel-Anweisungsdatei, "
1223
+ "Laufzeitschicht oder framework/core/).",
1224
+ "Diese Dateien erzeugt der Installer aus Kern und Overlay; eine Aenderung "
1225
+ "von Hand ginge beim naechsten Update verloren.",
1226
+ "Projektwerte im Overlay eintragen (Modus M6) und danach im eigenen "
1227
+ f"Terminal 'python {CORE_REL}/install.py --update --target .' ausfuehren; "
1228
+ "eine Regelaenderung als Rueckmeldung an den Framework Owner "
1229
+ f"({CORE_REL}/governance/FEEDBACK_PROCESS.md).",
1230
+ "Die Operation laeuft nicht; die Laufzeitschicht bleibt die erzeugte."))
1231
+ for s in aufgeloest_secret:
1232
+ for pattern in SECRET_PATH_PATTERNS:
1233
+ if pattern.search(s):
1234
+ block(hinweis(
1235
+ "ein Zugriff auf einen Secret-Pfad ausserhalb der Projektwurzel.",
1236
+ "Secrets duerfen nicht verarbeitet werden "
1237
+ f"({CORE_REL}/framework/core/02-privacy.md, Abschnitt 5).",
1238
+ "Die Aufgabe ohne diese Datei loesen; einen benoetigten Wert gibt "
1239
+ "die Person selbst ein.",
1240
+ "Die Operation laeuft nicht; die Datei bleibt unberuehrt."))
1241
+
1242
+ # Schreiboperationen zusaetzlich auf das gesamte Kernverzeichnis blockieren. Das
1243
+ # Kernverzeichnis gehoert dem Framework Owner und wird ausschliesslich ueber ein
1244
+ # Release ausgetauscht - auch die Skripte darin, die genau diese Zusagen durchsetzen.
1245
+ #
1246
+ # 'unbekannt' steht hier mit, und darin liegt die Berichtigung aus B06: Bis 0.33.0
1247
+ # haengte diese Pruefung an 'tool_name in WRITE_TOOLS', waehrend der Kommentar drei
1248
+ # Zeilen darueber die unbekannte Operation zur strengeren erklaerte. Sie war die
1249
+ # einzige, bei der der Kern ungeschuetzt blieb.
1250
+ if schreibend:
1251
+ for s in zu_pruefen + aufgeloest_voll:
1252
+ for pattern in PROTECTED_WRITE_PATH_PATTERNS:
1253
+ if pattern.search(s):
1254
+ block(hinweis(
1255
+ f"Schreiben in das Kernverzeichnis {CORE_REL}/.",
1256
+ "Der Kern gehoert dem Framework Owner und wird nur ueber ein "
1257
+ "Release ausgetauscht; eine lokale Aenderung macht die "
1258
+ "Installation ungleich ihrem Release.",
1259
+ "Den Befund als Rueckmeldung an den Framework Owner geben "
1260
+ f"({CORE_REL}/governance/FEEDBACK_PROCESS.md); projekteigene "
1261
+ "Regeln gehoeren ins Overlay (Modus M6).",
1262
+ f"Die Operation laeuft nicht; 'install.py --check' bleibt gruen."))
1263
+
1264
+ # Die Modusbindung (K-179, D-501) - nach allen Sperren oben, damit deren Grund zuerst
1265
+ # steht. Ein Ziel ausserhalb der Projektwurzel liegt nie in der Plan-Ablage.
1266
+ if schreibend:
1267
+ bindung = modus_lesen()
1268
+ if bindung:
1269
+ modus = bindung["modus"]
1270
+ ablage = bindung.get("ablage")
1271
+ fremd = []
1272
+ if modus == "M1" or verb == "unbekannt":
1273
+ fremd = list(ziele) or ["(Ziel unbekannt)"]
1274
+ else:
1275
+ for roh in ziele:
1276
+ try:
1277
+ alle = lesarten(roh, basis)
1278
+ except Unpruefbar:
1279
+ alle = []
1280
+ rels = [os.path.relpath(e, PROJEKTWURZEL).replace("\\", "/")
1281
+ for e in alle if innerhalb(e, PROJEKTWURZEL)]
1282
+ if (not rels or len(rels) < len(alle)
1283
+ or not all(modus_erlaubt(r, bindung) for r in rels)):
1284
+ fremd.append(roh)
1285
+ if fremd:
1286
+ if modus == "M2":
1287
+ wo, tun = f" ausserhalb der Plan-Ablage {ablage}.", f"Den Plan unter {ablage} ablegen. "
1288
+ elif modus in MODUS_MIT_PFADEN:
1289
+ wo = (" ausserhalb der gebundenen Pfade "
1290
+ + ", ".join(bindung["umfang"] or bindung["pfade"]) + ".")
1291
+ tun = "Nur unter den gebundenen Pfaden schreiben. "
1292
+ else:
1293
+ wo, tun = ".", ""
1294
+ block(hinweis(
1295
+ f"ein Schreibaufruf im gebundenen Modus {modus}" + wo,
1296
+ f"Der Mensch hat {modus} gebunden: M1 schreibt nichts, M2 nur den Plan, "
1297
+ "M3 bis M5 nur in ihre Pfade aus dem Overlay "
1298
+ f"({CORE_REL}/framework/core/05-working-model.md, Abschnitt 2).",
1299
+ tun + "Fuer einen anderen Modus bittet der Agent die Person, im eigenen "
1300
+ f"Terminal auszufuehren: python {CORE_REL}/mandat.py modus aus",
1301
+ f"Die Operation laeuft nicht; die Bindung endet von selbst um "
1302
+ f"{bindung['bis']} UTC."))
1303
+ durchlassen()
1304
+
1305
+
1306
+ if __name__ == "__main__":
1307
+ main()