@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,1358 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ clientmap.py - Semantikabbildung der Berechtigungs- und Hook-Konfiguration.
4
+
5
+ Alle uebrigen Laufzeitartefakte unterscheiden sich zwischen zwei Client Packs nur in
6
+ der Form: ein Frontmatter-Feld heisst anders, eine Werkzeugliste ist kommagetrennt
7
+ statt eingerueckt. Berechtigungen und Hooks sind der Fall darueber hinaus. Hier
8
+ unterscheiden sich die Werkzeugnamen selbst, ein Client trennt Aendern und Anlegen in
9
+ zwei Werkzeuge, und Befehlsverbote greifen bei dem einen woertlich, bei dem anderen
10
+ ueber ein Praefix. Eine reine Formtransformation wuerde das nicht tragen.
11
+
12
+ Die Regelmenge ist Framework-Gut (Ebene 3) und liegt deshalb einmal unter
13
+ framework/runtime/permissions.json und framework/runtime/hooks.json. Wie sie beim
14
+ jeweiligen Client aussieht, steht in clients/<pack>/manifest.json.
15
+
16
+ Weil an dieser Abbildung die Kernzusagen B1 bis B6 haengen, erzwingt dieses Modul drei
17
+ Zusicherungen, statt sie nur zu behaupten:
18
+
19
+ 1. Eine deny- oder ask-Regel, fuer die ein Client kein Werkzeug kennt, ist ein
20
+ Fehler. Sie stillschweigend wegzulassen waere eine Lockerung. Bei allow ist das
21
+ Weglassen zulaessig - es faellt dann auf den strengeren Standard zurueck.
22
+ 2. Die Praefixform eines Befehlsverbots muss ein Praefix seiner woertlichen Form
23
+ sein. Damit ist die Praefixform nachweislich mindestens so breit; die Abweichung
24
+ ist eine Verschaerfung und keine Luecke.
25
+ 3. Bei allow ist jede Verbreiterung unzulaessig: dort muessen beide Formen
26
+ uebereinstimmen.
27
+ 4. Ein Werkzeugverb des Frontmatters, das ein Pack weder abbildet noch ausdruecklich
28
+ als nicht abgebildet deklariert, ist ein Fehler. Bis 0.39.0 wurde es woertlich
29
+ durchgereicht und fiel in permissions.deny lautlos aus (D-78).
30
+
31
+ _core_rules_integrity.deny_must_contain wird aus denselben Quellregeln erzeugt
32
+ (Kennzeichnung "core": true). Der Validator vergleicht die installierte Datei damit -
33
+ eine geloeschte Kernregel faellt dadurch auf, auch wenn das Projekt zugleich die
34
+ Integritaetsliste gekuerzt hat.
35
+
36
+ Seit 0.26.0 traegt die Berechtigungsdatei zusaetzlich die Importsteuerung des Clients
37
+ (D-37): Das Framework importiert keine Regel- und Skillquellen fremder Werkzeugformate.
38
+ Das Manifest fuehrt sie unter "import_control" als Schluessel und Wert; kennt ein Client
39
+ keinen solchen Mechanismus, fehlt das Feld und es bleibt bei der Auskunft im Client Pack.
40
+ Die Masznahme ist ein Standard, keine Schranke - die Benutzerkonfiguration hat Vorrang
41
+ (K-27 gemessen 2026-09-11). Pruefung 22 haelt die Abbildung fest, nicht ihre Wirkung.
42
+ """
43
+ from __future__ import annotations
44
+
45
+ import json
46
+ import os
47
+ import subprocess
48
+
49
+
50
+ class AbbildungsFehler(ValueError):
51
+ """Die Quellregeln lassen sich nicht verlustfrei auf diesen Client abbilden."""
52
+
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Platzhalter
56
+ # ---------------------------------------------------------------------------
57
+
58
+ def resolve_placeholders(text: str, man: dict) -> str:
59
+ """Loest die Laufzeit-Platzhalter des Kerns in die Pfade dieses Client Packs auf.
60
+
61
+ Projektplatzhalter (<EXCLUDED_PATHS>, <TEST_COMMAND>, ...) bleiben unberuehrt - die
62
+ fuellt der Overlay Owner. Hier werden nur die Platzhalter aufgeloest, deren Wert
63
+ vom Client abhaengt und die deshalb im Manifest stehen.
64
+ """
65
+ for platzhalter, pfad in man.get("runtime_placeholders", {}).items():
66
+ text = text.replace(platzhalter, pfad)
67
+ return text
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # DIE LAGE DES KERNS UNTER DER PROJEKTWURZEL - ZWEI SEGMENTE SEIT 0.88.0
72
+ # ---------------------------------------------------------------------------
73
+ # Bis 0.87.0 hiess der Kern `leitwerk-core` und war EIN Verzeichnissegment. Vier
74
+ # Werkzeuge haben <CORE_DIR> deshalb mit os.path.basename() aus dem eigenen Ort
75
+ # gebunden - und genau dort steht in install.py der Satz, das stehe dort, "damit
76
+ # eine spaetere Umbenennung nur eine Stelle beruehrt".
77
+ #
78
+ # Mit `.koolie/core` liefert os.path.basename() den Wert "core". Jede gerendete
79
+ # Regeldatei truege dann einen Pfad, den es nicht gibt - und der Validator saehe
80
+ # es nicht, weil er denselben falschen Wert bindet (D-299).
81
+ #
82
+ # Deshalb steht die Lage hier als ZEICHENKETTE und wird nicht abgeleitet. Wer den
83
+ # Kern woanders hinlegt, bricht ohnehin die Hook-Kommandos und die Pruefungen 45,
84
+ # 59 und 75; ein Wert, der sich still anpasst, verdeckt das nur.
85
+ CORE_REL = ".koolie/core"
86
+ #: Wieviele Verzeichnisebenen zwischen Kern und Projektwurzel liegen.
87
+ CORE_TIEFE = CORE_REL.count("/") + 1
88
+
89
+
90
+ def projektwurzel(kern: str) -> str:
91
+ """Die Projektwurzel aus dem Pfad des Kerns - ueber CORE_TIEFE, nicht ueber
92
+ eine im Quelltext gezaehlte Zahl von dirname-Aufrufen."""
93
+ pfad = os.path.abspath(kern)
94
+ for _ in range(CORE_TIEFE):
95
+ pfad = os.path.dirname(pfad)
96
+ return pfad
97
+
98
+
99
+ def core_dir_name(man: dict) -> str:
100
+ return man.get("runtime_placeholders", {}).get("<CORE_DIR>", CORE_REL)
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # DER LIEFERUMFANG EINER INSTALLATION (D-367, CR-2026-141)
105
+ # ---------------------------------------------------------------------------
106
+ # Ein Projekt waehlt beim Installieren den ganzen Kern ("voll") oder nur das zur
107
+ # Nutzung Noetige ("nutzung"). Die Liste des Noetigen wird NICHT aufgezaehlt - sie
108
+ # fehlte nach dem ersten neuen Traeger (D-366). Aufgezaehlt ist das Gegenteil, und es
109
+ # ist nach seinem ABLAGEORT geschlossen: die Nachweisschicht aus Aenderungsantraegen,
110
+ # Abnahmeprotokollen, Erhebungen und dem Bau des Hauptdokuments. Ein neuer Traeger zur
111
+ # Nutzung ist damit von selbst dabei, ein neuer Nachweis von selbst draussen.
112
+ #
113
+ # ZWEI ABLEITUNGEN SIND GEMESSEN GESCHEITERT (2026-09-25, sechs frische
114
+ # Installationen): Die Lesespur taugt nicht - der Validator liest im Projekt alle 549
115
+ # Kerndateien. Die transitive Verweishuelle taugt nicht - mit Verzeichnisverweisen
116
+ # umfasst sie alles, ohne sie behaelt sie 39 Protokolle und verliert die Skillquellen,
117
+ # die --update braucht. OB die Nutzung ohne die Nachweisschicht auskommt, entscheidet
118
+ # deshalb der Validator an einer reduzierten Installation (Sonde L367).
119
+ #
120
+ # Die Wahl steht im Projekt in LIEFERUMFANG neben VERSION; install.py schreibt sie bei
121
+ # jedem --target. Fehlt die Datei, gilt "voll" - so liegt jede Installation vor 1.8.0.
122
+ LIEFERUMFANG_DATEI = "LIEFERUMFANG"
123
+ LIEFERUMFAENGE = ("voll", "nutzung")
124
+ NACHWEIS_ABLAGEN = (
125
+ "governance/change-requests",
126
+ "tests/protocols",
127
+ "tests/erhebungen",
128
+ "build",
129
+ )
130
+
131
+
132
+ def ist_nachweis(rel: str) -> bool:
133
+ """Liegt der Pfad (relativ zum Kern, mit Schraegstrichen) in der Nachweisschicht?"""
134
+ return any(rel == a or rel.startswith(a + "/") for a in NACHWEIS_ABLAGEN)
135
+
136
+
137
+ def lieferumfang(kern: str) -> str:
138
+ """Der Lieferumfang, den der Kern unter diesem Pfad fuehrt - roh gelesen.
139
+
140
+ Ohne Datei "voll". Einen unbekannten Wert gibt die Funktion unveraendert zurueck;
141
+ ihn zu melden ist Sache des Aufrufers (Pruefung 90, install.py).
142
+ """
143
+ try:
144
+ with open(os.path.join(kern, LIEFERUMFANG_DATEI), encoding="utf-8") as fh:
145
+ return fh.read().strip()
146
+ except OSError:
147
+ return "voll"
148
+
149
+ # ---------------------------------------------------------------------------
150
+ # Werkzeugverben des Frontmatters
151
+ # ---------------------------------------------------------------------------
152
+
153
+ # Das Vokabular, das ein Skill oder ein Agentenprofil des Kerns in seinem Frontmatter
154
+ # schreiben darf - in allowed-tools wie in permissions.deny. Es steht hier und nicht in
155
+ # install.py, weil drei Stellen es brauchen: die Abbildung beim Installieren, die
156
+ # Abbildung von permissions.deny auf die Werkzeugsperre je Skill (D-65) und Pruefung 38.
157
+ #
158
+ # WARUM ES UEBERHAUPT EINES BRAUCHT. Bis 0.39.0 hatte die Quelle kein deklariertes
159
+ # Vokabular, und der unbekannte Fall war an beiden Stellen STILL - gemessen am
160
+ # 2026-09-13 (tests/protocols/2026-09-13-gegenpruefung-werkzeugabbildung.md):
161
+ # * allowed-tools: ein Verb ohne Abbildung wurde WOERTLICH durchgereicht. Aus
162
+ # 'allowed-tools: banane' wurde in der installierten Fassung der Werkzeugname
163
+ # 'banane'; aus einer geleerten Abbildung wurde 'tools: read, grep, glob' im
164
+ # Agentenprofil fw-reviewer - drei Namen, die dieser Client nicht kennt (M16).
165
+ # * permissions.deny: dasselbe Verb fiel lautlos ganz aus. 'deny: glob' erzeugte
166
+ # keine Werkzeugsperre, und der Validator meldete 0 Fehler (M6).
167
+ # Die drei uebrigen Werkzeugabbildungen desselben Manifests - hook_tools,
168
+ # permission_tools und der Hook-Matcher - brechen im selben Fall ab (M17 bis M19).
169
+ # Diese eine war die unbewachte, und sie kommt zweimal vor.
170
+ FRONTMATTER_VERBEN = ("read", "grep", "glob", "edit", "exec")
171
+
172
+ # Die Bruecke zwischen dem Vokabular der Quelle und dem der Durchsetzungsschichten
173
+ # (hook_tools, permission_tools, framework/runtime/*.json). Die beiden Vokabulare sind
174
+ # getrennt gewachsen und nicht deckungsgleich: Die Quelle trennt das Suchen in 'grep'
175
+ # und 'glob', die Durchsetzung fasst beides als 'search'; die Quelle sagt 'edit', die
176
+ # Durchsetzung 'write'.
177
+ #
178
+ # DIE BRUECKE WIRD BEWACHT, NICHT AUFGELOEST (D-78). Die beiden Listen gleichzuziehen
179
+ # hiesse, eine der Seiten zu aendern - und die Vorabfreigabe auf die Sperrliste zu heben
180
+ # waere eine AUSWEITUNG: Aus 'allowed-tools: Edit, Write' wuerde 'Edit, Write,
181
+ # NotebookEdit'. Verlangt wird deshalb allein die RICHTUNG: Die Sperrliste darf fuer
182
+ # kein Verbpaar enger sein als die Vorabfreigabe. Heute ist sie es bei keinem
183
+ # (gemessen, M4); Pruefung 38 haelt es fest.
184
+ VERB_BRUECKE = {"read": "read", "grep": "search", "glob": "search",
185
+ "edit": "write", "exec": "exec"}
186
+
187
+
188
+ def frontmatter_werkzeuge(man: dict, block: str, verb: str) -> list[str]:
189
+ """Die Werkzeugnamen dieses Clients fuer ein Verb aus dem Frontmatter der Quelle.
190
+
191
+ `block` ist "skill_frontmatter" oder "agent_frontmatter" - die beiden Stellen, an
192
+ denen dasselbe Vokabular abgebildet wird.
193
+
194
+ Drei Ausgaenge, und keiner davon ist still:
195
+
196
+ * in tool_names abgebildet -> die Werkzeugnamen dieses Clients;
197
+ * in tool_names_unmapped deklariert -> das Verb unveraendert. Ein Client, dessen
198
+ Frontmatter die Verben selbst als Werkzeugnamen fuehrt - oder fuer den das
199
+ unerhoben ist -, sagt das ausdruecklich. Bauform wie hook_tools_absent nach
200
+ D-47: Eine Abwesenheit wird erklaert, nie aus einer Luecke erraten;
201
+ * weder noch -> AbbildungsFehler.
202
+
203
+ Der dritte Ausgang ist die Aenderung gegenueber 0.39.0. Dort reichte derselbe Fall
204
+ das Verb woertlich durch, und niemand erfuhr davon.
205
+ """
206
+ if verb not in FRONTMATTER_VERBEN:
207
+ raise AbbildungsFehler(
208
+ f"{man.get('client', '?')}: Das Werkzeugverb '{verb}' gehoert nicht zum "
209
+ f"Vokabular des Frontmatters {list(FRONTMATTER_VERBEN)}. Ein Skill oder "
210
+ f"Agentenprofil des Kerns nennt nur diese Verben - alles andere ist ein "
211
+ f"Schreibfehler oder ein Verb der Durchsetzungsschicht, das hier nicht "
212
+ f"hingehoert (D-78)")
213
+ fmt = man.get(block) or {}
214
+ abbildung = fmt.get("tool_names") or {}
215
+ if verb in abbildung:
216
+ return list(abbildung[verb])
217
+ if verb in (fmt.get("tool_names_unmapped") or []):
218
+ return [verb]
219
+ raise AbbildungsFehler(
220
+ f"{man.get('client', '?')}/manifest.json: {block}.tool_names kennt das "
221
+ f"Werkzeugverb '{verb}' nicht. Fuehrt dieser Client die Werkzeugnamen des "
222
+ f"Frontmatters unveraendert - oder sind sie unerhoben -, gehoert das Verb in "
223
+ f"{block}.tool_names_unmapped samt _tool_names_unmapped_note; eine Luecke "
224
+ f"allein ist keine Aussage (D-78, Bauform wie hook_tools_absent nach D-47)")
225
+
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Berechtigungen
229
+ # ---------------------------------------------------------------------------
230
+
231
+ def _werkzeuge(man: dict, verb: str) -> list[str]:
232
+ abbildung = man.get("permission_tools")
233
+ if abbildung is None:
234
+ raise AbbildungsFehler(
235
+ f"{man.get('client', '?')}/manifest.json: Feld permission_tools fehlt")
236
+ if verb not in abbildung:
237
+ raise AbbildungsFehler(
238
+ f"{man.get('client', '?')}/manifest.json: permission_tools kennt das "
239
+ f"Werkzeugverb '{verb}' nicht")
240
+ return list(abbildung[verb])
241
+
242
+
243
+ def _muster(rohmuster: str, man: dict) -> str:
244
+ """Pfadmuster in der Schreibweise dieses Clients.
245
+
246
+ Manche Clients verlangen fuer einen Namen ohne Verzeichnisanteil eine ausdrueckliche
247
+ Wurzelangabe, weil ein blosser Name sonst mehrdeutig ist. Aufgeloeste Muster mit
248
+ Verzeichnisanteil, Globs und offene Projektplatzhalter bleiben unberuehrt.
249
+ """
250
+ muster = resolve_placeholders(rohmuster, man)
251
+ vorsatz = man.get("permission_path_prefix", "")
252
+ if not vorsatz or "/" in muster or muster.startswith(("<", "*")):
253
+ return muster
254
+ return vorsatz + muster
255
+
256
+
257
+ def _befehl(regel: dict, man: dict, korb: str) -> str:
258
+ """Befehlsverbot in der Schreibweise dieses Clients (woertlich oder praefixbasiert)."""
259
+ woertlich = regel["command"]
260
+ praefix = regel.get("prefix", woertlich.strip())
261
+ if not woertlich.strip().startswith(praefix):
262
+ raise AbbildungsFehler(
263
+ f"permissions.json: prefix '{praefix}' ist kein Praefix von command "
264
+ f"'{woertlich.strip()}' - die Praefixform waere nicht nachweislich breiter "
265
+ f"als die woertliche")
266
+ if man.get("permission_exec_match", "literal") != "prefix":
267
+ return resolve_placeholders(woertlich, man)
268
+ if korb == "allow" and praefix != woertlich.strip():
269
+ raise AbbildungsFehler(
270
+ f"permissions.json: allow-Regel '{woertlich.strip()}' hat eine kuerzere "
271
+ f"prefix-Form - bei allow waere das eine Lockerung")
272
+ praefix = resolve_placeholders(praefix, man)
273
+ if praefix.startswith("<"):
274
+ # Offener Projektplatzhalter: Der Overlay Owner traegt den vollstaendigen Befehl
275
+ # ein; ein angehaengtes Praefixzeichen wuerde ihn verfaelschen.
276
+ return praefix
277
+ return praefix + man.get("permission_exec_suffix", ":*")
278
+
279
+
280
+ def _regel_rendern(regel: dict, man: dict, korb: str) -> list[str]:
281
+ verb = regel["tool"]
282
+ ziele = _werkzeuge(man, verb)
283
+ if not ziele:
284
+ if korb in ("deny", "ask"):
285
+ raise AbbildungsFehler(
286
+ f"{man.get('client', '?')}: kein Werkzeug fuer '{verb}', die "
287
+ f"{korb}-Regel {regel} liesse sich nur durch Weglassen abbilden - das "
288
+ f"waere eine Lockerung")
289
+ return []
290
+ ohne_muster = set(man.get("permission_tools_bare", []))
291
+ if verb == "exec":
292
+ argument = _befehl(regel, man, korb)
293
+ elif verb == "skill":
294
+ # Ein Aufrufname, kein Pfad: weder Wurzelpraefix noch Praefixzeichen, und
295
+ # keine Musterausweitung. Gemessen am 2026-09-14 (D-82): Das Argument wird
296
+ # woertlich verglichen - Skill(fw-*) laesst den Aufruf von fw-code-explain
297
+ # NICHT durch. Wer hier ein Muster erzeugte, erzeugte eine Freigabe, die
298
+ # lautlos nichts freigibt - der Befundtyp von D-66, mit umgekehrtem
299
+ # Vorzeichen.
300
+ argument = regel["pattern"]
301
+ else:
302
+ argument = _muster(regel["pattern"], man)
303
+ return [name if name in ohne_muster else f"{name}({argument})" for name in ziele]
304
+
305
+
306
+ def _korb_rendern(quelle: dict, man: dict, korb: str) -> list[str]:
307
+ raus: list[str] = []
308
+ for regel in quelle.get(korb, []):
309
+ for gerendert in _regel_rendern(regel, man, korb):
310
+ if gerendert not in raus:
311
+ raus.append(gerendert)
312
+ return raus
313
+
314
+
315
+ def basket_rules(quelle: dict, man: dict, korb: str) -> list[str]:
316
+ """Die Regeln eines Korbes in der Schreibweise dieses Clients.
317
+
318
+ Dieselbe Bauart wie core_rules, nur ohne die Einschraenkung auf 'core': true. Der
319
+ Validator haelt die installierte Datei damit gegen die Kernquelle (Pruefung 37,
320
+ CR-2026-061, D-77): Was hier erzeugt wird, muss dort stehen; was dort zusaetzlich
321
+ steht, ist nur im deny-Korb zulaessig - dort ist es eine Verschaerfung, in ask und
322
+ allow eine Ausweitung.
323
+
324
+ Oeffentlich und nicht _korb_rendern, weil eine Pruefung, die auf eine private
325
+ Funktion greift, beim naechsten Umbau still ausfaellt.
326
+ """
327
+ return _korb_rendern(quelle, man, korb)
328
+
329
+
330
+ def core_rules(quelle: dict, man: dict) -> list[str]:
331
+ """Die Kernzusagen B1 bis B6 in der Schreibweise dieses Clients."""
332
+ raus: list[str] = []
333
+ for regel in quelle.get("deny", []):
334
+ if not regel.get("core"):
335
+ continue
336
+ for gerendert in _regel_rendern(regel, man, "deny"):
337
+ if gerendert not in raus:
338
+ raus.append(gerendert)
339
+ return raus
340
+
341
+
342
+ def import_control(man: dict) -> tuple[str, object] | None:
343
+ """Importsteuerung dieses Clients als (Schluessel, Wert) - oder None.
344
+
345
+ Ein Client ohne solchen Mechanismus traegt das Feld nicht; dann bleibt es bei der
346
+ Auskunft im Abschnitt "Anweisungs- und Konfigurationsquellen ausserhalb des
347
+ Projekts" seines Client Packs (D-37). Dasselbe Feld liest Pruefung 22.
348
+ """
349
+ steuerung = man.get("import_control")
350
+ if not steuerung:
351
+ return None
352
+ schluessel = steuerung.get("key")
353
+ if not schluessel or "value" not in steuerung:
354
+ raise AbbildungsFehler(
355
+ f"{man.get('client', '?')}/manifest.json: import_control braucht 'key' "
356
+ f"und 'value'")
357
+ return schluessel, steuerung["value"]
358
+
359
+
360
+ def _kommentar(man: dict, mit_hooks: bool) -> str:
361
+ kern = core_dir_name(man)
362
+ pack = man.get("client", "?")
363
+ teile = [
364
+ f"Berechtigungsvorlage des Frameworks (Ebene 3), erzeugt fuer das Client "
365
+ f"Pack {pack}.",
366
+ f"Regelmenge: {kern}/framework/runtime/permissions.json. Abbildung auf die "
367
+ f"Werkzeuge dieses Clients: {kern}/clients/{pack}/manifest.json. Inhaltliche "
368
+ f"Aenderungen gehoeren dorthin und laufen als Aenderungsantrag.",
369
+ "Mechanismus: deny vor ask vor allow.",
370
+ ]
371
+ if mit_hooks:
372
+ teile.append("Dieser Client kennt keine eigene Hook-Datei; die Hooks stehen "
373
+ "deshalb hier unter 'hooks'.")
374
+ teile.append(
375
+ "Platzhalter in spitzen Klammern traegt der Overlay Owner hier ein - das ist der "
376
+ "einzige Teil dieser Datei, der dem Projekt gehoert.")
377
+ teile.append(
378
+ "Die unter _core_rules_integrity aufgefuehrten Regeln duerfen vom Projekt nicht "
379
+ f"entfernt werden; {kern}/tests/scripts/validate-framework.py prueft sie gegen "
380
+ "die Kernquelle.")
381
+ teile.append(
382
+ "Derselbe Lauf haelt alle drei Koerbe gegen die Kernquelle: Was dort erzeugt "
383
+ "wird, muss hier stehen. Eine zusaetzliche Regel ist nur unter 'deny' zulaessig "
384
+ "- dort ist sie eine Verschaerfung; unter 'ask' und 'allow' waere sie eine "
385
+ "Ausweitung und ist ein Fehler. Ein zusaetzlicher freigegebener Befehl gehoert "
386
+ "deshalb nicht hierher, sondern in Abschnitt 6 des Overlays und damit in die "
387
+ "Regelschicht.")
388
+ teile.append(
389
+ "Ein Befehlsschlitz traegt den Befehl, den Abschnitt 5 oder 6 des Overlays fuer "
390
+ "seinen Platzhalter erklaert - ein anderer Befehl an dieser Stelle ist ein "
391
+ "Fehler, auch wenn die Zahl der Zeilen stimmt. Bis 0.43.0 stand der Satz "
392
+ "darueber hier ohne diese Bedingung und versprach zu viel: Ein offener Schlitz "
393
+ "deckte eine hinzugefuegte Zeile (CR-2026-066, D-90). Die Pfadlisten haelt "
394
+ "der Validator unter --strict-overlay gegen das Overlay, in einer Richtung: "
395
+ "Jeder ausgeschlossene Pfad braucht hier eine Lese- und eine Schreibsperre, "
396
+ "jeder nur lesbare eine Schreibsperre (Pruefungen 59 und 89; K-151, D-399).")
397
+ if import_control(man) is not None:
398
+ teile.append(
399
+ "Die Importsteuerung schaltet Regel- und Skillquellen fremder "
400
+ "Werkzeugformate ab (D-37). Das eigene Format der Wurzel-Anweisungsdatei "
401
+ "bleibt eingeschaltet. Sie ist ein Standard, keine Schranke: Die "
402
+ "Benutzerkonfiguration dieser Arbeitsstation hat Vorrang.")
403
+ hinweis = man.get("permissions_note")
404
+ if hinweis:
405
+ teile.append(hinweis)
406
+ teile.append("Exakte Mustersemantik: siehe Zeile B3 der Faehigkeitsmatrix "
407
+ "dieses Client Packs.")
408
+ return " ".join(teile)
409
+
410
+
411
+ def render_permissions(quelltext: str, man: dict, hooks_quelltext: str | None = None) -> str:
412
+ """Berechtigungsdatei dieses Client Packs aus der neutralen Regelmenge.
413
+
414
+ hooks_quelltext wird nur uebergeben, wenn der Client keine eigene Hook-Datei kennt;
415
+ die Hooks werden dann in dieselbe Datei eingebettet.
416
+ """
417
+ quelle = json.loads(quelltext)
418
+ rechte: dict = {}
419
+ rechte.update(man.get("permissions_extra", {}))
420
+ for korb in ("deny", "ask", "allow"):
421
+ rechte[korb] = _korb_rendern(quelle, man, korb)
422
+
423
+ ergebnis: dict = {"_comment": _kommentar(man, hooks_quelltext is not None),
424
+ "permissions": rechte}
425
+ # Zusatzschluessel auf der OBERSTEN Ebene der Einstellungsdatei - nicht innerhalb
426
+ # von permissions. Der Unterschied ist keine Kosmetik: Ein Schluessel auf der
427
+ # falschen Ebene wird von diesem Client stillschweigend nicht gelesen, und nichts
428
+ # meldet es. permissions_extra deckt die eine Ebene, settings_extra die andere
429
+ # (CR-2026-087, D-155). Pruefung 54 haelt beide gegen die erzeugte Datei.
430
+ for schluessel, wert in man.get("settings_extra", {}).items():
431
+ ergebnis[schluessel] = wert
432
+ steuerung = import_control(man)
433
+ if steuerung is not None:
434
+ ergebnis[steuerung[0]] = steuerung[1]
435
+ if hooks_quelltext is not None:
436
+ ergebnis["hooks"] = _hooks_objekt(hooks_quelltext, man)
437
+ ergebnis["_core_rules_integrity"] = {"deny_must_contain": core_rules(quelle, man)}
438
+ return json.dumps(ergebnis, indent=2, ensure_ascii=False) + "\n"
439
+
440
+
441
+ # ---------------------------------------------------------------------------
442
+ # Hooks
443
+ # ---------------------------------------------------------------------------
444
+
445
+ def _hook_matcher(verben: list[str], man: dict) -> str:
446
+ abbildung = man.get("hook_tools")
447
+ if abbildung is None:
448
+ raise AbbildungsFehler(
449
+ f"{man.get('client', '?')}/manifest.json: Feld hook_tools fehlt")
450
+ # Ein Pack darf eine Werkzeugklasse nicht dadurch aus der Durchsetzung nehmen, dass
451
+ # es sie leer laesst - deshalb ist eine leere Liste ein Fehler. Fuehrt der Client
452
+ # aber tatsaechlich kein Werkzeug dieser Art, muss es einen Weg geben, das zu sagen:
453
+ # hook_tools_absent erklaert die Abwesenheit ausdruecklich. Der Unterschied ist der
454
+ # zwischen "nicht abgebildet" und "gibt es nicht", und er muss deklariert werden,
455
+ # nicht aus einer leeren Liste erraten (CR-2026-047 E3, D-47). Pruefung 26 prueft,
456
+ # dass eine so erklaerte Abwesenheit auch in permission_tools steht.
457
+ erklaert_abwesend = set(man.get("hook_tools_absent") or [])
458
+ # SEIT 1.20.0 EIN DRITTER ZUSTAND (K-184, D-486): "unerhoben". Ein Verb, das die
459
+ # Kernquelle neu fuehrt, ist nicht fuer jeden Client gemessen - und ein Matcher auf
460
+ # eine ungemessene Werkzeugform waere eine Zusage ohne Mechanismus (D-78). Das Pack
461
+ # erklaert es ausdruecklich; Pruefung 26 verlangt die Begruendung und haelt die drei
462
+ # Zustaende auseinander.
463
+ erklaert_unerhoben = set(man.get("hook_tools_unerhoben") or [])
464
+ namen: list[str] = []
465
+ for verb in verben:
466
+ if verb in erklaert_unerhoben and not abbildung.get(verb):
467
+ continue
468
+ if verb not in abbildung:
469
+ raise AbbildungsFehler(
470
+ f"{man.get('client', '?')}/manifest.json: hook_tools kennt das "
471
+ f"Werkzeugverb '{verb}' nicht")
472
+ if not abbildung[verb]:
473
+ if verb in erklaert_abwesend:
474
+ continue
475
+ raise AbbildungsFehler(
476
+ f"{man.get('client', '?')}: kein Hook-Werkzeug fuer '{verb}' - der Hook "
477
+ f"wuerde fuer diese Werkzeugklasse nicht ausloesen. Fuehrt der Client "
478
+ f"kein solches Werkzeug, gehoert das Verb in hook_tools_absent")
479
+ for name in abbildung[verb]:
480
+ if name not in namen:
481
+ namen.append(name)
482
+ return man.get("hook_matcher_separator", "|").join(namen)
483
+
484
+
485
+ # Kandidaten in der Reihenfolge, in der sie geprueft werden. "python3" steht vorn,
486
+ # weil es auf POSIX-Systemen der verlaessliche Name ist; "py" ist der Windows-Launcher.
487
+ INTERPRETER_KANDIDATEN = ("python3", "python", "py")
488
+ _SONDE = "KOOLIE-INTERPRETER-OK"
489
+ _interpreter_gemerkt: list[str] = []
490
+
491
+
492
+ def python_interpreter() -> str:
493
+ """Ermittelt einen Interpreternamen, der auf dieser Maschine wirklich Python startet.
494
+
495
+ Nicht "python3" fest verdrahten: Unter Windows ist das haeufig der
496
+ Microsoft-Store-Alias, der nur eine Fehlermeldung ausgibt und mit Exit-Code 49
497
+ endet, ohne dass ein Interpreter startet. Ein Hook, der so aufgerufen wird, laeuft
498
+ nie - und ein Schutz-Hook, der nicht laeuft, blockiert nichts (AP2-CC-13).
499
+
500
+ Geprueft wird deshalb nicht die Anwesenheit des Namens, sondern seine Wirkung:
501
+ Der Kandidat muss eine Sonde ausgeben. Das ist dieselbe Unterscheidung, die D-23
502
+ fuer Pruefungen verlangt - Anwesenheit ist kein Nachweis.
503
+ """
504
+ if _interpreter_gemerkt:
505
+ return _interpreter_gemerkt[0]
506
+ versucht = []
507
+ for kandidat in INTERPRETER_KANDIDATEN:
508
+ try:
509
+ lauf = subprocess.run(
510
+ [kandidat, "-c", "import sys; sys.stdout.write('%s')" % _SONDE],
511
+ capture_output=True, text=True, timeout=15)
512
+ except (OSError, subprocess.SubprocessError) as fehler:
513
+ versucht.append("%s (%s)" % (kandidat, type(fehler).__name__))
514
+ continue
515
+ if lauf.returncode == 0 and _SONDE in (lauf.stdout or ""):
516
+ _interpreter_gemerkt.append(kandidat)
517
+ return kandidat
518
+ versucht.append("%s (Exit %s)" % (kandidat, lauf.returncode))
519
+ raise AbbildungsFehler(
520
+ "Kein funktionsfaehiger Python-Interpreter fuer die Hook-Aufrufe gefunden. "
521
+ "Geprueft: " + ", ".join(versucht) + ". Die Hooks des Frameworks setzen die "
522
+ "Zusagen H2 und die Overlay-Statusmeldung durch; ohne Interpreter liefen sie "
523
+ "nicht, und ein Schutz-Hook, der nicht laeuft, blockiert nichts (AP2-CC-13). "
524
+ "Installation abgebrochen, statt eine Zusage zu erzeugen, die nicht traegt (D-26)")
525
+
526
+
527
+ def _hook_befehl(script: str, man: dict, enforcing: bool = False,
528
+ needs_project_paths: bool = False) -> str:
529
+ """Aufrufkommando eines Hooks fuer diesen Client.
530
+
531
+ Bei einem durchsetzenden Hook (enforcing in der Kernquelle) haengt die Abbildung
532
+ --fail-closed an, sobald das Pack das Eingabeschema seines Clients als bestaetigt
533
+ fuehrt. Der Schalter steht damit im Kommando, das der Client ohnehin ausfuehrt, und
534
+ nicht in einer Umgebungsvariablen: Ob ein Client env an den Hook-Prozess weiterreicht,
535
+ ist fuer keines der Packs belegt, und eine Sperre, die auf einer unbelegten
536
+ Clientzusage steht, ist genau die Lage, aus der AP2-CC-13 kam (D-31).
537
+
538
+ Dieselbe Begruendung traegt needs_project_paths: Ein Hook, der Projektverzeichnis und
539
+ Regelablage braucht, bekommt sie als Argumente aus dieser Abbildung - nicht aus der
540
+ Umgebung und schon gar nicht geraten. Ein Skript des Kerns kennt die Laufzeitschicht
541
+ eines Packs nicht (D-30, fortgeschrieben mit CR-2026-037).
542
+ """
543
+ variable = man.get("hook_project_dir_var")
544
+ ausdruck = man.get("hook_project_dir_expr")
545
+ if not variable and not ausdruck:
546
+ raise AbbildungsFehler(
547
+ f"{man.get('client', '?')}/manifest.json: Feld hook_project_dir_var fehlt")
548
+ # WARUM ES DANEBEN EINEN AUSDRUCK GIBT (CR-2026-133, D-347). Eine Variable ist
549
+ # nur eine der Formen, in denen ein Client dem Hook sein Projektverzeichnis sagt -
550
+ # und bei openai-codex ist es keine: Am 2026-09-23 ist der Hook-Prozess
551
+ # aufgezeichnet worden, und seine Umgebung fuehrt AUSSER CODEX_HOME nichts; sein
552
+ # ARBEITSVERZEICHNIS ist das Projektverzeichnis. Eine Variable, die es nicht gibt,
553
+ # haette das Kommando auf einen Pfad unterhalb der Wurzel zeigen lassen - der Hook
554
+ # waere gestartet worden und haette nichts gefunden.
555
+ wurzel = ausdruck if ausdruck else "$" + variable
556
+ befehl = (python_interpreter() + ' "' + wurzel + '/'
557
+ + core_dir_name(man) + '/' + script + '"')
558
+ if enforcing and man.get("hook_fail_closed") is True:
559
+ befehl += " --fail-closed"
560
+ if enforcing:
561
+ # DIE SPERRFORM GEHOERT IN DAS KOMMANDO, UND DER GRUND IST GEMESSEN
562
+ # (CR-2026-133, D-347). Ein durchsetzender Hook sagt seinem Client, dass er
563
+ # sperrt - und die Form dieser Aussage ist clientgebunden. Am 2026-09-23 an
564
+ # einer realen Installation von openai-codex gemessen: Die bisherige Form
565
+ # ({"decision": "block"} und Exit 2) meldet der Client als FEHLGESCHLAGENEN
566
+ # Hook und FUEHRT DIE OPERATION AUS - im Gegenlauf kam der Koederinhalt
567
+ # woertlich heraus. Dieselbe Sperre in der Form, die dieser Client liest,
568
+ # blockiert - auch im Modus, der Rueckfragen und Sandkasten abschaltet.
569
+ # ➡️ Ein Hook, der laeuft und dessen Sperrform der Client nicht liest, ist
570
+ # eine Zusage ohne Mechanismus, und nichts meldet es.
571
+ # Der Standard bleibt die bisherige Form; ein Pack, das eine andere braucht,
572
+ # SAGT sie. Pruefung 86 verlangt von jedem Pack mit durchsetzendem Hook, dass
573
+ # das Skript die genannte Form kennt.
574
+ form = man.get("hook_block_form")
575
+ if form:
576
+ befehl += " --sperrform " + form
577
+ if needs_project_paths:
578
+ regelablage = man.get("runtime_placeholders", {}).get("<RULES_DIR>")
579
+ if not regelablage:
580
+ raise AbbildungsFehler(
581
+ f"{man.get('client', '?')}/manifest.json: <RULES_DIR> fehlt in "
582
+ f"runtime_placeholders; der meldende Hook braucht die Regelablage")
583
+ befehl += ' "' + wurzel + '" "' + regelablage + '"'
584
+ return befehl
585
+
586
+
587
+ def _hooks_objekt(quelltext: str, man: dict) -> dict:
588
+ quelle = json.loads(quelltext)
589
+ ergebnis: dict = {}
590
+ for ereignis, eintraege in quelle.items():
591
+ if ereignis.startswith("_"):
592
+ continue
593
+ gerendert = []
594
+ for eintrag in eintraege:
595
+ neu: dict = {}
596
+ if "on" in eintrag:
597
+ neu["matcher"] = _hook_matcher(eintrag["on"], man)
598
+ # Zusatzfelder, die dieser Client je Hook-Eintrag verlangt. Bei
599
+ # openai-codex ist das "enabled": true - gemessen am 2026-09-23: ohne
600
+ # dieses Feld laeuft der Eintrag nicht, und der Client meldet es nicht.
601
+ # Ein Pack, das kein solches Feld braucht, fuehrt das Manifestfeld nicht.
602
+ neu.update(man.get("hook_handler_extra") or {})
603
+ # "enforcing" und "needs_project_paths" sind Steuerinformation der
604
+ # Kernquelle und keine Felder des Clients; sie steuern nur, ob das Kommando
605
+ # --fail-closed traegt und ob ihm Projektverzeichnis und Regelablage folgen.
606
+ neu["hooks"] = [
607
+ {"type": h["type"],
608
+ "command": _hook_befehl(h["script"], man,
609
+ h.get("enforcing") is True,
610
+ h.get("needs_project_paths") is True),
611
+ "timeout": h["timeout"]}
612
+ for h in eintrag["hooks"]
613
+ ]
614
+ gerendert.append(neu)
615
+ ergebnis[ereignis] = gerendert
616
+ return ergebnis
617
+
618
+
619
+ def render_hooks(quelltext: str, man: dict) -> str:
620
+ """Eigenstaendige Hook-Datei fuer Clients, die eine kennen.
621
+
622
+ Manche Clients erwarten die Ereignisse unter einem Schluessel der obersten Ebene
623
+ statt unmittelbar in der Datei. Welcher das ist, sagt das Pack in
624
+ hooks_file_wrapper - gemessen, nicht geraten: Bei openai-codex meldet der Client
625
+ eine Datei ohne diesen Schluessel mit *unknown field `PreToolUse`* und LAEDT SIE
626
+ NICHT; er startet trotzdem, und ohne die Meldung im Blick haette man einen
627
+ ausgelieferten Schutz-Hook gehabt, der nie laeuft (D-347).
628
+
629
+ Seit 1.13.0 gibt es eine zweite Gestalt (hooks_format "kiro-v1", D-414): eine
630
+ LISTE von Hooks mit Ausloeser je Eintrag statt eines Objekts je Ereignis.
631
+ """
632
+ if man.get("hooks_format") == "kiro-v1":
633
+ return render_hooks_kiro(quelltext, man)
634
+ if man.get("hooks_format") == "cursor-v1":
635
+ return render_hooks_cursor(quelltext, man)
636
+ objekt = _hooks_objekt(quelltext, man)
637
+ schluessel = man.get("hooks_file_wrapper")
638
+ if schluessel:
639
+ objekt = {schluessel: objekt}
640
+ return json.dumps(objekt, indent=2, ensure_ascii=False) + "\n"
641
+
642
+
643
+ # ---------------------------------------------------------------------------
644
+ # Hilfsfunktionen fuer die Aufrufer
645
+ # ---------------------------------------------------------------------------
646
+
647
+ def hooks_in_permissions(man: dict) -> bool:
648
+ """Wahr, wenn dieser Client keine eigene Hook-Datei kennt.
649
+
650
+ Erkennbar daran, dass Hook- und Berechtigungsdatei auf denselben Pfad zeigen; eine
651
+ zusaetzliche Angabe im Manifest waere eine zweite Wahrheit ueber dieselbe Tatsache.
652
+ """
653
+ p = man.get("runtime_placeholders", {})
654
+ hooks, rechte = p.get("<HOOKS_FILE>"), p.get("<PERMISSIONS_FILE>")
655
+ return hooks is not None and hooks == rechte
656
+
657
+
658
+ def load_source(core_dir: str, name: str) -> str:
659
+ with open(os.path.join(core_dir, "framework", "runtime", name), encoding="utf-8") as fh:
660
+ return fh.read()
661
+
662
+
663
+ # ---------------------------------------------------------------------------
664
+ # Zweite Ausgabeform: Rechteprofil (TOML) und Befehlsregeln (eigene Regelsprache)
665
+ # ---------------------------------------------------------------------------
666
+ #
667
+ # WARUM ES SIE GIBT, UND DER GRUND IST GEMESSEN (CR-2026-133, D-346). Die erste
668
+ # Ausgabeform rendert Koerbe aus Regeln der Gestalt Werkzeug(Muster) in EINE Datei.
669
+ # Der Client openai-codex kennt diese Gestalt nicht. Er bindet Pfade an eine
670
+ # Zugriffsart (read / write / deny) in einem Rechteprofil seiner Konfigurationsdatei,
671
+ # und Befehle an eigene Regeldateien in einer eigenen Sprache. Die Regelmenge des
672
+ # Kerns zerfaellt damit in ZWEI Erzeugnisse, und keines davon ist eine Liste von
673
+ # Werkzeug(Muster)-Zeilen.
674
+ #
675
+ # WAS DABEI AUSFAELLT, FAELLT NICHT LAUTLOS AUS. Gemessen am 2026-09-23
676
+ # (tests/protocols/2026-09-23-bau-openai-codex.md):
677
+ # * Ein Musterausdruck ist als Schluessel nur mit ABSOLUTEM oder ~/-Vorsatz
678
+ # zulaessig - und dann nur fuer die Zugriffsart deny. Ein projektrelativer
679
+ # Schluessel (":workspace/<pfad>") nimmt KEIN Muster. Musterform und
680
+ # Versionierbarkeit schliessen einander damit aus, und B3 faellt an beiden Enden.
681
+ # (Gemessen mit 0.156.1. Die Herstellerdokumentation nennt seit 0.157 Globs
682
+ # unter ":workspace_roots" als deny-Leseregeln - nicht nachgemessen, K-160.)
683
+ # * Seit 0.157 heisst das Sonderziel ":workspace_roots" und traegt seine
684
+ # Unterpfade als eigene Tabelle ("." = der Arbeitsbereich selbst). Die alte
685
+ # Form ":workspace/<pfad>" meldet 0.157 als unbekannt und IGNORIERT sie - mit
686
+ # ihr war der ganze Arbeitsbereich schreibgeschuetzt, gemessen mit
687
+ # `codex sandbox` am 2026-09-26 (K-157, D-412).
688
+ # * Ein deny-Leserecht verlangt ausserdem den erhoehten Windows-Sandkasten; auf
689
+ # einem unerhoehten Arbeitsplatz LAEUFT DER CLIENT DANN NICHT (fail-closed).
690
+ # Diese Ausgabeform rendert den Lesekorb deshalb NICHT und fuehrt seine Regeln in
691
+ # nicht_abgebildete_pfadregeln() auf. Pruefung 87 haelt die Zahl gegen das Pack; das
692
+ # Pack traegt B3 als [NICHT ABBILDBAR] mit Begruendung, Ersatz und Freigabe nach
693
+ # clients/README.md Abschnitt 4. Eine Regel, die den Client am Starten hindert, waere
694
+ # keine Schranke, sondern ein Ausfall.
695
+
696
+
697
+ def _toml_zeichenkette(text: str) -> str:
698
+ return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
699
+
700
+
701
+ def _toml_wert(wert: object) -> str:
702
+ if wert is True:
703
+ return "true"
704
+ if wert is False:
705
+ return "false"
706
+ if isinstance(wert, int):
707
+ return str(wert)
708
+ return _toml_zeichenkette(str(wert))
709
+
710
+
711
+ def _profil_schluessel(rohmuster: str, man: dict) -> str | None:
712
+ """Projektrelativer Schluessel des Rechteprofils - oder None, wenn nicht abbildbar.
713
+
714
+ Abbildbar ist genau, was nach dem Aufloesen der Platzhalter KEIN Muster mehr
715
+ enthaelt: Die Schluesselsyntax dieses Clients nimmt Muster nur mit absolutem oder
716
+ ~/-Vorsatz an, und ein absoluter Pfad in einem versionierten Traeger waere ein Wert
717
+ dieser Arbeitsstation. Ein offener Projektplatzhalter bleibt ebenfalls aus - er
718
+ traegt eine Liste, die der Overlay Owner fuellt, und die Schluesselseite einer
719
+ TOML-Tabelle kennt keinen Schlitz.
720
+ """
721
+ muster = resolve_placeholders(rohmuster, man)
722
+ if muster.startswith("<"):
723
+ return None
724
+ # Ein Teilbaummuster ist die eine Musterform, die dieser Client projektrelativ
725
+ # nimmt - gemessen am 2026-09-23: ":workspace/.koolie/**" wird angenommen und
726
+ # steht danach als Teilbaum ":workspace/.koolie" im wirksamen Profil. Alles
727
+ # andere - ein Namensmuster wie **/*.lock - bleibt aus.
728
+ if muster.endswith("/**"):
729
+ muster = muster[:-3]
730
+ if any(z in muster for z in ("*", "?", "[")):
731
+ return None
732
+ vorsatz = man.get("permission_path_special", ":workspace_roots")
733
+ if muster.startswith("./"):
734
+ muster = muster[2:]
735
+ return vorsatz + "/" + muster
736
+
737
+
738
+ def _profil_eintraege(quelle: dict, man: dict) -> tuple[dict, list[str]]:
739
+ """Die Pfadregeln des Kerns als Eintraege des Rechteprofils.
740
+
741
+ Rueckgabe: (Eintraege, nicht abgebildete Rohregeln). Die zweite Haelfte ist der
742
+ Grund, warum dieser Ausgabeform eine Zeile im Pack gehoert.
743
+ """
744
+ eintraege: dict[str, str] = {}
745
+ offen: list[str] = []
746
+ for schluessel, wert in (man.get("permission_path_base") or {}).items():
747
+ eintraege[schluessel] = wert
748
+ for regel in quelle.get("deny", []):
749
+ verb = regel.get("tool")
750
+ if verb == "write":
751
+ schluessel = _profil_schluessel(regel["pattern"], man)
752
+ if schluessel is None:
753
+ offen.append("write " + regel["pattern"])
754
+ continue
755
+ # Schreibverbot auf einem Teilbaum heisst hier: lesbar, nicht schreibbar.
756
+ eintraege[schluessel] = "read"
757
+ elif verb == "read":
758
+ offen.append("read " + regel["pattern"])
759
+ return eintraege, offen
760
+
761
+
762
+ def nicht_abgebildete_pfadregeln(quelltext: str, man: dict) -> list[str]:
763
+ """Die Pfadregeln, die diese Ausgabeform nicht traegt - oeffentlich fuer Pruefung 87."""
764
+ return _profil_eintraege(json.loads(quelltext), man)[1]
765
+
766
+
767
+ def _toml_kommentar(man: dict) -> list[str]:
768
+ kern = core_dir_name(man)
769
+ pack = man.get("client", "?")
770
+ return [
771
+ "Berechtigungsvorlage des Frameworks (Ebene 3), erzeugt fuer das Client Pack "
772
+ + pack + ".",
773
+ "Regelmenge: " + kern + "/framework/runtime/permissions.json. Abbildung auf die",
774
+ "Form dieses Clients: " + kern + "/clients/" + pack + "/manifest.json.",
775
+ "Inhaltliche Aenderungen gehoeren dorthin und laufen als Aenderungsantrag.",
776
+ "Diese Datei traegt die PFADSEITE. Die Befehlsseite steht in der Regeldatei, die",
777
+ "dasselbe Werkzeug erzeugt; beide zusammen sind die Berechtigungsschicht dieses",
778
+ "Packs.",
779
+ "Was diese Datei NICHT traegt, steht in Zeile B3 der Faehigkeitsmatrix des Packs:",
780
+ "Der Lesekorb des Kerns ist hier nicht abbildbar - ein Muster verlangt einen",
781
+ "absoluten Vorsatz und waere damit ein Wert dieser Arbeitsstation, und ein",
782
+ "deny-Leserecht verlangt den erhoehten Windows-Sandkasten.",
783
+ "Die projektlokale Schicht laedt NUR, wenn dieses Projekt in der Benutzer-",
784
+ "konfiguration des Clients als vertraut eingetragen ist. Ohne diesen Eintrag",
785
+ "traegt diese Datei nichts.",
786
+ "Diese Datei traegt ausschliesslich Schluessel, die der Client kennt: Ein",
787
+ "unbekannter Schluessel wird gemeldet und mit --strict-config zum Fehler. Die",
788
+ "Kernregeln haelt deshalb der Validator gegen die Kernquelle und nicht eine",
789
+ "zweite Liste in dieser Datei.",
790
+ ]
791
+
792
+
793
+ def render_permissions_toml(quelltext: str, man: dict) -> str:
794
+ """Rechteprofil dieses Client Packs aus der neutralen Regelmenge."""
795
+ quelle = json.loads(quelltext)
796
+ profil = man.get("permission_profile_name")
797
+ if not profil:
798
+ raise AbbildungsFehler(
799
+ man.get("client", "?") + "/manifest.json: Feld permission_profile_name "
800
+ "fehlt - ohne Profilnamen gibt es keine Tabelle, in die die Pfadregeln "
801
+ "gehoeren")
802
+ eintraege, _ = _profil_eintraege(quelle, man)
803
+
804
+ zeilen = ["# " + teil for teil in _toml_kommentar(man)]
805
+ zeilen.append("")
806
+ zeilen.append("default_permissions = " + _toml_zeichenkette(profil))
807
+ zeilen.append("")
808
+ zeilen.append("[permissions." + profil + "]")
809
+ beschreibung = man.get("permission_profile_description")
810
+ if beschreibung:
811
+ zeilen.append("description = " + _toml_zeichenkette(beschreibung))
812
+ zeilen.append("")
813
+ zeilen.append("[permissions." + profil + ".filesystem]")
814
+ # Das Sonderziel des Arbeitsbereichs traegt seine Unterpfade als eigene Tabelle:
815
+ # der Arbeitsbereich selbst als ".", jeder Teilbaum relativ dazu (K-157, D-412).
816
+ vorsatz = man.get("permission_path_special", ":workspace_roots")
817
+ unter: dict[str, str] = {}
818
+ for schluessel in sorted(eintraege):
819
+ if schluessel == vorsatz:
820
+ unter["."] = eintraege[schluessel]
821
+ elif schluessel.startswith(vorsatz + "/"):
822
+ unter[schluessel[len(vorsatz) + 1:]] = eintraege[schluessel]
823
+ else:
824
+ zeilen.append(_toml_zeichenkette(schluessel) + " = "
825
+ + _toml_zeichenkette(eintraege[schluessel]))
826
+ if unter:
827
+ zeilen.append("")
828
+ zeilen.append("[permissions." + profil + ".filesystem."
829
+ + _toml_zeichenkette(vorsatz) + "]")
830
+ for schluessel in sorted(unter):
831
+ zeilen.append(_toml_zeichenkette(schluessel) + " = "
832
+ + _toml_zeichenkette(unter[schluessel]))
833
+ netz = man.get("permission_profile_network")
834
+ if netz:
835
+ zeilen.append("")
836
+ zeilen.append("[permissions." + profil + ".network]")
837
+ for schluessel in sorted(netz):
838
+ zeilen.append(schluessel + " = " + _toml_wert(netz[schluessel]))
839
+ for tabelle in sorted(man.get("permissions_toml_extra") or {}):
840
+ inhalt = man["permissions_toml_extra"][tabelle]
841
+ zeilen.append("")
842
+ zeilen.append("[" + tabelle + "]")
843
+ for schluessel in sorted(inhalt):
844
+ zeilen.append(schluessel + " = " + _toml_wert(inhalt[schluessel]))
845
+ return "\n".join(zeilen) + "\n"
846
+
847
+
848
+ def _policy_zeile(regel: dict, man: dict, decision: str) -> str | None:
849
+ """Eine Befehlsregel in der Regelsprache dieses Clients - oder None bei Schlitz."""
850
+ woertlich = regel["command"].strip()
851
+ praefix = regel.get("prefix", woertlich).strip()
852
+ if not woertlich.startswith(praefix):
853
+ raise AbbildungsFehler(
854
+ "permissions.json: prefix '" + praefix + "' ist kein Praefix von command '"
855
+ + woertlich + "' - die Praefixform waere nicht nachweislich breiter als "
856
+ "die woertliche")
857
+ if decision == "allow" and praefix != woertlich:
858
+ raise AbbildungsFehler(
859
+ "permissions.json: allow-Regel '" + woertlich + "' hat eine kuerzere "
860
+ "prefix-Form - bei allow waere das eine Lockerung")
861
+ aufgeloest = resolve_placeholders(praefix, man)
862
+ if aufgeloest.startswith("<"):
863
+ # Offener Projektplatzhalter: Sein Wert gehoert dem Projekt, und diese
864
+ # Regelsprache kennt keinen Schlitz. Der Befehl bleibt aus - das ist bei
865
+ # 'prompt' eine Verschaerfung (er laeuft in die Rueckfrage) und bei 'allow'
866
+ # ebenfalls (er ist nicht vorab freigegeben).
867
+ return None
868
+ tokens = ", ".join(_toml_zeichenkette(t) for t in aufgeloest.split())
869
+ grund = regel.get("justification") or ("Framework-Regel (" + decision + ")")
870
+ return ("prefix_rule(pattern = [" + tokens + "], decision = "
871
+ + _toml_zeichenkette(decision) + ", justification = "
872
+ + _toml_zeichenkette(grund) + ")")
873
+
874
+
875
+ #: Korb der Kernquelle -> Entscheidung der Regelsprache dieses Clients.
876
+ POLICY_ENTSCHEIDUNG = {"deny": "forbidden", "ask": "prompt", "allow": "allow"}
877
+
878
+
879
+ def _policy_kommentar(man: dict) -> list[str]:
880
+ kern = core_dir_name(man)
881
+ pack = man.get("client", "?")
882
+ return [
883
+ "Befehlsregeln des Frameworks (Ebene 3), erzeugt fuer das Client Pack " + pack + ".",
884
+ "Regelmenge: " + kern + "/framework/runtime/permissions.json, Regeln mit tool=exec.",
885
+ "Reihenfolge der Koerbe: deny vor ask vor allow - forbidden vor prompt vor allow.",
886
+ "Die Praefixform ist nachweislich mindestens so breit wie die woertliche Form;",
887
+ "ihre benannte Grenze ist eine andere Schreibweise desselben Befehls.",
888
+ "Ein Befehlsschlitz des Overlays steht NICHT hier: Sein Wert gehoert dem Projekt,",
889
+ "und diese Regelsprache kennt keinen Platzhalter. Sein Ausbleiben ist in beiden",
890
+ "Koerben eine Verschaerfung.",
891
+ ]
892
+
893
+
894
+ def render_exec_policy(quelltext: str, man: dict) -> str:
895
+ """Befehlsregeln dieses Client Packs aus der neutralen Regelmenge.
896
+
897
+ Die Regeldatei liegt projektlokal und ist damit versioniert - das ist die Haelfte
898
+ von B1, die dieser Client traegt. Gemessen am 2026-09-23 an einer realen
899
+ Installation: Ein Befehl, den diese Datei verbietet, wird abgewiesen, und der
900
+ Client nennt dabei die Begruendung dieser Datei woertlich.
901
+ """
902
+ quelle = json.loads(quelltext)
903
+ zeilen = ["# " + teil for teil in _policy_kommentar(man)]
904
+ zeilen.append("")
905
+ for korb in ("deny", "ask", "allow"):
906
+ entscheidung = POLICY_ENTSCHEIDUNG[korb]
907
+ raus: list[str] = []
908
+ for regel in quelle.get(korb, []):
909
+ if regel.get("tool") != "exec":
910
+ continue
911
+ gerendert = _policy_zeile(regel, man, entscheidung)
912
+ if gerendert and gerendert not in raus:
913
+ raus.append(gerendert)
914
+ if not raus:
915
+ continue
916
+ zeilen.append("# --- " + korb + " -> " + entscheidung + " ---")
917
+ zeilen.extend(raus)
918
+ zeilen.append("")
919
+ return "\n".join(zeilen).rstrip("\n") + "\n"
920
+
921
+
922
+ def permissions_format(man: dict) -> str:
923
+ """Ausgabeform der Berechtigungsdatei dieses Packs ('json', 'toml', 'kiro-agent',
924
+ 'cursor-json').
925
+
926
+ Der Standard ist 'json': Zwei der fuenf Packs fuehren ihn, und ein fehlendes Feld
927
+ darf nicht die neue Form bedeuten - ein Pack soll seine Form SAGEN und sie nicht
928
+ durch Schweigen erben.
929
+ """
930
+ return man.get("permissions_format", "json")
931
+
932
+
933
+ # ---------------------------------------------------------------------------
934
+ # Dritte Ausgabeform: das Agentenprofil mit Faehigkeitsregeln (kiro)
935
+ # ---------------------------------------------------------------------------
936
+ #
937
+ # WARUM ES SIE GIBT, UND DER GRUND IST GEMESSEN (CR-2026-150, D-414). Der Client kiro
938
+ # fuehrt seine Berechtigungen als Regeln der Gestalt {capability, match, effect} - und
939
+ # die Datei, die ein Projekt dafuer im Arbeitsbereich halten koennte, liegt NICHT im
940
+ # Repositorium, sondern je Arbeitsplatz unter ~/.kiro/workspace-roots/<hash>/. Der
941
+ # einzige versionierbare Traeger ist das Feld `permissions` eines Agentenprofils unter
942
+ # .kiro/agents/. Gemessen am 2026-09-26 mit kiro-cli 2.24.1, Engine V3:
943
+ # * Ein deny im Agentenprofil weist ab und nennt sich ("Source: agent-profile").
944
+ # * Das Profil wirkt nur, wenn es der AKTIVE Agent ist. Die Arbeitsbereichsdatei
945
+ # .kiro/settings/cli.json waehlt ihn (chat.defaultAgent) und schlaegt eine globale
946
+ # Einstellung; render_client_settings() erzeugt sie.
947
+ # * Fehlt das Profil oder ist es kein gueltiges JSON, faellt der Client STILL auf
948
+ # seinen eingebauten Agenten zurueck - nur eine Warnung auf stderr, und .env war
949
+ # lesbar. Pruefung 96 haelt deshalb Einstellung und Profil gegeneinander.
950
+ # * Eine Regel mit unbekannter Faehigkeit wird uebersprungen, gemeldet nur im
951
+ # Protokoll des Clients. Diese Abbildung erzeugt deshalb nur Faehigkeiten aus
952
+ # KIRO_FAEHIGKEITEN, und Pruefung 96 liest dieselbe Menge.
953
+ # * Befehlsmuster: ohne '*' vergleicht der Client WOERTLICH ("echo" sperrte
954
+ # "echo hallo" nicht). Die Praefixform braucht deshalb das Suffix '*'.
955
+ # * Ein Pfadmuster ohne Wildcard trifft auch Unterverzeichnisse (".env" sperrte
956
+ # "sub/.env") - breiter als woertlich, also keine Lockerung.
957
+
958
+ #: Die Faehigkeiten, die der Client kennt (Herstellerdokumentation, Seite
959
+ #: "Permissions", abgerufen 2026-09-26). Eine Regel ausserhalb dieser Menge wuerde
960
+ #: der Client ueberspringen - gemessen (K23 im Protokoll des Baus).
961
+ KIRO_FAEHIGKEITEN = ("fs_read", "fs_write", "shell", "web_fetch", "web_search", "mcp",
962
+ "subagent", "skill", "power", "context", "diagnostics",
963
+ "sandbox_network", "all", "builtin", "filesystem")
964
+
965
+
966
+ def _kiro_argument(regel: dict, man: dict, korb: str) -> str:
967
+ verb = regel["tool"]
968
+ if verb == "exec":
969
+ return _befehl(regel, man, korb)
970
+ if verb == "skill":
971
+ # Ein Aufrufname, kein Pfad, und woertlich: gemessen am 2026-09-26 - die
972
+ # Freigabe auf einen Namen laesst genau diesen Skill laden, einen anderen nicht.
973
+ return regel["pattern"]
974
+ return _muster(regel["pattern"], man)
975
+
976
+
977
+ def kiro_regeln(quelle: dict, man: dict) -> list[dict]:
978
+ """Die Regelmenge des Kerns als Faehigkeitsregeln dieses Clients.
979
+
980
+ Je Korb und Faehigkeit EINE Regel mit der Liste ihrer Muster, in der Reihenfolge
981
+ der Quelle. Dieselben drei Zusicherungen wie bei den anderen Ausgabeformen: deny
982
+ und ask ohne Zielfaehigkeit brechen ab, die Praefixform ist nachweislich breiter,
983
+ allow wird nie verbreitert (_befehl).
984
+ """
985
+ # AUSNAHMEN INNERHALB EINER REGEL (D-415). Die Kernregel "write <RUNTIME_DIR>/**"
986
+ # sperrt bei diesem Client auch .kiro/specs/ - und dort legt der Client sein
987
+ # Planartefakt ab, das nach D-415 der Traeger des Plans ist. Kein Framework-Artefakt,
988
+ # aber im Schutzbereich einer Kernregel. Ein allow koennte es nicht freigeben
989
+ # (deny gewinnt); das Feld exclude der Regel nimmt den Teilbaum aus, und nur ihn.
990
+ # Das Manifest nennt die Ausnahme je Quellregel ("verb muster"), und nur fuer deny.
991
+ ausnahmen = man.get("permission_rule_exclude") or {}
992
+ regeln: list[dict] = []
993
+ for korb in ("deny", "ask", "allow"):
994
+ gruppen: dict[str, list[str]] = {}
995
+ einzeln: list[dict] = []
996
+ for regel in quelle.get(korb, []):
997
+ schluessel = f"{regel['tool']} {regel.get('pattern', regel.get('command', ''))}"
998
+ if schluessel in ausnahmen and korb != "deny":
999
+ raise AbbildungsFehler(
1000
+ f"{man.get('client', '?')}/manifest.json: permission_rule_exclude nennt "
1001
+ f"die {korb}-Regel '{schluessel}' - eine Ausnahme ist nur an einem "
1002
+ f"Verbot zulaessig; an ask oder allow veraenderte sie die Breite einer "
1003
+ f"Freigabe")
1004
+ ziele = _werkzeuge(man, regel["tool"])
1005
+ if not ziele:
1006
+ if korb in ("deny", "ask"):
1007
+ raise AbbildungsFehler(
1008
+ f"{man.get('client', '?')}: keine Faehigkeit fuer "
1009
+ f"'{regel['tool']}', die {korb}-Regel {regel} liesse sich nur "
1010
+ f"durch Weglassen abbilden - das waere eine Lockerung")
1011
+ continue
1012
+ argument = _kiro_argument(regel, man, korb)
1013
+ for faehigkeit in ziele:
1014
+ if faehigkeit not in KIRO_FAEHIGKEITEN:
1015
+ raise AbbildungsFehler(
1016
+ f"{man.get('client', '?')}/manifest.json: permission_tools nennt "
1017
+ f"die Faehigkeit '{faehigkeit}', die der Client nicht kennt - er "
1018
+ f"uebersprange die Regel und meldete es nur im Protokoll (D-414)")
1019
+ if schluessel in ausnahmen:
1020
+ einzeln.append({"capability": faehigkeit, "match": [argument],
1021
+ "exclude": list(ausnahmen[schluessel]),
1022
+ "effect": korb})
1023
+ continue
1024
+ liste = gruppen.setdefault(faehigkeit, [])
1025
+ if argument not in liste:
1026
+ liste.append(argument)
1027
+ for faehigkeit, muster in gruppen.items():
1028
+ regeln.append({"capability": faehigkeit, "match": muster, "effect": korb})
1029
+ regeln.extend(einzeln)
1030
+ return regeln
1031
+
1032
+
1033
+ def kiro_kernregeln(quelle: dict, man: dict) -> list[str]:
1034
+ """Die Kernzusagen als 'faehigkeit muster' - fuer _core_rules_integrity."""
1035
+ raus: list[str] = []
1036
+ for regel in quelle.get("deny", []):
1037
+ if not regel.get("core"):
1038
+ continue
1039
+ argument = _kiro_argument(regel, man, "deny")
1040
+ for faehigkeit in _werkzeuge(man, regel["tool"]):
1041
+ eintrag = faehigkeit + " " + argument
1042
+ if eintrag not in raus:
1043
+ raus.append(eintrag)
1044
+ return raus
1045
+
1046
+
1047
+ def render_permissions_kiro(quelltext: str, man: dict) -> str:
1048
+ """Agentenprofil dieses Client Packs aus der neutralen Regelmenge.
1049
+
1050
+ Unbekannte Schluessel (_comment, _core_rules_integrity) nimmt der Client an -
1051
+ gemessen am 2026-09-26: Mit beiden im Profil blieb es der aktive Agent, und sein
1052
+ deny wies ab (K21 im Protokoll des Baus).
1053
+ """
1054
+ quelle = json.loads(quelltext)
1055
+ name = man.get("permission_profile_name")
1056
+ if not name:
1057
+ raise AbbildungsFehler(
1058
+ man.get("client", "?") + "/manifest.json: Feld permission_profile_name "
1059
+ "fehlt - ohne Namen gibt es kein Agentenprofil, das die Einstellungsdatei "
1060
+ "waehlen koennte")
1061
+ kern = core_dir_name(man)
1062
+ pack = man.get("client", "?")
1063
+ kommentar = " ".join([
1064
+ f"Agentenprofil des Frameworks (Ebene 3), erzeugt fuer das Client Pack {pack}.",
1065
+ f"Regelmenge: {kern}/framework/runtime/permissions.json. Abbildung: "
1066
+ f"{kern}/clients/{pack}/manifest.json. Inhaltliche Aenderungen gehoeren dorthin "
1067
+ f"und laufen als Aenderungsantrag.",
1068
+ "Mechanismus: deny vor ask vor allow, ueber alle Ebenen des Clients - ein deny "
1069
+ "an irgendeiner Stelle gewinnt.",
1070
+ "Dieses Profil wirkt NUR als aktiver Agent. Die Datei "
1071
+ + (man.get("client_settings_file") or "?") + " waehlt es als Standard; wer mit "
1072
+ "--agent einen anderen Agenten waehlt, arbeitet ohne diese Regeln. Fehlt diese "
1073
+ "Datei oder ist sie kein gueltiges JSON, faellt der Client still auf seinen "
1074
+ "eingebauten Agenten zurueck (Pruefung 96).",
1075
+ "Platzhalter in spitzen Klammern traegt der Overlay Owner in die match-Listen "
1076
+ "ein. Zusaetzliche Regeln sind nur als deny zulaessig.",
1077
+ "Exakte Mustersemantik: Zeilen B3 und B6 der Faehigkeitsmatrix dieses Client "
1078
+ "Packs.",
1079
+ ])
1080
+ profil: dict = {
1081
+ "_comment": kommentar,
1082
+ "name": name,
1083
+ "description": man.get("permission_profile_description", ""),
1084
+ "tools": list(man.get("permission_profile_tools") or ["*"]),
1085
+ "permissions": {"rules": kiro_regeln(quelle, man)},
1086
+ "_core_rules_integrity": {"deny_must_contain": kiro_kernregeln(quelle, man)},
1087
+ }
1088
+ return json.dumps(profil, indent=2, ensure_ascii=False) + "\n"
1089
+
1090
+
1091
+ def render_client_settings(man: dict) -> str:
1092
+ """Die Einstellungsdatei des Arbeitsbereichs, die das Agentenprofil waehlt.
1093
+
1094
+ Ohne Kommentarschluessel: Die Datei ist die Einstellungsablage des Clients und
1095
+ wird von seinem Werkzeug `settings --workspace` geschrieben; ein fremder
1096
+ Schluessel darin ist nicht gemessen. Die Erklaerung steht in der Kernquelle
1097
+ framework/runtime/client-settings.json und in der README der Laufzeitschicht.
1098
+ """
1099
+ werte = man.get("client_settings")
1100
+ if not werte or not isinstance(werte, dict):
1101
+ raise AbbildungsFehler(
1102
+ man.get("client", "?") + "/manifest.json: client_settings fehlt oder ist "
1103
+ "kein Objekt")
1104
+ return json.dumps(werte, indent=2, ensure_ascii=False) + "\n"
1105
+
1106
+
1107
+ def render_hooks_kiro(quelltext: str, man: dict) -> str:
1108
+ """Hook-Datei in der Gestalt {version: v1, hooks: [...]}.
1109
+
1110
+ Gemessen am 2026-09-26 in einer interaktiven Sitzung (die Hooks laufen NICHT im
1111
+ Betrieb ohne Rueckfragen - der Client aktiviert sie dort gar nicht):
1112
+ * Der Ausloeser steht je Eintrag; die Namen der Kernquelle (PreToolUse,
1113
+ SessionStart) sind gueltig. "PromptSubmit" aus der Dokumentation ist es nicht.
1114
+ * Der Matcher ist ein REGULAERER AUSDRUCK ueber den Werkzeugnamen - "*" allein
1115
+ kompiliert nicht, und der Eintrag laedt dann ohne Filter nicht.
1116
+ * Das Zeitlimit steht in SEKUNDEN (Schema des Clients).
1117
+ * Exit 2 sperrt die Werkzeugausfuehrung; der Folgeaufruf lief.
1118
+ Kein Kommentarschluessel auf oberster Ebene: Die Erklaerung steht in
1119
+ `description` jedes Eintrags, einem Feld, das das Schema fuehrt.
1120
+ """
1121
+ quelle = json.loads(quelltext)
1122
+ eintraege: list[dict] = []
1123
+ for ereignis, gruppen in quelle.items():
1124
+ if ereignis.startswith("_"):
1125
+ continue
1126
+ for gi, gruppe in enumerate(gruppen):
1127
+ matcher = _hook_matcher(gruppe["on"], man) if "on" in gruppe else None
1128
+ for hi, h in enumerate(gruppe["hooks"]):
1129
+ skript = os.path.basename(h["script"]).rsplit(".", 1)[0]
1130
+ eintrag: dict = {
1131
+ "name": f"koolie-{skript}",
1132
+ "description": (f"Hook des Frameworks ({ereignis}), erzeugt aus "
1133
+ f"{core_dir_name(man)}/framework/runtime/hooks.json "
1134
+ f"fuer das Client Pack {man.get('client', '?')}."),
1135
+ "trigger": ereignis,
1136
+ }
1137
+ if matcher:
1138
+ eintrag["matcher"] = matcher
1139
+ eintrag["action"] = {
1140
+ "type": h["type"],
1141
+ "command": _hook_befehl(h["script"], man,
1142
+ h.get("enforcing") is True,
1143
+ h.get("needs_project_paths") is True),
1144
+ }
1145
+ eintrag["timeout"] = h["timeout"]
1146
+ eintraege.append(eintrag)
1147
+ return json.dumps({"version": "v1", "hooks": eintraege},
1148
+ indent=2, ensure_ascii=False) + "\n"
1149
+
1150
+
1151
+ def exec_policy_file(man: dict) -> str | None:
1152
+ """Zielpfad der Befehlsregeldatei - oder None, wenn das Pack keine fuehrt."""
1153
+ return man.get("exec_policy_file")
1154
+
1155
+
1156
+ # ---------------------------------------------------------------------------
1157
+ # Die Endung der Regeldateien je Pack (seit 1.16.0, CR-2026-155)
1158
+ # ---------------------------------------------------------------------------
1159
+ #
1160
+ # Der Client cursor laedt aus .cursor/rules/ NUR Dateien mit der Endung .mdc - eine
1161
+ # .md-Datei dort ignoriert er, gemessen am 2026-09-26 (Kennwort in r2.md stand in
1162
+ # keiner Sitzung im Kontext; Herstellerdokumentation QU-1 sagt dasselbe). Die Kernquellen
1163
+ # heissen weiter *.md; das Pack nennt die Endung, und jede Stelle, die eine Regeldatei
1164
+ # der Ablage beim Namen nennt, fragt hier.
1165
+
1166
+ def regel_endung(man: dict) -> str:
1167
+ return man.get("rule_file_ext", ".md")
1168
+
1169
+
1170
+ def regeldatei(man: dict, name: str) -> str:
1171
+ """Der Dateiname einer Regel in der Ablage dieses Packs ('20-project-overlay.md'
1172
+ wird bei cursor zu '20-project-overlay.mdc')."""
1173
+ endung = regel_endung(man)
1174
+ if endung != ".md" and name.endswith(".md"):
1175
+ return name[:-3] + endung
1176
+ return name
1177
+
1178
+
1179
+ def ist_regeldatei(man: dict, dateiname: str) -> bool:
1180
+ return dateiname.endswith(regel_endung(man))
1181
+
1182
+
1183
+ # ---------------------------------------------------------------------------
1184
+ # Vierte Ausgabeform: die Berechtigungsdatei des Projekts bei cursor (cursor-json)
1185
+ # ---------------------------------------------------------------------------
1186
+ #
1187
+ # WARUM ES SIE GIBT, UND DER GRUND IST GEMESSEN (CR-2026-155, D-440). Der Client fuehrt
1188
+ # Regeln der Gestalt Werkzeug(Muster) wie die erste Ausgabeform - aber drei Dinge
1189
+ # schliessen die erste Form aus, alle am 2026-09-26 an cursor-agent 2026.09.26 gemessen:
1190
+ # * Die Projektdatei .cursor/cli.json darf NUR den Schluessel permissions tragen. Mit
1191
+ # _comment und _core_rules_integrity brach der Client mit Exit 1 ab ("unrecognized
1192
+ # keys") - fail-closed, aber ein Pack, das den Client nicht starten laesst, ist ein
1193
+ # Ausfall. Die Kernregeln haelt deshalb Pruefung 97 gegen die Kernquelle.
1194
+ # * Es gibt nur allow und deny, KEINEN Rueckfragekorb. Was nicht erlaubt ist, faellt
1195
+ # bei Befehlen und externen Werkzeugen in die Rueckfrage des Clients - bei Dateien
1196
+ # IM Arbeitsbereich nicht: Geschrieben wird dort ohne Rueckfrage (gemessen, auch ohne
1197
+ # --force). Die Rueckfrageregeln der Kernquelle werden deshalb nicht gerendert; jedes
1198
+ # ihrer Verben muss das Manifest unter permission_ask_ohne_korb erklaeren.
1199
+ # * Ein Pfadmuster wird VERANKERT gegen den ABSOLUTEN Pfad verglichen, und '*' steht
1200
+ # fuer eine beliebige Zeichenfolge ueber Trenner hinweg. Unter Windows traf deshalb
1201
+ # weder 'Read(.env)' noch 'Read(**/.env)' - beide Koeder kamen heraus -, wohl aber
1202
+ # 'Read(*\.env)'. Jedes Pfadmuster wird darum in ZWEI Schreibweisen erzeugt, mit
1203
+ # '/' und mit '\', beide mit fuehrendem '*'. Das ist breiter als das Kernmuster
1204
+ # (ein 'AGENTS.md' in einem Unterordner faellt mit darunter) - bei deny eine
1205
+ # Verschaerfung, und allow fuehrt nur '**', das ohnehin alles trifft.
1206
+
1207
+ def cursor_pfadmuster(rohmuster: str, man: dict) -> list[str]:
1208
+ """Die Schreibweisen eines Pfadmusters, die der Client gegen den absoluten Pfad trifft."""
1209
+ muster = resolve_placeholders(rohmuster, man)
1210
+ if muster.startswith("<") or muster in ("*", "**"):
1211
+ return [muster]
1212
+ rest = muster[2:] if muster.startswith("./") else muster
1213
+ while rest.startswith("**/"):
1214
+ rest = rest[3:]
1215
+ return ["*/" + rest, "*\\" + rest.replace("/", "\\")]
1216
+
1217
+
1218
+ def _cursor_argumente(regel: dict, man: dict, korb: str) -> list[str]:
1219
+ verb = regel["tool"]
1220
+ if verb == "exec":
1221
+ return [_befehl(regel, man, korb)]
1222
+ if verb in ("read", "write"):
1223
+ return cursor_pfadmuster(regel["pattern"], man)
1224
+ # fetch nennt eine Domain, kein Pfad; skill und mcp einen Namen.
1225
+ return [resolve_placeholders(regel["pattern"], man)]
1226
+
1227
+
1228
+ def cursor_koerbe(quelle: dict, man: dict) -> dict:
1229
+ """allow und deny in der Schreibweise dieses Clients - ask wird erklaert, nicht erzeugt."""
1230
+ ohne_korb = man.get("permission_ask_ohne_korb") or {}
1231
+ for regel in quelle.get("ask", []):
1232
+ if regel["tool"] not in ohne_korb:
1233
+ raise AbbildungsFehler(
1234
+ f"{man.get('client', '?')}/manifest.json: permission_ask_ohne_korb erklaert "
1235
+ f"das Verb '{regel['tool']}' nicht. Dieser Client kennt keinen Rueckfragekorb; "
1236
+ f"eine Rueckfrageregel, deren Verbleib nicht erklaert ist, waere still "
1237
+ f"entfallen (D-440)")
1238
+ koerbe: dict = {"allow": [], "deny": []}
1239
+ for korb in ("allow", "deny"):
1240
+ for regel in quelle.get(korb, []):
1241
+ ziele = _werkzeuge(man, regel["tool"])
1242
+ if not ziele:
1243
+ if korb == "deny":
1244
+ raise AbbildungsFehler(
1245
+ f"{man.get('client', '?')}: kein Werkzeug fuer '{regel['tool']}', die "
1246
+ f"deny-Regel {regel} liesse sich nur durch Weglassen abbilden - das "
1247
+ f"waere eine Lockerung")
1248
+ continue
1249
+ for argument in _cursor_argumente(regel, man, korb):
1250
+ for typ in ziele:
1251
+ eintrag = f"{typ}({argument})"
1252
+ if eintrag not in koerbe[korb]:
1253
+ koerbe[korb].append(eintrag)
1254
+ return koerbe
1255
+
1256
+
1257
+ def cursor_kernregeln(quelle: dict, man: dict) -> list[str]:
1258
+ """Die Kernzusagen in der Schreibweise dieses Clients - fuer Pruefung 97."""
1259
+ raus: list[str] = []
1260
+ for regel in quelle.get("deny", []):
1261
+ if not regel.get("core"):
1262
+ continue
1263
+ for argument in _cursor_argumente(regel, man, "deny"):
1264
+ for typ in _werkzeuge(man, regel["tool"]):
1265
+ eintrag = f"{typ}({argument})"
1266
+ if eintrag not in raus:
1267
+ raus.append(eintrag)
1268
+ return raus
1269
+
1270
+
1271
+ def render_permissions_cursor(quelltext: str, man: dict) -> str:
1272
+ """Die Berechtigungsdatei des Projekts: {"permissions": {"allow": [...], "deny": [...]}}.
1273
+
1274
+ Kein weiterer Schluessel - der Client verweigert sonst den Start (D-440). Die
1275
+ Erklaerung, die bei den uebrigen Formen in _comment steht, steht in der README der
1276
+ Laufzeitschicht und im Pack.
1277
+ """
1278
+ koerbe = cursor_koerbe(json.loads(quelltext), man)
1279
+ return json.dumps({"permissions": koerbe}, indent=2, ensure_ascii=False) + "\n"
1280
+
1281
+
1282
+ def cursor_ignore_muster(quelle: dict, man: dict) -> list[str]:
1283
+ """Die Leseverbote der Kernquelle als Zeilen der Ausschlussdatei (ohne Projektschlitze)."""
1284
+ raus: list[str] = []
1285
+ for regel in quelle.get("deny", []):
1286
+ if regel.get("tool") != "read":
1287
+ continue
1288
+ muster = resolve_placeholders(regel["pattern"], man)
1289
+ if muster.startswith("<") or muster in raus:
1290
+ continue
1291
+ raus.append(muster)
1292
+ return raus
1293
+
1294
+
1295
+ def render_ignore_cursor(quelltext: str, man: dict) -> str:
1296
+ """Die Ausschlussdatei des Clients (.cursorignore) aus den Leseverboten der Kernquelle.
1297
+
1298
+ WARUM ES SIE NEBEN DER BERECHTIGUNGSDATEI GIBT (CR-2026-155, D-443). Gemessen am
1299
+ 2026-09-26: Das Suchwerkzeug des Clients wertet ein Read-Verbot der
1300
+ Berechtigungsdatei NICHT aus - der Koeder aus secrets/ kam heraus. Mit derselben
1301
+ Angabe in .cursorignore wies der Client das Lesen von .env und sub/.env ab, meldete
1302
+ die Suche im Ordner als ausgefiltert und fand bei der Suche ueber das ganze Projekt
1303
+ nichts. Die Datei spricht die Syntax von .gitignore - relativ zum Projekt und ohne
1304
+ die Trennerfrage der Berechtigungsdatei. Die Shell erreicht sie nicht ('cat .env'
1305
+ lief); das haelt der Schutz-Hook.
1306
+ """
1307
+ kern = core_dir_name(man)
1308
+ kopf = [
1309
+ "# Ausschlussdatei des Frameworks, erzeugt fuer das Client Pack "
1310
+ + man.get("client", "?") + ".",
1311
+ "# Quelle: die Leseverbote in " + kern + "/framework/runtime/permissions.json.",
1312
+ "# Der Client verweigert dem Agenten Lesen und Suchen in diesen Pfaden.",
1313
+ "# Die Datei gehoert dem Projekt: <EXCLUDED_PATHS> hier zusaetzlich eintragen.",
1314
+ "# Eine Zeile des Kerns zu entfernen ist eine Lockerung (Pruefung 97).",
1315
+ "",
1316
+ ]
1317
+ return "\n".join(kopf + cursor_ignore_muster(json.loads(quelltext), man)) + "\n"
1318
+
1319
+
1320
+ def render_hooks_cursor(quelltext: str, man: dict) -> str:
1321
+ """Hook-Datei in der Gestalt {version: 1, hooks: {ereignis: [...]}}.
1322
+
1323
+ Gemessen am 2026-09-26 (CR-2026-155, D-441):
1324
+ * Die Ereignisse heissen klein (preToolUse, sessionStart); hook_event_names bildet
1325
+ die Namen der Kernquelle ab.
1326
+ * Der Matcher ist ein regulaerer Ausdruck ueber den Werkzeugnamen.
1327
+ * Ein Hook, der scheitert, laesst die Operation standardmaessig DURCH (fail-open).
1328
+ Ein durchsetzender Hook traegt deshalb failClosed - und dann wertet der Client
1329
+ auch einen Hook OHNE AUSGABE als gescheitert; die Sperrform permission-json
1330
+ antwortet beim Durchlass mit '{}'.
1331
+ * Der Hook-Prozess steht in der Projektwurzel (hook_project_dir_expr ".").
1332
+ """
1333
+ quelle = json.loads(quelltext)
1334
+ namen = man.get("hook_event_names") or {}
1335
+ ergebnis: dict = {}
1336
+ for ereignis, gruppen in quelle.items():
1337
+ if ereignis.startswith("_"):
1338
+ continue
1339
+ if ereignis not in namen:
1340
+ raise AbbildungsFehler(
1341
+ f"{man.get('client', '?')}/manifest.json: hook_event_names kennt das "
1342
+ f"Ereignis '{ereignis}' nicht - der Hook liefe unter keinem Namen, den der "
1343
+ f"Client ausloest")
1344
+ eintraege: list[dict] = []
1345
+ for gruppe in gruppen:
1346
+ matcher = _hook_matcher(gruppe["on"], man) if "on" in gruppe else None
1347
+ for h in gruppe["hooks"]:
1348
+ durchsetzend = h.get("enforcing") is True
1349
+ eintrag: dict = {"command": _hook_befehl(h["script"], man, durchsetzend,
1350
+ h.get("needs_project_paths") is True),
1351
+ "timeout": h["timeout"]}
1352
+ if matcher:
1353
+ eintrag["matcher"] = matcher
1354
+ if durchsetzend and man.get("hook_fail_closed") is True:
1355
+ eintrag["failClosed"] = True
1356
+ eintraege.append(eintrag)
1357
+ ergebnis[namen[ereignis]] = eintraege
1358
+ return json.dumps({"version": 1, "hooks": ergebnis}, indent=2, ensure_ascii=False) + "\n"