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