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