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