diff --git a/CHANGELOG.md b/CHANGELOG.md index a86b6ab..fa0fa29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Format: [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), [Semantic Vers ## [Unreleased] +## [0.2.0] - 2026-10-11 + +### Hinzugefügt / Added + +- Befehl `system-agent`: versionierte, gehashte Systemagent-Vorlage (`githubbot-core.system-agent/v1`) + mit Agent-Studio-Blueprint-Feldern, nur bei installiertem Paket vorhanden / + `system-agent` command: versioned, hashed system agent template with agent-studio blueprint fields, + present only where the package is installed. + ## [0.1.0] - 2026-10-11 ### Hinzugefügt / Added diff --git a/README.md b/README.md index 599c8f3..04ce54e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ pull-request summaries, read-only health/traffic/star reports, a preview/apply c write, a plugin SPI and signed exact-SHA review receipts. Pure Python standard library, no runtime dependencies. -> Status: alpha (0.1.0). The CLI, the JSON result envelope and SPI version 1 are the contract; +> Status: alpha (0.2.0). The CLI, the JSON result envelope and SPI version 1 are the contract; > everything else may still change. ## Features @@ -62,8 +62,9 @@ githubbot-core duplicates --input issues.json --threshold 0.8 # Pull-request summary (+ checks from installed plugins) githubbot-core review-pr --repo owner/repo --pr 12 -# Plugins +# Plugins and system agent template githubbot-core plugins +githubbot-core --json system-agent githubbot-core run [--plugin-arg ...] [--apply] ``` @@ -85,6 +86,15 @@ The Python API is the package itself: `githubbot_core.triage.IssueTriage`, `githubbot_core.actions.preview/apply`, `githubbot_core.health.RepoHealthCollector`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +### System agent template + +`githubbot-core --json system-agent` prints the neutral agent template shipped with the package +(schema `githubbot-core.system-agent/v1`): `version` (package version), `sha256` of the canonical +template, `capability`, the effective `permissions` and a `blueprint` with the field names of the +agent-studio blueprint payload (persona, role, contractus, governance, skills). The template +exists only where the package is installed. Consumers bind an agent to `version` + `sha256` and must +not overwrite an existing agent configuration when a newer template appears. + ## Configuration and permissions The packaged defaults (`githubbot_core/default_config.json`) are extended with `--config file.json` diff --git a/README_de.md b/README_de.md index ab7f822..2edb27f 100644 --- a/README_de.md +++ b/README_de.md @@ -7,7 +7,7 @@ Duplikaterkennung, Pull-Request-Zusammenfassungen, lesende Berichte zu Gesundhei Sternen, ein Vorschau-/Ausführungsvertrag für jede Schreibaktion, eine Plugin-Schnittstelle (SPI) und signierte Exact-SHA-Review-Belege. Reine Python-Standardbibliothek, keine Laufzeitabhängigkeiten. -> Status: Alpha (0.1.0). Verbindlich sind CLI, JSON-Ergebnishülle und SPI-Version 1; alles andere +> Status: Alpha (0.2.0). Verbindlich sind CLI, JSON-Ergebnishülle und SPI-Version 1; alles andere > kann sich noch ändern. ## Funktionen @@ -62,8 +62,9 @@ githubbot-core duplicates --input issues.json --threshold 0.8 # Pull-Request-Zusammenfassung (+ Prüfungen installierter Plugins) githubbot-core review-pr --repo owner/repo --pr 12 -# Plugins +# Plugins und Systemagent-Vorlage githubbot-core plugins +githubbot-core --json system-agent githubbot-core run [--plugin-arg ...] [--apply] ``` @@ -86,6 +87,15 @@ Die Python-API ist das Paket selbst: `githubbot_core.triage.IssueTriage`, `githubbot_core.actions.preview/apply`, `githubbot_core.health.RepoHealthCollector`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +### Systemagent-Vorlage + +`githubbot-core --json system-agent` gibt die neutrale, mit dem Paket ausgelieferte Agentenvorlage aus +(Schema `githubbot-core.system-agent/v1`): `version` (Paketversion), `sha256` der kanonischen Vorlage, +`capability`, die wirksamen `permissions` und ein `blueprint` mit den Feldnamen des +Agent-Studio-Blueprint-Payloads (Persona, Rolle, Contractus, Governance, Skills). Die Vorlage +existiert nur dort, wo das Paket installiert ist. Konsumenten binden einen Agenten an `version` + +`sha256` und überschreiben bei einer neueren Vorlage keine bestehende Agentenkonfiguration. + ## Konfiguration und Rechte Die mitgelieferten Standardwerte (`githubbot_core/default_config.json`) werden durch diff --git a/llms.txt b/llms.txt index 97b8bcc..fc0b8b6 100644 --- a/llms.txt +++ b/llms.txt @@ -13,6 +13,8 @@ Languages: en, de - Python API: `githubbot_core.triage`, `githubbot_core.actions`, `githubbot_core.health`, `githubbot_core.traffic`, `githubbot_core.stars`, `githubbot_core.pr_review`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +- System agent template: `githubbot-core --json system-agent` (schema + `githubbot-core.system-agent/v1`, version + sha256, agent-studio blueprint fields). - Plugin SPI v1: entry-point group `githubbot_core.plugins`, factory returning an object with `name`, `spi_version`, `describe()`, optional `plan(ctx)` and `pr_policy_checks(pr, files, diff)`. diff --git a/pyproject.toml b/pyproject.toml index 9ddfb17..10474d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ version = { attr = "githubbot_core.__version__" } where = ["src"] [tool.setuptools.package-data] -githubbot_core = ["default_config.json", "templates/*.yml"] +githubbot_core = ["default_config.json", "system_agent.json", "templates/*.yml"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/githubbot_core/__init__.py b/src/githubbot_core/__init__.py index ddc8116..923bd0c 100644 --- a/src/githubbot_core/__init__.py +++ b/src/githubbot_core/__init__.py @@ -5,4 +5,4 @@ a plugin SPI and signed exact-SHA review receipts. Standard library only. """ -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/src/githubbot_core/cli.py b/src/githubbot_core/cli.py index a2e1438..9413de2 100644 --- a/src/githubbot_core/cli.py +++ b/src/githubbot_core/cli.py @@ -15,6 +15,7 @@ from __future__ import annotations import argparse +import hashlib import json import shutil import sys @@ -114,6 +115,18 @@ def cmd_plugins(args, config, transport) -> int: return EXIT_OK +def cmd_system_agent(args, config, transport) -> int: + template = json.loads(resources.files("githubbot_core").joinpath("system_agent.json").read_text(encoding="utf-8")) + canonical = json.dumps(template, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + data = {"schema": template["_meta"]["schema"], "id": template["id"], "version": __version__, + "sha256": hashlib.sha256(canonical.encode("utf-8")).hexdigest(), + "capability": {"package": "githubbot-core", "version": __version__, "spi_version": plugins.SPI_VERSION}, + "blueprint": template["blueprint"], "permissions": config.get("permissions", {})} + text = f"system agent {data['id']} {data['version']} sha256 {data['sha256']}" + _emit(args, "system-agent", True, "read", data, [], text) + return EXIT_OK + + def cmd_classify(args, config, transport) -> int: classifier = IssueClassifier(config) results = [classifier.classify(issue) for issue in _read_issues(Path(args.input))] @@ -240,6 +253,8 @@ def build_parser() -> argparse.ArgumentParser: sub.add_parser("info", help="version, contract and plugin overview").set_defaults(func=cmd_info) sub.add_parser("doctor", help="check gh CLI, credentials and permissions").set_defaults(func=cmd_doctor) sub.add_parser("plugins", help="list installed plugins").set_defaults(func=cmd_plugins) + sub.add_parser("system-agent", help="versioned system agent template of this package").set_defaults( + func=cmd_system_agent) p = sub.add_parser("classify", help="classify issues from a JSON file (offline)") p.add_argument("--input", required=True) diff --git a/src/githubbot_core/system_agent.json b/src/githubbot_core/system_agent.json new file mode 100644 index 0000000..a297fa7 --- /dev/null +++ b/src/githubbot_core/system_agent.json @@ -0,0 +1,20 @@ +{ + "_meta": { + "schema": "githubbot-core.system-agent/v1", + "description": "Neutral system agent template shipped with this package. It exists only where githubbot-core is installed; read it with `githubbot-core --json system-agent`. `blueprint` uses the field names of the agent-studio blueprint payload. Consumers may change persona, skills, governance and model; a newer template version must not silently overwrite an existing agent configuration." + }, + "id": "githubbot", + "blueprint": { + "name": "githubbot", + "title": "GitHub Maintenance Agent", + "kind": "agent", + "description": "Issue triage, duplicate detection, pull-request summaries and read-only health, traffic and star reports. Every write is a previewed plan that is applied only with explicit approval.", + "persona_role": "github-maintainer", + "persona_prompt": "You are the GitHub maintenance agent of githubbot-core. You triage issues, find duplicates, summarise pull requests and produce read-only health, traffic and star reports for the repositories assigned to you. Every write is first shown as a preview plan; you apply a plan only when a current, explicit approval names its plan_sha256, and you never widen the configured permissions. A review verdict is valid only for the exact head SHA it was made on. If a source cannot be read, report the error instead of treating it as empty.", + "skills": [], + "animus_type": "cli", + "modus": "casualis", + "contractus": {"turns": 15}, + "governance": {"profile": "read_only_research", "tool_whitelist": ["read_files", "search_content"]} + } +} diff --git a/tests/test_consumer_install.py b/tests/test_consumer_install.py index 63babed..46b6506 100644 --- a/tests/test_consumer_install.py +++ b/tests/test_consumer_install.py @@ -44,6 +44,10 @@ def test_installed_package_cli_without_private_extension(tmp_path: Path) -> None envelope = json.loads(info.stdout) assert envelope["ok"] is True and envelope["data"]["plugins"] == [] + agent = _run([str(cli), "--json", "system-agent"], cwd=work) # template ships as package data + assert agent.returncode == 0, agent.stderr + assert json.loads(agent.stdout)["data"]["blueprint"]["name"] == "githubbot" + origin = _run([str(python), "-c", "import githubbot_core, sys; print(githubbot_core.__file__)"], cwd=work) assert "site-packages" in origin.stdout, origin.stdout # not the source tree diff --git a/tests/test_system_agent.py b/tests/test_system_agent.py new file mode 100644 index 0000000..835006f --- /dev/null +++ b/tests/test_system_agent.py @@ -0,0 +1,50 @@ +"""System agent template: versioned, hashed, shaped like the agent-studio blueprint payload.""" +from __future__ import annotations + +import hashlib +import json +import re +from importlib import resources + +from githubbot_core import __version__, cli + +# Rules of the consuming agent-studio save/materialize path (blueprint service). +NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$") +KINDS = {"agent", "role", "skill", "workflow", "service", "contractus"} +ANIMUS = {"api", "cli", "subscription", "local"} +EXECUTABLE_MODUS = {"casualis", "usus"} + + +def _system_agent(capsys, *extra: str) -> dict: + assert cli.main([*extra, "--json", "system-agent"]) == 0 + envelope = json.loads(capsys.readouterr().out) + assert envelope["ok"] is True and envelope["mode"] == "read" + return envelope["data"] + + +def test_template_identity_is_version_plus_canonical_hash(capsys): + data = _system_agent(capsys) + template = json.loads(resources.files("githubbot_core").joinpath("system_agent.json").read_text(encoding="utf-8")) + canonical = json.dumps(template, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + assert data["schema"] == "githubbot-core.system-agent/v1" + assert data["id"] == "githubbot" and data["version"] == __version__ + assert data["sha256"] == hashlib.sha256(canonical.encode("utf-8")).hexdigest() + assert data["capability"] == {"package": "githubbot-core", "version": __version__, "spi_version": 1} + + +def test_blueprint_is_accepted_by_the_agent_studio_payload_rules(capsys): + bp = _system_agent(capsys)["blueprint"] + assert NAME.fullmatch(bp["name"]) and bp["name"] == bp["name"].lower() + assert bp["kind"] in KINDS and bp["kind"] == "agent" + assert bp["persona_prompt"].strip() and len(bp["title"]) <= 120 + assert bp["animus_type"] in ANIMUS and bp["modus"] in EXECUTABLE_MODUS + assert isinstance(bp["skills"], list) and all(NAME.fullmatch(s) for s in bp["skills"]) + assert isinstance(bp["contractus"], dict) and isinstance(bp["governance"], dict) + assert "is_template" not in bp # system templates are registered by the consumer, never self-declared + + +def test_permissions_are_the_effective_narrowed_configuration(capsys, tmp_path): + layer = tmp_path / "narrow.json" + layer.write_text(json.dumps({"permissions": {"enabled": []}}), encoding="utf-8") + assert _system_agent(capsys)["permissions"]["enabled"] == ["issue.add_labels", "issue.comment"] + assert _system_agent(capsys, "--config", str(layer))["permissions"]["enabled"] == []