@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,1433 @@
1
+ """Der Grundbestand: Pflichtdateien, Berechtigungsdatei, Regeltexte, Skills, verbotene
2
+ Inhalte, Platzhalter, Querverweise, Manifest, Versionskette, Clientname im Kern, Mermaid
3
+ und Werkzeugneutralitaet.
4
+
5
+ Pruefungen 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14 und 48. Teil des Validators
6
+ validate-framework.py, seit 1.19.1 nach Gegenstand in Module geteilt (K-174). Das
7
+ Register aller Pruefungen steht im Kopfkommentar des Einstiegs, die Grenze jeder
8
+ einzelnen in ihrem Kopfkommentar hier."""
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+
19
+ import mermaid_renderer
20
+
21
+ from .gemeinsam import (
22
+ _clientmap, err, formatgebunden, fremde_praefixe, hinweis, iter_text_files, KERN,
23
+ NEUTRAL_CHRONIK,
24
+ NEUTRAL_CHRONIK_BASENAMES, NEUTRAL_DOKUMENT, nicht_geliefert, parse_frontmatter,
25
+ PLACEHOLDER_RE, read, regel_endung, regeldatei, skill_dirs, SKILL_STATUS,
26
+ SKIP_DIRS, TBD_RE, TEXT_EXT, warn, yaml, ZELLTRENNER_RE)
27
+
28
+
29
+ # Lockdateien sind erzeugte Abhaengigkeitsmetadaten. Sie enthalten naturgemaess fremde
30
+ # E-Mail-Adressen und Registry-Adressen und werden weder vom Framework noch vom Projekt
31
+ # redaktionell gepflegt - eine Inhaltspruefung dagegen erzeugt nur Rauschen.
32
+ SKIP_FILES = {"package-lock.json", "yarn.lock", "pnpm-lock.yaml", "npm-shrinkwrap.json",
33
+ "composer.lock", "Cargo.lock", "poetry.lock", "go.sum", "Gemfile.lock"}
34
+
35
+ # Clientneutral: gilt fuer jedes Client Pack. Die clientspezifischen Pflichtpfade
36
+ # (Wurzel-Anweisung, Berechtigungsdatei, Skills, Regeltexte) kommen aus dem Manifest.
37
+ REQUIRED_PATHS = [
38
+ "README.md", ".koolie/core/CHANGELOG.md", ".koolie/core/VERSION", ".koolie/core/OWNERS.md",
39
+ ".koolie/core/clients/README.md",
40
+ ".koolie/core/framework/core/00-principles.md", ".koolie/core/framework/core/02-privacy.md", ".koolie/core/framework/core/03-security.md",
41
+ ".koolie/core/framework/core/05-working-model.md", ".koolie/core/framework/core/08-skill-conventions.md",
42
+ ".koolie/core/framework/core/09-risk-model.md", ".koolie/core/framework/role-packs", ".koolie/core/framework/tech-packs",
43
+ ".koolie/project-overlay/OVERLAY.md", ".koolie/project-overlay/overlay-manifest.yaml",
44
+ ".koolie/core/templates/SKILL_TEMPLATE.md", ".koolie/core/prompts",
45
+ ".koolie/core/checklists", ".koolie/core/decision-trees",
46
+ ".koolie/core/onboarding", ".koolie/core/examples",
47
+ ".koolie/core/tests/TEST_CATALOG.md", ".koolie/core/governance/RACI.md", ".koolie/core/governance/DECISION_LOG.md",
48
+ ".koolie/core/docs/PLACEHOLDER_REGISTRY.md",
49
+ ]
50
+
51
+ RULE_TRIGGERS = {"always_on", "manual", "model_decision", "agent", "glob"}
52
+ SKILL_SECTIONS = [
53
+ "## 1. Zweck, Zielgruppe und Trigger",
54
+ "## 2. Vorbedingungen, Eingaben und Kontext",
55
+ "## 3. Arbeitsschritte",
56
+ "## 4. Grenzen und Rückfragenregeln",
57
+ "## 5. Ausgabeformat",
58
+ "## 6. Qualitätskriterien sowie Prüf- und Freigabeschritt",
59
+ "## 7. Fehlerbehandlung und Abbruch",
60
+ ]
61
+ SKILL_META_KEYS = ["ID", "Name", "Version", "Status", "Owner (Rolle)", "Betriebsmodus", "Zulässige Kontrollstufen"]
62
+
63
+ # Dieselben Kategorien, die der Schutz-Hook in einer Werkzeugeingabe blockiert
64
+ # (tests/scripts/hook-check-secrets.py). Ein Muster, das dort blockiert, darf in einer
65
+ # versionierten Datei nicht unbemerkt stehen bleiben - sonst waere dieselbe Zusage an
66
+ # zwei Stellen unterschiedlich streng.
67
+ #
68
+ # Ein Wert in spitzen Klammern ist ein Platzhalter und kein Secret; das Framework
69
+ # schreibt seine Beispiele durchgehend so. Nur deshalb kommen die Regeln hier ohne
70
+ # Ausnahmeliste aus. Bei der Verbindungszeichenfolge zaehlt allein das Kennwort: Ein
71
+ # Platzhalter als Benutzername sagt nichts darueber, ob das Kennwort echt ist.
72
+ SECRET_PATTERNS = [
73
+ ("privater Schlüssel", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")),
74
+ ("Cloud-Zugangsschlüssel", re.compile(r"\b(AKIA|ASIA)[0-9A-Z]{16}\b")),
75
+ ("JWT", re.compile(r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b")),
76
+ ("Bearer-Token", re.compile(r"\bBearer\s+[A-Za-z0-9\-_\.=]{20,}", re.I)),
77
+ ("Zugangsdaten-Zuweisung", re.compile(
78
+ r"(?i)\b(password|passwd|pwd|secret|api[_-]?key|access[_-]?key|token)\b"
79
+ r"\s*[:=]\s*['\"]?(?![<`])[^\s'\"]{8,}")),
80
+ ("Verbindungszeichenfolge mit Anmeldedaten", re.compile(
81
+ r"(?i)\b[a-z][a-z0-9+\-.]*://[^/\s:]+:(?!<)[^@\s]+@")),
82
+ ]
83
+ EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
84
+ IP_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
85
+ INTERNAL_HOST_RE = re.compile(r"\b[a-z0-9-]+\.(?:internal|intra|corp|lan)\b", re.I)
86
+ URL_RE = re.compile(r"https?://[^\s)>\]\"']+")
87
+ URL_ALLOWLIST = ("docs.devin.ai", "devin.ai", "cli.devin.ai", "docs.windsurf.com", "windsurf.com",
88
+ "example.com", "example.org", "example.invalid", "localhost",
89
+ # das oeffentliche Repositorium des Frameworks, eng gefasst (D-507)
90
+ "github.com/Renoxar/koolie",
91
+ # die Paketseiten und die Badges der README, ebenso eng (D-535)
92
+ "pypi.org/project/koolie", "npmjs.com/package/@renoxar/koolie",
93
+ "img.shields.io/pypi/v/koolie", "img.shields.io/npm/v/@renoxar/koolie",
94
+ "img.shields.io/badge/Lizenz-GPL", "img.shields.io/badge/license-GPL")
95
+ FENCE4_RE = re.compile(r"^`{4,}", re.M)
96
+ MERMAID_RE = re.compile(r"```mermaid\n(.*?)```", re.S)
97
+
98
+ # --- Querverweisprüfung (FW-KO-04) ---------------------------------------------
99
+ # Markdown-Links: [Text](ziel) und [Text](ziel "Titel"); Bildlinks eingeschlossen.
100
+ MD_LINK_RE = re.compile(r"""!?\[[^\]]*\]\(\s*<?([^)>\s]+)>?(?:\s+["'][^"']*["'])?\s*\)""")
101
+ # Inline-Code: Kandidaten für Pfadnennungen.
102
+ BACKTICK_RE = re.compile(r"`([^`\n]+)`")
103
+ # Nur Pfade unter diesen Wurzeln werden aus Backticks geprüft. Bewusst eng gehalten:
104
+ # Sie benennen Framework-Artefakte und sind damit genau die Verweise, die bei einer
105
+ # Umbenennung oder Umstrukturierung stillschweigend brechen.
106
+ #
107
+ # Die Wurzeln des Frameworks stehen fest, die der Laufzeitschicht kommen aus den
108
+ # Manifesten (_link_roots). Bis 0.56.2 standen '.devin/', 'AGENTS.md' und
109
+ # 'AGENTS.local.md' hier WOERTLICH - die Pfade des jeweils anderen Packs waren damit
110
+ # gar kein Kandidat der Heuristik (CR-2026-080). Die Ableitung hat heute keine
111
+ # gemessene Wirkung; sie schafft eine gepflegte Clientliste ab, die ein drittes Client
112
+ # Pack von Hand nachtragen muesste und die niemand nachzaehlt.
113
+ LINK_ROOTS_FEST = (".koolie/core/", ".koolie/project-overlay/", "README.md")
114
+ # Zeichen, die einen Kandidaten als Glob, Platzhalter, Befehl oder Prosa ausweisen.
115
+ NOT_A_PATH = set("*<>|?\"' \t()[]{}$!,")
116
+ #
117
+ # Bis 0.56.2 stand hier zusaetzlich OPTIONAL_RUNTIME_RE: eine Ausnahme fuer die
118
+ # Laufzeitpfade, die erst durch eine Projektentscheidung entstehen (30-, 40-, 2N-,
119
+ # Pack- und Projektskills). Sie ist mit 0.57.0 weggefallen. In drei Zuschnitten
120
+ # gemessen (CR-2026-080): Seit die Client-Bindungs-Warnung nach Pruefung 48 gewandert
121
+ # ist, deckt die Fremdpfaderkennung weiter unten denselben Fall VOLLSTAENDIG ab - ein
122
+ # Verweis unter der Laufzeitwurzel irgendeines Packs laeuft ohnehin nicht in die
123
+ # Totpfadmeldung. Eine Ausnahme, die nichts mehr ausnimmt, ist schlimmer als keine:
124
+ # Sie sieht wie Sorgfalt aus. Und sie hat sich mit der engen Wurzelliste gegenseitig
125
+ # gedeckt - zwei Fehler in derselben Richtung fallen einzeln nicht auf.
126
+ #
127
+ # Dateien mit absichtlich nicht existierenden Beispielpfaden (synthetische Beispiele,
128
+ # Vorlagen, Migrationshinweise auf frühere Stände). Markdown-Links werden auch dort
129
+ # geprüft – nur die Backtick-Heuristik ist ausgesetzt.
130
+ LINK_EXCEPTIONS = (
131
+ ".koolie/core/docs/PLACEHOLDER_REGISTRY.md",
132
+ ".koolie/core/CHANGELOG.md",
133
+ ".koolie/core/governance/CHANGE_REQUEST_TEMPLATE.md",
134
+ ".koolie/core/governance/change-requests/",
135
+ ".koolie/core/templates/SKILL_TEMPLATE.md",
136
+ ".koolie/core/examples/",
137
+ # Client Packs beschreiben die Pfade ihres Clients, nicht die der Installation.
138
+ ".koolie/core/clients/_template/",
139
+ # Das Glossar bildet die Begriffe des Kerns auf die Pfade je Client ab; dort sind
140
+ # clientfremde Pfade der Inhalt, nicht eine Altlast.
141
+ ".koolie/core/docs/RUNTIME_GLOSSARY.md",
142
+ # Historische Dokumente behalten die Pfade ihres Entstehungsstands.
143
+ ".koolie/core/tests/protocols/",
144
+ ".koolie/core/governance/DECISION_LOG.md",
145
+ )
146
+ # Eine CLIENT_PACK.md nennt naturgemaess Pfade, die nur bei ihrem Client existieren.
147
+ LINK_EXCEPTION_BASENAMES = ("CLIENT_PACK.md",)
148
+
149
+
150
+ def fundstelle(rel: str, text: str, pos: int) -> str:
151
+ """Pfad, Zeile und Spalte eines Treffers - **ohne den Treffer selbst**.
152
+
153
+ Die Diagnosen der Inhaltspruefung nannten bis 0.26.1 den gefundenen Wert im
154
+ Klartext: die E-Mail-Adresse, die IP, den internen Hostnamen, die vollstaendige
155
+ URL samt Parametern - und den gesperrten Begriff. Damit trug ein Schutzlauf genau
156
+ die Angaben weiter, die er finden soll: in ein Terminal, ein Protokoll, eine
157
+ Agentensitzung.
158
+
159
+ Am schaerfsten beim Sperrbegriff. Die Liste in .koolie/project-overlay/forbidden-terms.txt
160
+ enthaelt reale Projekt-, Kunden- und Behoerdennamen; die Pruefung nimmt diese Datei
161
+ deshalb ausdruecklich von der eigenen Inhaltspruefung aus - und schrieb den Namen
162
+ dann in die Fehlermeldung. Die eine Zeichenkette, die in keiner Ausgabe des
163
+ Frameworks stehen darf, stand dort durch die Pruefung, die sie verhindern soll.
164
+
165
+ Die Secret-Diagnose derselben Funktion machte es von Anfang an richtig: Kategorie
166
+ ohne Wert - aber ohne Position. Diese Funktion zieht die uebrigen nach und traegt
167
+ die Fundstelle dort nach, wo sie fehlte (B03, D-39).
168
+
169
+ Spalte statt nur Zeile, damit zwei Treffer derselben Zeile unterscheidbar bleiben -
170
+ sonst faellt der zweite als scheinbares Duplikat nicht auf.
171
+ """
172
+ zeile = text.count(chr(10), 0, pos) + 1
173
+ spalte = pos - (text.rfind(chr(10), 0, pos) + 1) + 1
174
+ return f"{rel}:{zeile}:{spalte}"
175
+
176
+
177
+ def check_required(root: str, man: dict) -> None:
178
+ pflicht = list(REQUIRED_PATHS)
179
+ pflicht += [man["root_instruction_file"], man["permissions_file"],
180
+ man["skills_dir"], man["agents_dir"]]
181
+ pflicht += [f"{man['pack_runtime_dir']}/{n}{regel_endung(man)}" for n in
182
+ ("00-framework-core", "10-privacy-security", "15-development-rules",
183
+ "20-project-overlay")]
184
+ for rel in pflicht:
185
+ if not os.path.exists(os.path.join(root, rel)):
186
+ err(f"Pflichtpfad fehlt: {rel}")
187
+
188
+
189
+ def soll_kernregeln(root: str, man: dict) -> list[str]:
190
+ """Kernzusagen B1 bis B6 in der Schreibweise dieses Clients, aus der Kernquelle.
191
+
192
+ Berechtigungen sind Saat: Nach der Erstinstallation gehoert die Datei dem Projekt und
193
+ wird nie ueberschrieben - install.py --check meldet dort also nichts. Diese Pruefung
194
+ ist deshalb die einzige Stelle, an der eine entfernte Kernregel auffaellt. Fehlt das
195
+ Abbildungsmodul (etwa in einer Teilkopie des Kerns), wird nur gewarnt: Die Pruefungen
196
+ gegen die Datei selbst greifen weiterhin.
197
+ """
198
+ kern = os.path.join(root, KERN)
199
+ if not os.path.isdir(kern):
200
+ return []
201
+ if kern not in sys.path:
202
+ sys.path.insert(0, kern)
203
+ try:
204
+ import clientmap
205
+ except ImportError:
206
+ warn("clientmap.py nicht gefunden – Kernregeln werden nur gegen die "
207
+ "Berechtigungsdatei selbst geprüft, nicht gegen die Kernquelle")
208
+ return []
209
+ try:
210
+ quelle = json.loads(clientmap.load_source(kern, "permissions.json"))
211
+ return clientmap.core_rules(quelle, man)
212
+ except (OSError, ValueError) as exc:
213
+ err(f"Kernquelle der Berechtigungen nicht auswertbar: {exc}")
214
+ return []
215
+
216
+
217
+ def check_config(root: str, man: dict) -> None:
218
+ if formatgebunden(man, 2):
219
+ return
220
+ rel = man["permissions_file"]
221
+ path = os.path.join(root, *rel.split("/"))
222
+ if not os.path.exists(path):
223
+ return
224
+ try:
225
+ cfg = json.loads(read(path))
226
+ except json.JSONDecodeError as exc:
227
+ err(f"{rel} ist kein gültiges JSON: {exc}")
228
+ return
229
+ perms = cfg.get("permissions", {})
230
+ deny = set(perms.get("deny", []))
231
+ allow = set(perms.get("allow", []))
232
+
233
+ # Eine Pfadregel fuer ein Werkzeug, das der Client dafuer nicht auswertet, wird
234
+ # angenommen und nie konsultiert - sie taeuscht Schutz vor und erzeugt beim
235
+ # Sitzungsstart Laerm. Welche Werkzeuge Pfadregeln kennen, sagt das Manifest; ohne die
236
+ # Angabe unterbleibt die Pruefung, weil sie dann unbelegt waere (AP2-CC-02, D-26).
237
+ pfadwerkzeuge = man.get("permission_path_tools")
238
+ if pfadwerkzeuge:
239
+ befehlswerkzeuge = set(man.get("permission_tools", {}).get("exec", []))
240
+ namenswerkzeuge = set(man.get("permission_name_tools", []))
241
+ for korb in ("deny", "ask", "allow"):
242
+ for regel in perms.get(korb, []):
243
+ if not isinstance(regel, str) or "(" not in regel:
244
+ continue
245
+ werkzeug = regel.split("(", 1)[0]
246
+ if (werkzeug in befehlswerkzeuge or werkzeug in pfadwerkzeuge
247
+ or werkzeug in namenswerkzeuge):
248
+ continue
249
+ err(f"{rel}: {korb}-Regel '{regel}' nennt ein Werkzeug, fuer das dieser "
250
+ f"Client keine Pfadregeln auswertet. Zulaessig sind "
251
+ f"{', '.join(sorted(pfadwerkzeuge))}; eine Regel ohne Pfad wirkt "
252
+ f"weiterhin auf Werkzeugebene")
253
+ must = cfg.get("_core_rules_integrity", {}).get("deny_must_contain", [])
254
+ if not must:
255
+ err(f"{rel}: Block _core_rules_integrity.deny_must_contain fehlt")
256
+ for rule in must:
257
+ if rule not in deny:
258
+ err(f"{rel}: Kernregel fehlt in deny: {rule}")
259
+ if rule in allow:
260
+ err(f"{rel}: Kernverbot steht in allow: {rule}")
261
+ # Die Liste gegen die Kernquelle abgleichen, nicht nur gegen sich selbst. Ohne diesen
262
+ # Schritt genuegte es, eine Kernregel in beiden Listen zu streichen - die Datei bliebe
263
+ # in sich stimmig und der Verlust unbemerkt.
264
+ for rule in soll_kernregeln(root, man):
265
+ if rule in must:
266
+ continue # von der Schleife darueber bereits geprueft
267
+ err(f"{rel}: Kernregel fehlt in _core_rules_integrity.deny_must_contain "
268
+ f"(laut Kernquelle vorgesehen): {rule}")
269
+ if rule not in deny:
270
+ err(f"{rel}: Kernregel fehlt in deny: {rule}")
271
+ # Nie erlaubt, unabhaengig vom Client: Push, Merge, Rechteausweitung, Loeschen.
272
+ # Die Werkzeugnamen unterscheiden sich je Client, die Absicht nicht.
273
+ verboten = ("Exec(git push", "Exec(sudo", "Exec(rm -rf",
274
+ "Bash(git push", "Bash(git merge", "Bash(sudo", "Bash(rm")
275
+ for rule in allow:
276
+ if rule.startswith(verboten):
277
+ err(f"{rel}: unzulässige allow-Regel: {rule}")
278
+ # Abrufwerkzeuge: aus dem Manifest, nicht aus einer Namensliste. Bis 0.32.0 standen
279
+ # hier "Fetch(*", "WebFetch" und "WebSearch" fest verdrahtet - und das war
280
+ # asymmetrisch: 'Fetch(domain:...)' lief durch, 'WebFetch(domain:...)' fiel, dieselbe
281
+ # Absicht bei zwei Packs verschieden entschieden. Nach D-59 gibt es keine
282
+ # Domain-Ausnahme; damit ist **jede** allow-Regel auf ein Abrufverb unzulaessig, und
283
+ # der dokumentierte Weg ist, das globale Verbot per Aenderungsantrag zu ersetzen
284
+ # (B11, V10).
285
+ abrufwerkzeuge = tuple(man.get("permission_tools", {}).get("fetch", ()))
286
+ for rule in allow:
287
+ if not isinstance(rule, str):
288
+ continue
289
+ name = rule.split("(", 1)[0].strip()
290
+ if abrufwerkzeuge and name in abrufwerkzeuge:
291
+ err(f"{rel}: unzulässige allow-Regel auf ein Abrufwerkzeug: {rule}. Das "
292
+ f"generelle Netzverbot kennt keine Ausnahme je Domain - `deny` gewinnt "
293
+ f"immer, eine zusätzliche allow-Regel hebt es nicht auf. Wer externen "
294
+ f"Abruf braucht, ersetzt die Verbotsregel über einen Änderungsantrag "
295
+ f"(V10) und weist die Ersatzbeschränkung nach (B11, D-59)")
296
+ # Weitere JSON-Dateien der Laufzeitschicht auf Gueltigkeit pruefen.
297
+ rt = man["runtime_dir"]
298
+ for name in os.listdir(os.path.join(root, *rt.split("/"))) if os.path.isdir(os.path.join(root, *rt.split("/"))) else []:
299
+ if not name.endswith((".json", ".json.example")):
300
+ continue
301
+ p = os.path.join(root, *rt.split("/"), name)
302
+ try:
303
+ data = json.loads(read(p))
304
+ except json.JSONDecodeError as exc:
305
+ err(f"{rt}/{name} ist kein gültiges JSON: {exc}")
306
+ continue
307
+ if "mcp" in name and not name.endswith(".example"):
308
+ blob = json.dumps(data).lower()
309
+ if any(k in blob for k in ("password", "token", "secret", "apikey", "api_key")):
310
+ err(f"{rt}/{name} enthält Schlüsselwörter für Zugangsdaten – gehören in eine nicht versionierte Datei oder einen Tresor")
311
+
312
+
313
+ KERNREGEL_PRAEFIXE = ("00-", "10-", "15-", "20-")
314
+
315
+
316
+ def _check_rule_client_form(rel: str, fn: str, text: str, spec: dict) -> int:
317
+ """Regeldatei in der Bedingungssprache des Clients. Rueckgabe: Zeichen, die stets laden.
318
+
319
+ Geprueft wird die **installierte** Fassung, nicht die Quelle (D-26): Ein stehen
320
+ gebliebenes `trigger:` oder `globs:` bedeutet, dass die Datei nicht durch die Abbildung
321
+ des Client Packs gelaufen ist - der Client wertet diese Felder nicht aus, die
322
+ Ladebedingung waere damit verfallen.
323
+ """
324
+ feld = spec.get("condition_field")
325
+ erlaubt = set(spec.get("allowed_fields", []))
326
+ if not text.startswith("---"):
327
+ return len(text)
328
+ fm, _ = parse_frontmatter(text)
329
+ if not fm or "_error" in fm:
330
+ err(f"{rel}: Frontmatter vorhanden, aber nicht lesbar")
331
+ return 0
332
+ if "_raw" in fm:
333
+ return 0
334
+ for schluessel in sorted(set(fm) - erlaubt):
335
+ err(f"{rel}: Frontmatter-Feld '{schluessel}' – dieser Client wertet für Regeldateien "
336
+ f"nur {sorted(erlaubt)} aus (K-18). Ein stehen gebliebenes 'trigger' oder 'globs' "
337
+ f"heißt: Die Datei ist nicht durch die Abbildung des Client Packs gelaufen, ihre "
338
+ f"Ladebedingung ist verfallen")
339
+ if feld not in fm:
340
+ return len(text)
341
+ werte = fm[feld]
342
+ if spec.get("condition_format", "list") == "list" and (
343
+ not isinstance(werte, list) or not werte
344
+ or not all(isinstance(w, str) and w.strip() for w in werte)):
345
+ err(f"{rel}: '{feld}' muss eine nichtleere Liste von Dateimustern sein")
346
+ if fn.startswith(KERNREGEL_PRAEFIXE):
347
+ err(f"{rel}: Kernregel über '{feld}' an Dateimuster gebunden – sie gilt für jede "
348
+ f"Aufgabe; eine Ladebedingung wäre hier eine Lockerung")
349
+ return 0
350
+
351
+
352
+ def check_rules(root: str, man: dict) -> None:
353
+ """Regeltexte der Laufzeitschicht.
354
+
355
+ Drei Bauarten, je nach Client:
356
+
357
+ * Der Client kennt die Ladetrigger der Kernquelle (`devin-desktop`): geprueft wird das
358
+ Quellfrontmatter - `description`, `trigger`, bei `glob` zusaetzlich `globs`.
359
+ * Der Client kennt eine **eigene** Bedingungssprache (`rule_triggers` im Manifest, bei
360
+ `claude-code` das Feld `paths`): geprueft wird die installierte Fassung gegen die
361
+ Felder, die er auswertet.
362
+ * Der Client laedt Regeldateien nicht von sich aus: Dort wirkt eine Datei erst, wenn die
363
+ Wurzel-Anweisung sie einbindet - eine nicht eingebundene Datei ist stillschweigend
364
+ wirkungslos, und genau das wird geprueft.
365
+
366
+ DIE ZEICHENGRENZE IST EIN BUDGET FUER DIE SUMME (D-387, K-130). Verbindlich ist, was
367
+ jede Sitzung laedt: Wurzel-Anweisung plus die unbedingt geladenen Regeltexte, hoechstens
368
+ 40.000 Zeichen - bei jedem Pack, bei den Ladetriggern der Kernquelle die Regeln mit
369
+ `always_on`. Die 12.000 Zeichen je Datei und die 6.000 fuer die Overlay-Laufzeitregel
370
+ sind Warnungen. Alle drei Zahlen sind eine Vorgabe des Frameworks, keine Eigenschaft
371
+ eines Clients (CR-2026-027 E3).
372
+ """
373
+ rules_rel = man["pack_runtime_dir"]
374
+ rules_dir = os.path.join(root, *rules_rel.split("/"))
375
+ if not os.path.isdir(rules_dir):
376
+ return
377
+
378
+ wurzel_rel = man["root_instruction_file"]
379
+ wurzel_pfad = os.path.join(root, *wurzel_rel.split("/"))
380
+ wurzel_text = read(wurzel_pfad) if os.path.exists(wurzel_pfad) else ""
381
+ eigene_bedingung = man.get("rule_triggers")
382
+ mit_triggern = man.get("has_rule_triggers", True)
383
+ ueber_import = not mit_triggern
384
+ stets_geladen = len(wurzel_text)
385
+
386
+ for fn in sorted(os.listdir(rules_dir)):
387
+ if not fn.endswith(regel_endung(man)):
388
+ continue
389
+ text = read(os.path.join(rules_dir, fn))
390
+ rel = f"{rules_rel}/{fn}"
391
+
392
+ if not ueber_import:
393
+ if len(text) > 12000:
394
+ warn(f"{rel}: {len(text)} Zeichen (> 12.000, SOLL-Grenze je Datei)")
395
+ if fn == regeldatei(man, "20-project-overlay.md") and len(text) > 6000:
396
+ warn(f"{rel}: {len(text)} Zeichen (> 6.000, SOLL-Grenze)")
397
+ if fn == "README.md":
398
+ continue
399
+
400
+ if eigene_bedingung:
401
+ stets_geladen += _check_rule_client_form(rel, fn, text, eigene_bedingung)
402
+ elif mit_triggern:
403
+ fm, _ = parse_frontmatter(text)
404
+ if not fm or "_error" in fm:
405
+ err(f"{rel}: Frontmatter fehlt oder ungültig")
406
+ continue
407
+ if "_raw" in fm:
408
+ continue
409
+ if "description" not in fm or not str(fm.get("description", "")).strip():
410
+ err(f"{rel}: Frontmatter-Feld description fehlt")
411
+ trig = fm.get("trigger")
412
+ if trig not in RULE_TRIGGERS:
413
+ err(f"{rel}: trigger '{trig}' nicht in {sorted(RULE_TRIGGERS)}")
414
+ if trig == "glob" and not fm.get("globs"):
415
+ err(f"{rel}: trigger glob ohne globs")
416
+ if trig == "always_on":
417
+ stets_geladen += len(text)
418
+ else:
419
+ # Ohne eigene Ladebedingung entscheidet die Einbindung. Eine Kernregel muss
420
+ # in der Wurzel-Anweisung GENANNT sein, sonst waere sie wirkungslos.
421
+ #
422
+ # BIS 1.3.0 VERLANGTE DIESE ZEILE DIE FORM `@<pfad>`, UND DIE FORM WAR
423
+ # GERATEN (CR-2026-133, D-348): Kein ausgeliefertes Pack war so gebaut, und
424
+ # die `@`-Schreibweise stammte aus der Wurzel-Anweisung eines anderen
425
+ # Clients. Am 2026-09-23 ist sie zum ersten Mal an einem Client gemessen
426
+ # worden, der Regeldateien wirklich nicht von sich aus laedt - und dort
427
+ # bewirkt `@<pfad>` NICHTS: Die Sonde stand im Prompt-Eingang nicht.
428
+ # ➡️ Eine Prüfung, die eine ungemessene Schreibweise verlangt, misst die
429
+ # Schreibweise und nicht die Sache.
430
+ # Geprueft wird deshalb die NENNUNG. Sie ist die schwaechere Form, und das
431
+ # ist ehrlich: Was eine Nennung bewirkt, haengt am Modell und nicht an der
432
+ # Engine - die Zeilen R2 und R3 des betroffenen Packs sagen es.
433
+ eingebunden = rel in wurzel_text
434
+ if fn.startswith(KERNREGEL_PRAEFIXE) and not eingebunden:
435
+ err(f"{rel}: nicht in {wurzel_rel} genannt – die Regel wäre wirkungslos, "
436
+ f"weil dieser Client Regeldateien nicht von sich aus lädt "
437
+ f"(manifest.json: has_rule_triggers = false)")
438
+ if eingebunden:
439
+ stets_geladen += len(text)
440
+ if text.startswith("---"):
441
+ warn(f"{rel}: YAML-Frontmatter, obwohl dieser Client keine Ladebedingung kennt "
442
+ f"(Frontmatter nur mit dokumentierten Feldern, K-18)")
443
+
444
+ if stets_geladen > 40000:
445
+ err(f"{wurzel_rel} und die unbedingt geladenen Regeltexte ergeben {stets_geladen} "
446
+ f"Zeichen, die in jeder Sitzung geladen werden (> 40.000, Least Context, D-387)")
447
+
448
+ if os.path.exists(wurzel_pfad) and not ueber_import and len(wurzel_text) > 12000:
449
+ warn(f"{wurzel_rel}: {len(wurzel_text)} Zeichen (> 12.000, SOLL-Grenze je Datei)")
450
+
451
+
452
+ def check_skills(root: str, man: dict) -> None:
453
+ ids: dict[str, str] = {}
454
+ runtime = man["skills_dir"]
455
+ # Fremde Skills, die das Projekt im Overlay-Manifest deklariert, sind keine Koolie-Skills
456
+ # und werden nicht nach dessen Regeln geprueft - nur in der Laufzeitablage, nie in den
457
+ # Quellen des Kerns (1.21.0, K-31). Den Korb verlangt Pruefung 72 weiter (D-238).
458
+ fremd = fremde_praefixe(root)
459
+ for skills_dir, prefix in skill_dirs(root, man):
460
+ # Die Modellwahl-Sperre wird nur in der *installierten* Fassung geprueft: In der
461
+ # Quelle steht die Aussage als `triggers`, erst die Abbildung uebersetzt sie.
462
+ check_skills_in(skills_dir, prefix, ids,
463
+ man if prefix == runtime else None,
464
+ fremd if prefix == runtime else ())
465
+
466
+
467
+ def check_skills_in(skills_dir: str, prefix: str, ids: dict[str, str],
468
+ man: dict | None = None, fremd=()) -> None:
469
+ for name in sorted(os.listdir(skills_dir)):
470
+ sdir = os.path.join(skills_dir, name)
471
+ if not os.path.isdir(sdir):
472
+ continue
473
+ if fremd and any(name.startswith(p) for p in fremd):
474
+ continue
475
+ rel = f"{prefix}/{name}"
476
+ if not re.fullmatch(r"[a-z0-9-]+", name):
477
+ err(f"{rel}: Verzeichnisname muss aus Kleinbuchstaben, Ziffern, Bindestrichen bestehen")
478
+ if not re.match(r"^(fw|prj|role-[a-z0-9]+|tech-[a-z0-9]+)-", name):
479
+ warn(f"{rel}: Präfix entspricht nicht fw-/prj-/role-<pack>-/tech-<pack>-")
480
+ for req in ("SKILL.md", "EXAMPLES.md", "TESTS.md", "CHANGELOG.md"):
481
+ if not os.path.exists(os.path.join(sdir, req)):
482
+ err(f"{rel}: {req} fehlt")
483
+ skill_path = os.path.join(sdir, "SKILL.md")
484
+ if not os.path.exists(skill_path):
485
+ continue
486
+ text = read(skill_path)
487
+ fm, body = parse_frontmatter(text)
488
+ if not fm or "_error" in fm:
489
+ err(f"{rel}/SKILL.md: Frontmatter fehlt oder ungültig")
490
+ continue
491
+ if "_raw" not in fm:
492
+ if fm.get("name") != name:
493
+ err(f"{rel}/SKILL.md: name '{fm.get('name')}' != Verzeichnisname")
494
+ if not str(fm.get("description", "")).strip():
495
+ err(f"{rel}/SKILL.md: description fehlt")
496
+ fmt = (man or {}).get("skill_frontmatter", {})
497
+ # Die Quelle nennt allowed-tools als Liste von Verben, die installierte Fassung
498
+ # je nach Client als Liste oder als kommagetrennte Zeichenkette von Werkzeugnamen.
499
+ # Ohne diese Unterscheidung lief die Pruefung ueber die *Zeichen* der Zeichenkette
500
+ # und meldete jeden installierten Skill als nicht schreibend (AP2-CC-09).
501
+ tools = fm.get("allowed-tools") or []
502
+ if isinstance(tools, str):
503
+ tools = [x for x in re.split(r"[,\s]+", tools) if x]
504
+ if not tools:
505
+ err(f"{rel}/SKILL.md: allowed-tools fehlt (minimale Werkzeugmenge angeben)")
506
+ schreibverben = ("edit", "exec", "write")
507
+ namen = fmt.get("tool_names", {})
508
+ schreibnamen = {n for v in schreibverben for n in namen.get(v, [])}
509
+ writes = any(t in schreibverben or t in schreibnamen for t in tools)
510
+
511
+ sperre = fmt.get("model_invocation_field")
512
+ # Traegt dieser Ablageort `triggers` ueberhaupt? Ein Client, der das Feld nicht
513
+ # kennt, bekommt die Aussage abgebildet - dann ist ihr Fehlen kein Fehler,
514
+ # sondern die Abbildung ist zu pruefen (AP2-CC-01 und AP2-CC-09, D-26).
515
+ abgebildet = bool(sperre) and "triggers" in fmt.get("drop_fields", [])
516
+ triggers = fm.get("triggers") or []
517
+ if abgebildet:
518
+ if writes and fm.get(sperre) is not True:
519
+ err(f"{rel}/SKILL.md: schreibender/ausfuehrender Skill ohne "
520
+ f"'{sperre}: true' - die Quelle verlangt triggers: [user], die "
521
+ f"installierte Fassung muss das in der Form dieses Clients tragen")
522
+ else:
523
+ if writes and triggers != ["user"]:
524
+ err(f"{rel}/SKILL.md: schreibender/ausfuehrender Skill muss "
525
+ f"triggers: [user] haben")
526
+ if not triggers:
527
+ err(f"{rel}/SKILL.md: triggers fehlt")
528
+
529
+ erlaubt = ("name", "description", "argument-hint", "allowed-tools", "permissions",
530
+ "triggers", "model", "subagent", "agent")
531
+ # Die beiden abgebildeten Felder heissen je Client anders und stehen deshalb
532
+ # nicht in der Liste, sondern kommen aus dem Manifest: die Modellwahl-Sperre
533
+ # (AP2-CC-01) und seit 0.35.0 die Werkzeugsperre je Skill (D-64).
534
+ deny_feld = fmt.get("skill_deny_field")
535
+ for key in fm:
536
+ if key not in erlaubt and key != sperre and key != deny_feld:
537
+ warn(f"{rel}/SKILL.md: Frontmatter-Feld '{key}' ist nicht dokumentiert")
538
+ for key in SKILL_META_KEYS:
539
+ if not re.search(rf"^\|\s*{re.escape(key)}\s*\|", body, re.M):
540
+ err(f"{rel}/SKILL.md: Metadatenzeile '{key}' fehlt")
541
+ m = re.search(r"^\|\s*Status\s*\|\s*`?([^`|]+?)`?\s*\|", body, re.M)
542
+ if m and m.group(1).strip() not in SKILL_STATUS:
543
+ err(f"{rel}/SKILL.md: Status '{m.group(1).strip()}' unbekannt")
544
+ # Bis 0.12.0 genuegte irgendein Wert - 'banane' blieb unbemerkt, und die Version
545
+ # konnte dem Aenderungsverlauf widersprechen (FW-VN-01, Sonden S4 und S5).
546
+ m = re.search(r"^\|\s*Version\s*\|\s*`?([^`|]+?)`?\s*\|", body, re.M)
547
+ cl_path = os.path.join(sdir, "CHANGELOG.md")
548
+ if m:
549
+ fassung = m.group(1).strip()
550
+ if not SEMVER_RE.match(fassung):
551
+ err(f"{rel}/SKILL.md: Version '{fassung}' ist kein Semantic Versioning "
552
+ f"(MAJOR.MINOR.PATCH)")
553
+ elif os.path.exists(cl_path) and not re.search(
554
+ rf"^\|\s*{re.escape(fassung)}\s*\|", read(cl_path), re.M):
555
+ err(f"{rel}: Version {fassung} hat keinen Eintrag in CHANGELOG.md "
556
+ f"(08-skill-conventions.md Abschnitt 7)")
557
+ m = re.search(r"^\|\s*ID\s*\|\s*`?([A-Z0-9-]+)`?\s*\|", body, re.M)
558
+ if m:
559
+ vorher = ids.get(m.group(1))
560
+ # Gleicher Skillname in Quellablage und aktivierter Schicht ist eine Kopie,
561
+ # kein Konflikt. Nur unterschiedliche Skills mit gleicher ID sind ein Fehler.
562
+ if vorher is not None and vorher != name:
563
+ err(f"{rel}/SKILL.md: ID {m.group(1)} doppelt (auch in {vorher})")
564
+ ids[m.group(1)] = name
565
+ else:
566
+ err(f"{rel}/SKILL.md: ID nicht lesbar")
567
+ for sec in SKILL_SECTIONS:
568
+ if sec not in body:
569
+ err(f"{rel}/SKILL.md: Pflichtabschnitt fehlt: {sec}")
570
+ for word in ("Rückfrage", "Fundstelle"):
571
+ if word not in body:
572
+ err(f"{rel}/SKILL.md: Begriff '{word}' fehlt (Rückfragen-/Belegpflicht)")
573
+ ex_path = os.path.join(sdir, "EXAMPLES.md")
574
+ if os.path.exists(ex_path):
575
+ ex = read(ex_path)
576
+ if "synthetisch" not in ex:
577
+ err(f"{rel}/EXAMPLES.md: Kennzeichnung 'synthetisch' fehlt")
578
+ if "Positivbeispiel" not in ex or "Negativbeispiel" not in ex:
579
+ err(f"{rel}/EXAMPLES.md: Positiv- und Negativbeispiel erforderlich")
580
+ t_path = os.path.join(sdir, "TESTS.md")
581
+ if os.path.exists(t_path):
582
+ t = read(t_path)
583
+ if not re.search(r"-P0\d", t) or not re.search(r"-N0\d", t):
584
+ err(f"{rel}/TESTS.md: mindestens ein Positivtest (-P0n) und ein Negativtest (-N0n) erforderlich")
585
+ for col in ("Test-ID", "Ziel", "Vorbedingung", "Eingabe", "Erwartetes Verhalten",
586
+ "Unzulässiges Verhalten", "Prüfmethode", "Ergebnisstatus"):
587
+ if col not in t:
588
+ err(f"{rel}/TESTS.md: Spalte '{col}' fehlt")
589
+
590
+
591
+ def load_placeholder_registry(root: str) -> set[str]:
592
+ path = os.path.join(root, ".koolie/core", "docs", "PLACEHOLDER_REGISTRY.md")
593
+ if not os.path.exists(path):
594
+ return set()
595
+ return set(PLACEHOLDER_RE.findall(read(path)))
596
+
597
+
598
+ def check_content(root: str) -> None:
599
+ registry = load_placeholder_registry(root)
600
+ forbidden_terms: list[str] = []
601
+ ft_path = os.path.join(root, ".koolie/project-overlay", "forbidden-terms.txt")
602
+ if os.path.exists(ft_path):
603
+ forbidden_terms = [l.strip() for l in read(ft_path).splitlines()
604
+ if l.strip() and not l.startswith("#")]
605
+ unknown_placeholders: dict[str, set[str]] = {}
606
+ for path in iter_text_files(root):
607
+ # os.path.relpath liefert unter Windows Backslashes; ohne diese Normalisierung
608
+ # greift unten kein einziger Pfadvergleich - check_links macht es seit jeher so.
609
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
610
+ if rel.startswith(f"{KERN}/tests/scripts/") or rel == ".koolie/project-overlay/forbidden-terms.txt":
611
+ continue
612
+ if os.path.basename(path) in SKIP_FILES:
613
+ continue
614
+ text = read(path)
615
+ for label, pat in SECRET_PATTERNS:
616
+ m = pat.search(text)
617
+ if m:
618
+ err(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-SECRET: "
619
+ f"Secret-Muster ({label})")
620
+ for m in EMAIL_RE.finditer(text):
621
+ if not m.group(0).lower().endswith(("example.com", "example.org", "example.invalid")):
622
+ err(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-EMAIL: E-Mail-Adresse")
623
+ for m in IP_RE.finditer(text):
624
+ if not m.group(0).startswith(("0.", "127.", "192.0.2.", "198.51.100.", "203.0.113.")):
625
+ err(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-IP: IP-Adresse")
626
+ for m in INTERNAL_HOST_RE.finditer(text):
627
+ err(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-HOST: interner Hostname")
628
+ for m in URL_RE.finditer(text):
629
+ if not any(host in m.group(0) for host in URL_ALLOWLIST):
630
+ warn(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-URL: URL außerhalb der Allowlist")
631
+ for term in forbidden_terms:
632
+ m = re.search(rf"(?i)\b{re.escape(term)}\b", text)
633
+ if m:
634
+ err(f"{fundstelle(rel, text, m.start())}: FW-CONTENT-TERM: gesperrter Begriff")
635
+ if FENCE4_RE.search(text) and rel.startswith(
636
+ (".devin/", ".claude/", ".koolie/project-overlay/") +
637
+ tuple(f"{KERN}/{d}/" for d in ("framework", "prompts", "checklists", "decision-trees",
638
+ "onboarding", "templates", "governance", "pilot",
639
+ "build", "docs", "examples"))):
640
+ err(f"{rel}: Codeblock mit vier oder mehr Backticks (bricht die Dokumentassemblierung)")
641
+ if registry and rel not in OHNE_PLATZHALTERREGISTER:
642
+ for ph in set(PLACEHOLDER_RE.findall(text)):
643
+ if ph not in registry and ph not in ("TBD",):
644
+ unknown_placeholders.setdefault(ph, set()).add(rel)
645
+ for ph, files in sorted(unknown_placeholders.items()):
646
+ warn(f"Platzhalter <{ph}> nicht in .koolie/core/docs/PLACEHOLDER_REGISTRY.md (in {', '.join(sorted(files)[:3])}{'…' if len(files) > 3 else ''})")
647
+
648
+
649
+ def _normalize_target(raw: str):
650
+ """Bereinigt eine Verweisangabe zu einem Pfad oder liefert None, wenn keiner vorliegt."""
651
+ t = raw.strip()
652
+ if not t or t.startswith(("http://", "https://", "mailto:", "#", "//")):
653
+ return None
654
+ t = t.split("#", 1)[0] # Anker abtrennen
655
+ t = re.sub(r":\d+(?:-\d+)?$", "", t) # Fundstellenformat pfad/datei:zeile
656
+ t = t.rstrip(".,;:") # Satzzeichen am Satzende
657
+ if not t or t in (".", ".."):
658
+ return None
659
+ if t.endswith("-"): # Präfixnennung, z. B. ".devin/rules/00-"
660
+ return None
661
+ if "…" in t or t.endswith("..."): # Sammelnennung, z. B. "framework/core/…"
662
+ return None
663
+ if any(c in NOT_A_PATH for c in t):
664
+ return None
665
+ return t
666
+
667
+
668
+ def _client_runtime_paths(root: str) -> dict:
669
+ """Laufzeitpfade aller Client Packs: Praefix -> Clientname.
670
+
671
+ Damit laesst sich unterscheiden, ob eine nicht aufloesbare Pfadangabe ein echter
672
+ toter Verweis ist oder die Nennung eines Laufzeitpfads, der zu einem anderen
673
+ Client Pack gehoert. Letzteres ist im werkzeugneutralen Kern eine Altlast,
674
+ kein Fehler - aber es gehoert sichtbar gemacht.
675
+ """
676
+ out = {}
677
+ base = os.path.join(root, KERN, "clients")
678
+ if not os.path.isdir(base):
679
+ return out
680
+ for name in sorted(os.listdir(base)):
681
+ mp = os.path.join(base, name, "manifest.json")
682
+ if not os.path.exists(mp):
683
+ continue
684
+ try:
685
+ man = json.loads(read(mp))
686
+ except json.JSONDecodeError:
687
+ continue
688
+ for key in ("runtime_dir", "root_instruction_file"):
689
+ if man.get(key):
690
+ out[man[key]] = man["client"]
691
+ return out
692
+
693
+
694
+ def _link_roots(root: str) -> tuple:
695
+ """Wurzeln der Backtick-Heuristik: die festen des Frameworks und die jedes Packs.
696
+
697
+ Die Laufzeitwurzeln stammen aus den Manifesten, damit ein neues Client Pack ohne
698
+ Aenderung an dieser Pruefung erfasst wird - dieselbe Bauform wie
699
+ _client_actor_names in Pruefung 14.
700
+ """
701
+ runtime: set[str] = set()
702
+ base = os.path.join(root, KERN, "clients")
703
+ if os.path.isdir(base):
704
+ for name in sorted(os.listdir(base)):
705
+ if name.startswith("_"):
706
+ continue
707
+ mp = os.path.join(base, name, "manifest.json")
708
+ if not os.path.exists(mp):
709
+ continue
710
+ try:
711
+ man = json.loads(read(mp))
712
+ except json.JSONDecodeError:
713
+ continue
714
+ if man.get("runtime_dir"):
715
+ runtime.add(man["runtime_dir"].rstrip("/") + "/")
716
+ for key in ("root_instruction_file", "root_instruction_local"):
717
+ if man.get(key):
718
+ runtime.add(man[key])
719
+ return LINK_ROOTS_FEST + tuple(sorted(runtime))
720
+
721
+
722
+ def _target_exists(root: str, src_rel: str, target: str) -> bool:
723
+ """Prüft repo-relativ, relativ zur verweisenden Datei und - für Dateien innerhalb
724
+ eines Client Packs - relativ zu dessen root-template."""
725
+ base = os.path.basename(target)
726
+ # Nutzerlokale Dateien (Namensbestandteil ".local.") sind per .gitignore bewusst
727
+ # nicht versioniert - CLAUDE.local.md, .claude/settings.local.json und Ähnliches.
728
+ # Ein Verweis darauf beschreibt eine Möglichkeit, keine vorhandene Datei.
729
+ if ".local." in base or base.endswith(".local"):
730
+ return True
731
+ # Die Angabe des Lieferumfangs schreibt install.py erst im Projekt (D-367); im
732
+ # Quellrepositorium beschreibt ein Verweis darauf die Installation, nicht diesen Baum.
733
+ cm = _clientmap(root)
734
+ if cm is not None and target == f"{KERN}/{cm.LIEFERUMFANG_DATEI}":
735
+ return True
736
+ # Eine mitgelieferte Vorlage zählt als Nachweis: Dateien wie .mcp.json legt die
737
+ # nutzende Person selbst aus der .example-Fassung an.
738
+ for cand in (target, target + ".example", target + ".template"):
739
+ if os.path.exists(os.path.join(root, cand)):
740
+ return True
741
+ src_dir = os.path.dirname(os.path.join(root, src_rel))
742
+ if os.path.exists(os.path.normpath(os.path.join(src_dir, target))):
743
+ return True
744
+ # Eine Datei im root-template eines Client Packs beschreibt die Laufzeitschicht
745
+ # dieses Packs, nicht die installierte. Ihre Pfade dort aufloesen.
746
+ m = re.match(r"(\.koolie/core/clients/[^/]+/root-template)/", src_rel)
747
+ if m and os.path.exists(os.path.join(root, *m.group(1).split("/"), *target.split("/"))):
748
+ return True
749
+ return False
750
+
751
+
752
+ def check_links(root: str) -> None: # noqa: C901
753
+ """FW-KO-04: Querverweise zeigen auf existierende Dateien oder Verzeichnisse.
754
+
755
+ Zwei Quellen: Markdown-Links (überall geprüft) und in Backticks genannte
756
+ Framework-Pfade (Heuristik, in LINK_EXCEPTIONS ausgesetzt). Globs, Platzhalter
757
+ und Befehlszeilen werden über NOT_A_PATH verworfen statt gemeldet.
758
+ """
759
+ fremdpfade = _client_runtime_paths(root)
760
+ link_roots = _link_roots(root)
761
+ herkunft = {} # Traeger -> Zahl der Verweise in die nicht gelieferte Nachweisschicht
762
+
763
+ def geliefert_fehlt(rel: str, target: str) -> bool:
764
+ """Zeigt der Verweis in die Nachweisschicht, die dieser Umfang nicht liefert?
765
+ Dann ist er eine Herkunftsangabe und wird gezaehlt statt gemeldet (D-367)."""
766
+ ziel = os.path.normpath(os.path.join(os.path.dirname(rel), target)).replace(os.sep, "/")
767
+ if nicht_geliefert(root, target.rstrip("/")) or nicht_geliefert(root, ziel):
768
+ herkunft[rel] = herkunft.get(rel, 0) + 1
769
+ return True
770
+ return False
771
+
772
+ for path in iter_text_files(root):
773
+ if not path.endswith((".md", ".template")):
774
+ continue
775
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
776
+ if rel.startswith(".koolie/core/tests/scripts/"):
777
+ continue
778
+ text = read(path)
779
+
780
+ for m in MD_LINK_RE.finditer(text):
781
+ target = _normalize_target(m.group(1))
782
+ if target is not None and not _target_exists(root, rel, target) and not geliefert_fehlt(rel, target):
783
+ err(f"{rel}: Markdown-Link zeigt ins Leere: {m.group(1)}")
784
+
785
+ if rel.startswith(LINK_EXCEPTIONS) or os.path.basename(rel) in LINK_EXCEPTION_BASENAMES:
786
+ continue
787
+ seen = set()
788
+ for m in BACKTICK_RE.finditer(text):
789
+ tok = m.group(1).strip()
790
+ if not tok.startswith(link_roots):
791
+ continue
792
+ target = _normalize_target(tok)
793
+ if target is None or target in seen:
794
+ continue
795
+ seen.add(target)
796
+ if _target_exists(root, rel, target):
797
+ continue
798
+ # Gehoert der Pfad zur Laufzeitschicht eines anderen Client Packs, ist es
799
+ # keine tote Referenz, sondern eine Client-Bindung im Kern (Arbeitsliste
800
+ # fuer die Neutralisierung).
801
+ fremd = next((c for pfx, c in fremdpfade.items()
802
+ if target == pfx or target.startswith(pfx.rstrip("/") + "/")), None)
803
+ # Eine Datei im root-template eines Packs beschreibt dessen eigene
804
+ # Laufzeitschicht - dort ist der Pfad richtig, nicht eine Altlast.
805
+ if fremd and re.match(r"\.koolie/core/clients/[^/]+/root-template/", rel):
806
+ continue
807
+ # Ein Pfad eines anderen Packs ist kein toter Verweis, sondern eine
808
+ # Client-Bindung. Seit 0.57.0 meldet sie Pruefung 48 - als Fehler, ueber
809
+ # alle Traeger und ohne die drei Grenzen dieser Heuristik (D-128).
810
+ if not fremd and not geliefert_fehlt(rel, target):
811
+ err(f"{rel}: Pfadangabe existiert nicht: {tok}")
812
+ if herkunft:
813
+ hinweis(f"{sum(herkunft.values())} Verweis(e) aus {len(herkunft)} Träger(n) zeigen "
814
+ f"in die Nachweisschicht, die dieser Lieferumfang nicht mitliefert - "
815
+ f"Herkunftsangaben, nicht gemeldet (Prüfung 12, D-367)")
816
+
817
+
818
+ RUNTIME_PLACEHOLDER_RE = re.compile(
819
+ r"<(RUNTIME_DIR|ROOT_INSTRUCTION_FILE|ROOT_INSTRUCTION_LOCAL|PERMISSIONS_FILE"
820
+ r"|SKILLS_DIR|RULES_DIR|AGENTS_DIR|HOOKS_FILE|MCP_FILE|CLIENT_NAME)>")
821
+
822
+
823
+ def check_runtime_placeholders(root: str, man: dict) -> None:
824
+ """In der installierten Laufzeitschicht darf kein Laufzeit-Platzhalter stehen.
825
+
826
+ Sie werden von install.py aus dem Manifest aufgeloest. Bleibt einer stehen, fehlt
827
+ er im Manifest des Client Packs - der Text verweist dann ins Leere.
828
+ """
829
+ rt = os.path.join(root, *man["runtime_dir"].split("/"))
830
+ if not os.path.isdir(rt):
831
+ return
832
+ for dirpath, dirnames, filenames in os.walk(rt):
833
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
834
+ for fn in filenames:
835
+ if os.path.splitext(fn)[1] not in TEXT_EXT:
836
+ continue
837
+ p = os.path.join(dirpath, fn)
838
+ gefunden = {m.group(0) for m in RUNTIME_PLACEHOLDER_RE.finditer(read(p))}
839
+ if gefunden:
840
+ err(f"{os.path.relpath(p, root)}: unaufgelöste Laufzeit-Platzhalter "
841
+ f"{', '.join(sorted(gefunden))} – fehlen in runtime_placeholders des Client Packs")
842
+
843
+
844
+ def check_manifest(root: str) -> None:
845
+ path = os.path.join(root, ".koolie/project-overlay", "overlay-manifest.yaml")
846
+ if not os.path.exists(path) or yaml is None:
847
+ return
848
+ try:
849
+ data = yaml.safe_load(read(path)) or {}
850
+ except yaml.YAMLError as exc: # type: ignore[attr-defined]
851
+ err(f"overlay-manifest.yaml ungültig: {exc}")
852
+ return
853
+ types = {"ai-governance", "ai-process-model", "roadmap", "architecture", "coding-guidelines",
854
+ "definition-of-ready", "definition-of-done", "branching-strategy", "deployment",
855
+ "security", "quality", "roles", "glossary", "other"}
856
+ for key in ("manifest_version", "project_code", "overlay_version"):
857
+ if key not in data:
858
+ err(f"overlay-manifest.yaml: Kopfschlüssel {key} fehlt")
859
+ seen = set()
860
+ for doc in data.get("documents", []) or []:
861
+ did = doc.get("id", "?")
862
+ if did in seen:
863
+ err(f"overlay-manifest.yaml: doppelte id {did}")
864
+ seen.add(did)
865
+ for key in ("id", "type", "title", "path", "context_class", "status", "load", "approved_by"):
866
+ if key not in doc:
867
+ err(f"overlay-manifest.yaml {did}: Feld {key} fehlt")
868
+ if doc.get("type") not in types:
869
+ err(f"overlay-manifest.yaml {did}: type '{doc.get('type')}' unbekannt")
870
+ if doc.get("context_class") not in ("K1", "K2"):
871
+ err(f"overlay-manifest.yaml {did}: context_class muss K1 oder K2 sein (K3 wird nicht registriert)")
872
+ if doc.get("status") not in ("entwurf", "aktuell", "veraltet"):
873
+ err(f"overlay-manifest.yaml {did}: status unbekannt")
874
+ if doc.get("load") not in ("summary", "on-demand", "rule", "never"):
875
+ err(f"overlay-manifest.yaml {did}: load unbekannt")
876
+ if doc.get("load") == "rule" and not doc.get("rule_file"):
877
+ err(f"overlay-manifest.yaml {did}: load rule ohne rule_file")
878
+ if doc.get("load") == "summary" and doc.get("context_class") == "K2":
879
+ err(f"overlay-manifest.yaml {did}: summary-Laden nur für K1 zulässig")
880
+ # Der registrierte Traeger existiert (CR-2026-139, D-360). Bis 1.5.0 pruefte
881
+ # diese Pruefung Schluessel und Aufzaehlungswerte, aber nicht, ob unter `path`
882
+ # etwas liegt - ein Register, das auf nichts zeigt, bestand. Ein Eintrag, dessen
883
+ # Pfad noch einen Ausfuellschlitz traegt, ist ein Beispiel der Vorlage und kein
884
+ # Traeger; er bleibt aussen vor, und mit ihm seine Regeldatei.
885
+ pfad = str(doc.get("path") or "")
886
+ if pfad and "<" not in pfad:
887
+ if not os.path.exists(os.path.join(root, *pfad.split("/"))):
888
+ err(f"overlay-manifest.yaml {did}: path '{pfad}' existiert nicht. Ein "
889
+ f"registriertes Dokument, das nicht da ist, sieht im Register aus wie "
890
+ f"eines, das der KI-Client lesen darf (D-360)")
891
+ regel = str(doc.get("rule_file") or "")
892
+ if doc.get("load") == "rule" and regel and "<" not in regel \
893
+ and not os.path.exists(os.path.join(root, *regel.split("/"))):
894
+ err(f"overlay-manifest.yaml {did}: rule_file '{regel}' existiert nicht. "
895
+ f"Mit load rule trägt die Regeldatei die Kernaussagen des Dokuments; "
896
+ f"fehlt sie, wirkt das Dokument nicht (D-360)")
897
+
898
+
899
+ SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
900
+
901
+
902
+ def _overlay_version_angaben(root: str, man: dict) -> list[tuple[str, str]]:
903
+ """Alle Stellen, an denen ein Overlay seine Version *erklaert*, als (Datei, Wert).
904
+
905
+ Die Version steht dreimal: im Steckbrief, im Manifest und in der Laufzeitfassung.
906
+ Bis 0.12.0 pruefte der Validator nur, ob der Manifestschluessel vorhanden ist - die
907
+ drei Werte konnten beliebig auseinanderlaufen (FW-VN-01, Sonden S2 und S3). Das ist
908
+ dieselbe Luecke, die D-23 fuer den Overlay-*Status* geschlossen hat.
909
+ """
910
+ angaben: list[tuple[str, str]] = []
911
+ quellen = [
912
+ os.path.join(root, ".koolie/project-overlay", "OVERLAY.md"),
913
+ os.path.join(root, *man["pack_runtime_dir"].split("/"), regeldatei(man, "20-project-overlay.md")),
914
+ ]
915
+ for path in quellen:
916
+ if not os.path.exists(path):
917
+ continue
918
+ text = read(path)
919
+ # Schraegstriche wie in den uebrigen Meldungen: Unter Windows liefert relpath
920
+ # Backslashes, und eine gemischte Schreibweise war in D-23 bereits ein echter Fehler.
921
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
922
+ for m in re.finditer(r"^\|\s*Overlay-Version\s*\|\s*`?([^`|]+)", text, re.M):
923
+ angaben.append((rel, m.group(1).strip()))
924
+ for m in re.finditer(r"^-?\s*Overlay-Version:\s*`?([^`\n·]+)", text, re.M):
925
+ angaben.append((rel, m.group(1).strip()))
926
+ mpath = os.path.join(root, ".koolie/project-overlay", "overlay-manifest.yaml")
927
+ if os.path.exists(mpath) and yaml is not None:
928
+ try:
929
+ data = yaml.safe_load(read(mpath)) or {}
930
+ except yaml.YAMLError: # type: ignore[attr-defined]
931
+ data = {}
932
+ if "overlay_version" in data:
933
+ angaben.append((".koolie/project-overlay/overlay-manifest.yaml",
934
+ str(data["overlay_version"]).strip()))
935
+ return angaben
936
+
937
+
938
+ def check_versions(root: str, man: dict) -> None:
939
+ """Pruefung 13 - die Nachweiskette aus governance/RELEASE_PROCESS.md Abschnitt 8.
940
+
941
+ Geprueft wird nicht, ob Versionsfelder *da* sind - das taten die Pruefungen 5 und 8
942
+ schon -, sondern ob sie *stimmen*. Genau diese Unterscheidung hat FW-VN-01 als Luecke
943
+ ausgewiesen: Eine Versionsangabe, die keiner anderen widersprechen kann, unterscheidet
944
+ keine zwei Zeitpunkte.
945
+ """
946
+ # Overlay-Version: alle Ablageorte muessen denselben Wert nennen. Noch nicht
947
+ # ausgefuellte Vorlagen (<TBD: ...>) bleiben aussen vor - sie erklaeren nichts.
948
+ angaben = [(rel, wert) for rel, wert in _overlay_version_angaben(root, man)
949
+ if not TBD_RE.search(wert)]
950
+ werte = {wert for _, wert in angaben}
951
+ if len(werte) > 1:
952
+ stellen = "; ".join(f"{rel}: {wert}" for rel, wert in angaben)
953
+ if gueltiges_mandat(root):
954
+ # D-461 (K-182 (3)): Waehrend eines Mandats traegt der Client die Version in den
955
+ # Steckbrief ein; Manifest und Laufzeitfassung zieht erst der Abgleich nach,
956
+ # den 'mandat.py beenden' ausloest. Der Zwischenstand ist gewollt.
957
+ warn(f"Overlay-Version während eines Mandats noch nicht abgeglichen ({stellen}). "
958
+ f"Das ist der gewollte Zwischenstand einer Eintragung in M6; die Person "
959
+ f"schließt ihn im eigenen Terminal ab: python {KERN}/mandat.py beenden "
960
+ f"(D-461)")
961
+ else:
962
+ err(f"Overlay-Version widersprüchlich angegeben ({stellen}). Steckbrief, Manifest "
963
+ f"und Laufzeitfassung müssen denselben Wert nennen (RELEASE_PROCESS 8)")
964
+ for rel, wert in angaben:
965
+ if not SEMVER_RE.match(wert):
966
+ err(f"{rel}: Overlay-Version '{wert}' ist kein Semantic Versioning "
967
+ f"(MAJOR.MINOR.PATCH)")
968
+
969
+ # Kompatible Framework-Version des Steckbriefs gegen die ausgelieferte Version.
970
+ vpath = os.path.join(root, KERN, "VERSION")
971
+ opath = os.path.join(root, ".koolie/project-overlay", "OVERLAY.md")
972
+ if not (os.path.exists(vpath) and os.path.exists(opath)):
973
+ return
974
+ version = read(vpath).strip()
975
+ if not SEMVER_RE.match(version):
976
+ err(f"{KERN}/VERSION: '{version}' ist kein Semantic Versioning (MAJOR.MINOR.PATCH)")
977
+ return
978
+ m = re.search(r"^\|\s*Kompatible Framework-Version\s*\|\s*`?([^`|]+)", read(opath), re.M)
979
+ if not m:
980
+ return
981
+ angabe = m.group(1).strip()
982
+ if TBD_RE.search(angabe):
983
+ return
984
+ major, minor, _ = version.split(".")
985
+ if angabe not in (version, f"{major}.{minor}.x"):
986
+ err(f".koolie/project-overlay/OVERLAY.md: Kompatible Framework-Version '{angabe}' passt nicht "
987
+ f"zu {KERN}/VERSION ({version}). Erwartet '{major}.{minor}.x' oder '{version}' – "
988
+ f"nach einer Aktualisierung ist der Steckbrief nachzuziehen "
989
+ f"(docs/ADOPTION_GUIDE.md, Abschnitt Aktualisierung)")
990
+
991
+
992
+ # Der Status steht zweimal: als Zeile im Steckbrief und als Aussage im
993
+ # Aktivierungsabschnitt. Beide Schreibweisen liest overlay_status.status_angaben; sie
994
+ # stand bis 0.32.0 hier und im Status-Hook getrennt, mit verschiedenem Verhalten (B08).
995
+
996
+
997
+ # Steckbriefzeile eines versionierten Artefakts - genau zwei Spalten. Die Verankerung
998
+ # auf das Zeilenende ist noetig: Der Aenderungsverlauf eines Overlays beginnt mit der
999
+ # Kopfzeile '| Version | Datum | Aenderung | ... |', und die ist kein Steckbrief. "(Skill)" kommt in einzelnen
1000
+ # Steckbriefen vor; die Klammer gehoert zur Beschriftung, nicht zum Wert.
1001
+ ARTEFAKT_VERSION_RE = re.compile(
1002
+ r"^\|\s*Version(?:\s*\([^)]*\))?\s*\|\s*`?([^`|]+?)`?\s*\|\s*$", re.M)
1003
+
1004
+
1005
+ def check_artefakt_versionen(root: str) -> None:
1006
+ """Teil von Pruefung 13: Die Versionsfelder der Kernartefakte haben die Form
1007
+ MAJOR.MINOR.PATCH.
1008
+
1009
+ Der Kopfkommentar sagte diese Pruefung seit 0.13.0 zu; tatsaechlich deckte
1010
+ check_versions nur die Overlay-Version, <CORE_DIR>/VERSION und die Steckbriefangabe
1011
+ ab. Ein Artefaktfeld '0.1' oder 'abc' lief mit 0 Fehlern durch - aufgefallen bei der
1012
+ Regressionsprobe R1 zu CR-2026-020, waehrend 62 Versionsfelder von Hand gehoben
1013
+ wurden, ohne dass irgendetwas das Ergebnis geprueft haette.
1014
+
1015
+ Historische Dokumente sind ausgenommen: Ein Aenderungsverlauf listet Versionen in
1016
+ Tabellenzeilen, nicht in einem Steckbrief, und beschreibt einen vergangenen Zustand.
1017
+ """
1018
+ for path in iter_text_files(root):
1019
+ if not path.endswith(".md"):
1020
+ continue
1021
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
1022
+ if not rel.startswith(KERN + "/") or rel.startswith(KERN + "/clients/"):
1023
+ continue
1024
+ if (rel.startswith(OHNE_ARTEFAKTVERSION)
1025
+ or os.path.basename(rel) in NEUTRAL_CHRONIK_BASENAMES):
1026
+ continue
1027
+ m = ARTEFAKT_VERSION_RE.search(read(path))
1028
+ if not m:
1029
+ continue
1030
+ wert = m.group(1).strip()
1031
+ if not wert or TBD_RE.search(wert):
1032
+ continue
1033
+ if not SEMVER_RE.match(wert):
1034
+ err(f"{rel}: Versionsfeld '{wert}' ist kein Semantic Versioning "
1035
+ f"(MAJOR.MINOR.PATCH). Eine Angabe, die keiner Form folgt, laesst sich "
1036
+ f"nicht vergleichen - und eine Version, die sich nicht vergleichen laesst, "
1037
+ f"unterscheidet keine zwei Zeitpunkte (D-25, FW-VN-01)")
1038
+ # Die Abbildungstabellen und die Client Packs muessen beide Namen nennen.
1039
+ NEUTRAL_ABBILDUNG = (
1040
+ KERN + "/docs/RUNTIME_GLOSSARY.md",
1041
+ KERN + "/docs/PLACEHOLDER_REGISTRY.md",
1042
+ KERN + "/clients/",
1043
+ )
1044
+ NEUTRAL_AUSNAHMEN = NEUTRAL_CHRONIK + NEUTRAL_ABBILDUNG + NEUTRAL_DOKUMENT
1045
+
1046
+ # --- Die Ausnahme, die bis 0.89.0 ein Verzeichnis war (D-313) ----------------------
1047
+ #
1048
+ # Die beiden Werkzeuge unter build/ und ihre README fuehren eigene Marker in spitzen
1049
+ # Klammern - <CORE> und <VERSION> stehen dort fuer den Wert, den das Werkzeug zur
1050
+ # Laufzeit einsetzt. Sie sind KEINE Framework-Platzhalter und gehoeren in kein Register.
1051
+ #
1052
+ # 🔴 GENAU DAS WAR BIS 0.89.0 DER GRUND, DIE DREI TRAEGER GANZ AUS iter_text_files
1053
+ # HERAUSZUHALTEN - eine Begruendung fuer EINE Frage, wirksam auf ZWOELF Pruefungen.
1054
+ # Was sie mitverdeckt hat, steht im Kopfkommentar von iter_text_files.
1055
+ OHNE_PLATZHALTERREGISTER = (
1056
+ KERN + "/build/assemble.py",
1057
+ KERN + "/build/build-docx.py",
1058
+ KERN + "/build/README.md",
1059
+ )
1060
+
1061
+ # Eine ANDERE Frage mit derselben Antwortliste, und deshalb eine eigene Konstante:
1062
+ # Welches Dokument traegt ueberhaupt eine eigene Artefaktversion? Chronik traegt keine -
1063
+ # aber docs/ROADMAP.md traegt eine und gehoert geprueft. Bis 0.57.0 teilten sich
1064
+ # Pruefung 13 und Pruefung 14 eine Liste; wer sie fuer den einen Zweck erweitert, haette
1065
+ # sie fuer den anderen stillschweigend mit erweitert.
1066
+ OHNE_ARTEFAKTVERSION = (
1067
+ KERN + "/CHANGELOG.md",
1068
+ KERN + "/governance/change-requests/",
1069
+ KERN + "/governance/DECISION_LOG.md",
1070
+ KERN + "/tests/protocols/",
1071
+ )
1072
+
1073
+
1074
+ def neutral_ausgenommen(rel: str) -> bool:
1075
+ """Wahr, wenn dieser Traeger von der Werkzeugneutralitaet ausgenommen ist."""
1076
+ return (rel.startswith(NEUTRAL_AUSNAHMEN)
1077
+ or os.path.basename(rel) in NEUTRAL_CHRONIK_BASENAMES)
1078
+
1079
+
1080
+ def _client_actor_names(root: str) -> list[tuple[str, str]]:
1081
+ """Kapitalisierter Produktname je Client Pack, abgeleitet aus dessen Kennung.
1082
+
1083
+ Aus der Kennung, nicht aus einer gepflegten Liste: Ein neues Client Pack bringt
1084
+ seinen Namen damit selbst mit und wird ohne Aenderung an dieser Pruefung erfasst.
1085
+ """
1086
+ namen = set()
1087
+ cdir = os.path.join(root, ".koolie/core", "clients")
1088
+ if not os.path.isdir(cdir):
1089
+ return []
1090
+ for name in sorted(os.listdir(cdir)):
1091
+ mf = os.path.join(cdir, name, "manifest.json")
1092
+ if not os.path.isfile(mf):
1093
+ continue
1094
+ try:
1095
+ kennung = json.loads(read(mf)).get("client", name)
1096
+ except Exception:
1097
+ kennung = name
1098
+ teile = [w.capitalize() for w in kennung.split("-")]
1099
+ namen.add((teile[0], "-".join(teile)))
1100
+ return sorted(namen)
1101
+
1102
+
1103
+ def check_actor_naming(root: str) -> None:
1104
+ """Pruefung 14 (D-02, D-129): Im Kern steht kein Clientname - auch nicht als Produkt.
1105
+
1106
+ BIS 0.57.0 GALT EINE AUSNAHME: Der Produktname MIT ZUSATZ war zulaessig, weil er
1107
+ "ein Produkt benennt und nicht den Handelnden" (D-28). Am 2026-09-18 ist ihr
1108
+ Geltungsbereich ausgezaehlt worden (CR-2026-081): FUENFZEHN Nennungen in ZWOELF
1109
+ anweisenden Traegern - und in KEINER EINZIGEN wurde der Name blosse genannt. Jede
1110
+ trug etwas: einen Geltungsbereich ("Das Framework regelt den Einsatz von <Produkt>"),
1111
+ eine Produktaussage ("<Produkt> fordert vor jedem MCP-Aufruf eine Bestaetigung an")
1112
+ oder eine Voraussetzung ("Zugang zu <Produkt> vorhanden"). Die Ausnahme hatte in
1113
+ ihrem eigenen Geltungsbereich KEINEN EINZIGEN berechtigten Fall - Client Packs und
1114
+ Chronik sind ohnehin ausgenommen.
1115
+
1116
+ WAS BLEIBT, IST DER UNTERSCHIED ZWISCHEN NENNEN UND ZUSCHREIBEN:
1117
+ * NENNEN - der Text traegt den Namen, sagt aber nichts ueber das Produkt. Dafuer
1118
+ gibt es <CLIENT_NAME>, und er loest sich auf:
1119
+ framework/runtime/root-instruction.md traegt ihn im Titel. Das ist der einzige
1120
+ angewandte Fall im ganzen Bestand - gezaehlt, nicht geschaetzt.
1121
+ * ZUSCHREIBEN - der Text sagt etwas UEBER das Produkt. Das gehoert in dessen
1122
+ Client Pack; der Kern verweist auf die Faehigkeitsmatrix.
1123
+ Ein ausgeschriebener Produktname kann beides sein, und kein Skript kann es
1124
+ unterscheiden - deshalb ist er im Kern jetzt gar nicht mehr zulaessig. Dieselbe
1125
+ Lehre wie bei <TBD...> in 0.52.0: Eine Marke mit zwei Bedeutungen taugt weder als
1126
+ Bedingung noch als Entlastung.
1127
+
1128
+ GRENZE. Sie sucht den kapitalisierten Namen aus der Pack-Kennung. Eine Umschreibung
1129
+ ("das Werkzeug aus Kapitel 3") laeuft durch - dieselbe Ehrlichkeit wie bei
1130
+ Pruefung 48, die Pfade findet und keine Prosa.
1131
+ """
1132
+ namen = _client_actor_names(root)
1133
+ if not namen:
1134
+ err(f"{KERN}/clients/: kein Client Pack mit manifest.json gefunden - Pruefung 14 "
1135
+ f"leitet ihre Namen daraus ab und hat ihren Gegenstand verloren; sie bestuende "
1136
+ f"sonst leise (D-23)")
1137
+ return
1138
+ muster = re.compile(r"\b(%s)\b" % "|".join(k for k, _ in namen))
1139
+ for path in iter_text_files(root):
1140
+ if not path.endswith((".md", ".py", ".template")):
1141
+ continue
1142
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
1143
+ if not rel.startswith(KERN + "/") or neutral_ausgenommen(rel):
1144
+ continue
1145
+ for i, zeile in enumerate(read(path).splitlines(), 1):
1146
+ m = muster.search(zeile)
1147
+ if not m:
1148
+ continue
1149
+ err(f"{rel}:{i}: '{m.group(1)}' nennt ein Client-Produkt; der Kern ist "
1150
+ f"werkzeugneutral (D-02, D-129). Sagt der Text etwas UEBER das Produkt, "
1151
+ f"gehoert es in dessen Client Pack und der Kern verweist auf die "
1152
+ f"Faehigkeitsmatrix; benennt er es nur, steht dort <CLIENT_NAME> - und der "
1153
+ f"loest sich nur in einer gerenderten Quelle auf")
1154
+
1155
+
1156
+ # Ein Platzhalter, der einen Clientnamen traegt, bindet den Kern genauso an ein Produkt
1157
+ # wie eine Akteursnennung - nur faellt er dort nicht auf, weil er in Grossbuchstaben
1158
+ # steht. Das Register selbst ist ausgenommen; es steht seit 0.57.1 als
1159
+ # Abbildungstabelle in NEUTRAL_AUSNAHMEN und braucht keine eigene Liste mehr.
1160
+ PLATZHALTER_RE = re.compile(r"<([A-Z][A-Z0-9_ ]{2,80})>")
1161
+
1162
+
1163
+ def check_placeholder_naming(root: str) -> None:
1164
+ """Teil von Pruefung 14: Kein Platzhalter des Kerns traegt einen Clientnamen.
1165
+
1166
+ Der Marker 'VERIFY AGAINST CURRENT <name> DOCUMENTATION' stand acht Releases im Kern,
1167
+ obwohl die clientneutrale Form daneben im Register gefuehrt wurde. Die Akteurspruefung
1168
+ fand ihn nicht: Sie sucht den kapitalisierten Namen, der Marker schreibt ihn gross.
1169
+
1170
+ Der Anlassfall ist mit 0.87.0 entfallen - beide Markerformen sind abgeschafft (D-291).
1171
+ Die Pruefung bleibt: Ihr Gegenstand ist JEDER Platzhalter des Kerns, nicht dieser eine,
1172
+ und die Sonde bringt ihren Fall selbst mit.
1173
+ """
1174
+ namen = _client_actor_names(root)
1175
+ if not namen:
1176
+ return
1177
+ gesucht = {k.upper() for k, _ in namen}
1178
+ for path in iter_text_files(root):
1179
+ if not path.endswith((".md", ".py", ".template")):
1180
+ continue
1181
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
1182
+ if not rel.startswith(KERN + "/") or rel.startswith(KERN + "/clients/"):
1183
+ continue
1184
+ if neutral_ausgenommen(rel):
1185
+ continue
1186
+ for i, zeile in enumerate(read(path).splitlines(), 1):
1187
+ for m in PLATZHALTER_RE.finditer(zeile):
1188
+ # An Leerzeichen UND Unterstrichen trennen: Ein Platzhalter wie
1189
+ # 'NAME_PROJECT_DIR' bindet genauso an ein Produkt wie einer, der den
1190
+ # Namen als eigenes Wort fuehrt.
1191
+ treffer = gesucht & set(re.split(r"[ _]+", m.group(1)))
1192
+ if treffer:
1193
+ err(f"{rel}:{i}: Der Platzhalter <{m.group(1)}> traegt den Clientnamen "
1194
+ f"'{sorted(treffer)[0]}'. Ein Platzhalter bindet den Kern damit an ein "
1195
+ f"Produkt (D-02); die clientneutrale Form gehoert in den Kern, die "
1196
+ f"clientgebundene in das Client Pack")
1197
+
1198
+
1199
+ def check_mermaid(root: str) -> None:
1200
+ mmdc = shutil.which("mmdc")
1201
+ if not mmdc:
1202
+ warn("mmdc nicht installiert – Mermaid-Syntaxprüfung übersprungen")
1203
+ return
1204
+ # Derselbe Browser und dieselbe Konfiguration wie beim Bau (K-145, D-398): Ohne sie
1205
+ # scheiterte der Renderer auf einem Arbeitsplatz ohne den Browser von Puppeteer an
1206
+ # JEDEM Block, und die Pruefung meldete Diagramme als ungueltig, die der Bau renderte.
1207
+ browser = mermaid_renderer.browserpfad()
1208
+ if browser is None:
1209
+ warn("kein Chromium-artiger Browser für den Mermaid-Renderer gefunden – "
1210
+ "Mermaid-Syntaxprüfung übersprungen (Pfad über PUPPETEER_EXECUTABLE_PATH)")
1211
+ return
1212
+ with tempfile.TemporaryDirectory() as konf:
1213
+ pptr = mermaid_renderer.puppeteer_konfiguration(
1214
+ os.path.join(konf, "puppeteer-config.json"), browser)
1215
+ _mermaid_bloecke(root, mmdc, pptr, browser)
1216
+
1217
+
1218
+ def _mermaid_bloecke(root: str, mmdc: str, pptr: str, browser: str) -> None:
1219
+ for path in iter_text_files(root):
1220
+ if not path.endswith(".md"):
1221
+ continue
1222
+ text = read(path)
1223
+ for i, block in enumerate(MERMAID_RE.findall(text), 1):
1224
+ with tempfile.TemporaryDirectory() as td:
1225
+ src = os.path.join(td, "d.mmd")
1226
+ out = os.path.join(td, "d.svg")
1227
+ with open(src, "w", encoding="utf-8") as fh:
1228
+ fh.write(block)
1229
+ res = subprocess.run([mmdc, "-i", src, "-o", out, "-q", "-p", pptr],
1230
+ capture_output=True, text=True, encoding="utf-8",
1231
+ errors="replace")
1232
+ if res.returncode != 0 and mermaid_renderer.ist_umgebungsfehler(res.stderr):
1233
+ # Der Renderer ist an der Umgebung gescheitert, nicht am Block - dann
1234
+ # scheitert er an jedem Block gleich. Eine Warnung, einmal, und keine
1235
+ # Aussage ueber die Diagramme (K-145, D-398).
1236
+ warn(f"Mermaid-Renderer startet den Browser nicht ({os.path.basename(browser)}) "
1237
+ f"– Mermaid-Syntaxprüfung abgebrochen, kein Block beurteilt")
1238
+ return
1239
+ if res.returncode != 0:
1240
+ # Die Fehlerausgabe des Renderers zitiert den Quelltext des Blocks. Sie
1241
+ # hier auszugeben traegt Diagramminhalt in Terminal und Protokoll - genau
1242
+ # der Fehler, den B03 fuer die Inhaltsdiagnosen beanstandet (D-39). Der
1243
+ # Block bleibt ueber Datei und Nummer auffindbar; wer die Meldung des
1244
+ # Renderers braucht, ruft ihn von Hand auf.
1245
+ err(f"{os.path.relpath(path, root)}: Mermaid-Block {i} ungültig "
1246
+ f"(Renderer-Exitcode {res.returncode}; Fehlerausgabe nicht wiedergegeben)")
1247
+
1248
+
1249
+
1250
+ # ---------------------------------------------------------------------------
1251
+ # Pruefung 48: Der werkzeugneutrale Kern nennt keinen Pfad eines Client Packs
1252
+ # ---------------------------------------------------------------------------
1253
+ #
1254
+ # ANLASS. Gemessen am 2026-09-18 (CR-2026-080, D-128): SIEBZEHN Fundstellen in
1255
+ # VIERZEHN anweisenden Traegern nannten den Dateinamen oder das Verzeichnis genau
1256
+ # eines Client Packs - darunter sechs Prompt-Vorlagen und mit Abschnitt 2 von
1257
+ # 08-skill-conventions.md ein NORMATIVES Kernmodul, dessen Prosa zwei Zeilen tiefer
1258
+ # richtig "die Skill-Ablage der Laufzeitschicht" sagt. Die Regel dazu steht seit
1259
+ # 0.31.0 in docs/RUNTIME_GLOSSARY.md, und durchgesetzt hat sie nichts.
1260
+ #
1261
+ # WARUM PRUEFUNG 12 SIE NICHT FAND - drei Gruende, alle nachgelesen:
1262
+ # 1. Sie liest nur Token in BACKTICKS. Zehn der siebzehn standen ohne: in einem
1263
+ # Codeblock, in Prosa oder in einem HTML-Kommentar.
1264
+ # 2. Sie meldet nur Pfade, die es NICHT GIBT. Im Framework-Repositorium ist genau
1265
+ # ein Pack installiert; dessen Laufzeitschicht existiert und ist damit
1266
+ # unsichtbar. Gemeldet wurde nur der jeweils ANDERE Client.
1267
+ # 3. Und ihre Wurzelliste war selbst clientgebunden: LINK_ROOTS fuehrte woertlich
1268
+ # '.devin/', 'AGENTS.md' und 'AGENTS.local.md'. Die Pfade des anderen Packs
1269
+ # waren damit gar kein Kandidat - die Pruefung, die die Bindung melden sollte,
1270
+ # trug sie selbst. Dieser dritte Grund stand in keiner Fassung des Befunds; er
1271
+ # ist beim Nachzaehlen aufgefallen.
1272
+ #
1273
+ # UND ER HAT SICH BEIM MESSEN VERKLEINERT, nicht vergroessert. Der Verdacht war, die
1274
+ # enge Wurzelliste erzeuge in einer Installation des anderen Packs eine Falschmeldung.
1275
+ # Drei Zuschnitte sagen: nein. Sie deckte sich mit der zweiten engen Stelle desselben
1276
+ # Blocks, OPTIONAL_RUNTIME_RE, die ebenfalls nur '.devin/' kannte - beide waren in
1277
+ # DERSELBEN Richtung zu eng und haben einander gedeckt. Die Ableitung aus den
1278
+ # Manifesten bleibt trotzdem: Sie schafft eine gepflegte Clientliste ab. Aber sie
1279
+ # behebt keine gemessene Falschmeldung, und das gehoert hierhin und nicht weggelassen.
1280
+ #
1281
+ # DIE MARKEN STAMMEN AUS DEN MANIFESTEN, nicht aus einer gepflegten Liste - dieselbe
1282
+ # Bauform wie _client_actor_names in Pruefung 14: Ein neues Client Pack bringt seine
1283
+ # Pfade selbst mit und wird ohne Aenderung an dieser Pruefung erfasst.
1284
+ #
1285
+ # DREI GATTUNGEN SIND AUSGENOMMEN, und sie stehen in docs/RUNTIME_GLOSSARY.md und
1286
+ # nicht nur hier - eine Ausnahme, die allein im Quelltext steht, ist keine Regel:
1287
+ # * CHRONIK berichtet einen vergangenen Stand. Ihn nachtraeglich zu glaetten,
1288
+ # zerstoert die Nachvollziehbarkeit. docs/ROADMAP.md gehoert dazu: Sie fuehrt die
1289
+ # Erhebungsergebnisse der Arbeitspakete und die Befundberichte je Release.
1290
+ # * WERKZEUGE (.py) stellen Installationen her oder pruefen sie; sie MUESSEN Pfade
1291
+ # nennen. Diese Datei ist selbst eines davon.
1292
+ # * Die ABBILDUNGSTABELLEN - Glossar, Platzhalterregister und die Client Packs -
1293
+ # muessen beide Namen nennen; dort ist der Pfad der Inhalt.
1294
+ #
1295
+ # EINE VIERTE AUSNAHME TRAEGT EINE FRIST. build/ haelt die Quellen des Hauptdokuments;
1296
+ # es ist ueber vierzig Releases zurueck und wird mit AP11 (~0.69.0) neu gesetzt. Die
1297
+ # Ausnahme faellt mit diesem Schritt, und sie steht dort in der Roadmap.
1298
+ #
1299
+ # EINE SPALTE STATT EINER DATEI. In tests/TEST_CATALOG.md ist die letzte Zelle einer
1300
+ # Tabellenzeile der Ergebnisstatus; ein Pfad dort nennt, was ein Lauf gelesen hat, und
1301
+ # gehoert zum gemessenen Client Pack (D-117). Die anweisenden Spalten derselben Zeile
1302
+ # stehen unter der Regel - die Eingabe "Passe AGENTS.md an" war eine davon. Denselben
1303
+ # Zuschnitt - die LETZTE Zelle - benutzt Pruefung 46 fuer den Ergebnisstatus.
1304
+ #
1305
+ # GRENZE. Sie findet die PFADE eines Packs, nicht seinen Produktnamen - den setzt
1306
+ # Pruefung 14 durch. Beide teilen sich seit 0.57.1 EINE Ausnahmemenge
1307
+ # (NEUTRAL_AUSNAHMEN); bis dahin waren es zwei, und sie waren schon auseinandergelaufen.
1308
+ #
1309
+ # Traeger, in denen die LETZTE Tabellenzelle ein Beleg ist und kein Anweisungstext.
1310
+ P48_ERGEBNISSPALTE = (KERN + "/tests/TEST_CATALOG.md",)
1311
+
1312
+
1313
+ def _client_pfadmarken(root: str) -> list:
1314
+ """(Pfad, Clientname) je Laufzeitartefakt jedes Client Packs, laengste zuerst.
1315
+
1316
+ Quelle sind die runtime_placeholders der Manifeste - nicht eine gepflegte Liste.
1317
+ <CLIENT_NAME> traegt keinen Pfad, und <CORE_DIR> gehoert der Installation und
1318
+ keinem Client (docs/PLACEHOLDER_REGISTRY.md).
1319
+
1320
+ Laengste zuerst, damit '.claude/skills' als sich selbst zaehlt und nicht als
1321
+ '.claude': Wer kurz vor lang prueft, zaehlt dieselbe Stelle zweimal.
1322
+ """
1323
+ marken: dict[str, str] = {}
1324
+ base = os.path.join(root, KERN, "clients")
1325
+ if not os.path.isdir(base):
1326
+ return []
1327
+ for name in sorted(os.listdir(base)):
1328
+ if name.startswith("_"):
1329
+ continue
1330
+ mp = os.path.join(base, name, "manifest.json")
1331
+ if not os.path.exists(mp):
1332
+ continue
1333
+ try:
1334
+ man = json.loads(read(mp))
1335
+ except json.JSONDecodeError:
1336
+ continue
1337
+ client = man.get("client", name)
1338
+ for schluessel, wert in (man.get("runtime_placeholders") or {}).items():
1339
+ if schluessel in ("<CLIENT_NAME>", "<CORE_DIR>") or not wert:
1340
+ continue
1341
+ marken.setdefault(wert, client)
1342
+ return sorted(marken.items(), key=lambda kv: (-len(kv[0]), kv[0]))
1343
+
1344
+
1345
+ def _belegspalte(zeile: str) -> int:
1346
+ """Spaltenindex, ab dem die LETZTE Zelle einer Markdown-Tabellenzeile beginnt.
1347
+
1348
+ Keine Tabellenzeile - oder eine ohne Inhalt hinter dem letzten Trenner - liefert
1349
+ die Zeilenlaenge; dann liegt kein Treffer dahinter und alles steht unter der Regel.
1350
+ """
1351
+ if not zeile.lstrip().startswith("|"):
1352
+ return len(zeile)
1353
+ trenner = [m.start() for m in ZELLTRENNER_RE.finditer(zeile)]
1354
+ if len(trenner) < 2:
1355
+ return len(zeile)
1356
+ # Die letzte Zelle liegt zwischen dem vorletzten und dem letzten Trenner.
1357
+ return trenner[-2] + 1
1358
+
1359
+
1360
+ def check_tool_neutrality(root: str) -> None:
1361
+ """Pruefung 48 (D-02, D-128): Kein anweisender Kerntraeger nennt einen Clientpfad."""
1362
+ marken = _client_pfadmarken(root)
1363
+ if not marken:
1364
+ err(f"{KERN}/clients/: kein Client Pack mit runtime_placeholders gefunden – "
1365
+ f"Prüfung 48 leitet ihre Marken daraus ab und hat ihren Gegenstand "
1366
+ f"verloren; sie bestünde sonst leise (D-23)")
1367
+ return
1368
+ for path in iter_text_files(root):
1369
+ if not path.endswith((".md", ".template")):
1370
+ continue
1371
+ rel = os.path.relpath(path, root).replace(os.sep, "/")
1372
+ if not rel.startswith(KERN + "/"):
1373
+ continue
1374
+ if neutral_ausgenommen(rel):
1375
+ continue
1376
+ belegspalte = rel in P48_ERGEBNISSPALTE
1377
+ for i, zeile in enumerate(read(path).splitlines(), 1):
1378
+ ab = _belegspalte(zeile) if belegspalte else len(zeile)
1379
+ belegt = [False] * (len(zeile) + 1)
1380
+ for marke, client in marken:
1381
+ start = 0
1382
+ while True:
1383
+ j = zeile.find(marke, start)
1384
+ if j < 0:
1385
+ break
1386
+ start = j + 1
1387
+ ende = j + len(marke)
1388
+ if j >= ab or any(belegt[j:ende]):
1389
+ continue
1390
+ # Teil eines laengeren Namens ist kein Treffer.
1391
+ if (j and (zeile[j - 1].isalnum() or zeile[j - 1] in "/._-")):
1392
+ continue
1393
+ if zeile[ende:ende + 1].isalnum() or zeile[ende:ende + 1] in "._-":
1394
+ continue
1395
+ for k in range(j, ende):
1396
+ belegt[k] = True
1397
+ err(f"{rel}:{i}: '{marke}' gehört der Laufzeitschicht des Client "
1398
+ f"Packs '{client}'. Der Kern ist werkzeugneutral (D-02) und "
1399
+ f"nennt den Begriff, nicht den Pfad – die Entsprechung je Pack "
1400
+ f"steht in {KERN}/docs/RUNTIME_GLOSSARY.md (D-128)")
1401
+
1402
+
1403
+ # --- Das Mandat, aus Sicht des Validators (D-461) ------------------------------------
1404
+ # Dieselben Bedingungen wie im Schutz-Hook und in mandat.py, soweit der Validator sie
1405
+ # braucht: die Datei im Git-Verzeichnis, ein Ende in der Zukunft, dasselbe Projekt.
1406
+ # Pruefung 99 haelt Dateiname und Hoechstdauer in Hook und mandat.py gleich; hier steht
1407
+ # nur der Dateiname, weil der Validator ein Mandat nie ausweitet, sondern nur eine
1408
+ # Meldung abschwaecht.
1409
+ MANDAT_DATEINAME = "koolie-mandat.json"
1410
+
1411
+
1412
+ def gueltiges_mandat(root: str) -> bool:
1413
+ git = os.path.join(root, ".git")
1414
+ if os.path.isfile(git):
1415
+ try:
1416
+ zeile = read(git).strip()
1417
+ except OSError:
1418
+ return False
1419
+ if not zeile.startswith("gitdir:"):
1420
+ return False
1421
+ git = os.path.normpath(os.path.join(root, zeile[len("gitdir:"):].strip()))
1422
+ pfad = os.path.join(git, MANDAT_DATEINAME)
1423
+ if not os.path.isfile(pfad):
1424
+ return False
1425
+ try:
1426
+ daten = json.loads(read(pfad))
1427
+ import datetime as _dt
1428
+ bis = _dt.datetime.strptime(str(daten["bis"]), "%Y-%m-%dT%H:%M:%SZ")
1429
+ except (ValueError, KeyError, TypeError, OSError):
1430
+ return False
1431
+ jetzt = _dt.datetime.now(_dt.timezone.utc).replace(tzinfo=None)
1432
+ projekt = os.path.normcase(os.path.realpath(str(daten.get("projekt", ""))))
1433
+ return bis > jetzt and projekt == os.path.normcase(os.path.realpath(root))