@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,1559 @@
|
|
|
1
|
+
"""Die Berechtigungsschicht: Koerbe, Importsteuerung, Skill-Sperren, Agentenprofile,
|
|
2
|
+
Werkzeugabbildung, Skillfreigaben, Mandat, Modellaufruf und MCP-Freigaben.
|
|
3
|
+
|
|
4
|
+
Pruefungen 22, 33, 34, 35, 37, 38, 39, 72, 88, 96, 97, 99, 100 und 101. Teil des
|
|
5
|
+
Validators validate-framework.py, seit 1.19.1 nach Gegenstand in Module geteilt (K-174).
|
|
6
|
+
Das Register aller Pruefungen steht im Kopfkommentar des Einstiegs, die Grenze jeder
|
|
7
|
+
einzelnen in ihrem Kopfkommentar hier."""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from .gemeinsam import (
|
|
17
|
+
_hook_interpreter, _hook_lauf, err, formatgebunden, fremde_praefixe, FRONTMATTER_BLOECKE,
|
|
18
|
+
KERN,
|
|
19
|
+
korb_zerlegung, parse_frontmatter, read, skill_dirs, soll_korbregeln,
|
|
20
|
+
tabellenzellen, TBD_RE, warn, yaml)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def installierte_skills(root: str, man: dict) -> set:
|
|
24
|
+
"""Die Skills, die in DIESER Installation liegen - abgeleitet aus der Ablage."""
|
|
25
|
+
rel = man.get("skills_dir")
|
|
26
|
+
if not rel:
|
|
27
|
+
return set()
|
|
28
|
+
ablage = os.path.join(root, *rel.split("/"))
|
|
29
|
+
if not os.path.isdir(ablage):
|
|
30
|
+
return set()
|
|
31
|
+
return {n for n in os.listdir(ablage)
|
|
32
|
+
if os.path.isfile(os.path.join(ablage, n, "SKILL.md"))}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def skillfreigaben(root: str, man: dict) -> set:
|
|
36
|
+
"""Die Korbeintraege, die einen Skill DIESER Installation beim Namen nennen.
|
|
37
|
+
|
|
38
|
+
\U0001F534 ZWEI PRUEFUNGEN DESSELBEN REPOSITORIUMS STANDEN GEGENEINANDER, UND DAS
|
|
39
|
+
IST GEMESSEN (2026-09-21, Herrichtung von Buendel 5). Pruefung 72 verlangt seit
|
|
40
|
+
`0.81.0` zu jedem Skill der Installation einen Eintrag der Berechtigungsdatei -
|
|
41
|
+
das ist der dritte Teil der Aktivierung eines Packs (D-238). Pruefung 37 hielt
|
|
42
|
+
denselben Eintrag fuer eine AUSWEITUNG, weil die Kernquelle ihn nicht erzeugt:
|
|
43
|
+
Am Messbaum von Buendel 5 meldete sie `Skill(role-re-ticket)` als eine Regel zu
|
|
44
|
+
viel im allow-Korb.
|
|
45
|
+
|
|
46
|
+
Damit war die Abhilfe von D-238 in keinem Projekt umsetzbar, ohne den eigenen
|
|
47
|
+
Validator rot zu faerben - und `0.81.0` hat das nicht gesehen, weil es den
|
|
48
|
+
Korbeintrag beim Messen noch gar nicht gab.
|
|
49
|
+
|
|
50
|
+
Wer eine Pruefung baut, die etwas VERLANGT, fragt, ob eine andere desselben
|
|
51
|
+
Repositoriums es VERBIETET.
|
|
52
|
+
|
|
53
|
+
Ein solcher Eintrag ist keine Ausweitung: Pruefung 72 deckt beide Richtungen ab -
|
|
54
|
+
ein Skill ohne Eintrag und ein Eintrag ohne Skill werden beide gemeldet. Was
|
|
55
|
+
bleibt, ist genau die Menge, die das Projekt durch seine Aktivierung entschieden
|
|
56
|
+
hat, und die Menge wird aus der Skillablage ABGELEITET, nicht gepflegt.
|
|
57
|
+
"""
|
|
58
|
+
werkzeuge = tuple(w for w in (man.get("permission_tools") or {}).get("skill") or ()
|
|
59
|
+
if isinstance(w, str))
|
|
60
|
+
if not werkzeuge:
|
|
61
|
+
return set()
|
|
62
|
+
namen = installierte_skills(root, man)
|
|
63
|
+
return {f"{w}({n})" for w in werkzeuge for n in namen}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _fremder_skilleintrag(regel: str, man: dict, fremd) -> bool:
|
|
67
|
+
"""Der Korbeintrag eines deklarierten fremden Skills (1.21.0, K-31, D-514), auch als Muster."""
|
|
68
|
+
if not fremd:
|
|
69
|
+
return False
|
|
70
|
+
m = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\(([^()]*)\)$", regel.strip())
|
|
71
|
+
namen = [w for w in (man.get("permission_tools", {}).get("skill") or []) if isinstance(w, str)]
|
|
72
|
+
return bool(m and m.group(1) in namen and any(m.group(2).startswith(p) for p in fremd))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def check_berechtigungskoerbe(root: str, man: dict) -> None:
|
|
76
|
+
"""Pruefung 37: Was die Kernquelle erzeugt, steht in der installierten Datei.
|
|
77
|
+
|
|
78
|
+
Warum nicht einfach _core_rules_integrity auf alle Regeln erweitern: Das faengt die
|
|
79
|
+
geloeschte und die verengte Regel, aber weder die ergaenzte allow-Zeile noch den
|
|
80
|
+
geleerten ask-Korb. Die Liste sagt, was fehlen darf - nicht, was zuviel sein darf.
|
|
81
|
+
|
|
82
|
+
Ein Platzhalterschlitz (Bash(<TEST_COMMAND>) und die drei Pfadschlitze) ist der
|
|
83
|
+
einzige Teil dieser Datei, der dem Projekt gehoert. Er darf gefuellt sein, gefuellt
|
|
84
|
+
zaehlt er gegen den Ueberschuss - und er darf das Praefixzeichen des Clients nicht
|
|
85
|
+
tragen: clientmap._befehl haengt es an einen offenen Projektplatzhalter bewusst nicht
|
|
86
|
+
an, weil der Overlay Owner dort den vollstaendigen Befehl eintraegt. Von Hand
|
|
87
|
+
nachgetragen macht es aus der Freigabe eines Befehls die Freigabe einer
|
|
88
|
+
Befehlsfamilie - am Piloten am 2026-09-13 so vorgefunden.
|
|
89
|
+
"""
|
|
90
|
+
if formatgebunden(man, 37):
|
|
91
|
+
return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
|
|
92
|
+
rel = man["permissions_file"]
|
|
93
|
+
path = os.path.join(root, *rel.split("/"))
|
|
94
|
+
if not os.path.exists(path):
|
|
95
|
+
return
|
|
96
|
+
try:
|
|
97
|
+
cfg = json.loads(read(path))
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
return # check_config hat das bereits gemeldet
|
|
100
|
+
soll = soll_korbregeln(root, man)
|
|
101
|
+
if soll is None:
|
|
102
|
+
return
|
|
103
|
+
if not any(soll.values()):
|
|
104
|
+
err(f"{KERN}/framework/runtime/permissions.json: die Abbildung erzeugt für das "
|
|
105
|
+
f"Client Pack {man.get('client', '?')} keine einzige Regel – Prüfung 37 hätte "
|
|
106
|
+
f"nichts zu vergleichen und bestünde leise")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
perms = cfg.get("permissions", {})
|
|
110
|
+
exec_werkzeuge = tuple(man.get("permission_tools", {}).get("exec", ()))
|
|
111
|
+
praefixzeichen = (man.get("permission_exec_suffix", ":*")
|
|
112
|
+
if man.get("permission_exec_match") == "prefix" else None)
|
|
113
|
+
skillkorb = skillfreigaben(root, man)
|
|
114
|
+
|
|
115
|
+
for korb in ("deny", "ask", "allow"):
|
|
116
|
+
ist = [r for r in perms.get(korb, []) if isinstance(r, str)]
|
|
117
|
+
pflicht, schlitze, ungefuellt, zusatz = korb_zerlegung(ist, soll[korb])
|
|
118
|
+
for regel in pflicht:
|
|
119
|
+
if regel not in ist:
|
|
120
|
+
err(f"{rel}: die Kernquelle erzeugt für den {korb}-Korb die Regel "
|
|
121
|
+
f"'{regel}'; dort steht sie nicht. Eine fehlende Regel ist eine "
|
|
122
|
+
f"Lockerung, gleich in welchem Korb – Änderungen an der Regelmenge "
|
|
123
|
+
f"laufen über einen Änderungsantrag (V10, D-77)")
|
|
124
|
+
if korb == "deny":
|
|
125
|
+
# Eine zusaetzliche deny-Regel ist eine Verschaerfung und deshalb zulaessig.
|
|
126
|
+
continue
|
|
127
|
+
# Der Korbeintrag eines aktivierten Pack- oder Projektskills ist keine
|
|
128
|
+
# Ausweitung, sondern der dritte Teil seiner Aktivierung - Pruefung 72
|
|
129
|
+
# verlangt ihn und deckt beide Richtungen ab (D-238, D-243).
|
|
130
|
+
zusatz = [r for r in zusatz if r not in skillkorb]
|
|
131
|
+
# Ebenso der Eintrag eines deklarierten fremden Skills (1.21.0, K-31, D-514):
|
|
132
|
+
# Pruefung 72 verlangt ihn, Pruefung 111 haelt die Deklaration.
|
|
133
|
+
zusatz = [r for r in zusatz if not _fremder_skilleintrag(r, man, fremde_praefixe(root))]
|
|
134
|
+
# Gedeckt wird ein Ueberschuss nur von den Schlitzen, die NICHT mehr
|
|
135
|
+
# woertlich dastehen - ein woertlich vorhandener Schlitz ist ungefuellt.
|
|
136
|
+
offen = [s for s in schlitze if s not in ungefuellt]
|
|
137
|
+
if len(zusatz) > len(offen):
|
|
138
|
+
err(f"{rel}: der {korb}-Korb führt {len(zusatz)} Regel(n), die die Kernquelle "
|
|
139
|
+
f"nicht erzeugt, bei {len(offen)} gefüllten Platzhalterschlitz(en): "
|
|
140
|
+
f"{', '.join(zusatz)}. Eine zusätzliche Freigabe ist eine Ausweitung und "
|
|
141
|
+
f"nicht Sache des Overlays; ein weiterer freigegebener Befehl gehört in "
|
|
142
|
+
f"Abschnitt 6 des Overlays und damit in die Regelschicht (D-76)")
|
|
143
|
+
if not praefixzeichen or len(zusatz) > len(offen):
|
|
144
|
+
# Kein Praefixzeichen: Dieser Client sperrt Befehle woertlich.
|
|
145
|
+
# Ueberschuss groesser als die offenen Schlitze: Dann ist nicht entschieden,
|
|
146
|
+
# welche Zeile ein gefuellter Schlitz ist und welche eine hinzugefuegte
|
|
147
|
+
# Freigabe - und eine Meldung, die "gefuellter Platzhalterschlitz" sagt, wo
|
|
148
|
+
# keiner ist, sagt etwas anderes als der Fall hergibt. Die Meldung darueber
|
|
149
|
+
# benennt den Ueberschuss bereits richtig.
|
|
150
|
+
continue
|
|
151
|
+
for regel in zusatz:
|
|
152
|
+
teile = regel.split("(", 1)
|
|
153
|
+
if len(teile) != 2 or teile[0] not in exec_werkzeuge:
|
|
154
|
+
continue
|
|
155
|
+
inhalt = teile[1][:-1] if teile[1].endswith(")") else teile[1]
|
|
156
|
+
if inhalt.endswith(praefixzeichen):
|
|
157
|
+
err(f"{rel}: '{regel}' im {korb}-Korb trägt das Präfixzeichen "
|
|
158
|
+
f"'{praefixzeichen}'. Die Abbildung hängt es an einen gefüllten "
|
|
159
|
+
f"Projektplatzhalter bewusst nicht an – von Hand nachgetragen macht "
|
|
160
|
+
f"es aus der Freigabe eines Befehls die Freigabe einer "
|
|
161
|
+
f"Befehlsfamilie (D-77)")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def check_importsteuerung(root: str, man: dict) -> None:
|
|
165
|
+
"""Pruefung 22 (D-37): Die Importsteuerung steht so, wie das Manifest sie abbildet.
|
|
166
|
+
|
|
167
|
+
GRENZE - vorab benannt wie bei Pruefung 19: Sie belegt **Anwesenheit und
|
|
168
|
+
Uebereinstimmung, nicht Richtigkeit und erst recht nicht Wirkung.** Dass die
|
|
169
|
+
Einstellung in der Datei steht, heisst nicht, dass sie greift: Die Benutzer-
|
|
170
|
+
konfiguration dieser Arbeitsstation hat Vorrang, in beide Richtungen gemessen
|
|
171
|
+
(K-27). Die Masznahme ist ein Standard, keine Schranke. Was wirkt, misst nur eine
|
|
172
|
+
Sitzung, die den Kontext prueft - nicht das Register des Clients (ERH-02).
|
|
173
|
+
|
|
174
|
+
Ein Pack ohne das Feld laeuft durch: Kennt ein Client keinen solchen Mechanismus -
|
|
175
|
+
oder liefert das Framework bewusst keine Vorgabe aus, wie bei claude-code -, bleibt
|
|
176
|
+
es bei der Auskunft im Client Pack (CR-2026-038 E2).
|
|
177
|
+
"""
|
|
178
|
+
steuerung = man.get("import_control")
|
|
179
|
+
rechte = man.get("permissions_file")
|
|
180
|
+
if not rechte:
|
|
181
|
+
return
|
|
182
|
+
pfad = os.path.join(root, *rechte.split("/"))
|
|
183
|
+
if not os.path.isfile(pfad):
|
|
184
|
+
return
|
|
185
|
+
try:
|
|
186
|
+
installiert = json.loads(read(pfad))
|
|
187
|
+
except Exception:
|
|
188
|
+
return # ungueltiges JSON meldet Pruefung 2
|
|
189
|
+
if not steuerung:
|
|
190
|
+
return
|
|
191
|
+
schluessel = steuerung.get("key")
|
|
192
|
+
erwartet = steuerung.get("value")
|
|
193
|
+
if not schluessel:
|
|
194
|
+
err(f"{man.get('client', '?')}/manifest.json: import_control ohne 'key'")
|
|
195
|
+
return
|
|
196
|
+
ist = installiert.get(schluessel)
|
|
197
|
+
if ist is None:
|
|
198
|
+
err(f"{rechte}: Die Importsteuerung '{schluessel}' fehlt. Das Manifest des Packs "
|
|
199
|
+
f"'{man.get('client', '?')}' bildet sie ab (D-37): Das Framework importiert "
|
|
200
|
+
f"keine Regel- und Skillquellen fremder Werkzeugformate. Eine Installation, "
|
|
201
|
+
f"die sie verliert, laedt sie wieder - still")
|
|
202
|
+
return
|
|
203
|
+
if ist != erwartet:
|
|
204
|
+
err(f"{rechte}: Die Importsteuerung '{schluessel}' steht als {json.dumps(ist, ensure_ascii=False)}, "
|
|
205
|
+
f"das Manifest bildet {json.dumps(erwartet, ensure_ascii=False)} ab. Die "
|
|
206
|
+
f"maschinenlesbare Quelle gilt (D-37)")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Pruefung 33: Die Abbildung von permissions.deny auf die Werkzeugsperre des Clients
|
|
210
|
+
# (Befund B01 in seiner Nachfolge, CR-2026-057, D-64 bis D-66).
|
|
211
|
+
#
|
|
212
|
+
# WARUM SIE DIE ERZEUGTE FASSUNG MISST UND NICHT DIE QUELLE: Genau daran ist B01
|
|
213
|
+
# vorbeigekommen. Zwoelf Quellskills fuehrten `permissions: deny: [edit, exec]`, das Feld
|
|
214
|
+
# stand in drop_fields, und keine installierte Fassung trug etwas davon - die Quelle sagte
|
|
215
|
+
# mehr, als die Installation hielt, und beides war fuer sich dokumentiert.
|
|
216
|
+
#
|
|
217
|
+
# WARUM SIE IHRE ERWARTUNG SELBST AUSRECHNET: Sie koennte deny_abbilden() aus install.py
|
|
218
|
+
# importieren. Dann pruefte sie aber nur noch, DASS der Installer gelaufen ist, nicht dass
|
|
219
|
+
# er richtig abbildet - sie teilte jeden Fehler der Abbildung. Die Verbtabelle steht
|
|
220
|
+
# deshalb bewusst ein zweites Mal hier. Laufen die beiden auseinander, faellt diese
|
|
221
|
+
# Pruefung; das ist der Zweck, nicht ein Versehen.
|
|
222
|
+
#
|
|
223
|
+
# WAS SIE NICHT LEISTET: Sie misst am erzeugten Text, nicht am Client. Dass
|
|
224
|
+
# disallowed-tools wirklich sperrt, belegt die Erhebung
|
|
225
|
+
# (tests/protocols/2026-09-13-erhebung-disallowed-tools.md), nicht der Validator.
|
|
226
|
+
#
|
|
227
|
+
# SEIT 0.40.0 fuehrt sie dasselbe Vokabular wie die Quelle (D-79): read, grep, glob,
|
|
228
|
+
# edit, exec. Bis dahin fuehrte sie 'write' und 'search' - zwei Verben der
|
|
229
|
+
# Durchsetzungsschicht - und kannte 'grep' und 'glob' nicht, obwohl jede der vierzehn
|
|
230
|
+
# Quellen sie im Nachbarfeld desselben Frontmatters nennt. Sie teilte damit genau die
|
|
231
|
+
# Luecke, die sie haette fangen sollen.
|
|
232
|
+
DENY_VERB_EIMER_33 = {"read": "read", "grep": "search", "glob": "search",
|
|
233
|
+
"edit": "write", "exec": "exec"}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _deny_eintraege(fm_text: str) -> list[str]:
|
|
237
|
+
"""Die Eintraege unter permissions.deny einer Quelldatei, in ihrer Reihenfolge."""
|
|
238
|
+
m = re.search(r"^permissions:[ \t]*\n((?:[ \t]+\S.*\n)+)", fm_text, re.M)
|
|
239
|
+
if not m:
|
|
240
|
+
return []
|
|
241
|
+
m2 = re.search(r"^([ \t]+)deny:[ \t]*\n((?:[ \t]+-[ \t]+.*\n)+)", m.group(1), re.M)
|
|
242
|
+
if not m2:
|
|
243
|
+
return []
|
|
244
|
+
return [z.strip().lstrip("-").strip() for z in m2.group(2).splitlines() if z.strip()]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _werkzeugliste(wert) -> list[str]:
|
|
248
|
+
"""Ein Frontmatter-Werkzeugfeld als Liste - die Quelle notiert es als Liste, die
|
|
249
|
+
erzeugte Fassung je nach Client als kommagetrennte Zeichenkette (AP2-CC-09)."""
|
|
250
|
+
if isinstance(wert, str):
|
|
251
|
+
return [x for x in re.split(r"[,\s]+", wert) if x]
|
|
252
|
+
return [str(x) for x in (wert or [])]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _flaches_feld(fm, schluessel: str):
|
|
256
|
+
"""Ein einzeiliges Frontmatter-Feld - auch ohne PyYAML (D-364).
|
|
257
|
+
|
|
258
|
+
Ohne PyYAML liefert parse_frontmatter nur den Rohtext. Bis 1.6.0 las Pruefung 33
|
|
259
|
+
daraus mit .get() NICHTS und meldete an jeder claude-code-Installation neun Skills
|
|
260
|
+
mit leerem disallowed-tools - falsche Fehler, waehrend die Warnung zu PyYAML
|
|
261
|
+
behauptete, es werde nur "eingeschraenkt" geprueft. Gemessen am 2026-09-25 mit
|
|
262
|
+
Python 3.8 ohne PyYAML; die anderen Packs blieben still. Das Feld ist in der
|
|
263
|
+
erzeugten Fassung eine einzeilige, kommagetrennte Zeichenkette (AP2-CC-09), und
|
|
264
|
+
genau diese Form liest der Rueckfall - eine Listenform bleibt PyYAML vorbehalten.
|
|
265
|
+
"""
|
|
266
|
+
fm = fm or {}
|
|
267
|
+
if "_raw" not in fm:
|
|
268
|
+
return fm.get(schluessel)
|
|
269
|
+
for zeile in fm["_raw"].splitlines():
|
|
270
|
+
if zeile.startswith(schluessel + ":"):
|
|
271
|
+
wert = zeile[len(schluessel) + 1:].strip()
|
|
272
|
+
if len(wert) >= 2 and wert[0] == wert[-1] and wert[0] in "\"'":
|
|
273
|
+
wert = wert[1:-1]
|
|
274
|
+
return wert
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def check_skill_deny_abbildung(root: str, man: dict) -> None:
|
|
279
|
+
"""Pruefung 33: permissions.deny wird abgebildet, und zwar ohne stille Schranken."""
|
|
280
|
+
fmt = (man or {}).get("skill_frontmatter", {})
|
|
281
|
+
deny_feld = fmt.get("skill_deny_field")
|
|
282
|
+
if not deny_feld:
|
|
283
|
+
return # Ein Pack, das permissions nicht verwirft, braucht keine Abbildung
|
|
284
|
+
|
|
285
|
+
# Die Sonde auf den verlorenen Anker: Diese Pruefung misst eine Abbildung, die in
|
|
286
|
+
# install.py liegt. Verschwindet sie, prueft die Pruefung einen Aufbau, den es nicht
|
|
287
|
+
# mehr gibt - und bestuende dabei leise (D-23).
|
|
288
|
+
inst = os.path.join(root, KERN, "install.py")
|
|
289
|
+
quelle = read(inst) if os.path.isfile(inst) else ""
|
|
290
|
+
# Der zweite Anker hiess bis 0.39.0 DENY_VERB_EIMER und lag in install.py; seit
|
|
291
|
+
# 0.40.0 steht die Bruecke in clientmap (D-78). Der Ankerwechsel ist nachgezogen
|
|
292
|
+
# worden, weil der Sondenlauf ihn gemeldet hat - der Validatorlauf des Repositoriums
|
|
293
|
+
# konnte es nicht, weil Pruefung 33 dort gar nicht laeuft (B02).
|
|
294
|
+
for anker in ("def deny_abbilden(", "clientmap.VERB_BRUECKE"):
|
|
295
|
+
if anker not in quelle:
|
|
296
|
+
err(f"{KERN}/install.py: '{anker}' fehlt. Pruefung 33 misst die Abbildung von "
|
|
297
|
+
f"permissions.deny auf {deny_feld}; ohne sie prueft sie einen Aufbau, den "
|
|
298
|
+
f"es nicht mehr gibt, und bestuende leise (D-23, CR-2026-057)")
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
# Was bewusst NICHT abgebildet wird, MUSS deklariert sein - nie erraten (D-47).
|
|
302
|
+
if not str(fmt.get("skill_deny_unmapped") or "").strip():
|
|
303
|
+
err(f"{man.get('client', '?')}/manifest.json: skill_frontmatter.skill_deny_field "
|
|
304
|
+
f"ist gesetzt, aber skill_deny_unmapped fehlt. Befehlsgenaue Verbote der Form "
|
|
305
|
+
f"Exec(git push) sind bei diesem Client nicht ausdrueckbar; was nicht "
|
|
306
|
+
f"abgebildet wird, wird deklariert und nicht verschwiegen (D-47, "
|
|
307
|
+
f"CR-2026-057 E3)")
|
|
308
|
+
|
|
309
|
+
eimer = man.get("hook_tools") or {}
|
|
310
|
+
qdir = os.path.join(root, KERN, "framework", "skills")
|
|
311
|
+
idir = os.path.join(root, *man["skills_dir"].split("/"))
|
|
312
|
+
if not os.path.isdir(qdir) or not os.path.isdir(idir):
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
for name in sorted(os.listdir(qdir)):
|
|
316
|
+
qpfad = os.path.join(qdir, name, "SKILL.md")
|
|
317
|
+
ipfad = os.path.join(idir, name, "SKILL.md")
|
|
318
|
+
if not os.path.isfile(qpfad) or not os.path.isfile(ipfad):
|
|
319
|
+
continue
|
|
320
|
+
qtext = read(qpfad)
|
|
321
|
+
qkopf = qtext.split("\n---\n", 1)[0][4:] if qtext.startswith("---\n") else ""
|
|
322
|
+
eintraege = _deny_eintraege(qkopf + "\n")
|
|
323
|
+
|
|
324
|
+
# Erwartung, unabhaengig ausgerechnet: grobe Verben ueber hook_tools, alles mit
|
|
325
|
+
# Klammer bleibt draussen (CR-2026-057 E3).
|
|
326
|
+
erwartet: list[str] = []
|
|
327
|
+
unabbildbar = [e for e in eintraege if "(" in e]
|
|
328
|
+
for e in eintraege:
|
|
329
|
+
if "(" in e:
|
|
330
|
+
continue
|
|
331
|
+
k = DENY_VERB_EIMER_33.get(e.strip().lower())
|
|
332
|
+
for w in eimer.get(k, []) if k else []:
|
|
333
|
+
if w not in erwartet:
|
|
334
|
+
erwartet.append(w)
|
|
335
|
+
|
|
336
|
+
ifm, _ = parse_frontmatter(read(ipfad))
|
|
337
|
+
ist = _werkzeugliste(_flaches_feld(ifm, deny_feld))
|
|
338
|
+
rel = f"{man['skills_dir']}/{name}/SKILL.md"
|
|
339
|
+
|
|
340
|
+
if set(ist) != set(erwartet):
|
|
341
|
+
err(f"{rel}: {deny_feld} traegt {ist or '[]'}; aus permissions.deny der Quelle "
|
|
342
|
+
f"ergibt sich {erwartet or '[]'}. Die Quelle sagt sonst mehr, als die "
|
|
343
|
+
f"Installation haelt - genau das war Befund B01 (D-65, CR-2026-057)")
|
|
344
|
+
|
|
345
|
+
# Ein Eintrag mit Klammer wirkt gemessen LAUTLOS gar nicht: Er blockiert nichts und
|
|
346
|
+
# meldet nichts. Er darf in keiner erzeugten Fassung stehen (D-66, E5).
|
|
347
|
+
for w in ist:
|
|
348
|
+
if "(" in w or ")" in w:
|
|
349
|
+
err(f"{rel}: {deny_feld} enthaelt das Argumentmuster '{w}'. Gemessen am "
|
|
350
|
+
f"2026-09-13 laesst ein solcher Eintrag den Befehl lautlos durchlaufen "
|
|
351
|
+
f"- wer ihn schreibt, hat gar keine Schranke, nicht bloss eine "
|
|
352
|
+
f"groebere. Nur der blosse Werkzeugname sperrt (D-66)")
|
|
353
|
+
|
|
354
|
+
# Ein Werkzeug in beiden Listen ist ein Widerspruch. Gemessen gewinnt die Sperre -
|
|
355
|
+
# aber ein Skill, der ein Werkzeug zugleich vorabfreigibt und entfernt, sagt zwei
|
|
356
|
+
# Dinge, und eines davon ist falsch.
|
|
357
|
+
doppelt = sorted(set(ist) & set(_werkzeugliste((ifm or {}).get("allowed-tools"))))
|
|
358
|
+
if doppelt:
|
|
359
|
+
err(f"{rel}: {', '.join(doppelt)} steht zugleich in allowed-tools und in "
|
|
360
|
+
f"{deny_feld}. Gemessen gewinnt die Sperre; die Vorabfreigabe daneben ist "
|
|
361
|
+
f"eine Aussage, die nicht stimmt (CR-2026-057)")
|
|
362
|
+
|
|
363
|
+
# Traegt die Quelle unabbildbare Eintraege, MUSS das Pack sie deklariert haben -
|
|
364
|
+
# oben schon geprueft; hier bleibt der Hinweis, dass es diesen Skill betrifft.
|
|
365
|
+
if unabbildbar and not str(fmt.get("skill_deny_unmapped") or "").strip():
|
|
366
|
+
err(f"{rel}: die Quelle nennt {len(unabbildbar)} befehlsgenaue(s) Verbot(e), "
|
|
367
|
+
f"die dieser Client nicht ausdruecken kann, und das Pack deklariert es "
|
|
368
|
+
f"nicht (D-47)")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# Pruefung 34: Das Startwerkzeug fuer Unteragenten ist genannt oder seine Abwesenheit
|
|
372
|
+
# erklaert (CR-2026-058 E2, D-70).
|
|
373
|
+
#
|
|
374
|
+
# Bauform wie Pruefung 26 (hook_tools_absent, D-47), eine Ebene weiter. Der Anlass ist ein
|
|
375
|
+
# gemessener Befund: Ein Skill kann einen Unteragenten starten, und das Startwerkzeug stand
|
|
376
|
+
# in KEINER Werkzeugliste eines Manifests - nicht in hook_tools, nicht in permission_tools,
|
|
377
|
+
# nicht in agent_frontmatter.tool_names. Ein Kanal ohne Deklaration ist genau das, was D-47
|
|
378
|
+
# abgestellt hat.
|
|
379
|
+
#
|
|
380
|
+
# Bei claude-code ist gemessen, dass beide Schreibweisen (Agent, Task) in disallowed-tools
|
|
381
|
+
# wirken; bei devin-desktop ist NICHTS gemessen, und die leere Liste sagt deshalb etwas
|
|
382
|
+
# ueber den Belegstand, nicht ueber den Client.
|
|
383
|
+
#
|
|
384
|
+
# EINE ERKLAERUNG REICHT NICHT, WENN DAS PACK A1 OHNE VORBEHALT ZUSAGT: Zeile A1
|
|
385
|
+
# verspricht ein rein lesendes Reviewprofil. Ein Pack, das diese Zeile auf [TECHNISCH]
|
|
386
|
+
# stellt UND keinen offenen Beleg mehr darauf fuehrt, behauptet, dass es
|
|
387
|
+
# Unteragenten gibt und dass ihre Werkzeuge beschraenkbar sind - dann ist "kennt kein
|
|
388
|
+
# Startwerkzeug" kein zulaessiger Stand.
|
|
389
|
+
#
|
|
390
|
+
# DER VORBEHALT GEHOERT DAZU, und das hat diese Pruefung bei ihrem ersten Lauf selbst
|
|
391
|
+
# gezeigt: Ohne ihn fiel devin-desktop durch. Dessen Zeile A1 steht auf [TECHNISCH], aber
|
|
392
|
+
# die Praeambel des Packs sagt ausdruecklich, die Spalte nenne die VORGESEHENE
|
|
393
|
+
# Durchsetzungstiefe, und die Zeile trug einen offenen Beleg auf genau die
|
|
394
|
+
# Profilwirkung. Die Einstufung allein sagt also nicht, ob eine Zusage schon gilt - das
|
|
395
|
+
# sagt die Belegzelle. Eine Pruefung, die beides verwechselt, meldet einen Fehler, wo das
|
|
396
|
+
# Pack ehrlich ist (CR-2026-058, Wirkungsnachweis).
|
|
397
|
+
#
|
|
398
|
+
# SEIT 0.87.0 STEHT DER VORBEHALT AUF DER NACHFOLGEFORM (CR-2026-121 E5, D-291). Die
|
|
399
|
+
# Markerform ist abgeschafft; der Nachweisstand steht in der Belegspalte, und eine Zeile
|
|
400
|
+
# ohne Beleg sagt BELEG OFFEN mit Grund und Datum. Haette der Vorbehalt den alten
|
|
401
|
+
# Suchtext behalten, waere seine Bedingung dauerhaft wahr - eine Ausnahme, die nichts
|
|
402
|
+
# mehr ausnimmt (0.57.1), und sie saehe wie Sorgfalt aus. HEUTE HAT SIE KEINEN FALL:
|
|
403
|
+
# Beide Packs fuehren agent_start_tools gefuellt, und der Zweig wird nicht erreicht.
|
|
404
|
+
# Sie ist Vorsorge fuer das naechste Pack, und Sonde 34e praepariert sie.
|
|
405
|
+
#
|
|
406
|
+
# WAS DIESE PRUEFUNG NICHT LEISTET: Sie prueft die Deklaration, nicht ihre Richtigkeit. Ob
|
|
407
|
+
# der genannte Name beim Client wirklich sperrt, belegt allein eine Erhebung.
|
|
408
|
+
def check_agent_startwerkzeug(root: str) -> None:
|
|
409
|
+
"""Pruefung 34 (D-70): agent_start_tools ist genannt oder erklaert."""
|
|
410
|
+
basis = os.path.join(root, KERN, "clients")
|
|
411
|
+
if not os.path.isdir(basis):
|
|
412
|
+
return
|
|
413
|
+
for pack in sorted(os.listdir(basis)):
|
|
414
|
+
if pack.startswith("_"):
|
|
415
|
+
continue
|
|
416
|
+
pfad = os.path.join(basis, pack, "manifest.json")
|
|
417
|
+
if not os.path.isfile(pfad):
|
|
418
|
+
continue
|
|
419
|
+
rel = os.path.relpath(pfad, root).replace(os.sep, "/")
|
|
420
|
+
try:
|
|
421
|
+
man = json.loads(read(pfad))
|
|
422
|
+
except ValueError:
|
|
423
|
+
continue
|
|
424
|
+
namen = man.get("agent_start_tools")
|
|
425
|
+
if namen is None:
|
|
426
|
+
err(f"{rel}: Feld 'agent_start_tools' fehlt. Ein Skill kann einen Unteragenten "
|
|
427
|
+
f"starten; mit welchem Werkzeug, gehoert deklariert - und wenn es "
|
|
428
|
+
f"unbekannt oder unerhoben ist, gehoert das ausdruecklich dorthin statt "
|
|
429
|
+
f"verschwiegen (D-70, Bauform wie hook_tools_absent nach D-47)")
|
|
430
|
+
continue
|
|
431
|
+
if not isinstance(namen, list):
|
|
432
|
+
err(f"{rel}: 'agent_start_tools' ist keine Liste")
|
|
433
|
+
continue
|
|
434
|
+
gefuellt = [n for n in namen if isinstance(n, str) and n.strip()]
|
|
435
|
+
abwesend = man.get("agent_start_tools_absent") or []
|
|
436
|
+
notiz = str(man.get("_agent_start_tools_absent_note") or "").strip()
|
|
437
|
+
if gefuellt:
|
|
438
|
+
if abwesend:
|
|
439
|
+
err(f"{rel}: 'agent_start_tools' nennt {gefuellt} und "
|
|
440
|
+
f"'agent_start_tools_absent' erklaert zugleich eine Abwesenheit. "
|
|
441
|
+
f"Beides zugleich geht nicht (D-70)")
|
|
442
|
+
continue
|
|
443
|
+
if not abwesend:
|
|
444
|
+
err(f"{rel}: 'agent_start_tools' ist leer, ohne dass "
|
|
445
|
+
f"'agent_start_tools_absent' die Abwesenheit erklaert. Eine leere Liste "
|
|
446
|
+
f"allein sagt nicht, ob der Client kein solches Werkzeug kennt oder ob es "
|
|
447
|
+
f"nur nicht erhoben ist - genau diese Unterscheidung ist der Zweck des "
|
|
448
|
+
f"Feldes (D-70, D-47)")
|
|
449
|
+
continue
|
|
450
|
+
if not notiz:
|
|
451
|
+
err(f"{rel}: 'agent_start_tools_absent' nennt {sorted(abwesend)}, aber "
|
|
452
|
+
f"'_agent_start_tools_absent_note' fehlt oder ist leer. Eine erklaerte "
|
|
453
|
+
f"Abwesenheit ohne Begruendung ist eine Behauptung (D-70, D-47)")
|
|
454
|
+
# Wer A1 auf [TECHNISCH] stellt, sagt zu, dass es Unteragenten gibt und dass ihre
|
|
455
|
+
# Werkzeuge beschraenkbar sind. Dann ist eine Erklaerung kein zulaessiger Stand.
|
|
456
|
+
pack_md = os.path.join(basis, pack, "CLIENT_PACK.md")
|
|
457
|
+
if not os.path.isfile(pack_md):
|
|
458
|
+
continue
|
|
459
|
+
for zeile in read(pack_md).replace("\r\n", "\n").split("\n"):
|
|
460
|
+
if (re.match(r"^\|\s*A1\s*\|", zeile) and "[TECHNISCH]" in zeile
|
|
461
|
+
and "BELEG OFFEN" not in zeile):
|
|
462
|
+
err(f"{rel}: Zeile A1 des Packs steht auf [TECHNISCH] - das Pack sagt ein "
|
|
463
|
+
f"rein lesendes Unteragentenprofil technisch zu -, aber "
|
|
464
|
+
f"'agent_start_tools' ist leer und nur erklaert. Wer Unteragenten "
|
|
465
|
+
f"zusagt, nennt das Werkzeug, mit dem sie starten (D-70)")
|
|
466
|
+
break
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# Pruefung 35: Ein Agentenprofil bekommt kein Startwerkzeug (CR-2026-059 E1, D-73).
|
|
471
|
+
#
|
|
472
|
+
# Gemessen am 2026-09-13 (tests/protocols/2026-09-13-erhebung-unteragent-tiefe.md,
|
|
473
|
+
# Lauf STARTLOS): Ein Profil mit tools: Read, Grep, Glob - genau die Form, die
|
|
474
|
+
# fw-reviewer nach der Abbildung traegt - hat KEIN Startwerkzeug und konnte deshalb
|
|
475
|
+
# keinen weiteren Unteragenten starten, dessen Profil weniger beschraenkt waere. Ohne
|
|
476
|
+
# diesen Befund waere die Zusage A1 ueber eine zweite Ebene aushebelbar.
|
|
477
|
+
#
|
|
478
|
+
# WAS DIESE PRUEFUNG IST UND WAS NICHT: Sie faengt heute NICHTS. Die Abbildung
|
|
479
|
+
# agent_frontmatter.tool_names kennt gar kein Startwerkzeug, also kann keine erzeugte
|
|
480
|
+
# tools-Liste eines enthalten. Sie ist eine VERANKERUNG, keine Behebung - dieselbe
|
|
481
|
+
# Bauart wie der fuenfte Gegenstand der Pruefung 32, und sie wird mit derselben
|
|
482
|
+
# Ehrlichkeit begruendet: Erweitert jemand tool_names um ein Startwerkzeug, faellt die
|
|
483
|
+
# Zusage LAUTLOS, und niemand prueft es. Danach faengt diese Pruefung es.
|
|
484
|
+
#
|
|
485
|
+
# Geprueft wird an ZWEI Stellen, weil eine allein zu wenig waere:
|
|
486
|
+
# 1. Die ABBILDUNG - tool_names darf keinen Namen aus agent_start_tools fuehren.
|
|
487
|
+
# 2. Die QUELLE - kein ausgeliefertes Agentenprofil nennt eines direkt. Diese Haelfte
|
|
488
|
+
# faengt den Fall, dass jemand am Profil vorbei an der Abbildung schreibt.
|
|
489
|
+
def check_agent_profil_ohne_start(root: str) -> None:
|
|
490
|
+
"""Pruefung 35 (D-73): Kein Agentenprofil bekommt ein Startwerkzeug."""
|
|
491
|
+
basis = os.path.join(root, KERN, "clients")
|
|
492
|
+
if not os.path.isdir(basis):
|
|
493
|
+
return
|
|
494
|
+
alle_start = set()
|
|
495
|
+
for pack in sorted(os.listdir(basis)):
|
|
496
|
+
if pack.startswith("_"):
|
|
497
|
+
continue
|
|
498
|
+
pfad = os.path.join(basis, pack, "manifest.json")
|
|
499
|
+
if not os.path.isfile(pfad):
|
|
500
|
+
continue
|
|
501
|
+
rel = os.path.relpath(pfad, root).replace(os.sep, "/")
|
|
502
|
+
try:
|
|
503
|
+
man = json.loads(read(pfad))
|
|
504
|
+
except ValueError:
|
|
505
|
+
continue
|
|
506
|
+
start = {n.strip().lower() for n in (man.get("agent_start_tools") or [])
|
|
507
|
+
if isinstance(n, str) and n.strip()}
|
|
508
|
+
if not start:
|
|
509
|
+
continue # ob das zulaessig ist, entscheidet Pruefung 34
|
|
510
|
+
alle_start |= start
|
|
511
|
+
|
|
512
|
+
# 1. Die Abbildung darf kein Startwerkzeug fuehren.
|
|
513
|
+
abbildung = (man.get("agent_frontmatter") or {}).get("tool_names") or {}
|
|
514
|
+
for verb, namen in sorted(abbildung.items()):
|
|
515
|
+
for name in (namen if isinstance(namen, list) else []):
|
|
516
|
+
if isinstance(name, str) and name.strip().lower() in start:
|
|
517
|
+
err(f"{rel}: agent_frontmatter.tool_names bildet das Verb "
|
|
518
|
+
f"'{verb}' auf '{name}' ab - ein Werkzeug, mit dem ein "
|
|
519
|
+
f"Unteragent GESTARTET wird (agent_start_tools). Ein "
|
|
520
|
+
f"Agentenprofil, das es bekommt, kann eine zweite, weniger "
|
|
521
|
+
f"beschraenkte Ebene oeffnen; die Zusage A1 waere damit "
|
|
522
|
+
f"aushebelbar (D-73, CR-2026-059)")
|
|
523
|
+
|
|
524
|
+
# 2. Kein ausgeliefertes Agentenprofil nennt ein Startwerkzeug direkt.
|
|
525
|
+
#
|
|
526
|
+
# Diese Haelfte laeuft EINMAL ueber die Ablage des Kerns, nicht je Pack: Die Profile
|
|
527
|
+
# sind geteilt (shared_core), die Startwerkzeuge sind es nicht. Geprueft wird gegen
|
|
528
|
+
# die VEREINIGUNG aller Packs - ein Name, der bei einem Pack startet, gehoert in kein
|
|
529
|
+
# Profil, weil dasselbe Profil auch dort ausgeliefert wird.
|
|
530
|
+
if not alle_start:
|
|
531
|
+
return
|
|
532
|
+
adir = os.path.join(root, KERN, "framework", "runtime", "agents")
|
|
533
|
+
if not os.path.isdir(adir):
|
|
534
|
+
err(f"{KERN}/framework/runtime/agents: fehlt. Pruefung 35 misst die "
|
|
535
|
+
f"Agentenprofile des Kerns; ohne die Ablage prueft sie nichts und "
|
|
536
|
+
f"bestuende leise (D-23)")
|
|
537
|
+
return
|
|
538
|
+
for datei in sorted(os.listdir(adir)):
|
|
539
|
+
if not datei.endswith(".md"):
|
|
540
|
+
continue
|
|
541
|
+
rel = KERN + "/framework/runtime/agents/" + datei
|
|
542
|
+
roh = read(os.path.join(adir, datei)).replace("\r\n", "\n")
|
|
543
|
+
teile = roh.split("---")
|
|
544
|
+
if len(teile) < 3:
|
|
545
|
+
err(f"{rel}: kein Frontmatter zwischen zwei '---'. Ohne es prueft "
|
|
546
|
+
f"Pruefung 35 nichts und bestuende leise (D-23)")
|
|
547
|
+
continue
|
|
548
|
+
for name in sorted(alle_start):
|
|
549
|
+
muster = r"(?<![A-Za-z])%s(?![A-Za-z])" % re.escape(name.lower())
|
|
550
|
+
if re.search(muster, teile[1].lower()):
|
|
551
|
+
err(f"{rel}: Das Frontmatter nennt '{name}' - ein Werkzeug, mit dem "
|
|
552
|
+
f"ein Unteragent GESTARTET wird (agent_start_tools). Ein Profil, "
|
|
553
|
+
f"das es bekommt, kann eine zweite, weniger beschraenkte Ebene "
|
|
554
|
+
f"oeffnen, und die Zusage A1 waere aushebelbar (D-73, "
|
|
555
|
+
f"CR-2026-059)")
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _frontmatter_verben(text: str) -> tuple[list[str], list[str]]:
|
|
559
|
+
"""Die groben Verben aus allowed-tools und permissions.deny eines Frontmatters."""
|
|
560
|
+
fm = text.replace("\r\n", "\n")
|
|
561
|
+
if not fm.startswith("---\n") or "\n---\n" not in fm:
|
|
562
|
+
return [], []
|
|
563
|
+
fm = fm.split("\n---\n", 1)[0][4:] + "\n"
|
|
564
|
+
m = re.search(r"^allowed-tools:[ \t]*\n((?:[ \t]+-[ \t]+\S+[ \t]*\n)+)", fm, re.M)
|
|
565
|
+
erlaubt = [x.strip("- \t") for x in m.group(1).strip().split("\n")] if m else []
|
|
566
|
+
verboten: list[str] = []
|
|
567
|
+
m = re.search(r"^permissions:[ \t]*\n((?:[ \t]+\S.*\n)+)", fm, re.M)
|
|
568
|
+
if m:
|
|
569
|
+
m2 = re.search(r"^([ \t]+)deny:[ \t]*\n((?:[ \t]+-[ \t]+.*\n)+)", m.group(1), re.M)
|
|
570
|
+
if m2:
|
|
571
|
+
verboten = [z.strip().lstrip("-").strip() for z in m2.group(2).splitlines()
|
|
572
|
+
if z.strip() and "(" not in z]
|
|
573
|
+
return erlaubt, verboten
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _quelldateien(root: str) -> list[tuple[str, str]]:
|
|
577
|
+
"""Die ausgelieferten Quellen mit Frontmatter-Verben: Skills und Agentenprofile."""
|
|
578
|
+
out: list[tuple[str, str]] = []
|
|
579
|
+
basis = os.path.join(root, KERN, "framework")
|
|
580
|
+
for wurzel, verzeichnisse, dateien in os.walk(basis):
|
|
581
|
+
verzeichnisse[:] = sorted(d for d in verzeichnisse if d != "__pycache__")
|
|
582
|
+
for datei in sorted(dateien):
|
|
583
|
+
if datei != "SKILL.md" and not wurzel.endswith(os.sep + "agents"):
|
|
584
|
+
continue
|
|
585
|
+
if not datei.endswith(".md"):
|
|
586
|
+
continue
|
|
587
|
+
pfad = os.path.join(wurzel, datei)
|
|
588
|
+
out.append((os.path.relpath(pfad, root).replace(os.sep, "/"), read(pfad)))
|
|
589
|
+
return out
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def check_werkzeugabbildung(root: str) -> None:
|
|
593
|
+
"""Pruefung 38 (D-78 bis D-80): eine Quelle, ein Vokabular, eine Richtung."""
|
|
594
|
+
kern = os.path.join(root, KERN)
|
|
595
|
+
if kern not in sys.path:
|
|
596
|
+
sys.path.insert(0, kern)
|
|
597
|
+
try:
|
|
598
|
+
import clientmap
|
|
599
|
+
except ImportError:
|
|
600
|
+
warn(f"{KERN}/clientmap.py nicht gefunden - Pruefung 38 laeuft nicht")
|
|
601
|
+
return
|
|
602
|
+
# Die Sonde auf den verlorenen Anker (seit 0.32.0): Diese Pruefung findet ihren
|
|
603
|
+
# Gegenstand ueber zwei Namen des Moduls. Verschwinden sie, bestuende sie leise.
|
|
604
|
+
fehlend = [n for n in ("FRONTMATTER_VERBEN", "VERB_BRUECKE")
|
|
605
|
+
if not getattr(clientmap, n, None)]
|
|
606
|
+
if fehlend:
|
|
607
|
+
err(f"{KERN}/clientmap.py: {', '.join(fehlend)} fehlt - Pruefung 38 hat ihren "
|
|
608
|
+
f"Anker verloren und wuerde leise bestehen (D-23, D-78)")
|
|
609
|
+
return
|
|
610
|
+
vokabular = tuple(clientmap.FRONTMATTER_VERBEN)
|
|
611
|
+
bruecke = dict(clientmap.VERB_BRUECKE)
|
|
612
|
+
|
|
613
|
+
basis = os.path.join(root, KERN, "clients")
|
|
614
|
+
if not os.path.isdir(basis):
|
|
615
|
+
return
|
|
616
|
+
gesehen = 0
|
|
617
|
+
for pack in sorted(os.listdir(basis)):
|
|
618
|
+
if pack.startswith("_"):
|
|
619
|
+
continue
|
|
620
|
+
pfad = os.path.join(basis, pack, "manifest.json")
|
|
621
|
+
if not os.path.isfile(pfad):
|
|
622
|
+
continue
|
|
623
|
+
rel = os.path.relpath(pfad, root).replace(os.sep, "/")
|
|
624
|
+
try:
|
|
625
|
+
man = json.loads(read(pfad))
|
|
626
|
+
except ValueError:
|
|
627
|
+
continue
|
|
628
|
+
gesehen += 1
|
|
629
|
+
hook = man.get("hook_tools") or {}
|
|
630
|
+
for block in FRONTMATTER_BLOECKE:
|
|
631
|
+
fmt = man.get(block)
|
|
632
|
+
if fmt is None:
|
|
633
|
+
err(f"{rel}: Block '{block}' fehlt - ohne ihn ist nicht entschieden, "
|
|
634
|
+
f"welche Werkzeugnamen die installierte Fassung traegt (D-78)")
|
|
635
|
+
continue
|
|
636
|
+
abbildung = fmt.get("tool_names")
|
|
637
|
+
if not isinstance(abbildung, dict):
|
|
638
|
+
err(f"{rel}: {block}.tool_names fehlt oder ist kein Objekt")
|
|
639
|
+
continue
|
|
640
|
+
erklaert = fmt.get("tool_names_unmapped") or []
|
|
641
|
+
if not isinstance(erklaert, list):
|
|
642
|
+
err(f"{rel}: {block}.tool_names_unmapped ist keine Liste")
|
|
643
|
+
erklaert = []
|
|
644
|
+
if erklaert and not str(fmt.get("_tool_names_unmapped_note") or "").strip():
|
|
645
|
+
err(f"{rel}: {block}.tool_names_unmapped nennt {sorted(erklaert)}, aber "
|
|
646
|
+
f"'_tool_names_unmapped_note' fehlt oder ist leer. Ein Verb nicht "
|
|
647
|
+
f"abzubilden ist eine Aussage ueber den Client oder ueber den "
|
|
648
|
+
f"Belegstand - sie gehoert begruendet, nicht bloss eingetragen "
|
|
649
|
+
f"(D-78, Bauform wie hook_tools_absent nach D-47)")
|
|
650
|
+
for schluessel in sorted(abbildung):
|
|
651
|
+
if schluessel not in vokabular:
|
|
652
|
+
err(f"{rel}: {block}.tool_names bildet '{schluessel}' ab - kein "
|
|
653
|
+
f"Verb des Frontmatter-Vokabulars {list(vokabular)}. Eine "
|
|
654
|
+
f"Quelle des Kerns kann es nicht schreiben, die Abbildung "
|
|
655
|
+
f"laeuft also ins Leere (D-78)")
|
|
656
|
+
for verb in vokabular:
|
|
657
|
+
hat = verb in abbildung
|
|
658
|
+
erklaert_hier = verb in erklaert
|
|
659
|
+
if hat and erklaert_hier:
|
|
660
|
+
err(f"{rel}: {block} bildet das Verb '{verb}' ab UND erklaert es "
|
|
661
|
+
f"zugleich fuer nicht abgebildet. Beides zugleich geht nicht - "
|
|
662
|
+
f"eine der beiden Aussagen ist falsch (D-78)")
|
|
663
|
+
elif not hat and not erklaert_hier:
|
|
664
|
+
err(f"{rel}: {block}.tool_names kennt das Werkzeugverb '{verb}' "
|
|
665
|
+
f"nicht, und tool_names_unmapped erklaert die Abwesenheit "
|
|
666
|
+
f"nicht. Bis 0.39.0 wurde das Verb dann WOERTLICH als "
|
|
667
|
+
f"Werkzeugname durchgereicht; eine Luecke allein ist keine "
|
|
668
|
+
f"Aussage (D-78)")
|
|
669
|
+
|
|
670
|
+
# Gegenstand 3: Die Sperrliste darf nicht enger sein als die Vorabfreigabe.
|
|
671
|
+
#
|
|
672
|
+
# Die Regel vergleicht zwei Listen und setzt damit voraus, dass sie
|
|
673
|
+
# DENSELBEN Namensraum fuehren. Bei claude-code tun sie das; bei
|
|
674
|
+
# devin-desktop nicht, und das ist gemessen (2026-09-14, D-88): Sein
|
|
675
|
+
# Frontmatter kennt ein eigenes, normalisiertes Vokabular - read,
|
|
676
|
+
# grep, glob, edit, exec, web_search -, waehrend seine Laufzeit das
|
|
677
|
+
# glob-foermige Werkzeug find_file_by_name nennt. Ein Vergleich der
|
|
678
|
+
# beiden Listen meldete dann einen Unterschied, den es nicht gibt.
|
|
679
|
+
#
|
|
680
|
+
# Ein Pack sagt das mit tool_names_namespace: 'eigen'. WAS DABEI
|
|
681
|
+
# VERLOREN GEHT, gehoert gesagt: Fuer ein solches Pack prueft niemand
|
|
682
|
+
# mehr, ob die Sperre die Vorabfreigabe deckt - die Frage von D-80
|
|
683
|
+
# bleibt dort offen und ist nur anders gestellt, nicht beantwortet.
|
|
684
|
+
if str(fmt.get("tool_names_namespace") or "") == "eigen":
|
|
685
|
+
if not str(fmt.get("_tool_names_note") or "").strip():
|
|
686
|
+
err(f"{rel}: {block}.tool_names_namespace ist 'eigen', aber "
|
|
687
|
+
f"'_tool_names_note' fehlt oder ist leer. Einen eigenen "
|
|
688
|
+
f"Namensraum zu erklaeren nimmt die Richtungsregel von "
|
|
689
|
+
f"D-80 ausser Kraft - das gehoert begruendet, nicht "
|
|
690
|
+
f"bloss eingetragen (D-88)")
|
|
691
|
+
continue
|
|
692
|
+
for verb in sorted(abbildung):
|
|
693
|
+
ziel = bruecke.get(verb)
|
|
694
|
+
if ziel is None or ziel not in hook:
|
|
695
|
+
continue
|
|
696
|
+
vorab = {str(x) for x in (abbildung.get(verb) or [])}
|
|
697
|
+
sperre = {str(x) for x in (hook.get(ziel) or [])}
|
|
698
|
+
zuviel = sorted(vorab - sperre)
|
|
699
|
+
if zuviel:
|
|
700
|
+
err(f"{rel}: {block}.tool_names['{verb}'] gibt {zuviel} vorab "
|
|
701
|
+
f"frei, hook_tools['{ziel}'] fuehrt sie nicht. Die Sperrliste "
|
|
702
|
+
f"ist damit enger als die Vorabfreigabe: Ein Skill, der "
|
|
703
|
+
f"'{verb}' freigibt und '{verb}' sperrt, bekaeme ein Werkzeug "
|
|
704
|
+
f"freigegeben, das die Sperre nicht erfasst. Die umgekehrte "
|
|
705
|
+
f"Abweichung ist zulaessig (D-80)")
|
|
706
|
+
if not gesehen:
|
|
707
|
+
err(f"{KERN}/clients: kein Manifest gefunden. Pruefung 38 misst die "
|
|
708
|
+
f"Werkzeugabbildungen der Packs; ohne sie prueft sie nichts und bestuende "
|
|
709
|
+
f"leise (D-23)")
|
|
710
|
+
|
|
711
|
+
# Gegenstand 4: die Quellen selbst.
|
|
712
|
+
quellen = _quelldateien(root)
|
|
713
|
+
if not quellen:
|
|
714
|
+
err(f"{KERN}/framework: keine Quelle mit Frontmatter gefunden. Pruefung 38 "
|
|
715
|
+
f"misst die Verben der ausgelieferten Skills und Agentenprofile; ohne sie "
|
|
716
|
+
f"prueft der vierte Gegenstand nichts und bestuende leise (D-23)")
|
|
717
|
+
return
|
|
718
|
+
for rel, text in quellen:
|
|
719
|
+
erlaubt, verboten = _frontmatter_verben(text)
|
|
720
|
+
for feld, verben in (("allowed-tools", erlaubt), ("permissions.deny", verboten)):
|
|
721
|
+
for verb in verben:
|
|
722
|
+
if verb not in vokabular:
|
|
723
|
+
err(f"{rel}: {feld} nennt das Verb '{verb}' - das Vokabular des "
|
|
724
|
+
f"Frontmatters kennt nur {list(vokabular)}. In allowed-tools "
|
|
725
|
+
f"wurde es bis 0.39.0 woertlich als Werkzeugname "
|
|
726
|
+
f"durchgereicht, in permissions.deny fiel es lautlos aus "
|
|
727
|
+
f"(D-78, D-79)")
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
# ---------------------------------------------------------------------------
|
|
732
|
+
# Pruefung 39: Die Vorabfreigabe des Skillaufrufs deckt sich mit den ausgelieferten
|
|
733
|
+
# Skills, und die Skillwahl steht an allen vier Traegern (CR-2026-063, D-81 bis D-84).
|
|
734
|
+
#
|
|
735
|
+
# ANLASS. Die Wurzel-Anweisungsdatei fordert seit jeher, fuer Standardaufgaben die
|
|
736
|
+
# Skills zu nutzen - und die ausgelieferte Berechtigungsdatei kannte das Werkzeug, mit
|
|
737
|
+
# dem das geht, in KEINEM Korb. Gemessen am 2026-09-14
|
|
738
|
+
# (tests/protocols/2026-09-14-erhebung-skillaufruf.md): Der Aufruf wurde abgewiesen, die
|
|
739
|
+
# Sitzung las die SKILL.md ersatzweise als Datei, und die Ausgabe sah aus wie ein
|
|
740
|
+
# gelungener Lauf. Die Ursache lag nicht im Pack, sondern im Vokabular der Kernquelle -
|
|
741
|
+
# sechs Verben, keines fuer den Skillaufruf.
|
|
742
|
+
#
|
|
743
|
+
# VIER GEGENSTAENDE:
|
|
744
|
+
# 1. Der verlorene Anker. Fehlt das Skillverzeichnis des Kerns oder fuehrt
|
|
745
|
+
# permissions.json keine einzige skill-Regel, meldet diese Pruefung das selbst -
|
|
746
|
+
# sonst bestuende sie leise (D-23).
|
|
747
|
+
# 2. Die Deckung in BEIDE Richtungen. Jeder ausgelieferte Skill hat genau eine
|
|
748
|
+
# allow-Regel, und jede Regel nennt einen ausgelieferten Skill. Das ist der Preis
|
|
749
|
+
# von D-81 E3: Die Regeln stehen in der Datei, statt aus dem Verzeichnis erzeugt zu
|
|
750
|
+
# werden - nach D-53 IST der Inhalt dieser Datei die Berechtigung. Ohne diese
|
|
751
|
+
# Pruefung waere das die zweite Liste fuer dieselbe Sache, also der Befundtyp von
|
|
752
|
+
# CR-2026-062.
|
|
753
|
+
# 3. Kein Musterzeichen. Gemessen am 2026-09-14 (D-82): Der Vergleich ist woertlich.
|
|
754
|
+
# Skill(fw-*) weist den Aufruf ab, Skill(fw-code-explain) laesst ihn durch,
|
|
755
|
+
# Skill(fw-plan) weist fw-code-explain ab (Kontrolllauf). Eine Regel mit * oder ?
|
|
756
|
+
# saehe richtig aus und gaebe LAUTLOS NICHTS frei - die Bauform von D-66 mit
|
|
757
|
+
# umgekehrtem Vorzeichen.
|
|
758
|
+
# 4. Die vier Regeltraeger. Die Skillwahl stand vor 0.41.0 an fuenf Stellen und
|
|
759
|
+
# erreichte den Agenten an keiner verbindlich. Sie steht jetzt in der
|
|
760
|
+
# Wurzel-Anweisungsdatei, in der always-on-Kurzfassung, im Arbeitsmodell und in den
|
|
761
|
+
# Prompting-Regeln. Geprueft wird die ANWESENHEIT je Traeger, nicht der Wortlaut:
|
|
762
|
+
# Eine Pruefung, die Prosa vergleicht, bricht bei jeder Umformulierung.
|
|
763
|
+
#
|
|
764
|
+
# WAS SIE HEUTE FAENGT: nichts - Gegenstand 2 und 3 sind mit demselben Release entstanden
|
|
765
|
+
# und passen per Konstruktion zu sich selbst; Gegenstand 4 ebenso. Das ist die Lage von
|
|
766
|
+
# Pruefung 37 und dieselbe Ehrlichkeit: Der Gegenbeweis gegen den Vorstand ist hier eine
|
|
767
|
+
# KONSTRUKTION, kein Abzaehlen, und was die Pruefung wert ist, haengt an ihren Sonden.
|
|
768
|
+
#
|
|
769
|
+
# WAS SIE NICHT LEISTET: Sie belegt nicht, dass der Agent den Skill waehlt. Das ist ein
|
|
770
|
+
# Sitzungstest, und er ist nicht gefahren. Sie belegt, dass die Freigabe zur Skillmenge
|
|
771
|
+
# passt und dass keiner der vier Traeger die Regel verliert.
|
|
772
|
+
SKILLWAHL_TRAEGER = (
|
|
773
|
+
("framework/runtime/root-instruction.md",
|
|
774
|
+
"Bevor du einen Schritt beginnst"),
|
|
775
|
+
("framework/runtime/rules/00-framework-core.md",
|
|
776
|
+
"Skillwahl vor dem Schritt"),
|
|
777
|
+
("framework/core/05-working-model.md",
|
|
778
|
+
"ist er der vorgesehene Weg des Schrittes"),
|
|
779
|
+
("framework/core/06-prompting-rules.md",
|
|
780
|
+
"Skills bevorzugen \u2013 von beiden Seiten"),
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _skill_regeln(root: str) -> list[str] | None:
|
|
785
|
+
"""Die Aufrufnamen aus den skill-Regeln der Kernquelle, oder None ohne Quelle."""
|
|
786
|
+
pfad = os.path.join(root, KERN, "framework", "runtime", "permissions.json")
|
|
787
|
+
if not os.path.exists(pfad):
|
|
788
|
+
return None
|
|
789
|
+
try:
|
|
790
|
+
quelle = json.loads(read(pfad))
|
|
791
|
+
except json.JSONDecodeError:
|
|
792
|
+
return None
|
|
793
|
+
return [r.get("pattern", "") for r in quelle.get("allow", [])
|
|
794
|
+
if isinstance(r, dict) and r.get("tool") == "skill"]
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def check_skillfreigabe(root: str) -> None:
|
|
798
|
+
"""Pruefung 39 (D-81 bis D-84): Freigabe, Skillmenge und Regeltraeger decken sich."""
|
|
799
|
+
verzeichnis = os.path.join(root, KERN, "framework", "skills")
|
|
800
|
+
if not os.path.isdir(verzeichnis):
|
|
801
|
+
err(f"{KERN}/framework/skills: fehlt. Pruefung 39 misst die Vorabfreigabe des "
|
|
802
|
+
f"Skillaufrufs gegen die ausgelieferten Skills; ohne das Verzeichnis "
|
|
803
|
+
f"prueft sie nichts und bestuende leise (D-23)")
|
|
804
|
+
return
|
|
805
|
+
ausgeliefert = sorted(
|
|
806
|
+
name for name in os.listdir(verzeichnis)
|
|
807
|
+
if os.path.isfile(os.path.join(verzeichnis, name, "SKILL.md")))
|
|
808
|
+
|
|
809
|
+
regeln = _skill_regeln(root)
|
|
810
|
+
if regeln is None:
|
|
811
|
+
err(f"{KERN}/framework/runtime/permissions.json: nicht lesbar. Pruefung 39 hat "
|
|
812
|
+
f"ihren Anker verloren und bestuende sonst leise (D-23)")
|
|
813
|
+
return
|
|
814
|
+
if not regeln:
|
|
815
|
+
err(f"{KERN}/framework/runtime/permissions.json: keine einzige allow-Regel mit "
|
|
816
|
+
f"dem Verb 'skill'. Die Wurzel-Anweisungsdatei fordert in Abschnitt 17 die "
|
|
817
|
+
f"Nutzung der Skills; ohne Freigabe laeuft jeder Aufruf in die Rueckfrage "
|
|
818
|
+
f"und im rueckfragefreien Betrieb in die Abweisung (D-81)")
|
|
819
|
+
return
|
|
820
|
+
|
|
821
|
+
# Gegenstand 2: die Deckung, in beide Richtungen.
|
|
822
|
+
for name in ausgeliefert:
|
|
823
|
+
if name not in regeln:
|
|
824
|
+
err(f"{KERN}/framework/runtime/permissions.json: der ausgelieferte Skill "
|
|
825
|
+
f"'{name}' hat keine allow-Regel. Sein Aufruf laeuft in die Rueckfrage, "
|
|
826
|
+
f"und der Fehlschlag ist stumm - die Sitzung liest die SKILL.md "
|
|
827
|
+
f"ersatzweise als Datei, ohne die Werkzeugbeschraenkung des Skills "
|
|
828
|
+
f"(D-81, D-83)")
|
|
829
|
+
for name in regeln:
|
|
830
|
+
if name not in ausgeliefert:
|
|
831
|
+
err(f"{KERN}/framework/runtime/permissions.json: die allow-Regel fuer "
|
|
832
|
+
f"'{name}' nennt keinen ausgelieferten Skill. Eine Vorabfreigabe fuer "
|
|
833
|
+
f"einen Skill, den es nicht gibt, ist eine Zusage ohne Gegenstand "
|
|
834
|
+
f"(D-81)")
|
|
835
|
+
doppelt = sorted({n for n in regeln if regeln.count(n) > 1})
|
|
836
|
+
if doppelt:
|
|
837
|
+
err(f"{KERN}/framework/runtime/permissions.json: doppelte skill-Regel(n) fuer "
|
|
838
|
+
f"{', '.join(doppelt)}")
|
|
839
|
+
|
|
840
|
+
# Gegenstand 3: kein Musterzeichen.
|
|
841
|
+
for name in regeln:
|
|
842
|
+
if any(z in name for z in "*?["):
|
|
843
|
+
err(f"{KERN}/framework/runtime/permissions.json: die skill-Regel '{name}' "
|
|
844
|
+
f"traegt ein Musterzeichen. Der Vergleich ist woertlich - gemessen am "
|
|
845
|
+
f"2026-09-14 (D-82): Skill(fw-*) weist den Aufruf ab. Eine Regel mit "
|
|
846
|
+
f"Muster saehe richtig aus und gaebe lautlos nichts frei")
|
|
847
|
+
|
|
848
|
+
# Gegenstand 4: die vier Regeltraeger.
|
|
849
|
+
for rel, anker in SKILLWAHL_TRAEGER:
|
|
850
|
+
pfad = os.path.join(root, KERN, *rel.split("/"))
|
|
851
|
+
if not os.path.exists(pfad):
|
|
852
|
+
err(f"{KERN}/{rel}: fehlt. Pruefung 39 misst dort die Skillwahl (D-84)")
|
|
853
|
+
continue
|
|
854
|
+
if anker not in read(pfad):
|
|
855
|
+
err(f"{KERN}/{rel}: die Skillwahl fehlt (gesucht: '{anker}'). Sie stand vor "
|
|
856
|
+
f"0.41.0 an fuenf Stellen und erreichte den Agenten an keiner "
|
|
857
|
+
f"verbindlich; faellt ein Traeger weg, faellt sie leise zurueck (D-84)")
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
# --- Pruefung 72: Ein aktiviertes Pack steht auch im Berechtigungskorb -------------
|
|
861
|
+
#
|
|
862
|
+
# ANLASS, UND ER IST GEMESSEN (2026-09-21, Vorbedingungsdurchgang von Buendel 5,
|
|
863
|
+
# D-238). Bundel 5 misst `role-re-ticket`, den einzigen Skill dieses Frameworks, der
|
|
864
|
+
# nicht im Kern liegt. Nach der Aktivierung WORTGETREU nach
|
|
865
|
+
# `framework/role-packs/README.md` - Laufzeitfassung kopiert, Skillverzeichnis kopiert -
|
|
866
|
+
# und nach `install.py --update` stand im Messbaum:
|
|
867
|
+
#
|
|
868
|
+
# Skillverzeichnisse in .claude/skills/ 13
|
|
869
|
+
# Skill(...)-Eintraege im allow-Korb 12
|
|
870
|
+
# Skill(role-re-ticket) fehlt
|
|
871
|
+
# validate-framework.py --strict-overlay 0 Fehler, 0 Warnungen
|
|
872
|
+
#
|
|
873
|
+
# DIE REGELSCHICHT DES PACKS WAR VOLLSTAENDIG, DIE TECHNISCHE KANNTE ES NICHT. Und
|
|
874
|
+
# `defaultMode` steht auf `default`: Ein nicht genannter Aufruf faellt in den
|
|
875
|
+
# Rueckfragekorb, im nicht-interaktiven Betrieb also in die Abweisung. D-81 hat genau
|
|
876
|
+
# diesen Ausgang beschrieben - "der Fehlschlag ist stumm; die Sitzung liest die
|
|
877
|
+
# SKILL.md ersatzweise als Datei, OHNE die Werkzeugbeschraenkung des Skills".
|
|
878
|
+
#
|
|
879
|
+
# WARUM PRUEFUNG 39 ES NICHT SIEHT, OBWOHL SIE DAFUER GEBAUT IST. Sie haelt
|
|
880
|
+
# `framework/runtime/permissions.json` gegen `framework/skills/` - Regelmenge des Kerns
|
|
881
|
+
# gegen Skills des Kerns, beides Ebene 3, und dort deckt es sich (12 zu 12). Ein
|
|
882
|
+
# Packskill ist Ebene 6 und kommt in keiner der beiden Mengen vor. Derselbe Zuschnitt,
|
|
883
|
+
# der D-234 drei Tage zuvor unterlaufen ist: eine Stelle, die `framework/skills` sagt
|
|
884
|
+
# und `framework/role-packs/<pack>/skills` meint.
|
|
885
|
+
#
|
|
886
|
+
# WARUM DIE ABHILFE NICHT IN permissions.json LIEGT. Eine allow-Regel dort traegt JEDE
|
|
887
|
+
# Installation - auch die, die das Pack nicht aktiviert hat. Das waere "eine
|
|
888
|
+
# Vorabfreigabe fuer einen Skill, den es nicht gibt", und genau das verbietet
|
|
889
|
+
# Pruefung 39 in ihrer Gegenrichtung. Die Aktivierung ist eine Projektentscheidung
|
|
890
|
+
# (README Punkt 4) und hat deshalb DREI Teile, nicht zwei; diese Pruefung setzt den
|
|
891
|
+
# dritten durch, und zwar dort, wo er hingehoert: in der Installation.
|
|
892
|
+
#
|
|
893
|
+
# WARUM SIE BEI EINEM CLIENT SCHWEIGT, UND WARUM DAS KEINE STILLE NULL IST. Das
|
|
894
|
+
# Manifest fuehrt die Schreibweise des Skillaufrufs als `permission_tools.skill`. Bei
|
|
895
|
+
# `claude-code` steht dort `["Skill"]`, bei `devin-desktop` eine LEERE Liste - erhoben
|
|
896
|
+
# am 2026-09-14 (D-89): Der Aufruf ist dort zwar ein Werkzeugaufruf, aber es ist keine
|
|
897
|
+
# Schreibweise bekannt, mit der eine Regel ihn beim Namen nennt. Eine leere Liste ist
|
|
898
|
+
# deklariert; ein FEHLENDES Feld ist ein Befund, und den meldet sie - dieselbe
|
|
899
|
+
# Trennlinie zwischen "nicht abgebildet" und "gibt es nicht", die D-155 zieht.
|
|
900
|
+
P72_MUSTER = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)\(([^()]*)\)$")
|
|
901
|
+
P72_SELBSTPROBE = "Skill(role-beispiel)" # SYNTHETISCH
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _p72_eintraege(cfg) -> list:
|
|
905
|
+
"""Jeder Regeleintrag der Berechtigungsdatei, ueber alle Koerbe.
|
|
906
|
+
|
|
907
|
+
Ueber die Koerbe zu laufen statt `allow` zu nennen ist Absicht: Ein Eintrag in
|
|
908
|
+
`ask` oder `deny` NENNT den Skill ebenso, und wer nur `allow` liest, meldet ein
|
|
909
|
+
ausdrueckliches Verbot als fehlenden Eintrag.
|
|
910
|
+
"""
|
|
911
|
+
aus: list = []
|
|
912
|
+
|
|
913
|
+
def rein(o) -> None:
|
|
914
|
+
if isinstance(o, dict):
|
|
915
|
+
for v in o.values():
|
|
916
|
+
rein(v)
|
|
917
|
+
elif isinstance(o, list):
|
|
918
|
+
for x in o:
|
|
919
|
+
rein(x)
|
|
920
|
+
elif isinstance(o, str):
|
|
921
|
+
aus.append(o)
|
|
922
|
+
|
|
923
|
+
rein(cfg.get("permissions", cfg) if isinstance(cfg, dict) else cfg)
|
|
924
|
+
return aus
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def check_pack_im_korb(root: str, man: dict) -> None:
|
|
928
|
+
"""Pruefung 72 (D-238): Jeder Skill der Installation steht im Berechtigungskorb."""
|
|
929
|
+
if formatgebunden(man, 72):
|
|
930
|
+
return # D-346, D-416: diese Ausgabeform erreicht die Pruefung nicht
|
|
931
|
+
if not P72_MUSTER.match(P72_SELBSTPROBE):
|
|
932
|
+
err(f"Pruefung 72: das eigene Muster trifft {P72_SELBSTPROBE!r} nicht mehr - "
|
|
933
|
+
f"sie haette ihren Gegenstand verloren und bestuende leise (D-23, D-238)")
|
|
934
|
+
return
|
|
935
|
+
pack = man.get("client", "?")
|
|
936
|
+
werkzeuge = man.get("permission_tools")
|
|
937
|
+
if not isinstance(werkzeuge, dict) or "skill" not in werkzeuge:
|
|
938
|
+
err(f"clients/{pack}/manifest.json: Feld permission_tools.skill fehlt. Ein "
|
|
939
|
+
f"Pack fuehrt es, notfalls leer - der Unterschied zwischen „nicht "
|
|
940
|
+
f"abgebildet“ und „gibt es nicht“ gehoert deklariert und nicht aus einem "
|
|
941
|
+
f"fehlenden Feld erraten (D-155, D-238)")
|
|
942
|
+
return
|
|
943
|
+
namen = [w for w in (werkzeuge.get("skill") or []) if isinstance(w, str)]
|
|
944
|
+
if not namen:
|
|
945
|
+
return # Deklariert leer (devin-desktop, D-89) - keine Schreibweise bekannt
|
|
946
|
+
rel_skills = man.get("skills_dir")
|
|
947
|
+
rel_perm = man.get("permissions_file")
|
|
948
|
+
if not rel_skills or not rel_perm:
|
|
949
|
+
return
|
|
950
|
+
ablage = os.path.join(root, *rel_skills.split("/"))
|
|
951
|
+
pfad = os.path.join(root, *rel_perm.split("/"))
|
|
952
|
+
if not os.path.isdir(ablage) or not os.path.isfile(pfad):
|
|
953
|
+
return # Keine Installation an dieser Wurzel - hier gibt es nichts zu decken
|
|
954
|
+
try:
|
|
955
|
+
cfg = json.loads(read(pfad))
|
|
956
|
+
except json.JSONDecodeError:
|
|
957
|
+
return # Pruefung 3 meldet das bereits
|
|
958
|
+
im_baum = sorted(n for n in os.listdir(ablage)
|
|
959
|
+
if os.path.isfile(os.path.join(ablage, n, "SKILL.md")))
|
|
960
|
+
genannt = []
|
|
961
|
+
if man.get("permissions_format") == "kiro-agent":
|
|
962
|
+
# Das Agentenprofil nennt einen Skill nicht als Werkzeug(Name), sondern als
|
|
963
|
+
# Muster einer Regel mit der Faehigkeit des Skillaufrufs (D-414, D-416).
|
|
964
|
+
regeln = ((cfg.get("permissions") or {}).get("rules") or []
|
|
965
|
+
if isinstance(cfg, dict) else [])
|
|
966
|
+
for regel in regeln:
|
|
967
|
+
if isinstance(regel, dict) and regel.get("capability") in namen:
|
|
968
|
+
genannt.extend(m for m in (regel.get("match") or [])
|
|
969
|
+
if isinstance(m, str) and m.strip())
|
|
970
|
+
for eintrag in ([] if genannt else _p72_eintraege(cfg)):
|
|
971
|
+
m = P72_MUSTER.match(eintrag.strip())
|
|
972
|
+
if m and m.group(1) in namen and m.group(2):
|
|
973
|
+
genannt.append(m.group(2))
|
|
974
|
+
# Ein deklarierter fremder Skill (1.21.0, K-31) ist auch durch ein Muster seines
|
|
975
|
+
# Praefixes genannt, etwa Skill(openspec-*) - der Korb bleibt Pflicht, nur die
|
|
976
|
+
# Schreibweise ist die des fremden Rahmenwerks.
|
|
977
|
+
fremd = fremde_praefixe(root)
|
|
978
|
+
muster = [g[:-1] for g in genannt if g.endswith("*")]
|
|
979
|
+
for name in im_baum:
|
|
980
|
+
if name not in genannt and not (
|
|
981
|
+
any(name.startswith(p) for p in fremd)
|
|
982
|
+
and any(m and name.startswith(m) for m in muster)):
|
|
983
|
+
err(f"{rel_perm}: der Skill '{name}' liegt in {rel_skills}/, wird aber "
|
|
984
|
+
f"von keinem Eintrag der Berechtigungsdatei genannt. Sein Aufruf "
|
|
985
|
+
f"faellt in den Rueckfragekorb und im rueckfragefreien Betrieb in die "
|
|
986
|
+
f"Abweisung; die Sitzung liest die SKILL.md dann ersatzweise als "
|
|
987
|
+
f"Datei, ohne die Werkzeugbeschraenkung des Skills (D-81). Bei einem "
|
|
988
|
+
f"Pack-Skill ist das der dritte Teil der Aktivierung "
|
|
989
|
+
f"(framework/role-packs/README.md), nicht ein Schritt von install.py "
|
|
990
|
+
f"(D-238)")
|
|
991
|
+
for name in sorted(set(genannt)):
|
|
992
|
+
if name not in im_baum and not name.endswith("*"):
|
|
993
|
+
err(f"{rel_perm}: der Eintrag fuer '{name}' nennt keinen Skill in "
|
|
994
|
+
f"{rel_skills}/. Eine Freigabe fuer einen Skill, den diese "
|
|
995
|
+
f"Installation nicht hat, ist eine Zusage ohne Gegenstand (D-81, "
|
|
996
|
+
f"D-238)")
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
# ---------------------------------------------------------------------------
|
|
1000
|
+
# PRUEFUNG 88: DIE DATEI, DIE DIE WURZEL-ANWEISUNG VERDRAENGT
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
# ANLASS, UND ER IST DER SCHWERSTE BEFUND DER ERHEBUNG VON 1.3.0 (D-341, hier
|
|
1003
|
+
# durchgesetzt mit CR-2026-133). Bei einem Client kann eine Datei NEBEN der
|
|
1004
|
+
# Wurzel-Anweisung diese vollstaendig ersetzen: Liegt sie im Projekt, steht die
|
|
1005
|
+
# Wurzel-Anweisung des Frameworks in KEINER Nachricht der Sitzung - gemessen mit
|
|
1006
|
+
# Gegenprobe. Ein aufnehmendes Projekt, das die Datei in sein `.gitignore` schreibt -
|
|
1007
|
+
# der naheliegende Ort fuer eine persoenliche Fassung -, haette damit eine
|
|
1008
|
+
# unversionierte Ebene 1 je Arbeitsplatz.
|
|
1009
|
+
# ➡️ Eine Wurzel-Anweisung, die eine ungepruefte Datei im selben Verzeichnis ersetzen
|
|
1010
|
+
# kann, ist keine Ebene 1 - sie ist ein Standard.
|
|
1011
|
+
# Welches Pack eine solche Datei kennt, sagt das Manifest (root_instruction_override).
|
|
1012
|
+
# Der Schutz besteht aus drei Teilen, und dies ist der dritte: Der deny-Korb stellt die
|
|
1013
|
+
# Datei schreibgeschuetzt, der Schutz-Hook fuehrt sie in seinen Mustern - und diese
|
|
1014
|
+
# Pruefung MELDET SIE, wenn sie trotzdem da ist. Die ersten beiden verhindern, dass der
|
|
1015
|
+
# Agent sie anlegt; ein Mensch kann es weiterhin, und dann soll es nicht still bleiben.
|
|
1016
|
+
def check_verdraengende_wurzelanweisung(root: str, man: dict) -> None:
|
|
1017
|
+
"""Pruefung 88 (D-341, D-347): Keine Datei, die die Wurzel-Anweisung verdraengt."""
|
|
1018
|
+
if not man.get("root_instruction_override"):
|
|
1019
|
+
return
|
|
1020
|
+
rel = (man.get("runtime_placeholders") or {}).get("<ROOT_INSTRUCTION_LOCAL>")
|
|
1021
|
+
if not rel:
|
|
1022
|
+
err(f"clients/{man.get('client', '?')}/manifest.json: root_instruction_override "
|
|
1023
|
+
f"ist gesetzt, aber <ROOT_INSTRUCTION_LOCAL> fehlt in runtime_placeholders – "
|
|
1024
|
+
f"Prüfung 88 wüsste nicht, welche Datei sie meldet (D-23)")
|
|
1025
|
+
return
|
|
1026
|
+
if os.path.exists(os.path.join(root, *rel.split("/"))):
|
|
1027
|
+
err(f"{rel}: liegt im Projekt und VERDRÄNGT bei diesem Client die "
|
|
1028
|
+
f"Wurzel-Anweisung {man.get('root_instruction_file', '?')} vollständig – "
|
|
1029
|
+
f"deren Text steht dann in keiner Nachricht der Sitzung. Ebene 1 der "
|
|
1030
|
+
f"Prioritätshierarchie wäre damit durch eine ungeprüfte Datei ersetzt, und "
|
|
1031
|
+
f"bis 1.4.0 meldete es nichts (D-341)")
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
# ---------------------------------------------------------------------------
|
|
1035
|
+
# PRUEFUNG 96: DAS AGENTENPROFIL, DAS DIE BERECHTIGUNGEN TRAEGT, IST AUCH DAS AKTIVE
|
|
1036
|
+
# ---------------------------------------------------------------------------
|
|
1037
|
+
# ANLASS, GEMESSEN AM 2026-09-26 (CR-2026-150, D-414). Beim Client kiro stehen die
|
|
1038
|
+
# Berechtigungen des Frameworks in einem Agentenprofil, und sie wirken NUR, wenn es der
|
|
1039
|
+
# aktive Agent ist. Die Einstellungsdatei des Arbeitsbereichs waehlt es. Drei Befunde
|
|
1040
|
+
# machen daraus eine Pruefung:
|
|
1041
|
+
# * Zeigt die Einstellung auf ein Profil, das es nicht gibt, oder ist das Profil kein
|
|
1042
|
+
# gueltiges JSON, faellt der Client STILL auf seinen eingebauten Agenten zurueck -
|
|
1043
|
+
# eine Warnung auf stderr, und .env war lesbar (K15, K16).
|
|
1044
|
+
# * Eine Regel mit unbekannter Faehigkeit ueberspringt er und meldet es nur in seinem
|
|
1045
|
+
# Protokoll (K23). Sein eigenes Pruefkommando `agent validate` endet auch bei
|
|
1046
|
+
# kaputtem JSON mit Exit 0 und prueft keine Faehigkeit (K22).
|
|
1047
|
+
# * Die Pruefungen der Koerbe (37 und Verwandte) lesen deny/ask/allow und erreichen
|
|
1048
|
+
# diese Form nicht (D-416). Diese Pruefung tritt fuer das Agentenprofil an ihre
|
|
1049
|
+
# Stelle.
|
|
1050
|
+
# GEPRUEFT: (a) die Einstellungsdatei traegt die Werte des Manifests; (b) das Profil ist
|
|
1051
|
+
# ein JSON-Objekt mit dem Namen, den die Einstellung waehlt; (c) jede Regel hat eine
|
|
1052
|
+
# bekannte Faehigkeit, einen bekannten Effekt und eine Liste von Mustern; (d) jedes
|
|
1053
|
+
# deny- und ask-Muster der Kernquelle steht mit seinem Effekt (und seiner Ausnahme) im
|
|
1054
|
+
# Profil, und kein allow-Muster steht darin, das die Kernquelle nicht erzeugt - ausser
|
|
1055
|
+
# dem Namen eines installierten Skills (Bauform von Pruefung 72, D-238).
|
|
1056
|
+
# ⚠️ GRENZEN, BENANNT: Ein Muster mit offenem Platzhalter (<EXCLUDED_PATHS> ...) gehoert
|
|
1057
|
+
# dem Projekt und wird nicht verglichen. Und die Pruefung misst die Dateien, nicht den
|
|
1058
|
+
# Start: Wer den Client mit --agent auf einen anderen Agenten stellt, arbeitet ohne diese
|
|
1059
|
+
# Regeln, und keine Pruefung sieht es.
|
|
1060
|
+
P96_EFFEKTE = ("deny", "ask", "allow")
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def _p96_regeln(cfg: dict) -> list:
|
|
1064
|
+
return ((cfg.get("permissions") or {}).get("rules") or []) if isinstance(cfg, dict) else []
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def check_agentenprofil(root: str, man: dict) -> None:
|
|
1068
|
+
"""Pruefung 96 (D-414): Die Einstellung waehlt das Profil, und das Profil traegt."""
|
|
1069
|
+
if man.get("permissions_format") != "kiro-agent":
|
|
1070
|
+
return
|
|
1071
|
+
pack = man.get("client", "?")
|
|
1072
|
+
kern = os.path.join(root, KERN)
|
|
1073
|
+
if kern not in sys.path:
|
|
1074
|
+
sys.path.insert(0, kern)
|
|
1075
|
+
try:
|
|
1076
|
+
import clientmap
|
|
1077
|
+
except ImportError:
|
|
1078
|
+
return # check_config meldet das fehlende Abbildungsmodul bereits
|
|
1079
|
+
if not hasattr(clientmap, "kiro_regeln") or not hasattr(clientmap, "KIRO_FAEHIGKEITEN"):
|
|
1080
|
+
err(f"{KERN}/clientmap.py: 'kiro_regeln' oder 'KIRO_FAEHIGKEITEN' fehlt – Prüfung "
|
|
1081
|
+
f"96 hat ihren Gegenstand verloren und bestünde sonst leise (D-23)")
|
|
1082
|
+
return
|
|
1083
|
+
# (a) Die Einstellungsdatei.
|
|
1084
|
+
einst_rel = man.get("client_settings_file")
|
|
1085
|
+
soll = man.get("client_settings") or {}
|
|
1086
|
+
profil_rel = man.get("permissions_file")
|
|
1087
|
+
if not einst_rel or not soll or not profil_rel:
|
|
1088
|
+
err(f"clients/{pack}/manifest.json: client_settings_file, client_settings und "
|
|
1089
|
+
f"permissions_file gehören zusammen – ohne sie weiß Prüfung 96 nicht, welche "
|
|
1090
|
+
f"Datei das Profil wählt (D-414)")
|
|
1091
|
+
return
|
|
1092
|
+
einst_pfad = os.path.join(root, *einst_rel.split("/"))
|
|
1093
|
+
profil_pfad = os.path.join(root, *profil_rel.split("/"))
|
|
1094
|
+
if not os.path.exists(einst_pfad) and not os.path.exists(profil_pfad):
|
|
1095
|
+
return # keine Installation dieses Packs an dieser Wurzel
|
|
1096
|
+
if not os.path.isfile(einst_pfad):
|
|
1097
|
+
err(f"{einst_rel}: fehlt. Ohne diese Datei startet der Client mit seinem "
|
|
1098
|
+
f"eingebauten Agenten und seiner Standard-Engine – die Berechtigungen in "
|
|
1099
|
+
f"{profil_rel} wirken dann nicht, und nichts meldet es (D-414)")
|
|
1100
|
+
else:
|
|
1101
|
+
try:
|
|
1102
|
+
einst = json.loads(read(einst_pfad))
|
|
1103
|
+
except json.JSONDecodeError:
|
|
1104
|
+
einst = None
|
|
1105
|
+
if not isinstance(einst, dict):
|
|
1106
|
+
err(f"{einst_rel}: kein JSON-Objekt. Der Client läse daraus weder Agent noch "
|
|
1107
|
+
f"Engine (D-414)")
|
|
1108
|
+
else:
|
|
1109
|
+
for schluessel, wert in soll.items():
|
|
1110
|
+
if einst.get(schluessel) != wert:
|
|
1111
|
+
err(f"{einst_rel}: '{schluessel}' ist {einst.get(schluessel)!r}, das "
|
|
1112
|
+
f"Pack verlangt {wert!r}. Mit einem anderen Wert wählt der Client "
|
|
1113
|
+
f"einen anderen Agenten oder eine Engine, die das Profilfeld "
|
|
1114
|
+
f"permissions nicht kennt – und fällt dabei still zurück (D-414)")
|
|
1115
|
+
# (b) Das Profil.
|
|
1116
|
+
if not os.path.isfile(profil_pfad):
|
|
1117
|
+
err(f"{profil_rel}: fehlt. Die Einstellung wählt ein Profil, das es nicht gibt – "
|
|
1118
|
+
f"der Client fällt STILL auf seinen eingebauten Agenten zurück, gemessen: "
|
|
1119
|
+
f".env war danach lesbar (D-414)")
|
|
1120
|
+
return
|
|
1121
|
+
try:
|
|
1122
|
+
cfg = json.loads(read(profil_pfad))
|
|
1123
|
+
except json.JSONDecodeError as exc:
|
|
1124
|
+
err(f"{profil_rel}: kein gültiges JSON ({exc.msg}, Zeile {exc.lineno}). Der Client "
|
|
1125
|
+
f"fällt dann STILL auf seinen eingebauten Agenten zurück – sein eigenes "
|
|
1126
|
+
f"Prüfkommando 'agent validate' endet dabei mit Exit 0 (D-414)")
|
|
1127
|
+
return
|
|
1128
|
+
name = man.get("permission_profile_name")
|
|
1129
|
+
if not isinstance(cfg, dict) or cfg.get("name") != name:
|
|
1130
|
+
err(f"{profil_rel}: das Profil heißt {cfg.get('name') if isinstance(cfg, dict) else '?'!r}, "
|
|
1131
|
+
f"die Einstellung wählt {name!r}. Ein Name, den kein Profil trägt, ist der "
|
|
1132
|
+
f"stille Rückfall aus D-414")
|
|
1133
|
+
return
|
|
1134
|
+
# (c) Jede Regel ist eine, die der Client ausführt.
|
|
1135
|
+
regeln = _p96_regeln(cfg)
|
|
1136
|
+
if not regeln:
|
|
1137
|
+
err(f"{profil_rel}: keine einzige Regel unter permissions.rules – das Profil wäre "
|
|
1138
|
+
f"aktiv und sperrte nichts (D-414)")
|
|
1139
|
+
return
|
|
1140
|
+
ist: dict = {e: {} for e in P96_EFFEKTE}
|
|
1141
|
+
for nr, regel in enumerate(regeln, 1):
|
|
1142
|
+
if not isinstance(regel, dict):
|
|
1143
|
+
err(f"{profil_rel}: Regel {nr} ist kein Objekt (D-414)")
|
|
1144
|
+
continue
|
|
1145
|
+
faehigkeit, effekt = regel.get("capability"), regel.get("effect")
|
|
1146
|
+
muster = regel.get("match")
|
|
1147
|
+
if faehigkeit not in clientmap.KIRO_FAEHIGKEITEN:
|
|
1148
|
+
err(f"{profil_rel}: Regel {nr} nennt die Fähigkeit {faehigkeit!r}, die der "
|
|
1149
|
+
f"Client nicht kennt. Er überspringt die Regel und meldet es nur in seinem "
|
|
1150
|
+
f"Protokoll – gemessen (D-414)")
|
|
1151
|
+
continue
|
|
1152
|
+
if effekt not in P96_EFFEKTE:
|
|
1153
|
+
err(f"{profil_rel}: Regel {nr} hat den Effekt {effekt!r}; bekannt sind "
|
|
1154
|
+
f"{', '.join(P96_EFFEKTE)} (D-414)")
|
|
1155
|
+
continue
|
|
1156
|
+
if not isinstance(muster, list) or not all(isinstance(m, str) for m in muster):
|
|
1157
|
+
err(f"{profil_rel}: Regel {nr} führt 'match' nicht als Liste von Mustern (D-414)")
|
|
1158
|
+
continue
|
|
1159
|
+
ausnahme = tuple(regel.get("exclude") or ())
|
|
1160
|
+
for m in muster:
|
|
1161
|
+
ist[effekt].setdefault((faehigkeit, m), set()).add(ausnahme)
|
|
1162
|
+
# (d) Gegen die Kernquelle.
|
|
1163
|
+
try:
|
|
1164
|
+
quelle = json.loads(clientmap.load_source(kern, "permissions.json"))
|
|
1165
|
+
erzeugt = clientmap.kiro_regeln(quelle, man)
|
|
1166
|
+
except (OSError, ValueError) as exc:
|
|
1167
|
+
err(f"Kernquelle der Berechtigungen nicht auswertbar: {exc}")
|
|
1168
|
+
return
|
|
1169
|
+
soll_regeln: dict = {e: {} for e in P96_EFFEKTE}
|
|
1170
|
+
for regel in erzeugt:
|
|
1171
|
+
for m in regel["match"]:
|
|
1172
|
+
soll_regeln[regel["effect"]].setdefault((regel["capability"], m), set()).add(
|
|
1173
|
+
tuple(regel.get("exclude") or ()))
|
|
1174
|
+
for effekt in ("deny", "ask"):
|
|
1175
|
+
for (faehigkeit, m), ausnahmen in soll_regeln[effekt].items():
|
|
1176
|
+
if m.startswith("<"):
|
|
1177
|
+
continue # Projektschlitz - gehört dem Overlay
|
|
1178
|
+
vorhanden = ist[effekt].get((faehigkeit, m))
|
|
1179
|
+
if not vorhanden:
|
|
1180
|
+
err(f"{profil_rel}: die Kernquelle erzeugt '{effekt} {faehigkeit} {m}'; im "
|
|
1181
|
+
f"Profil steht es nicht. Eine fehlende Regel ist eine Lockerung (D-77, "
|
|
1182
|
+
f"D-414)")
|
|
1183
|
+
elif not ausnahmen <= vorhanden:
|
|
1184
|
+
err(f"{profil_rel}: '{effekt} {faehigkeit} {m}' steht im Profil mit einer "
|
|
1185
|
+
f"anderen Ausnahme als in der Abbildung {sorted(ausnahmen)} – eine "
|
|
1186
|
+
f"breitere Ausnahme ist eine Lockerung (D-415)")
|
|
1187
|
+
skills = {f"skill {n}" for n in installierte_skills(root, man)}
|
|
1188
|
+
for (faehigkeit, m) in ist["allow"]:
|
|
1189
|
+
if (faehigkeit, m) in soll_regeln["allow"] or m.startswith("<"):
|
|
1190
|
+
continue
|
|
1191
|
+
if faehigkeit == "skill" and f"skill {m}" in skills:
|
|
1192
|
+
continue # aktivierter Pack-Skill, Bauform von Prüfung 72 (D-238)
|
|
1193
|
+
err(f"{profil_rel}: 'allow {faehigkeit} {m}' erzeugt die Kernquelle nicht. Eine "
|
|
1194
|
+
f"zusätzliche Freigabe ist eine Ausweitung und nicht Sache des Profils (D-77)")
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
# ---------------------------------------------------------------------------
|
|
1198
|
+
# PRUEFUNGEN 91 BIS 94: DIE DOKUMENTATION (CR-2026-142, D-371 bis D-375)
|
|
1199
|
+
|
|
1200
|
+
# ---------------------------------------------------------------------------
|
|
1201
|
+
# PRUEFUNG 97: DIE BERECHTIGUNGSDATEI, MIT DER DER CLIENT STARTET UND DIE TRIFFT
|
|
1202
|
+
# ---------------------------------------------------------------------------
|
|
1203
|
+
# ANLASS, UND ER IST GEMESSEN (CR-2026-155, D-440). Am 2026-09-26 an cursor-agent
|
|
1204
|
+
# 2026.09.26 unter Windows:
|
|
1205
|
+
# * Mit den Schluesseln _comment und _core_rules_integrity in .cursor/cli.json brach
|
|
1206
|
+
# der Client mit Exit 1 ab ("Unrecognized key(s) in object") - ebenso bei kaputtem
|
|
1207
|
+
# JSON. Die Kernregeln koennen deshalb nicht in der Datei selbst stehen wie bei der
|
|
1208
|
+
# ersten Ausgabeform; diese Pruefung haelt sie gegen die Kernquelle.
|
|
1209
|
+
# * Ein Pfadmuster vergleicht der Client verankert mit dem ABSOLUTEN Pfad. 'Read(.env)'
|
|
1210
|
+
# und 'Read(**/.env)' liessen den Koeder durch, 'Read(*\.env)' nicht. Ein Verbot,
|
|
1211
|
+
# das so nie trifft, sieht aus wie eines.
|
|
1212
|
+
# * Die Pruefungen der Koerbe (37 und Verwandte) erreichen diese Form nicht (D-416).
|
|
1213
|
+
# GEPRUEFT: (a) gueltiges JSON, ein Objekt mit genau dem Schluessel permissions, darin nur
|
|
1214
|
+
# allow und deny als Listen von Zeichenketten; (b) jeder Eintrag hat die Gestalt
|
|
1215
|
+
# Typ(Argument) mit einem Regeltyp des Clients; (c) jedes deny der Kernquelle steht darin,
|
|
1216
|
+
# eine Kernzusage mit eigenem Wortlaut; kein allow, das die Kernquelle nicht erzeugt;
|
|
1217
|
+
# (d) Warnung fuer ein Pfadverbot ohne fuehrendes '*' und ohne absoluten Pfad.
|
|
1218
|
+
# GRENZE, BENANNT: Gemessen ist Windows. Die Schreibweise mit '/' fuer POSIX folgt aus dem
|
|
1219
|
+
# Programmcode des Clients, nicht aus einer Messung auf macOS oder Linux.
|
|
1220
|
+
P97_TYPEN = ("Shell", "Bash", "Read", "Write", "WebFetch", "Mcp")
|
|
1221
|
+
P97_EINTRAG_RE = re.compile(r"^\s*([A-Za-z]+)\s*\((.*)\)\s*$")
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def check_cursor_berechtigungen(root: str, man: dict) -> None:
|
|
1225
|
+
"""Pruefung 97 (D-440): Die Datei laesst den Client starten, und ihre Verbote treffen."""
|
|
1226
|
+
if man.get("permissions_format") != "cursor-json":
|
|
1227
|
+
return
|
|
1228
|
+
rel = man.get("permissions_file") or ""
|
|
1229
|
+
pfad = os.path.join(root, *rel.split("/"))
|
|
1230
|
+
if not os.path.isfile(pfad):
|
|
1231
|
+
return # keine Installation dieses Packs an dieser Wurzel; Pflichtpfade melden es
|
|
1232
|
+
kern = os.path.join(root, KERN)
|
|
1233
|
+
if kern not in sys.path:
|
|
1234
|
+
sys.path.insert(0, kern)
|
|
1235
|
+
try:
|
|
1236
|
+
import clientmap
|
|
1237
|
+
except ImportError:
|
|
1238
|
+
return # check_config meldet das fehlende Abbildungsmodul bereits
|
|
1239
|
+
if not hasattr(clientmap, "cursor_koerbe") or not hasattr(clientmap, "cursor_kernregeln"):
|
|
1240
|
+
err(f"{KERN}/clientmap.py: 'cursor_koerbe' oder 'cursor_kernregeln' fehlt – Prüfung "
|
|
1241
|
+
f"97 hat ihren Gegenstand verloren und bestünde sonst leise (D-23)")
|
|
1242
|
+
return
|
|
1243
|
+
# (a) Die Gestalt, mit der der Client startet.
|
|
1244
|
+
try:
|
|
1245
|
+
cfg = json.loads(read(pfad))
|
|
1246
|
+
except json.JSONDecodeError as exc:
|
|
1247
|
+
err(f"{rel}: kein gültiges JSON ({exc.msg}, Zeile {exc.lineno}). Der Client startet "
|
|
1248
|
+
f"damit nicht – gemessen: Exit 1 (D-440)")
|
|
1249
|
+
return
|
|
1250
|
+
if not isinstance(cfg, dict) or set(cfg) != {"permissions"}:
|
|
1251
|
+
fremd = sorted(set(cfg) - {"permissions"}) if isinstance(cfg, dict) else ["?"]
|
|
1252
|
+
err(f"{rel}: die Datei trägt {', '.join(fremd) or 'keinen Schlüssel permissions'}. "
|
|
1253
|
+
f"Der Client nimmt auf Projektebene nur 'permissions' an und startet mit einem "
|
|
1254
|
+
f"weiteren Schlüssel nicht – gemessen mit _comment und _core_rules_integrity: "
|
|
1255
|
+
f"Exit 1, 'Unrecognized key(s)' (D-440)")
|
|
1256
|
+
return
|
|
1257
|
+
rechte = cfg["permissions"]
|
|
1258
|
+
if not isinstance(rechte, dict) or not set(rechte) <= {"allow", "deny"}:
|
|
1259
|
+
err(f"{rel}: 'permissions' darf nur allow und deny führen – dieser Client kennt "
|
|
1260
|
+
f"keinen Rückfragekorb (D-440)")
|
|
1261
|
+
return
|
|
1262
|
+
ist: dict = {}
|
|
1263
|
+
for korb in ("allow", "deny"):
|
|
1264
|
+
liste = rechte.get(korb, [])
|
|
1265
|
+
if not isinstance(liste, list) or not all(isinstance(e, str) for e in liste):
|
|
1266
|
+
err(f"{rel}: permissions.{korb} ist keine Liste von Zeichenketten (D-440)")
|
|
1267
|
+
return
|
|
1268
|
+
ist[korb] = set(liste)
|
|
1269
|
+
# (b) Jeder Eintrag ist eine Regel, die der Client kennt.
|
|
1270
|
+
for eintrag in liste:
|
|
1271
|
+
m = P97_EINTRAG_RE.match(eintrag)
|
|
1272
|
+
if not m or m.group(1) not in P97_TYPEN:
|
|
1273
|
+
err(f"{rel}: '{eintrag}' in permissions.{korb} ist keine Regel der Gestalt "
|
|
1274
|
+
f"Typ(Argument) mit einem Typ dieses Clients ({', '.join(P97_TYPEN)}). "
|
|
1275
|
+
f"Eine solche Zeile wertet der Client nicht aus (D-440)")
|
|
1276
|
+
continue
|
|
1277
|
+
# (d) Ein Pfadverbot, das nie trifft.
|
|
1278
|
+
argument = m.group(2).strip()
|
|
1279
|
+
if korb == "deny" and m.group(1) in ("Read", "Write") \
|
|
1280
|
+
and not argument.startswith(("*", "<", "/", "~")) \
|
|
1281
|
+
and not re.match(r"^[A-Za-z]:[\\/]", argument):
|
|
1282
|
+
warn(f"{rel}: '{eintrag}' trifft bei diesem Client nie. Er vergleicht das "
|
|
1283
|
+
f"Muster mit dem ABSOLUTEN Pfad – gemessen: 'Read(.env)' und "
|
|
1284
|
+
f"'Read(**/.env)' ließen den Köder durch. Die Schreibweise mit "
|
|
1285
|
+
f"führendem '*' in beiden Trennern: 'Read(*/<pfad>)' und "
|
|
1286
|
+
f"'Read(*\\<pfad>)' (D-440)")
|
|
1287
|
+
# (c) Gegen die Kernquelle.
|
|
1288
|
+
try:
|
|
1289
|
+
quelle = json.loads(clientmap.load_source(kern, "permissions.json"))
|
|
1290
|
+
soll = clientmap.cursor_koerbe(quelle, man)
|
|
1291
|
+
kernregeln = set(clientmap.cursor_kernregeln(quelle, man))
|
|
1292
|
+
except (OSError, ValueError) as exc:
|
|
1293
|
+
err(f"Kernquelle der Berechtigungen nicht auswertbar: {exc}")
|
|
1294
|
+
return
|
|
1295
|
+
for eintrag in soll["deny"]:
|
|
1296
|
+
if "<" in eintrag or eintrag in ist["deny"]:
|
|
1297
|
+
continue # Projektschlitz gehört dem Overlay
|
|
1298
|
+
if eintrag in kernregeln:
|
|
1299
|
+
err(f"{rel}: die Kernzusage '{eintrag}' fehlt in permissions.deny. Kernregeln "
|
|
1300
|
+
f"darf das Projekt nicht entfernen; diese Datei kann sie nicht selbst "
|
|
1301
|
+
f"auflisten, deshalb hält diese Prüfung sie gegen die Kernquelle (D-440)")
|
|
1302
|
+
else:
|
|
1303
|
+
err(f"{rel}: '{eintrag}' aus der Kernquelle fehlt in permissions.deny. Eine "
|
|
1304
|
+
f"Regel des Kerns zu streichen ist eine Lockerung (D-77, D-440)")
|
|
1305
|
+
for eintrag in sorted(ist["allow"] - set(soll["allow"])):
|
|
1306
|
+
err(f"{rel}: '{eintrag}' in permissions.allow erzeugt die Kernquelle nicht. Eine "
|
|
1307
|
+
f"zusätzliche Freigabe ist eine Ausweitung; ein freigegebener Befehl gehört in "
|
|
1308
|
+
f"Abschnitt 6 des Overlays und damit in die Regelschicht (D-77, D-440)")
|
|
1309
|
+
# (e) Die Ausschlussdatei (D-443): die zweite Lesesperre, und die einzige, die das
|
|
1310
|
+
# Suchwerkzeug beachtet - gemessen am 2026-09-26.
|
|
1311
|
+
ign_rel = man.get("ignore_file")
|
|
1312
|
+
if not ign_rel or not hasattr(clientmap, "cursor_ignore_muster"):
|
|
1313
|
+
return
|
|
1314
|
+
ign_pfad = os.path.join(root, *ign_rel.split("/"))
|
|
1315
|
+
if not os.path.isfile(ign_pfad):
|
|
1316
|
+
err(f"{ign_rel}: fehlt. Ohne sie wertet das Suchwerkzeug dieses Clients kein "
|
|
1317
|
+
f"Leseverbot aus – gemessen: der Köder aus secrets/ kam heraus (D-443)")
|
|
1318
|
+
return
|
|
1319
|
+
zeilen = {z.strip() for z in read(ign_pfad).splitlines()}
|
|
1320
|
+
for muster in clientmap.cursor_ignore_muster(quelle, man):
|
|
1321
|
+
if muster not in zeilen:
|
|
1322
|
+
err(f"{ign_rel}: das Leseverbot '{muster}' der Kernquelle fehlt. Das "
|
|
1323
|
+
f"Suchwerkzeug fände dort wieder, was die Berechtigungsdatei nur dem "
|
|
1324
|
+
f"Lesewerkzeug verbietet (D-443)")
|
|
1325
|
+
P99_ANKER = ("MANDAT_DATEI = ", "MANDATS_MUSTER = ", "def mandat_lesen(")
|
|
1326
|
+
P99_WERTE_RE = (re.compile(r'^MANDAT_DATEI = "([^"]+)"', re.M),
|
|
1327
|
+
re.compile(r"^MANDAT_HOECHSTDAUER_MIN = (\d+)", re.M),
|
|
1328
|
+
re.compile(r"^MANDAT_UMFAENGE = \{(.*?)\n\}", re.M | re.S),
|
|
1329
|
+
# Die Modusbindung liegt neben dem Mandat (D-501, K-179)
|
|
1330
|
+
re.compile(r'^MODUS_DATEI = "([^"]+)"', re.M),
|
|
1331
|
+
re.compile(r"^MODUS_GEBUNDEN = (\(.*?\))", re.M))
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
def _p99_werte(text: str) -> tuple:
|
|
1335
|
+
return tuple((r.search(text).group(1).strip() if r.search(text) else None)
|
|
1336
|
+
for r in P99_WERTE_RE)
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
# Seit 1.23.0 (CR-2026-169, K-201): M3 bis M5 binden Globs aus dem Overlay. mandat.py
|
|
1340
|
+
# zeigt sie an, der Hook wertet sie aus - mit je einer Funktion 'glob_muster'. Zwei
|
|
1341
|
+
# Lesarten desselben Globs hiessen: Der Mensch sieht eine Grenze, der Hook zieht eine
|
|
1342
|
+
# andere. Verglichen wird der Rumpf ohne Docstring, als Syntaxbaum.
|
|
1343
|
+
def _p99_glob_rumpf(text: str):
|
|
1344
|
+
try:
|
|
1345
|
+
baum = ast.parse(text)
|
|
1346
|
+
except SyntaxError:
|
|
1347
|
+
return None
|
|
1348
|
+
for knoten in baum.body:
|
|
1349
|
+
if isinstance(knoten, ast.FunctionDef) and knoten.name == "glob_muster":
|
|
1350
|
+
rumpf = list(knoten.body)
|
|
1351
|
+
if (rumpf and isinstance(rumpf[0], ast.Expr)
|
|
1352
|
+
and isinstance(getattr(rumpf[0], "value", None), ast.Constant)
|
|
1353
|
+
and isinstance(rumpf[0].value.value, str)):
|
|
1354
|
+
rumpf = rumpf[1:]
|
|
1355
|
+
return "".join(ast.dump(k) for k in rumpf)
|
|
1356
|
+
return None
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
def check_mandatsschutz(root: str, man: dict) -> None:
|
|
1360
|
+
"""Pruefung 99 (D-447, D-448): Das Mandat erteilt nur der Mensch, und es wirkt."""
|
|
1361
|
+
skript = os.path.join(root, KERN, "tests", "scripts", "hook-check-secrets.py")
|
|
1362
|
+
mandat = os.path.join(root, KERN, "mandat.py")
|
|
1363
|
+
if not os.path.isfile(skript):
|
|
1364
|
+
return
|
|
1365
|
+
hook = read(skript)
|
|
1366
|
+
fehlend = [a for a in P99_ANKER if a not in hook]
|
|
1367
|
+
if fehlend:
|
|
1368
|
+
err(f"{KERN}/tests/scripts/hook-check-secrets.py: {', '.join(fehlend)} fehlt. Pruefung "
|
|
1369
|
+
f"99 misst den Schutz des Mandats; ohne ihn bestuende sie leise (D-23, D-447)")
|
|
1370
|
+
return
|
|
1371
|
+
if not os.path.isfile(mandat):
|
|
1372
|
+
err(f"{KERN}/mandat.py fehlt. Der Hook kennt ein Mandat, das niemand erteilen kann - "
|
|
1373
|
+
f"Modus M6 waere eine Zusage ohne Werkzeug (D-447)")
|
|
1374
|
+
return
|
|
1375
|
+
if _p99_werte(hook) != _p99_werte(read(mandat)):
|
|
1376
|
+
err(f"{KERN}/mandat.py und hook-check-secrets.py fuehren verschiedene Werte fuer "
|
|
1377
|
+
f"Dateiname, Hoechstdauer oder Umfaenge des Mandats: Hook {_p99_werte(hook)}, "
|
|
1378
|
+
f"mandat.py {_p99_werte(read(mandat))}. Ein Mandat, das das Werkzeug schreibt und "
|
|
1379
|
+
f"der Hook nicht liest, gibt nichts frei (D-447)")
|
|
1380
|
+
glob_hook, glob_mandat = _p99_glob_rumpf(hook), _p99_glob_rumpf(read(mandat))
|
|
1381
|
+
if glob_hook is None or glob_hook != glob_mandat:
|
|
1382
|
+
err(f"{KERN}/mandat.py und hook-check-secrets.py werten die Globs der Modusbindung "
|
|
1383
|
+
f"nicht gleich aus: Die Funktion glob_muster fehlt in einer der beiden Dateien "
|
|
1384
|
+
f"oder ihr Rumpf weicht ab. Der Mensch saehe beim Binden eine andere Grenze, als "
|
|
1385
|
+
f"der Hook zieht (K-201)")
|
|
1386
|
+
quelle = os.path.join(root, KERN, "framework", "runtime", "permissions.json")
|
|
1387
|
+
if os.path.isfile(quelle):
|
|
1388
|
+
try:
|
|
1389
|
+
regeln = json.loads(read(quelle)).get("deny", [])
|
|
1390
|
+
except (json.JSONDecodeError, AttributeError):
|
|
1391
|
+
regeln = []
|
|
1392
|
+
if any(isinstance(r, dict) and r.get("tool") == "write"
|
|
1393
|
+
and "project-overlay" in str(r.get("pattern", "")) for r in regeln):
|
|
1394
|
+
err(f"{KERN}/framework/runtime/permissions.json: sperrt das Overlay im deny-Korb. "
|
|
1395
|
+
f"Eine statische Sperre hebt kein Mandat auf; das Overlay sperrt der "
|
|
1396
|
+
f"Schutz-Hook (D-448)")
|
|
1397
|
+
interpreter = _hook_interpreter()
|
|
1398
|
+
if interpreter is None:
|
|
1399
|
+
return
|
|
1400
|
+
faelle = [
|
|
1401
|
+
("Aufruf von mandat.py erteilen",
|
|
1402
|
+
{"tool_name": "exec", "tool_input": {"command":
|
|
1403
|
+
f"python {KERN}/mandat.py erteilen --rolle P99 --umfang overlay --minuten 5"}}, 2),
|
|
1404
|
+
("Schreiben der Mandatsdatei",
|
|
1405
|
+
{"tool_name": "write", "tool_input": {"file_path": ".git/koolie-mandat.json",
|
|
1406
|
+
"content": "{}"}}, 2),
|
|
1407
|
+
("die Auskunft mandat.py status",
|
|
1408
|
+
{"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py status"}}, 0),
|
|
1409
|
+
# claude-code schickt eine Beschreibung mit - sie darf die Auskunft nicht sperren
|
|
1410
|
+
# (gemessen am 2026-09-27, Lauf sk013n01)
|
|
1411
|
+
("die Auskunft mit einer Beschreibung daneben",
|
|
1412
|
+
{"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py status",
|
|
1413
|
+
"description": "Mandatsstatus abfragen"}}, 0),
|
|
1414
|
+
("die Auskunft mit einem zweiten Befehl dahinter",
|
|
1415
|
+
{"tool_name": "exec", "tool_input": {"command":
|
|
1416
|
+
f"python {KERN}/mandat.py status; python {KERN}/mandat.py erteilen --rolle P99"}}, 2),
|
|
1417
|
+
# Die Modusbindung setzt wie das Mandat nur der Mensch (D-501, K-179)
|
|
1418
|
+
("Aufruf von mandat.py modus",
|
|
1419
|
+
{"tool_name": "exec", "tool_input": {"command": f"python {KERN}/mandat.py modus aus"}},
|
|
1420
|
+
2),
|
|
1421
|
+
("Schreiben der Modusdatei",
|
|
1422
|
+
{"tool_name": "write", "tool_input": {"file_path": ".git/koolie-modus.json",
|
|
1423
|
+
"content": "{}"}}, 2),
|
|
1424
|
+
]
|
|
1425
|
+
# Das Overlay ohne Mandat - nur, wenn im gepruefen Baum gerade keines erteilt ist: Ein
|
|
1426
|
+
# erteiltes Mandat ist ein gueltiger Zustand, kein Befund.
|
|
1427
|
+
if not os.path.exists(os.path.join(root, ".git", "koolie-mandat.json")):
|
|
1428
|
+
faelle.append(("Schreiben in das Overlay ohne Mandat",
|
|
1429
|
+
{"tool_name": "write", "tool_input": {
|
|
1430
|
+
"file_path": ".koolie/project-overlay/OVERLAY.md", "content": "x"}}, 2))
|
|
1431
|
+
for was, ereignis, erwartet in faelle:
|
|
1432
|
+
ergebnis = _hook_lauf(interpreter, skript, json.dumps(ereignis))
|
|
1433
|
+
if ergebnis != erwartet:
|
|
1434
|
+
err(f"{KERN}/tests/scripts/hook-check-secrets.py: {was} endet mit Exit {ergebnis}, "
|
|
1435
|
+
f"erwartet {erwartet}. Das Mandat erteilt nur der Mensch im eigenen Terminal; "
|
|
1436
|
+
f"der Client darf es weder anlegen noch aufrufen, nur nachsehen (D-447)")
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def check_modellaufruf_nur_lesend(root: str, man: dict) -> None:
|
|
1440
|
+
"""Pruefung 100 (D-451): Ein Skill mit Trigger 'model' ist rein lesend."""
|
|
1441
|
+
if yaml is None:
|
|
1442
|
+
return # ohne PyYAML kein Frontmatter - die Warnung dazu gibt main()
|
|
1443
|
+
for skills_dir, prefix in skill_dirs(root, man):
|
|
1444
|
+
if not prefix.startswith(KERN + "/"):
|
|
1445
|
+
continue # die installierte Fassung traegt das Feld nicht in jedem Pack
|
|
1446
|
+
for name in sorted(os.listdir(skills_dir)):
|
|
1447
|
+
pfad = os.path.join(skills_dir, name, "SKILL.md")
|
|
1448
|
+
if not os.path.isfile(pfad):
|
|
1449
|
+
continue
|
|
1450
|
+
fm, _ = parse_frontmatter(read(pfad))
|
|
1451
|
+
if not isinstance(fm, dict) or "model" not in (fm.get("triggers") or []):
|
|
1452
|
+
continue
|
|
1453
|
+
deny = [str(d).strip().lower()
|
|
1454
|
+
for d in ((fm.get("permissions") or {}).get("deny") or [])]
|
|
1455
|
+
fehlt = [v for v in ("edit", "exec") if v not in deny]
|
|
1456
|
+
if fehlt:
|
|
1457
|
+
err(f"{prefix}/{name}/SKILL.md: traegt den Trigger 'model', sperrt aber "
|
|
1458
|
+
f"{', '.join(fehlt)} nicht in permissions.deny. Den Modellaufruf erlaubt "
|
|
1459
|
+
f"08-skill-conventions.md nur einem rein lesenden Skill (D-451)")
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
# --- Pruefung 101: die MCP-Freigaben (CR-2026-157, D-459) ------------------------------
|
|
1463
|
+
P101_ABSCHNITT = "### 13.2 MCP-Server"
|
|
1464
|
+
P101_NAME_RE = re.compile(r"`([A-Za-z0-9_.-]+)`")
|
|
1465
|
+
P101_KERNQUELLE = "framework/runtime/permissions.json"
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
def _p101_freigaben(text: str) -> list:
|
|
1469
|
+
"""Die Zeilen der Tabelle in Overlay Abschnitt 13.2 - Server, Zweck, Werkzeuge."""
|
|
1470
|
+
if P101_ABSCHNITT not in text:
|
|
1471
|
+
return []
|
|
1472
|
+
teil = text.replace("\r\n", "\n").split(P101_ABSCHNITT, 1)[1]
|
|
1473
|
+
teil = re.split(r"\n#{1,3} ", teil, maxsplit=1)[0]
|
|
1474
|
+
zeilen = []
|
|
1475
|
+
for z in teil.split("\n"):
|
|
1476
|
+
if not z.strip().startswith("|"):
|
|
1477
|
+
continue
|
|
1478
|
+
zellen = tabellenzellen(z)
|
|
1479
|
+
if len(zellen) < 5 or zellen[0].startswith("Server") or not zellen[0].strip("-: "):
|
|
1480
|
+
continue
|
|
1481
|
+
server = zellen[0].strip().strip("`").strip()
|
|
1482
|
+
if not server or TBD_RE.search(zellen[0]) or server in ("–", "-", "keine"):
|
|
1483
|
+
continue
|
|
1484
|
+
zeilen.append({"server": server, "zweck": zellen[2].lower(),
|
|
1485
|
+
"lesen": P101_NAME_RE.findall(zellen[3]),
|
|
1486
|
+
"schreiben": P101_NAME_RE.findall(zellen[4])})
|
|
1487
|
+
return zeilen
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
def check_mcp_freigaben(root: str, man: dict, strikt: bool) -> None:
|
|
1491
|
+
"""Pruefung 101 (D-459): MCP-Werkzeuge stehen einzeln und nur lesend in allow."""
|
|
1492
|
+
kq = os.path.join(root, KERN, *P101_KERNQUELLE.split("/"))
|
|
1493
|
+
if os.path.isfile(kq):
|
|
1494
|
+
try:
|
|
1495
|
+
quelle = json.loads(read(kq))
|
|
1496
|
+
except json.JSONDecodeError:
|
|
1497
|
+
quelle = {}
|
|
1498
|
+
for regel in quelle.get("allow") or []:
|
|
1499
|
+
if isinstance(regel, dict) and regel.get("tool") == "mcp":
|
|
1500
|
+
err(f"{KERN}/{P101_KERNQUELLE}: gibt ein MCP-Werkzeug im allow-Korb frei "
|
|
1501
|
+
f"({json.dumps(regel, ensure_ascii=False)}). Eine MCP-Freigabe gehört dem "
|
|
1502
|
+
f"Projekt: Sie nennt Server, Zweck und Werkzeuge im Overlay Abschnitt 13.2, "
|
|
1503
|
+
f"und der Kern gibt nichts vorab frei (02-privacy.md 3.8, D-459)")
|
|
1504
|
+
|
|
1505
|
+
regelform = (man or {}).get("mcp_permission_rule")
|
|
1506
|
+
overlay = os.path.join(root, ".koolie", "project-overlay", "OVERLAY.md")
|
|
1507
|
+
rel = (man or {}).get("permissions_file")
|
|
1508
|
+
if not isinstance(regelform, dict) or not rel or not os.path.isfile(overlay):
|
|
1509
|
+
return
|
|
1510
|
+
pfad = os.path.join(root, *rel.split("/"))
|
|
1511
|
+
if not os.path.isfile(pfad):
|
|
1512
|
+
return
|
|
1513
|
+
try:
|
|
1514
|
+
cfg = json.loads(read(pfad))
|
|
1515
|
+
except json.JSONDecodeError:
|
|
1516
|
+
return # Pruefung 3 meldet das bereits
|
|
1517
|
+
rechte = cfg.get("permissions") if isinstance(cfg, dict) else None
|
|
1518
|
+
if not isinstance(rechte, dict):
|
|
1519
|
+
return
|
|
1520
|
+
allow = [r for r in rechte.get("allow") or [] if isinstance(r, str)]
|
|
1521
|
+
ask = [r for r in rechte.get("ask") or [] if isinstance(r, str)]
|
|
1522
|
+
form = str(regelform.get("werkzeug", ""))
|
|
1523
|
+
praefix = form.split("{server}", 1)[0]
|
|
1524
|
+
if not praefix:
|
|
1525
|
+
return
|
|
1526
|
+
freigaben = _p101_freigaben(read(overlay))
|
|
1527
|
+
lesen = {form.format(server=f["server"], werkzeug=w)
|
|
1528
|
+
for f in freigaben if "lesen" in f["zweck"] for w in f["lesen"]}
|
|
1529
|
+
schreiben = {form.format(server=f["server"], werkzeug=w)
|
|
1530
|
+
for f in freigaben for w in f["schreiben"]}
|
|
1531
|
+
# Der Serveranteil der Regelform ('mcp__atlassian', 'Mcp(atlassian') - eine Regel, die nur
|
|
1532
|
+
# ihn nennt, gilt fuer jedes Werkzeug des Servers.
|
|
1533
|
+
serverteil = form.split("{werkzeug}", 1)[0]
|
|
1534
|
+
ganze_server = {serverteil.format(server=f["server"]).rstrip("_:(") for f in freigaben}
|
|
1535
|
+
for regel in allow:
|
|
1536
|
+
if not regel.startswith(praefix) or regel in lesen:
|
|
1537
|
+
continue
|
|
1538
|
+
if regel in schreiben:
|
|
1539
|
+
grund = "ein Schreibwerkzeug – es verlangt bei jedem Aufruf die Bestätigung des Menschen"
|
|
1540
|
+
elif "*" in regel or regel.rstrip("_:()") in ganze_server:
|
|
1541
|
+
grund = "ein Muster für mehrere Werkzeuge – es gäbe auch die Schreibwerkzeuge frei"
|
|
1542
|
+
else:
|
|
1543
|
+
grund = "ein Werkzeug, das Overlay Abschnitt 13.2 keinem Server zum Lesen zuweist"
|
|
1544
|
+
err(f"{rel}: „{regel}“ steht in allow – {grund}. In allow stehen nur die "
|
|
1545
|
+
f"Lesewerkzeuge der Freigabe, einzeln; Schreibwerkzeuge gehören einzeln in ask "
|
|
1546
|
+
f"(02-privacy.md 3.8, D-459)")
|
|
1547
|
+
if not strikt:
|
|
1548
|
+
return
|
|
1549
|
+
for regel in sorted(lesen - set(allow)):
|
|
1550
|
+
err(f"{rel}: das freigegebene Lesewerkzeug „{regel}“ fehlt in allow. Overlay "
|
|
1551
|
+
f"Abschnitt 13.2 gibt es zum Lesen frei; ohne die Regel fragt der Client bei "
|
|
1552
|
+
f"jedem Aufruf nach, und ein nicht-interaktiver Lauf wird abgewiesen (D-459)")
|
|
1553
|
+
pauschal = regelform.get("pauschal")
|
|
1554
|
+
if lesen and pauschal in ask and regelform.get("rueckfrage_schlaegt_freigabe"):
|
|
1555
|
+
err(f"{rel}: „{pauschal}“ steht im ask-Korb, während Overlay Abschnitt 13.2 Werkzeuge "
|
|
1556
|
+
f"zum Lesen freigibt. Bei diesem Client schlägt die Rückfrage die Freigabe – "
|
|
1557
|
+
f"auch das einzeln freigegebene Lesewerkzeug wird abgewiesen (gemessen, D-459). "
|
|
1558
|
+
f"Die Pauschale durch die Einzelregeln ersetzen: Lesewerkzeuge in allow, "
|
|
1559
|
+
f"Schreibwerkzeuge in ask")
|